Import kdiff3_1.7.90.orig.tar.gz
authorEike Sauer <eike@debian.org>
Tue, 8 Jan 2019 10:17:00 +0000 (10:17 +0000)
committerEike Sauer <eike@debian.org>
Tue, 8 Jan 2019 10:17:00 +0000 (10:17 +0000)
[dgit import orig kdiff3_1.7.90.orig.tar.gz]

168 files changed:
.arcconfig [new file with mode: 0644]
.clang-format [new file with mode: 0644]
.clang-tidy [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.gitlab-ci.yml [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
INSTALL [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
diff_ext_for_kdiff3/LICENSE [new file with mode: 0644]
diff_ext_for_kdiff3/Makefile [new file with mode: 0644]
diff_ext_for_kdiff3/Makefile_64bit [new file with mode: 0644]
diff_ext_for_kdiff3/Messages.sh [new file with mode: 0644]
diff_ext_for_kdiff3/README [new file with mode: 0644]
diff_ext_for_kdiff3/class_factory.cpp [new file with mode: 0644]
diff_ext_for_kdiff3/class_factory.h [new file with mode: 0644]
diff_ext_for_kdiff3/diff_ext.cpp [new file with mode: 0644]
diff_ext_for_kdiff3/diff_ext.h [new file with mode: 0644]
diff_ext_for_kdiff3/diff_ext_for_kdiff3.def [new file with mode: 0644]
diff_ext_for_kdiff3/diff_ext_for_kdiff3.rc [new file with mode: 0644]
diff_ext_for_kdiff3/diff_ext_for_kdiff3.vcproj [new file with mode: 0644]
diff_ext_for_kdiff3/diff_ext_for_kdiff3_msvc.def [new file with mode: 0644]
diff_ext_for_kdiff3/diffextstring.h [new file with mode: 0644]
diff_ext_for_kdiff3/server.cpp [new file with mode: 0644]
diff_ext_for_kdiff3/server.h [new file with mode: 0644]
doc/CMakeLists.txt [new file with mode: 0644]
doc/en/CMakeLists.txt [new file with mode: 0644]
doc/en/dirbrowser.png [new file with mode: 0644]
doc/en/dirmergebig.png [new file with mode: 0644]
doc/en/index.docbook [new file with mode: 0644]
doc/en/iteminfo.png [new file with mode: 0644]
doc/en/letter_by_letter.png [new file with mode: 0644]
doc/en/man-kdiff3.1.docbook [new file with mode: 0644]
doc/en/merge_current.png [new file with mode: 0644]
doc/en/new.png [new file with mode: 0644]
doc/en/open_dialog.png [new file with mode: 0644]
doc/en/screenshot_diff.png [new file with mode: 0644]
doc/en/screenshot_merge.png [new file with mode: 0644]
doc/en/triple_diff.png [new file with mode: 0644]
doc/en/white_space.png [new file with mode: 0644]
kdiff3fileitemactionplugin/CMakeLists.txt [new file with mode: 0644]
kdiff3fileitemactionplugin/Messages.sh [new file with mode: 0644]
kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp [new file with mode: 0644]
kdiff3fileitemactionplugin/kdiff3fileitemaction.h [new file with mode: 0644]
kdiff3fileitemactionplugin/kdiff3fileitemaction.json [new file with mode: 0644]
src/CMakeLists.txt [new file with mode: 0644]
src/DirectoryInfo.h [new file with mode: 0644]
src/MergeFileInfos.cpp [new file with mode: 0644]
src/MergeFileInfos.h [new file with mode: 0644]
src/Messages.sh [new file with mode: 0644]
src/OptionItems.h [new file with mode: 0644]
src/PixMapUtils.cpp [new file with mode: 0644]
src/PixMapUtils.h [new file with mode: 0644]
src/ProgressProxyExtender.cpp [new file with mode: 0644]
src/ProgressProxyExtender.h [new file with mode: 0644]
src/Utils.cpp [new file with mode: 0644]
src/Utils.h [new file with mode: 0644]
src/common.cpp [new file with mode: 0644]
src/common.h [new file with mode: 0644]
src/cvsignorelist.cpp [new file with mode: 0644]
src/cvsignorelist.h [new file with mode: 0644]
src/diff.cpp [new file with mode: 0644]
src/diff.h [new file with mode: 0644]
src/difftextwindow.cpp [new file with mode: 0644]
src/difftextwindow.h [new file with mode: 0644]
src/directorymergewindow.cpp [new file with mode: 0644]
src/directorymergewindow.h [new file with mode: 0644]
src/fileaccess.cpp [new file with mode: 0644]
src/fileaccess.h [new file with mode: 0644]
src/gnudiff_analyze.cpp [new file with mode: 0644]
src/gnudiff_diff.h [new file with mode: 0644]
src/gnudiff_io.cpp [new file with mode: 0644]
src/gnudiff_system.h [new file with mode: 0644]
src/gnudiff_xmalloc.cpp [new file with mode: 0644]
src/guiutils.h [new file with mode: 0644]
src/icons/128-apps-kdiff3.png [new file with mode: 0644]
src/icons/16-apps-kdiff3.png [new file with mode: 0644]
src/icons/22-apps-kdiff3.png [new file with mode: 0644]
src/icons/256-apps-kdiff3.png [new file with mode: 0644]
src/icons/32-apps-kdiff3.png [new file with mode: 0644]
src/icons/48-apps-kdiff3.png [new file with mode: 0644]
src/icons/64-apps-kdiff3.png [new file with mode: 0644]
src/icons/CMakeLists.txt [new file with mode: 0644]
src/icons/sc-apps-kdiff3.svgz [new file with mode: 0644]
src/kdiff3.cpp [new file with mode: 0644]
src/kdiff3.h [new file with mode: 0644]
src/kdiff3.ico [new file with mode: 0644]
src/kdiff3_part.cpp [new file with mode: 0644]
src/kdiff3_part.h [new file with mode: 0644]
src/kdiff3_part.rc [new file with mode: 0644]
src/kdiff3_shell.cpp [new file with mode: 0644]
src/kdiff3_shell.h [new file with mode: 0644]
src/kdiff3_shell.rc [new file with mode: 0644]
src/kdiff3part.desktop [new file with mode: 0644]
src/kdiff3win.rc [new file with mode: 0644]
src/main.cpp [new file with mode: 0644]
src/merger.cpp [new file with mode: 0644]
src/merger.h [new file with mode: 0644]
src/mergeresultwindow.cpp [new file with mode: 0644]
src/mergeresultwindow.h [new file with mode: 0644]
src/optiondialog.cpp [new file with mode: 0644]
src/optiondialog.h [new file with mode: 0644]
src/options.h [new file with mode: 0644]
src/org.kde.kdiff3.appdata.xml [new file with mode: 0644]
src/org.kde.kdiff3.desktop [new file with mode: 0644]
src/pdiff.cpp [new file with mode: 0644]
src/progress.cpp [new file with mode: 0644]
src/progress.h [new file with mode: 0644]
src/selection.cpp [new file with mode: 0644]
src/selection.h [new file with mode: 0644]
src/smalldialogs.cpp [new file with mode: 0644]
src/smalldialogs.h [new file with mode: 0644]
src/xpm/autoadvance.xpm [new file with mode: 0644]
src/xpm/currentpos.xpm [new file with mode: 0644]
src/xpm/down1arrow.xpm [new file with mode: 0644]
src/xpm/down2arrow.xpm [new file with mode: 0644]
src/xpm/downend.xpm [new file with mode: 0644]
src/xpm/file.xpm [new file with mode: 0644]
src/xpm/filenew.xpm [new file with mode: 0644]
src/xpm/fileopen.xpm [new file with mode: 0644]
src/xpm/fileprint.xpm [new file with mode: 0644]
src/xpm/filesave.xpm [new file with mode: 0644]
src/xpm/folder.xpm [new file with mode: 0644]
src/xpm/iconA.xpm [new file with mode: 0644]
src/xpm/iconB.xpm [new file with mode: 0644]
src/xpm/iconC.xpm [new file with mode: 0644]
src/xpm/link_arrow.xpm [new file with mode: 0644]
src/xpm/nextunsolved.xpm [new file with mode: 0644]
src/xpm/prevunsolved.xpm [new file with mode: 0644]
src/xpm/reload.xpm [new file with mode: 0644]
src/xpm/showequalfiles.xpm [new file with mode: 0644]
src/xpm/showfilesonlyina.xpm [new file with mode: 0644]
src/xpm/showfilesonlyinb.xpm [new file with mode: 0644]
src/xpm/showfilesonlyinc.xpm [new file with mode: 0644]
src/xpm/showlinenumbers.xpm [new file with mode: 0644]
src/xpm/showwhitespace.xpm [new file with mode: 0644]
src/xpm/showwhitespacechars.xpm [new file with mode: 0644]
src/xpm/startmerge.xpm [new file with mode: 0644]
src/xpm/up1arrow.xpm [new file with mode: 0644]
src/xpm/up2arrow.xpm [new file with mode: 0644]
src/xpm/upend.xpm [new file with mode: 0644]
test/alignmenttest.cpp [new file with mode: 0644]
test/fakefileaccess.cpp [new file with mode: 0644]
test/fakekdiff3_part.cpp [new file with mode: 0644]
test/fakeprogressproxy.cpp [new file with mode: 0644]
test/generate_testdata_from_git_merges.py [new file with mode: 0755]
test/generate_testdata_from_permutations.py [new file with mode: 0755]
test/testdata/1_simpletest_base.txt [new file with mode: 0644]
test/testdata/1_simpletest_contrib1.txt [new file with mode: 0644]
test/testdata/1_simpletest_contrib2.txt [new file with mode: 0644]
test/testdata/1_simpletest_expected_result.txt [new file with mode: 0644]
test/testdata/2_prefer_identical_to_space_differences_base.txt [new file with mode: 0644]
test/testdata/2_prefer_identical_to_space_differences_contrib1.txt [new file with mode: 0644]
test/testdata/2_prefer_identical_to_space_differences_contrib2.txt [new file with mode: 0644]
test/testdata/2_prefer_identical_to_space_differences_expected_result.txt [new file with mode: 0644]
test/testdata/README [new file with mode: 0644]
windows_installer/COPYING.txt [new file with mode: 0644]
windows_installer/DIFF-EXT-LICENSE.txt [new file with mode: 0644]
windows_installer/Kdiff3-64bit.nsi [new file with mode: 0644]
windows_installer/README [new file with mode: 0644]
windows_installer/README_WIN.txt [new file with mode: 0644]
windows_installer/diff3_cmd.bat [new file with mode: 0644]
windows_installer/installForAllUsersPage.ini [new file with mode: 0644]
windows_installer/kdiff3.bmp [new file with mode: 0644]
windows_installer/kdiff3.nsi [new file with mode: 0644]

diff --git a/.arcconfig b/.arcconfig
new file mode 100644 (file)
index 0000000..377c7ec
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "phabricator.uri" : "https://phabricator.kde.org/"
+}
diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..4013bfc
--- /dev/null
@@ -0,0 +1,32 @@
+---
+Language: Cpp
+BasedOnStyle: LLVM
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: Never
+SpaceInEmptyParentheses: false
+SpacesInCStyleCastParentheses: false
+IndentCaseLabels: true
+IndentWidth: 4
+TabWidth: 4
+UseTab: Never
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortBlocksOnASingleLine: true
+AllowShortIfStatementsOnASingleLine: true
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+ColumnLimit: 0
+DerivePointerAlignment: true
+PointerAlignment: Left
+BreakBeforeBraces: Custom
+BraceWrapping:
+  AfterClass:      true
+  AfterControlStatement: true
+  AfterEnum:       true
+  AfterFunction:   true
+  AfterNamespace:  false
+  AfterStruct:     false
+  AfterUnion:      false
+  BeforeCatch:     true
+  BeforeElse:      true
+  IndentBraces:    false
+...
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644 (file)
index 0000000..548479d
--- /dev/null
@@ -0,0 +1,28 @@
+---
+Checks:          '-clang-analyzer-optin.cplusplus.VirtualCall,-clang-analyzer-optin.performance.Padding'
+WarningsAsErrors: ''
+HeaderFilterRegex: '.*'
+AnalyzeTemporaryDtors: false
+CheckOptions:    
+  - key:             google-readability-braces-around-statements.ShortStatementLines
+    value:           '1'
+  - key:             google-readability-function-size.StatementThreshold
+    value:           '800'
+  - key:             google-readability-namespace-comments.ShortNamespaceLines
+    value:           '10'
+  - key:             google-readability-namespace-comments.SpacesBeforeComments
+    value:           '2'
+  - key:             modernize-loop-convert.MaxCopySize
+    value:           '16'
+  - key:             modernize-loop-convert.MinConfidence
+    value:           reasonable
+  - key:             modernize-loop-convert.NamingStyle
+    value:           CamelCase
+  - key:             modernize-pass-by-value.IncludeStyle
+    value:           llvm
+  - key:             modernize-replace-auto-ptr.IncludeStyle
+    value:           llvm
+  - key:             modernize-use-nullptr.NullMacros
+    value:           'NULL'
+...
+
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f0b2112
--- /dev/null
@@ -0,0 +1,31 @@
+# general
+apidocs
+.directory
+.kdev4
+build
+*~
+*.patch
+*.diff
+*.bak
+*.orig
+*.pyc
+*.rej
+*.swp
+doxygen.log
+Doxyfile
+*.kdevelop
+*.kdevelop.filelist
+*.kdevelop.pcs
+*.kdevses
+.*kate-swp
+build/
+mem.log.*
+massif.*
+callgrind.*
+perf.data*
+
+# from kdiff3
+*.BACKUP.*
+*.BASE.*
+*.LOCAL.*
+*.REMOTE.*
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644 (file)
index 0000000..5e58d8e
--- /dev/null
@@ -0,0 +1,25 @@
+image: reporter123/cmake:bionic
+
+build:
+  stage: build
+  
+  before_script: 
+     - apt-get update && apt-get install -y extra-cmake-modules gettext qtbase5-dev extra-cmake-modules libkf5i18n-dev libkf5coreaddons-dev libkf5iconthemes-dev libkf5parts-dev libkf5doctools-dev libkf5crash-dev 
+  script: 
+    - cmake .
+    - make
+    - make install
+    
+  #artifacts:
+  #  paths:
+  #    - mybinary
+  # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
+  # cache:
+  #   paths:
+  #     - "*.o"
+
+# run tests using the binary built before
+#test:
+#  stage: test
+#  script:
+#    - ./runmytests.sh
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..e4781f1
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Joachim Eibl <joachim.eibl@gmx.de>
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..002883c
--- /dev/null
@@ -0,0 +1,102 @@
+#cmake < 3.1 has no sane way of checking C++11 features and needed flags
+cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
+
+project(kdiff3)
+
+set(CMAKE_CXX_EXTENSIONS OFF ) #don't use non-standard extensions
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+set(ECM_MIN_VERSION "5.10.0")
+set(QT_MIN_VERSION "5.6.0")
+set(KF5_MIN_VERSION "5.23.0")
+
+find_package(ECM ${ECM_MIN_VERSION} CONFIG REQUIRED)
+set(
+    CMAKE_MODULE_PATH
+    ${CMAKE_MODULE_PATH}
+    ${ECM_MODULE_PATH}
+    ${ECM_KDE_MODULE_DIR}
+)
+
+include(KDEInstallDirs)
+include(KDECompilerSettings NO_POLICY_SCOPE)
+include(KDECMakeSettings NO_POLICY_SCOPE)
+include(FeatureSummary)
+
+include(ECMInstallIcons)
+include(ECMAddAppIcon)
+include(ECMSetupVersion)
+
+ecm_setup_version(1.7.90 VARIABLE_PREFIX KDIFF3 VERSION_HEADER ${CMAKE_BINARY_DIR}/src/version.h)
+
+find_package(
+    Qt5 ${QT_MIN_VERSION}
+    CONFIG
+    REQUIRED
+    COMPONENTS
+    Core
+    Gui
+    Widgets
+    PrintSupport
+)
+
+find_package(
+    KF5 ${KF5_MIN_VERSION}
+    REQUIRED
+    COMPONENTS
+    I18n
+    CoreAddons
+    Crash
+    DocTools
+    IconThemes
+)
+
+
+set(KDiff3_LIBRARIES ${Qt5PrintSupport_LIBRARIES} KF5::I18n KF5::CoreAddons KF5::Crash KF5::IconThemes )
+
+if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    #Adjust clang specific  warnings
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wshadow")
+    set(CLANG_WARNING_FLAGS "-Wno-invalid-pp-token -Wno-comment -Wshorten-64-to-32 -Wstring-conversion -Wc++11-narrowing")
+    set(CMAKE_CXX_FLAGS  "${CMAKE_CXX_FLAGS} ${CLANG_WARNING_FLAGS}")
+elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
+    add_definitions(-DNOMINMAX) #Suppress MSVCs min/max macros
+elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wduplicated-cond -Wduplicated-branches -Wshadow")
+endif()
+
+#new in cmake 3.6+ integrate clang-tidy
+if(NOT ${CMAKE_VERSION} VERSION_LESS "3.6.0")
+    find_program(CLANG_TIDY_EXE NAMES "clang-tidy" "clang-tidy-7" "clang-tidy-6.0" "clang-tidy-6" DOC "Path to clang-tidy executable")
+    if(NOT CLANG_TIDY_EXE)
+        message(STATUS "clang-tidy not found disabling integration.")
+    else()
+        message(STATUS "Found clang-tidy: ${CLANG_TIDY_EXE}")
+        set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE}" "-header-filter=.*")
+    endif()
+endif()
+
+set(CMAKE_C_FLAGS  "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS}")
+
+set(
+    needed_features
+    cxx_nullptr
+    cxx_override
+    cxx_nonstatic_member_init
+    cxx_inheriting_constructors
+)
+
+add_definitions(
+    -DQT_DEPRECATED_WARNINGS #Get warnings from QT about deprecated functions.
+    -DQT_NO_URL_CAST_FROM_STRING #implict casting from string to url does not always behave as you might think
+    -DQT_RESTRICTED_CAST_FROM_ASCII #casting from char*/QByteArray to QString can produce unexpected results for non-latin characters.
+)
+
+add_subdirectory(src)
+add_subdirectory(doc)
+
+add_subdirectory(kdiff3fileitemactionplugin)
+
+ki18n_install(po)
+kdoctools_install(po)
+
+feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..a3e6345
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+               51 Franklin Street, Fifth Floor, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+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.
+\f
+  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.
+\f
+  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
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..25c235d
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,574 @@
+Version 1.7.90 - 2017-01-23
+============================
+-ported to QT5/KDEFramework5
+-depreciated cmake 2.8.12 support in master
+-abandonded Konqueror specific plugin no longer applicable.
+-kde4 support dropped due to extensive changes needed for QT5/KDEFramework5
+  -QT5 now required because of above changes.
+-bugfix: Show Indentical files setting not applied after rescan.
+-Change parameter errors to use dialog on Linux as well as console output.
+-Don't sort twice when sorting in reverse order.
+-fix memmory leak introduced in 0.9.91.
+-change version numbering.
+-scrap ClearCase support
+
+Version 0.9.98 - 2014-07-04
+===========================
+- Text rendering now with QTextLayout fixes the following issues
+  - Correct handling for variable width fonts.
+  - Corrected display of highlighted text with Qt4.8.x on Ubuntu and Mac.
+  - Improved handling of texts with both right to left and left to right languages (mixed Arabic and western texts).
+  - Improved handling of Chinese and Japanese.
+  - Whitespace characters are now shown as dots for spaces and arrows for tabs, and not only in differences.
+  - Fixed symlink comparison (Qt4 symLinkTarget returns absolute paths) 
+- Text analysis for rendering with QTextLayout is interruptable and multithreaded. (See progressbar and abort-button in statusbar)
+- Fix for saving to relative path in KDE-environments. (Patch from Harald Sitter)
+- Fixed bug in 0.9.97: Directory compare was always case sensitive.
+- Fix for saving files on KDE with relative path specified via command line option -o.
+- Fixed problem with KIO (nonlocal urls).
+- Improved Mac support.
+- Write --confighelp information to stdout instead of stderr.
+- Directory Merge Window: Enabled state of "Delete A And B" now also depends on existence of source file A.
+- Works now with Qt4 and Qt5
+- Progress dialog during printing.
+- Workaround for bug in QSplitter::childEvent that broke QFileDialog::getSaveFileName
+
+Version 0.9.97 - 2012-08-10
+===========================
+- Memory usage optimized for comparison of large directories. (ca. 1/5 needed)
+- On Windows use config file .kdiff3rc next to kdiff3.exe if exists.
+- In overwiev for two way diff show if only one side contains text.
+- Fix for Fedora by Neal Becker in src-QT4/kdiff3part.desktop.
+- When word wrap is active toggling line numbers now recalculates the word wrap.
+- Removed confusing "For compatibility ..." hint from option -qall
+- Fixed mouse wheel problem. (Patch by David Hay)
+- Change an encoding in diff text window via click on encoding label. (Patch by Alexey Kostromin)
+- Fix for tab-key moving focus instead of adding a tab character in MergeResultWindow.
+- Workaround for git on Cygwin that allows KDiff3 to find files in a Cygwin "/tmp" directory 
+  when environment variable "CYGWIN_BIN" is set. (Patch by Nigel Stewart)
+- Removed iostream dependency (Patch by Nigel Stewart)
+- Regression test framework (by Maurice van der Pot)
+- Documentation patch (by Burkard Lueck)
+- Select text in Find dialog (by Eike Sauer)
+- If text is selected in either input or output window use that in Find dialog.
+- Added default directory anti patterns ".hg" and ".git" and file anti patterns ".rej" and ".bak" for better mercurial and git integration.
+- Command line option --cs doesn't change the config value permanently anymore.
+- On KDE: Not creating a KDiff3Part anymore.
+- Windows (Vista or Win 7): Shell context menu in directory comparison view now again displays text.
+- Windows 64 bit: Diff-Ext-for-KDiff3 context menu now installed as 32 bit and 64 bit versions to allow other 32 bit programs to use it too.
+- Windows 64 bit specific installer.
+
+Version 0.9.96 - 2011-09-02
+===========================
+- KDiff3FileItemActionPlugin : Context menu that also works in dolphin (for KDE>=4.6)
+- Parser for preprocessor commands. (Allows single apostrophs ')
+- On Windows if the preprocessor command is "sed" try to use a "bin\sed.exe" next to the kdiff3.exe, if available.
+- Warn if conversion errors appear (Invalid characters)
+- Fix crash on A/B-overview (infinite recursion)
+- Fix clearcase temp files not deleted problem on windows
+- KDiff3 plugin: When launching KDiff3 konqueror isn't blocked anymore
+- String corrections (Frederik Schwarzer)
+- Fixed writing to KIO.
+- OS2-Port (Patch by Silvan Scherrer)
+- Fixed problem where destination directory would be renamed or deleted during copy operation. 
+  Now if the destination directory exists only the files inside will be copied.
+- In merge: Separate lines where the automatic choice would be the same but for different reasons.
+- Fixed some problems with huge files in directory comparison mode (>2GB) (but direct comparison is still not possible)
+- Fixed documentation compile errors with KDE>=4.5
+- Fixed white space merge default options
+- Fixed regexp test tool.
+- Exclude printing code if Qt was compiled without printing support.
+- For Windows: Fixed handling of unicode characters in command line parameters.
+- Improved "old mac" lineendstyle handling: Break lines.
+- Detect encoding specified in xml header or html "meta" tag. 
+
+Version 0.9.95 - 2009/03/03
+===========================
+- Show line end style for each file.
+- Updated message translations.
+- Fixed permissions when writing executable file. (Un*x only)
+- Fixed IgnorableCmdLineOptions (important for SVNs '-u'-option)
+- Directory merge: Error when either B or C is changed and the other is deleted. (User choice required.)
+- Qt-only Un*x-version looks for translations in /usr/share/locale/<lang>/LC_MESSAGES/kdiff3.qm
+  (for Debian, by Eike Sauer)
+- New script: po/create_qm_files: To create and install translations for Qt-only version.
+
+Version 0.9.94 - 2009/01/17
+===========================
+- Fix for hidden text windows with --auto-flag.
+- Fix for pasting clipboard truncated text if it contained characters that needed more than one byte in UTF-8 encoding.
+- Fix for horizontal scrolling if word wrap is enabled.
+- Directory tree: files hidden due to options (e.g. patterns etc.) don't affect folder equality any more.
+- KDE: KIO-progress dialog is now hidden. (KDiff3 has its own progress dialog.)
+- Directory merge: Default op for change in either B or C and delete in the other is now merge (previously copy).
+- Directory merge: Not preserving merge operation after reload, because it might have changed.
+
+Version 0.9.93 - 2009/01/06
+===========================
+- Support for KDE4 (with much porting help from Valentin Rusu)
+- Fix for diff_ext_for_kdiff3 (by Sergey Zorin)
+- Win32-Installation: SendTo-integration fixed for Vista.
+- Optional auto detection of line end style for saving.
+- Option to close on ESC (default is off)
+- Option to align B and C for 3 input files (default is off which is usually better for merging).
+
+Version 0.9.92 - 2007/04/15
+===========================
+- Windows installer now allows you to install KDiff3 as Clearcase Diff and Merge Tool
+- Windows installer "SVN Merge tool" corrected: Not creating $AppData\Subversion\config subdir anymore.
+- KDE-Konqueror plugin: Launch KDiff3 from Konqueror. (Similar to Diff-Ext on Windows.)
+- Qt4-version
+  - Printing crash fixed
+  - Compilation issue for Mac fixed
+- Dir Rescan keeps settings for Show identical files etc.
+- Bugfix: Empty file and not existing file were detected as binary equal.
+- Temp file names use the process id in file name to allow several instances.
+- Suppress flicker during startup. (Don't show status info window on creation.)
+- New File comparison mode: Trust the size and date, but use binary comparison if date doesn't match (unsafe)
+- After explicitly selecting files any file of the selected may be right clicked for context menu.
+- Open dialog also shows current directories in directory comparison mode.
+- Writing a file via --auto option didn't work for relative files. (Reported by Guilhem Bichot)
+- New option for history merge: Max number of history entries
+- New option "Auto save and quit on merge without conflicts"
+- Directory Merge with Case sensitivity disabled: Correct destination filename chosen for merge.
+
+Version 0.9.91 - 2006/10/29
+===========================
+- Encoding auto detection
+- Fix for crash after double click below last line
+- Saving of maximized window-state (Patch by Robert Vock)
+- Separated Merge-options in own tab because "Diff and Merge"-options tab got too big.
+- When pasting multiple lines into openfile dialog only first line is kept
+- Drawing in directory view fixed.
+- When specifying a separate output dir then for equal files a copy operation will also be offered.
+- Windows specific:
+  - Windows installer problems fixed for users without admin-rights
+  - Fix for slow startup problem on Windows (Patch by Manfred Koehler)
+  - New: diff-ext-for-kdiff3 - Shell extension (originally by Sergey Zorin)
+- Qt4-version:
+  - Saving of merge-result didn't work.
+  - Start external processes directly without cmd.exe-window
+  - Rewrote everything requiring Qt3-support
+
+Version 0.9.90 - 2006/05/14
+===========================
+- Fixed KIO-problems of type "File exists" with tempfiles (introduced in 0.9.89)
+- Fix for manual alignment with 3 files which caused crash (new feature in 0.9.89)
+- Fix for Alt-Left caused crash for leftmost window on windows (due to changed compiler)
+- Use of WResizeNoErase|WRepaintNoErase instead of WNoAutoErase (fix for compiler error with Qt3.1)
+- Removed #include <konq_popupmenu.h> which is (currently) unneeded and required extra dependencies.
+- Removed "Save/Load Directory Merge State ..." in directory menu. (These aren't working yet.)
+- Fixed crash when used as Diff-part with KDevelop.
+- Preserve executable bit when overwriting an existing file.
+
+Version 0.9.89 - 2006/04/09
+===========================
+New features:
+- Version control history auto merge plus sorting
+- Auto merge regular expression
+- Splitting and joining differences for merging
+- Manual Diff Alignment tool
+- Printing of differences
+- Colorsettings for Dir-Colors
+- Dir-show identical/different/A-only/B-only/C-only files with immediate effect (instead of option "List only deltas")
+- Filename-edit above DiffInputWindows
+- Windows-Context Menu in A/B/C-columns for dir-comparison (Windows only)
+- Edit Menu: Select All (Ctrl-A)
+- New commandline options: 
+  --config filename: Select an individual config file. (Now also available for Windows and Qt-only version.)
+  --cs config: Change one specific setting via the command line. (For settings that were previously adjustable via GUI only.)
+  --confighelp: Show available config items and their current values.
+- Dircomp: "Compare/Merge explicitly selected files" (Select files/dirs by clicking icons in columns A/B/C)
+- User definable ignored command line options.
+- Ability to swap pathnames in open dialog
+- "Ignore"-button in error dialog when option not understood (Windows only)
+- Quadratical scroll speedup during selection when mouse moves out of the diff input window.
+Bugfixes, redesign:
+- Preparations for Qt4-Port + some redesign
+- GNU-Diff algorithm improved to be independent of line endings (needed for manual diff alignment)
+- Avoid restoring a window where it is almost invisible (if moved almost out of the screen area)
+- Go to next delta honors special "A vs. B", "A vs. C" or "B vs. C" overview when active. (Patch by Vladan Bato)
+- DirectoryMergeWindow: File/Antifile and DirPattern changes will update immediately without rescan.
+- Blue toolbar icons (for better visibility of disabled state)
+- Bugfix: Crash when merging and selecting "Choose A/B/C for all unsolved conflicts"
+  and one of the solved conflicts contained no lines in chosen input.
+- Fix: With --auto option, GUI stays invisible if not necessary
+- Fixed odd ProgressDialog-behaviour when continuing after an error or abort.
+- Directory merge: Fixed FollowFileLinks. (Didn't work when copying a file.)
+- Initial position now (x=0,y=22). This solves a problem on some Macs.
+- Better alignment of B and C in 3-file comparison
+- Correctly updating the selection when scrolling via keys and mouse is pressed
+- Horizontal scrolling in right-to-left language caused vertical lines - fixed.
+
+
+Version 0.9.88 - 2005/25/02
+===========================
+- Fixed crash that occurred in Directory Comparison mode "Full-Analysis".
+- Fix for Windows: Didn't save encoding correctly.
+- Many translations updated.
+
+Version 0.9.87 - 2005/30/01
+===========================
+- Unicode16 and UTF8 support (Internal data format is now QString (Unicode16). Conversion during save and load.)
+- Directory "Full Analysis": Equality-Coloring for files with only whitespace differences. (Michael Denio)
+- Support for right to left languages.
+- In MergeResultWindow show "<Merge Conflict (Whitespace only)>" for whitespace-only conflicts
+- Statusbar shows the number of remaining conflicts and whitespace conflicts.
+- Go Next/Prev Difference/Conflict now have improved tooltips informing about "Show White Space"-disabled-behaviour.
+
+Version 0.9.86 - 2004/06/14
+===========================
+- Double click on any file in directory merge would close the directory merge window. (Regression in 0.9.85)
+
+Version 0.9.85 - 2004/06/14
+===========================
+- When solving a conflict KDiff3 reports the number of remaining unsolved conflicts in the status bar.
+Bugfixes:
+- Fix for MergeResultWindow-contextmenu: All items were disabled always. (new in 0.9.84)
+- Fix for problem when opening files specified relative to current directory. (new in 0.9.84, qt-only-version)
+- Fix for compilation with older gcc (2.9x)
+- Several Word-wrap problems fixed: 
+     - Find string with word wrap active didn't work if found text was not in first wrap-line.
+     - overview-position was not updated when toggling word wrap
+     - horizontal scrollbar was not updated when toggling word wrap
+     - current selection was lost when toggling word wrap
+     - selecting a conflict in the diff-text-window didn't work right with word wrap.
+- Qt-only: Bold attribute for fonts was not persistent
+- Qt-only: Toolbar position was not persistent
+- Qt-only: Language-choice shows also the full language name.
+- Cursor and windows-boundary-lines were always black instead of having the foreground color
+- Starting KDiff3 with two not existing files showed a dialog saying that files are binary equal.
+- Errors while starting a directory comparison now also reopens the open-dialog.
+- Speedup during directory comparison by avoiding unnecessary redraws. (These always creep in again :-()
+- On KDE: When resetting to default options (or first start) now the default KDE-fixed font will be used.
+- Mergeresultwindow: Improved behaviour after automatic merge operation.
+
+Version 0.9.84 - 2004/05/29
+============================
+New Features:
+- Word Wrap for DiffTextWindow
+- Directory-Comparison: Option "Full Analysis" allows you to show the number of solved vs. unsolved 
+  conflicts or deltas vs. whitespace-changes in the directory tree.
+- Diff-Menu for Diff-view specific entries
+- Docs now contain a new chapter for uses of preprocessor and line-matching-preprocessor. 
+- Added several credits which now are also visible in the Qt-only version.
+- The Qt-only version now also shows all command-line options. Under windows a dialog shows them.
+- Command line options -u and -L for Subversion-support.
+- Command line options --L1/2/3 for specifying alias names.
+- In the Qt-only-version the user-interface-language can be set via the regional-settings 
+  (only effective after a restart).
+- ProgressDialog redesign for recursive use.
+- Overview now allows you to show the delta between two other files in triplediff-mode.
+- Option to ignore case which treats case-changes like white space (instead of conversion to upcase).
+Bugfixes:
+- Dir-Comp: When one file exists, but the other doesn't then instead the latest used other file was displayed.
+- Open dialog: When previously a file C was used, but should be empty now, it reappeared unbidden.
+- Several bugs for 64-bit systems fixed.
+- Fixed crash when one file ended with a newline and the other did not.
+- Windows: Case insensitive filename-pattern matching.
+- Corrected behaviour for files with size 0.
+- Fix for crash due to a race-condition (Patch by Eike Sauer)
+- Windows: Scrolling didn't work right when another window was in front.
+- Mergeresultwindow didn't show correct position when starting a second or later merge.
+- Fix for problem where sometimes the A/B/C-buttons were in wrong state.
+- Pasting from selection via the middle mousebutton.
+
+Version 0.9.83 - 2004/03/06
+===========================
+- Reading directorys fixed for Win95/98
+- Caseinsensitive filename matching for windows.
+- Autocopy to selection for systems that support this. (Patch by Stefan Partheymueller)
+- Drawing during recalc suppressed in merge result editor.
+- Cursor could go beyond last line in merge result editor. (Corrected NrOfLine-counting.)
+- Windows: Start with invalid cmd-line-options brings up a messagebox with the list of options.
+- Corrected encoding when copying to or pasting from clipboard.
+- Corrected char-by-char-diff at beginning of line. ("012345 12345 xyz" <-> "012345 xyz")
+- Warning when merging with preprocessor or upcase-conversion enabled.
+- Rewrite of preprocessing code should fix several problems. E.g.:
+  - Ignore C/C++-comments only worked with a preprocessor active.
+  - Preprocessor output now is input of line-matching preprocessor.
+  - Paste to diff-window, didn't work if LMPP or Ignore C/C++-Comments was set.
+
+Version 0.9.82 - 2004/02/02
+===========================
+- DirectoryMerge: Running merge op for last item in a folder, performed the
+  merge op for all following items in following folders. (Possible data loss!)
+- Fix: Preprocessors and "Ignore Comments" didn't work at the same time.
+- Fix: Preprocessors crashed with remote files.
+- Open-Dialog: When either input is changed, then reset the output to be empty.
+  (To avoid accidental overwrites.)
+- Icon for "Highlight white space differences."
+- Editor-Option: Line End Style for saving: Dos/Windows "\r\n" vs. Unix "\n"
+- Merge output editor: Corrected wrong encoding for output-filename and 
+  user-typed characters.
+- Speedup for reading directories under Windows.
+- Enhanced progress dialog responsiveness during local file copy.
+- Fix for non-KDE-version: No URL-encoding when dropping files in open dialog.
+
+Version 0.9.81 - 2004/01/08
+===========================
+- Allow to compile with --enable-final
+- Bugfix for 3 file-compare (and A or B don't exist, crashed)
+- Bugfix for crash when second directory is merged
+- Some keyboard-shortcuts for selection of merge-operation didn't work correctly.
+- Shortcuts Ctrl-1/2/3 are possible in textmergewindow and in dirmergewindow, 
+  depending on the focus.
+- First steps towards internationalisation
+- Manpage doc/en/kdiff3.1 by Eike Sauer (for Debian)
+- Directory rescan shortcut SHIFT-F5
+
+Version 0.9.80 - 2003/12/08
+===========================
+New Text Diff/Merge Features:
+- Now using GNU-diff algorithms internally. (Option "External Diff" removed.)
+- Option for treating C/C++ comments as whitespace during diff.
+- Bugfix for locale character encoding (+ new option "Use string encoding")
+- Option for suppressing highlighting in white-space changes.
+  (Also suppresses highlighting in comments and numbers when the
+  respective options are active.)
+- Merge-menu: Choose A/B/C for all unsolved conflicts.
+              Choose A/B/C for all unsolved whitespace conflicts.
+- Options to automatically choose a certain source for whitespace conflicts.
+- Shorcut F5 now used to reload the current file.
+
+New Directory-Comparison/Merge Features:
+- Option to trust filesize. (Some directory services don't copy the date/time correctly.)
+- Shortcut F7 now starts complete directory merge (previously F5).
+- Do the selected merge operation for the selected file/dir only
+  "Run Operation For Current Item" (F6).
+- Shortcuts for selecting the merge operation for the selected item.
+  Ctrl-1/2/3/4/Del select A/B/C/Merge/Delete respectively.
+
+Other Improvements:
+- Several i18n-corrections (by Stephan Binner)
+- Bugfix for option CVS-ignore: Didn't work correctly in subdirectories.
+- Bugfix for remote operations: Operation can now be aborted, when KIO-slaves doesn't respond.
+- Cancel-Button in progress bar.
+- Default diff-view now again side by side instead of one above the other.
+
+
+Version 0.9.71 - 2003/10/17
+===========================
+- Windows-Installer by Sebastien Fricker.
+- Bugfixes for Windows. (Problems with setFont() in paintEvent().)
+- Default font for Windows now "Courier New" (instead of Courier)
+- Fix for compilation with gcc 2.95
+- Support for Ctrl-Tab under Windows.
+- Fix for finding documentation.
+- Fix for problem with directory-sync-mode (new in 0.9.70).
+- Fix for several subsequent CR-characters in input file.
+
+Version 0.9.70 - 2003/09/28
+===========================
+- Transparent access to URLs via KIO (KDE only):
+  Compare files and directories on ftp, fish, smb, tar etc. ressources.
+- Workaround for a Win32-bug (Crashed sometimes during selections)
+- When the merge flag is selected in the open dialog, the directory-tool
+  always starts a merge by default for each file. Without the flag only a
+  diff will be started by default.
+- Immediately showing progress bar in dir scan.
+- Showing progress bar for file comparison too.
+- Directory-menu: Fold/Unfold all subdirs
+- Bugfix for 3-way auto-merge: A line deleted from the base in B and C
+  resulted in a empty line instead of being completely removed.
+- Improved locale support
+- KDiff3 is now a KPart
+   - in KDevelop3 it can be used to compare the current text with the
+     last saved version, or the current version on disk with the last cvs version.
+   - in Konqueror it can be used to look at a unified *.patch-file if one complete
+     version is available too.
+- Documentation is now in docbook-format.
+- "Toggle Split Orientation" for Diff-Input windows. (Good for long lines.)
+- When "Dir and Text Split-Screen-View" is off: Now "Focus Next/Prev Window"
+  also toggles between dir and text-windows. Selecting a file via double click
+  switches to text-screen.
+- KDiff3 displays a warning when trying to read a dir without the permission.
+- Directory-Diff-Option "Use CVS-Ignore" to ignore files like CVS does.
+- Displaying a status message at the end of the directory-comparison.
+- Cursor in MergeResultWindow is automatically placed at current difference when a jump occurred.
+  (But not when something was selected.)
+- Fix for cursor blinking in the topline of the MergeResultWindow.
+
+
+Version 0.9.61 - 2003/06/07
+===========================
+- Compilation problem fixed.
+- Directory merge: Preserving file attributes and times during copy. (now also for Win32)
+- Crash fixed, when directory comparison from the command-line was started.
+
+
+Version 0.9.60 - 2003/06/01
+===========================
+New features:
+- New ways to select input for the diff window:
+  - Pasting clipboard text into a diff window.
+  - Drag and drop a file from a filemanager (e.g. konqueror) onto a diff window.
+  - Drag and drop text from an editor (e.g. kate) onto a diff window.
+  Reanalysis starts immediately if no merge is in progress.
+  (This should help you to compare similar parts in the same file.)
+- New/Deleted white lines are now also considered as white deltas.
+- Configurable keyboard shortcuts for most actions (KDE version only).
+- The overview now also distinguishes whitespace deltas.
+New preprocessor options:
+- You can now define your own external Preprocessor and LineMatchingPreprocessor:
+- "Convert to upper case",
+- "Ignore numbers"
+Fixed bugs:
+- Directory merge: Preserving file attributes and times during copy.
+  (not for Win32 yet)
+Source-tree-structure:
+- Switch to KDevelop3 (Gideon): Renamed subdir "kdiff3" to "src".
+- xpm-files in xpm-subdirectory.
+
+
+Version 0.9.51 - 2003/04/14
+===========================
+- Compilation fix for gcc 2.95.
+
+
+Version 0.9.50 - 2003/03/30
+===========================
+Fixed bugs:
+- Auto-Advance setting was lost when entering the settings-dialog.
+- Windows specific: Keys with AltGr-Combination didn't work.
+- Windows 95/98/ME: Fixed crash when KDiff3 is called used without parameters,
+  and corrected support for external diff.
+New Features:
+- Search-function: Search for a string in all open text windows.
+- Special background colors for current region.
+- Button to toggle showing of whitespace in differences.
+- Buttons to go to next/prev unsolved (!) conflict.
+- While auto-advance waits, no more choices are allowed.
+- New setting: Auto-advance-delay.
+  (Note that with delay 0 fast clicks might be detected as double clicks and the second
+  click does nothing. My advice: Prefer the keyboard-shortcuts Ctrl-1/2/3)
+- Functions to Show/Hide Diff Window A, B or C. The other windows then have more space.
+- Merge editor: The right mouse button selects the current region and lets you choose
+  A, B or C via a popup menu.
+- Commandline option --auto: No GUI if all conflicts are auto-solvable.
+- When equal files are compared, then a message box informs you.
+- Merge current file: When comparing two or three files, the merge can be started with a single click.
+- Option dialog: Warning for "Defaults" added, because it resets all options.
+- A warning is given, when the user tries to merge binary files. (i.e. files that contain '\0'-bytes)
+Changed behaviour:
+- 3 file automerge: When for a line B==C (and A!=B) then C will be selected.
+  (In older versions this was a conflict. I was convinced that this is no problem.)
+- Auto-Advance now jumps to next unsolved (!) conflict.
+- On 256-color-displays KDiff3 uses them. (Previously KDiff3 only used 16 colors.)
+- On 16-color-displays the Defaults-button in the options dialog selects special colors.
+
+
+Version 0.941 - 2003/02/09
+==========================
+Fixed bugs:
+- Qt-only-version: Compile problem corrected.
+- Documentation: Formatting for tables corrected.
+
+
+Version 0.94 - 2003/02/09
+=========================
+New features:
+- Option to use external GNU-diff for line matching.
+  (Sometimes GNU-diff is better, sometimes not: You may choose now.)
+- In diff-windows a tooltip shows the full path if you move the mouse on the filename.
+- Speedup of directory-merge operations without user interaction.
+  (Not every item in the tree is made visible anymore. This took too long.)
+- When opening a file for comparison or merge KDiff3 immediately shows the first difference.
+- "Go To Top/Bottom"-action have been changed to "Go To First/Last Delta".
+- Font-Option "Italic For Deltas" added.
+- Many icons and actions will only be enabled, when the operation is possible.
+- Icon for merge of current file in directory merge mode added.
+- New action "Go to Current Delta".
+- Conflicts where some lines contained only-white-space-changes are now separated from
+  other non-white-space-conflicts.
+- Experimental: Use as replacement for ClearCase-cleardiffmrg.exe (under Windows only).
+  See main.cpp for details.
+
+Fixed bugs:
+- If files were different, but had the same dates, the "not existant"-icon was
+  shown for one file. Now a error message will be shown if the option
+  "Copy newer instead of merging" is used.
+- Documentation: Section "The Operation Column" corrected.
+- Qt-only-version: Fontsize wasn't correctly restored.
+-                  Keyboard accelerators didn't work for ToggleActions.
+
+
+Version 0.931 - 2003/01/19
+==========================
+Fix for compilation problems with gcc version < 3.
+
+
+Version 0.93 - 2003/01/16
+=========================
+New features:
+- Directory comparison and merge. (More than 3000 new lines of code only here!!!)
+- Open-Dialog: Filename specification: If no previous filename is there then start
+  directory is taken from another file.
+- Message about number of found and automatically solved conflicts.
+- Support for wheelmouse based scrolling.
+- New option in Diff-tab: Preserve Carriage Return Characters
+
+Fixed bugs:
+- Save button disabled until all conflicts are solved.
+- Copy-operation conserves conflict messages "<Merge Conflict>".
+- Paste operation created pseudo conflicts when the clipboard contained empty lines.
+- W95/98/ME specific program crash removed.
+
+
+Version 0.92 - 2002/11/04
+=========================
+Severe bug corrected:
+- Merge menu: Choose A/B/C Everywhere sometimes lost data. (introduced in 0.9)
+
+
+Version 0.91 - 2002/11/03
+=========================
+Speed improvements for very big/complicated files:
+- Faster analysis because of limited search range (can be adjusted).
+- Faster scrolling and editor behaviour.
+
+Fixed bugs:
+- Compilation problem with gcc 3.2 fixed.
+- When comparing two lines, matching spaces often were undetected.
+- Merge editor appended extra empty line when saving.
+- Sometimes the next diff/conflict wasn't made visible.
+- The Auto-Advance setting is saved now.
+- When doing a merge the application now has modified-state,
+  even without further input. (The old method wasn't safe.)
+- File selection now always in directory of respective file.
+
+
+Version 0.9 - 2002/10/16
+========================
+New features:
+- Qt-only support. Allows compilation under KDE2, Gnome, Mac, Windows, ...
+  Note that KDE3 still gets special treatment.
+- For Mergers: Auto-Advance after selection, Choose A/B/C everwhere, ...
+- Commandline: If files with same name in different directories
+  are compared, only the first parameter needs the filename.
+- Shift-Del, Ctrl-Ins, Shift-Ins supported for Cut/Copy/Paste
+
+Fixed bugs:
+- Make failed on some systems because of missing "minmax.h".
+- Files where opened for reading, but not closed afterwards.
+- Vertical scrollbar sometimes didn't work correctly.
+
+
+
+Version 0.81 - 2002/08/18
+=========================
+New features:
+- Now KDE3 is also supported. Previously only KDE2 was supported.
+- Navigation via click into the overview column now supported.
+
+Fixed bugs:
+- Some input files caused a crash in the diff-algorithm.
+- The meaning of option "Ignore trivial matches" was inverted.
+- When selecting a text in one window, this deselects any previously
+  active selection in the same or another window.
+
+
+
+Version 0.8 - 2002/07/28
+========================
+This is the first version to be released.
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..71ec147
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,4 @@
+Please read the README-file.
+
+Joachim
+
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..203ae39
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+See the ChangeLog
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..ab79d32
--- /dev/null
+++ b/README
@@ -0,0 +1,100 @@
+KDiff3-Readme
+=============
+
+Author: Joachim Eibl  (joachim.eibl at gmx.de)
+Port to KF5/Qt5 by Michael Reeves (reeves.87@gmail.com)
+KDiff3-Version: 1.7.90
+
+Now requires Qt 5.6 or later and KF5 5.14+. Legacy 0.9.98 and earlier builds are not supported.
+MacOSX build is untested since port. The Konqueror specific plugin is not ported and no longer maintained.
+This plugin would only apply to KDE before 4.6. Support as been removed from main CMakeLists.txt.
+As of 1/13/17 cmake 3.1+ is the targeted cmake version.
+
+
+cmake is now the only build system supported. KF5/Qt5 was big jump. A lot changed besides just the API.
+I am not against a Qt5-only build variant but see no reason to maintain two separate build systems.
+At present KF5 is my focus.
+
+The original pre KF5/Qt5 Readme follows old build instructions have been removed to avoid confusion:
+
+
+Copyright: (C) 2002-2014 by Joachim Eibl
+
+KDiff3 runs best on KDE but can be built without it, depending only on Qt-libs.
+These are available for Un*x, Windows, Mac.
+Thus there are many setup possibilities to consider.
+
+Supported Qt-versions: 4.8, 5.2 or higher.
+Supported KDE-version: 4, 5
+(For KDE3/Qt3 use KDiff3-0.9.92 or older.)
+
+Contents
+--------
+
+- Introduction
+- License
+- Additional hints
+
+
+Introduction
+------------
+
+KDiff3 is a program that
+- compares and merges two or three input files or directories,
+- shows the differences line by line and character by character (!),
+- provides an automatic merge-facility and
+- an integrated editor for comfortable solving of merge-conflicts
+- has support for KDE-KIO (ftp, sftp, http, fish, smb),
+- has an intuitive graphical user interface,
+- provides a context menu for KDE-Dolphin and Windows-Explorer,
+- supports 64 bit systems. (Some build issues are discussed in here.)
+- Support for many encodings and Unicode.
+
+Do you want help translating? Read the instructions on https://l10n.kde.org/ !
+
+
+License
+-------
+
+    GNU GENERAL PUBLIC LICENSE, Version 2, June 1991
+    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 02111-1307
+    USA
+
+    For details see file "COPYING".
+
+
+------------------------------------------------------------------------
+
+Additional hints
+----------------
+
+   Start from commandline:
+   - Comparing 2 files:     kdiff3 file1 file2
+   - Merging 2 files:       kdiff3 file1 file2 -o outputfile
+   - Comparing 3 files:     kdiff3 file1 file2 file3
+   - Merging 3 files:       kdiff3 file1 file2 file3 -o outputfile
+        Note that file1 will be treated as base of file2 and file3.
+
+   If all files have the same name but are in different directories, you can
+   reduce typework by specifying the filename only for the first file. E.g.:
+   - Comparing 3 files:     kdiff3 dir1/filename dir2 dir3
+   (This also works in the open-dialog.)
+
+   If you start without arguments, then a dialog will appear where you can
+   select your files via a filebrowser.
+
+   For more documentation, see the help-menu or the subdirectory doc.
+
+   Have fun!
diff --git a/diff_ext_for_kdiff3/LICENSE b/diff_ext_for_kdiff3/LICENSE
new file mode 100644 (file)
index 0000000..c05a18d
--- /dev/null
@@ -0,0 +1,25 @@
+Diff-Ext: 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  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   OWNER  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/diff_ext_for_kdiff3/Makefile b/diff_ext_for_kdiff3/Makefile
new file mode 100644 (file)
index 0000000..5ca0dfc
--- /dev/null
@@ -0,0 +1,63 @@
+# Project: diff_ext
+# Generates diff_ext_for_kdiff3.dll with gcc.
+# Can be used for Cygwin and MingW (MingW ignores -mno-cygwin)
+#
+PROJ := diff_ext_for_kdiff3
+
+CXX  ?= g++.exe
+
+ifdef DEBUG
+  CXXFLAGS ?= -g
+else
+  CXXFLAGS ?= -Os
+  LDFLAGS += -L. -s -static-libgcc
+endif
+CXXFLAGS += -pedantic -Wall -W -D_UNICODE -DUNICODE
+
+LIBS :=  -luuid -lole32
+DEFFILE = $(PROJ).def
+STATICLIB = $(PROJ).a
+EXPLIB = $(PROJ).exp
+
+SRC-CXX = $(wildcard *.cpp)
+SRC-RC = $(wildcard *.rc)
+
+OBJ  := $(SRC-CXX:.cpp=.o)
+RES  := $(SRC-RC:.rc=.res)
+OBJ += $(RES)
+DLL  := $(PROJ).dll
+
+.PHONY: all clean
+
+.SUFFIXES: .rc .res
+
+all: .depend $(DLL)
+
+debug:
+       $(MAKE) DEBUG=YES UNICODE=YES
+
+release:
+       $(MAKE) 
+
+.depend: Makefile $(SRC-RC) $(SRC-CXX)
+       $(CXX) -M $(CXXFLAGS) $(SRC-RC) $(SRC-CXX) > .depend
+       
+include .depend
+
+clean:
+       del  .depend $(OBJ) $(DLL) ${EXPLIB} $(STATICLIB)
+
+$(DLL): $(OBJ)
+       dllwrap.exe \
+               --def $(DEFFILE) \
+               --output-exp ${EXPLIB} \
+               --driver-name c++ -L/usr/local/lib -L/usr/lib/mingw \
+               --implib $(STATICLIB) \
+               $(OBJ) $(LDFLAGS) $(LIBS) \
+               -o $@
+
+.cpp.o:
+       $(CXX) $(CXXFLAGS) -c $< -o $@ 
+
+.rc.res:
+       windres.exe  $< -J rc -o $@ -O coff -DMING
diff --git a/diff_ext_for_kdiff3/Makefile_64bit b/diff_ext_for_kdiff3/Makefile_64bit
new file mode 100644 (file)
index 0000000..404b36c
--- /dev/null
@@ -0,0 +1,64 @@
+# Project: diff_ext
+# Generates diff_ext_for_kdiff3.dll with gcc.
+# Can be used for Cygwin and MingW (MingW ignores -mno-cygwin)
+#
+PROJ := diff_ext_for_kdiff3
+
+CXX  := x86_64-w64-mingw32-g++.exe
+
+ifdef DEBUG
+  CXXFLAGS ?= -g
+else
+  CXXFLAGS ?= -Os
+  LDFLAGS += -s
+endif
+CXXFLAGS += -ansi -pedantic -Wall -W -D_UNICODE -DUNICODE
+
+LIBS := -luuid -lole32
+DEFFILE = $(PROJ).def
+STATICLIB = $(PROJ).a
+EXPLIB = $(PROJ).exp
+
+SRC-CXX = $(wildcard *.cpp)
+SRC-RC = $(wildcard *.rc)
+
+OBJ  := $(SRC-CXX:.cpp=.o)
+RES  := $(SRC-RC:.rc=.res)
+OBJ += $(RES)
+DLL  := $(PROJ).dll
+
+.PHONY: all clean
+
+.SUFFIXES: .rc .res
+
+all: .depend $(DLL)
+
+debug:
+       $(MAKE) DEBUG=YES UNICODE=YES
+
+release:
+       $(MAKE) 
+
+.depend: Makefile $(SRC-RC) $(SRC-CXX)
+       $(CXX) -M $(CXXFLAGS) $(SRC-RC) $(SRC-CXX) > .depend
+       
+include .depend
+
+clean: clean-custom
+       ${RM}  $(OBJ) $(DLL) ${EXPLIB} $(STATICLIB)
+       
+$(DLL): $(OBJ)
+       x86_64-w64-mingw32-dllwrap.exe \
+               --mno-cygwin \
+               --def $(DEFFILE) \
+               --output-exp ${EXPLIB} \
+               --driver-name x86_64-w64-mingw32-g++ -static-libgcc  -L/Users/Joachim/qt/mingw-w64-bin_i686-mingw_20100105/mingw/lib \
+               --implib $(STATICLIB) \
+               $(OBJ) $(LDFLAGS) $(LIBS) \
+               -o $@
+
+.cpp.o:
+       $(CXX) $(CXXFLAGS) -c $< -o $@ 
+
+.rc.res:
+       x86_64-w64-mingw32-windres.exe  $< -J rc -o $@ -O coff -DMING
diff --git a/diff_ext_for_kdiff3/Messages.sh b/diff_ext_for_kdiff3/Messages.sh
new file mode 100644 (file)
index 0000000..9e0d8f5
--- /dev/null
@@ -0,0 +1,3 @@
+#! /usr/bin/env bash
+$EXTRACTRC *.rc >> rc.cpp
+$XGETTEXT `find -name \*.cpp -o -name \*.h` -o $podir/diff_ext.pot
diff --git a/diff_ext_for_kdiff3/README b/diff_ext_for_kdiff3/README
new file mode 100644 (file)
index 0000000..8adde0c
--- /dev/null
@@ -0,0 +1,41 @@
+Diff-Ext for KDiff3 - Readme
+============================
+
+Authors: 
+   Sergey Zorin (Author of diff-ext, see http://diff-ext.sourceforge.net)
+   Joachim Eibl (KDiff3-specific extensions and integration, see http://kdiff3.sourceforge.net)
+
+
+Copyright (c):
+Original Diff-Ext:     Copyright (c) 2003-2006, Sergey Zorin, All rights reserved.
+Extensions for KDiff3: Copyright (c) 2006, Joachim Eibl
+
+
+License: See file LICENSE in this subdirectory
+
+
+Building:
+Via MinGW-compiler package (http://www.mingw.org/): Compile via gnu-make (Makefile)
+Via MSVC2005: Use vcproj-file.
+
+
+Installation:
+For basic testing you can run "regsvr32 diff_ext_for_kdiff3.dll".
+To use all features the installation that comes with the KDiff3-setup*.exe is recommended.
+See also the nsi-file available on the KDiff3-subversion-repository:
+http://svn.sourceforge.net/viewvc/*checkout*/kdiff3/trunk/kdiff3/windows_installer/kdiff3.nsi
+
+
+Translation:
+If you would like help translating diff-ext-for-kdiff3 please copy the diff_ext.pot to
+diff_ext_xx.po (where xx is the language-shortcut).
+Then edit that file and fill in the msgstr-string for each respective msgid-string.
+Then place the for in the KDiff3-translations subdirectory.
+Use the language selection within KDiff3 to switch the language or set the language shortcut
+in the registry HKEY_CURRENT_USER\Software\KDiff3\diff-ext: Language
+If everything works, please send me the created file.
+
+
+Have fun,
+Joachim
+
diff --git a/diff_ext_for_kdiff3/class_factory.cpp b/diff_ext_for_kdiff3/class_factory.cpp
new file mode 100644 (file)
index 0000000..619d38b
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ *
+ */
+#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<CLASS_FACTORY*>(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
new file mode 100644 (file)
index 0000000..1930bd2
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef __class_factory_h__
+#define __class_factory_h__
+#include <shlobj.h>
+#include <shlguid.h>
+
+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
new file mode 100644 (file)
index 0000000..b91c1db
--- /dev/null
@@ -0,0 +1,659 @@
+/*
+ * 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.
+ *
+ */
+
+#define _CRT_SECURE_NO_DEPRECATE
+
+#include <assert.h>
+#include <stdio.h>
+#include <tchar.h>
+
+#include "diff_ext.h"
+#include <map>
+#include <vector>
+
+
+#ifdef UNICODE
+
+static void parseString( const std::wstring& s, size_t& i /*pos*/, std::wstring& r /*result*/ )
+{
+   size_t size = s.size();
+   ++i; // Skip initial '"'
+   for( ; i<size; ++i )
+   {
+      if ( s[i]=='"' )
+      {
+         ++i;
+         break;
+      }
+      else if ( s[i]==L'\\' && i+1<size )
+      {
+         ++i;
+         switch( s[i] ) {
+            case L'n':  r+=L'\n'; break;
+            case L'r':  r+=L'\r'; break;
+            case L'\\': r+=L'\\'; break;
+            case L'"':  r+=L'"';  break;
+            case L't':  r+=L'\t'; break;
+            default:    r+=L'\\'; r+=s[i]; break;
+         }
+      }
+      else
+         r+=s[i];
+   }
+}
+
+static std::map< std::wstring, std::wstring > s_translationMap;
+static tstring s_translationFileName;
+
+void readTranslationFile()
+{
+   s_translationMap.clear();
+   FILE* pFile = _tfopen( s_translationFileName.c_str(), TEXT("rb") );
+   if ( pFile )
+   {
+      MESSAGELOG( TEXT( "Reading translations: " ) + s_translationFileName );
+      std::vector<char> buffer;
+      try {
+         if ( fseek(pFile, 0, SEEK_END)==0 )
+         {
+            size_t length = ftell(pFile); // Get the file length
+            buffer.resize(length);
+            fseek(pFile, 0, SEEK_SET );
+            fread(&buffer[0], 1, length, pFile );
+         }
+      } 
+      catch(...) 
+      {
+      }
+      fclose(pFile);
+
+      if (buffer.size()>0)
+      {
+         size_t bufferSize = buffer.size();
+         int offset = 0;
+         if ( buffer[0]=='\xEF' && buffer[1]=='\xBB' && buffer[2]=='\xBF' )
+         {
+            offset += 3;
+            bufferSize -= 3;
+         }
+
+         size_t sLength = MultiByteToWideChar(CP_UTF8,0,&buffer[offset], (int)bufferSize, 0, 0 );
+         std::wstring s( sLength, L' ' );         
+         MultiByteToWideChar(CP_UTF8,0,&buffer[offset], (int)bufferSize, &s[0], (int)s.size() );
+
+         // Now analyze the file and extract translation strings
+         std::wstring msgid;
+         std::wstring msgstr;
+         msgid.reserve( 1000 );
+         msgstr.reserve( 1000 );
+         bool bExpectingId = true;
+         for( size_t i=0; i<sLength; ++i )
+         {
+            wchar_t c = s[i];
+            if( c == L'\n' || c == L'\r' || c==L' ' || c==L'\t' )
+               continue;
+            else if ( s[i]==L'#' ) // Comment
+               while( s[i]!='\n' && s[i]!=L'\r' && i<sLength )
+                  ++i;
+            else if ( s[i]==L'"' )
+            {
+               if ( bExpectingId ) parseString(s,i,msgid);
+               else                parseString(s,i,msgstr);
+            }
+            else if ( sLength-i>5 && wcsncmp( &s[i], L"msgid", 5 )==0 )
+            {
+               if ( !msgid.empty() && !msgstr.empty() )
+               {
+                  s_translationMap[msgid] = msgstr;
+               }
+               bExpectingId = true;
+               msgid.clear();
+               i+=4;
+            }
+            else if ( sLength-i>6 && wcsncmp( &s[i], L"msgstr", 6 )==0 )
+            {
+               bExpectingId = false;
+               msgstr.clear();
+               i+=5;
+            }
+            else
+            {
+               // Unexpected ?
+            }
+         }
+      }
+   }
+   else
+   {
+      ERRORLOG( TEXT( "Reading translations failed: " ) + s_translationFileName );
+   }
+}
+
+static tstring getTranslation( const tstring& fallback )
+{
+   std::map< std::wstring, std::wstring >::iterator i = s_translationMap.find( fallback );
+   if (i!=s_translationMap.end())
+      return i->second;
+   return fallback;
+}
+#else
+
+static tstring getTranslation( const tstring& fallback )
+{
+   return fallback;
+}
+
+#endif
+
+
+static void replaceArgs( tstring& s, const tstring& r1, const tstring& r2=TEXT(""), const tstring& r3=TEXT("") )
+{
+   tstring arg1 = TEXT("%1");
+   size_t pos1 = s.find( arg1 );
+   tstring arg2 = TEXT("%2");
+   size_t pos2 = s.find( arg2 );
+   tstring arg3 = TEXT("%3");
+   size_t pos3 = s.find( arg3 );
+   if ( pos1 != size_t(-1) )
+   {
+      s.replace( pos1, arg1.length(), r1 );
+      if ( pos2 != size_t(-1) && pos1<pos2 )
+         pos2 += r1.length() - arg1.length();
+      if ( pos3 != size_t(-1) && pos1<pos3 )
+         pos3 += r1.length() - arg1.length();
+   }
+   if ( pos2 != size_t(-1) )
+   {
+      s.replace( pos2, arg2.length(), r2 );
+      if ( pos3 != size_t(-1) && pos2<pos3 )
+         pos3 += r2.length() - arg2.length();
+   }
+   if ( pos3 != size_t(-1) )
+   {
+      s.replace( pos3, arg3.length(), r3 );
+   }
+}
+
+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<IShellExtInit*>(this);
+  } else if (IsEqualIID(refiid, IID_IContextMenu)) {
+    *ppv = static_cast<IContextMenu*>(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();
+
+#ifdef UNICODE
+   tstring installDir = SERVER::instance()->getRegistryKeyString( TEXT(""), TEXT("InstallDir") );
+   tstring language = SERVER::instance()->getRegistryKeyString( TEXT(""), TEXT("Language") );
+   tstring translationFileName = installDir + TEXT("\\translations\\diff_ext_") + language + TEXT(".po");
+   if ( s_translationFileName != translationFileName )
+   {
+      s_translationFileName = translationFileName;
+      readTranslationFile();
+   }
+#endif
+
+  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];
+    
+    //initialize_language();
+    
+    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 = i18n("Compare with %1");
+         tstring menuStringMerge   = i18n("Merge with %1");
+         tstring firstFileName;
+         if( nrOfRecentFiles>=1 )
+         {
+            firstFileName = TEXT("'") + cut_to_length( m_recentFiles.front() ) + TEXT("'");
+         } 
+         replaceArgs( menuStringCompare, firstFileName );
+         replaceArgs( menuStringMerge,   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++, i18n("3-way merge with base"), 
+            nrOfRecentFiles >=2 ? MFS_ENABLED : MFS_DISABLED );
+
+         menuString = i18n("Save '%1' for later");
+         replaceArgs( menuString, _file_name1 );
+         m_id_DiffLater = insertMenuItemHelper( subMenu, id++, pos2++, menuString );
+
+         HMENU file_list = CreateMenu();
+         std::list<tstring>::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++, i18n("Compare with ..."), 
+            nrOfRecentFiles > 0 ? MFS_ENABLED : MFS_DISABLED, file_list );
+
+         m_id_ClearList = insertMenuItemHelper( subMenu, id++, pos2++, 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++, i18n("Compare") );
+      } 
+      else if ( m_nrOfSelectedFiles == 3 ) 
+      {      
+         m_id_Diff3 = insertMenuItemHelper( subMenu, id++, pos2++, i18n("3 way comparison") );
+      } 
+      else 
+      {
+         // More than 3 files selected?
+      }
+      m_id_About = insertMenuItemHelper( subMenu, id++, pos2++, 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();
+         std::wstring sBits = i18n("(32 Bit)");
+         if (sizeof(void*)==8)
+             sBits = i18n("(64 Bit)");
+         MessageBox( _hwnd, (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")
+            + i18n("Homepage for KDiff3: http://kdiff3.sourceforge.net")).c_str()
+            , (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) {
+      tstring 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'");
+            replaceArgs( helpString, _file_name1, m_recentFiles.front() );
+         }
+      } 
+      else if(idCmd == m_id_DiffLater) 
+      {
+         helpString = i18n("Save '%1' for later operation");
+         replaceArgs( helpString, _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<tstring>::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'");
+               replaceArgs( helpString, _file_name1, *i );
+            }
+         }
+      }
+      lstrcpyn( (LPTSTR)pszName, 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 = i18n("Could not start KDiff3. Please rerun KDiff3 installation.");
+      message += TEXT("\n") + i18n("Command") + TEXT(": ") + command;
+      message += TEXT("\n") + i18n("CommandLine") + TEXT(": ") + commandLine;
+      MessageBox(_hwnd, message.c_str(), i18n("Diff-Ext For KDiff3").c_str(), MB_OK);
+   }
+}
+
+void
+DIFF_EXT::diff_with(unsigned int num, bool bMerge) 
+{
+   LOG();
+   std::list<tstring>::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
new file mode 100644 (file)
index 0000000..b126f73
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ *
+ */
+
+
+#ifndef __diff_ext_h__
+#define __diff_ext_h__
+
+#include <windows.h>
+#include <windowsx.h>
+#include <shlobj.h>
+
+#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);
+    void initialize_language();
+
+  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/diff_ext_for_kdiff3.def b/diff_ext_for_kdiff3/diff_ext_for_kdiff3.def
new file mode 100644 (file)
index 0000000..51a257a
--- /dev/null
@@ -0,0 +1,6 @@
+LIBRARY        "diff_ext_for_kdiff3"
+EXPORTS
+    DllCanUnloadNow=DllCanUnloadNow@0
+    DllGetClassObject=DllGetClassObject@12
+    DllRegisterServer=DllRegisterServer@0
+    DllUnregisterServer=DllUnregisterServer@0
diff --git a/diff_ext_for_kdiff3/diff_ext_for_kdiff3.rc b/diff_ext_for_kdiff3/diff_ext_for_kdiff3.rc
new file mode 100644 (file)
index 0000000..7bb3a1e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ *
+ */
+
+#include <windows.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+  FILEVERSION 1,6,1,145
+  PRODUCTVERSION 1,6,1,145
+  FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+  FILEFLAGS VS_FF_DEBUG
+#else
+  FILEFLAGS 0L
+#endif
+  FILEOS VOS_NT_WINDOWS32
+  FILETYPE VFT_DLL
+  FILESUBTYPE VFT_UNKNOWN
+{
+    BLOCK "StringFileInfo"
+    {
+        BLOCK "040904b0"
+        {
+            VALUE "Comments", ""
+            VALUE "CompanyName", ""
+            VALUE "FileDescription", "diff shell extension"
+            VALUE "FileVersion", "Release 1.6.1"
+            VALUE "InternalName", "diff shell extension"
+            VALUE "LegalCopyright", "Copyright � 2003-2005 Sergey Zorin"
+            VALUE "LegalTrademarks", ""
+            VALUE "OriginalFilename", "diff_ext.dll"
+            VALUE "PrivateBuild", ""
+            VALUE "ProductName", "Diff Context Menu Extension"
+            VALUE "ProductVersion", "Release 1.6.1"
+        }
+    }
+    BLOCK "VarFileInfo"
+    {
+        VALUE "Translation", 0x409, 1200
+    }
+}
diff --git a/diff_ext_for_kdiff3/diff_ext_for_kdiff3.vcproj b/diff_ext_for_kdiff3/diff_ext_for_kdiff3.vcproj
new file mode 100644 (file)
index 0000000..6ce9ade
--- /dev/null
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+       ProjectType="Visual C++"
+       Version="8,00"
+       Name="diff_ext_for_kdiff3"
+       ProjectGUID="{9734C087-C745-4DCE-9076-73BD15145F83}"
+       RootNamespace="diff_ext_for_kdiff3"
+       Keyword="Win32Proj"
+       >
+       <Platforms>
+               <Platform
+                       Name="Win32"
+               />
+       </Platforms>
+       <ToolFiles>
+       </ToolFiles>
+       <Configurations>
+               <Configuration
+                       Name="Debug|Win32"
+                       OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+                       IntermediateDirectory="$(ConfigurationName)"
+                       ConfigurationType="2"
+                       CharacterSet="1"
+                       >
+                       <Tool
+                               Name="VCPreBuildEventTool"
+                       />
+                       <Tool
+                               Name="VCCustomBuildTool"
+                       />
+                       <Tool
+                               Name="VCXMLDataGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCWebServiceProxyGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCMIDLTool"
+                       />
+                       <Tool
+                               Name="VCCLCompilerTool"
+                               Optimization="0"
+                               PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;DIFF_EXT_FOR_KDIFF3_EXPORTS"
+                               MinimalRebuild="true"
+                               BasicRuntimeChecks="3"
+                               RuntimeLibrary="3"
+                               UsePrecompiledHeader="0"
+                               WarningLevel="3"
+                               Detect64BitPortabilityProblems="true"
+                               DebugInformationFormat="3"
+                       />
+                       <Tool
+                               Name="VCManagedResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCPreLinkEventTool"
+                       />
+                       <Tool
+                               Name="VCLinkerTool"
+                               RegisterOutput="false"
+                               LinkIncremental="2"
+                               ModuleDefinitionFile="diff_ext_for_kdiff3_msvc.def"
+                               GenerateDebugInformation="true"
+                               SubSystem="2"
+                               TargetMachine="1"
+                       />
+                       <Tool
+                               Name="VCALinkTool"
+                       />
+                       <Tool
+                               Name="VCManifestTool"
+                       />
+                       <Tool
+                               Name="VCXDCMakeTool"
+                       />
+                       <Tool
+                               Name="VCBscMakeTool"
+                       />
+                       <Tool
+                               Name="VCFxCopTool"
+                       />
+                       <Tool
+                               Name="VCAppVerifierTool"
+                       />
+                       <Tool
+                               Name="VCWebDeploymentTool"
+                       />
+                       <Tool
+                               Name="VCPostBuildEventTool"
+                       />
+               </Configuration>
+               <Configuration
+                       Name="Release|Win32"
+                       OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+                       IntermediateDirectory="$(ConfigurationName)"
+                       ConfigurationType="2"
+                       CharacterSet="1"
+                       WholeProgramOptimization="1"
+                       >
+                       <Tool
+                               Name="VCPreBuildEventTool"
+                       />
+                       <Tool
+                               Name="VCCustomBuildTool"
+                       />
+                       <Tool
+                               Name="VCXMLDataGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCWebServiceProxyGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCMIDLTool"
+                       />
+                       <Tool
+                               Name="VCCLCompilerTool"
+                               Optimization="1"
+                               PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;DIFF_EXT_FOR_KDIFF3_EXPORTS"
+                               RuntimeLibrary="2"
+                               UsePrecompiledHeader="0"
+                               WarningLevel="3"
+                               Detect64BitPortabilityProblems="true"
+                               DebugInformationFormat="3"
+                       />
+                       <Tool
+                               Name="VCManagedResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCPreLinkEventTool"
+                       />
+                       <Tool
+                               Name="VCLinkerTool"
+                               RegisterOutput="false"
+                               LinkIncremental="1"
+                               ModuleDefinitionFile="diff_ext_for_kdiff3_msvc.def"
+                               GenerateDebugInformation="true"
+                               SubSystem="2"
+                               OptimizeReferences="2"
+                               EnableCOMDATFolding="2"
+                               TargetMachine="1"
+                       />
+                       <Tool
+                               Name="VCALinkTool"
+                       />
+                       <Tool
+                               Name="VCManifestTool"
+                       />
+                       <Tool
+                               Name="VCXDCMakeTool"
+                       />
+                       <Tool
+                               Name="VCBscMakeTool"
+                       />
+                       <Tool
+                               Name="VCFxCopTool"
+                       />
+                       <Tool
+                               Name="VCAppVerifierTool"
+                       />
+                       <Tool
+                               Name="VCWebDeploymentTool"
+                       />
+                       <Tool
+                               Name="VCPostBuildEventTool"
+                       />
+               </Configuration>
+       </Configurations>
+       <References>
+       </References>
+       <Files>
+               <Filter
+                       Name="Source Files"
+                       Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+                       UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+                       >
+                       <File
+                               RelativePath="class_factory.cpp"
+                               >
+                       </File>
+                       <File
+                               RelativePath="diff_ext.cpp"
+                               >
+                       </File>
+                       <File
+                               RelativePath=".\diff_ext_for_kdiff3_msvc.def"
+                               >
+                       </File>
+                       <File
+                               RelativePath="server.cpp"
+                               >
+                       </File>
+               </Filter>
+               <Filter
+                       Name="Header Files"
+                       Filter="h;hpp;hxx;hm;inl;inc;xsd"
+                       UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+                       >
+                       <File
+                               RelativePath="class_factory.h"
+                               >
+                       </File>
+                       <File
+                               RelativePath="diff_ext.h"
+                               >
+                       </File>
+                       <File
+                               RelativePath=".\diffextstring.h"
+                               >
+                       </File>
+                       <File
+                               RelativePath="server.h"
+                               >
+                       </File>
+               </Filter>
+               <Filter
+                       Name="Resource Files"
+                       Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+                       UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+                       >
+               </Filter>
+       </Files>
+       <Globals>
+       </Globals>
+</VisualStudioProject>
diff --git a/diff_ext_for_kdiff3/diff_ext_for_kdiff3_msvc.def b/diff_ext_for_kdiff3/diff_ext_for_kdiff3_msvc.def
new file mode 100644 (file)
index 0000000..bc75265
--- /dev/null
@@ -0,0 +1,6 @@
+LIBRARY        "diff_ext_for_kdiff3"
+EXPORTS
+    DllCanUnloadNow PRIVATE
+    DllGetClassObject PRIVATE
+    DllRegisterServer PRIVATE
+    DllUnregisterServer PRIVATE
diff --git a/diff_ext_for_kdiff3/diffextstring.h b/diff_ext_for_kdiff3/diffextstring.h
new file mode 100644 (file)
index 0000000..d488499
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ *
+ */
+
+
+#ifndef __string_h__
+#define __string_h__
+
+#include <windows.h>
+#include <tchar.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+class STRING;
+inline STRING operator+( const STRING& s1, const STRING& s2);
+
+class STRING {
+  public:
+    static const int begin = 0;
+    static const int end = -1;
+  
+  public:
+    STRING(const STRING& s) {
+      _str = new TCHAR[s.length()+1];
+      lstrcpy(_str, s);
+    }
+    
+    STRING(const TCHAR* str = TEXT("")) {
+      _str = new TCHAR[lstrlen(str)+1];
+      lstrcpy(_str, str);
+    }
+    
+    ~STRING() {
+      delete[] _str;
+    }
+
+    void resize( size_t newLength )
+    {
+       size_t oldLength = length();
+       if ( newLength < oldLength ) {
+          _str[newLength] = 0; // Just truncate the string
+       } else if( newLength>oldLength) {
+          TCHAR* p = new TCHAR[ newLength + 1 ];
+          lstrcpy(p, _str);
+          for( size_t i=oldLength; i<newLength; ++i)
+             p[i]=TEXT(' ');
+          p[newLength]=0;
+       }
+    }
+    
+    STRING& operator=(const STRING& s) {
+      delete[] _str;
+      _str = new TCHAR[s.length()+1];
+      lstrcpy(_str, s);
+      return *this;
+    }
+    
+    operator TCHAR*() {
+      return _str;
+    }
+    
+    operator const TCHAR*() const {
+      return _str;
+    }
+
+    const TCHAR* c_str() const {
+       return _str;
+    }
+    
+    size_t length() const {
+      return _tcslen(_str);
+    }
+    
+    // Also returns the length. Behaviour like std::basic_string::size.
+    // See also sizeInBytes() below.
+    size_t size() const {
+      return length();
+    }
+
+    // String length in bytes. May differ from length() for Unicode or MBCS
+    size_t sizeInBytes() const {
+      return length()*sizeof(TCHAR);
+    }
+
+    bool empty() const
+    {
+       return length()==0;
+    }
+    
+    STRING substr(size_t from, size_t len=size_t(-1)) const {
+      STRING tmp;
+      size_t to = len==size_t(-1) ? length() : from + len;
+            
+      if(from < to && (to <= length())) {
+        size_t new_len = to - from + 1;
+        TCHAR* new_str = new TCHAR[new_len+1];
+        lstrcpyn(new_str, &_str[from], int(new_len) );
+        new_str[new_len] = 0;
+        
+        tmp = new_str;
+        delete[] new_str;
+      }
+      
+      return tmp;
+    }
+
+    STRING& replace( size_t pos, size_t num, const STRING& s )
+    {
+       *this = substr( 0, pos ) + s + substr( pos+num );
+       return *this;
+    }
+    
+    bool operator ==(const STRING& s) const {
+      return (lstrcmp(_str, s) == 0);
+    }
+
+    size_t find(const STRING& s) const
+    {
+       const TCHAR* p = _tcsstr( _str, s._str );
+       if (p)
+          return p - _str;
+       else
+          return size_t(-1);
+    }
+    
+    STRING& operator +=(const STRING& s) {
+      TCHAR* str = new TCHAR[lstrlen(_str)+s.length()+1];
+
+      lstrcpy(str, _str);
+      lstrcat(str, s);
+      
+      delete[] _str;
+      
+      _str = str;
+      
+      return *this;
+    }
+
+  private:
+    TCHAR* _str;
+};
+
+inline STRING operator+( const STRING& s1, const STRING& s2) {
+  STRING tmp(s1);
+  
+  tmp+=s2;
+  
+  return tmp;
+}
+
+
+
+#endif // __string_h__
diff --git a/diff_ext_for_kdiff3/server.cpp b/diff_ext_for_kdiff3/server.cpp
new file mode 100644 (file)
index 0000000..da47687
--- /dev/null
@@ -0,0 +1,486 @@
+/*
+ * 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.
+ *
+ */
+
+#define _WIN32_WINNT 0x0502
+#define _CRT_NON_CONFORMING_SWPRINTFS 
+#define _CRT_SECURE_NO_DEPRECATE
+
+#include <stdio.h>
+
+#include <windows.h>
+#include <tchar.h>
+
+#include <shlguid.h>
+#include <olectl.h>
+#include <objidl.h>
+
+#include <objbase.h>
+#include <initguid.h>
+
+#include <KLocalizedString>
+//#include <log/log.h>
+//#include <log/log_message.h>
+//#include <log/file_sink.h>
+//#include <debug/trace.h>
+
+#include "server.h"
+#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 _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<tstring>&
+SERVER::recent_files() 
+{
+   LOG();
+   if ( m_pRecentFiles==0 )
+   {
+      m_pRecentFiles = new std::list<tstring>;
+   }
+   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<tstring>::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
new file mode 100644 (file)
index 0000000..6ce2ca6
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ *
+ */
+#ifndef __server_h__
+#define __server_h__
+#include <list>   // std::list
+//#include <log/file_sink.h>
+#include <windows.h>
+#include <string> // std::wstring
+
+#ifdef UNICODE
+typedef std::wstring tstring;
+#else
+typedef std::string tstring;
+#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<tstring>* m_pRecentFiles;
+    static SERVER* _instance;
+    tstring m_registryBaseName;
+    FILE* m_pLogFile;
+};
+
+#endif // __server_h__
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
new file mode 100644 (file)
index 0000000..3276587
--- /dev/null
@@ -0,0 +1 @@
+add_subdirectory(en)
diff --git a/doc/en/CMakeLists.txt b/doc/en/CMakeLists.txt
new file mode 100644 (file)
index 0000000..79190b0
--- /dev/null
@@ -0,0 +1,2 @@
+kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kdiff3)
+kdoctools_create_manpage (man-kdiff3.1.docbook 1 INSTALL_DESTINATION ${KDE_INSTALL_MANDIR})
diff --git a/doc/en/dirbrowser.png b/doc/en/dirbrowser.png
new file mode 100644 (file)
index 0000000..01da6ec
Binary files /dev/null and b/doc/en/dirbrowser.png differ
diff --git a/doc/en/dirmergebig.png b/doc/en/dirmergebig.png
new file mode 100644 (file)
index 0000000..4e2e802
Binary files /dev/null and b/doc/en/dirmergebig.png differ
diff --git a/doc/en/index.docbook b/doc/en/index.docbook
new file mode 100644 (file)
index 0000000..d727f1d
--- /dev/null
@@ -0,0 +1,2109 @@
+<?xml version="1.0" ?>
+<!DOCTYPE book PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
+  <!ENTITY kdiff3 "<application>KDiff3</application>">
+  <!ENTITY % addindex "IGNORE">
+  <!ENTITY % English "INCLUDE">
+]>
+
+<book lang="&language;">
+
+<!-- This header contains all of the meta-information for the document such
+as Authors, publish date, the abstract, and Keywords -->
+
+<bookinfo>
+<title>The &kdiff3; Handbook</title>
+
+<authorgroup>
+<author>
+<!--<personname>-->
+<firstname>Joachim</firstname>
+<surname>Eibl</surname>
+<!--</personname>-->
+<affiliation><address>
+   <email>joachim.eibl at gmx.de</email>
+</address></affiliation>
+</author>
+<!-- TRANS:ROLES_OF_TRANSLATORS -->
+</authorgroup>
+
+<copyright>
+<year>2002-2007</year>
+<holder>Joachim Eibl</holder>
+</copyright>
+<copyright>
+<year>2017</year>
+<holder>Michael Reeves</holder>
+</copyright>
+<!-- Translators: put here the copyright notice of the translation -->
+<!-- Put here the FDL notice.  Read the explanation in fdl-notice.docbook
+     and in the FDL itself on how to use it. -->
+<legalnotice>&FDLNotice;</legalnotice>
+
+<!-- Date and version information of the documentation
+Don't forget to include this last date and this last revision number, we
+need them for translation coordination !
+Please respect the format of the date (YYYY-MM-DD) and of the version
+(V.MM.LL), it could be used by automation scripts.
+Do NOT change these in the translation. -->
+
+<date>2018-04-30</date>
+<releaseinfo>1.07.00</releaseinfo>
+
+
+<abstract>
+<para>
+   &kdiff3; is a file and directory diff and merge tool which
+<itemizedlist>
+<listitem><para>compares and merges two or three text input files or directories,</para></listitem>
+<listitem><para>shows the differences line by line and character by character(!),</para></listitem>
+<listitem><para>provides an automatic merge-facility,</para></listitem>
+<listitem><para>has an editor for comfortable solving of merge-conflicts,</para></listitem>
+<listitem><para>provides networktransparency via KIO,</para></listitem>
+<listitem><para>has options to highlight or hide changes in white-space or comments,</para></listitem>
+<listitem><para>supports Unicode, UTF-8 and other file encodings,</para></listitem>
+<listitem><para>prints differences,</para></listitem>
+<listitem><para>supports version control keyword and history merging.</para></listitem>
+</itemizedlist>
+</para><para>
+   This document describes &kdiff3;-version 1.7.90.
+</para>
+</abstract>
+
+<!-- This is a set of Keywords for indexing by search engines.
+Please at least include KDE, the KDE package it is in, the name
+ of your application, and a few relevant keywords. -->
+
+<keywordset>
+<keyword>KDE</keyword>
+<keyword>kdiff3</keyword>
+<keyword>diff</keyword>
+<keyword>merge</keyword>
+<keyword>CVS</keyword>
+<keyword>triplediff</keyword>
+<keyword>compare</keyword>
+<keyword>files</keyword>
+<keyword>directories</keyword>
+<keyword>version control</keyword>
+<keyword>three-way-merge</keyword>
+<keyword>in-line-differences</keyword>
+<keyword>synchronise</keyword>
+<keyword>kpart</keyword>
+<keyword>kio</keyword>
+<keyword>networktransparent</keyword>
+<keyword>editor</keyword>
+<keyword>white space</keyword>
+<keyword>comments</keyword>
+</keywordset>
+
+</bookinfo>
+
+<chapter id="introduction"><title>Introduction</title>
+<sect1 id="why"><title>Yet Another Diff Frontend?</title>
+<para>
+Several graphical diff tools exist. Why choose &kdiff3;? Let me say, why I wrote it.
+</para><para>
+&kdiff3; started because I had to do a difficult merge. Merging is necessary when several
+people work on the same files in a project. A merge can be somewhat automated, when the
+merge-tool not only has the new modified files (called "branches"), but also the original file
+(called "base"). The merge tool will automatically choose any modification that was only
+done in one branch. When several contributors change the same lines, then the merge tool
+detects a conflict which must be solved manually.
+</para><para>
+The merge then was difficult because one contributor had changed many things and corrected
+the indentation in many places. Another contributor also had changed much text in the same file,
+which resulted in several merge conflicts.
+</para><para>
+The tool I used then, only showed the changed lines, but not what had changed within these
+lines. And there was no information about where only the indentation was changed. The merge
+was a little nightmare.
+</para><para>
+So this was the start. The first version could show differences within a line and showed white space differences.
+Later many other features were added to increase the usefulness.
+</para><para>
+For example if you want to compare some text quickly, then you can copy it to the clipboard and
+paste it into either diff window.
+</para><para>
+A feature that required a big effort was the directory comparison and merge facility, which turned
+the program almost into a full file browser.
+</para><para>
+I hope &kdiff3; works for you too. Have fun!
+</para><para>
+Joachim Eibl (2003)
+</para>
+</sect1>
+
+<sect1 id="screenshots"><title>Screenshots and Features</title>
+<para>This screenshot shows the difference between two text files</para>
+<para>(Using an early version of &kdiff3;):</para>
+<screenshot><mediaobject>
+   <imageobject><imagedata fileref="screenshot_diff.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+
+<para>
+   3-way-merging is fully supported. This is useful if two people change code independently.
+   The original file (the base) is used to help &kdiff3; to automatically select the correct
+   changes.
+   The merge-editor below the diff-windows allows you to solve conflicts, while showing you the output you will get.
+   You can even edit the output.
+   This screenshot shows three input files being merged:
+</para><para>
+<screenshot><mediaobject>
+   <imageobject><imagedata fileref="screenshot_merge.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+</para>
+
+<para id="dirmergebigscreenshot">&kdiff3; also helps you to compare and merge complete directories.
+This screenshot shows &kdiff3; during a directory merge:
+</para><para>
+<screenshot><mediaobject>
+   <imageobject><imagedata fileref="dirmergebig.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+</para>
+</sect1>
+
+<sect1 id="features"><title>More Features</title>
+<sect2><title> Line-By-Line And Char-By-Char Diff-Viewer</title>
+<para>By using the possibilities of a graphical color display &kdiff3; shows
+   exactly what the difference is. When you have to do many code-reviews, you will like this.
+</para>
+<screenshot><mediaobject>
+   <imageobject><imagedata fileref="letter_by_letter.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+</sect2>
+
+<sect2><title> See White-Space Differences At One Glance</title>
+<para>Spaces and tabs that differ appear visibly. When lines differ only
+   in  the  amount of white space this can be seen at one look in the summary
+   column on the left side. (No more worries when people change the indentation.)
+</para>
+<screenshot><mediaobject>
+   <imageobject><imagedata fileref="white_space.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+</sect2>
+
+<sect2><title> Triple-Diff</title>
+<para> Analyze three files and see where they differ.
+</para><para>
+   The left/middle/right windows are named A/B/C and have the blue/green/magenta
+   color respectively.
+</para><para>
+   If one file is the same and one file is different on a line then the
+   color   shows which file is different. The red color means that both other
+   files  are different.
+</para>
+<screenshot><mediaobject>
+   <imageobject><imagedata fileref="triple_diff.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+</sect2>
+
+<sect2><title> Comfortable Merge Of Two Or Three Input Files</title>
+<para> &kdiff3; can be used to merge two or three input files and automatically
+   merges as much as possible. The result is presented in an editable window
+   where most conflicts can be solved with a single mouseclick: Select the
+   buttons  A/B/C from the button-bar to select the source that should be used.
+   You can  also select more than one source. Since this output window is an
+   editor even  conflicts which need further corrections can be solved here without
+   requiring  another tool.
+</para>
+</sect2>
+
+<sect2><title>And ...</title>
+<itemizedlist>
+   <listitem><para>Fast navigation via buttons.</para></listitem>
+   <listitem><para>A mouse-click into a summary column sync's all windows to show the same position.</para></listitem>
+   <listitem><para>Select and copy from any window and paste into the merge result window.</para></listitem>
+   <listitem><para>Overview column that shows where the changes and conflicts are.</para></listitem>
+   <listitem><para>The colors are adjustable to your specific preferences.</para></listitem>
+   <listitem><para>Adjustable Tab size.</para></listitem>
+   <listitem><para>Option to insert spaces instead of tabs.</para></listitem>
+   <listitem><para>Open files comfortably via dialog or specify files on the command line.</para></listitem>
+   <listitem><para>Search for strings in all text windows. Find (Ctrl-F) and Find Next (F3)</para></listitem>
+   <listitem><para>Show the line numbers for each line. </para></listitem>
+   <listitem><para>Paste clipboard or drag text into a diff input window.</para></listitem>
+   <listitem><para>Networktransparency via KIO.</para></listitem>
+   <listitem><para>Can be used as diff-viewer in KDevelop 3.</para></listitem>
+   <listitem><para>Word-wrap for long lines.</para></listitem>
+   <listitem><para>Support for Unicode, UTF-8 and other codecs.</para></listitem>
+   <listitem><para>Support for right to left languages.</para></listitem>
+   <listitem><para>...</para></listitem>
+</itemizedlist>
+</sect2>
+</sect1>
+</chapter>
+
+<chapter id="documentation"><title>File Comparison And Merge</title>
+
+<sect1 id="commandline"><title>Command-Line Options</title>
+
+<sect2><title>Comparing 2 files: </title>
+<screen>
+   <command>kdiff3</command> <replaceable>file1 file2</replaceable>
+</screen>
+</sect2>
+
+<sect2><title>Merging 2 files: </title>
+<screen>
+   <command>kdiff3</command> <replaceable>file1 file2</replaceable> -m
+   <command>kdiff3</command> <replaceable>file1 file2</replaceable> -o <replaceable>outputfile</replaceable>
+</screen>
+</sect2>
+
+<sect2><title>Comparing 3 files: </title>
+<screen>
+   <command>kdiff3</command> <replaceable>file1 file2 file3</replaceable>
+</screen>
+</sect2>
+
+<sect2><title>Merging 3 files: </title>
+<screen>
+   <command>kdiff3</command> <replaceable>file1 file2 file3</replaceable> -m
+   <command>kdiff3</command> <replaceable>file1 file2 file3</replaceable> -o <replaceable>outputfile</replaceable>
+</screen>
+<para>
+   Note that <replaceable>file1</replaceable> will be treated as
+   base of <replaceable>file2</replaceable> and
+   <replaceable>file3</replaceable>.
+</para>
+</sect2>
+
+<sect2><title>Special case: Files with the same name </title>
+<para>
+If all files have the same name but are in different directories, you can
+reduce typework by specifying the filename only for the first file. E.g.:
+</para>
+<screen>
+   <command>kdiff3</command> <replaceable>dir1/filename dir2 dir3</replaceable>
+</screen>
+</sect2>
+
+<sect2><title>Commandline for starting a directory comparison or merge: </title>
+<para>This is very similar, but now it's about directories.</para>
+<screen>
+   <command>kdiff3</command> <replaceable>dir1 dir2</replaceable>
+   <command>kdiff3</command> <replaceable>dir1 dir2</replaceable> -o <replaceable>destdir</replaceable>
+   <command>kdiff3</command> <replaceable>dir1 dir2 dir3</replaceable>
+   <command>kdiff3</command> <replaceable>dir1 dir2 dir3</replaceable> -o <replaceable>destdir</replaceable>
+</screen>
+<para>For directory comparison and merge you can continue to read <link linkend="dirmerge">here</link>.</para>
+</sect2>
+
+<sect2><title>Other command line options</title>
+<para>To see all available command line options type</para>
+<screen>
+<command>kdiff3</command> --help
+</screen>
+<para>Example output:</para>
+<screen>
+Options:
+  -m, --merge               Merge the input.
+  -b, --base file           Explicit base file. For compatibility with certain tools.
+  -o, --output file         Output file. Implies -m. E.g.: -o newfile.txt
+  --out file                Output file, again. (For compatibility with certain tools.)
+  --auto                    No GUI if all conflicts are auto-solvable. (Needs -o file)
+  --qall                    Don't solve conflicts automatically. (For compatibility...)
+  --L1 alias1               Visible name replacement for input file 1 (base).
+  --L2 alias2               Visible name replacement for input file 2.
+  --L3 alias3               Visible name replacement for input file 3.
+  -L, --fname alias         Alternative visible name replacement. Supply this once for every input.
+  --cs string               Override a config setting. Use once for every setting. E.g.: --cs "AutoAdvance=1"
+  --confighelp              Show list of config settings and current values.
+  --config file             Use a different config file.
+</screen>
+<para>The option <option>--cs</option> allows you to adjust a configuration value that is otherwise only adjustable via the configure dialogs. 
+But be aware that when &kdiff3; then terminates the changed value will be stored along with the other settings. 
+With <option>--confighelp</option> you can find out the names of the available items and current values.</para>
+<para>Via <option>--config</option> you can specify a different config file. When you often use &kdiff3; 
+with completely different setups this allows you to easily switch between them.</para>
+</sect2>
+<sect2><title>Ignorable command line options</title>
+<para>Many people want to use &kdiff3; with some version control system. 
+But when that version control system calls &kdiff3; using command line parameters that &kdiff3; doesn't recognise, then &kdiff3; terminates with an error.
+The integration settings allow to specify command line parameters that should be ignored by &kdiff3;. 
+They will appear in the usage help like in this example:</para>
+<screen>
+  --<replaceable>foo</replaceable>                     Ignored. (User defined.)
+</screen>
+<variablelist>
+  <varlistentry><term><emphasis>Command line options to ignore:</emphasis></term><listitem><para>
+     A list of options, separated via semicolon ';'. When one of these options appears on the commandline, 
+     then &kdiff3; will ignore it and run without reporting an error. 
+     (Default is "u;query;html;abort").</para></listitem></varlistentry>
+</variablelist>
+<para>When this isn't enough, then it is recommended to write a shell script that does the option translation.</para>
+</sect2>
+
+</sect1>
+
+<sect1 id="opendialog"><title>Open-Dialog</title>
+<para>
+   Since many input files must be selectable, the program has a special open dialog:
+</para>
+<screenshot><mediaobject>
+<imageobject><imagedata fileref="open_dialog.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+<para>
+   The open dialog allows you to edit the filenames by hand, selecting a file
+   via  the file-browser ("File...") or allows you to choose recent files from
+   the drop-down lists. If you open the dialog again, then the current names
+   still remain there. The third input file is not required. If the
+   entry   for "C" remains empty, then only a two file diff analysis will be
+   done.
+</para><para>
+   You can also select a directory via "Dir...". If for A a directory is specified
+   then a directory-comparison/merge starts. If A specifies a file but B, C or
+   the output specify a directory, then &kdiff3; uses the filename from A in the
+   specified directories.
+</para><para>
+   If "Merge" is selected, then the "Output"-line becomes editable. But it
+   is not required to specify the output filename immediately. You can also
+   postpone this until saving.
+</para><para>
+   The "Configure..."-button opens the options-dialog, so that you can set
+   the options before running the analysis.
+</para>
+</sect1>
+
+<sect1 id="pasteinput"><title>Paste and Drop Input</title>
+<para>
+   Sometimes you want to compare parts of a text that is not an own file. &kdiff3; also
+   allows you to paste text from the clipboard into the diff input window that has the focus.
+   The diff analysis happens immediately then.
+   In the open dialog you need not specify files then, but just close it via "Cancel".
+</para><para>
+   You can also use drag and drop: Drag a file from a file manager
+   or selected text from an editor and drop it onto a diff input window.
+</para><para>
+   What's the idea? Sometimes a file contains two similar functions, but checking how similar
+   they really are is a big effort if you first must create two files and then load them. Now
+   you can simply copy, paste and compare the relevant sections.
+</para><para>
+   Note: Currently you cannot drag anything from &kdiff3;. Only dropping in the diff input
+   is supported.
+</para><para>
+   Warning: Some editors still interpret the drag and drop into another program like cut
+   (instead of copy) and paste. Your original data might be lost then.
+</para>
+</sect1>
+
+<sect1 id="interpretinginformation"><title>Comparing Files And Interpreting The Information In The Input Windows</title>
+<screenshot><mediaobject>
+<imageobject><imagedata fileref="screenshot_diff.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+<sect2><title>Info Line</title><para>
+   At the top of each text window is its "info line". The info lines of
+   the input windows contain a letter "A", "B" or "C", the editable filename,
+   a button for browsing, and the line number of the first visible line in the window. 
+   (Note that window "C" is  optional.) Each info line appears in a different color.
+</para><para>
+   When you selected another file via browsing or finished editing the filename here 
+   by pressing enter, the new file will be loaded and 
+   compared with the already loaded file(s).
+</para></sect2><sect2><title>Coloring</title><para>
+   The three input windows are assigned the letters "A", "B" and "C".
+   "A"   has  color blue, "B" has green and "C" has magenta. (These are the
+   defaults,   but  can be changed in the Settings-Menu.)
+</para><para>
+   When a difference is detected then the color shows which input file
+   differs.   When both other input files differ then the color used to express
+   this is   red by default ("Conflict color" in the Settings).
+   This colorscheme is especially useful in the case of three input files, which will be
+   seen in the next section (<link linkend="merging">Merging</link>).
+</para></sect2><sect2><title>Summary Column</title><para>
+   Left of each text is the "summary column". If differences occurred on a
+   line then the summary column shows the respective color. For a white-space-only
+   difference the summary is chequered. For programming languages where white
+   space is not so important this is useful to see at one glance if anything
+   of importance was modified. (In C/C++ white space is only interesting within
+   strings, comments, for the preprocessor, and some only very esoteric situations.)
+</para><para>
+   The vertical line separating the summary column and the text is interrupted
+   if the input file had no lines there. When word-wrap is enabled then this vertical 
+   line appears dotted for wrapped lines.
+</para></sect2><sect2><title>Overview Column</title><para>
+   On the right side a "overview"-column is visible left of the vertical scrollbar.
+   It shows the compressed summary column of input "A". All the differences
+   and conflicts are visible at one glance. When only two input windows are
+   used, then all differences appear red here because every difference is
+   also   a conflict. A black rectangle frames the visible part of the inputs.
+   For  very long input files, when the number of input lines is bigger than
+   the height of the overview column in pixels, then several input lines share
+   one overview line. A conflict then has top priority over simple differences,
+   which have priority over no change, so that no difference or conflict is
+   lost here.  By clicking into this overview column the corresponding text
+   will be shown.
+</para></sect2><sect2 id="manualdiffhelp"><title>Manually Aligning Lines</title><para>
+   Sometimes the algorithm places the wrong lines next to each other. Or you want to compare 
+   one piece of text with text at a completely different position in the other file.
+   For these situations you can manually instruct &kdiff3; to align certain lines. 
+   Mark the text for which you want to improve the alignment with the mouse as you would 
+   for copy and paste in the first diff view and then choose "Add Manual Diff Alignment" 
+   in the "Diffview"-menu (keyboard shortcut <keycombo>&Ctrl;<keycap>Y</keycap></keycombo>). An orange bar will appear in 
+   the summary column next to the chosen text. Repeat this for the second and 
+   (if available) third diff view. &kdiff3; will immediately recalculate the differences everytime you do this,
+   and will align the chosen lines. Of course some of the previously matching lines in between 
+   might not match anymore.
+</para><para>
+   Currently merging doesn't support the use of manual diff help.
+</para></sect2><sect2 id="joinsplitsections"><title>Manually Joining and Splitting Diff Sections</title><para>
+   In some cases &kdiff3; will see too many or too few diff sections for merging. In such a 
+   case you can join or split existing sections.
+</para><para>
+   Add new sections by first selecting text in the lines that belong together in either input window (as for copying to the clipboard). 
+   Then choose "Split Diff At Selection" in the "Merge" menu.
+   Splits will be added above the first line and below the last line of the selected text.
+   If you only want to add one section, then select text beginning at another section-split.
+</para><para>
+   For joining sections in either input window select something in the lines from the sections to join.
+   (You can join several sections in one step too.) Then choose "Join selected Diffs" in the "Merge"-menu.
+</para></sect2>
+</sect1>
+
+
+<sect1 id="merging"><title>Merging And The Merge Output Editor Window</title>
+<screenshot><mediaobject>
+<imageobject><imagedata fileref="screenshot_merge.png" format="PNG"/></imageobject>
+</mediaobject></screenshot>
+<para>
+   The merge output editor window (below the diff input windows) also has an info line at the top showing "Output:", the
+   filename   and "[Modified]" if you edited something. Usually it will contain
+   some text  through the automatic merge facilities, but often it will also
+   contain conflicts.
+</para><para>
+   !!! Saving is disabled until all conflicts are resolved !!! (Use the "Go
+   to prev/next unsolved conflicts"-buttons to find the remaining conflicts.)
+</para><para>
+   With only two input files every difference is also a conflict that must
+   be solved manually.
+</para><para>
+   With three input files the first file is treated as base, while the
+   second   and third input files contain modifications. When at any line only
+   either   input B or input C have changed but not both then the changed source
+   will   automatically be selected. Only when B and C have changed on the same
+   lines,   then the tool detects a conflict that must be solved manually.
+   When B and C are the same, but not the same as A, then C is selected.
+</para><sect2><title>The Summary Column</title><para>
+   The merge output editor window also has a summary column on the left. It shows the
+   letter of the input from which a line was selected or nothing if all three
+   sources where equal on a line. For conflicts it shows a questionmark "?"
+   and the line shows "&lt;Merge Conflict&gt;", all in red. Because solving
+   conflicts  line by line would take very long, the lines are grouped into
+   groups that  have the same difference and conflict characteristics.
+   But only-white-space-conflicts are separated from non-white-space-conflicts
+   in order to ease the merging of files were the indentation changed for many
+   lines.
+</para></sect2><sect2 id="synchronise_views"><title>Setting The Current Group And Synchronising Merge And Diff View Position</title><para>
+   When clicking into  the summary column with the left mouse button in either
+   window then the beginning of the group belonging to that line will shown in all windows.
+   This group then becomes the "current group". It is highlighted with the
+   "Current range (diff) background color" and
+   a black bar appears on the left side of the text.
+</para></sect2><sect2><title>Choosing Inputs A, B or C For Current Conflict And Editing</title><para>
+   The button bar below the menubar contains three input selector buttons 
+   containing the letters "A", "B" and "C". Click the input selector 
+   button to insert (or remove if already inserted) the lines from the respective source.
+   To choose the lines from several inputs click the respective buttons in the
+   needed order. For example if you want that the lines from "B" appear before 
+   the lines from "A" in the output, first click "B", then "A".
+</para><para>
+   If you use the auto-advance option 
+   (<link linkend="autoadvance">"Automatically go to next unsolved conflict after source selection"</link>),
+   you should disable this before choosing lines from several inputs or if you want to
+   edit the lines after your choice. Otherwise &kdiff3; will jump to the next
+   conflict after choosing the first input.
+</para><para>
+   It is often helpful directly edit the merge output. 
+   The summary column will show "m" for every line that was manually modified. 
+   When for instance the differences are aligned in a way that simply choosing 
+   the inputs won't be satisfactory, then you can mark the needed text and use 
+   normal <link linkend="selections">copy and paste</link> to put it into the merge output.
+</para><para>
+   Sometimes, when a line is removed either by automatic merge or by editing
+   and no other lines remain in that group, then the text &lt;No src line&gt;
+   will appear in that line. This is just a placeholder for the group for
+   when  you might change your mind and select some source again. This text won't
+   appear in the saved file or in any selections you want to copy and paste.
+</para><para>
+   The text "&lt;Merge Conflict&gt;" will appear in the clipboard if you
+   copy and   paste some text containing such a line. But still be careful to
+   do so.
+</para></sect2><sect2><title>Choosing Input A, B, or C for All Conflicts</title><para>
+   The normal merge will start by solving simple conflicts automatically.
+   But the "Merge"-menu provides some actions for other common needs.
+   If you have to select the same source for most conflicts, then you can
+   choose "A", "B" or "C" everywhere, or only for the remaining unsolved
+   conflicts, or for unsolved white space conflicts. If you want to decide every
+   single delta yourself, you can "Set deltas to conflicts". Or if you want to
+   return to the automatic choices of &kdiff3; then select
+   "Automatically solve simple conflicts". &kdiff3; then restarts the merge.
+   For actions that change your previous modifications &kdiff3; will ask for your
+   confirmation before proceeding.
+</para><para>
+   Note: When choosing either source for unsolved white space conflicts and
+   the options "Ignore Numbers" or "Ignore C/C++ Comments" are used then changes in
+   numbers or comments will be treated like white space too.
+
+</para></sect2><sect2 id="vcskeywordsmergesupport"><title>Automatic Merge of Version Control Keywords and History (Log)</title><para>
+Many version control systems support special keywords in the file. (e.g. "&#36;Date&#36;", 
+"&#36;Header&#36;", "&#36;Author&#36;", "&#36;Log&#36;" etc.) During the 
+check-in the version control system (VCS) changes these lines. For instance 
+"&#36;Date&#36;" will turn into "&#36;Date: 2005/03/22 18:45:01 &#36;". Since this line will 
+be different in every version of the file, it would require manual interaction 
+during the merge.
+</para><para>
+&kdiff3; offers automatic merge for these items. For simple lines that match the 
+"Auto merge regular expression"-option in all input-files &kdiff3; will choose 
+the line from B or - if available - from C. (Additionally it is necessary that the lines 
+in question line up in the comparison and the previous line contains no conflict.)
+This auto merge can either be run immediately after a merge starts (activate the option 
+"Run regular expression auto merge on merge start") or later via the merge 
+menu "Run Regular Expression Auto Merge".
+</para><para>
+Automatic merge for version control history (also called "log") is also supported.
+The history automerge can either run immediately when the merge starts by activating the 
+option "Merge version control history on merge start" or later via the merge menu 
+"Automatically Solve History Conflicts".
+</para><para>
+Usually the version control history begins with a line containing the keyword "&#36;Log&#36;".
+This must be matched by the "History start regular expression"-option.
+&kdiff3; detects which subsequent lines are in the history by analysing the leading characters 
+that came before the "&#36;Log&#36;"-keyword. If the same "leading comment"-characters also appears in the following
+lines, then they are also included in the history.
+</para><para>
+During each check-in the VCS writes a unique line specifying version-, date- and time-information 
+followed by lines with user comments.
+These lines form one history-entry. This history section grows with every check-in and the 
+most recent entries appear at the top (after the history start line). 
+</para><para>
+When for parallel development two or more developers check-in a branch of the file then 
+the merge history will contain several entries that appear as conflicts during the merge 
+of the branches. Since merging these can become very tedious, &kdiff3; offers support with two 
+possible strategies: Just insert the history information from both contributors at the top
+or sort the history information by a user defined key.
+</para><para>
+The just-insert-all-entries-method is easier to configure. &kdiff3; just needs a method to
+detect, which lines belong to one history entry. Most VCS insert an empty line after each
+history entry. If there are no other empty lines, this is a sufficient criterion for &kdiff3;.
+Just set an empty "History entry start regular expression". If the empty line criterion 
+isn't sufficient, you can specify a regular expression to detect the history entry start.
+</para><para>
+Note that &kdiff3; will remove duplicate history entries. If a history entry appeared several times
+in the history of a input file, only one entry will remain in the output.
+</para><para>
+If you want to sort the history, then you have to specify how the sort key should be built.
+Use parentheses in the "History entry start regular expression" to group parts of the regular 
+expression that should later be used for the sort key.
+Then specify the "History entry start sort key order" specifying a comma "," separated list of 
+numbers referring to the position of the group in the regular expression.
+</para><para>
+Because this is not so easy to get right immediately, you are able to test and improve
+the regular expressions and key-generation in a dedicated dialog by pressing the 
+"Test your regular expressions"-button.
+</para><para>Example: Assume a history that looks like this:
+<screen>
+/**************************************************************************
+** HISTORY:    &#36;Log: \toms_merge_main_view\MyApplication\src\complexalgorithm.cpp &#36;
+**
+**     \main\integration_branch_12   2 Apr 2001 10:45:41   tom
+**  Merged branch simon_branch_15.
+**
+**     \main\henry_bugfix_branch_7\1   30 Mar 2001 19:22:05   henry
+**  Improved the speed for subroutine convertToMesh().
+**  Fixed crash.
+**************************************************************************/
+</screen>
+The history start line matches the regular expression ".*\&#36;Log.*\&#36;.*". Then follow 
+the history entries.
+</para><para>
+The line with the "&#36;Log&#36;"-keyword begins with two "*" after which follows a space. 
+&kdiff3; uses the first non-white-space string as "leading comment" and assumes that
+the history ends in the first line without this leading comment. In this example the
+last line ends with a string that also starts with two "*", but instead of a space 
+character more "*" follow. Hence this line ends the history.
+</para><para>
+If history sorting isn't required then the history entry start line regular expression
+could look like this. (This line is split in two because it wouldn't fit otherwise.)
+<screen>
+\s*\\main\\\S+\s+[0-9]+ (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
+ [0-9][0-9][0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\s+.*
+</screen>
+For details about regular expressions please see the
+<ulink url="http://doc.trolltech.com/3.3/qregexp.html#details">regular expression documentation by Trolltech</ulink>.
+Note that "\s" (with lowercase "s") matches any white space and "\S" (with uppercase "S") matches any non-white-space.
+In our example the history entry start contains first the version info with reg. exp. "\\main\\\S+", the date consisting of day "[0-9]+", month "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)" and year "[0-9][0-9][0-9][0-9]", the time "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]" and finally the developers login name ".*".
+</para><para>
+Note that the "leading comment"-characters (in the example "**") will already be removed by &kdiff3; 
+before trying to match, hence the regular expression begins with a match for none or more white-space characters "\s*".
+Because comment characters can differ in each file (e.g. C/C++ uses other comment characters than a Perl script)
+&kdiff3; takes care of the leading comment characters and you should not specify them in the regular expression.
+</para><para>
+If you require a sorted history. Then the sortkey must be calculated. For this the 
+relevant parts in the regular expression must be grouped by parentheses. 
+(The extra parentheses can also stay in if history sorting is disabled.)
+<screen>
+\s*\\main\\(\S+)\s+([0-9]+) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
+ ([0-9][0-9][0-9][0-9]) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9])\s+(.*)
+</screen>
+The parentheses now contain 1. version info, 2. day, 3. month, 4. year, 5. time, 6. name. 
+But if we want to sort by date and time, we need to construct a key with the elements in a different order of appearance:
+First the year, followed by month, day, time, version info and name. Hence the sortkey order to specify is "4,3,2,5,1,6".
+</para><para>
+Because  month names aren't good for sorting ("Apr" would be first) &kdiff3; detects in which order 
+the month names were given and uses that number instead ("Apr"->"04"). 
+And if a pure number is found it will be transformed to a 4-digit value with leading zeros for sorting.
+Finally the resulting sort key for the first history entry start line will be:
+<screen>
+2001 04 0002 10:45:41 integration_branch_12   tom 
+</screen>
+</para><para>
+For more information also see <link linkend="mergeoptions">Merge Settings</link>.
+</para>
+</sect2>
+</sect1>
+
+<sect1 id="navigation"><title>Navigation And Editing</title>
+<para>
+   Much navigation will be done with the scroll bars and the mouse but
+   you  can also navigate with the keys. If you click into either window then
+   you  can use the cursor buttons left, right, up, down, page up, page down,
+   home,  end, ctrl-home, ctrl-end as you would in other programs. The overview-column
+   next to the vertical scroll bar of the input files can also be used  for
+   navigating by clicking into it.
+</para><para>
+   You can also use the wheel mouse to scroll up and down.
+</para><para>
+   In the merge output editor you can also use the other keys for editing.
+   You can toggle between insert and overwrite mode with the insert key. (Default
+   is insert-mode.)
+</para><para>
+   A left-mouse-button-click into any summary column will synchronise all
+   windows to show the beginning of the same group of lines (as explained
+   in section <link linkend="synchronise_views">"Setting The Current Group And Synchronising Merge And Diff View Position"</link>).
+</para><para>
+   The button bar also contains nine navigation buttons with which you can
+   jump to the current/first/last difference, to the next/previous difference
+   (ctrl-down/ctrl-up), to the next/previous conflict (ctrl-pgdown/ctrl-pgup),
+   or to the next/previous unsolved conflict. Note that for &kdiff3; a "conflict"
+   that was not automatically solved at the start of the merge stays a "conflict"
+   even if it is solved. Hence the necessity to distinguish "unsolved conflicts".
+</para>
+<sect2 id="autoadvance"><title>Auto-Advance</title>
+<para>
+   There also is a button "Automatically go to next unsolved conflict after
+   source selection" (Auto-Advance). If you enable this, then, when one source
+   is selected, &kdiff3; will jump to and select the next unsolved conflict
+   automatically. This can help when you always want to choose one source only.
+   When you need both sources, or you want to edit after selecting, then you
+   probably want to switch this off. Before proceeding to the next unsolved conflict
+   &kdiff3; shows you the effect of your choice for a short time. This delay is
+   adjustable in the Diff- &amp; Merge-Settings: You can
+   specify the "Auto-Advance delay" in milli seconds between 0 and 2000. Hint:
+   Tired of many clicks? - Use a small Auto-Advance-delay and the shortcuts
+   Ctrl-1/2/3 to select A/B/C for many conflicts.
+</para>
+</sect2>
+</sect1>
+
+<sect1 id="selections"><title>Select, Copy And Paste</title>
+<para>
+   The input windows don't show a cursor, so selections must be made
+   with   the mouse by clicking with the left mouse button at the start, holding
+   down   the mousebutton and moving to the end, where you release the mouse
+   button   again. You can also select a word by double clicking it. In the merge
+   output   editor you can also select via the keyboard by holding the "shift"-button
+   and navigation with the cursor keys.
+</para><para>
+   If the selection exceeds the visible range you can move the mouse over the 
+   window borders which causes &kdiff3; to scroll in that direction. 
+</para><para>
+   For very large selections you can also use the navigation keys while holding down 
+   the mouse. E.g. use page up and page down to quickly go to a certain position. At the 
+   end position release the mouse button.
+</para><para>
+   In order to select everything in the current window use menu "Edit"->"Select All" (Ctrl-A).
+</para><para>
+   To copy to the clipboard you must press the "Copy"-button (Ctrl-C or Ctrl-Insert).
+   But there exists an option "Auto Copy Selection". If this is enabled,
+   then whatever you select is copied immediately and you don't need to explicitly
+   copy. But pay attention when using this because the contents of the clipboard
+   might then be destroyed accidentally.
+</para><para>
+   "Cut" (Ctrl-X or Shift-Delete) copies to the clipboard and deletes the
+   selected text.
+</para><para>
+   "Paste" (Ctrl-V or Shift-Insert) inserts the text in the clipboard at the 
+   cursorposition or over the current selection.
+   If you paste to either diff input window the contents of the clipboard will 
+   be shown in that window and the comparison will restart immediately. This is 
+   useful if you want to quickly grab a piece of text from somewhere and 
+   compare it with something else without first creating files.
+</para>
+</sect1>
+
+<sect1 id="saving"><title>Saving</title>
+<para>
+   Saving will only be allowed, when all conflicts were solved. If the file
+   already exists and the "Backup files"-option is enabled then the existing
+   file will be renamed with an ".orig"-extension, but if such a file exists
+   it will be deleted. When you exit or start another diff-analysis and data
+   wasn't saved yet, then &kdiff3; will ask if you want to save, cancel or proceed
+   without saving. (&kdiff3; does not catch any signals. So if you "kill" &kdiff3;
+   then your data will be lost.)
+</para><para>
+   Line endings are saved according to the normal method on the underlying
+   operating system. For Unices each line ends with an linefeed-character "\n",
+   while for Win32-based systems each line ends with a carriage-return + a linefeed
+   "\r\n". &kdiff3; does not preserve the line-endings of the input files, which
+   also means that you shouldn't use &kdiff3; with binary files.
+</para>
+</sect1>
+
+<sect1 id="find"><title>Finding Strings</title>
+<para>
+   You can search for a string in any text-window of &kdiff3;. The "Find ..."-command
+   (Ctrl-F) in the Edit-menu opens a dialog that lets you specify the string
+   to search for. You can also select the windows which should be searched.
+   Searching will always start at the top. Use the "Find Next"-command (F3)
+   to proceed to the next occurrence. If you select to search several windows then the first
+   window will be searched from top to bottom before the search starts in the next
+   window at the top again, etc.
+</para>
+</sect1>
+
+<sect1 id="printing"><title>Printing</title>
+<para>
+   &kdiff3; supports printing for textfile differences. The "Print..."-command (Ctrl-P) 
+   in the File-menu opens a dialog that allows you to select the printer and to adjust
+   other options.
+</para><para>
+   There are several possibilities to adjust the range. Due to different printing 
+   dialogs on different operating systems, the method to achieve certain range selections varies.
+</para>
+<variablelist>
+   <varlistentry><term>All:</term><listitem><para>Print everything.</para></listitem></varlistentry>
+   <varlistentry><term>Current:</term><listitem><para>Print a page starting at the first visible line in the window. 
+       (On systems without this option this can be achieved by specifying page number 10000 for printing.)</para></listitem></varlistentry>
+   <varlistentry><term>Selection:</term><listitem><para>
+       Before choosing to print select text with the mouse (like for copy and paste) 
+       in one of the diff input windows to define the start and end line. If no text 
+       in one of the diff input windows was selected, then this won't be an available 
+       choice. (On systems without this option this can be achived by specifying page 
+       number 9999 for printing.)</para></listitem></varlistentry>
+   <varlistentry><term>Range:</term><listitem><para>Specify the first and last page.
+       </para></listitem></varlistentry>
+</variablelist>
+<para>
+   Other important options for printing will be taken from the normal options:
+</para><itemizedlist>
+   <listitem><para>Font, font size</para></listitem>
+   <listitem><para>Show line numbers</para></listitem>
+   <listitem><para>Word wrap</para></listitem>
+   <listitem><para>Colors</para></listitem>
+   <listitem><para>etc.</para></listitem>
+</itemizedlist>
+<para>
+   Landscape formatting is also recommended for printing.
+</para>
+</sect1>
+
+<sect1 id="options"><title>Options</title>
+<para>
+   Options and the recent-file-list will be saved when you exit the program,
+   and reloaded when you start it. (Menu Settings->Configure &kdiff3; ...)
+</para>
+<sect2><title>Font</title>
+<para>
+   Select a fixed width font. (On some systems this dialog will also
+   present    variable width fonts, but you should not use them.)
+</para>
+<variablelist>
+   <varlistentry><term><emphasis>Italic Font for Deltas:</emphasis></term><listitem><para> If you select this, then text differences
+       will be drawn with the italic version of the selected font. If the font
+       doesn't  support italic, then this does nothing.</para>
+   </listitem></varlistentry>
+</variablelist>
+</sect2>
+
+<sect2><title>Colors</title>
+<variablelist>
+   <varlistentry><term><emphasis>Foreground color:</emphasis></term><listitem><para> Usually black. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Background color:</emphasis></term><listitem><para> Usually white. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Diff Background color:</emphasis></term><listitem><para> Usually light gray. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Color A:</emphasis></term><listitem><para> Usually dark blue. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Color B:</emphasis></term><listitem><para> Usually dark green. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Color C:</emphasis></term><listitem><para> Usually dark magenta. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Conflict Color:</emphasis></term><listitem><para> Usually red.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Current range background color:</emphasis></term><listitem><para> Usually light yellow.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Current range diff background color:</emphasis></term><listitem><para> Usually dark yellow.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Color for manually selected diff ranges:</emphasis></term><listitem><para> Usually orange.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Newest file color in directory comparison:</emphasis></term><listitem><para> Usually green.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Oldest file color in directory comparison:</emphasis></term><listitem><para> Usually red.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Middle age file color in directory comparison:</emphasis></term><listitem><para> Usually dark yellow.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Color for missing files in directory comparison:</emphasis></term><listitem><para> Usually black.</para></listitem></varlistentry>
+</variablelist>
+<para>
+   Changing the colors for directory comparison will be effective only when starting the next directory comparison.
+</para>
+<para>
+   On systems with only 16 or 256 colors some colors are not available in pure
+   form. On such systems the "Defaults"-button will choose a pure color.
+</para>
+</sect2>
+
+<sect2><title>Editor Settings</title>
+<variablelist>
+   <varlistentry><term><emphasis>Tab inserts spaces:</emphasis></term><listitem><para> If this is disabled and you press the
+      tabulator key,   a tab-character is inserted, otherwise the appropriate
+      amount  of characters   is inserted.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>   Tab size:</emphasis></term><listitem><para> Can be adjusted for your specific needs. Default is 8. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>   Auto indentation:</emphasis></term><listitem><para> When pressing Enter or Return the indentation
+      of the previous  line is used for the new line. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>   Auto copy selection:</emphasis></term><listitem><para> Every selection is immediately copied
+      to the clipboard   when active and you needn't explicitly copy it. </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>   Line end style:</emphasis></term><listitem><para> When saving you can select what line 
+      end style you prefer. The default setting is the common choice for the used operating system. </para></listitem></varlistentry>
+</variablelist>
+</sect2>
+
+<sect2 id="diffoptions"><title>Diff Settings</title>
+<para>
+   When comparing files, &kdiff3; first it tries to match lines that are equal
+   in all input files. Only during this step it might ignore white space. The
+   second step compares each line. In this step white space will not be ignored.
+   Also during the merge white space will not be ignored.
+</para>
+
+<variablelist>
+   <varlistentry><term><emphasis>Ignore numbers:</emphasis></term><listitem><para> Default is off. Number characters ('0'-'9', '.', '-')
+      will be ignored  in the first  part of the analysis in which the line matching is
+      done. In the result the differences will be shown nevertheless, but they are treated
+      as white space.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Ignore C/C++ comments:</emphasis></term><listitem><para> Default is off.
+      Changes in comments will be treated like changes in white space.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Ignore case:</emphasis></term><listitem><para>  Default is off. 
+      Case-differences of characters (like 'A' vs. 'a') will be treated like changes in white space.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Preprocessor-Command:</emphasis></term><listitem><para>
+   See <link linkend="preprocessors">next section</link>.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Line-Matching Preprocessor-Command:</emphasis></term><listitem><para>
+   See <link linkend="preprocessors">next section</link>.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Try Hard:</emphasis></term><listitem><para>
+      Try hard to find an even smaller delta. (Default is on.) This will probably
+      be effective for complicated and big files. And slow for very big files.
+   </para></listitem></varlistentry>
+</variablelist>
+</sect2>
+
+<sect2 id="mergeoptions"><title>Merge Settings</title>
+<variablelist>
+   <varlistentry><term><emphasis>Auto Advance Delay (ms):</emphasis></term><listitem><para> When in auto-advance-mode this setting specifies
+      how long to show the result of the selection before jumping to the next unsolved
+      conflict.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>White space 2/3-file merge default:</emphasis></term><listitem><para>
+      Automatically solve all white-space conflict by choosing the specified file.
+      (Default is manual choice.) Useful if white space really isn't important in many files.
+      If you need this only occasionally better use "Choose A/B/C For All Unsolved Whitespace Conflicts"
+      in the merge menu. Note that if you enable either "Ignore numbers" or "Ignore C/C++ comments"
+      then this auto-choice also applies for conflicts in numbers or comments.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Auto merge regular expression:</emphasis></term><listitem><para>
+      Regular expression for lines where &kdiff3; should automatically choose one source. See also <link linkend="vcskeywordsmergesupport">Automatic Merge ...</link>
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Run regular expression auto merge on merge start:</emphasis></term><listitem><para>
+      If activated &kdiff3; runs the automatic merge using the "Auto merge regular expression" when a merge is started.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>History start regular expression:</emphasis></term><listitem><para>
+      Regular expression for the start of the merge history entry.
+      Usually this line contains the "&#36;Log&#36;"-keyword.
+      Default value: ".*\&#36;Log.*\&#36;.*"
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>History entry start regular expression:</emphasis></term><listitem><para>
+      A merge history entry consists of several lines.
+      Specify the regular expression to detect the first line (without the leading comment).
+      Use parentheses to group the keys you want to use for sorting.
+      If left empty, then &kdiff3; assumes that empty lines separate history entries.
+      See also <link linkend="vcskeywordsmergesupport">Automatic Merge ...</link>
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>History merge sorting:</emphasis></term><listitem><para>
+      Enable version control history sorting.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>History entry start sort key order:</emphasis></term><listitem><para>
+      Each parentheses used in the regular expression for the history start entry
+      groups a key that can be used for sorting.
+      Specify the list of keys (that are numbered in order of occurrence
+      starting with 1) using ',' as separator (e.g. "4,5,6,1,2,3,7").
+      If left empty, then no sorting will be done.
+      See also <link linkend="vcskeywordsmergesupport">Automatic Merge ...</link>
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Merge version control history on merge start:</emphasis></term><listitem><para>
+      If activated &kdiff3; runs the automatic history merging using aforementioned options when a merge is started.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Max number of history entries:</emphasis></term><listitem><para>
+      &kdiff3; truncates the history list after the specified number of entries. Use -1 to avoid truncation. (Default is -1).
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Test your regular expressions</emphasis></term><listitem><para>
+      This button shows a dialog that allows you to improve and test the regular expressions above.
+      Just copy the respective data from your files into the example lines. The "Match results" 
+      will immediately show whether the match succeeds or not.
+      The "Sort key result" will display the key used for history merge sorting.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Irrelevant merge command:</emphasis></term><listitem><para>
+      Specify a command of your own that should be called when &kdiff3; detects 
+      that for a three file merge the file from B doesn't contribute any 
+      relevant data that isn't already contained in the file from C.
+      The command is called with the three filenames as parameters.
+      Data matched by the "Auto merge regular expression" or in the 
+      history isn't considered relevant.
+   </para></listitem></varlistentry>
+</variablelist>
+
+</sect2>
+
+<sect2><title>Directory Merge</title>
+<para>
+   These options are concerned with scanning the directory and handling the
+   merge: See the <link linkend="dirmergeoptions">Directory Comparison/Merge
+   Docs</link> for details.
+</para><para>
+Yet there is one option here that is also relevant for saving single files:
+</para>
+<variablelist>
+   <varlistentry><term><emphasis>Backup files:</emphasis></term><listitem><para> When a file is saved and an older version already
+      exists, then the original version will be renamed with an ".orig" extension.
+      If an old backup file with ".orig" extension already exists then this will
+      be deleted without backup.
+   </para></listitem></varlistentry>
+</variablelist>
+</sect2>
+
+<sect2><title>Regional and Language Options</title>
+  <variablelist>
+    <varlistentry><term><emphasis>Language:</emphasis></term><listitem><para>Adjust the language of the user interface. Changing this option doesn't affect the running program. You have to exit and restart &kdiff3; so that the language is changed. (This option is not available in the Frameworks version of &kdiff3;.)
+       </para></listitem></varlistentry>
+    <varlistentry><term><emphasis>Use the same encoding for everything:</emphasis></term><listitem><para> The following encoding options can be adjusted separately for each item or if this option is true, all values will take the first value.
+       </para></listitem></varlistentry>
+    <varlistentry><term><emphasis>Local Encoding:</emphasis></term><listitem><para>Above the codec-selectors appears a note that tells you what the local encoding is. (This is not adjustable but for your information just in case you don't know your local encoding, but need to select it.)
+       </para></listitem></varlistentry>
+    <varlistentry><term><emphasis>File Encoding for A/B/C:</emphasis></term><listitem><para> Adjust the file encoding for input files. This has an effect on how the special characters are interpreted. Since you can adjust each codec separately you can even compare and merge files that were saved using different codecs.
+       </para></listitem></varlistentry>
+    <varlistentry><term><emphasis>File Encoding for Merge Output and Saving:</emphasis></term><listitem><para> When you have edited a file, then you can adjust which encoding will be used when saving to disk.
+       </para></listitem></varlistentry>
+    <varlistentry><term><emphasis>File Encoding for Preprocessor Files:</emphasis></term><listitem><para>When you define preprocessors then they might not be able to operate on your codec. (e.g.: Your files are 16-bit-unicode and your preprocessor can only take 8-bit-ascii.) With this option you can define the encoding of preprocessor output.
+       </para></listitem></varlistentry>
+    <varlistentry><term><emphasis>Right To Left Language:</emphasis></term><listitem><para>Some languages are written right to left. When this option is enabled, &kdiff3; draws the text from right to left in the diff input windows and in the merge output window. Note that if you start &kdiff3; with the command line option "--reverse" then all layouting will be done right to left too. (This is a feature provided by Qt.) This documentation was written assuming that "Right To Left Language" or reverse layout are disabled. So some references to "left" or "right" must be replaced by their respective counterpart if you use these options.
+       </para></listitem></varlistentry>
+
+  </variablelist>
+</sect2>
+
+<sect2><title>Miscellaneous</title>
+<para>(These options and actions are available in menus or the buttonbar.)</para>
+<variablelist>
+  <varlistentry><term><emphasis>Show line numbers:</emphasis></term><listitem><para> You can select if line numbers should be
+     shown for the input files.</para></listitem></varlistentry>
+  <varlistentry><term><emphasis>Show space and tabulator characters for differences:</emphasis></term><listitem><para> Sometimes
+     the visible spaces and tabs are disturbing. You can turn this off.</para></listitem></varlistentry>
+  <varlistentry><term><emphasis>Show white space:</emphasis></term><listitem><para> Turn this off to suppress
+      any highlighting of white-space-only changes in the text or overview-columns.
+      (Note that this also applies to changes in numbers or comments if the options "Ignore numbers"
+      or "Ignore C/C++ comments" are active.)</para></listitem></varlistentry>
+  <varlistentry><term><emphasis>Overview options:</emphasis></term><listitem><para>
+     These choices are only available when you compare three files. In normal mode all
+     differences are shown in one color-coded overview-column. But sometimes you are  
+     especially interested in the differences between only two of these three files.
+     Selecting "A vs. B", "A vs. C" or "B vs. C"-overview will show a second overview 
+     column with the required information next to the normal overview.
+  </para></listitem></varlistentry>
+  <varlistentry><term><emphasis>Word wrap diff windows:</emphasis></term><listitem><para>
+     Wrap lines when their length would exceed the width of a window.
+  </para></listitem></varlistentry>
+  <varlistentry><term><emphasis>Show Window A/B/C:</emphasis></term><listitem><para> Sometimes you want to use the space on
+      the screen better for long lines. Hide the windows that are not important.
+      (In the Windows-menu.)</para></listitem></varlistentry>
+  <varlistentry><term><emphasis>Toggle Split Orientation:</emphasis></term><listitem><para>
+      Switch between diff windows shown next to each other (A left of B left of C) or above
+      each other (A above B above C). This should also help for long lines. (In the Windows-menu.)
+      </para></listitem></varlistentry>
+  <varlistentry><term><emphasis>Start a merge quickly:</emphasis></term><listitem><para>
+      Sometimes you are viewing the deltas and decide to merge.
+      <inlinemediaobject><imageobject><imagedata fileref="merge_current.png" format="PNG"/></imageobject></inlinemediaobject>
+      "Merge current file" in the Directory-menu also works if you only compare
+      two files. A single click starts the merge and uses the filename of the last
+      input-file as the default output filename. (When this is used to restart
+      a merge, then the output filename will be preserved.)</para></listitem></varlistentry>
+</variablelist>
+</sect2>
+
+<sect2 id="shortcuts"><title>Configuring Keyboard-Shortcuts</title>
+<para>
+   Currently only the Frameworks-version supports user-configurable keyboard-shortcuts.
+   (Menu Settings->Configure Shortcuts...)
+</para>
+</sect2>
+</sect1>
+
+<sect1 id="preprocessors"><title>Preprocessor Commands</title>
+<para>
+&kdiff3; supports two preprocessor options.
+</para><para>
+<variablelist>
+   <varlistentry><term><emphasis>Preprocessor-Command:</emphasis></term><listitem><para>
+      When any file is read, it will be piped through this external command.
+      The output of this command will be visible instead of the original file.
+      You can write your own preprocessor that fulfills your specific needs.
+      Use this to cut away disturbing parts of the file, or to automatically
+      correct the indentation etc.
+   </para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Line-Matching Preprocessor-Command:</emphasis></term><listitem><para>
+      When any file is read, it will be piped through this external command. If
+      a preprocessor-command (see above) is also specified, then the output of the
+      preprocessor is the input of the line-matching preprocessor.
+      The output will only be used during the line matching phase of the analysis.
+      You can write your own preprocessor that fulfills your specific needs.
+      Each input line must have a corresponding output line.
+   </para></listitem></varlistentry>
+</variablelist>
+</para>
+<para>
+The idea is to allow the user greater flexibility while configuring the diff-result.
+But this requires an external program, and many users don't want to write one themselves.
+The good news is that very often <command>sed</command> or <command>perl</command> 
+will do the job. 
+</para>
+<para>Example: Simple testcase: Consider file a.txt (6 lines):
+<screen>
+      aa
+      ba
+      ca
+      da
+      ea
+      fa
+</screen>
+And file b.txt (3 lines):
+<screen>
+      cg
+      dg
+      eg
+</screen>
+Without a preprocessor the following lines would be placed next to each other:
+<screen>
+      aa - cg
+      ba - dg
+      ca - eg
+      da
+      ea
+      fa
+</screen>
+This is probably not wanted since the first letter contains the actually interesting information.
+To help the matching algorithm to ignore the second letter we can use a line matching preprocessor 
+command, that replaces 'g' with 'a':
+<screen>
+   <command>sed</command> 's/g/a/'
+</screen>
+With this command the result of the comparison would be:
+<screen>
+      aa
+      ba
+      ca - cg
+      da - dg
+      ea - eg
+      fa
+</screen>
+Internally the matching algorithm sees the files after running the line matching preprocessor,
+but on the screen the file is unchanged. (The normal preprocessor would change the data also on 
+the screen.)
+</para>
+
+<sect2 id="sedbasics"><title><command>sed</command> Basics</title>
+<para>
+This section only introduces some very basic features of <command>sed</command>. For more
+information see <ulink url="info:/sed">info:/sed</ulink> or 
+<ulink url="http://www.gnu.org/software/sed/manual/html_mono/sed.html">
+http://www.gnu.org/software/sed/manual/html_mono/sed.html</ulink>.
+A precompiled version for Windows can be found at <ulink url="http://unxutils.sourceforge.net">
+http://unxutils.sourceforge.net</ulink>.
+Note that the following examples assume that the <command>sed</command>-command is in some 
+directory in the PATH-environment variable. If this is not the case, you have to specify the full absolute
+path for the command. 
+</para>
+<para>
+In this context only the <command>sed</command>-substitute-command is used:
+<screen>
+   <command>sed</command> 's/<replaceable>REGEXP</replaceable>/<replaceable>REPLACEMENT</replaceable>/<replaceable>FLAGS</replaceable>'
+</screen>
+Before you use a new command within &kdiff3;, you should first test it in a console.
+Here the <command>echo</command>-command is useful. Example:
+<screen>
+   <command>echo</command> abrakadabra | <command>sed</command> 's/a/o/'
+   -> obrakadabra
+</screen>
+This example shows a very simple sed-command that replaces the first occurance 
+of "a" with "o". If you want to replace all occurances then you need the "g"-flag:
+<screen>
+   <command>echo</command> abrakadabra | <command>sed</command> 's/a/o/g'
+   -> obrokodobro
+</screen>
+The "|"-symbol is the pipe-command that transfers the output of the previous 
+command to the input of the following command. If you want to test with a longer file
+then you can use <command>cat</command> on Unix-like systems or <command>type</command> 
+on Windows-like systems. <command>sed</command> will do the substitution for each line.
+<screen>
+   <command>cat</command> <replaceable>filename</replaceable> | <command>sed</command> <replaceable>options</replaceable>
+</screen>
+</para>
+</sect2>
+<sect2 id="sedforkdiff3"><title>Examples For <command>sed</command>-Use In &kdiff3;</title>
+<sect3><title>Ignoring Other Types Of Comments</title>
+<para>
+Currently &kdiff3; understands only C/C++ comments. Using the
+Line-Matching-Preprocessor-Command you can also ignore
+other types of comments, by converting them into C/C++-comments.
+
+Example: To ignore comments starting with "#", you would like to convert them
+to "//". Note that you also must enable the "Ignore C/C++-Comments" option to get 
+an effect. An appropriate Line-Matching-Preprocessor-Command would be:
+
+<screen>
+   <command>sed</command> 's/#/\/\//'
+</screen>
+Since for <command>sed</command> the "/"-character has a special meaning, it is necessary to place the 
+"\"-character before each "/" in the replacement-string. Sometimes the "\" is required
+to add or remove a special meaning of certain characters. The single quotation marks (') are only important
+when testing on the command shell as it will otherwise attempt to process some characters.
+KDiff3 does not do this except for the escape sequences '\"' and '\\'.
+</para>
+</sect3>
+<sect3><title>Caseinsensitive Diff</title>
+<para>
+Use the following Line-Matching-Preprocessor-Command to convert all input to uppercase:
+<screen>
+   <command>sed</command> 's/\(.*\)/\U\1/'
+</screen>
+Here the ".*" is a regular expression that matches any string and in this context matches 
+all characters in the line. 
+The "\1" in the replacement string refers to the matched text within the first pair of "\(" and "\)".
+The "\U" converts the inserted text to uppercase.
+</para>
+</sect3>
+
+<sect3><title>Ignoring Version Control Keywords</title>
+<para>
+CVS and other version control systems use several keywords to insert automatically
+generated strings (<ulink url="info:/cvs/Keyword substitution">info:/cvs/Keyword substitution</ulink>).
+All of them follow the pattern "$KEYWORD generated text$". We now need a
+Line-Matching-Preprocessor-Command that removes only the generated text:
+<screen>
+   <command>sed</command> 's/\$\(Revision\|Author\|Log\|Header\|Date\).*\$/\$\1\$/'
+</screen>
+The "\|" separates the possible keywords. You might want to modify this list 
+according to your needs.
+The "\" before the "$" is necessary because otherwise the "$" matches the end of the line.
+</para>
+<para>
+While experimenting with <command>sed</command> you might come to understand and even like
+these regular expressions. They are useful because there are many other programs that also 
+support similar things.
+</para>
+</sect3>
+
+<sect3><title>Ignoring Numbers</title>
+<para>
+Ignoring numbers actually is a built-in option. But as another example, this is how
+it would look as a Line-Matching-Preprocessor-command.
+<screen>
+   <command>sed</command> 's/[0123456789.-]//g'
+</screen>
+Any character within '[' and ']' is a match and will be replaced with nothing.
+</para>
+</sect3>
+
+<sect3><title>Ignoring Certain Columns</title>
+<para>
+Sometimes a text is very strictly formatted, and contains columns that you always want to ignore, while there are
+other columns you want to preserve for analysis. In the following example the first five columns (characters) are 
+ignored, the next ten columns are preserved, then again five columns are ignored and the rest of the line is preserved.
+<screen>
+   <command>sed</command> 's/.....\(..........\).....\(.*\)/\1\2/'
+</screen>
+Each dot '.' matches any single character. The "\1" and "\2" in the replacement string refer to the matched text within the first 
+and second pair of "\(" and "\)" denoting the text to be preserved.
+</para>
+</sect3>
+
+<sect3><title>Combining Several Substitutions</title>
+<para>
+Sometimes you want to apply several substitutions at once. You can then use the 
+semicolon ';' to separate these from each other. Example:
+<screen>
+   <command>echo</command> abrakadabra | <command>sed</command> 's/a/o/g;s/\(.*\)/\U\1/'
+   -> OBROKODOBRO
+</screen>
+</para>
+</sect3>
+
+<sect3><title>Using <command>perl</command> instead of <command>sed</command></title>
+<para>
+Instead of <command>sed</command> you might want to use something else like 
+<command>perl</command>.
+<screen>
+   <command>perl</command> -p -e 's/<replaceable>REGEXP</replaceable>/<replaceable>REPLACEMENT</replaceable>/<replaceable>FLAGS</replaceable>'
+</screen>
+But some details are different in <command>perl</command>. Note that where 
+<command>sed</command> needed "\(" and "\)" <command>perl</command>
+requires the simpler "(" and ")" without preceding '\'. Example:
+<screen>
+   <command>sed</command> 's/\(.*\)/\U\1/'
+   <command>perl</command> -p -e 's/(.*)/\U\1/'
+</screen>
+</para>
+</sect3>
+</sect2>
+
+<sect2><title>Order Of Preprocessor Execution</title>
+<para>
+The data is piped through all internal and external preprocessors in the 
+following order:
+</para>
+<itemizedlist>
+<listitem><para>Normal preprocessor,</para></listitem>
+<listitem><para>Line-Matching-Preprocessor,</para></listitem>
+<listitem><para>Ignore case (conversion to uppercase),</para></listitem>
+<listitem><para>Detection of C/C++ comments,</para></listitem>
+<listitem><para>Ignore numbers,</para></listitem>
+<listitem><para>Ignore white space</para></listitem>
+</itemizedlist>
+<para>
+The data after the normal preprocessor will be preserved for display and merging. The
+other operations only modify the data that the line-matching-diff-algorithm sees.
+</para><para>
+In the rare cases where you use a normal preprocessor note that 
+the line-matching-preprocessor sees the output of the normal preprocessor as input.
+</para>
+</sect2>
+
+<sect2><title>Warning</title>
+<para>
+The preprocessor-commands are often very useful, but as with any option that modifies
+your texts or hides away certain differences automatically, you might accidentally overlook 
+certain differences and in the worst case destroy important data.
+</para><para>
+For this reason during a merge if a normal preprocessor-command is being used &kdiff3; 
+will tell you so and ask you if it should be disabled or not. 
+But it won't warn you if a Line-Matching-Preprocessor-command is active. The merge will not complete until
+all conflicts are solved. If you disabled "Show White Space" then the differences that
+were removed with the Line-Matching-Preprocessor-command will also be invisible. If the 
+Save-button remains disabled during a merge (because of remaining conflicts), make sure to enable 
+"Show White Space". If you don't want to merge these less important differences manually
+you can select "Choose [A|B|C] For All Unsolved White space Conflicts" in the Merge-menu.
+</para>
+</sect2>
+</sect1>
+</chapter>
+
+
+<chapter id="dirmerge"><title>Directory Comparison and Merge with &kdiff3;</title>
+<sect1 id="dirmergeintro"><title>Introduction into Directory Comparison and Merge</title>
+<para>
+   Often programmers must modify many files in a directory to achieve their
+   purpose. For this &kdiff3; also lets you compare and merge complete directories
+   recursively!
+</para><para>
+   Even though comparing and merging directories seems to be quite obvious,
+   there are several details that you should know about. Most important is of
+   course the fact that now many files might be affected by each operation.
+   If you don't have backups of your original data, then it can be very hard
+   or even impossible to return to the original state. So before starting a merge,
+   make sure that your data is safe, and going back is possible. If you make
+   an archive or use some version control system is your decision, but even
+   experienced programmers and integrators need the old sources now and then.
+   And note that even though I (the author of &kdiff3;) try to do my best, I can't
+   guarantee that there are no bugs. According to the GNU-GPL there is NO WARRANTY
+   whatsoever for this program. So be humble and always keep in mind:
+</para>
+<blockquote><para>
+   <emphasis>To err is human, but to really mess things up you need a computer.</emphasis>
+</para></blockquote>
+<para>
+So this is what this program can do for you: &kdiff3; ...
+</para>
+<itemizedlist>
+     <listitem><para>... reads and compares two or three directories recursively,</para></listitem>
+     <listitem><para>... takes special care of symbolic links,</para></listitem>
+     <listitem><para>... lets you browse files on mouse double click,</para></listitem>
+     <listitem><para>... for each item proposes a merge operation, which you can change
+                         before starting the directory merge,</para></listitem>
+     <listitem><para>... lets you simulate the merge and lists the actions that would
+                         take  place, without actually doing them,</para></listitem>
+     <listitem><para>... lets you really do the merge, and lets you interact whenever
+                         manual interaction is needed,</para></listitem>
+     <listitem><para>... lets you run the selected operation for all items (key F7) or the selected item (key F6),</para></listitem>
+     <listitem><para>... lets you continue the merge after manual interaction with key F7,</para></listitem>
+     <listitem><para>... optionally creates backups, with the ".orig" extension,</para></listitem>
+     <listitem><para>...</para></listitem>
+</itemizedlist>
+</sect1>
+
+<sect1 id="startingdirmerge"><title>Starting Directory Comparison Or Merge</title>
+<para>
+   This is very similar to the single file merge and comparison. You just
+   have  to specify directories on the command line or in the file-open
+   dialog.
+</para>
+<sect2><title>Compare/Merge two directories: </title>
+<screen>
+   <command>kdiff3</command> <replaceable>dir1 dir2</replaceable>
+   <command>kdiff3</command> <replaceable>dir1 dir2</replaceable> -o <replaceable>destdir</replaceable>
+</screen>
+<para>
+   If no destination directory is specified, then &kdiff3; will use <replaceable>dir2</replaceable>.
+</para>
+</sect2>
+
+<sect2><title>Compare/Merge three directories: </title>
+<screen>
+   <command>kdiff3</command> <replaceable>dir1 dir2 dir3</replaceable>
+   <command>kdiff3</command> <replaceable>dir1 dir2 dir3</replaceable> -o <replaceable>destdir</replaceable>
+</screen>
+<para>
+   When three directories are merged then <replaceable>dir1</replaceable>
+   is used as the base for the merge.
+   If no destination directory is specified, then &kdiff3; will use <replaceable>dir3</replaceable>
+   as the  destination directory for the merge.
+</para>
+
+<para>
+   Note that only the comparison starts automatically, not the merge. For this you first must
+   select a menu entry or the key F7. (More details later.)
+</para>
+</sect2>
+</sect1>
+
+<sect1 id="dirmergevisible"><title>Directory Merge Visible Information</title>
+<para>
+   While reading the directories a message-box appears that informs you of
+   the progress. If you abort the directory scan, then only files that have
+   been  compared until then will be listed.
+</para><para>
+   When the directory scan is complete then &kdiff3; will show a listbox with
+   the results left, ...
+</para>
+<screenshot><mediaobject>
+<imageobject><imagedata fileref="dirbrowser.png" format="PNG"/></imageobject> <!--alt="Image of the directory browser."-->
+</mediaobject></screenshot>
+<para>
+   ... and details about the currently selected item on the right:
+</para>
+<screenshot><mediaobject>
+<imageobject><imagedata fileref="iteminfo.png" format="PNG"/></imageobject>
+   <!--alt="Image with information about the selected item."-->
+</mediaobject></screenshot>
+
+<sect2 id="name"><title>The Name Column</title>
+<para>
+   Each file and directory that was found during the scan is shown here in
+   a tree. You can select an item by clicking it with the mouse once.
+</para><para>
+   The directories are collapsed by default. You can expand and collapse
+   them by clicking on the "+"/"-" or by double-clicking the item or
+   by using  the left/right-arrow-keys. The "Directory"-menu also contains two
+   actions "Fold all subdirs" and "Unfold all subdirs" with which you can
+   collapse or expand all directories at once.
+</para><para>
+   If you double-click a file item then the file comparison starts and the
+   file-diff-window will appear.
+</para>
+<para>
+   The image in the name column reflects the file type in the first
+   directory  ("A"). It can be one of these:
+</para>
+<itemizedlist>
+     <listitem><para>Normal file</para></listitem>
+     <listitem><para>Normal directory (directory-image)</para></listitem>
+     <listitem><para>Link to a file (file-image with a link arrow)</para></listitem>
+     <listitem><para>Link to a directory (directory-image with a link arrow)</para></listitem>
+</itemizedlist>
+<para>
+   If the file type is different in the other directories, then this is visible
+   in the columns A/B/C and in the window that shows the details about the selected
+   item. Note that for such a case no merge operation can be selected automatically.
+   When starting the merge, then the user will be informed of problems of that
+   kind.
+</para>
+</sect2>
+
+<sect2 id="coloring"><title>The Columns A/B/C and the Coloring Scheme</title>
+<para>
+   As can be seen in the image above the colors red, green, yellow and black
+   are used in the columns A/B/C.
+</para>
+<itemizedlist>
+     <listitem><para>Black: This item doesn't exist in this directory.</para></listitem>
+     <listitem><para>Green: Newest item.</para></listitem>
+     <listitem><para>Yellow: Older than green, newer than red.</para></listitem>
+     <listitem><para>Red: Oldest item.</para></listitem>
+</itemizedlist>
+<para>
+   But for items that were identical in the comparison their color also is
+   identical even if the age is not.
+</para><para>
+   Directories are considered equal if all items they contain are identical.
+   Then they also will have the same color. But the age of a directory is not
+   considered for its color.
+</para><para>
+   The idea for this coloring scheme I came upon in
+   <ulink url="http://samba.org/cgi-bin/cvsweb/dirdiff">dirdiff</ulink>. The colors
+   resemble the colors of a leaf that is green when new, turns yellow later and red
+   when old.
+</para>
+
+</sect2><sect2 id="operation"><title>The Operation Column</title>
+<para>
+   After comparing the directories &kdiff3; also evaluates a proposal for a
+   merge  operation. This is shown in the "Operation" column. You can modify
+   the operation  by clicking on the operation you want to change. A small menu
+   will popup and allows you to select an operation for that item. (You can also
+   select the most needed operations via keyboard.
+   Ctrl+1/2/3/4/Del will select A/B/C/Merge/Delete respectively if available.)
+   This operation will be executed during the merge. It depends on the item and
+   on the merge-mode you are in, what operations are available. The merge-mode is one of
+</para>
+<itemizedlist>
+     <listitem><para>Three directory-merge ("A" is treated as older base of both).</para></listitem>
+     <listitem><para>Two directory-merge.</para></listitem>
+     <listitem><para>Two directory-sync-mode (activate via option "Synchronize Directories").</para></listitem>
+</itemizedlist>
+<para>
+   In three directory merge the operation proposal will be: If for an item ...
+</para>
+<itemizedlist>
+     <listitem><para>... all three directories are equal: Copy from C</para></listitem>
+     <listitem><para>... A and C are equal but B is not: Copy from B (or if B does not
+                         exist, delete the destination if exists)</para></listitem>
+     <listitem><para>... A and B are equal but C is not: Copy from C (or if C does not
+                         exist, delete the destination if exists)</para></listitem>
+     <listitem><para>... B and C are equal but A is not: Copy from C (or if C does not
+                         exist, delete the destination if exists)</para></listitem>
+     <listitem><para>... only A exists: Delete the destination (if exists)</para></listitem>
+     <listitem><para>... only B exists: Copy from B</para></listitem>
+     <listitem><para>... only C exists: Copy from C</para></listitem>
+     <listitem><para>... A, B and C are not equal: Merge</para></listitem>
+     <listitem><para>... A, B and C don't have the same file type (e.g. A is a directory,
+                         B is a file): "Error: Conflicting File Types". While such items exist the
+                         directory merge cannot start.</para></listitem>
+</itemizedlist>
+<para>
+   In two directory merge the operation proposal will be: If for an item ...
+</para>
+<itemizedlist>
+     <listitem><para>... both directories are equal: Copy from B</para></listitem>
+     <listitem><para>... A exists, but not B: Copy from A</para></listitem>
+     <listitem><para>... B exists, but not A: Copy from B</para></listitem>
+     <listitem><para>... A and B exist but are not equal: Merge</para></listitem>
+     <listitem><para>... A and B don't have the same file type (e.g. A is a directory,
+                         B is a file): "Error: Conflicting File Types". While such items exist the
+                         directory merge cannot start.</para></listitem>
+</itemizedlist>
+<para>
+   Sync-mode is active if only two directories and no explicit destination
+   were specified and if the option "Synchronize directories" is active. &kdiff3;
+   then selects a default operation so that both directories are the same afterwards.
+   If for an item ...
+</para>
+<itemizedlist>
+     <listitem><para>... both directories are equal: Nothing will be done.</para></listitem>
+     <listitem><para>... A exists, but not B: Copy A to B</para></listitem>
+     <listitem><para>... B exists, but not A: Copy B to A</para></listitem>
+     <listitem><para>... A and B exist, but are not equal: Merge and store the result
+                         in  both directories. (For the user the visible save-filename is B,
+                         but then &kdiff3; copies B also to A.)</para></listitem>
+     <listitem><para>... A and B don't have the same file type (e.g. A is a directory,
+                         B is a file): "Error: Conflicting File Types". While such items exist the
+                         directory merge cannot start.</para></listitem>
+</itemizedlist>
+<para>
+   When two directories are merged and the option "Copy newer instead of merging" is selected,
+   then &kdiff3; looks at the dates and proposes to choose the newer file. If the files are not
+   equal but have equal dates, then the operation will contain
+   "Error: Dates are equal but files are not." While such items exist the
+   directory merge cannot start.
+</para>
+</sect2>
+
+<sect2 id="status"><title>The Status Column</title>
+<para>
+   During the merge one file after the other will be processed. The status
+   column will show "Done" for items where the merge operation has succeeded,
+   and other texts if something unexpected happened. When a merge is complete,
+   then you should make a last check to see if the status for all items is
+   agreeable.
+</para>
+</sect2>
+
+<sect2 id="statisticscolulmns"><title>Statistics Columns</title>
+<para>
+   When the file comparison mode "Full Analysis" is enabled in the options, then
+   &kdiff3; will show extra columns containing the numbers of unsolved, solved, nonwhite and whitespace
+   conflicts. (The solved-column will only show when comparing or merging three directories.)
+</para>
+</sect2>
+
+<sect2 id="selectingvisiblefiles"><title>Selecting Listed Files</title>
+<para>   
+   Several options influence which files are listed here. Some are accessible in the 
+   <link linkend="dirmergeoptions">settings dialog</link>. The Directory-menu contains the entries:
+</para><para><itemizedlist>
+     <listitem><para>"Show Identical Files": Files that have been detected equal in all input directories.</para></listitem>
+     <listitem><para>"Show Different Files": Files that exist in two or more directories but are not equal.</para></listitem>
+     <listitem><para>"Show Files only in A": Files that exist only in A, but not in B or C.</para></listitem>
+     <listitem><para>"Show Files only in B": Files that exist only in B, but not in A or C.</para></listitem>
+     <listitem><para>"Show Files only in C": Files that exist only in C, but not in A or B.</para></listitem>
+</itemizedlist></para>
+<para>
+   Activate only the "Show"-options for the items you want listed. If for example you only want to list all items that 
+   exist either in A or in B but not in both, you'll have to activate "Show Files only in A" and "Show Files only in B" 
+   and deactivate all others ("Show Identical Files", "Show Different Files", "Show Files only in C").
+   The list will be updated immediately to reflect the change.
+</para><para>
+   These options also apply for directories with one exception: Disabling "Show Different Files" will not hide 
+   any complete directories. This will work only for files within.
+</para><para>
+   Note that of these only the "Show Identical Files"-option is persistant. The others are enabled when starting &kdiff3;. 
+</para>
+</sect2>
+
+</sect1>
+
+
+<sect1 id="dothemerge"><title>Doing A Directory Merge</title>
+<para>
+   You can either merge the currently selected item (file or directory), or all items.
+   When you have made all your operation choices (in all subdirectories too)
+   then you can start the merge.
+</para><para>
+   Be aware that if you didn't specify a destination directory explicitly,
+   then the destination will be "C" in three directory mode, "B" in two directory
+   merge mode, and in sync-mode it will be "A" or/and "B".
+</para><para>
+   If you have specified a destination directory also check that all items
+   that should be in the output, are in the tree. There are some options that
+   cause certain items to be omitted from the directory comparison and merge.
+   Check these options to avoid unpleasant surprises:
+</para>
+<itemizedlist>
+     <listitem><para>"Recursive Directories": If this is off, then items in subdirectories
+                     will not be found.</para></listitem>
+     <listitem><para>"Pattern"/"Anti-Pattern": Include/exclude items that match</para></listitem>
+     <listitem><para>"Exclude Hidden Files"</para></listitem>
+     <listitem><para><link linkend="selectingvisiblefiles">"Show"-options</link> (Show Identical/Different Files, Files only in A/B/C)</para></listitem>
+</itemizedlist>
+<para>
+   If you change the settings in order to list more files, you must do a rescan via menu "Directory"->"Rescan" yourself.
+   (The reason for this is that for faster comparison-speed &kdiff3; omits the comparison for files suppressed by these criteria.)
+   If you changed your file and dir patterns to exclude files, then the file-list will immediately be updated on closing
+   the options-dialog.
+</para><para>
+   Note that when you write to a completely new directory then you usually also want to copy the identical files.
+   In that case enable the "Show Identical Files"-option. If your destination-directory is one of the inputs, 
+   then this isn't necessary because the file is already there.
+</para><para>
+   If you are satisfied so far, the rest is easy.
+</para><para>
+   To merge all items: Select "Start/Continue directory merge" in the "Directory"-menu
+   or press F7 (which is the default shortcut).
+   To merge only the current item: Select "Run Operation For Current Item"
+   or press F6.
+</para><para>
+   If due to conflicting filetypes still some items with invalid operations
+   exist, then a messagebox will appear and these items will be pointed out,
+   so you can select a valid operation for the item.
+</para><para>
+   If you merge all items a dialog will appear giving you the options "Do it", "Simulate
+   it" and "Cancel".
+</para>
+<itemizedlist>
+     <listitem><para>Select "Simulate it" if you want to see what would be done without
+                     actually doing it. A verbose list of all operations will be shown.</para></listitem>
+     <listitem><para>Otherwise select "Do it" to really start merging.</para></listitem>
+</itemizedlist>
+<para>
+   Then &kdiff3; will run the specified operation for all items. If manual
+   interaction  is required (single file merge), then a merge window will open
+   (<link linkend="dirmergebigscreenshot">see the big screenshot</link>).
+</para><para>
+   When you have finished with manually merging a file, again select "Start/Continue directory
+   merge" or the key F7. If you haven't saved it yet, a dialog will ask you to
+   do so. Then &kdiff3; will continue with the next item.
+</para><para>
+   When &kdiff3; encounters an error, it will tell you so and will show the
+   verbose-status-information. At the bottom of this list, there will be some
+   error messages which should help you to understand the cause of the problem.
+   When you continue merging (F7 key) &kdiff3; will give you the choice to retry
+   or skip the item that caused the problem. This means that before continuing
+   you can choose another operation or solve the problem by other means.
+</para><para>
+   When the merge is complete, then &kdiff3; will inform you via a message
+   box.
+</para><para>
+   If some items were merged individually before running the directorymerge then 
+   &kdiff3; remembers this (while this
+   merge-session goes on), and doesn't merge them again when later the merge for
+   all items is run. Even when the merge was skipped or nothing was saved these
+   items count as completed. Only when you change the merge operation the 
+   "Done"-status of the item will be removed and it can be merged again.
+</para>
+</sect1>
+
+<sect1 id="dirmergeoptions"><title>Options for Comparing and Merging Directories</title>
+<para>
+   The &kdiff3;-preferences (menu "Settings"-&gt;"Configure &kdiff3;") has
+   a section called "Directory Merge" with these options:
+</para>
+
+<variablelist>
+   <varlistentry><term><emphasis>Recursive Directories:</emphasis></term><listitem><para> Select whether to search directories
+         recursively.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>File Pattern(s):</emphasis></term><listitem><para> Only files that match any pattern here will
+         be put in the tree. More than one pattern may be specified here by using
+         the semicolon ";" as separator. Valid wildcards: '*' and '?'. (e.g. "*.cpp;*.h").
+         Default is "*". This pattern is not used on directories.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>File Anti-Pattern(s):</emphasis></term><listitem><para> Files that match this pattern
+         will be excluded from the tree. More than one pattern may be specified here
+         via using the semicolon ";" as separator. Valid wildcards: '*' and '?'. Default
+         is "*.orig;*.o;*.obj".</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Directory Anti-Pattern(s):</emphasis></term><listitem><para> Directories that match this pattern
+         will be excluded from the tree. More than one pattern may be specified here
+         via using the semicolon ";" as separator. Valid wildcards: '*' and '?'. Default
+         is "CVS;deps;.svn".</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Use CVS-Ignore:</emphasis></term><listitem><para>
+         Ignore files and directories that would also be ignored by CVS.
+         Many automatically generated files are ignored by CVS.
+         The big advantage is that this can be directory specific via a local ".cvsignore"-file.
+         (See <ulink url="info:/cvs/cvsignore">info:/cvs/cvsignore</ulink>.)</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Find Hidden Files and Directories:</emphasis></term><listitem><para> On some file systems files
+         have an "Hidden"-attribute. On other systems a filename starting with a dot
+         "." causes it to be hidden. This option allows you to decide whether to
+         include  these files in the tree or not. Default is on.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Follow File Links:</emphasis></term><listitem><para> For links to files: When disabled, then
+         the symbolic links are compared. When enabled, then the files behind the
+         links are compared. Default is off.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Follow Directory Links:</emphasis></term><listitem><para> For links to directories: When disabled,
+         then the symbolic links will be compared. When enabled then the link will
+         be treated like a directory and it will be scanned recursively. (Note that
+         the program doesn't check if the link is "recursive". So for example a directory
+         that contains a link to the directory would cause an infinite loop, and after
+         some time when the stack overflows or all memory is used up, crash the program.)
+         Default is off.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Case Sensitive Filename Comparison:</emphasis></term><listitem><para> 
+         Default is false on Windows, true for other operating systems.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>File Comparison Mode:</emphasis></term><listitem><para>
+<variablelist>            
+   <varlistentry><term><emphasis>Binary Comparison:</emphasis></term><listitem><para>
+         This is the default file comparison mode.
+         </para></listitem></varlistentry>         
+   <varlistentry><term><emphasis>Full Analysis:</emphasis></term><listitem><para>
+         Do a full analysis of each file and show the statistics information columns.
+         (Number of solved, unsolved, nonwhite and white conflicts.)
+         The full analysis is slower than a simple binary analysis, and much
+         slower when used on files that don't contain text. 
+         (Specify the appropriate file-antipatterns.)
+         </para></listitem></varlistentry>         
+   <varlistentry><term><emphasis>Trust the modification date:</emphasis></term><listitem><para> If you compare big directories
+         over a slow network, it might be faster to compare the modification dates
+         and file length alone. But this speed improvement comes with the price of
+         a little uncertainty. Use this option with care. Default is off.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Trust the size:</emphasis></term><listitem><para>
+         Similar to trusting the modification date. No real comparison happens. Two
+         files are considered equal if their file-sizes are equal. This is useful
+         when the file-copy operation didn't preserve the modification date.
+         Use this option with care. Default is off.</para></listitem></varlistentry>
+</variablelist></para></listitem></varlistentry>
+         
+   <varlistentry><term><emphasis>Synchronize Directories:</emphasis></term><listitem><para> Activates "Sync-Mode" when two directories
+         are compared and no explicit destination directory was specified. In this
+         mode the proposed operations will be chosen so that both source directories
+         are equal afterwards. Also the merge result will be written to both directories.
+         Default is off.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Copy newer instead of merging:</emphasis></term><listitem><para> Instead of merging the proposed
+         operation will copy the newer source if changes happened. (Considered unsafe,
+         because it implies that you know, that the other file hasn't been edited
+         too. Check to make sure in every case.) Default is off.</para></listitem></varlistentry>
+   <varlistentry><term><emphasis>Backup files:</emphasis></term><listitem><para> If a file or complete directory is replaced
+         by  another or is deleted then the original version will be renamed with an
+         ".orig"  extension. If an old backup file with ".orig" extension already exists
+         then  this will be deleted without backup. This also affects the normal merging
+         of single files, not only in directory-merge mode. Default is on.</para></listitem></varlistentry>
+</variablelist>
+</sect1>
+
+<sect1 id="other"><title>Other Functions in Directory Merge Window</title>
+<sect2><title>Split/Full Screen Mode</title>
+<para>
+   Usually the directory merge list view remains visible while a single file
+   is compared or merged. With the mouse you can move the splitter bar that
+   separates the file list from the text-diff windows. If you don't want this,
+   you can disable "Split Screen View" in the "Directory"-menu. Then you can
+   use "Toggle View" in the "Directory"-menu to switch between the file list
+   and the text-diff view that then occupy the full screen.
+</para>
+</sect2>
+<sect2><title>Comparing or Merging a Single File</title>
+<para>
+   Probably you will prefer a simple double mouse click on a file in order
+   to compare it. Nevertheless there also exists an entry in the "Directory"-menu.
+   You can also directly merge a single file by selecting it and 
+   choosing "Merge current file" in the "Merge"-Menu. On saving the
+   result, the status will be set to done, and the file will not be merged again
+   if a directory merge is started.
+</para><para>
+   But note that this status information will be lost when you rerun a directory
+   scan: "Directory"-menu: "Rescan"
+</para>
+</sect2>
+<sect2><title>Comparing or Merging Files with Different Names</title>
+<para>
+   Sometimes you need to compare or merge files with different names (e.g. the current 
+   file and the backup in the same folder).
+</para><para>
+   Select the exact file by clicking onto the icon in the column A, B or C. The first 
+   file selected thus will be marked with an "A", the second and third with "B" and "C" 
+   regardless on what column they are in. Only up to three files can be chosen like this.
+</para><para>
+   Proceed by choosing "Compare Explicitly Selected Files" or "Merge Explicitly 
+   Selected Files" from the "Directory"-menu. For your convenience these menu entries 
+   also appear as context menu when you right-click the last selected file.
+</para><para>
+   The comparison or merge of a file will happen in the same window. 
+   If this method is used for directories a new window will be opened.
+</para></sect2>
+</sect1>
+</chapter>
+
+<chapter id="misc">
+<title>Miscellaneous Topics</title>
+<sect1 id="networktransparency">
+<title>Networktransparency via KIO</title>
+<sect2><title>KIO-Slaves</title>
+<para>
+The KIO library from Frameworks supports networktransparency via KIO-slaves.
+&kdiff3; uses this for reading input files and for scanning directories.
+This means that you can specify files and directories on local and
+remote resources via URLs.
+</para><para>
+Example:
+</para><para>
+<screen>
+   <command>kdiff3</command> test.cpp  ftp://ftp.faraway.org/test.cpp
+   <command>kdiff3</command> tar:/home/hacker/archive.tar.gz/dir ./dir
+</screen>
+</para>
+<para>The first line compares a local file with a file on an FTP-server. The second line
+compares a directory within an compressed archive with a local directory.
+</para><para>
+Other KIO-slaves that are interesting are:
+</para>
+<itemizedlist>
+<listitem><para>Files from the WWW (http:),</para></listitem>
+<listitem><para>Files from the FTP (ftp:),</para></listitem>
+<listitem><para>Encrypted file transfer (fish:, sftp:),</para></listitem>
+<listitem><para>Windows-resources (smb:),</para></listitem>
+<listitem><para>Local files (file:),</para></listitem>
+</itemizedlist>
+<para>
+Other things that are possible, but probably less useful are:
+</para>
+<itemizedlist>
+<listitem><para>Man-pages (man:),</para></listitem>
+<listitem><para>Info-pages (info:),</para></listitem>
+</itemizedlist>
+</sect2>
+
+<sect2><title>How To Write URLs</title>
+<para>
+   An URL has a different syntax compared with paths for local files and directories.
+   Some things should be considered:
+</para>
+<itemizedlist>
+<listitem><para>
+   A path can be relative and can contain "." or "..". This is not possible for URLs
+   which are always absolute.
+</para></listitem><listitem><para>
+   Special characters must be written with "escaping". ("#"->"%23", space->"%20", etc.)
+   E.g. A file with the name "/#foo#" would have the URL "file:/%23foo%23".
+</para></listitem><listitem><para>
+   When URLs don't work as expected, try to open them in Konqueror first.
+</para></listitem>
+</itemizedlist>
+
+</sect2>
+
+<sect2><title>Capabilities of KIO-Slaves</title>
+<para>
+   Networktransparency has one drawback: Not all resources have the same capabilities.
+</para><para>
+   Sometimes this is due to the file system of the server, sometimes due to the protocol.
+   Here is a short list of restrictions:
+</para>
+<itemizedlist>
+<listitem><para>
+   Sometimes there is no support for links.
+</para></listitem><listitem><para>
+   Or there is no way to distinguish if a link points to a file or a directory; always
+   assuming a file. (ftp:, sftp:).
+</para></listitem><listitem><para>
+   Can't always determine the filesize.
+</para></listitem><listitem><para>
+   Limited support for permissions.
+</para></listitem><listitem><para>
+   No possibility to modify permissions or modification time, so permissions or time
+   of a copy will differ from the original. (See the option "Trust the size".)
+   (To modify permissions or modification time is only possible for local files.)
+</para></listitem>
+</itemizedlist>
+</sect2>
+</sect1>
+
+<sect1 id="kpart">
+<title>Using &kdiff3; as a KPart</title>
+<para>
+&kdiff3; is a KPart. Currently it implements the KParts::ReadOnlyPart-interface.
+</para><para>
+It's main use is as difference-viewer in KDevelop. KDevelop always starts the
+internal difference viewer first. To invoke &kdiff3; press the right mouse button
+on the difference viewer window and select "Show in KDiff3Part" from the context menu.
+</para><para>
+&kdiff3; normally requires two complete files as input. When used as part &kdiff3;
+will assume that the input file is a patch-file in the unified format. &kdiff3;
+then retrieves the original filenames from the patch-file. At least one of
+the two files must be available. &kdiff3; will then invoke <command>patch</command> to
+recreate the second file.
+</para><para>
+In &dolphin; you can select a patch-file and select "Preview in"-"KDiff3Part" from
+the context menu. Be aware that this won't work if none of the original files are
+available, and it is not reliable if the original file(s) have changed since the
+patch-file was generated.
+</para><para>
+When run as a part &kdiff3; only provides the a two-file-diff, a very small toolbar
+and menu. Merging or directory-comparison are not supported then.
+</para>
+</sect1>
+
+<sect1 id="git">
+<title>Using &kdiff3; as a Git Diff and Merging Tool</title>
+<para>
+    &kdiff3; can be used as a <ulink url="https://git-scm.com/">Git</ulink> diff and merge tool.
+</para>
+<para>
+    Just add the following lines into your <filename>gitconfig</filename> file.
+</para>
+<programlisting>
+[diff]
+        tool = kdiff3
+[difftool "kdiff3"]
+        path = &lt;path to kdiff3 binary in your system>
+[difftool]
+        prompt = false
+        keepBackup = false
+        trustExitCode = false
+[merge]
+        tool = kdiff3
+[mergetool]
+        prompt = false
+        keepBackup = false
+        keepTemporaries = false
+[mergetool "kdiff3"]
+        path = &lt;path to kdiff3 binary in your system>
+</programlisting>
+<para>
+    Then to see the difference between two commits use <userinput>git difftool <replaceable>first_hash</replaceable> <replaceable>second_hash</replaceable> --tool=kdiff3 --cc <replaceable>some_file_in_the_git_tree</replaceable></userinput>
+</para>
+<para>
+    To merge a branch with &kdiff3; use <userinput>git merge <replaceable>branch_name</replaceable> &amp;&amp; git mergetool --tool=kdiff3</userinput>
+</para>
+<para>
+    After resolving merging conflicts in the <link linkend="synchronise_views">usual way</link> it is enough to commit the changes to do the job.
+</para>
+</sect1>
+</chapter>
+
+<chapter id="faq">
+<title>Questions and Answers</title>
+
+&reporting.bugs;
+&updating.documentation;
+
+<qandaset id="faqlist">
+
+<qandaentry><question><para>
+   Why is it called "&kdiff3;"?
+</para></question><answer><para>
+   Tools named "KDiff" and "KDiff2" (now called "Kompare") already exist. Also "KDiff3" should suggest
+   that it can merge like the "diff3"-tool in the Diff-Tool collection.
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   Why did I release it under GPL?
+</para></question><answer><para>
+   I'm using GPL programs for a very long time now and learned very much
+   by  having a look at many of the sources. Hence this is my "Thank You"
+   to   all  programmers that also did so or will do the same.
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   Some buttons and functions are missing. What's wrong?
+</para></question><answer><para>
+   You compiled from source but you probably didn't specify the correct prefix
+   for the installation. By default cmake wants to install in /usr/local but then
+   the user-interface resource file (i.e. kdiff3ui.rc) can't be found. The README-file contains
+   more information about the correct prefix.
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   Often lines that are similar but not identical appear next to each other
+   but sometimes not. Why?
+</para></question><answer><para>
+   Lines where only the amount of white space characters is different
+   are   treated as "equal" at first, while just one different non-white character
+   causes the lines to be "different". If similar lines appear next to each
+   other, this actually is coincidence but this fortunately is often the case.
+   See also <link linkend="manualdiffhelp">Manual Diff Help</link>.
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   Why must all conflicts be solved before the merge result can be saved?
+</para></question><answer><para>
+   For each equal or different section the editor in the merge result
+   window    remembers where it begins or ends. This is needed so that conflicts
+   can  be solved manually by simply selecting the source button (A, B or C).
+   This  information is lost while saving as text and it is too much effort to
+   create  a special file format that supports saving and restoring all necessary
+   information.
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   How can I synchronise the diff and merge views, so that all views show the same text position?
+</para></question><answer><para>
+  Click into the summary column left of the text. (<link linkend="synchronise_views">See also here.</link>)
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   Why does the editor in the merge result window not have an "undo"-function?
+</para></question><answer><para>
+  This was too much effort until now. You can always
+  restore a version from one source (A, B or C) by clicking the respective
+  button. For big editing the use of another editor is recommended anyway.
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   When I removed some text, then suddenly "&lt;No src line&gt;" appeared
+   and cannot be deleted. What does that mean and how can one remove this?
+</para></question><answer><para>
+   For each equal or different section the editor in the merge result
+   window    remembers where it begins or ends. "&lt;No src line&gt;" means
+   that    there is nothing left in a section, not even a new line character.
+   This  can happen either while merging automatically or by editing. This is
+   no problem,   since this hint won't appear in the saved file. If you want
+   the orignal source  back just select the section (click on the left summary
+   column) and then click the source button with the needed contents (A/B or
+   C).
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   Why doesn't &kdiff3; support syntax-highlighting?
+</para></question><answer><para>
+   &kdiff3; already uses many colors for difference highlighting. More
+   highlighting    would be confusing. Use another editor for this.
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   Can I use &kdiff3; to compare OpenOffice.Org, Word, Excel, PDF-, &etc; files?
+</para></question><answer><para>
+   Although &kdiff3; will analyse any kind of file the result will probably
+   not be very satisfactory for you.
+</para><para>
+   &kdiff3; was made to compare pure text files. OpenOffice, Word, Excel etc.
+   store much more information in the files (about fonts, pictures, pages,
+   colors etc.) which &kdiff3; doesn't know about. So &kdiff3; will 
+   show you the contents of the file interpreted as pure text, but 
+   this might be unreadable or at least it will look very odd.
+</para><para>
+   Since most programs nowadays store their contents in XML-format, you might 
+   be able to read it as pure text. So if the change was only small,
+   &kdiff3; still might help you.
+</para><para>
+   The best solution if you only want to compare the text (without embedded 
+   objects like pictures) is to use "Select All" and "Copy" in your program 
+   to copy the interesting text to the clipboard and then in &kdiff3; paste the 
+   text into either diff input window. 
+   (See also <link linkend="selections">Select, Copy And Paste</link>.)
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   Where has the directory option "List only deltas" gone?
+</para></question><answer><para>
+   There are now several <link linkend="selectingvisiblefiles">"Show"-options</link> in the directory menu.
+   Disabling "Show identical files" will achieve what enabling "List only deltas" used to do.
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   How can I make a big selection in the diff input window
+   because scrolling takes so long?
+</para></question><answer><para>
+   Start the selection as usual (click and hold the left mouse button). 
+   Then use the navigation keys (e.g. page up, page down) while holding the left mouse button down.
+   (See also <link linkend="selections">Select, Copy And Paste</link>.)
+</para></answer></qandaentry>
+
+<qandaentry><question><para>
+   There is so much information here, but your question is still not answered?
+</para></question><answer><para>
+   Please send me your question. I appreciate every comment.
+</para></answer></qandaentry>
+
+</qandaset>
+</chapter>
+
+<chapter id="credits">
+
+<title>Credits and License</title>
+
+<para>
+&kdiff3; - File and Directory Comparison and Merge Tool
+</para>
+<para>
+Program copyright 2002-2007 Joachim Eibl <email>joachim.eibl at gmx.de</email>
+</para>
+<para>
+Several cool ideas and bugreports came from colleagues and many people out in the Wild Wild Web. Thank you!
+</para>
+
+<para>
+Documentation Copyright &copy; 2002-2007 Joachim Eibl <email>joachim.eibl at gmx.de</email>
+</para>
+
+<!-- TRANS:CREDIT_FOR_TRANSLATORS -->
+
+&underFDL;               <!-- FDL: do not remove -->
+
+
+&underGPL;              <!-- GPL License -->
+
+</chapter>
+
+&documentation.index;
+</book>
+
+<!--
+Local Variables:
+mode: sgml
+sgml-minimize-attributes:nil
+sgml-general-insert-case:lower
+sgml-indent-step:0
+sgml-indent-data:nil
+End:
+
+vim:tabstop=2:shiftwidth=2:expandtab
+-->
diff --git a/doc/en/iteminfo.png b/doc/en/iteminfo.png
new file mode 100644 (file)
index 0000000..6ef5f55
Binary files /dev/null and b/doc/en/iteminfo.png differ
diff --git a/doc/en/letter_by_letter.png b/doc/en/letter_by_letter.png
new file mode 100644 (file)
index 0000000..2faa9ab
Binary files /dev/null and b/doc/en/letter_by_letter.png differ
diff --git a/doc/en/man-kdiff3.1.docbook b/doc/en/man-kdiff3.1.docbook
new file mode 100644 (file)
index 0000000..2243f92
--- /dev/null
@@ -0,0 +1,206 @@
+<?xml version="1.0" ?>
+<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
+<!ENTITY % English "INCLUDE">
+]>
+
+<refentry lang="&language;">
+<refentryinfo>
+<title>KDiff3 User's Manual</title>
+<author><firstname>Burkhard</firstname><surname>Lueck</surname>
+<contrib>KDiff3 man page.</contrib>
+&Burkhard.Lueck.mail;
+</author>
+<date>2018-05-19</date>
+<releaseinfo>kdiff3 1.7.90</releaseinfo>
+</refentryinfo>
+
+<refmeta>
+<refentrytitle><command>kdiff3</command></refentrytitle>
+<manvolnum>1</manvolnum>
+</refmeta>
+
+<refnamediv>
+<refname><command>kdiff3</command></refname>
+<refpurpose>Tool for Comparison and Merge of Files and Directories</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+<cmdsynopsis>
+<command>kdiff3</command>
+<arg choice="opt"><option>-u, --ignore</option></arg>
+<arg choice="opt"><option>--query</option></arg>
+<arg choice="opt"><option>--html</option></arg>
+<arg choice="opt"><option>--abort</option></arg>
+<arg choice="opt"><option>-m, --merge</option></arg>
+<arg choice="opt"><option>-b, --base</option> <replaceable>file</replaceable></arg>
+<arg choice="opt"><option>-o, --output</option> <replaceable>file</replaceable></arg>
+<arg choice="opt"><option>--out</option> <replaceable>file</replaceable></arg>
+<arg choice="opt"><option>--auto</option></arg>
+<arg choice="opt"><option>--qall</option></arg>
+<arg choice="opt"><option>-L1</option> <replaceable>alias1</replaceable></arg>
+<arg choice="opt"><option>-L2</option> <replaceable>alias2</replaceable></arg>
+<arg choice="opt"><option>-L3</option> <replaceable>alias3</replaceable></arg>
+<arg choice="opt"><option>-L, --fname</option> <replaceable>alias</replaceable></arg>
+<arg choice="opt"><option>--cs</option> <replaceable>string</replaceable></arg>
+<arg choice="opt"><option>--confighelp</option></arg>
+<arg choice="opt"><option>--config</option> <replaceable>file</replaceable></arg>
+<arg choice="opt"><option><replaceable>File1</replaceable></option></arg>
+<arg choice="opt"><option><replaceable>File2</replaceable></option></arg>
+<arg choice="opt"><option><replaceable>File3</replaceable></option></arg>
+</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+<title>Description</title>
+<para>Compares two or three input files or directories.</para> 
+
+</refsect1>
+
+<refsect1>
+<title>Options</title>
+<variablelist>
+<varlistentry>
+<term><option>-u, --ignore</option></term>
+<listitem><para>Ignored. (User defined.)
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--query</option></term>
+<listitem><para>Ignored. (User defined.)
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--html</option></term>
+<listitem><para>Ignored. (User defined.)
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--abort</option></term>
+<listitem><para>Ignored. (User defined.)
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>-m, --merge</option></term>
+<listitem><para>Merge the input.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>-b, --base</option> <replaceable>file</replaceable></term>
+<listitem><para>Explicit base file. For compatibility with certain tools.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>-o, --output</option> <replaceable>file</replaceable></term>
+<listitem><para>Output file. Implies -m. &eg;: -o newfile.txt
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--out</option> <replaceable>file</replaceable></term>
+<listitem><para>Output file, again. (For compatibility with certain tools.)
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--auto</option></term>
+<listitem><para>No GUI if all conflicts are auto-solvable.
+(Needs <option>-o</option> <replaceable>file</replaceable>)
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--qall</option></term>
+<listitem><para>Don't solve conflicts automatically.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>-L1</option> <replaceable>alias1</replaceable></term>
+<listitem><para>Visible name replacement for input file 1 (base).
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>-L2</option> <replaceable>alias2</replaceable></term>
+<listitem><para>Visible name replacement for input file 2.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>-L3</option> <replaceable>alias3</replaceable></term>
+<listitem><para>Visible name replacement for input file 3.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>-L, --fname</option> <replaceable>alias</replaceable></term>
+<listitem><para>Alternative visible name replacement. Supply this once for every input.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--cs</option> <replaceable>string</replaceable></term>
+<listitem><para>Override a config setting. Use once for every setting.
+&eg;: <option>--cs</option> <replaceable>AutoAdvance=1</replaceable>.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--confighelp</option></term>
+<listitem><para>Show list of config settings and current values.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option>--config</option> <replaceable>file</replaceable></term>
+<listitem><para>Use a different config file.
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option><replaceable>File1</replaceable></option></term>
+<listitem><para>file1 to open (base, if not specified via --<option>base</option>)
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option><replaceable>File2</replaceable></option></term>
+<listitem><para>file2 to open
+</para></listitem>
+</varlistentry>
+
+<varlistentry>
+<term><option><replaceable>File3</replaceable></option></term>
+<listitem><para>file2 to open
+</para></listitem>
+</varlistentry>
+
+</variablelist>
+
+</refsect1>
+
+<refsect1>
+<title>See Also</title>
+<simplelist>
+<member>More detailed user documentation is available from <ulink
+url="help:/kdiff3">help:/kdiff3</ulink>
+(either enter this &URL; into &konqueror;, or run
+<userinput><command>khelpcenter</command>
+<parameter>help:/kdiff3</parameter></userinput>).</member>
+<member>kf5options(7)</member>
+<member>qt5options(7)</member>
+</simplelist>
+</refsect1>
+
+<refsect1>
+<title>Authors</title>
+<para>This manual page was written by &Burkhard.Lueck; &Burkhard.Lueck.mail;.</para>
+</refsect1>
+
+</refentry>
diff --git a/doc/en/merge_current.png b/doc/en/merge_current.png
new file mode 100644 (file)
index 0000000..721552e
Binary files /dev/null and b/doc/en/merge_current.png differ
diff --git a/doc/en/new.png b/doc/en/new.png
new file mode 100644 (file)
index 0000000..09660a7
Binary files /dev/null and b/doc/en/new.png differ
diff --git a/doc/en/open_dialog.png b/doc/en/open_dialog.png
new file mode 100644 (file)
index 0000000..f764b2b
Binary files /dev/null and b/doc/en/open_dialog.png differ
diff --git a/doc/en/screenshot_diff.png b/doc/en/screenshot_diff.png
new file mode 100644 (file)
index 0000000..7e3f8e6
Binary files /dev/null and b/doc/en/screenshot_diff.png differ
diff --git a/doc/en/screenshot_merge.png b/doc/en/screenshot_merge.png
new file mode 100644 (file)
index 0000000..6aea60e
Binary files /dev/null and b/doc/en/screenshot_merge.png differ
diff --git a/doc/en/triple_diff.png b/doc/en/triple_diff.png
new file mode 100644 (file)
index 0000000..3cc9eb4
Binary files /dev/null and b/doc/en/triple_diff.png differ
diff --git a/doc/en/white_space.png b/doc/en/white_space.png
new file mode 100644 (file)
index 0000000..d7ca210
Binary files /dev/null and b/doc/en/white_space.png differ
diff --git a/kdiff3fileitemactionplugin/CMakeLists.txt b/kdiff3fileitemactionplugin/CMakeLists.txt
new file mode 100644 (file)
index 0000000..bef7a1b
--- /dev/null
@@ -0,0 +1,8 @@
+add_definitions(-DTRANSLATION_DOMAIN=\"kdiff3fileitemactionplugin\")
+
+find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS KIO
+         WidgetsAddons   # KMessageBox
+       )
+
+kcoreaddons_add_plugin(kdiff3fileitemaction SOURCES kdiff3fileitemaction.cpp JSON kdiff3fileitemaction.json INSTALL_NAMESPACE "kf5/kfileitemaction")
+target_link_libraries(kdiff3fileitemaction KF5::I18n KF5::WidgetsAddons KF5::KIOWidgets)
diff --git a/kdiff3fileitemactionplugin/Messages.sh b/kdiff3fileitemactionplugin/Messages.sh
new file mode 100644 (file)
index 0000000..b7834f9
--- /dev/null
@@ -0,0 +1,2 @@
+#! /usr/bin/env bash
+$XGETTEXT `find -name \*.cpp -o -name \*.h` -o $podir/kdiff3fileitemactionplugin.pot
diff --git a/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp b/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp
new file mode 100644 (file)
index 0000000..7da57a8
--- /dev/null
@@ -0,0 +1,299 @@
+/* This file is part of the KDiff3 project
+
+   Copyright (C) 2008 Joachim Eibl <joachim dot eibl at gmx dot 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; 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.
+*/
+
+#include "kdiff3fileitemaction.h"
+
+#include <QAction>
+#include <QMenu>
+#include <QUrl>
+
+#include <KLocalizedString>
+#include <KPluginFactory>
+#include <KPluginLoader>
+#include <KConfig>
+#include <KConfigGroup>
+#include <KMessageBox>
+#include <KProcess>
+#include <KIOCore/KFileItem>
+
+//#include <iostream>
+
+
+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<KDiff3FileItemAction>();)
+#include "kdiff3fileitemaction.moc"
+
+KDiff3FileItemAction::KDiff3FileItemAction (QObject* pParent, const QVariantList & /*args*/)
+: KAbstractFileItemActionPlugin(pParent)
+{
+}
+
+QList<QAction*> 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 (copy to a QStringList)
+   QList<QUrl> itemList = fileItemInfos.urlList();
+   foreach(const QUrl& item, itemList)
+   {
+      //m_urlList.append( item.url() );
+      m_list.append( item );
+   }
+
+
+   /* 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);
+         foreach (const QString &file, *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<const QAction*>( 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"
+                    "KDiff3 homepage: http://kdiff3.sourceforge.net\n\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 directory 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
new file mode 100644 (file)
index 0000000..2f6ca08
--- /dev/null
@@ -0,0 +1,55 @@
+/* This file is part of the KDiff3 project
+
+   Copyright (C) 2008 Joachim Eibl <Joachim dot Eibl at gmx dot 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; 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.
+*/
+
+#ifndef KDIFF3FILEITEMACTIONPLUGIN_H
+#define KDIFF3FILEITEMACTIONPLUGIN_H
+
+#include <KAbstractFileItemActionPlugin>
+#include <KFileItemListProperties>
+#include <QStringList>
+
+class QStringList;
+
+class KDiff3FileItemAction : public KAbstractFileItemActionPlugin
+{
+   Q_OBJECT
+public:
+  KDiff3FileItemAction (QObject* pParent, const QVariantList & args);
+  ~KDiff3FileItemAction() override;
+  // implement pure virtual method from KonqPopupMenuPlugin
+  QList<QAction*> 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<QUrl> m_list;
+   QWidget* m_pParentWidget;
+   //KFileItemListProperties m_fileItemInfos;
+};
+#endif
diff --git a/kdiff3fileitemactionplugin/kdiff3fileitemaction.json b/kdiff3fileitemactionplugin/kdiff3fileitemaction.json
new file mode 100644 (file)
index 0000000..a8132e3
--- /dev/null
@@ -0,0 +1,55 @@
+{
+    "KPlugin": {
+        "MimeTypes": [
+            "application/octet-stream",
+            "inode/directory"
+        ],
+        "Name": "Compare/Merge Files/Directories with KDiff3",
+        "Name[ca@valencia]": "Compara/fusiona fitxers/directoris amb el KDiff3",
+        "Name[ca]": "Compara/fusiona fitxers/directoris amb el KDiff3",
+        "Name[cs]": "Porovnávejte/slučujte soubory/adresáře pomocí KDiff3",
+        "Name[de]": "Dateien und Ordner vergleichen und zusammenführen mit KDiff3",
+        "Name[en_GB]": "Compare/Merge Files/Directories with KDiff3",
+        "Name[es]": "Comparar o fusionar archivos o directorios con KDiff3",
+        "Name[fi]": "Vertaa ja yhdistä tiedostoja ja kansioita KDiff3:lla",
+        "Name[fr]": "Comparaison et fusion de fichiers ou de dossiers à l'aide de « KDiff3 »",
+        "Name[gl]": "Comparar ou mesturar ficheiros ou directorios con KDiff3",
+        "Name[it]": "Confronta e fonde i file o le cartelle con KDiff3",
+        "Name[nl]": "Vergelijk/Voeg samen bestanden/mappen met KDiff3",
+        "Name[pl]": "Porównuj/Scalaj pliki/katalogi z KDiff3",
+        "Name[pt]": "Comparar/Reunir os Ficheiros/Pastas com o KDiff3",
+        "Name[pt_BR]": "Comparar/Mesclar arquivos/pastas com o KDiff3",
+        "Name[sk]": "Porovnať/zlúčiť súbory/adresáre s KDiff3",
+        "Name[sv]": "Jämför/Sammanfoga filer/kataloger med KDiff3",
+        "Name[uk]": "Порівняння та об'єднання файлів та каталогів за допомогою KDiff3",
+        "Name[x-test]": "xxCompare/Merge Files/Directories with KDiff3xx",
+        "ServiceTypes": [
+            "KFileItemAction/Plugin"
+        ]
+    },
+    "MimeType": "application/octet-stream;inode/directory;",
+    "X-KDE-Submenu": "KDiff3",
+    "X-KDE-Submenu[bg]": "KDiff3",
+    "X-KDE-Submenu[bs]": "KDiff3",
+    "X-KDE-Submenu[ca]": "KDiff3",
+    "X-KDE-Submenu[cs]": "KDiff3",
+    "X-KDE-Submenu[da]": "KDiff3",
+    "X-KDE-Submenu[de]": "KDiff3",
+    "X-KDE-Submenu[el]": "KDiff3",
+    "X-KDE-Submenu[es]": "KDiff3",
+    "X-KDE-Submenu[et]": "KDiff3",
+    "X-KDE-Submenu[fr]": "KDiff3",
+    "X-KDE-Submenu[hu]": "KDiff3",
+    "X-KDE-Submenu[it]": "KDiff3",
+    "X-KDE-Submenu[nb]": "KDiff3",
+    "X-KDE-Submenu[nds]": "KDiff3",
+    "X-KDE-Submenu[nl]": "KDiff3",
+    "X-KDE-Submenu[pl]": "KDiff3",
+    "X-KDE-Submenu[pt]": "KDiff3",
+    "X-KDE-Submenu[pt_BR]": "KDiff3",
+    "X-KDE-Submenu[ru]": "KDiff3",
+    "X-KDE-Submenu[sv]": "Kdiff3",
+    "X-KDE-Submenu[uk]": "KDiff3",
+    "X-KDE-Submenu[x-test]": "xxKDiff3xx",
+    "X-KDE-Submenu[zh_TW]": "KDiff3"
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b1bab4c
--- /dev/null
@@ -0,0 +1,64 @@
+
+
+########### kdiff3 KPart ###############
+find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Parts WidgetsAddons)
+
+set(kdiff3part_PART_SRCS
+   kdiff3_part.cpp
+   kdiff3.cpp
+   directorymergewindow.cpp
+   merger.cpp
+   pdiff.cpp
+   difftextwindow.cpp
+   diff.cpp
+   optiondialog.cpp
+   mergeresultwindow.cpp
+   fileaccess.cpp
+   gnudiff_analyze.cpp
+   gnudiff_io.cpp
+   gnudiff_xmalloc.cpp
+   common.cpp
+   smalldialogs.cpp
+   progress.cpp
+   ProgressProxyExtender.cpp
+   PixMapUtils.cpp
+   MergeFileInfos.cpp
+   Utils.cpp
+   selection.cpp
+   cvsignorelist.cpp )
+
+add_library(kdiff3part MODULE ${kdiff3part_PART_SRCS})
+
+set_target_properties(kdiff3part PROPERTIES DEFINE_SYMBOL KDIFF3_PART)
+target_compile_features(kdiff3part PRIVATE ${needed_features})
+target_link_libraries(kdiff3part ${KDiff3_LIBRARIES} KF5::Parts)
+
+install(TARGETS kdiff3part DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/parts )
+
+########### kdiff3 executable ###############
+find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Parts WidgetsAddons Config)
+
+set(kdiff3_SRCS
+   main.cpp
+   kdiff3_shell.cpp
+   ${kdiff3part_PART_SRCS}
+    )
+
+add_executable(kdiff3 ${kdiff3_SRCS})
+
+target_link_libraries(kdiff3 KF5::ConfigCore KF5::ConfigGui KF5::Parts ${KDiff3_LIBRARIES} )
+target_compile_features(kdiff3 PRIVATE ${needed_features})
+
+install(TARGETS kdiff3 ${INSTALL_TARGETS_DEFAULT_ARGS})
+
+
+########### install files ###############
+
+install( FILES  kdiff3part.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
+install( FILES  kdiff3_part.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kdiff3part )
+install( FILES  kdiff3_shell.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kdiff3 )
+#install( PROGRAMS  kdiff3.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} )
+install( PROGRAMS org.kde.kdiff3.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} )
+install( FILES org.kde.kdiff3.appdata.xml DESTINATION ${SHARE_INSTALL_PREFIX}/appdata )
+
+add_subdirectory(icons)
diff --git a/src/DirectoryInfo.h b/src/DirectoryInfo.h
new file mode 100644 (file)
index 0000000..18a06e4
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+#ifndef DIRECTORYINFO_H
+#define DIRECTORYINFO_H
+
+#include "fileaccess.h"
+
+class DirectoryInfo
+{
+    public:
+      DirectoryInfo(FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest)
+      {
+          m_dirA = dirA;
+          m_dirB = dirB;
+          m_dirC = dirC;
+          m_dirDest = dirDest;
+      }
+
+      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;
+      }
+
+    private:
+      FileAccess m_dirA, m_dirB, m_dirC;
+      FileAccess m_dirDest;
+};
+
+#endif
diff --git a/src/MergeFileInfos.cpp b/src/MergeFileInfos.cpp
new file mode 100644 (file)
index 0000000..94ff4ff
--- /dev/null
@@ -0,0 +1,131 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+#include "MergeFileInfos.h"
+#include "DirectoryInfo.h"
+#include "fileaccess.h"
+
+#include <QString>
+
+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();
+}
+
+//bool operator>( const MergeFileInfos& );
+QString MergeFileInfos::subPath() const
+{
+    if(m_pFileInfoA && m_pFileInfoA->exists())
+        return m_pFileInfoA->fileRelPath();
+    else if(m_pFileInfoB && m_pFileInfoB->exists())
+        return m_pFileInfoB->fileRelPath();
+    else if(m_pFileInfoC && m_pFileInfoC->exists())
+        return m_pFileInfoC->fileRelPath();
+    return QString("");
+}
+
+QString MergeFileInfos::fileName() const
+{
+    if(m_pFileInfoA && m_pFileInfoA->exists())
+        return m_pFileInfoA->fileName();
+    else if(m_pFileInfoB && m_pFileInfoB->exists())
+        return m_pFileInfoB->fileName();
+    else if(m_pFileInfoC && m_pFileInfoC->exists())
+        return m_pFileInfoC->fileName();
+    return QString("");
+}
+
+bool MergeFileInfos::conflictingFileTypes()
+{
+    // Now check if file/dir-types fit.
+    if(isLinkA() || isLinkB() || isLinkC())
+    {
+        if((existsInA() && !isLinkA()) ||
+           (existsInB() && !isLinkB()) ||
+           (existsInC() && !isLinkC()))
+        {
+            return true;
+        }
+    }
+
+    if(dirA() || dirB() || dirC())
+    {
+        if((existsInA() && !dirA()) ||
+           (existsInB() && !dirB()) ||
+           (existsInC() && !dirC()))
+        {
+            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();
+}
diff --git a/src/MergeFileInfos.h b/src/MergeFileInfos.h
new file mode 100644 (file)
index 0000000..68aa48e
--- /dev/null
@@ -0,0 +1,160 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+#ifndef MERGEFILEINFO_H
+#define MERGEFILEINFO_H
+
+#include "fileaccess.h"
+#include "diff.h"
+#include "DirectoryInfo.h"
+
+#include <QString>
+#include <QSharedPointer>
+//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 MergeFileInfos
+{
+  public:
+    MergeFileInfos();
+    ~MergeFileInfos();
+    
+    //bool operator>( const MergeFileInfos& );
+    QString subPath() const;
+    QString fileName() const;
+
+    bool dirA() const { return m_pFileInfoA ? m_pFileInfoA->isDir() : false; }
+    bool dirB() const { return m_pFileInfoB ? m_pFileInfoB->isDir() : false; }
+    bool dirC() const { return m_pFileInfoC ? m_pFileInfoC->isDir() : false; }
+    bool isLinkA() const { return m_pFileInfoA ? m_pFileInfoA->isSymLink() : false; }
+    bool isLinkB() const { return m_pFileInfoB ? m_pFileInfoB->isSymLink() : false; }
+    bool isLinkC() const { return m_pFileInfoC ? m_pFileInfoC->isSymLink() : false; }
+    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<MergeFileInfos*>& 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<DirectoryInfo> getDirectoryInfo() const { return m_dirInfo; }
+    void setDirectoryInfo(const QSharedPointer<DirectoryInfo> &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 const QSharedPointer<TotalDiffStatus>& diffStatus() const { return m_totalDiffStatus; }
+
+  private:
+    MergeFileInfos* m_pParent;
+    QList<MergeFileInfos*> m_children;
+
+    FileAccess* m_pFileInfoA;
+    FileAccess* m_pFileInfoB;
+    FileAccess* m_pFileInfoC;
+
+    QSharedPointer<DirectoryInfo> m_dirInfo;
+    
+    QSharedPointer<TotalDiffStatus> m_totalDiffStatus;
+  public:
+
+    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!
+};
+
+class MfiCompare
+{
+    Qt::SortOrder mOrder;
+
+  public:
+    explicit MfiCompare(Qt::SortOrder order)
+    {
+        mOrder = order;
+    }
+    bool operator()(MergeFileInfos* pMFI1, MergeFileInfos* pMFI2)
+    {
+        bool bDir1 = pMFI1->dirA() || pMFI1->dirB() || pMFI1->dirC();
+        bool bDir2 = pMFI2->dirA() || pMFI2->dirB() || pMFI2->dirC();
+        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/Messages.sh b/src/Messages.sh
new file mode 100644 (file)
index 0000000..badcefd
--- /dev/null
@@ -0,0 +1,3 @@
+#! /usr/bin/env bash
+$EXTRACTRC *.rc >> rc.cpp
+$XGETTEXT `find -name \*.cpp -o -name \*.h` -o $podir/kdiff3.pot
diff --git a/src/OptionItems.h b/src/OptionItems.h
new file mode 100644 (file)
index 0000000..17e6a31
--- /dev/null
@@ -0,0 +1,190 @@
+/**
+ * Copyright (C) 2002-2009  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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef OPTIONITEMS_H
+#define OPTIONITEMS_H
+
+#include "common.h"
+
+#include <QString>
+#include <QComboBox>
+#include <QLabel>
+#include <QPushButton>
+#include <QTextCodec>
+#include <QLineEdit>
+
+#include <KLocalizedString>
+
+class OptionItemBase
+{
+  public:
+    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*) = 0;
+    virtual void read(ValueMap*) = 0;
+    void doPreserve()
+    {
+        if(!m_bPreserved) {
+            m_bPreserved = true;
+            preserve();
+        }
+    }
+    void doUnpreserve()
+    {
+        if(m_bPreserved) {
+            unpreserve();
+        }
+    }
+    QString getSaveName() { return m_saveName; }
+  protected:
+    virtual void preserve() = 0;
+    virtual void unpreserve() = 0;
+    bool m_bPreserved;
+    QString m_saveName;
+
+    Q_DISABLE_COPY(OptionItemBase)
+};
+
+template <class T>
+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<T>(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) override { config->writeEntry(m_saveName, *m_pVar); }
+    void read(ValueMap* config) override { *m_pVar = config->readEntry(m_saveName, *m_pVar); }
+  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 T>
+class OptionNum : public Option<T>
+{
+  public:
+    using Option<T>::Option;
+    explicit OptionNum(T* pVar, const QString& saveName)
+        : Option<T>(pVar, saveName)
+    {
+        stringValue = QLocale().toString(*pVar);
+    }
+
+    explicit OptionNum(T* pVar, const T& defaultValue, const QString& saveName)
+        : Option<T>(pVar, defaultValue, saveName)
+    {
+        stringValue = QLocale().toString(*pVar);
+    }
+
+    void setCurrent(const T inValue) override
+    {
+        //setNum does not use locale formatting instead it always use QLocale::C.
+        stringValue = QLocale().toString(inValue);
+        Option<T>::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
+    {
+        return stringValue;
+    }
+
+  private:
+    QString stringValue;
+
+    Q_DISABLE_COPY(OptionNum)
+};
+
+typedef Option<bool> OptionToggleAction;
+typedef OptionNum<int> OptionInt;
+typedef Option<QPoint> OptionPoint;
+typedef Option<QSize> OptionSize;
+typedef Option<QStringList> OptionStringList;
+
+typedef Option<bool> OptionBool;
+typedef Option<QFont> OptionFont;
+typedef Option<QColor> OptionColor;
+typedef Option<QString> 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/PixMapUtils.cpp b/src/PixMapUtils.cpp
new file mode 100644 (file)
index 0000000..14f8027
--- /dev/null
@@ -0,0 +1,170 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <QPixmap>
+#include <KIconLoader>
+#include <QPainter>
+
+#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<quint32*>(img1.scanLine(y));
+        quint32* line2 = reinterpret_cast<quint32*>(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
new file mode 100644 (file)
index 0000000..04009df
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+#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
new file mode 100644 (file)
index 0000000..f58857b
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#include "ProgressProxyExtender.h"
+
+#include <QString>
+
+void ProgressProxyExtender::slotListDirInfoMessage(KJob*, const QString& msg)
+{
+    setInformation(msg, 0);
+}
+
+void ProgressProxyExtender::slotPercent(KJob*, qint64 percent)
+{
+    setCurrent(percent);
+}
+
+
diff --git a/src/ProgressProxyExtender.h b/src/ProgressProxyExtender.h
new file mode 100644 (file)
index 0000000..952ce50
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifndef PROGRESSPROXYEXTENDER_H
+#define PROGRESSPROXYEXTENDER_H
+
+#include "progress.h"
+#include <QString>
+
+class KJob;
+
+class ProgressProxyExtender: public ProgressProxy
+{
+  Q_OBJECT
+public:
+   ProgressProxyExtender() { setMaxNofSteps(100); }
+public Q_SLOTS:
+  void slotListDirInfoMessage( KJob*, const QString& msg );
+  void slotPercent( KJob*, qint64 percent );
+};
+#endif
diff --git a/src/Utils.cpp b/src/Utils.cpp
new file mode 100644 (file)
index 0000000..067fd63
--- /dev/null
@@ -0,0 +1,125 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ * 
+ */
+#include "Utils.h"
+
+#include <QString>
+#include <QStringList>
+#include <QHash>
+#include <QRegExp>
+#include <KLocalizedString>
+
+/* 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 reasor 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<QString, QRegExp> s_patternMap;
+
+    QStringList sl = wildcard.split(QChar(';'));
+
+    for(QStringList::Iterator it = sl.begin(); it != sl.end(); ++it)
+    {
+        QHash<QString, QRegExp>::iterator patIt = s_patternMap.find(*it);
+        if(patIt == s_patternMap.end())
+        {
+            QRegExp pattern(*it, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard);
+            patIt = s_patternMap.insert(*it, pattern);
+        }
+
+        if(patIt.value().exactMatch(testString))
+            return true;
+    }
+
+    return false;
+}
diff --git a/src/Utils.h b/src/Utils.h
new file mode 100644 (file)
index 0000000..bb94c21
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <QStringList>
+#include <QString>
+
+class Utils{
+    public:
+      static bool wildcardMultiMatch(const QString& wildcard, const QString& testString, bool bCaseSensitive);
+      static QString getArguments(QString cmd, QString& program, QStringList& args);
+};
+
+#endif
diff --git a/src/common.cpp b/src/common.cpp
new file mode 100644 (file)
index 0000000..836015b
--- /dev/null
@@ -0,0 +1,328 @@
+/***************************************************************************
+ *   Copyright (C) 2004-2007 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.          *
+ ***************************************************************************/
+
+#include "common.h"
+#include <QColor>
+#include <QSize>
+#include <QStringList>
+#include <QTextStream>
+#include <map>
+#include <qfont.h>
+#include <qpoint.h>
+
+ValueMap::ValueMap()
+{
+}
+
+ValueMap::~ValueMap()
+{
+}
+
+void ValueMap::save(QTextStream& ts)
+{
+    std::map<QString, QString>::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<QString, QString>::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<QString, QString>::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<QString, QString>::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<QString, QString>::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<QString, QString>::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<QString, QString>::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<QString, QString>::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<QString, QString>::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<QString, QString>::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
new file mode 100644 (file)
index 0000000..0b1009c
--- /dev/null
@@ -0,0 +1,118 @@
+/***************************************************************************
+                          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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <QAtomicInt>
+#include <QString>
+#include <map>
+
+template <class T>
+T min3( T d1, T d2, T d3 )
+{
+   if ( d1 < d2  &&  d1 < d3 ) return d1;
+   if ( d2 < d3 ) return d2;
+   return d3;
+}
+
+template <class T>
+T max3( T d1, T d2, T d3 )
+{
+
+   if ( d1 > d2  &&  d1 > d3 ) return d1;
+
+
+   if ( d2 > d3 ) return d2;
+   return d3;
+}
+
+template <class T>
+T minMaxLimiter( T d, T minimum, T maximum )
+{
+   Q_ASSERT(minimum<=maximum);
+   if ( d < minimum ) return minimum;
+   if ( d > maximum ) return maximum;
+   return d;
+}
+
+inline int getAtomic(QAtomicInt& ai)
+{
+   return ai.load();
+}
+
+inline qint64 getAtomic(QAtomicInteger<qint64>& ai)
+{
+   return ai.load();
+}
+
+class QFont;
+class QColor;
+class QSize;
+class QPoint;
+class QStringList;
+class QTextStream;
+
+class ValueMap
+{
+  private:
+    std::map<QString, QString> 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
new file mode 100644 (file)
index 0000000..fdc56d1
--- /dev/null
@@ -0,0 +1,204 @@
+/***************************************************************************
+ * class CvsIgnoreList from Cervisia cvsdir.cpp                            *
+ *    Copyright (C) 1999-2002 Bernd Gehrmann <bernd at mail.berlios.de>    *
+ * with elements from class StringMatcher                                  *
+ *    Copyright (c) 2003 Andre Woebbeking <Woebbeking at web.de>           *
+ * 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.                                   *
+ ***************************************************************************/
+#include "cvsignorelist.h"
+
+#include <QDir>
+#include <QTextStream>
+
+void CvsIgnoreList::init(FileAccess& dir, const t_DirectoryList* pDirList)
+{
+    static const char* ignorestr = ". .. 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 _$* *$";
+    static const char* varname = "CVSIGNORE";
+
+    addEntriesFromString(QString::fromLatin1(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");
+        qint64 size = file.exists() ? file.sizeForReading() : 0;
+        if(size > 0)
+        {
+            char* buf = new char[size];
+            if(buf != nullptr)
+            {
+                file.readFile(buf, size);
+                int pos1 = 0;
+                for(int pos = 0; pos <= size; ++pos)
+                {
+                    if(pos == size || buf[pos] == ' ' || buf[pos] == '\t' || buf[pos] == '\n' || buf[pos] == '\r')
+                    {
+                        if(pos > pos1)
+                        {
+                            addEntry(QString::fromLatin1(&buf[pos1], pos - pos1));
+                        }
+                        ++pos1;
+                    }
+                }
+                delete[] buf;
+            }
+        }
+    }
+}
+
+void CvsIgnoreList::addEntriesFromString(const QString& str)
+{
+    int posLast(0);
+    int pos;
+    while((pos = str.indexOf(' ', posLast)) >= 0)
+    {
+        if(pos > posLast)
+            addEntry(str.mid(posLast, pos - posLast));
+        posLast = pos + 1;
+    }
+
+    if(posLast < static_cast<int>(str.length()))
+        addEntry(str.mid(posLast));
+}
+
+void CvsIgnoreList::addEntriesFromFile(const QString& name)
+{
+    QFile file(name);
+
+    if(file.open(QIODevice::ReadOnly))
+    {
+        QTextStream stream(&file);
+        while(!stream.atEnd())
+        {
+            addEntriesFromString(stream.readLine());
+        }
+    }
+}
+
+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
+{
+    if(m_exactPatterns.indexOf(text) >= 0)
+    {
+        return true;
+    }
+
+    QStringList::ConstIterator it;
+    QStringList::ConstIterator itEnd;
+    for(it = m_startPatterns.begin(), itEnd = m_startPatterns.end(); it != itEnd; ++it)
+    {
+        if(text.startsWith(*it))
+        {
+            return true;
+        }
+    }
+
+    for(it = m_endPatterns.begin(), itEnd = m_endPatterns.end(); it != itEnd; ++it)
+    {
+        if(text.mid(text.length() - (*it).length()) == *it) //(text.endsWith(*it))
+        {
+            return true;
+        }
+    }
+
+    /*
+    for (QValueList<QCString>::const_iterator it(m_generalPatterns.begin()),
+                                              itEnd(m_generalPatterns.end());
+         it != itEnd; ++it)
+    {
+        if (::fnmatch(*it, text.local8Bit(), FNM_PATHNAME) == 0)
+        {
+            return true;
+        }
+    }
+    */
+
+    for(it = m_generalPatterns.begin(); it != m_generalPatterns.end(); ++it)
+    {
+        QRegExp pattern(*it, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard);
+        if(pattern.exactMatch(text))
+            return true;
+    }
+
+    return false;
+}
+
+bool CvsIgnoreList::cvsIgnoreExists(const t_DirectoryList* pDirList)
+{
+    t_DirectoryList::const_iterator i;
+    for(i = pDirList->begin(); i != pDirList->end(); ++i)
+    {
+        if(i->fileName() == ".cvsignore")
+            return true;
+    }
+    return false;
+}
diff --git a/src/cvsignorelist.h b/src/cvsignorelist.h
new file mode 100644 (file)
index 0000000..2fb18b7
--- /dev/null
@@ -0,0 +1,39 @@
+/***************************************************************************
+ * class CvsIgnoreList from Cervisia cvsdir.cpp                            *
+ *    Copyright (C) 1999-2002 Bernd Gehrmann <bernd at mail.berlios.de>    *
+ * with elements from class StringMatcher                                  *
+ *    Copyright (c) 2003 Andre Woebbeking <Woebbeking at web.de>           *
+ * 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.                                   *
+ ***************************************************************************/
+#ifndef CVSIGNORELIST_H
+#define CVSIGNORELIST_H
+
+#include "fileaccess.h"
+
+class CvsIgnoreList
+{
+  public:
+    CvsIgnoreList() {}
+    void init(FileAccess& dir, const t_DirectoryList* pDirList);
+    bool matches(const QString& text, bool bCaseSensitive) const;
+
+  private:
+    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
new file mode 100644 (file)
index 0000000..c11daf2
--- /dev/null
@@ -0,0 +1,2355 @@
+/***************************************************************************
+                          diff.cpp  -  description
+                             -------------------
+    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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "diff.h"
+
+#include "Utils.h"
+#include "fileaccess.h"
+#include "gnudiff_diff.h"
+#include "options.h"
+#include "progress.h"
+
+#include <cstdlib>
+#include <ctype.h>
+#include <map>
+
+#include <qglobal.h>
+
+#include <QDir>
+#include <QFileInfo>
+#include <QProcess>
+#include <QTemporaryFile>
+#include <QTextCodec>
+#include <QTextStream>
+
+#include <KLocalizedString>
+#include <KMessageBox>
+
+int LineData::width(int tabSize) const
+{
+    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;
+}
+
+// The bStrict flag is true during the test where a nonmatching area ends.
+// Then the equal()-function requires that the match has more than 2 nonwhite characters.
+// This is to avoid matches on trivial lines (e.g. with white space only).
+// This choice is good for C/C++.
+bool equal(const LineData& l1, const LineData& l2, bool bStrict)
+{
+    if(l1.pLine == nullptr || l2.pLine == nullptr) return false;
+
+    if(bStrict && g_bIgnoreTrivialMatches)
+        return false;
+
+    // Ignore white space diff
+    const QChar* p1 = l1.pLine;
+    const QChar* p1End = p1 + l1.size;
+
+    const QChar* p2 = l2.pLine;
+    const QChar* p2End = p2 + l2.size;
+
+    if(g_bIgnoreWhiteSpace)
+    {
+        int nonWhite = 0;
+        for(;;)
+        {
+            while(isWhite(*p1) && p1 != p1End) ++p1;
+            while(isWhite(*p2) && p2 != p2End) ++p2;
+
+            if(p1 == p1End && p2 == p2End)
+            {
+                if(bStrict && g_bIgnoreTrivialMatches)
+                { // Then equality is not enough
+                    return nonWhite > 2;
+                }
+                else // equality is enough
+                    return true;
+            }
+            else if(p1 == p1End || p2 == p2End)
+                return false;
+
+            if(*p1 != *p2)
+                return false;
+            ++p1;
+            ++p2;
+            ++nonWhite;
+        }
+    }
+    else
+    {
+        return (l1.size == l2.size && memcmp(p1, p2, l1.size) == 0);
+    }
+}
+
+static bool isLineOrBufEnd(const QChar* p, int i, int size)
+{
+    return i >= size            // End of file
+           || isEndOfLine(p[i]) // Normal end of line
+
+        // No support for Mac-end of line yet, because incompatible with GNU-diff-routines.
+        // || ( p[i]=='\r' && (i>=size-1 || p[i+1]!='\n')
+        //                 && (i==0        || p[i-1]!='\n') )  // Special case: '\r' without '\n'
+        ;
+}
+
+/* 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.
+*/
+
+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())
+    {
+        FileAccess::removeFile(m_tempInputFileName);
+        m_tempInputFileName = "";
+    }
+}
+
+void SourceData::setFilename(const QString& filename)
+{
+    if(filename.isEmpty())
+    {
+        reset();
+    }
+    else
+    {
+        FileAccess fa(filename);
+        setFileAccess(fa);
+    }
+}
+
+bool SourceData::isEmpty()
+{
+    return getFilename().isEmpty();
+}
+
+bool SourceData::hasData()
+{
+    return m_normalData.m_pBuf != nullptr;
+}
+
+bool SourceData::isValid()
+{
+    return isEmpty() || hasData();
+}
+
+void SourceData::setOptions(Options* pOptions)
+{
+    m_pOptions = pOptions;
+}
+
+QString SourceData::getFilename()
+{
+    return m_fileAccess.absoluteFilePath();
+}
+
+QString SourceData::getAliasName()
+{
+    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())
+    {
+        FileAccess::removeFile(m_tempInputFileName);
+        m_tempInputFileName = "";
+    }
+}
+void SourceData::setEncoding(QTextCodec* pEncoding)
+{
+    m_pEncoding = pEncoding;
+}
+
+QStringList SourceData::setData(const QString& data)
+{
+    QStringList errors;
+
+    // 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)
+    {
+        errors.append(i18n("Writing clipboard data to temp file failed."));
+    }
+    else
+    {
+        m_aliasName = i18n("From Clipboard");
+        m_fileAccess = FileAccess(""); // Effect: m_fileAccess.isValid() is false
+    }
+
+    return errors;
+}
+
+const LineData* SourceData::getLineDataForDiff() const
+{
+    if(m_lmppData.m_pBuf == nullptr)
+        return m_normalData.m_v.size() > 0 ? &m_normalData.m_v[0] : nullptr;
+    else
+        return m_lmppData.m_v.size() > 0 ? &m_lmppData.m_v[0] : nullptr;
+}
+
+const LineData* SourceData::getLineDataForDisplay() const
+{
+    return m_normalData.m_v.size() > 0 ? &m_normalData.m_v[0] : 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()
+{
+    return m_normalData.m_bIsText;
+}
+
+bool SourceData::isIncompleteConversion()
+{
+    return m_normalData.m_bIncompleteConversion;
+}
+
+bool SourceData::isFromBuffer()
+{
+    return !m_fileAccess.isValid();
+}
+
+bool SourceData::isBinaryEqualWith(const SourceData& 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 = true;
+    m_bIncompleteConversion = false;
+    m_eLineEndStyle = eLineEndStyleUndefined;
+}
+
+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;
+}
+
+void SourceData::FileData::copyBufFrom(const FileData& src)
+{
+    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);
+}
+
+// Convert the input file from input encoding to output encoding and write it to the output file.
+static bool 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;
+}
+
+static QTextCodec* 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.: <meta charset="utf-8"> or <?xml version="1.0" encoding="ISO-8859-1"?>
+        {
+            QByteArray encoding = s.mid(apostrophPos + 1, encodingEnd - (apostrophPos + 1));
+            return QTextCodec::codecForName(encoding);
+        }
+        else // e.g.: <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+        {
+            QByteArray encoding = s.mid(encodingPos + encodingTag.length(), apostrophPos - (encodingPos + encodingTag.length()));
+            return QTextCodec::codecForName(encoding);
+        }
+    }
+    return nullptr;
+}
+
+static QTextCodec* 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("<?xml");
+    if(xmlHeaderPos >= 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("<meta");
+        while(metaHeaderPos >= 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("<meta", metaHeaderEnd);
+            }
+            else
+                break;
+        }
+    }
+    return nullptr;
+}
+
+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;
+
+    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(fileNameIn1))
+            {
+                errors.append(i18n("Failed to read file: %1", fileNameIn1));
+                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))
+            {
+
+                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 = "";
+                if(!m_normalData.readFile(fileNameIn1))
+                {
+                    errors.append(i18n("Failed to read file: %1", fileNameIn1));
+                    return errors;
+                }
+
+                pEncoding1 = m_pEncoding;
+            }
+        }
+
+        // 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
+        { // We don't need any lmpp data at all.
+            m_lmppData.reset();
+        }
+    }
+
+    if(!m_normalData.preprocess(m_pOptions->m_bPreserveCarriageReturn, pEncoding1) ||
+       !m_lmppData.preprocess(false, pEncoding2))
+    {
+
+        errors.append(i18n("File %1 too large to process. Skipping.", fileNameIn1));
+    }
+    else
+    {
+        //TODO: Needed?
+        if(m_lmppData.m_vSize < m_normalData.m_vSize)
+        {
+            // Preprocessing command may result in smaller data buffer so adjust size
+            m_lmppData.m_v.resize((int)m_normalData.m_vSize);
+            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[(int)i].pLine = m_lmppData.m_unicodeBuf.unicode() + m_lmppData.m_unicodeBuf.length();
+            }
+
+            m_lmppData.m_vSize = m_normalData.m_vSize;
+        }
+
+        // Internal Preprocessing: Uppercase-conversion
+        if(m_pOptions->m_bIgnoreCase)
+        {
+            m_lmppData.m_unicodeBuf = QLocale::system().toUpper(m_lmppData.m_unicodeBuf);
+        }
+
+        // Ignore comments
+        if(m_pOptions->m_bIgnoreComments)
+        {
+            m_lmppData.removeComments();
+            LineRef vSize = (LineRef)std::min(m_normalData.m_vSize, m_lmppData.m_vSize);
+            for(int i = 0; i < (int)vSize; ++i)
+            {
+                m_normalData.m_v[i].bContainsPureComment = m_lmppData.m_v[i].bContainsPureComment;
+            }
+        }
+    }
+
+    return errors;
+}
+
+/** Prepare the linedata vector for every input line.*/
+bool SourceData::FileData::preprocess(bool bPreserveCR, QTextCodec* pEncoding)
+{
+    qint64 i;
+    // detect line end style
+    QVector<e_LineEndStyle> vOrigDataLineEndStyle;
+    m_eLineEndStyle = eLineEndStyleUndefined;
+    for(i = 0; i < m_size; ++i)
+    {
+        if(m_pBuf[i] == '\r')
+        {
+            if(i + 1 < m_size && m_pBuf[i + 1] == '\n') // not 16-bit unicode
+            {
+                vOrigDataLineEndStyle.push_back(eLineEndStyleDos);
+                ++i;
+            }
+            else if(i > 0 && i + 2 < m_size && m_pBuf[i - 1] == '\0' && m_pBuf[i + 1] == '\0' && m_pBuf[i + 2] == '\n') // 16-bit unicode
+            {
+                vOrigDataLineEndStyle.push_back(eLineEndStyleDos);
+                i += 2;
+            }
+            else // old mac line end style ?
+            {
+                vOrigDataLineEndStyle.push_back(eLineEndStyleUndefined);
+                const_cast<char*>(m_pBuf)[i] = '\n'; // fix it in original data
+            }
+        }
+        else if(m_pBuf[i] == '\n')
+        {
+            vOrigDataLineEndStyle.push_back(eLineEndStyleUnix);
+        }
+    }
+
+    if(!vOrigDataLineEndStyle.isEmpty())
+        m_eLineEndStyle = vOrigDataLineEndStyle[0];
+
+    qint64 skipBytes = 0;
+    QTextCodec* pCodec = ::detectEncoding(m_pBuf, m_size, skipBytes);
+    if(pCodec != pEncoding)
+        skipBytes = 0;
+
+    if(m_size - skipBytes > INT_MAX)
+        return false;
+
+    QByteArray ba = QByteArray::fromRawData(m_pBuf + skipBytes, (int)(m_size - skipBytes));
+    QTextStream ts(ba, QIODevice::ReadOnly | QIODevice::Text);
+    ts.setCodec(pEncoding);
+    ts.setAutoDetectUnicode(false);
+    m_unicodeBuf = ts.readAll();
+    ba.clear();
+
+    int ucSize = m_unicodeBuf.length();
+    const QChar* p = m_unicodeBuf.unicode();
+
+    m_bIsText = true;
+    int lines = 1;
+    m_bIncompleteConversion = false;
+    for(i = 0; i < ucSize; ++i)
+    {
+        if(i >= ucSize || p[i] == '\n')
+        {
+            ++lines;
+        }
+        if(p[i].isNull())
+        {
+            m_bIsText = false;
+        }
+        if(p[i] == QChar::ReplacementCharacter)
+        {
+            m_bIncompleteConversion = true;
+        }
+    }
+
+    m_v.resize(lines + 5);
+    int lineIdx = 0;
+    int lineLength = 0;
+    bool bNonWhiteFound = false;
+    int whiteLength = 0;
+    for(i = 0; i <= ucSize; ++i)
+    {
+        if(i >= ucSize || p[i] == '\n')
+        {
+            m_v[lineIdx].pLine = &p[i - lineLength];
+            while(/*!bPreserveCR  &&*/ lineLength > 0 && m_v[lineIdx].pLine[lineLength - 1] == '\r')
+            {
+                --lineLength;
+            }
+            m_v[lineIdx].pFirstNonWhiteChar = m_v[lineIdx].pLine + std::min(whiteLength, lineLength);
+            m_v[lineIdx].size = lineLength;
+            if(lineIdx < vOrigDataLineEndStyle.count() && bPreserveCR && i < ucSize)
+            {
+                ++m_v[lineIdx].size;
+                const_cast<QChar*>(m_v[lineIdx].pLine)[lineLength] = '\r';
+                //switch ( vOrigDataLineEndStyle[lineIdx] )
+                //{
+                //case eLineEndStyleUnix: const_cast<QChar*>(m_v[lineIdx].pLine)[lineLength] = '\n'; break;
+                //case eLineEndStyleDos:  const_cast<QChar*>(m_v[lineIdx].pLine)[lineLength] = '\r'; break;
+                //case eLineEndStyleUndefined: const_cast<QChar*>(m_v[lineIdx].pLine)[lineLength] = '\x0b'; break;
+                //}
+            }
+            lineLength = 0;
+            bNonWhiteFound = false;
+            whiteLength = 0;
+            ++lineIdx;
+        }
+        else
+        {
+            ++lineLength;
+
+            if(!bNonWhiteFound && isWhite(p[i]))
+                ++whiteLength;
+            else
+                bNonWhiteFound = true;
+        }
+    }
+    Q_ASSERT(lineIdx == lines);
+
+    m_vSize = lines;
+    return true;
+}
+
+// Must not be entered, when within a comment.
+// Returns either at a newline-character p[i]=='\n' or when i==size.
+// A line that contains only comments is still "white".
+// Comments in white lines must remain, while comments in
+// non-white lines are overwritten with spaces.
+void SourceData::FileData::checkLineForComments(
+    const QChar* p,          // pointer to start of buffer
+    int& i,                  // index of current position (in, out)
+    int size,                // size of buffer
+    bool& bWhite,            // false if this line contains nonwhite characters (in, out)
+    bool& bCommentInLine,    // true if any comment is within this line (in, out)
+    bool& bStartsOpenComment // true if the line ends within an comment (out)
+    )
+{
+    bStartsOpenComment = false;
+    for(; i < size; ++i)
+    {
+        // A single apostroph ' has prio over a double apostroph " (e.g. '"')
+        // (if not in a string)
+        if(p[i] == '\'')
+        {
+            bWhite = false;
+            ++i;
+            for(; !isLineOrBufEnd(p, i, size) && p[i] != '\''; ++i)
+                ;
+            if(p[i] == '\'') ++i;
+        }
+
+        // Strings have priority over comments: e.g. "/* Not a comment, but a string. */"
+        else if(p[i] == '"')
+        {
+            bWhite = false;
+            ++i;
+            for(; !isLineOrBufEnd(p, i, size) && !(p[i] == '"' && p[i - 1] != '\\'); ++i)
+                ;
+            if(p[i] == '"') ++i;
+        }
+
+        // C++-comment
+        else if(p[i] == '/' && i + 1 < size && p[i + 1] == '/')
+        {
+            int commentStart = i;
+            bCommentInLine = true;
+            i += 2;
+            for(; !isLineOrBufEnd(p, i, size); ++i)
+                ;
+            if(!bWhite)
+            {
+                size = i - commentStart;
+                m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size));
+            }
+            return;
+        }
+
+        // C-comment
+        else if(p[i] == '/' && i + 1 < size && p[i + 1] == '*')
+        {
+            int commentStart = i;
+            bCommentInLine = true;
+            i += 2;
+            for(; !isLineOrBufEnd(p, i, size); ++i)
+            {
+                if(i + 1 < size && p[i] == '*' && p[i + 1] == '/') // end of the comment
+                {
+                    i += 2;
+
+                    // More comments in the line?
+                    checkLineForComments(p, i, size, bWhite, bCommentInLine, bStartsOpenComment);
+                    if(!bWhite)
+                    {
+                        size = i - commentStart;
+                        m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size));                    }
+                    return;
+                }
+            }
+            bStartsOpenComment = true;
+            return;
+        }
+
+        if(isLineOrBufEnd(p, i, size))
+        {
+            return;
+        }
+        else if(!p[i].isSpace())
+        {
+            bWhite = false;
+        }
+    }
+}
+
+// Modifies the input data, and replaces C/C++ comments with whitespace
+// when the line contains other data too. If the line contains only
+// a comment or white data, remember this in the flag bContainsPureComment.
+void SourceData::FileData::removeComments()
+{
+    int line = 0;
+    const QChar* p = m_unicodeBuf.unicode();
+    bool bWithinComment = false;
+    int size = m_unicodeBuf.length();
+    for(int i = 0; i < size; ++i)
+    {
+        //      std::cout << "2        " << std::string(&p[i], m_v[line].size) << std::endl;
+        bool bWhite = true;
+        bool bCommentInLine = false;
+
+        if(bWithinComment)
+        {
+            int commentStart = i;
+            bCommentInLine = true;
+
+            for(; !isLineOrBufEnd(p, i, size); ++i)
+            {
+                if(i + 1 < size && p[i] == '*' && p[i + 1] == '/') // end of the comment
+                {
+                    i += 2;
+
+                    // More comments in the line?
+                    checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment);
+                    if(!bWhite)
+                    {
+                        size = i - commentStart;
+                        m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size));
+                    }
+                    break;
+                }
+            }
+        }
+        else
+        {
+            checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment);
+        }
+
+        // end of line
+        Q_ASSERT(isLineOrBufEnd(p, i, size));
+        m_v[line].bContainsPureComment = bCommentInLine && bWhite;
+        /*      std::cout << line << " : " <<
+       ( bCommentInLine ?  "c" : " " ) <<
+       ( bWhite ? "w " : "  ") <<
+       std::string(pLD[line].pLine, pLD[line].size) << std::endl;*/
+
+        ++line;
+    }
+}
+
+// First step
+void calcDiff3LineListUsingAB(
+    const DiffList* pDiffListAB,
+    Diff3LineList& d3ll)
+{
+    // First make d3ll for AB (from pDiffListAB)
+
+    DiffList::const_iterator i = pDiffListAB->begin();
+    int lineA = 0;
+    int lineB = 0;
+    Diff d(0, 0, 0);
+
+    for(;;)
+    {
+        if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0)
+        {
+            if(i != pDiffListAB->end())
+            {
+                d = *i;
+                ++i;
+            }
+            else
+                break;
+        }
+
+        Diff3Line d3l;
+        if(d.nofEquals > 0)
+        {
+            d3l.bAEqB = true;
+            d3l.lineA = lineA;
+            d3l.lineB = lineB;
+            --d.nofEquals;
+            ++lineA;
+            ++lineB;
+        }
+        else if(d.diff1 > 0 && d.diff2 > 0)
+        {
+            d3l.lineA = lineA;
+            d3l.lineB = lineB;
+            --d.diff1;
+            --d.diff2;
+            ++lineA;
+            ++lineB;
+        }
+        else if(d.diff1 > 0)
+        {
+            d3l.lineA = lineA;
+            --d.diff1;
+            ++lineA;
+        }
+        else if(d.diff2 > 0)
+        {
+            d3l.lineB = lineB;
+            --d.diff2;
+            ++lineB;
+        }
+
+        Q_ASSERT(d.nofEquals >= 0);
+
+        d3ll.push_back(d3l);
+    }
+}
+
+// Second step
+void calcDiff3LineListUsingAC(
+    const DiffList* pDiffListAC,
+    Diff3LineList& d3ll)
+{
+    ////////////////
+    // Now insert data from C using pDiffListAC
+
+    DiffList::const_iterator i = pDiffListAC->begin();
+    Diff3LineList::iterator i3 = d3ll.begin();
+    int lineA = 0;
+    int lineC = 0;
+    Diff d(0, 0, 0);
+
+    for(;;)
+    {
+        if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0)
+        {
+            if(i != pDiffListAC->end())
+            {
+                d = *i;
+                ++i;
+            }
+            else
+                break;
+        }
+
+        Diff3Line d3l;
+        if(d.nofEquals > 0)
+        {
+            // Find the corresponding lineA
+            while((*i3).lineA != lineA)
+                ++i3;
+
+            (*i3).lineC = lineC;
+            (*i3).bAEqC = true;
+            (*i3).bBEqC = (*i3).bAEqB;
+
+            --d.nofEquals;
+            ++lineA;
+            ++lineC;
+            ++i3;
+        }
+        else if(d.diff1 > 0 && d.diff2 > 0)
+        {
+            d3l.lineC = lineC;
+            d3ll.insert(i3, d3l);
+            --d.diff1;
+            --d.diff2;
+            ++lineA;
+            ++lineC;
+        }
+        else if(d.diff1 > 0)
+        {
+            --d.diff1;
+            ++lineA;
+        }
+        else if(d.diff2 > 0)
+        {
+            d3l.lineC = lineC;
+            d3ll.insert(i3, d3l);
+            --d.diff2;
+            ++lineC;
+        }
+    }
+}
+
+// Third step
+void calcDiff3LineListUsingBC(
+    const DiffList* pDiffListBC,
+    Diff3LineList& d3ll)
+{
+    ////////////////
+    // 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 = d3ll.begin();
+    Diff3LineList::iterator i3c = d3ll.begin();
+    int lineB = 0;
+    int lineC = 0;
+    Diff d(0, 0, 0);
+
+    for(;;)
+    {
+        if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0)
+        {
+            if(i != pDiffListBC->end())
+            {
+                d = *i;
+                ++i;
+            }
+            else
+                break;
+        }
+
+        Diff3Line d3l;
+        if(d.nofEquals > 0)
+        {
+            // Find the corresponding lineB and lineC
+            while(i3b != d3ll.end() && (*i3b).lineB != lineB)
+                ++i3b;
+
+            while(i3c != d3ll.end() && (*i3c).lineC != lineC)
+                ++i3c;
+
+            Q_ASSERT(i3b != d3ll.end());
+            Q_ASSERT(i3c != d3ll.end());
+
+            if(i3b == i3c)
+            {
+                Q_ASSERT((*i3b).lineC == 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 != d3ll.end() || i3c1 != d3ll.end());
+                    if(i3c1 != d3ll.end()) ++i3c1;
+                    if(i3b1 != d3ll.end()) ++i3b1;
+                }
+
+                if(i3c1 == i3b && !(*i3b).bAEqB) // i3c before i3b
+                {
+                    Diff3LineList::iterator i3 = i3c;
+                    int nofDisturbingLines = 0;
+                    while(i3 != i3b && i3 != d3ll.end())
+                    {
+                        if((*i3).lineB != -1)
+                            ++nofDisturbingLines;
+                        ++i3;
+                    }
+
+                    if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 )
+                    {
+                        Diff3LineList::iterator i3_last_equal_A = d3ll.end();
+
+                        i3 = i3c;
+                        while(i3 != i3b)
+                        {
+                            if(i3->bAEqB)
+                            {
+                                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 != d3ll.end());
+
+                        // Move the disturbing lines up, out of sight.
+                        i3 = i3c;
+                        while(i3 != i3b)
+                        {
+                            if((*i3).lineB != -1 ||
+                               (before_or_on_equal_line_in_A && i3->lineA != -1))
+                            {
+                                d3l.lineB = (*i3).lineB;
+                                (*i3).lineB = -1;
+
+                                // Move A along if it matched B
+                                if(before_or_on_equal_line_in_A)
+                                {
+                                    d3l.lineA = i3->lineA;
+                                    d3l.bAEqB = i3->bAEqB;
+                                    i3->lineA = -1;
+                                    i3->bAEqC = false;
+                                }
+
+                                (*i3).bAEqB = false;
+                                (*i3).bBEqC = false;
+                                d3ll.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).lineB = -1; // This might leave an empty line: removed later.
+                        (*i3b).bAEqB = false;
+                        (*i3b).bBEqC = false;
+                        (*i3c).lineB = lineB;
+                        (*i3c).bBEqC = true;
+                        (*i3c).bAEqB = (*i3c).bAEqC;
+                    }
+                }
+                else if(i3b1 == i3c && !(*i3c).bAEqC)
+                {
+                    Diff3LineList::iterator i3 = i3b;
+                    int nofDisturbingLines = 0;
+                    while(i3 != i3c && i3 != d3ll.end())
+                    {
+                        if((*i3).lineC != -1)
+                            ++nofDisturbingLines;
+                        ++i3;
+                    }
+
+                    if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 )
+                    {
+                        Diff3LineList::iterator i3_last_equal_A = d3ll.end();
+
+                        i3 = i3b;
+                        while(i3 != i3c)
+                        {
+                            if(i3->bAEqC)
+                            {
+                                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 != d3ll.end());
+
+                        // Move the disturbing lines up.
+                        i3 = i3b;
+                        while(i3 != i3c)
+                        {
+                            if((*i3).lineC != -1 ||
+                               (before_or_on_equal_line_in_A && i3->lineA != -1))
+                            {
+                                d3l.lineC = (*i3).lineC;
+                                (*i3).lineC = -1;
+
+                                // Move A along if it matched C
+                                if(before_or_on_equal_line_in_A)
+                                {
+                                    d3l.lineA = i3->lineA;
+                                    d3l.bAEqC = i3->bAEqC;
+                                    i3->lineA = -1;
+                                    i3->bAEqB = false;
+                                }
+
+                                (*i3).bAEqC = false;
+                                (*i3).bBEqC = false;
+                                d3ll.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).lineC = -1; // This might leave an empty line: removed later.
+                        (*i3c).bAEqC = false;
+                        (*i3c).bBEqC = false;
+                        (*i3b).lineC = lineC;
+                        (*i3b).bBEqC = true;
+                        (*i3b).bAEqC = (*i3b).bAEqB;
+                    }
+                }
+            }
+
+            --d.nofEquals;
+            ++lineB;
+            ++lineC;
+            ++i3b;
+            ++i3c;
+        }
+        else if(d.diff1 > 0)
+        {
+            Diff3LineList::iterator i3 = i3b;
+            while((*i3).lineB != lineB)
+                ++i3;
+            if(i3 != i3b && !(*i3).bAEqB)
+            {
+                // Take B from this line and move it up as far as possible
+                d3l.lineB = lineB;
+                d3ll.insert(i3b, d3l);
+                (*i3).lineB = -1;
+            }
+            else
+            {
+                i3b = i3;
+            }
+            --d.diff1;
+            ++lineB;
+            ++i3b;
+
+            if(d.diff2 > 0)
+            {
+                --d.diff2;
+                ++lineC;
+            }
+        }
+        else if(d.diff2 > 0)
+        {
+            --d.diff2;
+            ++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).lineA, (*it).lineB, (*it).lineC,
+         (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' );
+   }
+   printf("\n");*/
+}
+
+// Test if the move would pass a barrier. Return true if not.
+static bool isValidMove(ManualDiffHelpList* pManualDiffHelpList, int line1, int line2, int winIdx1, int winIdx2)
+{
+    if(line1 >= 0 && line2 >= 0)
+    {
+        ManualDiffHelpList::const_iterator i;
+        for(i = pManualDiffHelpList->begin(); i != pManualDiffHelpList->end(); ++i)
+        {
+            const ManualDiffHelpEntry& mdhe = *i;
+
+            // Barrier
+            int l1 = winIdx1 == 1 ? mdhe.lineA1 : winIdx1 == 2 ? mdhe.lineB1 : mdhe.lineC1;
+            int l2 = winIdx2 == 1 ? mdhe.lineA1 : winIdx2 == 2 ? mdhe.lineB1 : mdhe.lineC1;
+
+            if(l1 >= 0 && l2 >= 0)
+            {
+                if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2))
+                    return false;
+                l1 = winIdx1 == 1 ? mdhe.lineA2 : winIdx1 == 2 ? mdhe.lineB2 : mdhe.lineC2;
+                l2 = winIdx2 == 1 ? mdhe.lineA2 : winIdx2 == 2 ? mdhe.lineB2 : mdhe.lineC2;
+                ++l1;
+                ++l2;
+                if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2))
+                    return false;
+            }
+        }
+    }
+    return true; // no barrier passed.
+}
+
+static bool runDiff(const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList,
+                    Options* pOptions)
+{
+    ProgressProxy pp;
+    static GnuDiff gnuDiff; // All values are initialized with zeros.
+
+    pp.setCurrent(0);
+
+    diffList.clear();
+    if(p1[0].pLine == nullptr || p2[0].pLine == nullptr || size1 == 0 || size2 == 0)
+    {
+        Diff d(0, 0, 0);
+        if(p1[0].pLine == nullptr && p2[0].pLine == nullptr && size1 == size2)
+            d.nofEquals = size1;
+        else
+        {
+            d.diff1 = size1;
+            d.diff2 = size2;
+        }
+
+        diffList.push_back(d);
+    }
+    else
+    {
+        GnuDiff::comparison comparisonInput;
+        memset(&comparisonInput, 0, sizeof(comparisonInput));
+        comparisonInput.parent = nullptr;
+        comparisonInput.file[0].buffer = p1[0].pLine;                                                //ptr to buffer
+        comparisonInput.file[0].buffered = (p1[size1 - 1].pLine - p1[0].pLine + p1[size1 - 1].size); // size of buffer
+        comparisonInput.file[1].buffer = p2[0].pLine;                                                //ptr to buffer
+        comparisonInput.file[1].buffered = (p2[size2 - 1].pLine - p2[0].pLine + p2[size2 - 1].size); // 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 = 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(0, 0, 0);
+            d.nofEquals = e->line0 - currentLine1;
+            Q_ASSERT(d.nofEquals == e->line1 - currentLine2);
+            d.diff1 = e->deleted;
+            d.diff2 = e->inserted;
+            currentLine1 += d.nofEquals + d.diff1;
+            currentLine2 += d.nofEquals + d.diff2;
+            diffList.push_back(d);
+
+            p = e->link;
+            free(e);
+        }
+
+        if(diffList.empty())
+        {
+            Diff d(0, 0, 0);
+            d.nofEquals = std::min(size1, size2);
+            d.diff1 = size1 - d.nofEquals;
+            d.diff2 = size2 - d.nofEquals;
+            diffList.push_back(d);
+            /*         Diff d(0,0,0);
+         d.nofEquals = equalLinesAtStart;
+         if ( gnuDiff.files[0].missing_newline != gnuDiff.files[1].missing_newline )
+         {
+            d.diff1 = gnuDiff.files[0].missing_newline ? 0 : 1;
+            d.diff2 = gnuDiff.files[1].missing_newline ? 0 : 1;
+            ++d.nofEquals;
+         }
+         else if ( !gnuDiff.files[0].missing_newline )
+         {
+            ++d.nofEquals;
+         }
+         diffList.push_back(d);
+*/
+        }
+        else
+        {
+            diffList.front().nofEquals += equalLinesAtStart;
+            currentLine1 += equalLinesAtStart;
+            currentLine2 += equalLinesAtStart;
+
+            LineRef nofEquals = std::min(size1 - currentLine1, size2 - currentLine2);
+            if(nofEquals == 0)
+            {
+                diffList.back().diff1 += size1 - currentLine1;
+                diffList.back().diff2 += size2 - currentLine2;
+            }
+            else
+            {
+                Diff d(nofEquals, size1 - currentLine1 - nofEquals, size2 - currentLine2 - nofEquals);
+                diffList.push_back(d);
+            }
+
+            /*
+         if ( gnuDiff.files[0].missing_newline != gnuDiff.files[1].missing_newline )
+         {
+            diffList.back().diff1 += gnuDiff.files[0].missing_newline ? 0 : 1;
+            diffList.back().diff2 += gnuDiff.files[1].missing_newline ? 0 : 1;
+         }
+         else if ( !gnuDiff.files[0].missing_newline )
+         {
+            ++ diffList.back().nofEquals;
+         }
+         */
+        }
+    }
+
+    // Verify difflist
+    {
+        LineRef l1 = 0;
+        LineRef l2 = 0;
+        DiffList::iterator i;
+        for(i = diffList.begin(); i != diffList.end(); ++i)
+        {
+            l1 += i->nofEquals + i->diff1;
+            l2 += i->nofEquals + i->diff2;
+        }
+
+        //if( l1!=p1-p1start || l2!=p2-p2start )
+        Q_ASSERT(l1 == size1 && l2 == size2);
+    }
+
+    pp.setCurrent(1);
+
+    return true;
+}
+
+bool runDiff(const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList,
+             int winIdx1, int winIdx2,
+             ManualDiffHelpList* pManualDiffHelpList,
+             Options* pOptions)
+{
+    diffList.clear();
+    DiffList diffList2;
+
+    int l1begin = 0;
+    int l2begin = 0;
+    ManualDiffHelpList::const_iterator i;
+    for(i = pManualDiffHelpList->begin(); i != pManualDiffHelpList->end(); ++i)
+    {
+        const ManualDiffHelpEntry& mdhe = *i;
+
+        int l1end = winIdx1 == 1 ? mdhe.lineA1 : winIdx1 == 2 ? mdhe.lineB1 : mdhe.lineC1;
+        int l2end = winIdx2 == 1 ? mdhe.lineA1 : winIdx2 == 2 ? mdhe.lineB1 : mdhe.lineC1;
+
+        if(l1end >= 0 && l2end >= 0)
+        {
+            runDiff(p1 + l1begin, l1end - l1begin, p2 + l2begin, l2end - l2begin, diffList2, pOptions);
+            diffList.splice(diffList.end(), diffList2);
+            l1begin = l1end;
+            l2begin = l2end;
+
+            l1end = winIdx1 == 1 ? mdhe.lineA2 : winIdx1 == 2 ? mdhe.lineB2 : mdhe.lineC2;
+            l2end = winIdx2 == 1 ? mdhe.lineA2 : winIdx2 == 2 ? mdhe.lineB2 : mdhe.lineC2;
+
+            if(l1end >= 0 && l2end >= 0)
+            {
+                ++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 correctManualDiffAlignment(Diff3LineList& d3ll, 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 = d3ll.begin();
+        int missingWinIdx = 0;
+        int alignedSum = (iMDHL->lineA1 < 0 ? 0 : 1) + (iMDHL->lineB1 < 0 ? 0 : 1) + (iMDHL->lineC1 < 0 ? 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->lineA1 < 0 ? 1 : (iMDHL->lineB1 < 0 ? 2 : 3);
+        }
+        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 = 0;
+        for(; i3 != d3ll.end(); ++i3)
+        {
+            for(wi = 1; wi <= 3; ++wi)
+            {
+                if(i3->getLineInFile(wi) >= 0 && iMDHL->firstLine(wi) == i3->getLineInFile(wi))
+                    break;
+            }
+            if(wi <= 3)
+                break;
+        }
+
+        if(wi >= 1 && wi <= 3)
+        {
+            // 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 = 0;
+            for(; i3 != d3ll.end(); ++i3)
+            {
+                for(wi2 = 1; wi2 <= 3; ++wi2)
+                {
+                    if(wi != wi2 && i3->getLineInFile(wi2) >= 0 && iMDHL->firstLine(wi2) == i3->getLineInFile(wi2))
+                        break;
+                }
+                if(wi2 > 3)
+                { // Not yet found
+                    // Move both others up
+                    Diff3Line d3l;
+                    // Move both up
+                    if(wi == 1) // Move B and C up
+                    {
+                        d3l.bBEqC = i3->bBEqC;
+                        d3l.lineB = i3->lineB;
+                        d3l.lineC = i3->lineC;
+                        i3->lineB = -1;
+                        i3->lineC = -1;
+                    }
+                    if(wi == 2) // Move A and C up
+                    {
+                        d3l.bAEqC = i3->bAEqC;
+                        d3l.lineA = i3->lineA;
+                        d3l.lineC = i3->lineC;
+                        i3->lineA = -1;
+                        i3->lineC = -1;
+                    }
+                    if(wi == 3) // Move A and B up
+                    {
+                        d3l.bAEqB = i3->bAEqB;
+                        d3l.lineA = i3->lineA;
+                        d3l.lineB = i3->lineB;
+                        i3->lineA = -1;
+                        i3->lineB = -1;
+                    }
+                    i3->bAEqB = false;
+                    i3->bAEqC = false;
+                    i3->bBEqC = false;
+                    d3ll.insert(iDest, d3l);
+                }
+                else
+                {
+                    // align the found line with the line we already have here
+                    if(i3 != iDest)
+                    {
+                        if(wi2 == 1)
+                        {
+                            iDest->lineA = i3->lineA;
+                            i3->lineA = -1;
+                            i3->bAEqB = false;
+                            i3->bAEqC = false;
+                        }
+                        else if(wi2 == 2)
+                        {
+                            iDest->lineB = i3->lineB;
+                            i3->lineB = -1;
+                            i3->bAEqB = false;
+                            i3->bBEqC = false;
+                        }
+                        else if(wi2 == 3)
+                        {
+                            iDest->lineC = i3->lineC;
+                            i3->lineC = -1;
+                            i3->bBEqC = false;
+                            i3->bAEqC = false;
+                        }
+                    }
+
+                    if(missingWinIdx != 0)
+                    {
+                        for(; i3 != d3ll.end(); ++i3)
+                        {
+                            int wi3 = missingWinIdx;
+                            if(i3->getLineInFile(wi3) >= 0)
+                            {
+                                // not found, move the line before iDest
+                                Diff3Line d3l;
+                                if(wi3 == 1)
+                                {
+                                    if(i3->bAEqB) // Stop moving lines up if one equal is found.
+                                        break;
+                                    d3l.lineA = i3->lineA;
+                                    i3->lineA = -1;
+                                    i3->bAEqB = false;
+                                    i3->bAEqC = false;
+                                }
+                                if(wi3 == 2)
+                                {
+                                    if(i3->bAEqB)
+                                        break;
+                                    d3l.lineB = i3->lineB;
+                                    i3->lineB = -1;
+                                    i3->bAEqB = false;
+                                    i3->bBEqC = false;
+                                }
+                                if(wi3 == 3)
+                                {
+                                    if(i3->bAEqC)
+                                        break;
+                                    d3l.lineC = i3->lineC;
+                                    i3->lineC = -1;
+                                    i3->bAEqC = false;
+                                    i3->bBEqC = false;
+                                }
+                                d3ll.insert(iDest, d3l);
+                            }
+                        } // for(), searching for wi3
+                    }
+                    break;
+                }
+            } // for(), searching for wi2
+        }     // if, wi found
+    }         // for (iMDHL)
+}
+
+// Fourth step
+void calcDiff3LineListTrim(
+    Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC, ManualDiffHelpList* pManualDiffHelpList)
+{
+    const Diff3Line d3l_empty;
+    d3ll.removeAll(d3l_empty);
+
+    Diff3LineList::iterator i3 = d3ll.begin();
+    Diff3LineList::iterator i3A = d3ll.begin();
+    Diff3LineList::iterator i3B = d3ll.begin();
+    Diff3LineList::iterator i3C = d3ll.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 != d3ll.end(); ++i3, ++line)
+    {
+        if(iMDHL != pManualDiffHelpList->end())
+        {
+            if((i3->lineA >= 0 && i3->lineA == iMDHL->lineA1) ||
+               (i3->lineB >= 0 && i3->lineB == iMDHL->lineB1) ||
+               (i3->lineC >= 0 && i3->lineC == iMDHL->lineC1))
+            {
+                i3A = i3;
+                i3B = i3;
+                i3C = i3;
+                lineA = line;
+                lineB = line;
+                lineC = line;
+                ++iMDHL;
+            }
+        }
+
+        if(line > lineA && (*i3).lineA != -1 && (*i3A).lineB != -1 && (*i3A).bBEqC &&
+           ::equal(pldA[(*i3).lineA], pldB[(*i3A).lineB], false) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineB, 1, 2) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineC, 1, 3))
+        {
+            // Empty space for A. A matches B and C in the empty line. Move it up.
+            (*i3A).lineA = (*i3).lineA;
+            (*i3A).bAEqB = true;
+            (*i3A).bAEqC = true;
+
+            (*i3).lineA = -1;
+            (*i3).bAEqB = false;
+            (*i3).bAEqC = false;
+            ++i3A;
+            ++lineA;
+        }
+
+        if(line > lineB && (*i3).lineB != -1 && (*i3B).lineA != -1 && (*i3B).bAEqC &&
+           ::equal(pldB[(*i3).lineB], pldA[(*i3B).lineA], false) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineA, 2, 1) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineC, 2, 3))
+        {
+            // Empty space for B. B matches A and C in the empty line. Move it up.
+            (*i3B).lineB = (*i3).lineB;
+            (*i3B).bAEqB = true;
+            (*i3B).bBEqC = true;
+            (*i3).lineB = -1;
+            (*i3).bAEqB = false;
+            (*i3).bBEqC = false;
+            ++i3B;
+            ++lineB;
+        }
+
+        if(line > lineC && (*i3).lineC != -1 && (*i3C).lineA != -1 && (*i3C).bAEqB &&
+           ::equal(pldC[(*i3).lineC], pldA[(*i3C).lineA], false) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineA, 3, 1) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineB, 3, 2))
+        {
+            // Empty space for C. C matches A and B in the empty line. Move it up.
+            (*i3C).lineC = (*i3).lineC;
+            (*i3C).bAEqC = true;
+            (*i3C).bBEqC = true;
+            (*i3).lineC = -1;
+            (*i3).bAEqC = false;
+            (*i3).bBEqC = false;
+            ++i3C;
+            ++lineC;
+        }
+
+        if(line > lineA && (*i3).lineA != -1 && !(*i3).bAEqB && !(*i3).bAEqC &&
+           isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineB, 1, 2) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineC, 1, 3)) {
+            // Empty space for A. A doesn't match B or C. Move it up.
+            (*i3A).lineA = (*i3).lineA;
+            (*i3).lineA = -1;
+
+            if(i3A->lineB != -1 && ::equal(pldA[i3A->lineA], pldB[i3A->lineB], false))
+            {
+                i3A->bAEqB = true;
+            }
+            if((i3A->bAEqB && i3A->bBEqC) ||
+               (i3A->lineC != -1 && ::equal(pldA[i3A->lineA], pldC[i3A->lineC], false)))
+            {
+                i3A->bAEqC = true;
+            }
+
+            ++i3A;
+            ++lineA;
+        }
+
+        if(line > lineB && (*i3).lineB != -1 && !(*i3).bAEqB && !(*i3).bBEqC &&
+           isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineA, 2, 1) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineC, 2, 3))
+        {
+            // Empty space for B. B matches neither A nor C. Move B up.
+            (*i3B).lineB = (*i3).lineB;
+            (*i3).lineB = -1;
+
+            if(i3B->lineA != -1 && ::equal(pldA[i3B->lineA], pldB[i3B->lineB], false))
+            {
+                i3B->bAEqB = true;
+            }
+            if((i3B->bAEqB && i3B->bAEqC) ||
+               (i3B->lineC != -1 && ::equal(pldB[i3B->lineB], pldC[i3B->lineC], false)))
+            {
+                i3B->bBEqC = true;
+            }
+
+            ++i3B;
+            ++lineB;
+        }
+
+        if(line > lineC && (*i3).lineC != -1 && !(*i3).bAEqC && !(*i3).bBEqC &&
+           isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineA, 3, 1) &&
+           isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineB, 3, 2))
+        {
+            // Empty space for C. C matches neither A nor B. Move C up.
+            (*i3C).lineC = (*i3).lineC;
+            (*i3).lineC = -1;
+
+            if(i3C->lineA != -1 && ::equal(pldA[i3C->lineA], pldC[i3C->lineC], false))
+            {
+                i3C->bAEqC = true;
+            }
+            if((i3C->bAEqC && i3C->bAEqB) ||
+               (i3C->lineB != -1 && ::equal(pldB[i3C->lineB], pldC[i3C->lineC], false)))
+            {
+                i3C->bBEqC = true;
+            }
+
+            ++i3C;
+            ++lineC;
+        }
+
+        if(line > lineA && line > lineB && (*i3).lineA != -1 && (*i3).bAEqB && !(*i3).bAEqC)
+        {
+            // 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(isValidMove(pManualDiffHelpList, i->lineC, (*i3).lineA, 3, 1) &&
+               isValidMove(pManualDiffHelpList, i->lineC, (*i3).lineB, 3, 2))
+            {
+                (*i).lineA = (*i3).lineA;
+                (*i).lineB = (*i3).lineB;
+                (*i).bAEqB = true;
+
+                if(i->lineC != -1 && ::equal(pldA[i->lineA], pldC[i->lineC], false))
+                {
+                    (*i).bAEqC = true;
+                    (*i).bBEqC = true;
+                }
+
+                (*i3).lineA = -1;
+                (*i3).lineB = -1;
+                (*i3).bAEqB = false;
+                i3A = i;
+                i3B = i;
+                ++i3A;
+                ++i3B;
+                lineA = l + 1;
+                lineB = l + 1;
+            }
+        }
+        else if(line > lineA && line > lineC && (*i3).lineA != -1 && (*i3).bAEqC && !(*i3).bAEqB)
+        {
+            // 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(isValidMove(pManualDiffHelpList, i->lineB, (*i3).lineA, 2, 1) &&
+               isValidMove(pManualDiffHelpList, i->lineB, (*i3).lineC, 2, 3))
+            {
+                (*i).lineA = (*i3).lineA;
+                (*i).lineC = (*i3).lineC;
+                (*i).bAEqC = true;
+
+                if(i->lineB != -1 && ::equal(pldA[i->lineA], pldB[i->lineB], false))
+                {
+                    (*i).bAEqB = true;
+                    (*i).bBEqC = true;
+                }
+
+                (*i3).lineA = -1;
+                (*i3).lineC = -1;
+                (*i3).bAEqC = false;
+                i3A = i;
+                i3C = i;
+                ++i3A;
+                ++i3C;
+                lineA = l + 1;
+                lineC = l + 1;
+            }
+        }
+        else if(line > lineB && line > lineC && (*i3).lineB != -1 && (*i3).bBEqC && !(*i3).bAEqC)
+        {
+            // 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(isValidMove(pManualDiffHelpList, i->lineA, (*i3).lineB, 1, 2) &&
+               isValidMove(pManualDiffHelpList, i->lineA, (*i3).lineC, 1, 3))
+            {
+                (*i).lineB = (*i3).lineB;
+                (*i).lineC = (*i3).lineC;
+                (*i).bBEqC = true;
+
+                if(i->lineA != -1 && ::equal(pldA[i->lineA], pldB[i->lineB], false))
+                {
+                    (*i).bAEqB = true;
+                    (*i).bAEqC = true;
+                }
+
+                (*i3).lineB = -1;
+                (*i3).lineC = -1;
+                (*i3).bBEqC = false;
+                i3B = i;
+                i3C = i;
+                ++i3B;
+                ++i3C;
+                lineB = l + 1;
+                lineC = l + 1;
+            }
+        }
+
+        if((*i3).lineA != -1)
+        {
+            lineA = line + 1;
+            i3A = i3;
+            ++i3A;
+        }
+        if((*i3).lineB != -1)
+        {
+            lineB = line + 1;
+            i3B = i3;
+            ++i3B;
+        }
+        if((*i3).lineC != -1)
+        {
+            lineC = line + 1;
+            i3C = i3;
+            ++i3C;
+        }
+    }
+
+    d3ll.removeAll(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).lineA, (*it).lineB, (*it).lineC,
+         (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' );
+
+   }
+*/
+}
+
+void DiffBufferInfo::init(Diff3LineList* pD3ll, const Diff3LineVector* pD3lv,
+                          const LineData* pldA, LineRef sizeA, const LineData* pldB, LineRef sizeB, const LineData* pldC, LineRef sizeC)
+{
+    m_pDiff3LineList = pD3ll;
+    m_pDiff3LineVector = pD3lv;
+    m_pLineDataA = pldA;
+    m_pLineDataB = pldB;
+    m_pLineDataC = pldC;
+    m_sizeA = sizeA;
+    m_sizeB = sizeB;
+    m_sizeC = sizeC;
+    Diff3LineList::iterator i3 = pD3ll->begin();
+    for(; i3 != pD3ll->end(); ++i3)
+    {
+        i3->m_pDiffBufferInfo = this;
+    }
+}
+
+void calcWhiteDiff3Lines(
+    Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC)
+{
+    Diff3LineList::iterator i3 = d3ll.begin();
+
+    for(; i3 != d3ll.end(); ++i3)
+    {
+        i3->bWhiteLineA = ((*i3).lineA == -1 || pldA == nullptr || pldA[(*i3).lineA].whiteLine() || pldA[(*i3).lineA].bContainsPureComment);
+        i3->bWhiteLineB = ((*i3).lineB == -1 || pldB == nullptr || pldB[(*i3).lineB].whiteLine() || pldB[(*i3).lineB].bContainsPureComment);
+        i3->bWhiteLineC = ((*i3).lineC == -1 || pldC == nullptr || pldC[(*i3).lineC].whiteLine() || pldC[(*i3).lineC].bContainsPureComment);
+    }
+}
+
+inline bool equal(QChar c1, QChar c2, bool /*bStrict*/)
+{
+    // If bStrict then white space doesn't match
+
+    //if ( bStrict &&  ( c1==' ' || c1=='\t' ) )
+    //   return false;
+
+    return c1 == c2;
+}
+
+// My own diff-invention:
+template <class T>
+void calcDiff(const T* p1, LineRef size1, const T* p2, LineRef size2, DiffList& diffList, int match, int maxSearchRange)
+{
+    diffList.clear();
+
+    const T* p1start = p1;
+    const T* p2start = p2;
+    const T* p1end = p1 + size1;
+    const T* p2end = p2 + size2;
+    for(;;)
+    {
+        int nofEquals = 0;
+        while(p1 != p1end && p2 != p2end && equal(*p1, *p2, false))
+        {
+            ++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] == p1end || (bBestValid && i1 >= bestI1 + bestI2))
+            {
+                break;
+            }
+            for(i2 = 0; i2 < maxSearchRange; ++i2)
+            {
+                if(&p2[i2] == p2end || (bBestValid && i1 + i2 >= bestI1 + bestI2))
+                {
+                    break;
+                }
+                else if(equal(p2[i2], p1[i1], true) &&
+                        (match == 1 || abs(i1 - i2) < 3 || (&p2[i2 + 1] == p2end && &p1[i1 + 1] == p1end) ||
+                         (&p2[i2 + 1] != p2end && &p1[i1 + 1] != p1end && equal(p2[i2 + 1], p1[i1 + 1], false))))
+                {
+                    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 && equal(p1[bestI1 - 1], p2[bestI2 - 1], false))
+        {
+            --bestI1;
+            --bestI2;
+        }
+
+        bool bEndReached = false;
+        if(bBestValid)
+        {
+            // continue somehow
+            Diff d(nofEquals, bestI1, bestI2);
+            diffList.push_back(d);
+
+            p1 += bestI1;
+            p2 += bestI2;
+        }
+        else
+        {
+            // Nothing else to match.
+            Diff d(nofEquals, p1end - p1, p2end - 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;
+        const T* pu1 = p1 - 1;
+        const T* pu2 = p2 - 1;
+        while(pu1 >= p1start && pu2 >= p2start && equal(*pu1, *pu2, false))
+        {
+            ++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.diff1;
+                    --d.diff2;
+                    --nofUnmatched;
+                }
+                else if(d.nofEquals > 0)
+                {
+                    --d.nofEquals;
+                    --nofUnmatched;
+                }
+
+                if(d.nofEquals == 0 && (d.diff1 == 0 || d.diff2 == 0) && nofUnmatched > 0)
+                {
+                    if(diffList.empty())
+                        break;
+                    d.nofEquals += diffList.back().nofEquals;
+                    d.diff1 += diffList.back().diff1;
+                    d.diff2 += 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;
+    }
+
+    // Verify difflist
+    {
+        LineRef l1 = 0;
+        LineRef l2 = 0;
+        DiffList::iterator i;
+        for(i = diffList.begin(); i != diffList.end(); ++i)
+        {
+            l1 += i->nofEquals + i->diff1;
+            l2 += i->nofEquals + i->diff2;
+        }
+
+        Q_ASSERT(l1 == size1 && l2 == size2);
+    }
+}
+
+bool fineDiff(
+    Diff3LineList& diff3LineList,
+    int selector,
+    const LineData* v1,
+    const LineData* v2)
+{
+    // Finetuning: Diff each line with deltas
+    ProgressProxy pp;
+    int maxSearchLength = 500;
+    Diff3LineList::iterator i;
+    LineRef k1 = 0;
+    LineRef k2 = 0;
+    bool bTextsTotalEqual = true;
+    int listSize = diff3LineList.size();
+    pp.setMaxNofSteps(listSize);
+    int listIdx = 0;
+    for(i = diff3LineList.begin(); i != diff3LineList.end(); ++i)
+    {
+        Q_ASSERT(selector == 1 || selector == 2 || selector == 3);
+
+        if(selector == 1) {
+            k1 = i->lineA;
+            k2 = i->lineB;
+        }
+        else if(selector == 2)
+        {
+            k1 = i->lineB;
+            k2 = i->lineC;
+        }
+        else if(selector == 3)
+        {
+            k1 = i->lineC;
+            k2 = i->lineA;
+        }
+
+        if((k1 == -1 && k2 != -1) || (k1 != -1 && k2 == -1)) bTextsTotalEqual = false;
+        if(k1 != -1 && k2 != -1)
+        {
+            if(v1[k1].size != v2[k2].size || memcmp(v1[k1].pLine, v2[k2].pLine, v1[k1].size << 1) != 0)
+            {
+                bTextsTotalEqual = false;
+                DiffList* pDiffList = new DiffList;
+                calcDiff(v1[k1].pLine, v1[k1].size, v2[k2].pLine, v2[k2].size, *pDiffList, 2, maxSearchLength);
+
+                // Optimize the diff list.
+                DiffList::iterator dli;
+                bool bUsefulFineDiff = false;
+                for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli)
+                {
+                    if(dli->nofEquals >= 4)
+                    {
+                        bUsefulFineDiff = true;
+                        break;
+                    }
+                }
+
+                for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli)
+                {
+                    if(dli->nofEquals < 4 && (dli->diff1 > 0 || dli->diff2 > 0) && !(bUsefulFineDiff && dli == pDiffList->begin()))
+                    {
+                        dli->diff1 += dli->nofEquals;
+                        dli->diff2 += dli->nofEquals;
+                        dli->nofEquals = 0;
+                    }
+                }
+
+                Q_ASSERT(selector == 1 || selector == 2 || selector == 3);
+                if(selector == 1) {
+                    delete(*i).pFineAB;
+                    (*i).pFineAB = pDiffList;
+                }
+                else if(selector == 2)
+                {
+                    delete(*i).pFineBC;
+                    (*i).pFineBC = pDiffList;
+                }
+                else if(selector == 3)
+                {
+                    delete(*i).pFineCA;
+                    (*i).pFineCA = pDiffList;
+                }
+            }
+
+            if((v1[k1].bContainsPureComment || v1[k1].whiteLine()) && (v2[k2].bContainsPureComment || v2[k2].whiteLine()))
+            {
+                Q_ASSERT(selector == 1 || selector == 2 || selector == 3);
+
+                if(selector == 1) {
+                    i->bAEqB = true;
+                }
+                else if(selector == 2)
+                {
+                    i->bBEqC = true;
+                }
+                else if(selector == 3)
+                {
+                    i->bAEqC = true;
+                }
+            }
+        }
+        ++listIdx;
+        pp.step();
+    }
+    return bTextsTotalEqual;
+}
+
+// Convert the list to a vector of pointers
+void calcDiff3LineVector(Diff3LineList& d3ll, Diff3LineVector& d3lv)
+{
+    d3lv.resize(d3ll.size());
+    Diff3LineList::iterator i;
+    int j = 0;
+    for(i = d3ll.begin(); i != d3ll.end(); ++i, ++j)
+    {
+        d3lv[j] = &(*i);
+    }
+    Q_ASSERT(j == (int)d3lv.size());
+}
diff --git a/src/diff.h b/src/diff.h
new file mode 100644 (file)
index 0000000..a6ed5e9
--- /dev/null
@@ -0,0 +1,449 @@
+/***************************************************************************
+                          diff.h  -  description
+                             -------------------
+    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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef DIFF_H
+#define DIFF_H
+
+#include <QPainter>
+#include <QLinkedList>
+#include <QVector>
+#include <QTemporaryFile>
+
+#include "common.h"
+#include "fileaccess.h"
+#include "options.h"
+#include "gnudiff_diff.h"
+
+// Each range with matching elements is followed by a range with differences on either side.
+// Then again range of matching elements should follow.
+struct Diff
+{
+   LineRef nofEquals;
+
+   qint64 diff1;
+   qint64 diff2;
+
+   Diff(LineRef eq, qint64 d1, qint64 d2){nofEquals=eq; diff1=d1; diff2=d2; }
+};
+
+typedef std::list<Diff> DiffList;
+
+struct LineData
+{
+   const QChar* pLine;
+   const QChar* pFirstNonWhiteChar;
+   int size;
+
+   LineData(){ pLine=nullptr; pFirstNonWhiteChar=nullptr; size=0; /*occurrences=0;*/
+               bContainsPureComment=false; }
+   int width(int tabSize) const;  // Calcs width considering tabs.
+   //int occurrences;
+   bool whiteLine() const { return pFirstNonWhiteChar-pLine == size; }
+   bool bContainsPureComment;
+};
+
+class Diff3LineList;
+class Diff3LineVector;
+
+struct DiffBufferInfo
+{
+   const LineData* m_pLineDataA;
+   const LineData* m_pLineDataB;
+   const LineData* m_pLineDataC;
+   LineRef m_sizeA;
+   LineRef m_sizeB;
+   LineRef m_sizeC;
+   const Diff3LineList* m_pDiff3LineList;
+   const Diff3LineVector* m_pDiff3LineVector;
+   void init( Diff3LineList* d3ll, const Diff3LineVector* d3lv,
+      const LineData* pldA, LineRef sizeA, const LineData* pldB, LineRef sizeB, const LineData* pldC, LineRef sizeC );
+};
+
+struct Diff3Line
+{
+   LineRef lineA;
+   LineRef lineB;
+   LineRef lineC;
+
+   bool bAEqC : 1;             // These are true if equal or only white-space changes exist.
+   bool bBEqC : 1;
+   bool bAEqB : 1;
+
+   bool bWhiteLineA : 1;
+   bool bWhiteLineB : 1;
+   bool bWhiteLineC : 1;
+
+   DiffList* pFineAB;          // These are 0 only if completely equal or if either source doesn't exist.
+   DiffList* pFineBC;
+   DiffList* pFineCA;
+
+   int linesNeededForDisplay; // Due to wordwrap
+   int sumLinesNeededForDisplay; // For fast conversion to m_diff3WrapLineVector
+
+   DiffBufferInfo* m_pDiffBufferInfo; // For convenience
+
+   Diff3Line()
+   {
+      lineA=-1; lineB=-1; lineC=-1;
+      bAEqC=false; bAEqB=false; bBEqC=false;
+      pFineAB=nullptr; pFineBC=nullptr; pFineCA=nullptr;
+      linesNeededForDisplay=1;
+      sumLinesNeededForDisplay=0;
+      bWhiteLineA=false; bWhiteLineB=false; bWhiteLineC=false;
+      m_pDiffBufferInfo=nullptr;
+   }
+
+   ~Diff3Line()
+   {
+      if (pFineAB!=nullptr) delete pFineAB;
+      if (pFineBC!=nullptr) delete pFineBC;
+      if (pFineCA!=nullptr) delete pFineCA;
+      pFineAB=nullptr; pFineBC=nullptr; pFineCA=nullptr;
+   }
+
+   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( int src ) const
+   {
+      Q_ASSERT( m_pDiffBufferInfo!=nullptr );
+      if ( src == 1 && lineA >= 0 ) return &m_pDiffBufferInfo->m_pLineDataA[lineA];
+      if ( src == 2 && lineB >= 0 ) return &m_pDiffBufferInfo->m_pLineDataB[lineB];
+      if ( src == 3 && lineC >= 0 ) return &m_pDiffBufferInfo->m_pLineDataC[lineC];
+      return nullptr;
+   }
+   QString getString( int src ) const
+   {
+      const LineData* pld = getLineData(src);
+      if ( pld )
+         return QString( pld->pLine, pld->size);
+      else
+         return QString();
+   }
+   LineRef getLineInFile( int src ) const
+   {
+      if ( src == 1 ) return lineA;
+      if ( src == 2 ) return lineB;
+      if ( src == 3 ) return lineC;
+      return -1;
+   }
+};
+
+
+class Diff3LineList : public QLinkedList<Diff3Line>
+{
+};
+class Diff3LineVector : public QVector<Diff3Line*>
+{
+};
+
+class Diff3WrapLine
+{
+public:
+   Diff3Line* pD3L;
+   int diff3LineIndex;
+   int wrapLineOffset;
+   int wrapLineLength;
+};
+
+typedef QVector<Diff3WrapLine> Diff3WrapLineVector;
+
+
+class TotalDiffStatus
+{
+public:
+   TotalDiffStatus(){ reset(); }
+   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; }
+
+   bool isBinaryEqualAC() const { return bBinaryAEqC; }
+   bool isBinaryEqualBC() const { return bBinaryBEqC; }
+   bool isBinaryEqualAB() const { return bBinaryAEqB; }
+
+   bool bBinaryAEqC : 1;
+   bool bBinaryBEqC : 1;
+   bool bBinaryAEqB : 1;
+
+   bool bTextAEqC : 1;
+   bool bTextBEqC : 1;
+   bool bTextAEqB : 1;
+
+private:
+   int nofUnsolvedConflicts;
+   int nofSolvedConflicts;
+   int nofWhitespaceConflicts;
+};
+
+// Three corresponding ranges. (Minimum size of a valid range is one line.)
+class ManualDiffHelpEntry
+{
+public:
+   ManualDiffHelpEntry() { lineA1=-1; lineA2=-1;
+                           lineB1=-1; lineB2=-1;
+                           lineC1=-1; lineC2=-1; }
+   LineRef lineA1;
+   LineRef lineA2;
+   LineRef lineB1;
+   LineRef lineB2;
+   LineRef lineC1;
+   LineRef lineC2;
+   LineRef& firstLine( int winIdx )
+   {
+      return winIdx==1 ? lineA1 : (winIdx==2 ? lineB1 : lineC1 );
+   }
+   LineRef& lastLine( int winIdx )
+   {
+      return winIdx==1 ? lineA2 : (winIdx==2 ? lineB2 : lineC2 );
+   }
+   bool isLineInRange( LineRef line, int winIdx )
+   {
+      return line>=0 && 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;
+   }
+};
+
+// A list of corresponding ranges
+typedef std::list<ManualDiffHelpEntry> ManualDiffHelpList;
+
+void calcDiff3LineListUsingAB(
+   const DiffList* pDiffListAB,
+   Diff3LineList& d3ll
+   );
+
+void calcDiff3LineListUsingAC(
+   const DiffList* pDiffListAC,
+   Diff3LineList& d3ll
+   );
+
+void calcDiff3LineListUsingBC(
+   const DiffList* pDiffListBC,
+   Diff3LineList& d3ll
+   );
+
+void correctManualDiffAlignment( Diff3LineList& d3ll, ManualDiffHelpList* pManualDiffHelpList );
+
+class SourceData
+{
+public:
+   SourceData();
+   ~SourceData();
+
+   void setOptions( Options* pOptions );
+
+   LineRef getSizeLines() const;
+   qint64 getSizeBytes() const;
+   const char* getBuf() const;
+   const QString& getText() const;
+   const LineData* getLineDataForDisplay() const;
+   const LineData* getLineDataForDiff() const;
+
+   void setFilename(const QString& filename);
+   void setFileAccess( const FileAccess& fileAccess );
+   void setEncoding(QTextCodec* pEncoding);
+   //FileAccess& getFileAccess();
+   QString getFilename();
+   void setAliasName(const QString& name);
+   QString getAliasName();
+   bool isEmpty();  // File was set
+   bool hasData();  // Data was readable
+   bool isText();   // is it pure text (vs. binary data)
+   bool isIncompleteConversion(); // true if some replacement characters were found
+   bool isFromBuffer();  // was it set via setData() (vs. setFileAccess() or setFilename())
+   QStringList setData( const QString& data );
+   bool isValid(); // 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 SourceData& other ) const;
+
+   void reset();
+
+   QTextCodec* getEncoding() const { return m_pEncoding; }
+   e_LineEndStyle getLineEndStyle() const { return m_normalData.m_eLineEndStyle; }
+
+private:
+   QTextCodec* detectEncoding( const QString& fileName, QTextCodec* pFallbackCodec );
+   QString m_aliasName;
+   FileAccess m_fileAccess;
+   Options* m_pOptions;
+   QString m_tempInputFileName;
+   QTemporaryFile m_tempFile;//Created from clipboard content.
+
+   struct FileData
+   {
+      FileData(){ m_pBuf=nullptr; m_size=0; m_vSize=0; m_bIsText=false; m_eLineEndStyle=eLineEndStyleUndefined; m_bIncompleteConversion=false;}
+      ~FileData(){ reset(); }
+      const char* m_pBuf;
+      qint64 m_size;
+      qint64 m_vSize; // Nr of lines in m_pBuf1 and size of m_v1, m_dv12 and m_dv13
+      QString m_unicodeBuf;
+      QVector<LineData> m_v;
+      bool m_bIsText;
+      bool m_bIncompleteConversion;
+      e_LineEndStyle m_eLineEndStyle;
+      bool readFile( const QString& filename );
+      bool writeFile( const QString& filename );
+      bool preprocess(bool bPreserveCR, QTextCodec* pEncoding );
+      void reset();
+      void removeComments();
+      void copyBufFrom( const FileData& src );
+
+      void checkLineForComments(
+          const QChar* p,          // pointer to start of buffer
+          int& i,                  // index of current position (in, out)
+          int size,                // size of buffer
+          bool& bWhite,            // false if this line contains nonwhite characters (in, out)
+          bool& bCommentInLine,    // true if any comment is within this line (in, out)
+          bool& bStartsOpenComment // true if the line ends within an comment (out)
+      );
+   };
+   FileData m_normalData;
+   FileData m_lmppData;
+   QTextCodec* m_pEncoding;
+};
+
+void calcDiff3LineListTrim( Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC, ManualDiffHelpList* pManualDiffHelpList );
+void calcWhiteDiff3Lines(   Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC );
+
+void calcDiff3LineVector( Diff3LineList& d3ll, Diff3LineVector& d3lv );
+
+// Helper class that swaps left and right for some commands.
+class MyPainter : public QPainter
+{
+   int m_factor;
+   int m_xOffset;
+   int m_fontWidth;
+public:
+   MyPainter(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 );
+   }
+};
+
+bool runDiff( const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, int winIdx1, int winIdx2,
+              ManualDiffHelpList *pManualDiffHelpList, Options *pOptions);
+
+bool fineDiff(
+   Diff3LineList& diff3LineList,
+   int selector,
+   const LineData* v1,
+   const LineData* v2
+   );
+
+
+bool equal( const LineData& l1, const LineData& l2, bool bStrict );
+
+
+
+
+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_bIgnoreWhiteSpace;
+extern bool g_bIgnoreTrivialMatches;
+extern int g_bAutoSolve;
+
+// Cursor conversions that consider g_tabSize.
+int convertToPosInText( const QString& s, int posOnScreen, int tabSize );
+int convertToPosOnScreen( const QString& s, int posInText, int tabSize );
+
+enum e_CoordType { eFileCoords, eD3LLineCoords, eWrapCoords };
+
+void calcTokenPos( const QString&, int posOnScreen, int& pos1, int& pos2, int tabSize );
+
+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
new file mode 100644 (file)
index 0000000..3e2bbca
--- /dev/null
@@ -0,0 +1,2080 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "difftextwindow.h"
+
+#include "selection.h"
+#include "kdiff3.h"
+#include "merger.h"
+#include "options.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <QtMath>
+#include <QDir>
+#include <QDragEnterEvent>
+#include <QFileDialog>
+#include <QLabel>
+#include <QLayout>
+#include <QLineEdit>
+#include <QMenu>
+#include <QMimeData>
+#include <QPushButton>
+#include <QRunnable>
+#include <QStatusBar>
+#include <QTextCodec>
+#include <QTextLayout>
+#include <QThreadPool>
+#include <QToolTip>
+#include <QUrl>
+
+#include <KLocalizedString>
+
+QAtomicInt s_runnableCount = 0;
+
+class DiffTextWindowData
+{
+  public:
+    explicit DiffTextWindowData(DiffTextWindow* p)
+    {
+        m_pDiffTextWindow = p;
+        m_bPaintingAllowed = false;
+        m_pLineData = nullptr;
+        m_size = 0;
+        m_bWordWrap = false;
+        m_delayedDrawTimer = 0;
+        m_pDiff3LineVector = nullptr;
+        m_pManualDiffHelpList = nullptr;
+        m_pOptions = nullptr;
+        m_fastSelectorLine1 = 0;
+        m_fastSelectorNofLines = 0;
+        m_bTriple = false;
+        m_winIdx = 0;
+        m_firstLine = 0;
+        m_oldFirstLine = 0;
+        m_horizScrollOffset = 0;
+        m_lineNumberWidth = 0;
+        m_maxTextWidth = -1;
+        m_pStatusBar = nullptr;
+        m_scrollDeltaX = 0;
+        m_scrollDeltaY = 0;
+        m_bMyUpdate = false;
+        m_bSelectionInProgress = false;
+        m_pTextCodec = nullptr;
+#if defined(Q_OS_WIN)
+        m_eLineEndStyle = eLineEndStyleDos;
+#else
+        m_eLineEndStyle = eLineEndStyleUnix;
+#endif
+    }
+    DiffTextWindow* m_pDiffTextWindow;
+    DiffTextWindowFrame* m_pDiffTextWindowFrame = nullptr;
+    QTextCodec* m_pTextCodec;
+    e_LineEndStyle m_eLineEndStyle;
+
+    bool m_bPaintingAllowed;
+    const LineData* m_pLineData;
+    int m_size;
+    QString m_filename;
+    bool m_bWordWrap;
+    int m_delayedDrawTimer;
+
+    const Diff3LineVector* m_pDiff3LineVector;
+    Diff3WrapLineVector m_diff3WrapLineVector;
+    const ManualDiffHelpList* m_pManualDiffHelpList;
+
+    class WrapLineCacheData
+    {
+      public:
+        WrapLineCacheData()  {}
+        WrapLineCacheData(int d3LineIdx, int textStart, int textLength)
+            : m_d3LineIdx(d3LineIdx), m_textStart(textStart), m_textLength(textLength) {}
+        int m_d3LineIdx = 0;
+        int m_textStart = 0;
+        int m_textLength = 0;
+    };
+    QList<QVector<WrapLineCacheData>> m_wrapLineCacheList;
+
+    Options* m_pOptions;
+    QColor m_cThis;
+    QColor m_cDiff1;
+    QColor m_cDiff2;
+    QColor m_cDiffBoth;
+
+    int m_fastSelectorLine1;
+    int m_fastSelectorNofLines;
+
+    bool m_bTriple;
+    int m_winIdx;
+    int m_firstLine;
+    int m_oldFirstLine;
+    int m_horizScrollOffset;
+    int m_lineNumberWidth;
+    QAtomicInt m_maxTextWidth;
+
+    void getLineInfo(
+        const Diff3Line& d,
+        int& lineIdx,
+        DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values
+        int& changed, int& changed2);
+
+    QString getString(int d3lIdx);
+    QString getLineString(int line);
+
+    void writeLine(
+        MyPainter& p, const LineData* pld,
+        const DiffList* pLineDiff1, const DiffList* pLineDiff2, int line,
+        int whatChanged, int whatChanged2, int srcLineIdx,
+        int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect, int deviceWidth);
+
+    void draw(MyPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine);
+
+    QStatusBar* m_pStatusBar;
+
+    Selection m_selection;
+
+    int m_scrollDeltaX;
+    int m_scrollDeltaY;
+
+    bool m_bMyUpdate;
+    void myUpdate(int afterMilliSecs);
+
+    int leftInfoWidth() { return 4 + m_lineNumberWidth; } // Nr of information columns on left side
+    int convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine);
+
+    bool m_bSelectionInProgress;
+    QPoint m_lastKnownMousePos;
+    void prepareTextLayout(QTextLayout& textLayout, bool bFirstLine, int visibleTextWidth = -1);
+};
+
+DiffTextWindow::DiffTextWindow(
+    DiffTextWindowFrame* pParent,
+    QStatusBar* pStatusBar,
+    Options* pOptions,
+    int winIdx)
+    : QWidget(pParent)
+{
+    setObjectName(QString("DiffTextWindow%1").arg(winIdx));
+    setAttribute(Qt::WA_OpaquePaintEvent);
+    //setAttribute( Qt::WA_PaintOnScreen );
+
+    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;
+    d->m_bPaintingAllowed = 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 LineData* 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();
+    d->m_pDiffTextWindowFrame->init();
+}
+
+void DiffTextWindow::reset()
+{
+    d->m_pLineData = nullptr;
+    d->m_size = 0;
+    d->m_pDiff3LineVector = nullptr;
+    d->m_filename = "";
+    d->m_diff3WrapLineVector.clear();
+}
+
+void DiffTextWindow::setPaintingAllowed(bool bAllowPainting)
+{
+    if(d->m_bPaintingAllowed != bAllowPainting)
+    {
+        d->m_bPaintingAllowed = bAllowPainting;
+        if(d->m_bPaintingAllowed)
+            update();
+        else
+            reset();
+    }
+}
+
+void DiffTextWindow::dragEnterEvent(QDragEnterEvent* e)
+{
+    e->setAccepted(e->mimeData()->hasUrls() || e->mimeData()->hasText());
+    // Note that the corresponding drop is handled in KDiff3App::eventFilter().
+}
+
+void DiffTextWindow::setFirstLine(int firstLine)
+{
+    int fontHeight = fontMetrics().lineSpacing();
+
+    int newFirstLine = std::max(0, firstLine);
+
+    int deltaY = fontHeight * (d->m_firstLine - newFirstLine);
+
+    d->m_firstLine = newFirstLine;
+
+    if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine())
+    {
+        int line, 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 = fontMetrics().width('0');
+    int xOffset = d->leftInfoWidth() * fontWidth;
+
+    int deltaX = d->m_horizScrollOffset - qMax(0, horizScrollOffset);
+
+    d->m_horizScrollOffset = qMax(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())
+    {
+        int line, 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);
+}
+
+int DiffTextWindow::getNofLines()
+{
+    return d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size();
+}
+
+int DiffTextWindow::convertLineToDiff3LineIdx(int line)
+{
+    if(line >= 0 && d->m_bWordWrap && d->m_diff3WrapLineVector.size() > 0)
+        return d->m_diff3WrapLineVector[std::min(line, (int)d->m_diff3WrapLineVector.size() - 1)].diff3LineIndex;
+    else
+        return line;
+}
+
+int 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)
+        {
+            emit scrollDiffTextWindow(0, newFirstLine - d->m_firstLine);
+        }
+
+        update();
+    }
+}
+
+void DiffTextWindow::showStatusLine(int line)
+{
+    int d3lIdx = convertLineToDiff3LineIdx(line);
+    if(d->m_pDiff3LineVector != nullptr && d3lIdx >= 0 && d3lIdx < (int)d->m_pDiff3LineVector->size())
+    {
+        const Diff3Line* pD3l = (*d->m_pDiff3LineVector)[d3lIdx];
+        if(pD3l != nullptr)
+        {
+            int l = pD3l->getLineInFile(d->m_winIdx);
+
+            QString s;
+            if(l != -1)
+                s = i18n("File %1: Line %2", d->m_filename, l + 1);
+            else
+                s = i18n("File %1: Line not available", d->m_filename);
+            if(d->m_pStatusBar != nullptr) d->m_pStatusBar->showMessage(s);
+
+            emit lineClicked(d->m_winIdx, l);
+        }
+    }
+}
+
+void DiffTextWindow::focusInEvent(QFocusEvent* e)
+{
+    emit gotFocus();
+    QWidget::focusInEvent(e);
+}
+
+void DiffTextWindow::mousePressEvent(QMouseEvent* e)
+{
+    if(e->button() == Qt::LeftButton)
+    {
+        int line;
+        int pos;
+        convertToLinePos(e->x(), e->y(), line, pos);
+
+        int fontWidth = fontMetrics().width('0');
+        int xOffset = d->leftInfoWidth() * fontWidth;
+
+        if((!d->m_pOptions->m_bRightToLeftLanguage && e->x() < xOffset) || (d->m_pOptions->m_bRightToLeftLanguage && e->x() > width() - xOffset))
+        {
+            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);
+        }
+    }
+}
+
+bool 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 calcTokenPos(const QString& s, int posOnScreen, int& pos1, int& pos2, int tabSize)
+{
+    // Cursor conversions that consider g_tabSize
+    int pos = convertToPosInText(s, std::max(0, posOnScreen), tabSize);
+    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;
+    }
+}
+
+void DiffTextWindow::mouseDoubleClickEvent(QMouseEvent* e)
+{
+    d->m_bSelectionInProgress = false;
+    d->m_lastKnownMousePos = e->pos();
+    if(e->button() == Qt::LeftButton)
+    {
+        int 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 < 0 || 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 < 0 || line >= (int)d->m_pDiff3LineVector->size())
+                return;
+            s = d->getString(line);
+        }
+
+        if(!s.isEmpty())
+        {
+            int pos1, pos2;
+            calcTokenPos(s, pos, pos1, pos2, d->m_pOptions->m_tabSize);
+
+            resetSelection();
+            d->m_selection.start(line, convertToPosOnScreen(s, pos1, d->m_pOptions->m_tabSize));
+            d->m_selection.end(line, convertToPosOnScreen(s, pos2, d->m_pOptions->m_tabSize));
+            update();
+            // 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())
+        {
+            emit selectionEnd();
+        }
+    }
+    d->m_scrollDeltaX = 0;
+    d->m_scrollDeltaY = 0;
+}
+
+inline int sqr(int x) { return x * x; }
+
+void DiffTextWindow::mouseMoveEvent(QMouseEvent* e)
+{
+    int 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 = fm.width('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 - sqr(e->y()) / sqr(fm.lineSpacing());
+        if(e->y() > height()) deltaY = +1 + sqr(e->y() - height()) / sqr(fm.lineSpacing());
+        if((deltaX != 0 && d->m_scrollDeltaX != deltaX) || (deltaY != 0 && d->m_scrollDeltaY != deltaY))
+        {
+            d->m_scrollDeltaX = deltaX;
+            d->m_scrollDeltaY = deltaY;
+            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() != -1)
+        {
+            int lastLine;
+            int firstLine;
+            if(d->m_selection.getOldFirstLine() != -1)
+            {
+                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);
+        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, int& line, int& pos)
+{
+    const QFontMetrics& fm = fontMetrics();
+    int fontHeight = fm.lineSpacing();
+
+    int yOffset = -d->m_firstLine * fontHeight;
+
+    line = (y - yOffset) / fontHeight;
+    if(line >= 0 && (!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;
+
+  public:
+    QVector<QTextLayout::FormatRange> 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<QTextLayout::FormatRange> 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 = m_pDiffTextWindow->fontMetrics().width('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(
+    MyPainter& p,
+    const LineData* pld,
+    const DiffList* pLineDiff1,
+    const DiffList* pLineDiff2,
+    int line,
+    int whatChanged,
+    int whatChanged2,
+    int 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 = fm.width('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;
+
+    int changed = whatChanged;
+    if(pLineDiff1 != nullptr) changed |= 1;
+    if(pLineDiff2 != nullptr) changed |= 2;
+
+    QColor c = m_pOptions->m_fgColor;
+    p.setPen(c);
+    if(changed == 2) {
+        c = m_cDiff2;
+    }
+    else if(changed == 1)
+    {
+        c = m_cDiff1;
+    }
+    else if(changed == 3)
+    {
+        c = m_cDiffBoth;
+    }
+
+    if(pld != nullptr)
+    {
+        // First calculate the "changed" information for each character.
+        int i = 0;
+        QString lineString(pld->pLine, pld->size);
+        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<quint8> 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;
+            int cchanged = charChanged[i] | whatChanged;
+
+            if(cchanged == 2) {
+                c = m_cDiff2;
+            }
+            else if(cchanged == 1)
+            {
+                c = m_cDiff1;
+            }
+            else if(cchanged == 3)
+            {
+                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.m_formatRanges /*, 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.sprintf("%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;
+        int rangeLine1 = -1;
+        int rangeLine2 = -1;
+        if(m_winIdx == 1) {
+            rangeLine1 = mdhe.lineA1;
+            rangeLine2 = mdhe.lineA2;
+        }
+        if(m_winIdx == 2) {
+            rangeLine1 = mdhe.lineB1;
+            rangeLine2 = mdhe.lineB2;
+        }
+        if(m_winIdx == 3) {
+            rangeLine1 = mdhe.lineC1;
+            rangeLine2 = mdhe.lineC2;
+        }
+        if(rangeLine1 >= 0 && rangeLine2 >= 0 && 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() || !d->m_bPaintingAllowed)
+        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());
+
+    MyPainter p(this, d->m_pOptions->m_bRightToLeftLanguage, width(), fontMetrics().width('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())
+        emit newSelection();
+}
+
+void DiffTextWindow::print(MyPainter& p, const QRect&, int firstLine, int nofLinesPerPage)
+{
+    if(d->m_pDiff3LineVector == nullptr || !d->m_bPaintingAllowed ||
+       (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(MyPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine)
+{
+    m_lineNumberWidth = m_pOptions->m_bShowLineNumbers ? (int)log10((double)qMax(m_size, 1)) + 1 : 0;
+
+    if(m_winIdx == 1)
+    {
+        m_cThis = m_pOptions->m_colorA;
+        m_cDiff1 = m_pOptions->m_colorB;
+        m_cDiff2 = m_pOptions->m_colorC;
+    }
+    if(m_winIdx == 2)
+    {
+        m_cThis = m_pOptions->m_colorB;
+        m_cDiff1 = m_pOptions->m_colorC;
+        m_cDiff2 = m_pOptions->m_colorA;
+    }
+    if(m_winIdx == 3)
+    {
+        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;
+        int changed = 0;
+        int changed2 = 0;
+
+        int srcLineIdx = -1;
+        getLineInfo(*d3l, srcLineIdx, pFineDiff1, pFineDiff2, changed, changed2);
+
+        writeLine(
+            p,                                               // QPainter
+            srcLineIdx == -1 ? 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 >= (int)m_pDiff3LineVector->size())
+        return QString();
+    const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx];
+    DiffList* pFineDiff1;
+    DiffList* pFineDiff2;
+    int changed = 0;
+    int changed2 = 0;
+    int lineIdx = -1;
+
+    getLineInfo(*d3l, lineIdx, pFineDiff1, pFineDiff2, changed, changed2);
+
+    if(lineIdx == -1)
+        return QString();
+    else
+    {
+        const LineData* ld = &m_pLineData[lineIdx];
+        return QString(ld->pLine, ld->size);
+    }
+    return QString();
+}
+
+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 DiffTextWindowData::getLineInfo(
+    const Diff3Line& d3l,
+    int& lineIdx,
+    DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values
+    int& changed, int& changed2)
+{
+    changed = 0;
+    changed2 = 0;
+    bool bAEqB = d3l.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB);
+    bool bAEqC = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC);
+    bool bBEqC = d3l.bBEqC || (d3l.bWhiteLineB && d3l.bWhiteLineC);
+
+    Q_ASSERT(m_winIdx >= 1 && m_winIdx <= 3);
+    if(m_winIdx == 1) {
+        lineIdx = d3l.lineA;
+        pFineDiff1 = d3l.pFineAB;
+        pFineDiff2 = d3l.pFineCA;
+        changed |= ((d3l.lineB == -1) != (lineIdx == -1) ? 1 : 0) +
+                   ((d3l.lineC == -1) != (lineIdx == -1) && m_bTriple ? 2 : 0);
+        changed2 |= (bAEqB ? 0 : 1) + (bAEqC || !m_bTriple ? 0 : 2);
+    }
+    else if(m_winIdx == 2)
+    {
+        lineIdx = d3l.lineB;
+        pFineDiff1 = d3l.pFineBC;
+        pFineDiff2 = d3l.pFineAB;
+        changed |= ((d3l.lineC == -1) != (lineIdx == -1) && m_bTriple ? 1 : 0) +
+                   ((d3l.lineA == -1) != (lineIdx == -1) ? 2 : 0);
+        changed2 |= (bBEqC || !m_bTriple ? 0 : 1) + (bAEqB ? 0 : 2);
+    }
+    else if(m_winIdx == 3)
+    {
+        lineIdx = d3l.lineC;
+        pFineDiff1 = d3l.pFineCA;
+        pFineDiff2 = d3l.pFineBC;
+        changed |= ((d3l.lineA == -1) != (lineIdx == -1) ? 1 : 0) +
+                   ((d3l.lineB == -1) != (lineIdx == -1) ? 2 : 0);
+        changed2 |= (bAEqC ? 0 : 1) + (bBEqC ? 0 : 2);
+    }
+
+}
+
+void DiffTextWindow::resizeEvent(QResizeEvent* e)
+{
+    QSize s = e->size();
+    QFontMetrics fm = fontMetrics();
+    int visibleLines = s.height() / fm.lineSpacing() - 2;
+    int visibleColumns = s.width() / fm.width('0') - d->leftInfoWidth();
+    if(e->size().height() != e->oldSize().height())
+        emit resizeHeightChangedSignal(visibleLines);
+    if(e->size().width() != e->oldSize().width())
+        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() * fm.width('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 == 1) {
+            lineIdx = d3l->lineA;
+        }
+        else if(d->m_winIdx == 2)
+        {
+            lineIdx = d3l->lineB;
+        }
+        else if(d->m_winIdx == 3)
+        {
+            lineIdx = d3l->lineC;
+        }
+
+        if(lineIdx != -1)
+        {
+            const QChar* pLine = d->m_pLineData[lineIdx].pLine;
+            int size = d->m_pLineData[lineIdx].size;
+            QString lineString = QString(pLine, size);
+
+            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, int& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive)
+{
+    int it = d3vLine;
+    int endIt = bDirDown ? (int)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(int firstLine, int startPos, int lastLine, int endPos, int& l, int& p)
+{
+    d->m_selection.reset();
+    if(lastLine >= getNofLines())
+    {
+        lastLine = getNofLines() - 1;
+
+        const Diff3Line* d3l = (*d->m_pDiff3LineVector)[convertLineToDiff3LineIdx(lastLine)];
+        int line = -1;
+        if(d->m_winIdx == 1) line = d3l->lineA;
+        if(d->m_winIdx == 2) line = d3l->lineB;
+        if(d->m_winIdx == 3) line = d3l->lineC;
+        if(line >= 0)
+            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, convertToPosOnScreen(s1, wrapStartPos, d->m_pOptions->m_tabSize));
+        d->m_selection.end(lastWrapLine, convertToPosOnScreen(s2, wrapEndPos, d->m_pOptions->m_tabSize));
+        l = firstWrapLine;
+        p = wrapStartPos;
+    }
+    else
+    {
+        if(d->m_pDiff3LineVector != nullptr){
+            d->m_selection.start(firstLine, convertToPosOnScreen(d->getString(firstLine), startPos, d->m_pOptions->m_tabSize));
+            d->m_selection.end(lastLine, convertToPosOnScreen(d->getString(lastLine), endPos, d->m_pOptions->m_tabSize));
+            l = firstLine;
+            p = startPos;
+        }
+    }
+    update();
+}
+
+int DiffTextWindowData::convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine)
+{
+    int line = -1;
+    if(lineOnScreen >= 0)
+    {
+        if(coordType == eWrapCoords) return lineOnScreen;
+        int d3lIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(lineOnScreen);
+        if(!bFirstLine && d3lIdx >= (int)m_pDiff3LineVector->size())
+            d3lIdx = m_pDiff3LineVector->size() - 1;
+        if(coordType == eD3LLineCoords) return d3lIdx;
+        while(line < 0 && d3lIdx < (int)m_pDiff3LineVector->size() && d3lIdx >= 0)
+        {
+            const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx];
+            if(m_winIdx == 1) line = d3l->lineA;
+            if(m_winIdx == 2) line = d3l->lineB;
+            if(m_winIdx == 3) line = d3l->lineC;
+            if(bFirstLine)
+                ++d3lIdx;
+            else
+                --d3lIdx;
+        }
+        if(coordType == eFileCoords) return line;
+    }
+    return line;
+}
+
+void DiffTextWindow::getSelectionRange(int* pFirstLine, int* 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 || !d->m_bPaintingAllowed || !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 = convertToPosInText(s, d->m_selection.beginPos(), d->m_pOptions->m_tabSize);
+    convertLineCoordsToD3LCoords(d->m_selection.beginLine(), firstPosInText, firstD3LIdx, firstD3LPos);
+
+    int lastD3LIdx, lastD3LPos;
+    s = d->getLineString(d->m_selection.endLine());
+    int lastPosInText = convertToPosInText(s, d->m_selection.endPos(), d->m_pOptions->m_tabSize);
+    convertLineCoordsToD3LCoords(d->m_selection.endLine(), lastPosInText, lastD3LIdx, lastD3LPos);
+
+    d->m_selection.start(firstD3LIdx, firstD3LPos);
+    d->m_selection.end(lastD3LIdx, lastD3LPos);
+}
+
+int s_maxNofRunnables = 0;
+
+class RecalcWordWrapRunnable : public QRunnable
+{
+    DiffTextWindow* m_pDTW;
+    // DiffTextWindowData* m_pDTWData; // TODO unused?
+    int m_visibleTextWidth;
+    int m_cacheIdx;
+
+  public:
+    RecalcWordWrapRunnable(DiffTextWindow* p, DiffTextWindowData* pData, int visibleTextWidth, int cacheIdx)
+        : m_pDTW(p), /* m_pDTWData(pData),*/ m_visibleTextWidth(visibleTextWidth), m_cacheIdx(cacheIdx)
+    {
+        Q_UNUSED(pData) // TODO really unused?
+        setAutoDelete(true);
+        //++s_runnableCount; // in Qt>=5.3 only
+        s_runnableCount.fetchAndAddOrdered(1);
+    }
+    void run() override
+    {
+        m_pDTW->recalcWordWrapHelper(0, m_visibleTextWidth, m_cacheIdx);
+        // int newValue = --s_runnableCount; // in Qt>=5.3 only
+        int newValue = s_runnableCount.fetchAndAddOrdered(-1) - 1;
+        g_pProgressDialog->setCurrent(s_maxNofRunnables - getAtomic(s_runnableCount));
+        if(newValue == 0)
+        {
+            QWidget* p = m_pDTW;
+            while(p)
+            {
+                p = p->parentWidget();
+                if(KDiff3App* pKDiff3App = dynamic_cast<KDiff3App*>(p))
+                {
+                    QMetaObject::invokeMethod(pKDiff3App, "slotFinishRecalcWordWrap", Qt::QueuedConnection);
+                    break;
+                }
+            }
+        }
+    }
+};
+
+QList<QRunnable*> s_runnables;
+
+bool startRunnables()
+{
+    if(s_runnables.count() == 0)
+    {
+        return false;
+    }
+    else
+    {
+        g_pProgressDialog->setStayHidden(true);
+        g_pProgressDialog->push();
+        g_pProgressDialog->setMaxNofSteps(s_runnables.count());
+        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;
+    }
+}
+// Use conexpr when supported. QT
+const int s_linesPerRunnable = 2000;
+
+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)qMax(d->m_size, 1)) + 1 : 0;
+
+        d->m_diff3WrapLineVector.resize(wrapLineVectorSize);
+
+        if(wrapLineVectorSize == 0)
+            d->m_wrapLineCacheList.clear();
+
+        if(wrapLineVectorSize == 0)
+        {
+            d->m_bPaintingAllowed = false;
+            for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j)
+            //int i=0;
+            {
+                d->m_wrapLineCacheList.append(QVector<DiffTextWindowData::WrapLineCacheData>());
+                s_runnables.push_back(new RecalcWordWrapRunnable(this, d, visibleTextWidth, j));
+            }
+            //recalcWordWrap( bWordWrap, wrapLineVectorSize, visibleTextWidth, 0 );
+        }
+        else
+        {
+            recalcWordWrapHelper(wrapLineVectorSize, visibleTextWidth, 0);
+            d->m_bPaintingAllowed = true;
+        }
+    }
+    else
+    {
+        if(wrapLineVectorSize == 0 && getAtomic(d->m_maxTextWidth) < 0)
+        {
+            d->m_diff3WrapLineVector.resize(0);
+            d->m_wrapLineCacheList.clear();
+            d->m_bPaintingAllowed = false;
+            for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j)
+            {
+                s_runnables.push_back(new RecalcWordWrapRunnable(this, d, visibleTextWidth, j));
+            }
+        }
+        else
+        {
+            d->m_bPaintingAllowed = 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() * fontMetrics().width('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 : qMin(firstD3LineIdx + s_linesPerRunnable, size);
+        QVector<DiffTextWindowData::WrapLineCacheData>& 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(DiffTextWindowData::WrapLineCacheData(i, line.textStart(), line.textLength()));
+                }
+            }
+            else if(wrapLineVectorSize > 0 && cacheListIdx2 < d->m_wrapLineCacheList.count())
+            {
+                DiffTextWindowData::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->m_d3LineIdx <= i)
+                {
+                    if(pWrapLineCache->m_d3LineIdx == i)
+                    {
+                        Diff3WrapLine* pDiff3WrapLine = &d->m_diff3WrapLineVector[wrapLineIdx + l];
+                        pDiff3WrapLine->wrapLineOffset = pWrapLineCache->m_textStart;
+                        pDiff3WrapLine->wrapLineLength = pWrapLineCache->m_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.linesNeededForDisplay = 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 = qMin(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, convertToPosOnScreen(d->getLineString(firstLine), firstPos, d->m_pOptions->m_tabSize));
+        d->m_selection.end(lastLine, convertToPosOnScreen(d->getLineString(lastLine), lastPos, d->m_pOptions->m_tabSize));
+    }
+}
+
+class DiffTextWindowFrameData
+{
+  public:
+    DiffTextWindow* m_pDiffTextWindow;
+    QLineEdit* m_pFileSelection;
+    QPushButton* m_pBrowseButton;
+    Options* m_pOptions;
+    QLabel* m_pLabel;
+    QLabel* m_pTopLine;
+    QLabel* m_pEncoding;
+    QLabel* m_pLineEndStyle;
+    QWidget* m_pTopLineWidget;
+    int m_winIdx;
+};
+
+DiffTextWindowFrame::DiffTextWindowFrame(QWidget* pParent, QStatusBar* pStatusBar, Options* pOptions, int winIdx, SourceData* psd)
+    : QWidget(pParent)
+{
+    d = new DiffTextWindowFrameData;
+    d->m_winIdx = winIdx;
+    setAutoFillBackground(true);
+    d->m_pOptions = pOptions;
+    d->m_pTopLineWidget = new QWidget(this);
+    d->m_pFileSelection = new QLineEdit(d->m_pTopLineWidget);
+    d->m_pBrowseButton = new QPushButton("...", d->m_pTopLineWidget);
+    d->m_pBrowseButton->setFixedWidth(30);
+    connect(d->m_pBrowseButton, &QPushButton::clicked, this, &DiffTextWindowFrame::slotBrowseButtonClicked);
+    connect(d->m_pFileSelection, &QLineEdit::returnPressed, this, &DiffTextWindowFrame::slotReturnPressed);
+
+    d->m_pLabel = new QLabel("A:", d->m_pTopLineWidget);
+    d->m_pTopLine = new QLabel(d->m_pTopLineWidget);
+    d->m_pDiffTextWindow = nullptr;
+    d->m_pDiffTextWindow = new DiffTextWindow(this, pStatusBar, pOptions, winIdx);
+
+    QVBoxLayout* pVTopLayout = new QVBoxLayout(d->m_pTopLineWidget);
+    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(d->m_pLabel, 0);
+    pHL->addWidget(d->m_pFileSelection, 1);
+    pHL->addWidget(d->m_pBrowseButton, 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:"), this, psd, pOptions);
+
+    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(d->m_pTopLineWidget, 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->d->m_filename);
+        d->m_pFileSelection->setText(s);
+        QString winId = pDTW->d->m_winIdx == 1 ? (pDTW->d->m_bTriple ? i18n("A (Base)") : i18n("A")) : (pDTW->d->m_winIdx == 2 ? i18n("B") : i18n("C"));
+        d->m_pLabel->setText(winId + ':');
+        d->m_pEncoding->setText(i18n("Encoding: %1", pDTW->d->m_pTextCodec != nullptr ? QLatin1String(pDTW->d->m_pTextCodec->name()) : QString()));
+        d->m_pLineEndStyle->setText(i18n("Line end style: %1", pDTW->d->m_eLineEndStyle == eLineEndStyleDos ? i18n("DOS") : i18n("Unix")));
+    }
+}
+
+// Search for the first visible line (search loop needed when no line exist for this file.)
+int DiffTextWindow::calcTopLineInFile(int firstLine)
+{
+    int l = -1;
+    for(int i = convertLineToDiff3LineIdx(firstLine); i < (int)d->m_pDiff3LineVector->size(); ++i)
+    {
+        const Diff3Line* d3l = (*d->m_pDiff3LineVector)[i];
+        l = d3l->getLineInFile(d->m_winIdx);
+        if(l != -1) break;
+    }
+    return l;
+}
+
+void DiffTextWindowFrame::setFirstLine(int firstLine)
+{
+    DiffTextWindow* pDTW = d->m_pDiffTextWindow;
+    if(pDTW && pDTW->d->m_pDiff3LineVector)
+    {
+        QString s = i18n("Top line");
+        int lineNumberWidth = (int)log10((double)qMax(pDTW->d->m_size, 1)) + 1;
+
+        int l = pDTW->calcTopLineInFile(firstLine);
+
+        int w = d->m_pTopLine->fontMetrics().width(
+            s + ' ' + QString().fill('0', lineNumberWidth));
+        d->m_pTopLine->setMinimumWidth(w);
+
+        if(l == -1)
+            s = i18n("End");
+        else
+            s += ' ' + QString::number(l + 1);
+
+        d->m_pTopLine->setText(s);
+        d->m_pTopLine->repaint();
+    }
+}
+
+DiffTextWindow* DiffTextWindowFrame::getDiffTextWindow()
+{
+    return d->m_pDiffTextWindow;
+}
+
+bool DiffTextWindowFrame::eventFilter(QObject* o, QEvent* e)
+{
+    DiffTextWindow* pDTW = d->m_pDiffTextWindow;
+    if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut)
+    {
+        QColor c1 = d->m_pOptions->m_bgColor;
+        QColor c2;
+        if(d->m_winIdx == 1)
+            c2 = d->m_pOptions->m_colorA;
+        else if(d->m_winIdx == 2)
+            c2 = d->m_pOptions->m_colorB;
+        else if(d->m_winIdx == 3)
+            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);
+    }
+    if(o == d->m_pFileSelection && e->type() == QEvent::Drop)
+    {
+        QDropEvent* dropEvent = static_cast<QDropEvent*>(e);
+
+        if(dropEvent->mimeData()->hasUrls())
+        {
+            QList<QUrl> lst = dropEvent->mimeData()->urls();
+
+            if(lst.count() > 0)
+            {
+                static_cast<QLineEdit*>(o)->setText(lst[0].toString());
+                static_cast<QLineEdit*>(o)->setFocus();
+                emit fileNameChanged(lst[0].toString(), pDTW->d->m_winIdx);
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+void DiffTextWindowFrame::slotReturnPressed()
+{
+    DiffTextWindow* pDTW = d->m_pDiffTextWindow;
+    if(pDTW->d->m_filename != d->m_pFileSelection->text())
+    {
+        emit fileNameChanged(d->m_pFileSelection->text(), pDTW->d->m_winIdx);
+    }
+}
+
+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;
+        emit fileNameChanged(newURL.url(), pDTW->d->m_winIdx);
+    }
+}
+
+void DiffTextWindowFrame::sendEncodingChangedSignal(QTextCodec* c)
+{
+    emit encodingChanged(c);
+}
+
+EncodingLabel::EncodingLabel(const QString& text, DiffTextWindowFrame* pDiffTextWindowFrame, SourceData* pSD, Options* pOptions)
+    : QLabel(text)
+{
+    m_pDiffTextWindowFrame = pDiffTextWindowFrame;
+    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<int> mibs = QTextCodec::availableMibs();
+        QList<int> 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;
+            foreach(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"));
+        foreach(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<int>& codecEnumList, QMenu* pMenu, int currentTextCodecEnum)
+{
+    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.
+        QLatin1String codecName(pCodec->name());
+        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::slotEncodingChanged);
+        codecEnumList.append(CodecMIBEnum);
+    }
+}
+
+void EncodingLabel::slotEncodingChanged()
+{
+    QAction* pAction = qobject_cast<QAction*>(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);
+            }
+        }
+        m_pDiffTextWindowFrame->sendEncodingChangedSignal(pCodec);
+    }
+}
diff --git a/src/difftextwindow.h b/src/difftextwindow.h
new file mode 100644 (file)
index 0000000..3264e2c
--- /dev/null
@@ -0,0 +1,155 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef DIFFTEXTWINDOW_H
+#define DIFFTEXTWINDOW_H
+
+#include "diff.h"
+
+#include <QLabel>
+
+class QMenu;
+class QStatusBar;
+class Options;
+class DiffTextWindowData;
+class DiffTextWindowFrame;
+class EncodingLabel;
+
+class DiffTextWindow : public QWidget
+{
+   Q_OBJECT
+public:
+   DiffTextWindow(DiffTextWindowFrame* pParent, QStatusBar* pStatusBar, Options* pOptions, int winIdx);
+   ~DiffTextWindow() override;
+   void init(
+      const QString& fileName,
+      QTextCodec* pTextCodec,
+      e_LineEndStyle eLineEndStyle,
+      const LineData* pLineData,
+      int size,
+      const Diff3LineVector* pDiff3LineVector,
+      const ManualDiffHelpList* pManualDiffHelpList,
+      bool bTriple
+      );
+   void reset();
+   void convertToLinePos( int x, int y, int& line, int& pos );
+
+   QString getSelection();
+   int getFirstLine();
+   int calcTopLineInFile( int firstLine );
+
+   int getMaxTextWidth();
+   int getNofLines();
+   int getNofVisibleLines();
+   int getVisibleTextAreaWidth();
+
+   int convertLineToDiff3LineIdx( int line );
+   int 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, int& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive );
+   void setSelection( int firstLine, int startPos, int lastLine, int endPos, int& l, int& p );
+   void getSelectionRange( int* firstLine, int* 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 print( MyPainter& painter, const QRect& r, int firstLine, int nofLinesPerPage );
+Q_SIGNALS:
+   void resizeHeightChangedSignal(int nofVisibleLines);
+   void resizeWidthChangedSignal(int nofVisibleColumns);
+   void scrollDiffTextWindow( int deltaX, int deltaY );
+   void newSelection();
+   void selectionEnd();
+   void setFastSelectorLine( int line );
+   void gotFocus();
+   void lineClicked( int winIdx, int line );
+
+public Q_SLOTS:
+   void setFirstLine( int line );
+   void setHorizScrollOffset( int horizScrollOffset );
+   void resetSelection();
+   void setFastSelectorRange( int line1, int nofLines );
+
+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 focusInEvent( QFocusEvent* e ) override;
+
+   void resizeEvent( QResizeEvent* ) override;
+   void timerEvent(QTimerEvent*) override;
+
+private:
+   DiffTextWindowData* d;
+   void showStatusLine( int line );
+   friend class DiffTextWindowFrame;
+};
+
+
+class DiffTextWindowFrameData;
+
+class DiffTextWindowFrame : public QWidget
+{
+   Q_OBJECT
+public:
+   DiffTextWindowFrame( QWidget* pParent, QStatusBar* pStatusBar, Options* pOptions, int winIdx, SourceData* psd);
+   ~DiffTextWindowFrame() override;
+   DiffTextWindow* getDiffTextWindow();
+   void init();
+   void setFirstLine(int firstLine);
+   void sendEncodingChangedSignal(QTextCodec* c);
+Q_SIGNALS:
+   void fileNameChanged(const QString&, int);
+   void encodingChanged(QTextCodec*);
+protected:
+   bool eventFilter( QObject*, QEvent* ) override;
+   //void paintEvent(QPaintEvent*);
+private Q_SLOTS:
+   void slotReturnPressed();
+   void slotBrowseButtonClicked();
+private:
+   DiffTextWindowFrameData* d;
+};
+
+class EncodingLabel : public QLabel
+{
+   Q_OBJECT
+public:
+   EncodingLabel( const QString & text, DiffTextWindowFrame* pDiffTextWindowFrame, SourceData* psd, Options* pOptions);
+protected:
+   void mouseMoveEvent(QMouseEvent *ev) override;
+   void mousePressEvent(QMouseEvent *ev) override;
+private Q_SLOTS:
+   void slotEncodingChanged();
+private:
+   DiffTextWindowFrame* m_pDiffTextWindowFrame; //To send "EncodingChanged" signal
+   QMenu* m_pContextEncodingMenu;
+   SourceData* m_pSourceData; //SourceData to get access to "isEmpty()" and "isFromBuffer()" functions
+   static const int m_maxRecentEncodings  = 5;
+   Options* m_pOptions;
+
+   void insertCodec( const QString& visibleCodecName, QTextCodec* pCodec, QList<int> &CodecEnumList, QMenu* pMenu, int currentTextCodecEnum);
+};
+
+bool startRunnables();
+
+#endif
+
diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp
new file mode 100644 (file)
index 0000000..54e4853
--- /dev/null
@@ -0,0 +1,3520 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+#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 <algorithm>
+#include <map>
+#include <vector>
+
+#include <QAction>
+#include <QApplication>
+#include <QDialogButtonBox>
+#include <QDir>
+#include <QFileDialog>
+#include <QImage>
+#include <QKeyEvent>
+#include <QLabel>
+#include <QLayout>
+#include <QMenu>
+#include <QPushButton>
+#include <QRegExp>
+#include <QSplitter>
+#include <QStyledItemDelegate>
+#include <QTextStream>
+
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <KTextEdit>
+#include <KToggleAction>
+
+class StatusInfo : public QDialog
+{
+    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* q;
+    explicit DirectoryMergeWindowPrivate(DirectoryMergeWindow* pDMW)
+    {
+        q = pDMW;
+        m_pOptions = nullptr;
+        m_pDirectoryMergeInfo = nullptr;
+        m_bSimulatedMergeStarted = false;
+        m_bRealMergeStarted = false;
+        m_bError = false;
+        m_bSyncMode = false;
+        m_pStatusInfo = new StatusInfo(q);
+        m_pStatusInfo->hide();
+        m_bScanning = false;
+        m_bCaseSensitive = true;
+        m_bUnfoldSubdirs = false;
+        m_bSkipDirStatus = false;
+        m_pRoot = new MergeFileInfos;
+    }
+    ~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();
+        else
+        {
+            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 || rootMFI()->getDirectoryInfo() == nullptr) return false;
+        return rootMFI()->getDirectoryInfo()->dirC().isValid();
+    }
+    MergeFileInfos* rootMFI() const { return m_pRoot; }
+
+    static void setPixmaps(MergeFileInfos& mfi, bool);
+
+    Options* m_pOptions;
+
+    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);
+    bool fastFileComparison(FileAccess& fi1, FileAccess& fi2,
+                            bool& bError, QString& status);
+    bool compareFilesAndCalcAges(MergeFileInfos& mfi, QStringList& errors);
+
+    void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true);
+    bool isDir(const QModelIndex& mi);
+    QString getFileName(const QModelIndex& mi);
+
+    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);
+
+    t_DirectoryList m_dirListA;
+    t_DirectoryList m_dirListB;
+    t_DirectoryList m_dirListC;
+
+    QString m_dirMergeStateFilename;
+
+    void buildMergeMap(const QSharedPointer<DirectoryInfo>& dirInfo);
+
+  private:
+    class FileKey
+    {
+      public:
+        const FileAccess* m_pFA;
+        explicit FileKey(const FileAccess& fa)
+            : m_pFA(&fa) {}
+
+        int getParents(const FileAccess* pFA, const FileAccess* v[]) const
+        {
+            int s = 0;
+            for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s)
+                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];
+            int v1Size = getParents(m_pFA, v1);
+            int v2Size = getParents(fk.m_pFA, v2);
+
+            for(int 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<FileKey, MergeFileInfos> t_fileMergeMap;
+
+    MergeFileInfos* m_pRoot;
+
+  public:
+    t_fileMergeMap m_fileMergeMap;
+
+    bool m_bFollowDirLinks;
+    bool m_bFollowFileLinks;
+    bool m_bSimulatedMergeStarted;
+    bool m_bRealMergeStarted;
+    bool m_bError;
+    bool m_bSyncMode;
+    bool m_bDirectoryMerge; // if true, then merge is the default operation, otherwise it's diff.
+    bool m_bCaseSensitive;
+    bool m_bUnfoldSubdirs;
+    bool m_bSkipDirStatus;
+    bool m_bScanning; // true while in init()
+
+    DirectoryMergeInfo* m_pDirectoryMergeInfo;
+    StatusInfo* m_pStatusInfo;
+
+    typedef std::list<QModelIndex> 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<DirectoryInfo> &dirInfo, bool bDirectoryMerge, bool bReload);
+    void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus)
+    {
+        if(MergeFileInfos* pMFI = getMFI(mi))
+        {
+            pMFI->m_eOpStatus = eOpStatus;
+            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 i18n("Unsolved");
+                case s_SolvedCol:
+                    return i18n("Solved");
+                case s_NonWhiteCol:
+                    return i18n("Nonwhite");
+                case s_WhiteCol:
+                    return i18n("White");
+                    //default :           return QVariant();
+            }
+
+            if(s_OpCol == index.column())
+            {
+                bool bDir = pMFI->dirA() || pMFI->dirB() || pMFI->dirC();
+                switch(pMFI->m_eMergeOperation)
+                {
+                    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:
+                        return bDir ? i18n("Merge") : i18n("Merge (manual)");
+                        break;
+                    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->m_eOpStatus)
+                {
+                    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->isLinkA() || pMFI->isLinkB() || pMFI->isLinkC(),
+                                                 pMFI->dirA() || pMFI->dirB() || pMFI->dirC());
+            }
+
+            if(s_ACol == index.column())
+            {
+                return PixMapUtils::getOnePixmap(pMFI->m_ageA, pMFI->isLinkA(), pMFI->dirA());
+            }
+            if(s_BCol == index.column())
+            {
+                return PixMapUtils::getOnePixmap(pMFI->m_ageB, pMFI->isLinkB(), pMFI->dirB());
+            }
+            if(s_CCol == index.column())
+            {
+                return PixMapUtils::getOnePixmap(pMFI->m_ageC, pMFI->isLinkC(), pMFI->dirC());
+            }
+        }
+        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();
+}
+
+// Previously  Q3ListViewItem::paintCell(p,cg,column,width,align);
+class DirectoryMergeWindow::DirMergeItemDelegate : public QStyledItemDelegate
+{
+    DirectoryMergeWindow* m_pDMW;
+    DirectoryMergeWindow::DirectoryMergeWindowPrivate* d;
+
+  public:
+    explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent)
+        : QStyledItemDelegate(pParent), m_pDMW(pParent), d(pParent->d)
+    {
+    }
+    void paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const override
+    {
+        int 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<QIcon>(value).pixmap(16, 16);
+                    //icon = qvariant_cast<QIcon>(value);
+                    //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState));
+                }
+                else
+                {
+                    icon = qvariant_cast<QPixmap>(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<QPixmap>(); //pixmap(column);
+            if(!icon.isNull())
+            {
+                int yOffset = (sizeHint(option, index).height() - icon.height()) / 2;
+                p->drawPixmap(x + 2, y + yOffset, icon);
+
+                int i = index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0;
+                if(i != 0)
+                {
+                    Options* pOpts = d->m_pOptions;
+                    QColor c(i == 1 ? pOpts->m_colorA : i == 2 ? pOpts->m_colorB : pOpts->m_colorC);
+                    p->setPen(c); // highlight() );
+                    p->drawRect(x + 2, y + yOffset, icon.width(), icon.height());
+                    p->setPen(QPen(c, 0, Qt::DotLine));
+                    p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2);
+                    p->setPen(Qt::white);
+                    QString s(QChar('A' + i - 1));
+                    p->drawText(x + 2 + (icon.width() - p->fontMetrics().width(s)) / 2,
+                                y + yOffset + (icon.height() + p->fontMetrics().ascent()) / 2 - 1,
+                                s);
+                }
+                else
+                {
+                    p->setPen(m_pDMW->palette().background().color());
+                    p->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(p, 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, Options* 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;
+}
+
+bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::fastFileComparison(
+    FileAccess& fi1, FileAccess& fi2,
+    bool& bError, QString& status)
+{
+    ProgressProxy pp;
+    bool bEqual = false;
+
+    status = "";
+    bError = true;
+
+    if(!m_bFollowFileLinks)
+    {
+        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(m_pOptions->m_bDmTrustSize)
+    {
+        bEqual = true;
+        bError = false;
+        return bEqual;
+    }
+
+    if(m_pOptions->m_bDmTrustDate)
+    {
+        bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size());
+        bError = false;
+        status = i18n("Date & Size: ");
+        return bEqual;
+    }
+
+    if(m_pOptions->m_bDmTrustDateFallbackToBinary)
+    {
+        bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size());
+        if(bEqual)
+        {
+            bError = false;
+            status = i18n("Date & Size: ");
+            return bEqual;
+        }
+    }
+
+    QString fileName1 = fi1.absoluteFilePath();
+    QString fileName2 = fi2.absoluteFilePath();
+
+    if(!fi1.createLocalCopy())
+    {
+        status = i18n("Creating temp copy of %1 failed.", fileName1);
+        return bEqual;
+    }
+
+    if(!fi2.createLocalCopy())
+    {
+        status = i18n("Creating temp copy of %1 failed.", fileName2);
+        return bEqual;
+    }
+
+    std::vector<char> buf1(100000);
+    std::vector<char> buf2(buf1.size());
+
+    QFile file1(fi1.fileName());
+
+    if(!file1.open(QIODevice::ReadOnly))
+    {
+        status = i18n("Opening %1 failed. %2", fileName1, file1.errorString());
+        return bEqual;
+    }
+
+    QFile file2(fi2.fileName());
+
+    if(!file2.open(QIODevice::ReadOnly))
+    {
+        status = i18n("Opening %1 failed. %2", fileName2, file2.errorString());
+        return bEqual;
+    }
+
+    pp.setInformation(i18n("Comparing file..."), 0, false);
+    typedef qint64 t_FileSize;
+    t_FileSize fullSize = file1.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 != file1.read(&buf1[0], len))
+        {
+            status = i18n("Error reading from %1", fileName1);
+            return bEqual;
+        }
+
+        if(len != file2.read(&buf2[0], len))
+        {
+            status = i18n("Error reading from %1", fileName2);
+            return bEqual;
+        }
+
+        if(memcmp(&buf1[0], &buf2[0], len) != 0)
+        {
+            bError = false;
+            return bEqual;
+        }
+        sizeLeft -= len;
+        //pp.setCurrent(double(fullSize-sizeLeft)/fullSize, false );
+        pp.step();
+    }
+
+    // If the program really arrives here, then the files are really equal.
+    bError = false;
+    bEqual = true;
+    return bEqual;
+}
+
+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 directory merge. Are you sure, you want to abort the merge and rescan the directory?"),
+                                               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->dirA() || pMFI->dirB() || pMFI->dirC())
+    {
+        ++nofDirs;
+    }
+    else
+    {
+        ++nofFiles;
+        if(pMFI->m_bEqualAB && (!bThreeDirs || pMFI->m_bEqualAC))
+        {
+            ++nofEqualFiles;
+        }
+        else
+        {
+            if(pMFI->m_eMergeOperation == eMergeABCToDest || pMFI->m_eMergeOperation == eMergeABToDest)
+                ++nofManualMerges;
+        }
+    }
+    for(int childIdx = 0; childIdx < rowCount(mi); ++childIdx)
+        calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges);
+}
+
+struct t_ItemInfo {
+    bool bExpanded;
+    bool bOperationComplete;
+    QString status;
+    e_MergeOperation eMergeOperation;
+};
+
+bool DirectoryMergeWindow::init(
+    const QSharedPointer<DirectoryInfo> &dirInfo,
+    bool bDirectoryMerge,
+    bool bReload)
+{
+    return d->init(dirInfo, bDirectoryMerge, bReload);
+}
+
+void DirectoryMergeWindow::DirectoryMergeWindowPrivate::buildMergeMap(const QSharedPointer<DirectoryInfo>& dirInfo)
+{
+    t_DirectoryList::iterator dirIterator;
+
+    if(dirInfo->dirA().isValid())
+    {
+        for(dirIterator = m_dirListA.begin(); dirIterator != m_dirListA.end(); ++dirIterator)
+        {
+            MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
+
+            mfi.setFileInfoA(&(*dirIterator));
+            mfi.setDirectoryInfo(dirInfo);
+        }
+    }
+
+    if(dirInfo->dirB().isValid())
+    {
+        for(dirIterator = m_dirListB.begin(); dirIterator != m_dirListB.end(); ++dirIterator)
+        {
+            MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
+
+            mfi.setFileInfoB(&(*dirIterator));
+            mfi.setDirectoryInfo(dirInfo);
+        }
+    }
+
+    if(dirInfo->dirC().isValid())
+    {
+        for(dirIterator = m_dirListC.begin(); dirIterator != m_dirListC.end(); ++dirIterator)
+        {
+            MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
+
+            mfi.setFileInfoC(&(*dirIterator));
+            mfi.setDirectoryInfo(dirInfo);
+        }
+    }
+}
+
+bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::init(
+    const QSharedPointer<DirectoryInfo> &dirInfo,
+    bool bDirectoryMerge,
+    bool bReload)
+{
+    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;
+        emit q->checkIfCanContinue(&bCanContinue);
+        if(!bCanContinue)
+            return false;
+        emit q->startDiffMerge("", "", "", "", "", "", "", nullptr); // hide main window
+    }
+
+    q->show();
+    q->setUpdatesEnabled(true);
+
+    std::map<QString, t_ItemInfo> expandedDirsMap;
+
+    if(bReload)
+    {
+        // Remember expanded items TODO
+        //QTreeWidgetItemIterator it( this );
+        //while ( *it )
+        //{
+        //   DirMergeItem* pDMI = static_cast<DirMergeItem*>( *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->m_eMergeOperation;
+        //   ++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);
+    }
+
+    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 directories failed:"));
+        text += "\n\n";
+        if(!dirA.isDir())
+        {
+            text += i18n("Dir A \"%1\" does not exist or is not a directory.\n", dirA.prettyAbsPath());
+        }
+
+        if(!dirB.isDir())
+        {
+            text += i18n("Dir B \"%1\" does not exist or is not a directory.\n", dirB.prettyAbsPath());
+        }
+
+        if(dirC.isValid() && !dirC.isDir())
+        {
+            text += i18n("Dir C \"%1\" does not exist or is not a directory.\n", dirC.prettyAbsPath());
+        }
+
+        KMessageBox::sorry(q, text, i18n("Directory Open Error"));
+        return false;
+    }
+
+    if(dirC.isValid() &&
+       (dirDest.prettyAbsPath() == dirA.prettyAbsPath() || dirDest.prettyAbsPath() == dirB.prettyAbsPath()))
+    {
+        KMessageBox::error(q,
+                           i18n("The destination directory must not be the same as A or B when "
+                                "three directories are merged.\nCheck again before continuing."),
+                           i18n("Parameter Warning"));
+        return false;
+    }
+
+    m_bScanning = true;
+    emit q->statusBarMessage(i18n("Scanning directories..."));
+
+    m_bSyncMode = m_pOptions->m_bDmSyncMode && !dirC.isValid() && !dirDest.isValid();
+
+    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);
+    q->setColumnHidden(s_CCol, !dirC.isValid());
+    q->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis);
+    q->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis);
+    q->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis);
+    q->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && dirC.isValid()));
+
+    bool bListDirSuccessA = true;
+    bool bListDirSuccessB = true;
+    bool bListDirSuccessC = true;
+    m_dirListA.clear();
+    m_dirListB.clear();
+    m_dirListC.clear();
+    if(dirA.isValid())
+    {
+        pp.setInformation(i18n("Reading Directory A"));
+        pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
+        ++currentScan;
+
+        bListDirSuccessA = dirA.listDir(&m_dirListA,
+                                        m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden,
+                                        m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern,
+                                        m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks,
+                                        m_pOptions->m_bDmUseCvsIgnore);
+    }
+
+    if(dirB.isValid())
+    {
+        pp.setInformation(i18n("Reading Directory B"));
+        pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
+        ++currentScan;
+
+        bListDirSuccessB = dirB.listDir(&m_dirListB,
+                                        m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden,
+                                        m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern,
+                                        m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks,
+                                        m_pOptions->m_bDmUseCvsIgnore);
+    }
+
+    e_MergeOperation eDefaultMergeOp;
+    if(dirC.isValid())
+    {
+        pp.setInformation(i18n("Reading Directory C"));
+        pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
+        ++currentScan;
+
+        bListDirSuccessC = dirC.listDir(&m_dirListC,
+                                        m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden,
+                                        m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern,
+                                        m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks,
+                                        m_pOptions->m_bDmUseCvsIgnore);
+
+        eDefaultMergeOp = eMergeABCToDest;
+    }
+    else
+        eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest;
+
+    buildMergeMap(dirInfo);
+
+    bool bContinue = true;
+    if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC)
+    {
+        QString s = i18n("Some subdirectories 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 subdirectories.");
+        bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(q, s);
+    }
+
+    if(bContinue)
+    {
+        prepareListView(pp);
+
+        q->updateFileVisibilities();
+
+        for(int childIdx = 0; childIdx < rowCount(); ++childIdx)
+        {
+            QModelIndex mi = index(childIdx, 0, QModelIndex());
+            calcSuggestedOperation(mi, eDefaultMergeOp);
+        }
+    }
+
+    q->sortByColumn(0, Qt::AscendingOrder);
+
+    for(int column = 0; column < columnCount(QModelIndex()); ++column)
+        q->resizeColumnToContents(column);
+
+    // Try to improve the view a little bit.
+    QWidget* pParent = q->parentWidget();
+    QSplitter* pSplitter = static_cast<QSplitter*>(pParent);
+    if(pSplitter != nullptr)
+    {
+        QList<int> 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;
+    emit q->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("Directory Comparison Status\n\n"
+                 "Number of subdirectories: %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(q, 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<DirMergeItem*>( *it );
+        //   std::map<QString,t_ItemInfo>::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->getDirectoryInfo()->dirC().isValid();
+        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*)
+{
+    emit updateAvailabilities();
+}
+void DirectoryMergeWindow::focusOutEvent(QFocusEvent*)
+{
+    emit updateAvailabilities();
+}
+
+void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setAllMergeOperations(e_MergeOperation eDefaultOperation)
+{
+    if(KMessageBox::Yes == KMessageBox::warningYesNo(q,
+                                                     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);
+        }
+    }
+}
+
+bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::compareFilesAndCalcAges(MergeFileInfos& mfi, QStringList& errors)
+{
+    std::map<QDateTime, int> dateMap;
+
+    if(mfi.existsInA())
+    {
+        dateMap[mfi.getFileInfoA()->lastModified()] = 0;
+    }
+    if(mfi.existsInB())
+    {
+        dateMap[mfi.getFileInfoB()->lastModified()] = 1;
+    }
+    if(mfi.existsInC())
+    {
+        dateMap[mfi.getFileInfoC()->lastModified()] = 2;
+    }
+
+    if(m_pOptions->m_bDmFullAnalysis)
+    {
+        if((mfi.existsInA() && mfi.dirA()) || (mfi.existsInB() && mfi.dirB()) || (mfi.existsInC() && mfi.dirC()))
+        {
+            // If any input is a directory, don't start any comparison.
+            mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB();
+            mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC();
+            mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC();
+        }
+        else
+        {
+            emit q->startDiffMerge(
+                mfi.existsInA() ? mfi.getFileInfoA()->absoluteFilePath() : QString(""),
+                mfi.existsInB() ? mfi.getFileInfoB()->absoluteFilePath() : QString(""),
+                mfi.existsInC() ? mfi.getFileInfoC()->absoluteFilePath() : QString(""),
+                "",
+                "", "", "", mfi.diffStatus());
+            int nofNonwhiteConflicts = mfi.diffStatus()->getUnsolvedConflicts() +
+                                       mfi.diffStatus()->getSolvedConflicts() - mfi.diffStatus()->getWhitespaceConflicts();
+
+            if(m_pOptions->m_bDmWhiteSpaceEqual && nofNonwhiteConflicts == 0)
+            {
+                mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB();
+                mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC();
+                mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC();
+            }
+            else
+            {
+                mfi.m_bEqualAB = mfi.diffStatus()->isBinaryEqualAB();
+                mfi.m_bEqualBC = mfi.diffStatus()->isBinaryEqualBC();
+                mfi.m_bEqualAC = mfi.diffStatus()->isBinaryEqualAC();
+            }
+        }
+    }
+    else
+    {
+        bool bError = false;
+        QString eqStatus;
+        if(mfi.existsInA() && mfi.existsInB())
+        {
+            if(mfi.dirA())
+                mfi.m_bEqualAB = true;
+            else
+                mfi.m_bEqualAB = fastFileComparison(*mfi.getFileInfoA(), *mfi.getFileInfoB(), bError, eqStatus);
+        }
+        if(mfi.existsInA() && mfi.existsInC())
+        {
+            if(mfi.dirA())
+                mfi.m_bEqualAC = true;
+            else
+                mfi.m_bEqualAC = fastFileComparison(*mfi.getFileInfoA(), *mfi.getFileInfoC(), bError, eqStatus);
+        }
+        if(mfi.existsInB() && mfi.existsInC())
+        {
+            if(mfi.m_bEqualAB && mfi.m_bEqualAC)
+                mfi.m_bEqualBC = true;
+            else
+            {
+                if(mfi.dirB())
+                    mfi.m_bEqualBC = true;
+                else
+                    mfi.m_bEqualBC = fastFileComparison(*mfi.getFileInfoB(), *mfi.getFileInfoC(), bError, eqStatus);
+            }
+        }
+        if(bError)
+        {
+            //Limit size of error list in memmory.
+            if(errors.size() < 30)
+                errors.append(eqStatus);
+            return false;
+        }
+    }
+
+    if(mfi.isLinkA() != mfi.isLinkB()) mfi.m_bEqualAB = false;
+    if(mfi.isLinkA() != mfi.isLinkC()) mfi.m_bEqualAC = false;
+    if(mfi.isLinkB() != mfi.isLinkC()) mfi.m_bEqualBC = false;
+
+    if(mfi.dirA() != mfi.dirB()) mfi.m_bEqualAB = false;
+    if(mfi.dirA() != mfi.dirC()) mfi.m_bEqualAC = false;
+    if(mfi.dirB() != mfi.dirC()) mfi.m_bEqualBC = false;
+
+    Q_ASSERT(eNew == 0 && eMiddle == 1 && eOld == 2);
+
+    // The map automatically sorts the keys.
+    int age = eNew;
+    std::map<QDateTime, int>::reverse_iterator i;
+    for(i = dateMap.rbegin(); i != dateMap.rend(); ++i)
+    {
+        int n = i->second;
+        if(n == 0 && mfi.m_ageA == eNotThere)
+        {
+            mfi.m_ageA = (e_Age)age;
+            ++age;
+            if(mfi.m_bEqualAB)
+            {
+                mfi.m_ageB = mfi.m_ageA;
+                ++age;
+            }
+            if(mfi.m_bEqualAC)
+            {
+                mfi.m_ageC = mfi.m_ageA;
+                ++age;
+            }
+        }
+        else if(n == 1 && mfi.m_ageB == eNotThere)
+        {
+            mfi.m_ageB = (e_Age)age;
+            ++age;
+            if(mfi.m_bEqualAB)
+            {
+                mfi.m_ageA = mfi.m_ageB;
+                ++age;
+            }
+            if(mfi.m_bEqualBC)
+            {
+                mfi.m_ageC = mfi.m_ageB;
+                ++age;
+            }
+        }
+        else if(n == 2 && mfi.m_ageC == eNotThere)
+        {
+            mfi.m_ageC = (e_Age)age;
+            ++age;
+            if(mfi.m_bEqualAC)
+            {
+                mfi.m_ageA = mfi.m_ageC;
+                ++age;
+            }
+            if(mfi.m_bEqualBC)
+            {
+                mfi.m_ageB = mfi.m_ageC;
+                ++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(mfi.existsInC() && mfi.m_ageC == eNotThere)
+    {
+        mfi.m_ageC = (e_Age)age;
+        ++age;
+        mfi.m_bConflictingAges = true;
+    }
+    if(mfi.existsInB() && mfi.m_ageB == eNotThere)
+    {
+        mfi.m_ageB = (e_Age)age;
+        ++age;
+        mfi.m_bConflictingAges = true;
+    }
+    if(mfi.existsInA() && mfi.m_ageA == eNotThere)
+    {
+        mfi.m_ageA = (e_Age)age;
+        ++age;
+        mfi.m_bConflictingAges = true;
+    }
+
+    if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld)
+    {
+        if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld;
+        if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld;
+        if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld;
+    }
+
+    return true;
+}
+
+void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setPixmaps(MergeFileInfos& mfi, bool)
+{
+    if(mfi.dirA() || mfi.dirB() || mfi.dirC())
+    {
+        mfi.m_ageA = eNotThere;
+        mfi.m_ageB = eNotThere;
+        mfi.m_ageC = eNotThere;
+        int age = eNew;
+        if(mfi.existsInC())
+        {
+            mfi.m_ageC = (e_Age)age;
+            if(mfi.m_bEqualAC) mfi.m_ageA = (e_Age)age;
+            if(mfi.m_bEqualBC) mfi.m_ageB = (e_Age)age;
+            ++age;
+        }
+        if(mfi.existsInB() && mfi.m_ageB == eNotThere)
+        {
+            mfi.m_ageB = (e_Age)age;
+            if(mfi.m_bEqualAB) mfi.m_ageA = (e_Age)age;
+            ++age;
+        }
+        if(mfi.existsInA() && mfi.m_ageA == eNotThere)
+        {
+            mfi.m_ageA = (e_Age)age;
+        }
+        if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld)
+        {
+            if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld;
+            if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld;
+            if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld;
+        }
+    }
+}
+
+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() && q->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);
+
+    q->setRootIsDecorated(true);
+
+    bool bCheckC = isThreeWay();
+
+    t_fileMergeMap::iterator j;
+    int nrOfFiles = m_fileMergeMap.size();
+    int currentIdx = 1;
+    QTime 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.
+        compareFilesAndCalcAges(mfi, errors);
+        // 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()
+        }
+
+        setPixmaps(mfi, bCheckC);
+    }
+
+    if(errors.size() > 0)
+    {
+        if(errors.size() < 15)
+        {
+            KMessageBox::errorList(q, i18n("Some files could not be processed."), errors);
+        }
+        else
+        {
+            KMessageBox::error(q, 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->getDirectoryInfo()->dirC().isValid();
+    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->m_bEqualAB)
+            {
+                setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation);
+            }
+            else if(pMFI->existsInA() && pMFI->existsInB())
+            {
+                if(!bCopyNewer || pMFI->dirA())
+                    setMergeOperation(mi, eDefaultMergeOp);
+                else if(bCopyNewer && pMFI->m_bConflictingAges)
+                {
+                    setMergeOperation(mi, eConflictingAges);
+                }
+                else
+                {
+                    if(pMFI->m_ageA == 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->m_bEqualAB && pMFI->m_bEqualAC)
+            {
+                setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation);
+            }
+            else if(pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC())
+            {
+                if(pMFI->m_bEqualAB)
+                    setMergeOperation(mi, eCopyCToDest);
+                else if(pMFI->m_bEqualAC)
+                    setMergeOperation(mi, eCopyBToDest);
+                else if(pMFI->m_bEqualBC)
+                    setMergeOperation(mi, eCopyCToDest);
+                else
+                    setMergeOperation(mi, eMergeABCToDest);
+            }
+            else if(pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC())
+            {
+                if(pMFI->m_bEqualAB)
+                    setMergeOperation(mi, eDeleteFromDest);
+                else
+                    setMergeOperation(mi, eChangedAndDeleted);
+            }
+            else if(pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC())
+            {
+                if(pMFI->m_bEqualAC)
+                    setMergeOperation(mi, eDeleteFromDest);
+                else
+                    setMergeOperation(mi, eChangedAndDeleted);
+            }
+            else if(!pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC())
+            {
+                if(pMFI->m_bEqualBC)
+                    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)
+{
+    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)
+{
+    MergeFileInfos* pMFI = getMFI(mi);
+    if(pMFI != nullptr)
+    {
+        return mi.column() == s_ACol ? pMFI->dirA() : mi.column() == s_BCol ? pMFI->dirB() : pMFI->dirC();
+    }
+    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()) emit dataChanged(old1, old1);
+    if(old2.isValid()) emit dataChanged(old2, old2);
+    if(old3.isValid()) emit dataChanged(old3, old3);
+    if(m_selection1Index.isValid()) emit dataChanged(m_selection1Index, m_selection1Index);
+    if(m_selection2Index.isValid()) emit dataChanged(m_selection2Index, m_selection2Index);
+    if(m_selection3Index.isValid()) emit dataChanged(m_selection3Index, m_selection3Index);
+    emit q->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->dirC() )
+//   {
+//   }
+//   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->m_eMergeOperation)
+    {
+        pMFI->m_bOperationComplete = false;
+        setOpStatus(mi, eOpStatusNone);
+    }
+
+    pMFI->m_eMergeOperation = eMergeOp;
+    if(bRecursive)
+    {
+        e_MergeOperation eChildrenMergeOp = pMFI->m_eMergeOperation;
+        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->dirA() || pMFI->dirB() || pMFI->dirC()))
+        {
+            emit startDiffMerge(
+                pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""),
+                pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""),
+                pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""),
+                "",
+                "", "", "", nullptr);
+        }
+    }
+    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;
+    }
+
+    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();
+
+    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);
+
+    emit startDiffMerge(fn1, fn2, fn3,
+                        fn3.isEmpty() ? fn2 : fn3,
+                        "", "", "", nullptr);
+    d->m_selection1Index = QModelIndex();
+    d->m_selection2Index = QModelIndex();
+    d->m_selection3Index = QModelIndex();
+
+    emit updateAvailabilities();
+    update();
+}
+
+bool DirectoryMergeWindow::isFileSelected()
+{
+    if(MergeFileInfos* pMFI = d->getMFI(currentIndex()))
+    {
+        return !(pMFI->dirA() || pMFI->dirB() || pMFI->dirC() || 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->m_eMergeOperation == 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->m_eMergeOperation = eCopyBToA;
+                return;
+            }
+        }
+        d->setOpStatus(mi, eOpStatusDone);
+        pMFI->m_bOperationComplete = true;
+        if(d->m_mergeItemList.size() == 1)
+        {
+            d->m_mergeItemList.clear();
+            d->m_bRealMergeStarted = false;
+        }
+    }
+
+    emit updateAvailabilities();
+}
+
+bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::canContinue()
+{
+    bool bCanContinue = false;
+
+    emit q->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->m_bOperationComplete)
+        {
+            setOpStatus(mi, eOpStatusNotSaved);
+            pMFI->m_bOperationComplete = true;
+            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.m_eMergeOperation)
+    {
+        case eNoOperation:
+            break;
+        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(q, i18n("Unknown merge operation. (This must never happen!)"));
+    }
+
+    bool bSuccess = false;
+    bSingleFileMerge = false;
+    switch(mfi.m_eMergeOperation)
+    {
+        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(q, 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(q,
+                                                     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->m_bOperationComplete)
+        {
+            m_mergeItemList.push_back(mi);
+            QString errorText;
+            if(pMFI->m_eMergeOperation == eConflictingFileTypes)
+            {
+                errorText = i18n("The highlighted item has a different type in the different directories. Select what to do.");
+            }
+            if(pMFI->m_eMergeOperation == eConflictingAges)
+            {
+                errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do.");
+            }
+            if(pMFI->m_eMergeOperation == eChangedAndDeleted)
+            {
+                errorText = i18n("The highlighted item was changed in one directory and deleted in the other. Select what to do.");
+            }
+            if(!errorText.isEmpty())
+            {
+                q->scrollTo(mi, QAbstractItemView::EnsureVisible);
+                q->setCurrentIndex(mi);
+                KMessageBox::error(q, errorText);
+                m_mergeItemList.clear();
+                m_bRealMergeStarted = false;
+                return;
+            }
+        }
+    }
+
+    m_currentIndexForOperation = m_mergeItemList.begin();
+    return;
+}
+
+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 directory 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);
+        }
+    }
+    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(MergeItemList::iterator i = m_mergeItemList.begin(); i != m_mergeItemList.end(); ++i)
+    {
+        MergeFileInfos* pMFI = getMFI(*i);
+        ++nrOfItems;
+        if(pMFI->m_bOperationComplete)
+            ++nrOfCompletedItems;
+        if(pMFI->m_bSimOpComplete)
+            ++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(q,
+                                                     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->m_bSimOpComplete = true;
+                }
+            }
+            else
+            {
+                if(rowCount(miCurrent) == 0)
+                {
+                    if(!pMFI->m_bOperationComplete)
+                    {
+                        setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone);
+                        pMFI->m_bOperationComplete = true;
+                        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->m_bOperationComplete) || (bSim && pMFI->m_bSimOpComplete))
+                        {
+                            bDone = false;
+                            break;
+                        }
+                    }
+                    if(bDone)
+                    {
+                        pMFI = getMFI(miParent);
+                        if(bSim)
+                            pMFI->m_bSimOpComplete = bDone;
+                        else
+                        {
+                            setOpStatus(miParent, eOpStatusDone);
+                            pMFI->m_bOperationComplete = bDone;
+                        }
+                    }
+                    miParent = miParent.parent();
+                }
+            }
+        }
+
+        if(!miCurrent.isValid()) // end?
+        {
+            if(m_bRealMergeStarted)
+            {
+                if(bVerbose)
+                {
+                    KMessageBox::information(q, 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)->m_bSimOpComplete = false;
+                }
+                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();
+
+    q->setCurrentIndex(miCurrent);
+    q->scrollTo(miCurrent, EnsureVisible);
+    if(!bSuccess && !bSingleFileMerge)
+    {
+        KMessageBox::error(q, 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;
+    }
+    emit q->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 directory 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 dir operation failed while trying to read the directory."));
+                return false;
+            }
+
+            t_DirectoryList::iterator it; // create list iterator
+
+            for(it = dirList.begin(); it != dirList.end(); ++it) // for each file...
+            {
+                FileAccess& fi2 = *it;
+                if(fi2.fileName() == "." || fi2.fileName() == "..")
+                    continue;
+                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 = FileAccess::removeFile(name);
+            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);
+    q->scrollTo(*m_currentIndexForOperation, EnsureVisible);
+
+    emit q->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;
+        else
+        {
+            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;
+
+    if(FileAccess(destName, true).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(destName);
+    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 directory."));
+        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(QStringList() << i18n("Dir") << 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)
+        emit gotFocus();
+    return false;
+}
+
+void DirectoryMergeInfo::addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi)
+{
+    if(basePath.isEmpty())
+    {
+        return;
+    }
+    else
+    {
+        if(fi != nullptr && fi->exists())
+        {
+            QString dateString = fi->lastModified().toString("yyyy-MM-dd hh:mm:ss");
+            //TODO: Move logic to FileAccess
+            m_pInfoList->addTopLevelItem(new QTreeWidgetItem(
+                m_pInfoList,
+                QStringList() << dir << QString(fi->isDir() ? i18n("Dir") : 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,
+                QStringList() << 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() ? QString("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("B:    ");
+    m_pInfoB->setText(dirB.prettyAbsPath());
+
+    if(dirC.absoluteFilePath() == dirDest.absoluteFilePath())
+    {
+        m_pC->setText(i18n("C (Dest): "));
+        bHideDest = true;
+    }
+    else
+        m_pC->setText("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);
+}
+
+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.m_bEqualAB);
+    vm.writeEntry("EqualAC", mfi.m_bEqualAC);
+    vm.writeEntry("EqualBC", mfi.m_bEqualBC);
+
+    vm.writeEntry("MergeOperation", (int)mfi.m_eMergeOperation);
+    vm.writeEntry("DirA", mfi.dirA());
+    vm.writeEntry("DirB", mfi.dirB());
+    vm.writeEntry("DirC", mfi.dirC());
+    vm.writeEntry("LinkA", mfi.isLinkA());
+    vm.writeEntry("LinkB", mfi.isLinkB());
+    vm.writeEntry("LinkC", mfi.isLinkC());
+    vm.writeEntry("OperationComplete", mfi.m_bOperationComplete);
+
+    vm.writeEntry("AgeA", (int)mfi.m_ageA);
+    vm.writeEntry("AgeB", (int)mfi.m_ageB);
+    vm.writeEntry("AgeC", (int)mfi.m_ageC);
+    vm.writeEntry("ConflictingAges", mfi.m_bConflictingAges); // Equal age but files are not!
+
+    vm.save(ts);
+
+    ts << "}\n";
+
+    return ts;
+}
+
+void DirectoryMergeWindow::slotSaveMergeState()
+{
+    //slotStatusMsg(i18n("Saving Directory Merge State ..."));
+
+    QString s = QFileDialog::getSaveFileName(this, i18n("Save Directory Merge State As..."), QDir::currentPath());
+    if(!s.isEmpty())
+    {
+        d->m_dirMergeStateFilename = s;
+
+        QFile file(d->m_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->dirA() || pMFI->dirB() || pMFI->dirC();
+            if(loop == 0 && bDir)
+            {
+                bool bChange = false;
+                if(!pMFI->m_bEqualAB && pMFI->dirA() == pMFI->dirB() && pMFI->isLinkA() == pMFI->isLinkB())
+                {
+                    pMFI->m_bEqualAB = true;
+                    bChange = true;
+                }
+                if(!pMFI->m_bEqualBC && pMFI->dirC() == pMFI->dirB() && pMFI->isLinkC() == pMFI->isLinkB())
+                {
+                    pMFI->m_bEqualBC = true;
+                    bChange = true;
+                }
+                if(!pMFI->m_bEqualAC && pMFI->dirA() == pMFI->dirC() && pMFI->isLinkA() == pMFI->isLinkC())
+                {
+                    pMFI->m_bEqualAC = true;
+                    bChange = true;
+                }
+
+                if(bChange)
+                    DirectoryMergeWindow::DirectoryMergeWindowPrivate::setPixmaps(*pMFI, bThreeDirs);
+            }
+            bool bExistsEverywhere = pMFI->existsInA() && pMFI->existsInB() && (pMFI->existsInC() || !bThreeDirs);
+            int existCount = int(pMFI->existsInA()) + int(pMFI->existsInB()) + int(pMFI->existsInC());
+            bool bVisible =
+                (bShowIdentical && bExistsEverywhere && pMFI->m_bEqualAB && (pMFI->m_bEqualAC || !bThreeDirs)) || ((bShowDifferent || bDir) && existCount >= 2 && (!pMFI->m_bEqualAB || !(pMFI->m_bEqualAC || !bThreeDirs))) || (bShowOnlyInA && pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInB && !pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInC && !pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC());
+
+            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)));
+
+            setRowHidden(mi.row(), mi.parent(), !bVisible);
+
+            bool bEqual = bThreeDirs ? pMFI->m_bEqualAB && pMFI->m_bEqualAC : pMFI->m_bEqualAB;
+            if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal"
+            {
+                MergeFileInfos* p2 = pMFI->parent();
+                while(p2 != nullptr)
+                {
+                    bool bChange = false;
+                    if(!pMFI->m_bEqualAB && p2->m_bEqualAB)
+                    {
+                        p2->m_bEqualAB = false;
+                        bChange = true;
+                    }
+                    if(!pMFI->m_bEqualAC && p2->m_bEqualAC)
+                    {
+                        p2->m_bEqualAC = false;
+                        bChange = true;
+                    }
+                    if(!pMFI->m_bEqualBC && p2->m_bEqualBC)
+                    {
+                        p2->m_bEqualBC = false;
+                        bChange = true;
+                    }
+
+                    if(bChange)
+                        DirectoryMergeWindow::DirectoryMergeWindowPrivate::setPixmaps(*p2, bThreeDirs);
+                    else
+                        break;
+
+                    p2 = p2->parent();
+                }
+            }
+            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<QAction>(i18n("Start/Continue Directory Merge"), QKeySequence(Qt::Key_F7), this, &DirectoryMergeWindow::slotRunOperationForAllItems, ac, "dir_start_operation");
+    d->m_pDirRunOperationForCurrentItem = GuiUtils::createAction<QAction>(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<QAction>(i18n("Compare Selected File"), this, &DirectoryMergeWindow::compareCurrentFile, ac, "dir_compare_current");
+    d->m_pDirMergeCurrent = GuiUtils::createAction<QAction>(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, &KDiff3App::slotMergeCurrentFile, ac, "merge_current");
+    d->m_pDirFoldAll = GuiUtils::createAction<QAction>(i18n("Fold All Subdirs"), this, &DirectoryMergeWindow::collapseAll, ac, "dir_fold_all");
+    d->m_pDirUnfoldAll = GuiUtils::createAction<QAction>(i18n("Unfold All Subdirs"), this, &DirectoryMergeWindow::expandAll, ac, "dir_unfold_all");
+    d->m_pDirRescan = GuiUtils::createAction<QAction>(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<QAction>(i18n("Choose A for All Items"), this, &DirectoryMergeWindow::slotChooseAEverywhere, ac, "dir_choose_a_everywhere");
+    d->m_pDirChooseBEverywhere = GuiUtils::createAction<QAction>(i18n("Choose B for All Items"), this, &DirectoryMergeWindow::slotChooseBEverywhere, ac, "dir_choose_b_everywhere");
+    d->m_pDirChooseCEverywhere = GuiUtils::createAction<QAction>(i18n("Choose C for All Items"), this, &DirectoryMergeWindow::slotChooseCEverywhere, ac, "dir_choose_c_everywhere");
+    d->m_pDirAutoChoiceEverywhere = GuiUtils::createAction<QAction>(i18n("Auto-Choose Operation for All Items"), this, &DirectoryMergeWindow::slotAutoChooseEverywhere, ac, "dir_autochoose_everywhere");
+    d->m_pDirDoNothingEverywhere = GuiUtils::createAction<QAction>(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<KToggleAction>(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, &DirectoryMergeWindow::slotShowIdenticalFiles, ac, "dir_show_identical_files");
+    d->m_pDirShowDifferentFiles = GuiUtils::createAction<KToggleAction>(i18n("Show Different Files"), this, &DirectoryMergeWindow::slotShowDifferentFiles, ac, "dir_show_different_files");
+    d->m_pDirShowFilesOnlyInA = GuiUtils::createAction<KToggleAction>(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<KToggleAction>(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<KToggleAction>(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<QAction>(i18n("Compare Explicitly Selected Files"), this, &DirectoryMergeWindow::slotCompareExplicitlySelectedFiles, ac, "dir_compare_explicitly_selected_files");
+    d->m_pDirMergeExplicit = GuiUtils::createAction<QAction>(i18n("Merge Explicitly Selected Files"), this, &DirectoryMergeWindow::slotMergeExplicitlySelectedFiles, ac, "dir_merge_explicitly_selected_files");
+
+    d->m_pDirCurrentDoNothing = GuiUtils::createAction<QAction>(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_do_nothing");
+    d->m_pDirCurrentChooseA = GuiUtils::createAction<QAction>(i18n("A"), this, &DirectoryMergeWindow::slotCurrentChooseA, ac, "dir_current_choose_a");
+    d->m_pDirCurrentChooseB = GuiUtils::createAction<QAction>(i18n("B"), this, &DirectoryMergeWindow::slotCurrentChooseB, ac, "dir_current_choose_b");
+    d->m_pDirCurrentChooseC = GuiUtils::createAction<QAction>(i18n("C"), this, &DirectoryMergeWindow::slotCurrentChooseC, ac, "dir_current_choose_c");
+    d->m_pDirCurrentMerge = GuiUtils::createAction<QAction>(i18n("Merge"), this, &DirectoryMergeWindow::slotCurrentMerge, ac, "dir_current_merge");
+    d->m_pDirCurrentDelete = GuiUtils::createAction<QAction>(i18n("Delete (if exists)"), this, &DirectoryMergeWindow::slotCurrentDelete, ac, "dir_current_delete");
+
+    d->m_pDirCurrentSyncDoNothing = GuiUtils::createAction<QAction>(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_sync_do_nothing");
+    d->m_pDirCurrentSyncCopyAToB = GuiUtils::createAction<QAction>(i18n("Copy A to B"), this, &DirectoryMergeWindow::slotCurrentCopyAToB, ac, "dir_current_sync_copy_a_to_b");
+    d->m_pDirCurrentSyncCopyBToA = GuiUtils::createAction<QAction>(i18n("Copy B to A"), this, &DirectoryMergeWindow::slotCurrentCopyBToA, ac, "dir_current_sync_copy_b_to_a");
+    d->m_pDirCurrentSyncDeleteA = GuiUtils::createAction<QAction>(i18n("Delete A"), this, &DirectoryMergeWindow::slotCurrentDeleteA, ac, "dir_current_sync_delete_a");
+    d->m_pDirCurrentSyncDeleteB = GuiUtils::createAction<QAction>(i18n("Delete B"), this, &DirectoryMergeWindow::slotCurrentDeleteB, ac, "dir_current_sync_delete_b");
+    d->m_pDirCurrentSyncDeleteAAndB = GuiUtils::createAction<QAction>(i18n("Delete A && B"), this, &DirectoryMergeWindow::slotCurrentDeleteAAndB, ac, "dir_current_sync_delete_a_and_b");
+    d->m_pDirCurrentSyncMergeToA = GuiUtils::createAction<QAction>(i18n("Merge to A"), this, &DirectoryMergeWindow::slotCurrentMergeToA, ac, "dir_current_sync_merge_to_a");
+    d->m_pDirCurrentSyncMergeToB = GuiUtils::createAction<QAction>(i18n("Merge to B"), this, &DirectoryMergeWindow::slotCurrentMergeToB, ac, "dir_current_sync_merge_to_b");
+    d->m_pDirCurrentSyncMergeToAAndB = GuiUtils::createAction<QAction>(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);
+
+    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());
+
+    bool bThreeDirs = d->isThreeWay();
+
+    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
new file mode 100644 (file)
index 0000000..bbccc69
--- /dev/null
@@ -0,0 +1,166 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef DIRECTORY_MERGE_WINDOW_H
+#define DIRECTORY_MERGE_WINDOW_H
+
+#include <QTreeWidget>
+#include <QEvent>
+#include <list>
+#include <map>
+#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:
+   DirectoryMergeWindow( QWidget* pParent, Options* pOptions );
+   ~DirectoryMergeWindow() override;
+   void setDirectoryMergeInfo(DirectoryMergeInfo* p);
+   bool init(
+      const QSharedPointer<DirectoryInfo> &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(QString fn1,QString fn2, QString fn3, QString ofn, QString,QString,QString,const QSharedPointer<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:
+  class DirectoryMergeWindowPrivate;
+  friend class DirectoryMergeWindowPrivate;
+  DirectoryMergeWindowPrivate* d;
+  class DirMergeItemDelegate;
+  friend 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
new file mode 100644 (file)
index 0000000..bcb34b9
--- /dev/null
@@ -0,0 +1,1168 @@
+/***************************************************************************
+ *   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.                                   *
+ ***************************************************************************/
+#include "fileaccess.h"
+#include "cvsignorelist.h"
+#include "common.h"
+#include "progress.h"
+#include "Utils.h"
+
+#include <QDir>
+#include <QtMath>
+#include <QProcess>
+#include <QRegExp>
+#include <QTemporaryFile>
+
+#include <cstdlib>
+#include <vector>
+
+#include <KIO/CopyJob>
+#include <KIO/Job>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <kio/global.h>
+#include <kio/jobclasses.h>
+#include <kio/jobuidelegate.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifndef Q_OS_WIN
+#include <unistd.h> // Needed for creating symbolic links via symlink().
+#include <utime.h>
+#endif
+
+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();
+
+    m_url = QUrl();
+    m_bValidData = false;
+    m_name = QString();
+
+    m_linkTarget = "";
+    //m_fileType = -1;
+    m_pParent = nullptr;
+    tmpFile.clear();
+    tmpFile = QSharedPointer<QTemporaryFile>(new QTemporaryFile());
+}
+
+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();
+
+    m_url = url;
+    m_name = m_url.fileName();
+    //Insure 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 = QFileInfo(m_url.path());
+        m_pParent = nullptr;
+
+        loadData();
+    }
+    else
+    {
+        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 = 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_bSymLink)
+    {
+        m_linkTarget = m_fileInfo.readLink();
+#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
+    }
+
+    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::setUdsEntry(const KIO::UDSEntry& e)
+{
+    long acc = 0;
+    long fileType = 0;
+    QVector<uint> fields = e.fields();
+    QString filePath;
+
+    for(QVector<uint>::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:
+            break;
+        case KIO::UDSEntry::UDS_GUESSED_MIME_TYPE:
+            break;
+        case KIO::UDSEntry::UDS_XML_PROPERTIES:
+            break;
+        default:
+            break;
+        }
+    }
+
+    m_fileInfo = QFileInfo(filePath);
+    m_fileInfo.setCaching(true);
+    if(m_url.isEmpty())
+        m_url = QUrl::fromUserInput(m_fileInfo.absoluteFilePath());
+
+    m_name = m_url.fileName();
+    m_bExists = m_fileInfo.exists();
+    //insure modification time is initialized if it wasn't already.
+    if(m_modificationTime.isNull())
+        m_modificationTime = m_fileInfo.lastModified();
+
+    m_bValidData = true;
+    m_bSymLink = !m_linkTarget.isEmpty();
+    if(m_name.isEmpty())
+    {
+        m_name = m_fileInfo.fileName();
+    }
+
+
+#ifndef Q_OS_WIN
+    m_bHidden = m_name[0] == '.';
+#endif
+}
+
+bool FileAccess::isValid() const
+{
+    return m_bValidData;
+}
+
+bool FileAccess::isNormal() const
+{
+    return 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.isRelative())
+    {
+        url.setPath(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_fileInfo.fileName();
+}
+
+QString FileAccess::fileRelPath() const
+{
+    QString basePath = m_baseDir.canonicalPath();
+    QString filePath = m_fileInfo.canonicalFilePath();
+    QString path = filePath.replace(basePath + '/', QLatin1String(""));
+
+    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;
+}
+
+static bool interruptableReadFile(QFile& f, 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 = f.read((char*)pDestBuffer + i, nextLength);
+        if(reallyRead != nextLength)
+        {
+            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)
+{
+    //Avoid hang on linux for special files.
+    if(!isNormal())
+        return true;
+
+    if(!m_localCopy.isEmpty())
+    {
+        QFile f(m_localCopy);
+        if(f.open(QIODevice::ReadOnly))
+            return interruptableReadFile(f, pDestBuffer, maxLength); // maxLength == f.read( (char*)pDestBuffer, maxLength );
+    }
+    else if(isLocal())
+    {
+        QFile f(absoluteFilePath());
+
+        if(f.open(QIODevice::ReadOnly))
+            return interruptableReadFile(f, pDestBuffer, maxLength); //maxLength == f.read( (char*)pDestBuffer, maxLength );
+    }
+    else
+    {
+        FileAccessJobHandler jh(this);
+        return jh.get(pDestBuffer, maxLength);
+    }
+    return false;
+}
+
+bool FileAccess::writeFile(const void* pSrcBuffer, qint64 length)
+{
+    ProgressProxy pp;
+    if(isLocal())
+    {
+        QFile f(absoluteFilePath());
+        if(f.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 = f.write((char*)pSrcBuffer + i, nextLength);
+                if(reallyWritten != nextLength)
+                {
+                    return false;
+                }
+                i += reallyWritten;
+
+                pp.step();
+                if(pp.wasCancelled())
+                    return false;
+            }
+            f.close();
+
+            if(isExecutable()) // value is true if the old file was executable
+            {
+                // Preserve attributes
+                f.setPermissions(f.permissions() | QFile::ExeUser);
+            }
+
+            return true;
+        }
+    }
+    else
+    {
+        FileAccessJobHandler jh(this);
+        return jh.put(pSrcBuffer, length, true /*overwrite*/);
+    }
+    return false;
+}
+
+bool FileAccess::copyFile(const QString& dest)
+{
+    FileAccessJobHandler jh(this);
+    return jh.copyFile(dest); // Handles local and remote copying.
+}
+
+bool FileAccess::rename(const QString& 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::removeFile(const QString& name) // static
+{
+    return FileAccess(name).removeFile();
+}
+
+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;
+}
+
+bool FileAccess::createLocalCopy()
+{
+    if(isLocal() || !m_localCopy.isEmpty())
+       return true;
+
+    tmpFile->setAutoRemove(true);
+    tmpFile->setFileTemplate(QStringLiteral("XXXXXX-kdiff3tmp"));
+    tmpFile->open();
+    tmpFile->close();
+    m_localCopy = tmpFile->fileName();
+
+    return copyFile(tmpFile->fileName());
+}
+//static tempfile Generator
+void FileAccess::createTempFile(QTemporaryFile& tmpFile)
+{
+    tmpFile.setAutoRemove(true);
+    tmpFile.setFileTemplate(QStringLiteral("XXXXXX-kdiff3tmp"));
+    tmpFile.open();
+    tmpFile.close();
+}
+
+
+bool FileAccess::removeTempFile(const QString& name) // static
+{
+    return FileAccess(name).removeFile();
+}
+
+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()
+{
+    return m_statusText;
+}
+
+void FileAccess::setStatusText(const QString& s)
+{
+    m_statusText = s;
+}
+
+QString FileAccess::cleanPath(const QString& path) // static
+{
+    QUrl url = QUrl::fromUserInput(path, QString(""), QUrl::AssumeLocalFile);
+    if(url.isLocalFile() || !url.isValid())
+    {
+        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(bakName);// 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;
+    m_bSuccess = false;
+}
+
+bool FileAccessJobHandler::stat(int 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())
+    {
+        //pJob->uiDelegate()->showErrorMessage();
+        m_pFileAccess->doError();
+        m_bSuccess = true;
+    }
+    else
+    {
+        m_bSuccess = true;
+
+        const KIO::UDSEntry e = static_cast<KIO::StatJob*>(pJob)->statResult();
+
+        m_pFileAccess->setUdsEntry(e);
+    }
+
+    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, static_cast<void (KIO::TransferJob::*)(KJob*,unsigned long)>(&KIO::TransferJob::percent), &pp, &ProgressProxyExtender::slotPercent);
+
+        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())
+    {
+        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, static_cast<void (KIO::TransferJob::*)(KJob*,unsigned long)>(&KIO::TransferJob::percent), &pp, &ProgressProxyExtender::slotPercent);
+
+        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())
+    {
+        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())
+    {
+        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)
+{
+    QUrl dirURL = QUrl::fromUserInput(dirName, QString(""), QUrl::AssumeLocalFile);
+    if(dirName.isEmpty())
+        return false;
+    else if(dirURL.isLocalFile() || dirURL.isRelative())
+    {
+        return QDir().mkdir(dirURL.path());
+    }
+    else
+    {
+        m_bSuccess = false;
+        KIO::SimpleJob* pJob = KIO::mkdir(dirURL);
+        connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult);
+
+        ProgressProxy::enterEventLoop(pJob, i18n("Making directory: %1", dirName));
+        return m_bSuccess;
+    }
+}
+
+bool FileAccessJobHandler::rmDir(const QString& dirName)
+{
+    QUrl dirURL = QUrl::fromUserInput(dirName, QString(""), QUrl::AssumeLocalFile);
+    if(dirName.isEmpty())
+        return false;
+    else if(dirURL.isLocalFile())
+    {
+        return QDir().rmdir(dirURL.path());
+    }
+    else
+    {
+        m_bSuccess = false;
+        KIO::SimpleJob* pJob = KIO::rmdir(dirURL);
+        connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult);
+
+        ProgressProxy::enterEventLoop(pJob, i18n("Removing directory: %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 QString& dest)
+{
+    if(dest.isEmpty())
+        return false;
+
+    QUrl kurl = QUrl::fromUserInput(dest, QString(""), QUrl::AssumeLocalFile);
+    if(kurl.isRelative())
+        kurl = QUrl::fromUserInput(QDir().absoluteFilePath(dest), QString(""), QUrl::AssumeLocalFile); // assuming that invalid means relative
+
+    if(m_pFileAccess->isLocal() && kurl.isLocalFile())
+    {
+        return QDir().rename(m_pFileAccess->absoluteFilePath(), kurl.path());
+    }
+    else
+    {
+        ProgressProxyExtender pp;
+        int permissions = -1;
+        m_bSuccess = false;
+        KIO::FileCopyJob* pJob = KIO::file_move(m_pFileAccess->url(), kurl, permissions, KIO::HideProgressInfo);
+        connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult);
+        //connect(pJob, static_cast<void (KIO::FileCopyJob::*)(KJob*,unsigned long)>(&KIO::FileCopyJob::percent), &pp, &ProgressProxyExtender::slotPercent);
+
+        ProgressProxy::enterEventLoop(pJob,
+                                      i18n("Renaming file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest));
+        return m_bSuccess;
+    }
+}
+
+void FileAccessJobHandler::slotSimpleJobResult(KJob* pJob)
+{
+    if(pJob->error())
+    {
+        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& dest)
+{
+    ProgressProxyExtender pp;
+    QUrl destUrl = QUrl::fromUserInput(dest, QString(""), QUrl::AssumeLocalFile);
+    m_pFileAccess->setStatusText(QString());
+
+    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(), destUrl, permissions, KIO::HideProgressInfo);
+    connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult);
+    //connect(pJob, static_cast<void (KIO::FileCopyJob::*)(KJob*,unsigned long)>(&KIO::FileCopyJob::percent), &pp, &ProgressProxyExtender::slotPercent);
+    ProgressProxy::enterEventLoop(pJob,
+                                  i18n("Copying file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest));
+
+    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 directory: %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())
+        {
+            // No Permission to read directory or other error.
+            m_bSuccess = false;
+        }
+        else
+        {
+            foreach(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<void (KIO::ListJob::*)(KJob*,qint64)>(&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.
+    QUrl parentUrl = QUrl::fromUserInput(m_pFileAccess->absoluteFilePath(), QString(""), QUrl::AssumeLocalFile);
+
+    KIO::UDSEntryList::ConstIterator i;
+    for(i = l.begin(); i != l.end(); ++i)
+    {
+        const KIO::UDSEntry& e = *i;
+        FileAccess fa;
+
+        fa.m_pParent = m_pFileAccess;
+        fa.setUdsEntry(e);
+
+        if(fa.fileName() != "." && fa.fileName() != "..")
+        {
+            QUrl url = parentUrl.adjusted(QUrl::StripTrailingSlash);
+            url.setPath(url.path() + '/' + fa.fileName());
+            fa.setUrl(url);
+            //fa.m_absoluteFilePath = fa.url().url();
+            m_pDirList->push_back(fa);
+        }
+    }
+}
+
+//#include "fileaccess.moc"
diff --git a/src/fileaccess.h b/src/fileaccess.h
new file mode 100644 (file)
index 0000000..e22ebec
--- /dev/null
@@ -0,0 +1,192 @@
+/***************************************************************************
+ *   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.                                   *
+ ***************************************************************************/
+
+#ifndef FILEACCESS_H
+#define FILEACCESS_H
+
+#include "progress.h"
+#include "ProgressProxyExtender.h"
+
+#include <QDir>
+#include <QFileInfo>
+#include <QDateTime>
+#include <QSharedPointer>
+#include <QTemporaryFile>
+#include <QUrl>
+
+#include <KIO/UDSEntry>
+
+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& );
+   static bool removeTempFile( const QString& );
+   bool removeFile();
+   static bool removeFile( const QString& );
+   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 QString& );
+   static bool symLink( const QString& linkTarget, const QString& linkLocation );
+
+   void addPath( const QString& txt );
+   QString getStatusText();
+
+   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; }
+
+ private:
+   friend class FileAccessJobHandler;
+   void setUdsEntry(const KIO::UDSEntry& e);
+   void setStatusText( const QString& s );
+
+   void reset();
+
+   QUrl m_url;
+   bool m_bValidData;
+
+   //long m_fileType; // for testing only
+   FileAccess* m_pParent;
+
+   QDir m_baseDir;
+   QFileInfo m_fileInfo;
+   QString m_linkTarget;
+   QString m_name;
+   QString m_localCopy;
+   QSharedPointer<QTemporaryFile> tmpFile;
+
+   qint64 m_size;
+   QDateTime m_modificationTime;
+   bool m_bSymLink;
+   bool m_bFile;
+   bool m_bDir;
+   bool m_bExists;
+   bool m_bWritable;
+   bool m_bReadable;
+   bool m_bExecutable;
+   bool m_bHidden;
+
+   QString m_statusText; // Might contain an error string, when the last operation didn't succeed.
+};
+
+class t_DirectoryList : public std::list<FileAccess>
+{};
+
+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(int detailLevel=2, bool bWantToWrite=false );
+   bool copyFile( const QString& dest );
+   bool rename( const QString& 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;
+   bool m_bSuccess;
+
+   // Data needed during Job
+   qint64 m_transferredBytes;
+   char* m_pTransferBuffer = nullptr;  // Needed during get or put
+   qint64 m_maxLength;
+
+   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
new file mode 100644 (file)
index 0000000..85eb40c
--- /dev/null
@@ -0,0 +1,860 @@
+/* Analyze file differences for GNU DIFF.
+
+   Modified for KDiff3 by Joachim Eibl <joachim.eibl at gmx.de> 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.  */
+
+/* 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 "common.h"
+#include "gnudiff_diff.h"
+//#include <error.h>
+#include <stdlib.h>
+
+static LineRef *xvec, *yvec;  /* Vectors being compared. */
+static LineRef *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 LineRef *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 LineRef 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 {
+    LineRef 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.  */
+
+LineRef GnuDiff::diag(LineRef xoff, LineRef xlim, LineRef yoff, LineRef ylim, bool find_minimal,
+                  struct partition *part)
+{
+    LineRef *const fd = fdiag;        /* Give the compiler a chance. */
+    LineRef *const bd = bdiag;        /* Additional help for the compiler. */
+    LineRef const *const xv = xvec;   /* Still more help for the compiler. */
+    LineRef const *const yv = yvec;   /* And more and more . . . */
+    LineRef const dmin = xoff - ylim; /* Minimum valid diagonal. */
+    LineRef const dmax = xlim - yoff; /* Maximum valid diagonal. */
+    LineRef const fmid = xoff - yoff; /* Center diagonal of top-down search. */
+    LineRef const bmid = xlim - ylim; /* Center diagonal of bottom-up search. */
+    LineRef fmin = fmid, fmax = fmid; /* Limits of top-down search. */
+    LineRef bmin = bmid, bmax = bmid; /* Limits of bottom-up search. */
+    LineRef 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)
+    {
+        LineRef 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)
+        {
+            LineRef 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] = LINEREF_MAX : ++bmin;
+        bmax < dmax ? bd[++bmax + 1] = LINEREF_MAX : --bmax;
+        for(d = bmax; d >= bmin; d -= 2)
+        {
+            LineRef 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)
+        {
+            LineRef best;
+
+            best = 0;
+            for(d = fmax; d >= fmin; d -= 2)
+            {
+                LineRef dd = d - fmid;
+                LineRef x = fd[d];
+                LineRef y = x - d;
+                LineRef 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)
+            {
+                LineRef dd = d - bmid;
+                LineRef x = bd[d];
+                LineRef y = x - d;
+                LineRef 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)
+        {
+            LineRef fxybest, fxbest;
+            LineRef bxybest, bxbest;
+
+            fxbest = bxbest = 0; /* Pacify `gcc -Wall'.  */
+
+            /* Find forward diagonal that maximizes X + Y.  */
+            fxybest = -1;
+            for(d = fmax; d >= fmin; d -= 2)
+            {
+                LineRef x = MIN(fd[d], xlim);
+                LineRef 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 = LINEREF_MAX;
+            for(d = bmax; d >= bmin; d -= 2)
+            {
+                LineRef x = MAX(xoff, bd[d]);
+                LineRef 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(LineRef xoff, LineRef xlim, LineRef yoff, LineRef ylim, bool find_minimal)
+{
+    LineRef *const xv = xvec; /* Help the compiler.  */
+    LineRef *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
+    {
+        LineRef c;
+        struct partition part;
+
+        /* Find a point of correspondence in the middle of the files.  */
+
+        c = diag(xoff, xlim, yoff, ylim, find_minimal, &part);
+
+        if(c == 1)
+        {
+            /* This should be impossible, because it implies that
+         one of the two subsequences is empty,
+         and that case was handled above without calling `diag'.
+         Let's verify that this is true.  */
+            abort();
+#if 0
+      /* The two subsequences differ by a single insert or delete;
+         record it and we are done.  */
+      if (part.xmid - part.ymid < xoff - yoff)
+        files[1].changed[files[1].realindexes[part.ymid - 1]] = 1;
+      else
+        files[0].changed[files[0].realindexes[part.xmid]] = 1;
+#endif
+        }
+        else
+        {
+            /* 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(struct file_data filevec[])
+{
+    int f;
+    LineRef i;
+    char *discarded[2];
+    LineRef *equiv_count[2];
+    LineRef *p;
+
+    /* Allocate our results.  */
+    p = (LineRef *)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 = (LineRef *)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];
+        LineRef *counts = equiv_count[1 - f];
+        LineRef *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 < (LineRef)end; ++i)
+        {
+            LineRef nmatch;
+            if(equivs[i] == 0)
+                continue;
+            nmatch = counts[equivs[i]];
+            if(nmatch == 0)
+                discards[i] = 1;
+            else if(nmatch > (LineRef)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)
+    {
+        LineRef 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.  */
+                LineRef j;
+                LineRef length;
+                LineRef 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
+                {
+                    LineRef consec;
+                    LineRef minimum = 1;
+                    LineRef 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];
+        LineRef end = filevec[f].buffered_lines;
+        LineRef 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(struct file_data filevec[])
+{
+    int f;
+
+    for(f = 0; f < 2; ++f)
+    {
+        bool *changed = filevec[f].changed;
+        bool const *other_changed = filevec[1 - f].changed;
+        LineRef const *equivs = filevec[f].equivs;
+        LineRef i = 0;
+        LineRef j = 0;
+        LineRef i_end = filevec[f].buffered_lines;
+
+        while(true)
+        {
+            LineRef 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(LineRef line0, LineRef line1, LineRef deleted, LineRef inserted, struct change *old)
+{
+    struct 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(struct file_data const filevec[])
+{
+    struct change *script = nullptr;
+    bool *changed0 = filevec[0].changed;
+    bool *changed1 = filevec[1].changed;
+    LineRef len0 = filevec[0].buffered_lines;
+    LineRef len1 = filevec[1].buffered_lines;
+
+    /* Note that changedN[len0] does exist, and is 0.  */
+
+    LineRef i0 = 0, i1 = 0;
+
+    while(i0 < len0 || i1 < len1)
+    {
+        if(changed0[i0] | changed1[i1])
+        {
+            LineRef 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(struct file_data const filevec[])
+{
+    struct change *script = nullptr;
+    bool *changed0 = filevec[0].changed;
+    bool *changed1 = filevec[1].changed;
+    LineRef 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])
+        {
+            LineRef 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(struct comparison *cmp)
+{
+    LineRef diags;
+    int f;
+    struct 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 = (LineRef *)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 = MAX(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 `struct 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
new file mode 100644 (file)
index 0000000..ffb4f31
--- /dev/null
@@ -0,0 +1,360 @@
+/* Shared definitions for GNU DIFF
+
+   Modified for KDiff3 by Joachim Eibl <joachim.eibl at gmx.de> 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.  */
+
+#ifndef GNUDIFF_DIFF_H
+#define GNUDIFF_DIFF_H
+
+#include "gnudiff_system.h"
+
+#include <stdio.h>
+#include <QString>
+
+inline bool isEndOfLine( QChar c )
+{
+   return c=='\n' || c=='\r' || c=='\x0b';
+}
+
+#define TAB_WIDTH 8
+
+class GnuDiff
+{
+public:
+/* What kind of changes a hunk contains.  */
+enum changes
+{
+  /* No changes: lines common to both files.  */
+  UNCHANGED,
+
+  /* Deletes only: lines taken from just the first file.  */
+  OLD,
+
+  /* Inserts only: lines taken from just the second file.  */
+  NEW,
+
+  /* Both deletes and inserts: a hunk containing both old and new lines.  */
+  CHANGED
+};
+\f
+/* 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.  */
+LineRef context;
+
+/* Consider all files as text files (-a).
+   Don't interpret codes over 0177 as implying a "binary file".  */
+bool text;
+
+/* 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 blank lines (-B).  */
+bool ignore_blank_lines;
+
+/* 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;
+
+/* Ignore differences in case of letters in file names.  */
+bool ignore_file_name_case;
+
+/* Regexp to identify function-header lines (-F).  */
+//struct re_pattern_buffer function_regexp;
+
+/* Ignore changes that affect only lines matching this regexp (-I).  */
+//struct re_pattern_buffer ignore_regexp;
+
+/* Say only whether files differ, not how (-q).  */
+bool brief;
+
+/* Expand tabs in the output so the text lines up properly
+   despite the characters added to the front of each line (-t).  */
+bool expand_tabs;
+
+/* Use a tab in the output, rather than a space, before the text of an
+   input line, so as to keep the proper alignment in the input line
+   without changing the characters in it (-T).  */
+bool initial_tab;
+
+/* In directory comparison, specify file to start with (-S).
+   This is used for resuming an aborted comparison.
+   All file names less than this name are ignored.  */
+const QChar *starting_file;
+
+/* Pipe each file's output through pr (-l).  */
+bool paginate;
+
+/* Line group formats for unchanged, old, new, and changed groups.  */
+const QChar *group_format[CHANGED + 1];
+
+/* Line formats for unchanged, old, and new lines.  */
+const QChar *line_format[NEW + 1];
+
+/* If using OUTPUT_SDIFF print extra information to help the sdiff filter.  */
+bool sdiff_merge_assist;
+
+/* Tell OUTPUT_SDIFF to show only the left version of common lines.  */
+bool left_column;
+
+/* Tell OUTPUT_SDIFF to not show common lines.  */
+bool suppress_common_lines;
+
+/* The half line width and column 2 offset for OUTPUT_SDIFF.  */
+unsigned int sdiff_half_width;
+unsigned int sdiff_column2_offset;
+
+/* Use heuristics for better speed with large files with a small
+   density of changes.  */
+bool speed_large_files;
+
+/* Patterns that match file names to be excluded.  */
+struct exclude *excluded;
+
+/* Don't discard lines.  This makes things slower (sometimes much
+   slower) but will find a guaranteed minimal set of changes.  */
+bool minimal;
+
+\f
+/* 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
+{
+  struct change *link;         /* Previous or next edit command  */
+  LineRef inserted;                    /* # lines of file 1 changed here.  */
+  LineRef deleted;                     /* # lines of file 0 changed here.  */
+  LineRef line0;                       /* Line number of 1st deleted line.  */
+  LineRef 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.  */
+    LineRef 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].  */
+    LineRef 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.  */
+    LineRef *equivs;
+
+    /* Vector, like the previous one except that
+       the elements for discarded lines have been squeezed out.  */
+    LineRef *undiscarded;
+
+    /* Vector mapping virtual line numbers (not counting discarded lines)
+       to real ones (counting those lines).  Both are origin-0.  */
+    LineRef *realindexes;
+
+    /* Total number of nondiscarded lines.  */
+    LineRef 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.  */
+    LineRef equiv_max;
+};
+
+/* Data on two input files being compared.  */
+
+struct comparison
+  {
+    struct file_data file[2];
+    struct comparison const *parent;  /* parent, if a recursive comparison */
+  };
+
+/* Describe the two files currently being compared.  */
+
+struct file_data files[2];
+\f
+/* Stdio stream to output diffs to.  */
+
+FILE *outfile;
+\f
+/* Declare various functions.  */
+
+/* analyze.c */
+struct change* diff_2_files (struct comparison *);
+
+/* context.c */
+void print_context_header (struct file_data[], bool);
+void print_context_script (struct change *, bool);
+
+/* dir.c */
+int diff_dirs (struct comparison const *, int (*) (struct comparison const *, const QChar *, const QChar *));
+
+/* ed.c */
+void print_ed_script (struct change *);
+void pr_forward_ed_script (struct change *);
+
+/* ifdef.c */
+void print_ifdef_script (struct change *);
+
+/* io.c */
+void file_block_read (struct file_data *, size_t);
+bool read_files (struct file_data[], bool);
+
+/* normal.c */
+void print_normal_script (struct change *);
+
+/* rcs.c */
+void print_rcs_script (struct change *);
+
+/* side.c */
+void print_sdiff_script (struct change *);
+
+/* util.c */
+QChar *concat (const QChar *, const QChar *, const QChar *);
+bool lines_differ ( const QChar *, size_t, const QChar *, size_t );
+LineRef translate_line_number (struct file_data const *, LineRef);
+struct change *find_change (struct change *);
+struct change *find_reverse_change (struct change *);
+void *zalloc (size_t);
+enum changes analyze_hunk (struct change *, LineRef *, LineRef *, LineRef *, LineRef *);
+void begin_output (void);
+void debug_script (struct change *);
+void finish_output (void);
+void message (const QChar *, const QChar *, const QChar *);
+void message5 (const QChar *, const QChar *, const QChar *, const QChar *, const QChar *);
+void output_1_line (const QChar *, const QChar *, const QChar *, const QChar *);
+void perror_with_name (const QChar *);
+void setup_output (const QChar *, const QChar *, bool);
+void translate_range (struct file_data const *, LineRef, LineRef, long *, long *);
+
+/* version.c */
+//extern const QChar version_string[];
+
+private:
+   // gnudiff_analyze.cpp
+   LineRef diag (LineRef xoff, LineRef xlim, LineRef yoff, LineRef ylim, bool find_minimal, struct partition *part);
+   void compareseq (LineRef xoff, LineRef xlim, LineRef yoff, LineRef ylim, bool find_minimal);
+   void discard_confusing_lines (struct file_data filevec[]);
+   void shift_boundaries (struct file_data filevec[]);
+   struct change * add_change (LineRef line0, LineRef line1, LineRef deleted, LineRef inserted, struct change *old);
+   struct change * build_reverse_script (struct file_data const filevec[]);
+   struct change* build_script (struct file_data const filevec[]);
+
+   // gnudiff_io.cpp
+   void find_and_hash_each_line (struct file_data *current);
+   void find_identical_ends (struct file_data filevec[]);
+
+   // gnudiff_xmalloc.cpp
+   void *xmalloc (size_t n);
+   void *xrealloc(void *p, size_t n);
+   void xalloc_die (void);
+
+   inline bool isWhite( QChar c )
+   {
+      return c==' ' || c=='\t' ||  c=='\r';
+   }
+}; // class GnuDiff
+
+# define XMALLOC(Type, N_items) ((Type *) xmalloc (sizeof (Type) * (N_items)))
+# define XREALLOC(Ptr, Type, N_items) \
+  ((Type *) xrealloc ((void *) (Ptr), sizeof (Type) * (N_items)))
+
+/* Declare and alloc memory for VAR of type TYPE. */
+# define NEW(Type, Var)  Type *(Var) = XMALLOC (Type, 1)
+
+/* Free VAR only if non NULL. */
+# define XFREE(Var)    \
+   do {                 \
+      if (Var)          \
+        free (Var);     \
+   } while (0)
+
+/* Return a pointer to a malloc'ed copy of the array SRC of NUM elements. */
+# define CCLONE(Src, Num) \
+  (memcpy (xmalloc (sizeof (*Src) * (Num)), (Src), sizeof (*Src) * (Num)))
+
+/* Return a malloc'ed copy of SRC. */
+# define CLONE(Src) CCLONE (Src, 1)
+
+#endif
diff --git a/src/gnudiff_io.cpp b/src/gnudiff_io.cpp
new file mode 100644 (file)
index 0000000..c9bb275
--- /dev/null
@@ -0,0 +1,545 @@
+/* File I/O for GNU DIFF.
+
+   Modified for KDiff3 by Joachim Eibl <joachim.eibl at gmx.de> 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.  */
+
+#include "gnudiff_diff.h"
+#include <stdlib.h>
+
+/* 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;
+verify(hash_value_is_unsigned, !TYPE_SIGNED(hash_value));
+
+/* 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 {
+    LineRef 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 LineRef *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 struct equivclass *equivs;
+
+/* Index of first free element in the array `equivs'.  */
+static LineRef equivs_index;
+
+/* Number of elements allocated in the array `equivs'.  */
+static LineRef 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(struct file_data *current)
+{
+    hash_value h;
+    const QChar *p = current->prefix_end;
+    QChar c;
+    LineRef i, *bucket;
+    size_t length;
+
+    /* Cache often-used quantities in local variables to help the compiler.  */
+    const QChar **linbuf = current->linbuf;
+    LineRef alloc_lines = current->alloc_lines;
+    LineRef line = 0;
+    LineRef linbuf_base = current->linbuf_base;
+    LineRef *cureqs = (LineRef *)xmalloc(alloc_lines * sizeof *cureqs);
+    struct equivclass *eqs = equivs;
+    LineRef eqs_index = equivs_index;
+    LineRef 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 && !isEndOfLine(c = *p))
+                {
+                    if(!(isWhite(c) || (bIgnoreNumbers && (c.isDigit() || c == '-' || c == '.'))))
+                        h = HASH(h, c.toLower().unicode());
+                    ++p;
+                }
+                break;
+
+            default:
+                while(p < bufend && !isEndOfLine(c = *p))
+                {
+                    h = HASH(h, c.toLower().unicode());
+                    ++p;
+                }
+                break;
+            }
+        else
+            switch(ignore_white_space)
+            {
+            case IGNORE_ALL_SPACE:
+                while(p < bufend && !isEndOfLine(c = *p))
+                {
+                    if(!(isWhite(c) || (bIgnoreNumbers && (c.isDigit() || c == '-' || c == '.'))))
+                        h = HASH(h, c.unicode());
+                    ++p;
+                }
+                break;
+
+            default:
+                while(p < bufend && !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((LineRef)(LINEREF_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((LineRef)(LINEREF_MAX / 3) <= alloc_lines || (LineRef)(LINEREF_MAX / sizeof *cureqs) <= 2 * alloc_lines - linbuf_base || (LineRef)(LINEREF_MAX / sizeof *linbuf) <= alloc_lines - linbuf_base)
+                xalloc_die();
+            alloc_lines = 2 * alloc_lines - linbuf_base;
+            cureqs = (LineRef *)xrealloc(cureqs, alloc_lines * sizeof *cureqs);
+            linbuf += linbuf_base;
+            linbuf = (const QChar **)xrealloc(linbuf,
+                                              (alloc_lines - linbuf_base) * sizeof *linbuf);
+            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((LineRef)(LINEREF_MAX / 3) <= alloc_lines || (LineRef)(LINEREF_MAX / sizeof *cureqs) <= 2 * alloc_lines - linbuf_base || (LineRef)(LINEREF_MAX / sizeof *linbuf) <= 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 *linbuf);
+            linbuf -= linbuf_base;
+        }
+        linbuf[line] = p;
+
+        if(p >= bufend)
+            break;
+
+        if(context <= i && no_diff_means_no_output)
+            break;
+
+        line++;
+
+        while(p < bufend && !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.  */
+static LineRef
+guess_lines(LineRef n, size_t s, size_t t)
+{
+    size_t guessed_bytes_per_line = n < 10 ? 32 : s / (n - 1);
+    size_t guessed_lines = MAX((LineRef)1, t / guessed_bytes_per_line);
+    return (LineRef)MIN(guessed_lines, (LineRef)(LINEREF_MAX / (2 * sizeof(QChar *) + 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(struct 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 && !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 && !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;
+    LineRef alloc_lines0, alloc_lines1;
+    LineRef buffered_prefix, prefix_count, prefix_mask;
+    LineRef middle_guess, suffix_guess;
+    if(no_diff_means_no_output && context < (LineRef)(LINEREF_MAX / 4) && context < (LineRef)(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 + MIN(context, suffix_guess));
+    }
+    else
+    {
+        prefix_count = 0;
+        alloc_lines0 = guess_lines(0, 0, n0);
+    }
+
+    prefix_mask = prefix_count - 1;
+    LineRef lines = 0;
+    linbuf0 = (const QChar **)xmalloc(alloc_lines0 * sizeof(*linbuf0));
+    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)
+        {
+            LineRef l = lines++ & prefix_mask;
+            if(l == alloc_lines0)
+            {
+                if((LineRef)(LINEREF_MAX / (2 * sizeof *linbuf0)) <= alloc_lines0)
+                    xalloc_die();
+                alloc_lines0 *= 2;
+                linbuf0 = (const QChar **)xrealloc(linbuf0, alloc_lines0 * sizeof(*linbuf0));
+            }
+            linbuf0[l] = p0;
+            while(p0 < pEnd0 && !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 + MIN(context, suffix_guess);
+    if(alloc_lines1 < buffered_prefix || (LineRef)(LINEREF_MAX / sizeof *linbuf1) <= alloc_lines1)
+        xalloc_die();
+    linbuf1 = (const QChar **)xmalloc(alloc_lines1 * sizeof(*linbuf1));
+
+    LineRef 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
+   <http://www.utm.edu/research/primes/lists/2small/>.  */
+
+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.  */
+
+verify(enough_prime_offsets,
+       sizeof(size_t) * CHAR_BIT <= sizeof prime_offset);
+
+/* 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(struct file_data filevec[], bool /*pretend_binary*/)
+{
+    LineRef i;
+
+    find_identical_ends(filevec);
+
+    equivs_alloc = filevec[0].alloc_lines + filevec[1].alloc_lines + 1;
+    if((LineRef)(LINEREF_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; ((LineRef)1 << i) < equivs_alloc / 3; ++i)
+        continue;
+    nbuckets = ((LineRef)1 << i) - prime_offset[i];
+    if(LINEREF_MAX / sizeof *buckets <= nbuckets)
+        xalloc_die();
+    buckets = (LineRef *)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_system.h b/src/gnudiff_system.h
new file mode 100644 (file)
index 0000000..706b2bd
--- /dev/null
@@ -0,0 +1,66 @@
+/* System dependent declarations.
+
+   Modified for KDiff3 by Joachim Eibl <joachim.eibl at gmx.de> 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.  */
+
+#ifndef GNUDIFF_SYSTEM_H
+#define GNUDIFF_SYSTEM_H
+
+#include <QtGlobal>
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <limits.h>
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Determine whether an integer type is signed, and its bounds.
+   This code assumes two's (or one's!) complement with no holes.  */
+
+/* The extra casts work around common compiler bugs,
+   e.g. Cray C 5.0.3.0 when t == time_t.  */
+#ifndef TYPE_SIGNED
+# define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+#endif
+/* Verify a requirement at compile-time (unlike assert, which is runtime).  */
+#define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; }
+
+#ifndef MIN
+#define MIN(a, b) ((a) <= (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a, b) ((a) >= (b) ? (a) : (b))
+#endif
+
+
+/* The integer type of a line number. */
+
+typedef int LineRef;
+#define LINEREF_MAX INT_MAX
+
+verify(lin_is_signed, TYPE_SIGNED(LineRef));
+//verify(lin_is_wide_enough, sizeof(int) <= sizeof(LineRef));
+
+#endif
diff --git a/src/gnudiff_xmalloc.cpp b/src/gnudiff_xmalloc.cpp
new file mode 100644 (file)
index 0000000..000131d
--- /dev/null
@@ -0,0 +1,79 @@
+/* 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.  */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#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)(void) = nullptr;
+
+void GnuDiff::xalloc_die(void)
+{
+    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
new file mode 100644 (file)
index 0000000..4e75e2a
--- /dev/null
@@ -0,0 +1,134 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUIUTILS_H
+#define GUIUTILS_H
+
+#include <QObject>
+#include <kactioncollection.h>
+
+namespace GuiUtils {
+   //Use std::enable_if since complires don't disabiguate overloads based on return type alone
+   template <class T, class Receiver, class Func>
+      inline typename std::enable_if<std::is_same<T, QAction>::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 <class T, class Receiver, class Func>
+   inline typename std::enable_if<std::is_same<T, KToggleAction>::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 <class T, class Receiver, class Func>
+   T* createAction(
+     const QString& text,
+     const QKeySequence& shortcut,
+     Receiver receiver,
+     Func slot,
+     KActionCollection* ac,
+     const QString &actionName)
+   {
+      T* theAction = createAction<T, Receiver, Func>( text, receiver, slot, ac, actionName );
+      ac->setDefaultShortcut(theAction, shortcut);
+      return theAction;
+   }
+   template <class T, class Receiver, class Func>
+   T* createAction(
+      const QString& text,
+      const QIcon& icon,
+      Receiver receiver,
+      Func slot,
+      KActionCollection* ac,
+      const QString &actionName)
+   {
+      T* theAction = createAction<T, Receiver, Func>( text, receiver, slot, ac, actionName );
+      theAction->setIcon( icon );
+      return theAction;
+   }
+   template <class T, class Receiver, class Func>
+   T* createAction(
+      const QString& text,
+      const QIcon& icon,
+      const QString& iconText,
+      Receiver receiver,
+      Func slot,
+      KActionCollection* ac,
+      const QString &actionName)
+   {
+      T* theAction = createAction<T, Receiver, Func>( text, receiver, slot, ac, actionName );
+      theAction->setIcon( icon );
+      theAction->setIconText( iconText );
+      return theAction;
+   }
+   template <class T, class Receiver, class Func>
+   T* createAction(
+     const QString& text,
+     const QIcon& icon,
+     const QKeySequence& shortcut,
+     Receiver receiver,
+     Func slot,
+     KActionCollection* ac,
+     const QString &actionName)
+   {
+      T* theAction = createAction<T, Receiver, Func>( text, shortcut, receiver, slot, ac, actionName );
+      theAction->setIcon( icon );
+      return theAction;
+   }
+   template <class T, class Receiver, class Func>
+   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<T, Receiver, Func>( text, shortcut, receiver, slot, ac, actionName );
+      theAction->setIcon( icon );
+      theAction->setIconText( iconText );
+      return theAction;
+   }
+}
+
+#endif
diff --git a/src/icons/128-apps-kdiff3.png b/src/icons/128-apps-kdiff3.png
new file mode 100644 (file)
index 0000000..79c0e6e
Binary files /dev/null and b/src/icons/128-apps-kdiff3.png differ
diff --git a/src/icons/16-apps-kdiff3.png b/src/icons/16-apps-kdiff3.png
new file mode 100644 (file)
index 0000000..88820eb
Binary files /dev/null and b/src/icons/16-apps-kdiff3.png differ
diff --git a/src/icons/22-apps-kdiff3.png b/src/icons/22-apps-kdiff3.png
new file mode 100644 (file)
index 0000000..05ca280
Binary files /dev/null and b/src/icons/22-apps-kdiff3.png differ
diff --git a/src/icons/256-apps-kdiff3.png b/src/icons/256-apps-kdiff3.png
new file mode 100644 (file)
index 0000000..89d4b9b
Binary files /dev/null and b/src/icons/256-apps-kdiff3.png differ
diff --git a/src/icons/32-apps-kdiff3.png b/src/icons/32-apps-kdiff3.png
new file mode 100644 (file)
index 0000000..7aa73c1
Binary files /dev/null and b/src/icons/32-apps-kdiff3.png differ
diff --git a/src/icons/48-apps-kdiff3.png b/src/icons/48-apps-kdiff3.png
new file mode 100644 (file)
index 0000000..fd459c7
Binary files /dev/null and b/src/icons/48-apps-kdiff3.png differ
diff --git a/src/icons/64-apps-kdiff3.png b/src/icons/64-apps-kdiff3.png
new file mode 100644 (file)
index 0000000..c869563
Binary files /dev/null and b/src/icons/64-apps-kdiff3.png differ
diff --git a/src/icons/CMakeLists.txt b/src/icons/CMakeLists.txt
new file mode 100644 (file)
index 0000000..66addfe
--- /dev/null
@@ -0,0 +1,13 @@
+ecm_install_icons(
+  ICONS
+    16-apps-kdiff3.png
+    22-apps-kdiff3.png
+    32-apps-kdiff3.png
+    48-apps-kdiff3.png
+    64-apps-kdiff3.png
+    128-apps-kdiff3.png
+    256-apps-kdiff3.png
+    sc-apps-kdiff3.svgz
+  DESTINATION ${KDE_INSTALL_ICONDIR}
+  THEME hicolor
+)
diff --git a/src/icons/sc-apps-kdiff3.svgz b/src/icons/sc-apps-kdiff3.svgz
new file mode 100644 (file)
index 0000000..8fe736b
Binary files /dev/null and b/src/icons/sc-apps-kdiff3.svgz differ
diff --git a/src/kdiff3.cpp b/src/kdiff3.cpp
new file mode 100644 (file)
index 0000000..c0a09e2
--- /dev/null
@@ -0,0 +1,1089 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+// application specific includes
+#include "kdiff3.h"
+
+#include "directorymergewindow.h"
+#include "fileaccess.h"
+#include "guiutils.h" // namespace KDiff3
+#include "kdiff3_part.h"
+#include "kdiff3_shell.h"
+#include "optiondialog.h"
+#include "progress.h"
+#include "smalldialogs.h"
+#include "difftextwindow.h"
+#include "mergeresultwindow.h"
+// include files for QT
+#include <QClipboard>
+#include <QCheckBox>
+#include <QCommandLineParser>
+#include <QDesktopWidget>
+#include <QDir>
+#include <QFileDialog>
+#include <QLabel>
+#include <QLayout>
+#include <QLineEdit>
+#include <QMenu>
+#include <QMenuBar>
+#include <QPaintDevice>
+#include <QPainter>
+#include <QPointer>
+#include <QPrintDialog>
+#include <QPrinter>
+#include <QPushButton>
+#include <QSplitter>
+#include <QStatusBar>
+#include <QUrl>
+// include files for KDE
+#include <KConfig>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <KStandardAction>
+#include <KActionCollection>
+#include <KIconLoader>
+#include <KTextEdit>
+#include <KToggleAction>
+#include <KToolBar>
+
+
+#define ID_STATUS_MSG 1
+#define MAIN_TOOLBAR_NAME QLatin1String("mainToolBar")
+
+void printDiffTextWindow(MyPainter& painter, const QRect& view, const QString& headerText, DiffTextWindow* pDiffTextWindow, int line, int linesPerPage, const QColor& fgColor);
+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<KParts::MainWindow*>(pParent);
+
+    setWindowTitle("KDiff3");
+    setOpaqueResize(false); // faster resizing
+    setUpdatesEnabled(false);
+
+    // 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);
+
+    m_pMainSplitter = nullptr;
+    m_pDirectoryMergeSplitter = nullptr;
+    m_pDirectoryMergeWindow = nullptr;
+    m_pCornerWidget = nullptr;
+    m_pMainWidget = nullptr;
+    m_pDiffTextWindow1 = nullptr;
+    m_pDiffTextWindow2 = nullptr;
+    m_pDiffTextWindow3 = nullptr;
+    m_pDiffTextWindowFrame1 = nullptr;
+    m_pDiffTextWindowFrame2 = nullptr;
+    m_pDiffTextWindowFrame3 = nullptr;
+    m_pDiffWindowSplitter = nullptr;
+    m_pOverview = nullptr;
+    m_bTripleDiff = false;
+    m_pMergeResultWindow = nullptr;
+    m_pMergeWindowFrame = nullptr;
+    m_bOutputModified = false;
+    m_bFileSaved = false;
+    m_bTimerBlock = false;
+    m_pHScrollBar = nullptr;
+    m_pDiffVScrollBar = nullptr;
+    m_pMergeVScrollBar = nullptr;
+    viewToolBar = nullptr;
+    m_bRecalcWordWrapPosted = false;
+    m_bFinishMainInit = false;
+    m_pEventLoopForPrinting = nullptr;
+    m_bLoadFiles = false;
+
+    // Needed before any file operations via FileAccess happen.
+    if(!g_pProgressDialog)
+    {
+        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->m_options;
+
+    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"));
+            QPointer<QDialog> pDialog = QPointer<QDialog>(new QDialog(this));
+            pDialog->setAttribute(Qt::WA_DeleteOnClose);
+            pDialog->setModal(true);
+            pDialog->setWindowTitle(title);
+            QVBoxLayout* pVBoxLayout = new QVBoxLayout(pDialog);
+            QPointer<KTextEdit> pTextEdit = QPointer<KTextEdit>(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)
+            // A windows program has no console
+            printf("%s\n", title.toLatin1().constData());
+            printf("%s\n", s.toLatin1().constData());
+#endif
+            exit(1);
+        }
+    }
+
+    m_sd1.setOptions(m_pOptions);
+    m_sd2.setOptions(m_pOptions);
+    m_sd3.setOptions(m_pOptions);
+
+    m_bAutoFlag = false; //disable --auto option git hard codes this unwanted flag.
+    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)
+            {
+                //KMessageBox::information(this, i18n("Option --auto used, but no output file specified."));
+                fprintf(stderr, "%s\n", (const char*)i18n("Option --auto used, but no output file specified.").toLatin1());
+            }
+            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]);
+        }
+        //never properly defined and redundant
+        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());
+    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->m_bShowStatusBar);
+        slotViewStatusBar();
+
+        KToolBar *mainToolBar = toolBar(MAIN_TOOLBAR_NAME);
+        if(mainToolBar != nullptr){
+            mainToolBar->mainWindow()->addToolBar(m_pOptions->m_toolBarPos, 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);
+
+    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<void (DirectoryMergeWindow::*) (void)>(&DirectoryMergeWindow::updateAvailabilities), this, &KDiff3App::slotUpdateAvailabilities);
+    connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::statusBarMessage, this, &KDiff3App::slotStatusMsg);
+    connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &KDiff3App::slotClipboardChanged);
+    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->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);
+            if(!m_bAutoMode)
+            {
+                //Here we want the extra setup showMaximized does since the window has not be shown before
+                if(m_pOptions->m_bMaximised)
+                    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);
+    }
+
+    bool bSuccess = improveFilenames(false);
+
+    if(m_bAutoFlag && m_bAutoMode && m_bDirCompare)
+    {
+        fprintf(stderr, "%s\n", (const char*)i18n("Option --auto ignored for directory comparison.").toLatin1());
+        m_bAutoMode = false;
+    }
+    if(!m_bDirCompare)
+    {
+        m_pDirectoryMergeSplitter->hide();
+
+        mainInit();
+        if(m_bAutoMode)
+        {
+            SourceData* pSD = nullptr;
+            if(m_sd3.isEmpty()) {
+                if(m_totalDiffStatus->isBinaryEqualAB()) {
+                    pSD = &m_sd1;
+                }
+            }
+            else
+            {
+                if(m_totalDiffStatus->isBinaryEqualBC()) {
+                    pSD = &m_sd3; // B==C (assume A is old)
+                }
+                else if(m_totalDiffStatus->isBinaryEqualAB())
+                {
+                    pSD = &m_sd3; // assuming C has changed
+                }
+                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())
+                {
+                    QString newName = m_outputFilename + ".orig";
+                    if(FileAccess::exists(newName)) FileAccess::removeFile(newName);
+                    if(!FileAccess::exists(newName)) fa.rename(newName);
+                }
+
+                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)
+    {
+        if(m_pOptions->m_bMaximised)
+            //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<QAction>(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"
+    //#include "reload.xpm"
+
+    goCurrent = GuiUtils::createAction<QAction>(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Space), this, &KDiff3App::slotGoCurrent, ac, "go_current");
+
+    goTop = GuiUtils::createAction<QAction>(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, &KDiff3App::slotGoTop, ac, "go_top");
+
+    goBottom = GuiUtils::createAction<QAction>(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.)");
+    goPrevDelta = GuiUtils::createAction<QAction>(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Up), this, &KDiff3App::slotGoPrevDelta, ac, "go_prev_delta");
+    goPrevDelta->setToolTip(goPrevDelta->text() + omitsWhitespace);
+    goNextDelta = GuiUtils::createAction<QAction>(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Down), this, &KDiff3App::slotGoNextDelta, ac, "go_next_delta");
+    goNextDelta->setToolTip(goNextDelta->text() + omitsWhitespace);
+    goPrevConflict = GuiUtils::createAction<QAction>(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageUp), this, &KDiff3App::slotGoPrevConflict, ac, "go_prev_conflict");
+    goPrevConflict->setToolTip(goPrevConflict->text() + omitsWhitespace);
+    goNextConflict = GuiUtils::createAction<QAction>(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageDown), this, &KDiff3App::slotGoNextConflict, ac, "go_next_conflict");
+    goNextConflict->setToolTip(goNextConflict->text() + omitsWhitespace);
+    goPrevUnsolvedConflict = GuiUtils::createAction<QAction>(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), this, &KDiff3App::slotGoPrevUnsolvedConflict, ac, "go_prev_unsolved_conflict");
+    goPrevUnsolvedConflict->setToolTip(goPrevUnsolvedConflict->text() + includeWhitespace);
+    goNextUnsolvedConflict = GuiUtils::createAction<QAction>(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), this, &KDiff3App::slotGoNextUnsolvedConflict, ac, "go_next_unsolved_conflict");
+    goNextUnsolvedConflict->setToolTip(goNextUnsolvedConflict->text() + includeWhitespace);
+    chooseA = GuiUtils::createAction<KToggleAction>(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<KToggleAction>(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<KToggleAction>(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<KToggleAction>(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<KToggleAction>(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18n("White\nCharacters"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace_characters");
+    showWhiteSpace = GuiUtils::createAction<KToggleAction>(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18n("White\nDeltas"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace");
+
+    showLineNumbers = GuiUtils::createAction<KToggleAction>(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18n("Line\nNumbers"), this, &KDiff3App::slotShowLineNumbersToggled, ac, "diff_showlinenumbers");
+    chooseAEverywhere = GuiUtils::createAction<QAction>(i18n("Choose A Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1), this, &KDiff3App::slotChooseAEverywhere, ac, "merge_choose_a_everywhere");
+    chooseBEverywhere = GuiUtils::createAction<QAction>(i18n("Choose B Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2), this, &KDiff3App::slotChooseBEverywhere, ac, "merge_choose_b_everywhere");
+    chooseCEverywhere = GuiUtils::createAction<QAction>(i18n("Choose C Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3), this, &KDiff3App::slotChooseCEverywhere, ac, "merge_choose_c_everywhere");
+    chooseAForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose A for All Unsolved Conflicts"), this, &KDiff3App::slotChooseAForUnsolvedConflicts, ac, "merge_choose_a_for_unsolved_conflicts");
+    chooseBForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose B for All Unsolved Conflicts"), this, &KDiff3App::slotChooseBForUnsolvedConflicts, ac, "merge_choose_b_for_unsolved_conflicts");
+    chooseCForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose C for All Unsolved Conflicts"), this, &KDiff3App::slotChooseCForUnsolvedConflicts, ac, "merge_choose_c_for_unsolved_conflicts");
+    chooseAForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose A for All Unsolved Whitespace Conflicts"), this, &KDiff3App::slotChooseAForUnsolvedWhiteSpaceConflicts, ac, "merge_choose_a_for_unsolved_whitespace_conflicts");
+    chooseBForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose B for All Unsolved Whitespace Conflicts"), this, &KDiff3App::slotChooseBForUnsolvedWhiteSpaceConflicts, ac, "merge_choose_b_for_unsolved_whitespace_conflicts");
+    chooseCForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose C for All Unsolved Whitespace Conflicts"), this, &KDiff3App::slotChooseCForUnsolvedWhiteSpaceConflicts, ac, "merge_choose_c_for_unsolved_whitespace_conflicts");
+    autoSolve = GuiUtils::createAction<QAction>(i18n("Automatically Solve Simple Conflicts"), this, &KDiff3App::slotAutoSolve, ac, "merge_autosolve");
+    unsolve = GuiUtils::createAction<QAction>(i18n("Set Deltas to Conflicts"), this, &KDiff3App::slotUnsolve, ac, "merge_autounsolve");
+    mergeRegExp = GuiUtils::createAction<QAction>(i18n("Run Regular Expression Auto Merge"), this, &KDiff3App::slotRegExpAutoMerge, ac, "merge_regexp_automerge");
+    mergeHistory = GuiUtils::createAction<QAction>(i18n("Automatically Solve History Conflicts"), this, &KDiff3App::slotMergeHistory, ac, "merge_versioncontrol_history");
+    splitDiff = GuiUtils::createAction<QAction>(i18n("Split Diff At Selection"), this, &KDiff3App::slotSplitDiff, ac, "merge_splitdiff");
+    joinDiffs = GuiUtils::createAction<QAction>(i18n("Join Selected Diffs"), this, &KDiff3App::slotJoinDiffs, ac, "merge_joindiffs");
+
+    showWindowA = GuiUtils::createAction<KToggleAction>(i18n("Show Window A"), this, &KDiff3App::slotShowWindowAToggled, ac, "win_show_a");
+    showWindowB = GuiUtils::createAction<KToggleAction>(i18n("Show Window B"), this, &KDiff3App::slotShowWindowBToggled, ac, "win_show_b");
+    showWindowC = GuiUtils::createAction<KToggleAction>(i18n("Show Window C"), this, &KDiff3App::slotShowWindowCToggled, ac, "win_show_c");
+
+    overviewModeNormal = GuiUtils::createAction<KToggleAction>(i18n("Normal Overview"), this, &KDiff3App::slotOverviewNormal, ac, "diff_overview_normal");
+    overviewModeAB = GuiUtils::createAction<KToggleAction>(i18n("A vs. B Overview"), this, &KDiff3App::slotOverviewAB, ac, "diff_overview_ab");
+    overviewModeAC = GuiUtils::createAction<KToggleAction>(i18n("A vs. C Overview"), this, &KDiff3App::slotOverviewAC, ac, "diff_overview_ac");
+    overviewModeBC = GuiUtils::createAction<KToggleAction>(i18n("B vs. C Overview"), this, &KDiff3App::slotOverviewBC, ac, "diff_overview_bc");
+    wordWrap = GuiUtils::createAction<KToggleAction>(i18n("Word Wrap Diff Windows"), this, &KDiff3App::slotWordWrapToggled, ac, "diff_wordwrap");
+    addManualDiffHelp = GuiUtils::createAction<QAction>(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL + Qt::Key_Y), this, &KDiff3App::slotAddManualDiffHelp, ac, "diff_add_manual_diff_help");
+    clearManualDiffHelpList = GuiUtils::createAction<QAction>(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<QAction>(i18n("Focus Next Window"), QKeySequence(Qt::ALT + Qt::Key_Right), this, &KDiff3App::slotWinFocusNext, ac, "win_focus_next");
+    winFocusPrev = GuiUtils::createAction<QAction>(i18n("Focus Prev Window"), QKeySequence(Qt::ALT + Qt::Key_Left), this, &KDiff3App::slotWinFocusPrev, ac, "win_focus_prev");
+    winToggleSplitOrientation = GuiUtils::createAction<QAction>(i18n("Toggle Split Orientation"), this, &KDiff3App::slotWinToggleSplitterOrientation, ac, "win_toggle_split_orientation");
+
+    dirShowBoth = GuiUtils::createAction<KToggleAction>(i18n("Dir && Text Split Screen View"), this, &KDiff3App::slotDirShowBoth, ac, "win_dir_show_both");
+    dirShowBoth->setChecked(true);
+    dirViewToggle = GuiUtils::createAction<QAction>(i18n("Toggle Between Dir && 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->m_bMaximised = m_pKDiff3Shell->isMaximized();
+            if(!m_pKDiff3Shell->isMaximized() && m_pKDiff3Shell->isVisible())
+            {
+                m_pOptions->m_geometry = m_pKDiff3Shell->size();
+                m_pOptions->m_position = 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 directory 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 printDiffTextWindow(MyPainter& painter, const QRect& view, const QString& headerText, DiffTextWindow* pDiffTextWindow, 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(fm.width(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());
+    pDiffTextWindow->print(painter, view, line, linesPerPage);
+    painter.resetMatrix();
+}
+
+void KDiff3App::slotFilePrint()
+{
+    if(m_pDiffTextWindow1 == nullptr)
+        return;
+#ifdef QT_NO_PRINTER
+    slotStatusMsg(i18n("Printing not implemented."));
+#else
+    QPrinter printer;
+    QPointer<QPrintDialog> printDialog=QPointer<QPrintDialog>(new QPrintDialog(&printer, this));
+
+    LineRef firstSelectionD3LIdx = -1;
+    LineRef lastSelectionD3LIdx = -1;
+
+    m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords);
+
+    if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow2) {
+        m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords);
+    }
+    if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow3) {
+        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
+        MyPainter painter(&printer, m_pOptions->m_bRightToLeftLanguage, width(), fontMetrics().width('W'));
+
+        QPaintDevice* pPaintDevice = painter.device();
+        int dpiy = pPaintDevice->logicalDpiY();
+        int columnDistance = (int)((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 = fm.width(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();
+        QEventLoop eventLoopForPrinting;
+        m_pEventLoopForPrinting = &eventLoopForPrinting;
+        if(m_pOptions->m_bWordWrap)
+        {
+            // For printing the lines are wrapped differently (this invalidates the first line)
+            recalcWordWrap(columnWidth);
+            m_pEventLoopForPrinting->exec();
+        }
+
+        LineRef totalNofLines = std::max(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines());
+        if(m_bTripleDiff && m_pDiffTextWindow3)
+            totalNofLines = std::max(totalNofLines, m_pDiffTextWindow3->getNofLines());
+
+        QList<int> pageList; // = printer.pageList();
+
+        bool bPrintCurrentPage = false;
+        bool bFirstPrintedPage = false;
+
+        bool bPrintSelection = false;
+        int totalNofPages = (totalNofLines + linesPerPage - 1) / linesPerPage;
+        LineRef line = -1;
+        LineRef selectionEndLine = -1;
+
+        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<int>::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 >= 0 && 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);
+                printDiffTextWindow(painter, view1, headerText1, m_pDiffTextWindow1, 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);
+                printDiffTextWindow(painter, view2, headerText2, m_pDiffTextWindow2, line, linesPerPage, m_pOptions->m_fgColor);
+
+                if(m_bTripleDiff && m_pDiffTextWindow3)
+                {
+                    painter.setPen(m_pOptions->m_colorC);
+                    QString headerText3 = m_sd3.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1);
+                    printDiffTextWindow(painter, view3, headerText3, m_pDiffTextWindow3, 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() - painter.fontMetrics().width(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));
+        }
+
+        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->m_bShowToolBar = viewToolBar->isChecked();
+    ///////////////////////////////////////////////////////////////////
+    // turn Toolbar on or off
+    if(toolBar(MAIN_TOOLBAR_NAME) != nullptr)
+    {
+        if(!m_pOptions->m_bShowToolBar)
+        {
+            toolBar(MAIN_TOOLBAR_NAME)->hide();
+        }
+        else
+        {
+            toolBar(MAIN_TOOLBAR_NAME)->show();
+        }
+    }
+
+    slotStatusMsg(i18n("Ready."));
+}
+
+void KDiff3App::slotViewStatusBar()
+{
+    slotStatusMsg(i18n("Toggle the statusbar..."));
+    m_pOptions->m_bShowStatusBar = 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
new file mode 100644 (file)
index 0000000..2e451ea
--- /dev/null
@@ -0,0 +1,425 @@
+/***************************************************************************
+ *   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.                                   *
+ ***************************************************************************/
+
+#ifndef KDIFF3_H
+#define KDIFF3_H
+
+#include "diff.h"
+
+// include files for Qt
+#include <QAction>
+#include <QApplication>
+#include <QEventLoop>
+#include <QPointer>
+#include <QScrollBar>
+#include <QSplitter>
+
+// include files for KDE
+#include <KConfigGroup>
+#include <KMainWindow>
+#include <KParts/MainWindow>
+#include <KSharedConfig>
+#include <KToggleAction>
+// 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 && *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 && *m_pbRightToLeftLanguage)
+            m_realVal = maximum() - (i - minimum());
+        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);
+
+  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, const QSharedPointer<TotalDiffStatus> &pTotalDiffStatus);
+
+    void slotFileNameChanged(const QString& fileName, int 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();
+
+    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 slotChooseAEverywhere();
+    void slotChooseBEverywhere();
+    void slotChooseCEverywhere();
+    void slotChooseAForUnsolvedConflicts();
+    void slotChooseBForUnsolvedConflicts();
+    void slotChooseCForUnsolvedConflicts();
+    void slotChooseAForUnsolvedWhiteSpaceConflicts();
+    void slotChooseBForUnsolvedWhiteSpaceConflicts();
+    void slotChooseCForUnsolvedWhiteSpaceConflicts();
+    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 slotEncodingChangedA(QTextCodec*);
+    void slotEncodingChangedB(QTextCodec*);
+    void slotEncodingChangedC(QTextCodec*);
+  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;
+    KToggleAction* viewStatusBar;
+
+    ////////////////////////////////////////////////////////////////////////
+    // Special KDiff3 specific stuff starts here
+    QAction* editFind;
+    QAction* editFindNext;
+
+    QAction* goCurrent;
+    QAction* goTop;
+    QAction* goBottom;
+    QAction* goPrevUnsolvedConflict;
+    QAction* goNextUnsolvedConflict;
+    QAction* goPrevConflict;
+    QAction* goNextConflict;
+    QAction* goPrevDelta;
+    QAction* goNextDelta;
+    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* chooseAEverywhere;
+    QAction* chooseBEverywhere;
+    QAction* chooseCEverywhere;
+    QAction* chooseAForUnsolvedConflicts;
+    QAction* chooseBForUnsolvedConflicts;
+    QAction* chooseCForUnsolvedConflicts;
+    QAction* chooseAForUnsolvedWhiteSpaceConflicts;
+    QAction* chooseBForUnsolvedWhiteSpaceConflicts;
+    QAction* chooseCForUnsolvedWhiteSpaceConflicts;
+    QAction* autoSolve;
+    QAction* unsolve;
+    QAction* mergeHistory;
+    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;
+    QWidget* m_pMainWidget;
+    QWidget* m_pMergeWindowFrame;
+    ReversibleScrollBar* m_pHScrollBar;
+    QScrollBar* m_pDiffVScrollBar;
+    QScrollBar* m_pMergeVScrollBar;
+
+    DiffTextWindow* m_pDiffTextWindow1;
+    DiffTextWindow* m_pDiffTextWindow2;
+    DiffTextWindow* m_pDiffTextWindow3;
+    DiffTextWindowFrame* m_pDiffTextWindowFrame1;
+    DiffTextWindowFrame* m_pDiffTextWindowFrame2;
+    DiffTextWindowFrame* m_pDiffTextWindowFrame3;
+    QSplitter* m_pDiffWindowSplitter;
+
+    MergeResultWindow* m_pMergeResultWindow;
+    WindowTitleWidget* m_pMergeResultWindowTitle;
+    bool m_bTripleDiff;
+
+    QSplitter* m_pDirectoryMergeSplitter;
+    DirectoryMergeWindow* m_pDirectoryMergeWindow;
+    DirectoryMergeInfo* m_pDirectoryMergeInfo;
+    bool m_bDirCompare;
+
+    Overview* m_pOverview;
+
+    QWidget* m_pCornerWidget;
+
+    QSharedPointer<TotalDiffStatus> m_totalDiffStatus;
+
+    SourceData m_sd1;
+    SourceData m_sd2;
+    SourceData m_sd3;
+
+    QString m_outputFilename;
+    bool m_bDefaultFilename;
+
+    DiffList m_diffList12;
+    DiffList m_diffList23;
+    DiffList m_diffList13;
+
+    DiffBufferInfo m_diffBufferInfo;
+    Diff3LineList m_diff3LineList;
+    Diff3LineVector m_diff3LineVector;
+    //ManualDiffHelpDialog* m_pManualDiffHelpDialog;
+    ManualDiffHelpList m_manualDiffHelpList;
+
+    int m_neededLines;
+    int m_DTWHeight;
+    bool m_bOutputModified;
+    bool m_bFileSaved;
+    bool m_bTimerBlock; // Synchronization
+
+    OptionDialog* m_pOptionDialog;
+    Options* m_pOptions;
+    FindDialog* m_pFindDialog;
+
+    void mainInit(QSharedPointer<TotalDiffStatus> pTotalDiffStatus=nullptr, bool bLoadFiles = true, bool bUseCurrentEncoding = false);
+    bool m_bFinishMainInit;
+    bool m_bLoadFiles;
+
+    virtual void wheelEvent(QWheelEvent* pWheelEvent) override;
+    virtual void keyPressEvent(QKeyEvent* event) override;
+    bool eventFilter(QObject* o, QEvent* e) override;
+    void resizeEvent(QResizeEvent*) override;
+
+    bool improveFilenames(bool bCreateNewInstance);
+
+    bool canContinue();
+
+    void choose(int choice);
+
+    KActionCollection* actionCollection();
+    QStatusBar* statusBar();
+    KToolBar* toolBar(QLatin1String);
+    KDiff3Part* m_pKDiff3Part;
+    KParts::MainWindow* m_pKDiff3Shell;
+    bool m_bAutoFlag;
+    bool m_bAutoMode;
+    void recalcWordWrap(int visibleTextWidthForPrinting = -1);
+    bool m_bRecalcWordWrapPosted;
+    void setHScrollBarRange();
+
+    int m_iCumulativeWheelDelta;
+
+    int m_visibleTextWidthForPrinting; // only needed during recalcWordWrap
+    int m_firstD3LIdx;                 // only needed during recalcWordWrap
+    QPointer<QEventLoop> m_pEventLoopForPrinting;
+};
+
+#endif // KDIFF3_H
diff --git a/src/kdiff3.ico b/src/kdiff3.ico
new file mode 100644 (file)
index 0000000..a10847b
Binary files /dev/null and b/src/kdiff3.ico differ
diff --git a/src/kdiff3_part.cpp b/src/kdiff3_part.cpp
new file mode 100644 (file)
index 0000000..c247701
--- /dev/null
@@ -0,0 +1,277 @@
+/***************************************************************************
+ * Copyright (C) 2003-2007 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.          *
+ ***************************************************************************/
+
+#include "kdiff3_part.h"
+#include "fileaccess.h"
+#include "kdiff3.h"
+
+#include <QFile>
+#include <QProcess>
+#include <QTemporaryFile>
+#include <QTextStream>
+
+#include <KAboutData>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <KParts/MainWindow>
+
+#include <version.h>
+
+static KAboutData 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 <aurelien.gateau@free.fr>"));
+    aboutData.addAuthor(i18n("Joachim Eibl"), QString(), QString("joachim.eibl at gmx.de"));
+    return aboutData;
+}
+
+K_PLUGIN_FACTORY(KDiff3PartFactory, registerPlugin<KDiff3Part>();)
+//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<KParts::MainWindow*>(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);
+*/
+}
+
+static void 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());
+            if(FileAccess(fileName).exists()) break;
+            --pos2;
+        }
+
+        int vpos = str.lastIndexOf("\t", -1);
+        if(vpos > 0 && vpos > (int)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());
+    QFile file(localFilePath());
+    if(!file.open(QIODevice::ReadOnly))
+        return false;
+
+    // our example widget is text-based, so we use QTextStream instead
+    // of a raw QDataStream
+    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() <<"<->"<<tempFileName.toLatin1()<< std::endl;
+    }
+    else if(version2.isEmpty() && f2.exists())
+    {
+        // Reverse patch
+        // patch -f -u -R --ignore-whitespace -i [inputfile] -o [outfile] [patchfile]
+        QTemporaryFile tmpFile;
+        FileAccess::createTempFile(tmpFile);
+        QString tempFileName = tmpFile.fileName();
+        QString cmd = "patch -f -u -R --ignore-whitespace -i \"" + localFilePath() +
+                      "\" -o \"" + tempFileName + "\" \"" + fileName2 + "\"";
+
+        QProcess process;
+        process.start(cmd);
+        process.waitForFinished(-1);
+
+        m_widget->slotFileOpen2(tempFileName, fileName2, "", "",
+                                version1.isEmpty() ? fileName1 : "REV:" + version1 + ':' + fileName1, "", "", nullptr); // alias name
+                                                                                                                  //    std::cerr << "KDiff3: f2:" << fileName2.toLatin1() <<"<->"<<tempFileName.toLatin1()<< std::endl;
+    }
+    else if(!version1.isEmpty() && !version2.isEmpty())
+    {
+        fprintf(stderr, "KDiff3: f1/2:%s<->%s\n", fileName1.toLatin1().constData(), fileName2.toLatin1().constData());
+        // 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() <<"<->"<<tempFileName2.toLatin1()<< std::endl;
+        return true;
+    }
+    else
+    {
+        KMessageBox::sorry(m_widget, i18n("Could not find files for comparison."));
+    }
+
+    return true;
+}
+
+bool KDiff3Part::saveFile()
+{
+    /*    // if we aren't read-write, return immediately
+    if (isReadWrite() == false)
+        return false;
+
+    // localFilePath() is always local, so we use QFile
+    QFile file(localFilePath());
+    if (file.open(IO_WriteOnly) == false)
+        return false;
+
+    // use QTextStream to dump the text to the file
+    QTextStream stream(&file);
+    //stream << m_widget->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
new file mode 100644 (file)
index 0000000..ae18dff
--- /dev/null
@@ -0,0 +1,80 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
+ ***************************************************************************/
+
+#ifndef KDIFF3PART_H
+#define KDIFF3PART_H
+
+#include <kparts/part.h>
+#include <KPluginFactory>
+#include <KParts/ReadWritePart>
+
+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 <joachim.eibl at gmx.de>
+ */
+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:
+    KDiff3App* m_widget;
+    bool m_bIsShell;
+};
+
+#endif // _KDIFF3PART_H_
diff --git a/src/kdiff3_part.rc b/src/kdiff3_part.rc
new file mode 100644 (file)
index 0000000..72ae4c4
--- /dev/null
@@ -0,0 +1,24 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kdiff3_part" version="9">
+<MenuBar>
+  <Menu name="movement"><text>&amp;KDiff3</text>
+    <Action name="go_top"/>
+    <Action name="go_bottom"/>
+    <Action name="go_prev_delta"/>
+    <Action name="go_next_delta"/>
+    <Action name="diff_showwhitespace"/>
+    <Action name="diff_showlinenumbers"/>
+    <Action name="diff_wordwrap"/>
+    <Action name="win_toggle_split_orientation"/>
+    <Action name="options_configure"><text>Configure KDiff3</text></Action>
+  </Menu>
+</MenuBar>
+<ToolBar name="mainToolBar"><text>KDiff3</text>
+  <Action name="go_top"/>
+  <Action name="go_bottom"/>
+  <Action name="go_prev_delta"/>
+  <Action name="go_next_delta"/>
+  <Action name="diff_showwhitespace"/>
+  <Action name="diff_showlinenumbers"/>
+</ToolBar>
+</kpartgui>
diff --git a/src/kdiff3_shell.cpp b/src/kdiff3_shell.cpp
new file mode 100644 (file)
index 0000000..7325b3f
--- /dev/null
@@ -0,0 +1,172 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ *   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.          *
+ ***************************************************************************/
+
+#include "kdiff3_shell.h"
+#include "kdiff3.h"
+#include "kdiff3_part.h"
+
+#include <QApplication>
+#include <QCloseEvent>
+#include <QStatusBar>
+
+#include <KConfig>
+#include <KEditToolBar>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <KShortcutsDialog>
+#include <KStandardAction>
+#include <KToolBar>
+
+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<KPluginMetaData> plugin_offers = KPluginLoader::findPlugins( "kf5/kdiff3part" );
+    foreach( const KPluginMetaData & service, plugin_offers ) {
+        KPluginFactory *factory = KPluginLoader( service.fileName() ).factory();
+        m_part = factory->create<KDiff3Part>( this, QVariantList() << QVariant( QLatin1String( "KDiff3Part" ) ) );
+        if( m_part )
+            break;
+    }*/
+
+    m_part = new KDiff3Part(this, this, QVariantList() << QVariant(QLatin1String("KDiff3Part")));
+    m_widget = qobject_cast<KDiff3App*>(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
new file mode 100644 (file)
index 0000000..f58935a
--- /dev/null
@@ -0,0 +1,83 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
+ ***************************************************************************/
+
+#ifndef KDIFF3SHELL_H
+#define KDIFF3SHELL_H
+
+#include <KParts/MainWindow>
+#include <QCommandLineParser>
+#include <QString>
+
+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 <joachim.eibl at gmx.de>
+ */
+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/kdiff3_shell.rc b/src/kdiff3_shell.rc
new file mode 100644 (file)
index 0000000..241ac5b
--- /dev/null
@@ -0,0 +1,128 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kdiff3_shell" version="9">
+<MenuBar>
+  <Menu name="file"><text>&amp;File</text>
+    <Action name="file_reload"/>
+  </Menu>
+  <Menu name="directory"><text>&amp;Directory</text>
+    <Action name="dir_start_operation"/>
+    <Action name="dir_run_operation_for_current_item"/>
+    <Action name="dir_compare_current"/>
+    <Action name="dir_rescan"/>
+    <!-- <Action name="dir_save_merge_state"/>
+    <Action name="dir_load_merge_state"/> -->
+    <Action name="dir_fold_all"/>
+    <Action name="dir_unfold_all"/>
+    <Action name="dir_show_identical_files"/>
+    <Action name="dir_show_different_files"/>
+    <Action name="dir_show_files_only_in_a"/>
+    <Action name="dir_show_files_only_in_b"/>
+    <Action name="dir_show_files_only_in_c"/>
+    <Action name="dir_choose_a_everywhere"/>
+    <Action name="dir_choose_b_everywhere"/>
+    <Action name="dir_choose_c_everywhere"/>
+    <Action name="dir_autochoose_everywhere"/>
+    <Action name="dir_nothing_everywhere"/>
+    <Action name="dir_synchronize_directories"/>
+    <Action name="dir_choose_newer_files"/>
+    <Action name="dir_compare_explicitly_selected_files"/>
+    <Action name="dir_merge_explicitly_selected_files"/>
+    <Menu name="dir_current_merge_menu"><text>Current Item Merge Operation</text>
+       <Action name="dir_current_do_nothing"/>
+       <Action name="dir_current_choose_a"/>
+       <Action name="dir_current_choose_b"/>
+       <Action name="dir_current_choose_c"/>
+       <Action name="dir_current_merge"/>
+       <Action name="dir_current_delete"/>
+    </Menu>
+    <Menu name="dir_current_sync_menu"><text>Current Item Sync Operation</text>
+       <Action name="dir_current_sync_do_nothing"/>
+       <Action name="dir_current_sync_copy_a_to_b"/>
+       <Action name="dir_current_sync_copy_b_to_a"/>
+       <Action name="dir_current_sync_delete_a"/>
+       <Action name="dir_current_sync_delete_b"/>
+       <Action name="dir_current_sync_delete_a_and_b"/>
+       <Action name="dir_current_sync_merge_to_a"/>
+       <Action name="dir_current_sync_merge_to_b"/>
+       <Action name="dir_current_sync_merge_to_a_and_b"/>
+    </Menu>
+  </Menu>
+  <Menu name="movement"><text>M&amp;ovement</text>
+    <Action name="go_current"/>
+    <Action name="go_top"/>
+    <Action name="go_bottom"/>
+    <Action name="go_prev_delta"/>
+    <Action name="go_next_delta"/>
+    <Action name="go_prev_conflict"/>
+    <Action name="go_next_conflict"/>
+    <Action name="go_prev_unsolved_conflict"/>
+    <Action name="go_next_unsolved_conflict"/>
+  </Menu>
+  <Menu name="diff"><text>D&amp;iffview</text>
+    <Action name="diff_showlinenumbers"/>
+    <Action name="diff_show_whitespace_characters"/>
+    <Action name="diff_show_whitespace"/>
+    <Action name="diff_overview_normal"/>
+    <Action name="diff_overview_ab"/>
+    <Action name="diff_overview_ac"/>
+    <Action name="diff_overview_bc"/>
+    <Action name="diff_wordwrap"/>
+    <Action name="diff_add_manual_diff_help"/>
+    <Action name="diff_clear_manual_diff_help_list"/>
+  </Menu>  
+  <Menu name="merge"><text>M&amp;erge</text>
+    <Action name="merge_current"/>
+    <Action name="merge_choose_a"/>
+    <Action name="merge_choose_b"/>
+    <Action name="merge_choose_c"/>
+    <Action name="merge_autoadvance"/>
+    <Action name="merge_choose_a_everywhere"/>
+    <Action name="merge_choose_b_everywhere"/>
+    <Action name="merge_choose_c_everywhere"/>
+    <Action name="merge_choose_a_for_unsolved_conflicts"/>
+    <Action name="merge_choose_b_for_unsolved_conflicts"/>
+    <Action name="merge_choose_c_for_unsolved_conflicts"/>
+    <Action name="merge_choose_a_for_unsolved_whitespace_conflicts"/>
+    <Action name="merge_choose_b_for_unsolved_whitespace_conflicts"/>
+    <Action name="merge_choose_c_for_unsolved_whitespace_conflicts"/>
+    <Action name="merge_autosolve"/>
+    <Action name="merge_autounsolve"/>
+    <Action name="merge_splitdiff"/>
+    <Action name="merge_joindiffs"/>
+    <Action name="merge_regexp_automerge"/>
+    <Action name="merge_versioncontrol_history"/>
+  </Menu>
+  <Menu name="window"><text>&amp;Window</text>
+    <Action name="win_focus_prev"/>
+    <Action name="win_focus_next"/>
+    <Action name="win_show_a"/>
+    <Action name="win_show_b"/>
+    <Action name="win_show_c"/>
+    <Action name="win_dir_show_both"/>
+    <Action name="win_dir_view_toggle"/>
+    <Action name="win_toggle_split_orientation"/>
+  </Menu>
+</MenuBar>
+<ToolBar name="mainToolBar" iconText="icononly" fullWidth="true"><text>Main Toolbar</text>
+  <Action name="merge_current"/>
+  <Action name="go_current"/>
+  <Action name="go_top"/>
+  <Action name="go_bottom"/>
+  <Action name="go_prev_delta"/>
+  <Action name="go_next_delta"/>
+  <Action name="go_prev_conflict"/>
+  <Action name="go_next_conflict"/>
+  <Action name="go_prev_unsolved_conflict"/>
+  <Action name="go_next_unsolved_conflict"/>
+  <Action name="merge_choose_a"/>
+  <Action name="merge_choose_b"/>
+  <Action name="merge_choose_c"/>
+  <Action name="merge_autoadvance"/>
+  <Action name="diff_show_whitespace"/>
+  <Action name="diff_show_whitespace_characters"/>
+  <Action name="diff_showlinenumbers"/>
+  <Action name="dir_show_identical_files"/>
+  <Action name="dir_show_files_only_in_a"/>
+  <Action name="dir_show_files_only_in_b"/>
+</ToolBar>
+</kpartgui>
diff --git a/src/kdiff3part.desktop b/src/kdiff3part.desktop
new file mode 100644 (file)
index 0000000..124b1e0
--- /dev/null
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=KDiff3Part
+Name[bg]=KDiff3Part
+Name[bs]=KDiff3Part
+Name[ca]=Part del KDiff3
+Name[ca@valencia]=Part del KDiff3
+Name[cs]=KDiff3Part
+Name[da]=KDiff3Part
+Name[de]=KDiff3Part
+Name[el]=KDiff3Part
+Name[en_GB]=KDiff3Part
+Name[es]=KDiff3Part
+Name[et]=KDiff3 komponent
+Name[eu]=KDiff3Part
+Name[fi]=KDiff3Part
+Name[fr]=Module KDiff3
+Name[ga]=KDiff3Part
+Name[gl]=KDiff3Part
+Name[hi]=के-डिफ3पार्ट
+Name[hne]=के-डिफ3पार्ट
+Name[hu]=KDiff3Part
+Name[it]=KDiff3Part
+Name[ja]=KDiff3Part
+Name[km]=KDiff3Part
+Name[ko]=KDiff3Part
+Name[lt]=KDiff3Part
+Name[ml]=കെഡിഫ്3ഭാഗം
+Name[mr]=के-डिफ3-पार्ट
+Name[nb]=KDiff3Part
+Name[nds]=KDiff3Part
+Name[nl]=KDiff3Part
+Name[nn]=KDiff3Part
+Name[pl]=Moduł KDiff3
+Name[pt]=KDiff3Part
+Name[pt_BR]=KDiff3Part
+Name[ro]=KDiff3Part
+Name[ru]=KDiff3Part
+Name[sk]=KDiff3Part
+Name[sl]=KDiff3Part
+Name[sr]=К‑диф3 део
+Name[sr@ijekavian]=К‑диф3 део
+Name[sr@ijekavianlatin]=KDiff3 deo
+Name[sr@latin]=KDiff3 deo
+Name[sv]=Kdiff3-del
+Name[tr]=KDiff3Part
+Name[ug]=KDiff3Part
+Name[uk]=KDiff3Part
+Name[x-test]=xxKDiff3Partxx
+Name[zh_TW]=KDiff3Part
+ServiceTypes=KParts/ReadOnlyPart,KParts/ReadWritePart
+X-KDE-Library=kf5/parts/libkdiff3part
+Type=Service
diff --git a/src/kdiff3win.rc b/src/kdiff3win.rc
new file mode 100644 (file)
index 0000000..5b38078
--- /dev/null
@@ -0,0 +1 @@
+IDI_ICON1               ICON    DISCARDABLE     "kdiff3.ico"
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644 (file)
index 0000000..ec38997
--- /dev/null
@@ -0,0 +1,182 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+#include "kdiff3_shell.h"
+#include <version.h>
+
+#include <KAboutData>
+#include <KCrash/KCrash>
+#include <KLocalizedString>
+#include <KMessageBox>
+
+#include <QApplication>
+#include <QByteArray>
+#include <QCommandLineOption>
+#include <QCommandLineParser>
+#include <QFile>
+#include <QStringList>
+#include <QStandardPaths>
+#include <QTextStream>
+
+
+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())
+    {
+        QStringList ignorableOptions = sl.front().split(';');
+        for(QStringList::iterator i = ignorableOptions.begin(); i != ignorableOptions.end(); ++i)
+        {
+            (*i).remove('-');
+            if(!(*i).isEmpty())
+            {
+                if(i->length() == 1) {
+                    cmdLineParser->addOption(QCommandLineOption(QStringList() << *i << QLatin1String("ignore"), i18n("Ignored. (User defined.)")));
+                }
+                else
+                {
+                    cmdLineParser->addOption(QCommandLineOption(QStringList() << *i, 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 Directories");
+    const QString copyright = i18n("(c) 2002-2014 Joachim Eibl, (c) 2017 Michael Reeves KF5/Qt5 port");
+    const QString homePage = QString("");
+    const QString bugsAddress = QStringLiteral("reeves.87""@""gmail.com");
+
+    KAboutData aboutData(appName, i18nName,
+                         appVersion, description, KAboutLicense::GPL_V2, copyright, description,
+                         homePage, bugsAddress);
+
+    KAboutData::setApplicationData(aboutData);
+
+    QCommandLineParser* cmdLineParser = KDiff3Shell::getParser();
+    cmdLineParser->setApplicationDescription(aboutData.shortDescription());
+    cmdLineParser->addVersionOption();
+    cmdLineParser->addHelpOption();
+
+    aboutData.setupCommandLine(cmdLineParser);
+
+    initialiseCmdLineArgs(cmdLineParser);
+    // ignorable command options
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("m") << QLatin1String("merge"), i18n("Merge the input.")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("b") << QLatin1String("base"), i18n("Explicit base file. For compatibility with certain tools."), QLatin1String("file")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("o") << QLatin1String("output"), i18n("Output file. Implies -m. E.g.: -o newfile.txt"), QLatin1String("file")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("out"), i18n("Output file, again. (For compatibility with certain tools.)"), QLatin1String("file")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("auto"), i18n("No GUI if all conflicts are auto-solvable. (Needs -o file)")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("qall"), i18n("Do not solve conflicts automatically.")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("L1"), i18n("Visible name replacement for input file 1 (base)."), QLatin1String("alias1")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("L2"), i18n("Visible name replacement for input file 2."), QLatin1String("alias2")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("L3"), i18n("Visible name replacement for input file 3."), QLatin1String("alias3")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("L") << QLatin1String("fname alias"), i18n("Alternative visible name replacement. Supply this once for every input.")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("cs"), i18n("Override a config setting. Use once for every setting. E.g.: --cs \"AutoAdvance=1\""), QLatin1String("string")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("confighelp"), i18n("Show list of config settings and current values.")));
+    cmdLineParser->addOption(QCommandLineOption(QStringList() << 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"));
+
+    /*
+        Don't use QCommandLineParser::process as it auto terminates the program if an option is not recognized.
+        Further more errors are directed to the console alone if not running on windows. This makes for a bad
+        user experience when run from a graphical interface such as kde. Don't assume that this only happens
+        when running from a commandline.
+    */
+    if(!cmdLineParser->parse(QCoreApplication::arguments())) {
+        QString errorMessage = cmdLineParser->errorText();
+        QString helpText = cmdLineParser->helpText();
+        KMessageBox::error(nullptr, "<html><head/><body><h2>" + errorMessage + "</h2><pre>" + i18n("See kdiff3 --help for supported options.") + "</pre></body></html>", aboutData.displayName());
+#if !defined(Q_OS_WIN)
+        fputs(qPrintable(errorMessage), stderr);
+        fputs("\n\n", stderr);
+        fputs(qPrintable(helpText + '\n'), stderr);
+        fputs("\n", stderr);
+#endif
+        exit(1);
+    }
+
+    if(cmdLineParser->isSet(QStringLiteral("version"))) {
+        KMessageBox::information(nullptr,
+                                 aboutData.displayName() + ' ' + aboutData.version(), aboutData.displayName());
+#if !defined(Q_OS_WIN)
+        printf("%s %s\n", appName.data(), appVersion.toLocal8Bit().constData());
+#endif
+        exit(0);
+    }
+    if(cmdLineParser->isSet(QStringLiteral("help"))) {
+        KMessageBox::information(nullptr, "<html><head/><body><pre>" + cmdLineParser->helpText() + "</pre></body></html>", aboutData.displayName());
+#if !defined(Q_OS_WIN)
+        fputs(qPrintable(cmdLineParser->helpText()), stdout);
+#endif
+        exit(0);
+    }
+
+    aboutData.processCommandLine(cmdLineParser);
+    /**
+     * take component name and org. name from KAboutData
+     */
+    app.setApplicationName(aboutData.componentName());
+    app.setApplicationDisplayName(aboutData.displayName());
+    app.setOrganizationDomain(aboutData.organizationDomain());
+    app.setApplicationVersion(aboutData.version());
+
+    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 = app.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
new file mode 100644 (file)
index 0000000..807cb4d
--- /dev/null
@@ -0,0 +1,77 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "merger.h"
+
+#include <list>
+
+Merger::Merger(const DiffList* pDiffList1, const DiffList* pDiffList2)
+    : md1(pDiffList1, 0), md2(pDiffList2, 1)
+{
+}
+
+Merger::MergeData::MergeData(const DiffList* p, int i)
+    : d(0, 0, 0)
+{
+    idx = i;
+    pDiffList = p;
+    if(p != nullptr)
+    {
+        it = p->begin();
+        update();
+    }
+}
+
+bool Merger::MergeData::eq()
+{
+    return pDiffList == nullptr || d.nofEquals > 0;
+}
+
+bool Merger::MergeData::isEnd()
+{
+    return (pDiffList == nullptr || (it == pDiffList->end() && d.nofEquals == 0 &&
+                               (idx == 0 ? d.diff1 == 0 : d.diff2 == 0)));
+}
+
+void Merger::MergeData::update()
+{
+    if(d.nofEquals > 0)
+        --d.nofEquals;
+    else if(idx == 0 && d.diff1 > 0)
+        --d.diff1;
+    else if(idx == 1 && d.diff2 > 0)
+        --d.diff2;
+
+    while(d.nofEquals == 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();
+}
+
+int Merger::whatChanged()
+{
+    int changed = 0;
+    changed |= md1.eq() ? 0 : 1;
+    changed |= md2.eq() ? 0 : 2;
+    return changed;
+}
+
+bool Merger::isEndReached()
+{
+    return md1.isEnd() && md2.isEnd();
+}
diff --git a/src/merger.h b/src/merger.h
new file mode 100644 (file)
index 0000000..20ed09b
--- /dev/null
@@ -0,0 +1,55 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#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.
+   */
+   int whatChanged();
+
+   /** End of both diff lists reached. */
+   bool isEndReached();
+private:
+
+   struct MergeData
+   {
+      DiffList::const_iterator it;
+      const DiffList* pDiffList;
+      Diff d;
+      int idx;
+
+      MergeData( const DiffList* p, int i );
+      bool eq();
+      void update();
+      bool isEnd();
+   };
+
+   MergeData md1;
+   MergeData md2;
+};
+
+#endif
diff --git a/src/mergeresultwindow.cpp b/src/mergeresultwindow.cpp
new file mode 100644 (file)
index 0000000..8a5a10f
--- /dev/null
@@ -0,0 +1,3610 @@
+/***************************************************************************
+                          mergeresultwindow.cpp  -  description
+                             -------------------
+    begin                : Sun Apr 14 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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "mergeresultwindow.h"
+#include "options.h"
+
+#include <QApplication>
+#include <QClipboard>
+#include <QComboBox>
+#include <QCursor>
+#include <QDir>
+#include <QFile>
+#include <QHBoxLayout>
+#include <QKeyEvent>
+#include <QLabel>
+#include <QLineEdit>
+#include <QtMath>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QRegExp>
+#include <QStatusBar>
+#include <QTextCodec>
+#include <QTextLayout>
+#include <QTextStream>
+#include <QUrl>
+
+#include <QDropEvent>
+#include <QEvent>
+#include <QFocusEvent>
+#include <QInputEvent>
+#include <QMimeData>
+#include <QPaintEvent>
+#include <QPixmap>
+#include <QResizeEvent>
+#include <QTimerEvent>
+#include <QWheelEvent>
+
+#include <KLocalizedString>
+#include <KMessageBox>
+
+int g_bAutoSolve = true;
+
+#undef leftInfoWidth
+
+MergeResultWindow::MergeResultWindow(
+    QWidget* pParent,
+    Options* pOptions,
+    QStatusBar* pStatusBar)
+    : QWidget(pParent)
+{
+    setObjectName("MergeResultWindow");
+    setFocusPolicy(Qt::ClickFocus);
+
+    m_firstLine = 0;
+    m_horizScrollOffset = 0;
+    m_nofLines = 0;
+    m_totalSize = 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)
+        connect(m_pStatusBar, &QStatusBar::messageChanged, this, &MergeResultWindow::slotStatusMessageChanged);
+
+    m_pOptions = pOptions;
+    m_bPaintingAllowed = 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 LineData* pLineDataA, LineRef sizeA,
+    const LineData* pLineDataB, LineRef sizeB,
+    const LineData* pLineDataC, LineRef sizeC,
+    const Diff3LineList* pDiff3LineList,
+    const QSharedPointer<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, -1);
+    g_bAutoSolve = true;
+    update();
+    updateSourceMask();
+
+    showUnsolvedConflictsStatusMessage();
+}
+
+void MergeResultWindow::showUnsolvedConflictsStatusMessage()
+{
+    if(m_pStatusBar)
+    {
+        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::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 mergeOneLine(
+    const Diff3Line& d, e_MergeDetails& mergeDetails, bool& bConflict,
+    bool& bLineRemoved, int& src, bool bTwoInputs)
+{
+    mergeDetails = eDefault;
+    bConflict = false;
+    bLineRemoved = false;
+    src = 0;
+
+    if(bTwoInputs) // Only two input files
+    {
+        if(d.lineA != -1 && d.lineB != -1)
+        {
+            if(d.pFineAB == nullptr)
+            {
+                mergeDetails = eNoChange;
+                src = A;
+            }
+            else
+            {
+                mergeDetails = eBChanged;
+                bConflict = true;
+            }
+        }
+        else
+        {
+            if(d.lineA != -1 && d.lineB == -1)
+            {
+                mergeDetails = eBDeleted;
+                bConflict = true;
+            }
+            else if(d.lineA == -1 && d.lineB != -1)
+            {
+                mergeDetails = eBDeleted;
+                bConflict = true;
+            }
+        }
+        return;
+    }
+
+    // A is base.
+    if(d.lineA != -1 && d.lineB != -1 && d.lineC != -1)
+    {
+        if(d.pFineAB == nullptr && d.pFineBC == nullptr && d.pFineCA == nullptr)
+        {
+            mergeDetails = eNoChange;
+            src = A;
+        }
+        else if(d.pFineAB == nullptr && d.pFineBC != nullptr && d.pFineCA != nullptr)
+        {
+            mergeDetails = eCChanged;
+            src = C;
+        }
+        else if(d.pFineAB != nullptr && d.pFineBC != nullptr && d.pFineCA == nullptr)
+        {
+            mergeDetails = eBChanged;
+            src = B;
+        }
+        else if(d.pFineAB != nullptr && d.pFineBC == nullptr && d.pFineCA != nullptr)
+        {
+            mergeDetails = eBCChangedAndEqual;
+            src = C;
+        }
+        else if(d.pFineAB != nullptr && d.pFineBC != nullptr && d.pFineCA != nullptr)
+        {
+            mergeDetails = eBCChanged;
+            bConflict = true;
+        }
+        else
+            Q_ASSERT(true);
+    }
+    else if(d.lineA != -1 && d.lineB != -1 && d.lineC == -1)
+    {
+        if(d.pFineAB != nullptr)
+        {
+            mergeDetails = eBChanged_CDeleted;
+            bConflict = true;
+        }
+        else
+        {
+            mergeDetails = eCDeleted;
+            bLineRemoved = true;
+            src = C;
+        }
+    }
+    else if(d.lineA != -1 && d.lineB == -1 && d.lineC != -1)
+    {
+        if(d.pFineCA != nullptr)
+        {
+            mergeDetails = eCChanged_BDeleted;
+            bConflict = true;
+        }
+        else
+        {
+            mergeDetails = eBDeleted;
+            bLineRemoved = true;
+            src = B;
+        }
+    }
+    else if(d.lineA == -1 && d.lineB != -1 && d.lineC != -1)
+    {
+        if(d.pFineBC != nullptr)
+        {
+            mergeDetails = eBCAdded;
+            bConflict = true;
+        }
+        else // B==C
+        {
+            mergeDetails = eBCAddedAndEqual;
+            src = C;
+        }
+    }
+    else if(d.lineA == -1 && d.lineB == -1 && d.lineC != -1)
+    {
+        mergeDetails = eCAdded;
+        src = C;
+    }
+    else if(d.lineA == -1 && d.lineB != -1 && d.lineC == -1)
+    {
+        mergeDetails = eBAdded;
+        src = B;
+    }
+    else if(d.lineA != -1 && d.lineB == -1 && d.lineC == -1)
+    {
+        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->bAEqC == ml2.id3l->bAEqC && ml1.id3l->bAEqB == ml2.id3l->bAEqB;
+    }
+    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, int 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();
+        m_totalSize = 0;
+        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;
+            mergeOneLine(d, ml.mergeDetails, ml.bConflict, bLineRemoved, ml.srcSelect, m_pldC == nullptr);
+
+            // Automatic solving for only whitespace changes.
+            if(ml.bConflict &&
+               ((m_pldC == nullptr && (d.bAEqB || (d.bWhiteLineA && d.bWhiteLineB))) ||
+                (m_pldC != nullptr && ((d.bAEqB && d.bAEqC) || (d.bWhiteLineA && d.bWhiteLineB && d.bWhiteLineC)))))
+            {
+                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
+            {
+                ml.mergeEditLineList.setTotalSizePtr(&m_totalSize);
+                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 != 0) // Only two inputs
+        {
+            defaultSelector = m_pOptions->m_whiteSpace2FileMergeDefault;
+            bWhiteSpaceOnly = true;
+            bSolveWhiteSpaceConflicts = true;
+        }
+        else if(m_pldC != nullptr && m_pOptions->m_whiteSpace3FileMergeDefault != 0)
+        {
+            defaultSelector = 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 == -1 && 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 == 1 ? d3llit->lineA : defaultSelector == 2 ? d3llit->lineB : defaultSelector == 3 ? d3llit->lineC : -1;
+
+                        if(srcLine != -1)
+                        {
+                            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 = -1;
+        int oldSrc = -1;
+        MergeEditLineList::iterator melIt;
+        for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();)
+        {
+            MergeEditLine& mel = *melIt;
+            int melsrc = mel.src();
+
+            LineRef srcLine = mel.isRemoved() ? -1 : melsrc == 1 ? mel.id3l()->lineA : melsrc == 2 ? mel.id3l()->lineB : melsrc == 3 ? mel.id3l()->lineC : -1;
+
+            // At least one line remains because oldSrc != melsrc for first line in list
+            // Other empty lines will be removed
+            if(srcLine == -1 && oldSrcLine == -1 && 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())
+            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();
+
+    emit updateAvailabilities();
+    update();
+}
+
+void MergeResultWindow::setFirstLine(int 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(this);
+
+                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()
+{
+    return m_totalSize;
+}
+
+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 * fm.width('0');
+}
+
+void MergeResultWindow::resizeEvent(QResizeEvent* e)
+{
+    QWidget::resizeEvent(e);
+    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(int 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->bBinaryAEqB && m_pTotalDiffStatus->bBinaryAEqC)
+        totalInfo += i18n("All input files are binary equal.");
+    else if(m_pTotalDiffStatus->bTextAEqB && m_pTotalDiffStatus->bTextAEqC)
+        totalInfo += i18n("All input files contain the same text.");
+    else
+    {
+        if(m_pTotalDiffStatus->bBinaryAEqB)
+            totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B"));
+        else if(m_pTotalDiffStatus->bTextAEqB)
+            totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("B"));
+        if(m_pTotalDiffStatus->bBinaryAEqC)
+            totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C"));
+        else if(m_pTotalDiffStatus->bTextAEqC)
+            totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("C"));
+        if(m_pTotalDiffStatus->bBinaryBEqC)
+            totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C"));
+        else if(m_pTotalDiffStatus->bTextBEqC)
+            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;
+    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)
+    {
+        emit scrollMergeResultWindow(0, newFirstLine - m_firstLine);
+    }
+
+    if(m_selection.isEmpty())
+    {
+        m_cursorXPos = 0;
+        m_cursorOldXPixelPos = 0;
+        m_cursorYPos = line1;
+    }
+
+    update();
+    updateSourceMask();
+    emit updateAvailabilities();
+}
+
+void MergeResultWindow::choose(int 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() == 1 ? mel.id3l()->lineA : mel.src() == 2 ? mel.id3l()->lineB : mel.src() == 3 ? mel.id3l()->lineC : -1;
+
+            if(srcLine == -1)
+                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_totalSize)
+    {
+        m_cursorYPos = m_totalSize - 1;
+        m_cursorXPos = 0;
+    }
+
+    m_maxTextWidth = -1;
+    update();
+    updateSourceMask();
+    emit updateAvailabilities();
+    showUnsolvedConflictsStatusMessage();
+}
+
+// bConflictsOnly: automatically choose for conflicts only (true) or for everywhere (false)
+void MergeResultWindow::chooseGlobal(int selector, bool bConflictsOnly, bool bWhiteSpaceOnly)
+{
+    resetSelection();
+
+    merge(false, selector, bConflictsOnly, bWhiteSpaceOnly);
+    setModified(true);
+    update();
+    showUnsolvedConflictsStatusMessage();
+}
+
+void MergeResultWindow::slotAutoSolve()
+{
+    resetSelection();
+    merge(true, -1);
+    setModified(true);
+    update();
+    showUnsolvedConflictsStatusMessage();
+}
+
+void MergeResultWindow::slotUnsolve()
+{
+    resetSelection();
+    merge(false, -1);
+    setModified(true);
+    update();
+    showUnsolvedConflictsStatusMessage();
+}
+
+static QString calcHistoryLead(const QString& s)
+{
+    // Return the start of the line until the first white char after the first non white char.
+    int i;
+    for(i = 0; i < s.length(); ++i)
+    {
+        if(s[i] != ' ' && s[i] != '\t')
+        {
+            for(; i < s.length(); ++i)
+            {
+                if(s[i] == ' ' || s[i] == '\t')
+                {
+                    return s.left(i);
+                }
+            }
+            return s; // Very unlikely
+        }
+    }
+    return QString(); // Must be an empty string, not a null string.
+}
+
+static void findHistoryRange(const QRegExp& historyStart, bool bThreeFiles, const Diff3LineList* pD3LList,
+                             Diff3LineList::const_iterator& iBegin, Diff3LineList::const_iterator& iEnd, int& idxBegin, int& idxEnd)
+{
+    QString historyLead;
+    // Search for start of history
+    for(iBegin = pD3LList->begin(), idxBegin = 0; iBegin != pD3LList->end(); ++iBegin, ++idxBegin)
+    {
+        if(historyStart.exactMatch(iBegin->getString(A)) &&
+           historyStart.exactMatch(iBegin->getString(B)) &&
+           (!bThreeFiles || historyStart.exactMatch(iBegin->getString(C))))
+        {
+            historyLead = calcHistoryLead(iBegin->getString(A));
+            break;
+        }
+    }
+    // Search for end of history
+    for(iEnd = iBegin, idxEnd = idxBegin; iEnd != pD3LList->end(); ++iEnd, ++idxEnd)
+    {
+        QString sA = iEnd->getString(A);
+        QString sB = iEnd->getString(B);
+        QString sC = iEnd->getString(C);
+        if(!((sA.isEmpty() || historyLead == calcHistoryLead(sA)) &&
+             (sB.isEmpty() || historyLead == calcHistoryLead(sB)) &&
+             (!bThreeFiles || sC.isEmpty() || historyLead == calcHistoryLead(sC))))
+        {
+            break; // End of the history
+        }
+    }
+}
+
+bool findParenthesesGroups(const QString& s, QStringList& sl)
+{
+    sl.clear();
+    int i = 0;
+    std::list<int> 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)
+{
+    QStringList keyOrderList = keyOrder.split(',');
+    QString key;
+    for(QStringList::iterator keyIt = keyOrderList.begin(); keyIt != keyOrderList.end(); ++keyIt)
+    {
+        if((*keyIt).isEmpty())
+            continue;
+        bool bOk = false;
+        int groupIdx = (*keyIt).toInt(&bOk);
+        if(!bOk || groupIdx < 0 || groupIdx > (int)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.sprintf("%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.sprintf("%02d", idx + 1); // Up to 99 words in the groupRegExp (more than 12 aren't expected)
+                key += sIdx + ' ';
+            }
+        }
+    }
+    return key;
+}
+
+void MergeResultWindow::collectHistoryInformation(
+    int src, Diff3LineList::const_iterator &iHistoryBegin, Diff3LineList::const_iterator &iHistoryEnd,
+    HistoryMap& historyMap,
+    std::list<HistoryMap::iterator>& hitList // list of iterators
+)
+{
+    std::list<HistoryMap::iterator>::iterator itHitListFront = hitList.begin();
+    Diff3LineList::const_iterator id3l = iHistoryBegin;
+    QString historyLead;
+    {
+        const LineData* pld = id3l->getLineData(src);
+        QString s(pld->pLine, pld->size);
+        historyLead = calcHistoryLead(s);
+    }
+    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;
+        QString s(pld->pLine, pld->size);
+        if(historyLead.isEmpty()) historyLead = calcHistoryLead(s);
+        QString sLine = s.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<HistoryMap::iterator, bool> 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(s))
+        {
+            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<HistoryMap::iterator, bool> 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
+}
+
+MergeResultWindow::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
+    findHistoryRange(QRegExp(m_pOptions->m_historyStartRegExp), m_pldC != nullptr, m_pDiff3LineList, iD3LHistoryBegin, iD3LHistoryEnd, d3lHistoryBeginLineIdx, d3lHistoryEndLineIdx);
+
+    if(iD3LHistoryBegin != m_pDiff3LineList->end())
+    {
+        // Now collect the historyMap information
+        HistoryMap historyMap;
+        std::list<HistoryMap::iterator> 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 = 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<HistoryMap::iterator>::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(this);
+                QString firstLineOfEnd = iMLLEnd->mergeEditLineList.front().getString(this);
+                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
+MergeResultWindow::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);
+        emit scrollMergeResultWindow(m_scrollDeltaX, m_scrollDeltaY);
+        killTimer(m_delayedDrawTimer);
+        m_delayedDrawTimer = startTimer(50);
+    }
+}
+
+QString MergeResultWindow::MergeEditLine::getString(const MergeResultWindow* mrw)
+{
+    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.lineA != -1)
+            pld = &mrw->m_pldA[d3l.lineA];
+        else if(src == B && d3l.lineB != -1)
+            pld = &mrw->m_pldB[d3l.lineB];
+        else if(src == C && d3l.lineC != -1)
+            pld = &mrw->m_pldC[d3l.lineC];
+
+        //Not an error.
+        if(pld == nullptr)
+        {
+            return QString();
+        }
+
+        return QString(pld->pLine, pld->size);
+    }
+    else
+    {
+        return m_str;
+    }
+    return QString();
+}
+
+/// Converts the cursor-posOnScreen into a text index, considering tabulators.
+int convertToPosInText(const QString& /*s*/, int posOnScreen, int /*tabSize*/)
+{
+    return posOnScreen;
+}
+
+//   int localPosOnScreen = 0;
+//   int size=s.length();
+//   for ( int i=0; i<size; ++i )
+//   {
+//      if ( localPosOnScreen>=posOnScreen )
+//         return i;
+
+//      // All letters except tabulator have width one.
+//      int letterWidth = s[i]!='\t' ? 1 : tabber( localPosOnScreen, tabSize );
+
+//      localPosOnScreen += letterWidth;
+
+//      if ( localPosOnScreen>posOnScreen )
+//         return i;
+//   }
+//   return size;
+//}
+
+/// Converts the index into the text to a cursor-posOnScreen considering tabulators.
+int convertToPosOnScreen(const QString& /*p*/, int posInText, int /*tabSize*/)
+{
+    return posInText;
+}
+//   int posOnScreen = 0;
+//   for ( int i=0; i<posInText; ++i )
+//   {
+//      // All letters except tabulator have width one.
+//      int letterWidth = p[i]!='\t' ? 1 : tabber( posOnScreen, tabSize );
+
+//      posOnScreen += letterWidth;
+//   }
+//   return posOnScreen;
+//}
+
+QVector<QTextLayout::FormatRange> 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<QTextLayout::FormatRange> formats;
+        QTextLayout::FormatRange formatRange;
+        formatRange.start = 0;
+        formatRange.length = str.length();
+        formatRange.format.setFont(font());
+        formats.append(formatRange);
+        textLayout.setFormats(formats);
+    }
+    QVector<QTextLayout::FormatRange> selectionFormat;
+    textLayout.beginLayout();
+    if(m_selection.lineWithin(line))
+    {
+        int firstPosInText = convertToPosInText(str, m_selection.firstPosInLine(line), m_pOptions->m_tabSize);
+        int lastPosInText = convertToPosInText(str, m_selection.lastPosInLine(line), m_pOptions->m_tabSize);
+        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(
+    MyPainter& 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<QTextLayout::FormatRange> 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("<No src line>"));
+        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("<Merge Conflict (Whitespace only)>"));
+        else
+            p.drawText(xOffset, yOffset + fontAscent, i18n("<Merge Conflict>"));
+        p.drawText(1, yOffset + fontAscent, "?");
+        if(m_cursorYPos == line) m_cursorXPos = 0;
+    }
+    else
+        Q_ASSERT(true);
+
+    xOffset -= fm.width('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)
+{
+    m_bPaintingAllowed = bPaintingAllowed;
+    if(!m_bPaintingAllowed)
+    {
+        m_currentMergeLineIt = m_mergeLineList.end();
+        reset();
+    }
+    update();
+}
+
+void MergeResultWindow::paintEvent(QPaintEvent*)
+{
+    if(m_pDiff3LineList == nullptr || !m_bPaintingAllowed)
+        return;
+
+    bool bOldSelectionContainsData = m_selection.selectionContainsData();
+    const QFontMetrics& fm = fontMetrics();
+    int fontWidth = fm.width('0');
+
+    if(!m_bCursorUpdate) // Don't redraw everything for blinking cursor?
+    {
+        m_selection.bSelectionContainsData = false;
+        if(size() != m_pixmap.size())
+            m_pixmap = QPixmap(size());
+
+        MyPainter 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;
+        int 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(this);
+
+                        writeLine(p, line, s, mel.src(), ml.mergeDetails, rangeMark,
+                                  mel.isModified(), mel.isRemoved(), ml.bWhiteSpaceConflict);
+                    }
+                    ++line;
+                }
+            }
+        }
+
+        if(line != m_nofLines)
+        {
+            m_nofLines = line;
+            Q_ASSERT(m_nofLines == m_totalSize);
+
+            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())
+        emit newSelection();
+}
+
+void MergeResultWindow::updateSourceMask()
+{
+    int srcMask = 0;
+    int enabledMask = 0;
+    if(!hasFocus() || m_pDiff3LineList == nullptr || !m_bPaintingAllowed || 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() == 1) srcMask |= 1;
+            if(mel.src() == 2) srcMask |= 2;
+            if(mel.src() == 3) srcMask |= 4;
+            if(mel.isModified() || !mel.isEditableText()) bModified = true;
+        }
+
+        if(ml.mergeDetails == eNoChange)
+        {
+            srcMask = 0;
+            enabledMask = bModified ? 1 : 0;
+        }
+    }
+
+    emit sourceMask(srcMask, enabledMask);
+}
+
+void MergeResultWindow::focusInEvent(QFocusEvent* e)
+{
+    updateSourceMask();
+    QWidget::focusInEvent(e);
+}
+
+int MergeResultWindow::convertToLine(int y)
+{
+    const QFontMetrics& fm = fontMetrics();
+    int fontHeight = fm.lineSpacing();
+    int topLineYOffset = 0;
+
+    int yOffset = topLineYOffset - m_firstLine * fontHeight;
+
+    int line = std::min((y - yOffset) / fontHeight, m_totalSize - 1);
+    return line;
+}
+
+void MergeResultWindow::mousePressEvent(QMouseEvent* e)
+{
+    m_bCursorOn = true;
+
+    int xOffset = getTextXOffset();
+
+    int 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());
+
+    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(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)
+        {
+            emit showPopupMenu(QCursor::pos());
+        }
+    }
+    else if(bLMB) // Normal cursor placement
+    {
+        pos = std::max(pos, 0);
+        line = std::max(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(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)
+    {
+        int 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;
+
+            calcTokenPos(s, pos, pos1, pos2, m_pOptions->m_tabSize);
+
+            resetSelection();
+            m_selection.start(line, convertToPosOnScreen(s, pos1, m_pOptions->m_tabSize));
+            m_selection.end(line, convertToPosOnScreen(s, pos2, m_pOptions->m_tabSize));
+
+            update();
+            // 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())
+        {
+            emit selectionEnd();
+        }
+    }
+}
+
+void MergeResultWindow::mouseMoveEvent(QMouseEvent* e)
+{
+    int 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 = fm.width('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)
+        {
+            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();
+    emit scrollMergeResultWindow(0, std::min(d, getNofVisibleLines()));
+}
+
+bool MergeResultWindow::event(QEvent* e)
+{
+    if(e->type() == QEvent::KeyPress)
+    {
+        QKeyEvent* ke = static_cast<QKeyEvent*>(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(this);
+    int x = convertToPosInText(str, m_cursorXPos, m_pOptions->m_tabSize);
+
+    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:
+        break;
+    //case  Key_Tab:          break;
+    case Qt::Key_Backtab:
+        break;
+    case Qt::Key_Delete:
+    {
+        if(deleteSelection2(str, x, y, mlIt, melIt)) break;
+        if(!melIt->isEditableText()) break;
+        if(x >= (int)str.length())
+        {
+            if(y < m_totalSize - 1)
+            {
+                setModified();
+                MergeLineList::iterator mlIt1;
+                MergeEditLineList::iterator melIt1;
+                calcIteratorFromLineNr(y + 1, mlIt1, melIt1);
+                if(melIt1->isEditableText())
+                {
+                    QString s2 = melIt1->getString(this);
+                    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(this);
+                    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(this);
+                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 < (int)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:
+        break;
+    case Qt::Key_Print:
+        break;
+    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_totalSize - 1)
+                {
+                    ++y;
+                    x = 0;
+                }
+                else
+                {
+                    x = newX;
+                }
+            }
+            else
+            {
+                while(x < (int)str.length() && (str[x] == ' ' || str[x] == '\t'))
+                {
+                    int newX = textLayoutOrig.nextCursorPosition(x);
+                    if(newX == x) break;
+                    x = newX;
+                }
+                while(x < (int)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 = minMaxLimiter(y, 0, m_totalSize - 1);
+
+    calcIteratorFromLineNr(y, mlIt, melIt);
+    str = melIt->getString(this);
+
+    x = minMaxLimiter(x, 0, (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());
+        emit resizeSignal();
+    }
+    if(!bYMoveKey)
+        m_cursorOldXPixelPos = m_cursorXPixelPos;
+
+    m_bCursorOn = true;
+    m_cursorTimer.start(500);
+
+    update();
+    if(newFirstLine != m_firstLine || newHorizScrollOffset != m_horizScrollOffset)
+    {
+        emit scrollMergeResultWindow(newHorizScrollOffset - m_horizScrollOffset, newFirstLine - m_firstLine);
+        return;
+    }
+}
+
+void MergeResultWindow::calcIteratorFromLineNr(
+    int line,
+    MergeResultWindow::MergeLineList::iterator& mlIt,
+    MergeResultWindow::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(this);
+
+                    // 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("<Merge Conflict>");
+                }
+
+                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(this);
+        x = convertToPosInText(s, m_cursorXPos, m_pOptions->m_tabSize);
+        return true;
+    }
+
+    return false;
+}
+
+void MergeResultWindow::deleteSelection()
+{
+    if(!m_selection.selectionContainsData())
+    {
+        return;
+    }
+    Q_ASSERT(m_selection.isValidFirstLine());
+
+    setModified();
+
+    int line = 0;
+    MergeLineList::iterator mlItFirst;
+    MergeEditLineList::iterator melItFirst;
+    QString firstLineString;
+
+    int firstLine = -1;
+    int lastLine = -1;
+
+    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 == -1)
+                    firstLine = line;
+                lastLine = line;
+            }
+
+            ++line;
+        }
+    }
+
+    if(firstLine == -1)
+    {
+        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(this);
+
+                int firstPosInLine = m_selection.firstPosInLine(line);
+                int lastPosInLine = m_selection.lastPosInLine(line);
+
+                if(line == firstLine)
+                {
+                    mlItFirst = mlIt;
+                    melItFirst = melIt;
+                    int pos = convertToPosInText(lineString, firstPosInLine, m_pOptions->m_tabSize);
+                    firstLineString = lineString.left(pos);
+                }
+
+                if(line == lastLine)
+                {
+                    // This is the last line in the selection
+                    int pos = convertToPosInText(lineString, lastPosInLine, m_pOptions->m_tabSize);
+                    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(this);
+    int x = convertToPosInText(str, m_cursorXPos, m_pOptions->m_tabSize);
+
+    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 = convertToPosOnScreen(currentLine, x, m_pOptions->m_tabSize);
+    m_cursorOldXPixelPos = m_cursorXPixelPos;
+
+    update();
+}
+
+void MergeResultWindow::resetSelection()
+{
+    m_selection.reset();
+    update();
+}
+
+void MergeResultWindow::setModified(bool bModified)
+{
+    if(bModified != m_bModified)
+    {
+        m_bModified = bModified;
+        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(this);
+
+                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)
+{
+    MergeResultWindow::MergeLineList::iterator mlIt;
+    MergeResultWindow::MergeEditLineList::iterator melIt;
+    calcIteratorFromLineNr(lineIdx, mlIt, melIt);
+    QString s = melIt->getString(this);
+    return s;
+}
+
+bool MergeResultWindow::findString(const QString& s, int& 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, convertToPosOnScreen(getString(firstLine), startPos, m_pOptions->m_tabSize));
+    m_selection.end(lastLine, convertToPosOnScreen(getString(lastLine), endPos, m_pOptions->m_tabSize));
+    update();
+}
+
+Overview::Overview(Options* pOptions)
+//: QWidget( pParent, 0, Qt::WNoAutoErase )
+{
+    m_pDiff3LineList = nullptr;
+    m_pOptions = pOptions;
+    m_bTripleDiff = false;
+    m_eOverviewMode = eOMNormal;
+    m_nofLines = 1;
+    m_bPaintingAllowed = 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(int firstLine, int pageHeight)
+{
+    m_firstLine = firstLine;
+    m_pageHeight = pageHeight;
+    update();
+}
+void Overview::setFirstLine(int 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)
+        emit setLine((e->y() - h1 / 2) * m_nofLines / h);
+}
+
+void Overview::mouseMoveEvent(QMouseEvent* e)
+{
+    mousePressEvent(e);
+}
+
+void Overview::setPaintingAllowed(bool bAllowPainting)
+{
+    if(m_bPaintingAllowed != bAllowPainting)
+    {
+        m_bPaintingAllowed = bAllowPainting;
+        if(m_bPaintingAllowed)
+            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;
+        int src;
+        mergeOneLine(d3l, 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.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB);
+                break;
+
+            case eCAdded:
+            case eCDeleted:
+            case eCChanged:
+                bWhiteSpaceChange = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC);
+                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.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB);
+                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.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC);
+                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.bBEqC || (d3l.bWhiteLineB && d3l.bWhiteLineC);
+                break;
+            }
+        }
+
+        int x2 = x;
+        int w2 = w;
+
+        if(!m_bTripleDiff)
+        {
+            if(d3l.lineA == -1 && d3l.lineB >= 0)
+            {
+                c = m_pOptions->m_colorA;
+                x2 = w / 2;
+                w2 = x2;
+            }
+            if(d3l.lineA >= 0 && d3l.lineB == -1)
+            {
+                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 || !m_bPaintingAllowed) return;
+    int h = height() - 1;
+    int w = width();
+
+    if(m_pixmap.size() != size())
+    {
+        if(m_pOptions->m_bWordWrap)
+        {
+            m_nofLines = 0;
+            Diff3LineList::const_iterator i;
+            for(i = m_pDiff3LineList->begin(); i != m_pDiff3LineList->end(); ++i)
+            {
+                m_nofLines += i->linesNeededForDisplay;
+            }
+        }
+        else
+        {
+            m_nofLines = m_pDiff3LineList->size();
+        }
+
+        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 = h * m_firstLine / m_nofLines - 1;
+    int h1 = h * m_pageHeight / m_nofLines + 3;
+    painter.setPen(Qt::black);
+    painter.drawRect(1, y1, w - 1, h1);
+}
+
+WindowTitleWidget::WindowTitleWidget(Options* 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 QLineEdit();
+    pHLayout->addWidget(m_pFileNameLineEdit, 6);
+    m_pFileNameLineEdit->installEventFilter(this);
+    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<QString, QTextCodec*> names;
+    QList<int> mibs = QTextCodec::availableMibs();
+    foreach(int i, mibs)
+    {
+        QTextCodec* c = QTextCodec::codecForMib(i);
+        if(c != nullptr)
+            names[QLatin1String(c->name())] = c;
+    }
+
+    if(pCodecForA)
+        m_pEncodingSelector->addItem(i18n("Codec from A: %1", QLatin1String(pCodecForA->name())), QVariant::fromValue((void*)pCodecForA));
+    if(pCodecForB)
+        m_pEncodingSelector->addItem(i18n("Codec from B: %1", QLatin1String(pCodecForB->name())), QVariant::fromValue((void*)pCodecForB));
+    if(pCodecForC)
+        m_pEncodingSelector->addItem(i18n("Codec from C: %1", QLatin1String(pCodecForC->name())), QVariant::fromValue((void*)pCodecForC));
+
+    std::map<QString, QTextCodec*>::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 && pCodecForB && pCodecForA)
+    {
+        if(pCodecForA == pCodecForB)
+            m_pEncodingSelector->setCurrentIndex(2); // C
+        else if(pCodecForA == pCodecForC)
+            m_pEncodingSelector->setCurrentIndex(1); // B
+        else
+            m_pEncodingSelector->setCurrentIndex(2); // C
+    }
+    else if(pCodecForA && pCodecForB)
+        m_pEncodingSelector->setCurrentIndex(1); // B
+    else
+        m_pEncodingSelector->setCurrentIndex(0);
+}
+
+QTextCodec* WindowTitleWidget::getEncoding()
+{
+    return (QTextCodec*)m_pEncodingSelector->itemData(m_pEncodingSelector->currentIndex()).value<void*>();
+}
+
+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)
+{
+    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);
+    }
+    if(o == m_pFileNameLineEdit && e->type() == QEvent::Drop)
+    {
+        QDropEvent* d = static_cast<QDropEvent*>(e);
+
+        if(d->mimeData()->hasUrls())
+        {
+            QList<QUrl> lst = d->mimeData()->urls();
+
+            if(lst.count() > 0)
+            {
+                static_cast<QLineEdit*>(o)->setText(lst[0].toString());
+                static_cast<QLineEdit*>(o)->setFocus();
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+//#include "mergeresultwindow.moc"
diff --git a/src/mergeresultwindow.h b/src/mergeresultwindow.h
new file mode 100644 (file)
index 0000000..6a30bd9
--- /dev/null
@@ -0,0 +1,455 @@
+/***************************************************************************
+ *   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.                                   *
+ ***************************************************************************/
+
+#ifndef MERGERESULTWINDOW_H
+#define MERGERESULTWINDOW_H
+
+#include "diff.h"
+#include "selection.h"
+
+#include <QWidget>
+#include <QPixmap>
+#include <QTimer>
+#include <QStatusBar>
+#include <QTextLayout>
+
+class QPainter;
+
+class Overview : public QWidget
+{
+   Q_OBJECT
+public:
+   explicit Overview( Options* pOptions );
+
+   void init( Diff3LineList* pDiff3LineList, bool bTripleDiff );
+   void reset();
+   void setRange( int firstLine, int pageHeight );
+   void setPaintingAllowed( bool bAllowPainting );
+
+   enum e_OverviewMode { eOMNormal, eOMAvsB, eOMAvsC, eOMBvsC };
+   void setOverviewMode( e_OverviewMode eOverviewMode );
+   e_OverviewMode getOverviewMode();
+
+public Q_SLOTS:
+   void setFirstLine(int firstLine);
+   void slotRedraw();
+Q_SIGNALS:
+   void setLine(int);
+private:
+   const Diff3LineList* m_pDiff3LineList;
+   Options* m_pOptions;
+   bool m_bTripleDiff;
+   int m_firstLine;
+   int m_pageHeight;
+   QPixmap m_pixmap;
+   bool m_bPaintingAllowed;
+   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 );
+};
+
+
+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
+};
+
+void mergeOneLine( const Diff3Line& d, e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, int& src, bool bTwoInputs );
+
+enum e_MergeSrcSelector
+{
+   A=1,
+   B=2,
+   C=3
+};
+
+class MergeResultWindow : public QWidget
+{
+   Q_OBJECT
+public:
+   MergeResultWindow(QWidget* pParent, Options* pOptions, QStatusBar* pStatusBar);
+
+   void init(
+      const LineData* pLineDataA, LineRef sizeA,
+      const LineData* pLineDataB, LineRef sizeB,
+      const LineData* pLineDataC, LineRef sizeC,
+      const Diff3LineList* pDiff3LineList,
+      const QSharedPointer<TotalDiffStatus>& pTotalDiffStatus
+      );
+
+   void reset();
+
+   bool saveDocument( const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle );
+   int getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts=nullptr);
+   void choose(int selector);
+   void chooseGlobal(int selector, bool bConflictsOnly, bool bWhiteSpaceOnly );
+
+   int getMaxTextWidth();     // width of longest text line
+   int getNofLines();
+   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, int& 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();
+public Q_SLOTS:
+   void setFirstLine(int 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( int firstD3lLineIdx, int lastD3lLineIdx );
+   void slotJoinDiffs( int firstD3lLineIdx, int lastD3lLineIdx );
+   void slotSetFastSelectorLine(int);
+   void setPaintingAllowed(bool);
+   void updateSourceMask();
+   void slotStatusMessageChanged( const QString& );
+
+Q_SIGNALS:
+   void scrollMergeResultWindow( int deltaX, int deltaY );
+   void modifiedChanged(bool bModified);
+   void setFastSelectorRange( int line1, int 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, int defaultSelector, bool bConflictsOnly=false, bool bWhiteSpaceOnly=false );
+   QString getString( int lineIdx );
+
+   Options* m_pOptions;
+
+   const LineData* m_pldA;
+   const LineData* m_pldB;
+   const LineData* m_pldC;
+   LineRef m_sizeA;
+   LineRef m_sizeB;
+   LineRef m_sizeC;
+
+   const Diff3LineList* m_pDiff3LineList;
+   QSharedPointer<TotalDiffStatus> m_pTotalDiffStatus;
+
+   bool m_bPaintingAllowed;
+   int m_delayedDrawTimer;
+   Overview::e_OverviewMode m_eOverviewMode;
+   QString m_persistentStatusMessage;
+   void showUnsolvedConflictsStatusMessage();
+
+private:
+   class MergeEditLine
+   {
+   public:
+      explicit MergeEditLine(const Diff3LineList::const_iterator &i, int src=0){m_id3l=i; m_src=src; m_bLineRemoved=false; }
+      void setConflict() { m_src=0; m_bLineRemoved=false; m_str=QString(); }
+      bool isConflict()  { return  m_src==0 && !m_bLineRemoved && m_str.isEmpty(); }
+      void setRemoved(int src=0)  { m_src=src; m_bLineRemoved=true; m_str=QString(); }
+      bool isRemoved()   { return m_bLineRemoved; }
+      bool isEditableText() { return !isConflict() && !isRemoved(); }
+      void setString( const QString& s ){ m_str=s; m_bLineRemoved=false; m_src=0; }
+      QString getString( const MergeResultWindow* );
+      bool isModified() { return ! m_str.isEmpty() ||  (m_bLineRemoved && m_src==0); }
+
+      void setSource( int src, bool bLineRemoved ) { m_src=src; m_bLineRemoved =bLineRemoved; }
+      int src() { return m_src; }
+      Diff3LineList::const_iterator id3l(){return m_id3l;}
+      // getString() is implemented as MergeResultWindow::getString()
+   private:
+      Diff3LineList::const_iterator m_id3l;
+      int 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 : private std::list<MergeEditLine>
+   { // I want to know the size immediately!
+   private:
+      typedef std::list<MergeEditLine> BASE;
+      int m_size;
+      int* m_pTotalSize;
+   public:
+      typedef std::list<MergeEditLine>::iterator iterator;
+      typedef std::list<MergeEditLine>::reverse_iterator reverse_iterator;
+      typedef std::list<MergeEditLine>::const_iterator const_iterator;
+      MergeEditLineList(){m_size=0; m_pTotalSize=nullptr; }
+      void clear()                             { ds(-m_size); BASE::clear();          }
+      void push_back( const MergeEditLine& m)  { ds(+1); BASE::push_back(m);     }
+      void push_front( const MergeEditLine& m) { ds(+1); BASE::push_front(m);    }
+      void pop_back()                          { ds(-1); BASE::pop_back();    }
+      iterator erase( iterator i )             { ds(-1); return BASE::erase(i);  }
+      iterator insert( iterator i, const MergeEditLine& m ) { ds(+1); return BASE::insert(i,m); }
+      int size(){ if (!m_pTotalSize) m_size = (int) BASE::size(); return m_size; }
+      iterator begin(){return BASE::begin();}
+      iterator end(){return BASE::end();}
+      reverse_iterator rbegin(){return BASE::rbegin();}
+      reverse_iterator rend(){return BASE::rend();}
+      MergeEditLine& front(){return BASE::front();}
+      MergeEditLine& back(){return BASE::back();}
+      bool empty() { return m_size==0; }
+      void splice(iterator destPos, MergeEditLineList& srcList, iterator srcFirst, iterator srcLast)
+      {
+         int* pTotalSize = getTotalSizePtr() ? getTotalSizePtr() : srcList.getTotalSizePtr();
+         srcList.setTotalSizePtr(nullptr); // Force size-recalc after splice, because splice doesn't handle size-tracking
+         setTotalSizePtr(nullptr);
+         BASE::splice( destPos, srcList, srcFirst, srcLast );
+         srcList.setTotalSizePtr( pTotalSize );
+         setTotalSizePtr( pTotalSize );
+      }
+
+      void setTotalSizePtr(int* pTotalSize)
+      {
+         if ( pTotalSize==nullptr && m_pTotalSize!=nullptr ) { *m_pTotalSize -= size(); }
+         else if ( pTotalSize!=nullptr && m_pTotalSize==nullptr ) { *pTotalSize += size(); }
+         m_pTotalSize = pTotalSize;
+      }
+      int* getTotalSizePtr()
+      {
+         return m_pTotalSize;
+      }
+
+   private:
+      void ds(int deltaSize)
+      {
+         m_size+=deltaSize;
+         if (m_pTotalSize!=nullptr)  *m_pTotalSize+=deltaSize;
+      }
+   };
+
+   friend class MergeEditLine;
+
+   struct MergeLine
+   {
+      MergeLine()
+      {
+         srcSelect=0; mergeDetails=eDefault; d3lLineIdx = -1; srcRangeLength=0;
+         bConflict=false; bDelta=false; bWhiteSpaceConflict=false;
+      }
+      Diff3LineList::const_iterator id3l;
+      int d3lLineIdx;  // Needed to show the correct window pos.
+      int srcRangeLength; // how many src-lines have this properties
+      e_MergeDetails mergeDetails;
+      bool bConflict;
+      bool bWhiteSpaceConflict;
+      bool bDelta;
+      int srcSelect;
+      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.setTotalSizePtr( mergeEditLineList.getTotalSizePtr() );
+         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;
+      }
+   };
+
+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<QString,HistoryMapEntry> HistoryMap;
+   void collectHistoryInformation( int src, Diff3LineList::const_iterator &iHistoryBegin, Diff3LineList::const_iterator &iHistoryEnd, HistoryMap& historyMap, std::list< HistoryMap::iterator >& hitList );
+
+   typedef std::list<MergeLine> MergeLineList;
+   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<QTextLayout::FormatRange> getTextLayoutForLine(int line, const QString& s, QTextLayout& textLayout );
+   void myUpdate(int afterMilliSecs);
+   void timerEvent(QTimerEvent*) override;
+   void writeLine(
+      MyPainter& p, int line, const QString& str,
+      int srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict
+      );
+   void setFastSelector(MergeLineList::iterator i);
+   int convertToLine( int 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;
+   int m_firstLine;
+   int m_horizScrollOffset;
+   int m_nofLines;
+   int m_totalSize; //Same as m_nofLines, but calculated differently
+   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;
+   QLineEdit*   m_pFileNameLineEdit;
+   //QPushButton* m_pBrowseButton;
+   QLabel*      m_pModifiedLabel;
+   QLabel*      m_pLineEndStyleLabel;
+   QComboBox*   m_pLineEndStyleSelector;
+   QLabel*      m_pEncodingLabel;
+   QComboBox*   m_pEncodingSelector;
+   Options*     m_pOptions;
+public:
+   explicit WindowTitleWidget(Options* 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
new file mode 100644 (file)
index 0000000..d666481
--- /dev/null
@@ -0,0 +1,1793 @@
+/*
+ *   kdiff3 - Text Diff And Merge Tool
+ *   Copyright (C) 2002-2009  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.
+ *
+ */
+#include "optiondialog.h"
+#include "OptionItems.h"
+
+#include "diff.h"
+#include "smalldialogs.h"
+
+#include <QApplication>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QDir>
+#include <QFontDatabase>
+#include <QFontDialog>
+#include <QFrame>
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QLabel>
+#include <QLayout>
+#include <QLineEdit>
+#include <QLocale>
+#include <QPointer>
+#include <QPixmap>
+#include <QPlainTextEdit>
+#include <QPushButton>
+#include <QRadioButton>
+#include <QSettings>
+#include <QTextCodec>
+#include <QToolTip>
+
+#include <KColorButton>
+#include <KConfigGroup>
+#include <KHelpClient>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <KSharedConfig>
+#include <KToolBar>
+#include <map>
+
+#define KDIFF3_CONFIG_GROUP "KDiff3 Options"
+
+QString s_historyEntryStartRegExpToolTip;
+QString s_historyEntryStartSortKeyOrderToolTip;
+QString s_autoMergeRegExpToolTip;
+QString s_historyStartRegExpToolTip;
+
+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()); }
+
+    void apply() override { OptionBool::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()); }
+
+    void apply() override { OptionBool::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); }
+    void apply() override { OptionFont::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()); }
+    void apply() override { OptionColor::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());
+    }
+    void apply() override
+    {
+        OptionString::apply(currentText());
+        insertText();
+    }
+    void write(ValueMap* config) 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<int>
+{
+  public:
+    OptionIntEdit(int defaultVal, const QString& saveName, int* pVar, int rangeMin, int rangeMax,
+                  QWidget* pParent)
+        : QLineEdit(pParent), OptionNum<int>(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());
+    }
+    void apply() override
+    {
+        const QIntValidator* v = static_cast<const QIntValidator*>(validator());
+        setCurrent( minMaxLimiter(text().toInt(), v->bottom(), 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);
+    }
+    void apply() override
+    {
+        if(m_pVarNum != nullptr) {
+            *m_pVarNum = currentIndex();
+        }
+        else
+        {
+            *m_pVarStr = currentText();
+        }
+    }
+    void write(ValueMap* config) 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<QTextCodec*> 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<QString, QTextCodec*> names;
+        QList<int> mibs = QTextCodec::availableMibs();
+        foreach(int i, mibs)
+        {
+            QTextCodec* c = QTextCodec::codecForMib(i);
+            if(c != nullptr)
+                names[QString(QLatin1String(c->name())).toUpper()] = c;
+        }
+
+        std::map<QString, QTextCodec*>::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)
+        {
+            QString codecName = QLatin1String(c->name());
+            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 + QStringLiteral(" (") + codecName + QStringLiteral(")");
+            addItem(itemText, (int)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;
+                }
+            }
+        }
+    }
+    void apply() override
+    {
+        if(m_ppVarCodec != nullptr) {
+            *m_ppVarCodec = m_codecVec[currentIndex()];
+        }
+    }
+    void write(ValueMap* config) 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_optionItemList.push_back(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);
+
+    //showButtonSeparator( true );
+    //setHelp( "kdiff3/index.html", QString::null );
+
+    setupFontPage();
+    setupColorPage();
+    setupEditPage();
+    setupDiffPage();
+    setupMergePage();
+    setupOtherOptions();
+    if(bShowDirMergeSettings)
+        setupDirectoryMergePage();
+
+    setupRegionalPage();
+    setupIntegrationPage();
+
+    //setupKeysPage();
+
+    // 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"), QString());
+}
+
+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 OptionToggleAction(true, "Show Toolbar", &m_options.m_bShowToolBar));
+    addOptionItem(new OptionToggleAction(true, "Show Statusbar", &m_options.m_bShowStatusBar));
+
+    /*
+   TODO manage toolbar positioning
+   */
+    addOptionItem(new OptionNum<int>( Qt::TopToolBarArea, "ToolBarPos", (int*)&m_options.m_toolBarPos));
+    addOptionItem(new OptionSize(QSize(600, 400), "Geometry", &m_options.m_geometry));
+    addOptionItem(new OptionPoint(QPoint(0, 22), "Position", &m_options.m_position));
+    addOptionItem(new OptionToggleAction(false, "WindowStateMaximised", &m_options.m_bMaximised));
+
+    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()
+{
+    QFrame* page = new QFrame();
+    KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18nc("Title for color settings page","Color"));
+    pageItem->setHeader(i18n("Colors Settings"));
+    pageItem->setIcon(QIcon::fromTheme(QStringLiteral("colormanagement")));
+    addPage(pageItem);
+
+    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("Directory 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 directory 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()
+{
+    QFrame* page = new QFrame();
+    KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Editor"));
+    pageItem->setHeader(i18n("Editor Behavior"));
+    pageItem->setIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor")));
+    addPage(pageItem);
+
+    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()
+{
+    QFrame* page = new QFrame();
+    KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Diff"));
+    pageItem->setHeader(i18n("Diff Settings"));
+    pageItem->setIcon(QIcon::fromTheme(QStringLiteral("text-x-patch")));
+    addPage(pageItem);
+
+    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()
+{
+    QFrame* page = new QFrame();
+    KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Merge"));
+    pageItem->setHeader(i18n("Merge Settings"));
+    pageItem->setIcon(QIcon::fromTheme(QStringLiteral("merge")));
+    addPage(pageItem);
+
+    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);
+        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.");
+        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);
+        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.*\\$.*\"");
+        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);
+        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.");
+        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);
+        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.");
+        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()
+{
+    QFrame* page = new QFrame();
+    KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Directory"));
+    pageItem->setHeader(i18n("Directory"));
+    pageItem->setIcon(QIcon::fromTheme(QStringLiteral("inode-directory")));
+    addPage(pageItem);
+
+    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 directories"), true, "RecursiveDirs", &m_options.m_bDmRecursiveDirs, page);
+    gbox->addWidget(pRecursiveDirs, line, 0, 1, 2);
+    addOptionItem(pRecursiveDirs);
+    pRecursiveDirs->setToolTip(i18n("Whether to analyze subdirectories 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("Dir-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 directories 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 directory specific."));
+    ++line;
+
+    OptionCheckBox* pFindHidden = new OptionCheckBox(i18n("Find hidden files and directories"), true, "FindHidden", &m_options.m_bDmFindHidden, page);
+    gbox->addWidget(pFindHidden, line, 0, 1, 2);
+    addOptionItem(pFindHidden);
+#if defined(Q_OS_WIN)
+    pFindHidden->setToolTip(i18n("Finds files and directories with the hidden attribute."));
+#else
+    pFindHidden->setToolTip(i18n("Finds files and directories starting with '.'."));
+#endif
+    ++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 directory links"), false, "FollowDirLinks", &m_options.m_bDmFollowDirLinks, page);
+    gbox->addWidget(pFollowDirLinks, line, 0, 1, 2);
+    addOptionItem(pFollowDirLinks);
+    pFollowDirLinks->setToolTip(i18n(
+        "On: Compare the directory 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 directory comparison will compare files or directories 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 subdirectories on load"), false, "UnfoldSubdirs", &m_options.m_bDmUnfoldSubdirs, page);
+    gbox->addWidget(pUnfoldSubdirs, line, 0, 1, 2);
+    addOptionItem(pUnfoldSubdirs);
+    pUnfoldSubdirs->setToolTip(i18n(
+        "On: Unfold all subdirectories when starting a directory diff.\n"
+        "Off: Leave subdirectories folded."));
+    ++line;
+
+    OptionCheckBox* pSkipDirStatus = new OptionCheckBox(i18n("Skip directory 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 Directory 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 directories 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 directories 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 directories 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 directories"), 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 directories so that\n"
+        "both directories are the same afterwards.\n"
+        "Works only when comparing two directories 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 directories."));
+    ++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);
+}
+/*
+static void insertCodecs(OptionComboBox* p)
+{
+   std::multimap<QString,QString> m;  // Using the multimap for case-insensitive sorting.
+   int i;
+   for(i=0;;++i)
+   {
+      QTextCodec* pCodec = QTextCodec::codecForIndex ( i );
+      if ( pCodec != 0 )  m.insert( std::make_pair( QString(pCodec->mimeName()).toUpper(), pCodec->mimeName()) );
+      else                break;
+   }
+
+   p->insertItem( i18n("Auto"), 0 );
+   std::multimap<QString,QString>::iterator mi;
+   for(mi=m.begin(), i=0; mi!=m.end(); ++mi, ++i)
+      p->insertItem(mi->second, i+1);
+}
+*/
+/*
+// UTF8-Codec that saves a BOM
+// UTF8-Codec that saves a BOM
+class Utf8BOMCodec : public QTextCodec
+{
+   QTextCodec* m_pUtf8Codec;
+   class PublicTextCodec : public QTextCodec
+   {
+   public:
+      QString publicConvertToUnicode ( const char * p, int len, ConverterState* pState ) const
+      {
+         return convertToUnicode( p, len, pState );
+      }
+      QByteArray publicConvertFromUnicode ( const QChar * input, int number, ConverterState * pState ) const
+      {
+         return convertFromUnicode( input, number, pState );
+      }
+   };
+public:
+   Utf8BOMCodec()
+   {
+      m_pUtf8Codec = QTextCodec::codecForName("UTF-8");
+   }
+   QByteArray name () const { return "UTF-8-BOM"; }
+   int mibEnum () const { return 2123; }
+   QByteArray convertFromUnicode ( const QChar * input, int number, ConverterState * pState ) const
+   {
+      QByteArray r;
+      if ( pState && pState->state_data[2]==0)  // state_data[2] not used by QUtf8::convertFromUnicode (see qutfcodec.cpp)
+      {
+        r += "\xEF\xBB\xBF";
+        pState->state_data[2]=1;
+        pState->flags |= QTextCodec::IgnoreHeader;
+      }
+
+      r += ((PublicTextCodec*)m_pUtf8Codec)->publicConvertFromUnicode( input, number, pState );
+      return r;
+   }
+   QString convertToUnicode ( const char * p, int len, ConverterState* pState ) const
+   {
+      return ((PublicTextCodec*)m_pUtf8Codec)->publicConvertToUnicode( p, len, pState );
+   }
+};
+*/
+void OptionDialog::setupRegionalPage()
+{
+    /*
+     TODO: What is this line supposed to do besides leak memory? Introduced as is in .91 no explanation
+        new Utf8BOMCodec();
+   */
+
+    QFrame* page = new QFrame();
+    KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Regional Settings"));
+    pageItem->setHeader(i18n("Regional Settings"));
+    pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-locale")));
+    addPage(pageItem);
+
+    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<void (OptionEncodingComboBox::*)(int)>(&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()
+{
+    QFrame* page = new QFrame();
+    KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Integration"));
+    pageItem->setHeader(i18n("Integration Settings"));
+    pageItem->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal")));
+    addPage(pageItem);
+
+    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::setupKeysPage()
+{
+    //QVBox *page = addVBoxPage( i18n("Keys"), i18n("KeyDialog" ),
+    //                          BarIcon("fonts", KIconLoader::SizeMedium ) );
+
+    //QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
+    //           new KFontChooser( page,"font",false/*onlyFixed*/,QStringList(),false,6 );
+    //m_pKeyDialog=new KKeyDialog( false, 0 );
+    //topLayout->addWidget( m_pKeyDialog );
+}
+
+void OptionDialog::slotOk()
+{
+    slotApply();
+
+    accept();
+}
+
+/** Copy the values from the widgets to the public variables.*/
+void OptionDialog::slotApply()
+{
+    std::list<OptionItemBase*>::iterator i;
+    for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i)
+    {
+        (*i)->apply();
+    }
+
+    emit applyDone();
+
+#ifdef Q_OS_WIN
+    QString locale = m_options.m_language;
+    if(locale == "Auto" || locale.isEmpty())
+        locale = QLocale::system().name().left(2);
+    int spacePos = locale.indexOf(' ');
+    if(spacePos > 0) locale = locale.left(spacePos);
+    QSettings settings(QLatin1String("HKEY_CURRENT_USER\\Software\\KDiff3\\diff-ext"), QSettings::NativeFormat);
+    settings.setValue(QLatin1String("Language"), locale);
+#endif
+}
+
+/** 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()
+{
+    std::list<OptionItemBase*>::iterator i;
+    for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i)
+    {
+        (*i)->setToDefault();
+    }
+
+    slotEncodingChanged();
+}
+
+/** Initialise the widgets using the values in the public varibles. */
+void OptionDialog::setState()
+{
+    std::list<OptionItemBase*>::iterator i;
+    for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i)
+    {
+        (*i)->setToCurrent();
+    }
+
+    slotEncodingChanged();
+}
+
+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);
+    }
+};
+
+void OptionDialog::saveOptions(KSharedConfigPtr config)
+{
+    // No i18n()-Translations here!
+
+    ConfigValueMap cvm(config->group(KDIFF3_CONFIG_GROUP));
+    std::list<OptionItemBase*>::iterator i;
+    for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i)
+    {
+        (*i)->doUnpreserve();
+        (*i)->write(&cvm);
+    }
+}
+
+void OptionDialog::readOptions(KSharedConfigPtr config)
+{
+    // No i18n()-Translations here!
+
+    ConfigValueMap cvm(config->group(KDIFF3_CONFIG_GROUP));
+    std::list<OptionItemBase*>::iterator i;
+    for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i)
+    {
+        (*i)->read(&cvm);
+    }
+
+    setState();
+}
+
+QString OptionDialog::parseOptions(const QStringList& optionList)
+{
+    QString result;
+    QStringList::const_iterator i;
+    for(i = optionList.begin(); i != optionList.end(); ++i)
+    {
+        QString s = *i;
+
+        int pos = s.indexOf('=');
+        if(pos > 0) // seems not to have a tag
+        {
+            QString key = s.left(pos);
+            QString val = s.mid(pos + 1);
+            std::list<OptionItemBase*>::iterator j;
+            bool bFound = false;
+            for(j = m_optionItemList.begin(); j != m_optionItemList.end(); ++j)
+            {
+                if((*j)->getSaveName() == key)
+                {
+                    (*j)->doPreserve();
+                    ValueMap config;
+                    config.writeEntry(key, val); // Write the value as a string and
+                    (*j)->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 \"" + s + "\"\n";
+        }
+    }
+    return result;
+}
+
+QString OptionDialog::calcOptionHelp()
+{
+    ValueMap config;
+    std::list<OptionItemBase*>::iterator j;
+    for(j = m_optionItemList.begin(); j != m_optionItemList.end(); ++j)
+    {
+        (*j)->write(&config);
+    }
+    return config.getAsString();
+}
+
+void OptionDialog::slotHistoryMergeRegExpTester()
+{
+    QPointer<RegExpTester> dlg=QPointer<RegExpTester>(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
new file mode 100644 (file)
index 0000000..f82cf33
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ *   kdiff3 - Text Diff And Merge Tool
+ *   Copyright (C) 2002-2007  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.
+ *
+ */
+
+#ifndef OPTION_DIALOG_H
+#define OPTION_DIALOG_H
+
+#include <QStringList>
+#include <QGroupBox>
+
+#include <KPageDialog>
+#include <KSharedConfig>
+
+#include <list>
+
+#include "options.h"
+
+class QLabel;
+class QPlainTextEdit;
+
+class OptionItemBase;
+class OptionCheckBox;
+class OptionEncodingComboBox;
+class OptionLineEdit;
+class KKeyDialog;
+
+class OptionDialog : public KPageDialog
+{
+   Q_OBJECT
+
+public:
+
+    explicit OptionDialog( bool bShowDirMergeSettings, QWidget *parent = nullptr );
+    ~OptionDialog( void ) override;
+    QString parseOptions( const QStringList& optionList );
+    QString calcOptionHelp();
+
+    Options m_options;
+
+    void saveOptions(KSharedConfigPtr config);
+    void readOptions(KSharedConfigPtr config);
+
+    void setState(); // Must be called before calling exec();
+
+    void addOptionItem(OptionItemBase*);
+    KKeyDialog* m_pKeyDialog;
+protected Q_SLOTS:
+    virtual void slotDefault( void );
+    virtual void slotOk( void );
+    virtual void slotApply( void );
+    //virtual void buttonClicked( QAbstractButton* );
+    virtual void helpRequested();
+
+    void slotEncodingChanged();
+    void slotHistoryMergeRegExpTester();
+Q_SIGNALS:
+    void applyDone();
+private:
+    void resetToDefaults();
+
+    std::list<OptionItemBase*> m_optionItemList;
+
+    //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;
+
+private:
+    void setupFontPage();
+    void setupColorPage();
+    void setupEditPage();
+    void setupDiffPage();
+    void setupMergePage();
+    void setupDirectoryMergePage();
+    void setupKeysPage();
+    void setupRegionalPage();
+    void setupIntegrationPage();
+    void setupOtherOptions();
+};
+
+
+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 slots:
+   void slotSelectFont();
+};
+
+#endif
+
+
+
+
+
+
+
diff --git a/src/options.h b/src/options.h
new file mode 100644 (file)
index 0000000..97f46f7
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ *   kdiff3 - Text Diff And Merge Tool
+ *   Copyright (C) 2002-2007  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.
+ *
+ */
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+//#include <QToolBar>
+#include <QStringList>
+#include <list>
+
+enum e_LineEndStyle
+{
+   eLineEndStyleUnix=0,
+   eLineEndStyleDos,
+   eLineEndStyleAutoDetect,
+   eLineEndStyleUndefined, // only one line exists
+   eLineEndStyleConflict   // User must resolve manually
+};
+
+class Options
+{
+public:
+    // Some settings are not available in the option dialog:
+    QSize  m_geometry;
+    QPoint m_position;
+    bool   m_bMaximised;
+    bool   m_bShowToolBar;
+    bool   m_bShowStatusBar;
+    Qt::ToolBarArea    m_toolBarPos;
+
+    // These are the results of the option dialog.
+    QFont m_font;
+    //bool m_bItalicForDeltas;
+    QFont m_appFont;
+
+    QColor m_fgColor;
+    QColor m_bgColor;
+    QColor m_diffBgColor;
+    QColor m_colorA;
+    QColor m_colorB;
+    QColor m_colorC;
+    QColor m_colorForConflict;
+    QColor m_currentRangeBgColor;
+    QColor m_currentRangeDiffBgColor;
+    QColor m_oldestFileColor;
+    QColor m_midAgeFileColor;
+    QColor m_newestFileColor;
+    QColor m_missingFileColor;
+    QColor m_manualHelpRangeColor;
+
+    bool m_bWordWrap;
+
+    bool m_bReplaceTabs;
+    bool m_bAutoIndentation;
+    int  m_tabSize;
+    bool m_bAutoCopySelection;
+    bool m_bSameEncoding;
+    QTextCodec*  m_pEncodingA;
+    bool m_bAutoDetectUnicodeA;
+    QTextCodec*  m_pEncodingB;
+    bool m_bAutoDetectUnicodeB;
+    QTextCodec*  m_pEncodingC;
+    bool m_bAutoDetectUnicodeC;
+    QTextCodec*  m_pEncodingOut;
+    bool m_bAutoSelectOutEncoding;
+    QTextCodec*  m_pEncodingPP;
+    e_LineEndStyle  m_lineEndStyle;
+
+    bool m_bPreserveCarriageReturn;
+    bool m_bTryHard;
+    bool m_bShowWhiteSpaceCharacters;
+    bool m_bShowWhiteSpace;
+    bool m_bShowLineNumbers;
+    bool m_bHorizDiffWindowSplitting;
+    bool m_bShowInfoDialogs;
+    bool m_bDiff3AlignBC;
+
+    int  m_whiteSpace2FileMergeDefault;
+    int  m_whiteSpace3FileMergeDefault;
+    bool m_bIgnoreCase;
+    bool m_bIgnoreNumbers;
+    bool m_bIgnoreComments;
+    QString m_PreProcessorCmd;
+    QString m_LineMatchingPreProcessorCmd;
+    bool m_bRunRegExpAutoMergeOnMergeStart;
+    QString m_autoMergeRegExp;
+    bool m_bRunHistoryAutoMergeOnMergeStart;
+    QString m_historyStartRegExp;
+    QString m_historyEntryStartRegExp;
+    bool m_bHistoryMergeSorting;
+    QString m_historyEntryStartSortKeyOrder;
+    int m_maxNofHistoryEntries;
+    QString m_IrrelevantMergeCmd;
+    bool m_bAutoSaveAndQuitOnMergeWithoutConflicts;
+
+    bool m_bAutoAdvance;
+    int  m_autoAdvanceDelay;
+
+    QStringList m_recentAFiles;
+    QStringList m_recentBFiles;
+    QStringList m_recentCFiles;
+
+    QStringList m_recentEncodings;
+
+    QStringList m_recentOutputFiles;
+
+    // Directory Merge options
+    bool m_bDmSyncMode;
+    bool m_bDmRecursiveDirs;
+    bool m_bDmFollowFileLinks;
+    bool m_bDmFollowDirLinks;
+    bool m_bDmFindHidden;
+    bool m_bDmCreateBakFiles;
+    bool m_bDmBinaryComparison;
+    bool m_bDmFullAnalysis;
+    bool m_bDmTrustDate;
+    bool m_bDmTrustDateFallbackToBinary;
+    bool m_bDmTrustSize;
+    bool m_bDmCopyNewer;
+    //bool m_bDmShowOnlyDeltas;
+    bool m_bDmShowIdenticalFiles;
+    bool m_bDmUseCvsIgnore;
+    bool m_bDmWhiteSpaceEqual;
+    bool m_bDmCaseSensitiveFilenameComparison;
+    bool m_bDmUnfoldSubdirs;
+    bool m_bDmSkipDirStatus;
+    QString m_DmFilePattern;
+    QString m_DmFileAntiPattern;
+    QString m_DmDirAntiPattern;
+
+    QString m_language;
+    bool m_bRightToLeftLanguage;
+
+    QString m_ignorableCmdLineOptions;
+    bool m_bIntegrateWithClearCase;
+    bool m_bEscapeKeyQuits;
+};
+
+
+#endif
diff --git a/src/org.kde.kdiff3.appdata.xml b/src/org.kde.kdiff3.appdata.xml
new file mode 100644 (file)
index 0000000..045f136
--- /dev/null
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="utf-8"?>
+<component type="desktop">
+  <id>org.kde.kdiff3.desktop</id>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-2.0+</project_license>
+  <name>KDiff3</name>
+  <name xml:lang="ca">KDiff3</name>
+  <name xml:lang="ca-valencia">KDiff3</name>
+  <name xml:lang="cs">KDiff3</name>
+  <name xml:lang="de">KDiff3</name>
+  <name xml:lang="en-GB">KDiff3</name>
+  <name xml:lang="es">KDiff3</name>
+  <name xml:lang="fi">KDiff3</name>
+  <name xml:lang="fr">KDiff3</name>
+  <name xml:lang="gl">KDiff3</name>
+  <name xml:lang="id">KDiff3</name>
+  <name xml:lang="it">KDiff3</name>
+  <name xml:lang="nl">KDiff3</name>
+  <name xml:lang="pl">KDiff3</name>
+  <name xml:lang="pt">KDiff3</name>
+  <name xml:lang="ru">KDiff3</name>
+  <name xml:lang="sk">KDiff3</name>
+  <name xml:lang="sv">Kdiff3</name>
+  <name xml:lang="uk">KDiff3</name>
+  <name xml:lang="x-test">xxKDiff3xx</name>
+  <name xml:lang="zh-CN">KDiff3</name>
+  <summary>A File And Directory Comparison And Merge Tool</summary>
+  <summary xml:lang="ca">Una eina per a comparar i fusionar directoris i fitxers</summary>
+  <summary xml:lang="ca-valencia">Una eina per a comparar i fusionar directoris i fitxers</summary>
+  <summary xml:lang="cs">Nástroj pro porovnávání a slučování souborů a adresářů</summary>
+  <summary xml:lang="de">Programm zum Vergleichen und Zusammenführen von Dateien und Ordnern</summary>
+  <summary xml:lang="en-GB">A File And Directory Comparison And Merge Tool</summary>
+  <summary xml:lang="es">Una herramienta para comparar y fusionar archivos y directorios</summary>
+  <summary xml:lang="fi">Tiedostojen ja kansioiden vertailu- ja yhdistämistyökalu</summary>
+  <summary xml:lang="fr">Un outil de comparaison et de fusion de fichiers et de dossiers</summary>
+  <summary xml:lang="gl">Unha ferramenta de comparación e fusión de ficheiros e directorios</summary>
+  <summary xml:lang="id">Sebuah Alat Penggabung dan Pembanding Direktori Dan File</summary>
+  <summary xml:lang="it">Uno strumento di confronto e di fusione di file e cartelle</summary>
+  <summary xml:lang="nl">Een hulpmiddel voor het vergelijken en samenvoegen van bestanden en mappen</summary>
+  <summary xml:lang="pl">Narzędzie do porównywania i scalania plików i katalogów</summary>
+  <summary xml:lang="pt">Uma Ferramenta de Comparação e Junção de Pastas e Ficheiros</summary>
+  <summary xml:lang="sk">Nástroj na porovnanie a zlúčenie súborov a adresárov</summary>
+  <summary xml:lang="sv">Ett verktyg för jämförelse och sammanfogning av filer och kataloger</summary>
+  <summary xml:lang="uk">Програма для порівняння і об'єднання файлів та каталогів</summary>
+  <summary xml:lang="x-test">xxA File And Directory Comparison And Merge Toolxx</summary>
+  <summary xml:lang="zh-CN">一个文件和目录的比较和合并工具</summary>
+  <url type="homepage">https://gitlab.com/tfischer/kdiff3</url>
+  <project_group>KDE</project_group>
+  <description>
+    <p>KDiff3 is a file and directory diff and merge tool which</p>
+    <p xml:lang="ca">El KDiff3 és una eina per a la comparació i fusionat de fitxers i directoris</p>
+    <p xml:lang="ca-valencia">El KDiff3 és una eina per a la comparació i fusionat de fitxers i directoris</p>
+    <p xml:lang="de">KDiff3 ist ein Programm zum Anzeigen von Unterschieden und zum Zusammenführen von Dateien und Ordnern.</p>
+    <p xml:lang="en-GB">KDiff3 is a file and directory diff and merge tool which</p>
+    <p xml:lang="fi">Kdiff3 on tiedostojen ja kansioiden vertailu- ja yhdistämistyökalu, joka:</p>
+    <p xml:lang="fr">KDiff3 est un outil de comparaison et de fusion de fichiers et de dossiers qui</p>
+    <p xml:lang="gl">KDiff3 é unha ferramenta de diferenzas e fusión de ficheiros e directorios que</p>
+    <p xml:lang="id">KDiff3 adalah sebuah alat merge dan diff direktori dan file yang mana</p>
+    <p xml:lang="it">KDiff3 è uno strumento per fondere file e cartelle che</p>
+    <p xml:lang="nl">KDdiff3 is een hulpmiddel om bestanden en mappen te vergelijken en samen te voegen dat</p>
+    <p xml:lang="pl">KDiff3 jest narzędziem do pokazywania różnicy i scalania plików i katalogów, które</p>
+    <p xml:lang="pt">O KDiff3 é uma ferramenta de detecção e junção das diferenças entre ficheiros e pastas que</p>
+    <p xml:lang="sv">Kdiff3 är ett verktyg för jämförelse och sammanfogning av filer och kataloger, som</p>
+    <p xml:lang="uk">KDiff3 — програма для визначення відмінностей у файлах і каталогах та об'єднання цих відмінностей в один варіант, яка</p>
+    <p xml:lang="x-test">xxKDiff3 is a file and directory diff and merge tool whichxx</p>
+    <ul>
+      <li>compares and merges two or three text input files or directories,</li>
+      <li xml:lang="ca">compara i fusiona dos o tres fitxers o directoris des de l'entrada,</li>
+      <li xml:lang="ca-valencia">compara i fusiona dos o tres fitxers o directoris des de l'entrada,</li>
+      <li xml:lang="de">vergleicht zwei oder drei Textdateien bzw. Ordner und führt sie zusammen,</li>
+      <li xml:lang="en-GB">compares and merges two or three text input files or directories,</li>
+      <li xml:lang="fi">vertaa kahta tai kolmea tekstitiedostoa tai kansiota ja osaa yhdistää ne</li>
+      <li xml:lang="fr">compare et fusionne deux ou trois fichiers ou dossiers en entrée,</li>
+      <li xml:lang="gl">compara e fusiona dous ou tres ficheiros ou directorios de entrada de texto,</li>
+      <li xml:lang="id">membandingkan dan menggabungkan dua atau tiga input teks file atau direktori</li>
+      <li xml:lang="it">confronta e fonde due o tre file di testo o cartelle in ingresso,</li>
+      <li xml:lang="nl">twee of drie tekstbestanden of mappen vergelijkt en samenvoegt,</li>
+      <li xml:lang="pl">porównuje i scala dwa, trzy lub więcej plików wejściowych lub katalogów,</li>
+      <li xml:lang="pt">compara e junta dois ou três ficheiros de texto ou pastas de entrada,</li>
+      <li xml:lang="sv">jämför och sammanfogar två eller tre textfiler eller kataloger,</li>
+      <li xml:lang="uk">порівнює і об’єднує два або три вхідних текстових файлів або каталогів.</li>
+      <li xml:lang="x-test">xxcompares and merges two or three text input files or directories,xx</li>
+      <li>shows the differences line by line and character by character(!),</li>
+      <li xml:lang="ca">mostra les diferències línia per línia i caràcter per caràcter!,</li>
+      <li xml:lang="ca-valencia">mostra les diferències línia per línia i caràcter per caràcter!,</li>
+      <li xml:lang="de">kann Unterschiede zeilenweise und sogar Zeichen für Zeichen anzeigen,</li>
+      <li xml:lang="en-GB">shows the differences line by line and character by character(!),</li>
+      <li xml:lang="fi">näyttää erot riveittäin ja merkeittäin (!)</li>
+      <li xml:lang="fr">affiche les différences ligne par ligne et caractère par caractère (!),</li>
+      <li xml:lang="gl">mostra as diferenzas liña por liña e carácter por carácter(!),</li>
+      <li xml:lang="id">yang menampilkan perbedaan baris demi baris dan karakter demi karakter(!),</li>
+      <li xml:lang="it">mostra le differenze riga per riga e carattere per carattere(!),</li>
+      <li xml:lang="nl">toont de verschillen regel voor regel en teken voor teken(!),</li>
+      <li xml:lang="pl">pokazuje różnice wiersz po wierszu i znak po znaku(!),</li>
+      <li xml:lang="pt">mostra as diferenças linha-a-linha e carácter-a-carácter(!),</li>
+      <li xml:lang="sv">visar skillnaderna rad för rad och tecken för tecken (!),</li>
+      <li xml:lang="uk">показує відмінності за рядками і за символами(!),</li>
+      <li xml:lang="x-test">xxshows the differences line by line and character by character(!),xx</li>
+      <li>provides an automatic merge-facility,</li>
+      <li xml:lang="ca">proporciona la facilitat del fusionat automàtic,</li>
+      <li xml:lang="ca-valencia">proporciona la facilitat del fusionat automàtic,</li>
+      <li xml:lang="de">hat eine Funktion zum automatischen Zusammenführen,</li>
+      <li xml:lang="en-GB">provides an automatic merge-facility,</li>
+      <li xml:lang="fi">tarjoaa automaattisen yhdistämistoiminnon</li>
+      <li xml:lang="fr">offre des fonctionnalités de fusion automatique,</li>
+      <li xml:lang="gl">fornece unha ferramenta de fusión automática,</li>
+      <li xml:lang="id">menyediakan sebuah fasilitas penggabungan otomatis,</li>
+      <li xml:lang="it">fornisce un servizio automatico per la fusione,</li>
+      <li xml:lang="nl">een automatische samenvoegfunctie biedt,</li>
+      <li xml:lang="pl">zapewnia narzędzie do samoczynnego scalania,</li>
+      <li xml:lang="pt">oferece uma funcionalidade de junção automática,</li>
+      <li xml:lang="sv">tillhandahåller en automatisk sammanfogingsfunktion,</li>
+      <li xml:lang="uk">надає можливість автоматичного об’єднання,</li>
+      <li xml:lang="x-test">xxprovides an automatic merge-facility,xx</li>
+      <li>has an editor for comfortable solving of merge-conflicts,</li>
+      <li xml:lang="ca">té un editor per a la solució còmoda dels conflictes de fusionat,</li>
+      <li xml:lang="ca-valencia">té un editor per a la solució còmoda dels conflictes de fusionat,</li>
+      <li xml:lang="de">enthält einen speziellen komfortablen Editor zum Auflösen von Konflikten beim Zusammenführen,</li>
+      <li xml:lang="en-GB">has an editor for comfortable solving of merge-conflicts,</li>
+      <li xml:lang="fi">sisältää muokkaimen yhdistämisristiriitojen ratkaisemiseksi</li>
+      <li xml:lang="fr">dispose d'un éditeur pour résoudre confortablement les conflits lors de la fusion,</li>
+      <li xml:lang="gl">ten un editor para solucionar de maneira cómoda conflitos de fusión,</li>
+      <li xml:lang="id">memiliki sebuah editor untuk pemecahan yang baik pada penggabungan yang bentrok</li>
+      <li xml:lang="it">dispone di un editor per risolvere comodamente i conflitti di fusione,</li>
+      <li xml:lang="nl">een editor heeft voor het comfortabel oplossen van samenvoegconflicten,</li>
+      <li xml:lang="pl">ma edytor do wygodnego rozwiązywania sprzeczności przy scalaniu,</li>
+      <li xml:lang="pt">tem um editor para resolver confortavelmente os conflitos de junção,</li>
+      <li xml:lang="sv">har en editor för bekväm lösning av sammanfogningskonflikter,</li>
+      <li xml:lang="uk">містить редактор для полегшення розв’язання конфліктів під час об’єднання,</li>
+      <li xml:lang="x-test">xxhas an editor for comfortable solving of merge-conflicts,xx</li>
+      <li>provides network transparency via KIO,</li>
+      <li xml:lang="ca">proporciona transparència de xarxa a través del KIO,</li>
+      <li xml:lang="ca-valencia">proporciona transparència de xarxa a través del KIO,</li>
+      <li xml:lang="de">ist mit Hilfe von KIO Netzwerktransparent,</li>
+      <li xml:lang="en-GB">provides network transparency via KIO,</li>
+      <li xml:lang="fi">tarjoaa läpinäkyvän verkon KIOn kautta</li>
+      <li xml:lang="fr">fournit une transparence réseau via KIO,</li>
+      <li xml:lang="gl">fornece transparencia de rede mediante KIO,</li>
+      <li xml:lang="id">menyediakan transparan jaringan via KIO</li>
+      <li xml:lang="it">fornisce trasparenza di rete tramite KIO,</li>
+      <li xml:lang="nl">netwerktransparantie biedt via KIO,</li>
+      <li xml:lang="pl">zapewnia przejrzystość sieciową przez KIO,</li>
+      <li xml:lang="pt">oferece a transparência na rede com o KIO,</li>
+      <li xml:lang="sv">tillhandahåller nätverkstransparens via KIO,</li>
+      <li xml:lang="uk">забезпечує мережеву прозорість за допомогою KIO,</li>
+      <li xml:lang="x-test">xxprovides network transparency via KIO,xx</li>
+      <li>has options to highlight or hide changes in white-space or comments,</li>
+      <li xml:lang="ca">té opcions per a ressaltar o ocultar els canvis en els espais en blanc o els comentaris,</li>
+      <li xml:lang="ca-valencia">té opcions per a ressaltar o ocultar els canvis en els espais en blanc o els comentaris,</li>
+      <li xml:lang="de">kann Unterschiede in Leerraum-Zeichen oder Kommentaren besonders hervorheben oder ausblenden,</li>
+      <li xml:lang="en-GB">has options to highlight or hide changes in white-space or comments,</li>
+      <li xml:lang="fi">osaa korostaa tai piilottaa muutokset tyhjemerkeissä ja kommenteissa</li>
+      <li xml:lang="fr">dispose d'options permettant de surligner ou de cacher les différences d'espaces ou de commentaires,</li>
+      <li xml:lang="gl">ten opcións para salientar ou agochar cambios en espazos en branco ou comentarios,</li>
+      <li xml:lang="id">memiliki opsi untuk menyorot atau menyembunyikan perubahan dalam spasi putih atau komentar,</li>
+      <li xml:lang="it">ha opzioni per evidenziare o per nascondere le modifiche negli spazi o nei commenti,</li>
+      <li xml:lang="nl">opties heeft voor het accentueren of verbergen van wijzigingen in witruimte of commentaar,</li>
+      <li xml:lang="pl">ma opcje do podświetlenia i ukrycia zmian w białych znakach lub komentarzach,</li>
+      <li xml:lang="pt">tem opções para realçar ou esconder as alterações nos espaços em branco ou comentários,</li>
+      <li xml:lang="sv">har alternativ för att markera eller dölja ändringar av blanktecken eller kommentarer,</li>
+      <li xml:lang="uk">може підсвічувати або ховати зміни у пробілах або коментарях,</li>
+      <li xml:lang="x-test">xxhas options to highlight or hide changes in white-space or comments,xx</li>
+      <li>supports Unicode, UTF-8 and other file encodings,</li>
+      <li xml:lang="ca">admet Unicode, UTF-8 i altres codificacions de fitxer,</li>
+      <li xml:lang="ca-valencia">admet Unicode, UTF-8 i altres codificacions de fitxer,</li>
+      <li xml:lang="cs">podporuje Unicode, UTF-8 a další kódování souborů.</li>
+      <li xml:lang="de">unterstützt Unicode, UTF-8 und weitere Dateikodierungen,</li>
+      <li xml:lang="en-GB">supports Unicode, UTF-8 and other file encodings,</li>
+      <li xml:lang="fi">tukee Unicodea, UTF-8:aa ja muita merkistökoodauksia</li>
+      <li xml:lang="fr">prend en charge Unicode, UTF-8 et d'autres encodages de fichiers,</li>
+      <li xml:lang="gl">é compatíbel con Unicode, UTF-8 e outras codificacións de ficheiros,s</li>
+      <li xml:lang="id">dukungan Unicode, UTF-8 dqn pengenkodean file lain</li>
+      <li xml:lang="it">supporta Unicode, UTF-8 e altre codifiche di file,</li>
+      <li xml:lang="nl">Unicode, UTF-8 en andere bestandscoderingen ondersteunt,</li>
+      <li xml:lang="pl">obsługuje Unikod, UTF-8 i inne kodowania plików,</li>
+      <li xml:lang="pt">suporta o Unicode, o UTF-8 e outras codificações de ficheiros,</li>
+      <li xml:lang="sv">stöder Unicode, UTF-8 och andra filkodningar,</li>
+      <li xml:lang="uk">підтримує Unicode, UTF-8 та інші кодування текстів файлів,</li>
+      <li xml:lang="x-test">xxsupports Unicode, UTF-8 and other file encodings,xx</li>
+      <li>prints differences,</li>
+      <li xml:lang="ca">imprimeix les diferències,</li>
+      <li xml:lang="ca-valencia">imprimeix les diferències,</li>
+      <li xml:lang="de">druckt Abweichungen,</li>
+      <li xml:lang="en-GB">prints differences,</li>
+      <li xml:lang="fi">tulostaa eroavuudet</li>
+      <li xml:lang="fr">affiche les différences,</li>
+      <li xml:lang="gl">imprime diferenzas,</li>
+      <li xml:lang="id">cetakan yang berbeda</li>
+      <li xml:lang="it">stampa le differenze,</li>
+      <li xml:lang="nl">verschillen in regels afdrukt,</li>
+      <li xml:lang="pl">drukuje różnice,</li>
+      <li xml:lang="pt">imprime as diferenças,</li>
+      <li xml:lang="sv">skriver ut skillnader,</li>
+      <li xml:lang="uk">може надсилати на друк різницю,</li>
+      <li xml:lang="x-test">xxprints differences,xx</li>
+      <li>supports version control keyword and history merging.</li>
+      <li xml:lang="ca">admet la paraula clau per al control de versions i un historial de les fusions.</li>
+      <li xml:lang="ca-valencia">admet la paraula clau per al control de versions i un historial de les fusions.</li>
+      <li xml:lang="de">unterstützt Schlüsselwörter für Versionsverwaltung und Verlauf-Zusammenführung.</li>
+      <li xml:lang="en-GB">supports version control keyword and history merging.</li>
+      <li xml:lang="fi">tukee versionhallinnan avainsanoja ja historian yhdistämistä.</li>
+      <li xml:lang="fr">prend en charge les mots clés de contrôle de version et la fusion des historiques.</li>
+      <li xml:lang="gl">e permite fusionar historiais e palabras clave de control de versión.</li>
+      <li xml:lang="id">dukungan katakunci kendali versi dan histori penggabungan.</li>
+      <li xml:lang="it">supporta le parole chiave per il controllo di versione e la fusione della cronologia.</li>
+      <li xml:lang="nl">versiebeheersleutelwoorden ondersteunt en samenvoegen van geschiedenis.</li>
+      <li xml:lang="pl">obsługuje scalanie słów kluczowych i historii zarządzania wersjami</li>
+      <li xml:lang="pt">suporta as palavras-chave de controlo de versões e de junção do histórico.</li>
+      <li xml:lang="sv">stöder nyckelord för versionskontroll och sammanfogning av historik.</li>
+      <li xml:lang="uk">підтримує об’єднання за ключовими словами і журналом інструментів керування версіями.</li>
+      <li xml:lang="x-test">xxsupports version control keyword and history merging.xx</li>
+    </ul>
+  </description>
+  <provides>
+    <binary>kdiff3</binary>
+  </provides>
+</component>
diff --git a/src/org.kde.kdiff3.desktop b/src/org.kde.kdiff3.desktop
new file mode 100644 (file)
index 0000000..fd99824
--- /dev/null
@@ -0,0 +1,156 @@
+[Desktop Entry]
+Name=KDiff3
+Name[be]=KDiff3
+Name[bg]=KDiff3
+Name[bs]=KDiff3
+Name[ca]=KDiff3
+Name[ca@valencia]=KDiff3
+Name[cs]=KDiff3
+Name[da]=KDiff3
+Name[de]=KDiff3
+Name[el]=KDiff3
+Name[en_GB]=KDiff3
+Name[es]=KDiff3
+Name[et]=KDiff3
+Name[eu]=KDiff3
+Name[fi]=KDiff3
+Name[fr]=KDiff3
+Name[ga]=KDiff3
+Name[gl]=KDiff3
+Name[hi]=के-डिफ3
+Name[hne]=के-डिफ3
+Name[hr]=KDiff3
+Name[hu]=KDiff3
+Name[it]=KDiff3
+Name[ja]=KDiff3
+Name[km]=KDiff3
+Name[ko]=KDiff3
+Name[lt]=KDiff3
+Name[ml]=കെഡിഫ്3
+Name[mr]=के-डिफ3
+Name[nb]=KDiff3
+Name[nds]=KDiff3
+Name[nl]=KDiff3
+Name[nn]=KDiff3
+Name[pl]=KDiff3
+Name[pt]=KDiff3
+Name[pt_BR]=KDiff3
+Name[ro]=KDiff3
+Name[ru]=KDiff3
+Name[sk]=KDiff3
+Name[sl]=KDiff3
+Name[sr]=К‑диф3
+Name[sr@ijekavian]=К‑диф3
+Name[sr@ijekavianlatin]=KDiff3
+Name[sr@latin]=KDiff3
+Name[sv]=Kdiff3
+Name[tr]=KDiff3
+Name[ug]=KDiff3
+Name[uk]=KDiff3
+Name[x-test]=xxKDiff3xx
+Name[zh_CN]=KDiff3
+Name[zh_TW]=KDiff3
+GenericName=Diff/Patch Frontend
+GenericName[bg]=Интерфейс на Diff/Patch
+GenericName[bs]=Prikaz za Diff/Patch
+GenericName[ca]=Frontal del Diff/Patch
+GenericName[ca@valencia]=Frontal del Diff/Patch
+GenericName[cs]=Rozhraní pro Diff/Patch
+GenericName[da]=Brugerflade til diff/patch
+GenericName[de]=Grafische Oberfläche zu Diff/Patch
+GenericName[el]=Σύστημα υποστήριξης χρήστη για τα Diff/Patch
+GenericName[en_GB]=Diff/Patch Frontend
+GenericName[eo]=Fasado por la programoj "diff" kaj "patch"
+GenericName[es]=Interfaz para diff/patch
+GenericName[et]=Võrdlemise ja liitmise rakendus
+GenericName[eu]=Diff/Patch aurrealdekoa
+GenericName[fi]=Diff/Patch-käyttöliittymä
+GenericName[fr]=Interface graphique pour « Diff » / « Patch »
+GenericName[ga]=Comhéadan Diff/Patch
+GenericName[gl]=Interface para Diff e Patch
+GenericName[hi]=डिफ/पैच फ्रन्टएण्ड
+GenericName[hne]=डिफ/पैच फ्रन्टएन्ड
+GenericName[hu]=Diff/Patch Frontend
+GenericName[it]=Interfaccia per i comandi diff e patch
+GenericName[ja]=Diff/Patch フロントエンド
+GenericName[km]=Diff/Patch ខាង​មុខ
+GenericName[ko]=Diff/Patch 프론트엔드
+GenericName[lt]=Diff/Patch naudotojo sąsaja
+GenericName[ml]=ഡിഫ്/പാച്ച് പുരോഭാഗം
+GenericName[mr]=डिफ/पेच फ्रंटएन्ड
+GenericName[nb]=Diff-/Patch-grensesnitt
+GenericName[nds]=Böversiet för "diff" un "patch"
+GenericName[nl]=Diff/Patch-hulpprogramma
+GenericName[nn]=Motor for diff- og patch-filer
+GenericName[pl]=Interfejs dla Diff/Patch
+GenericName[pt]=Interface do Diff/Patch
+GenericName[pt_BR]=Interface do diff/patch
+GenericName[ro]=Interfață Diferențiere/Peticire
+GenericName[ru]=Графический интерфейс Diff/Patch
+GenericName[sk]=Rozhranie Diff/Patch
+GenericName[sl]=Začelje za diff/patch
+GenericName[sr]=Прочеље за diff и patch
+GenericName[sr@ijekavian]=Прочеље за diff и patch
+GenericName[sr@ijekavianlatin]=Pročelje za diff i patch
+GenericName[sr@latin]=Pročelje za diff i patch
+GenericName[sv]=Jämförelse- och programfixgränssnitt
+GenericName[tr]=Diff/Patch Arayüzü
+GenericName[ug]=سېلىشتۇرۇش/ياماش(Diff/Patch) نىڭ ئالدى ئۇچى
+GenericName[uk]=Графічна оболонка Diff/Patch
+GenericName[x-test]=xxDiff/Patch Frontendxx
+GenericName[zh_CN]=Diff/Patch 前端
+GenericName[zh_TW]=比較/修補程式前端介面
+Exec=kdiff3
+Icon=kdiff3
+Type=Application
+X-DocPath=kdiff3/index.html
+Comment=A File And Directory Comparison And Merge Tool
+Comment[bg]=Инструмент за сравняване и сливане на файлове и директории
+Comment[bs]=Alat za poređenje i spajanje direktorija i datoteka
+Comment[ca]=Una eina per a la comparació i fusió de fitxers i directoris
+Comment[ca@valencia]=Una eina per a la comparació i fusió de fitxers i directoris
+Comment[cs]=Nástroj pro porovnávání a slučování souborů a adresářů
+Comment[da]=Et værktøj til sammenfletning og sammenligning af filer og mapper
+Comment[de]=Programm zum Vergleichen und Zusammenführen von Dateien und Ordnern
+Comment[el]=Ένα εργαλείο σύγκρισης και συγχώνευσης αρχείων και καταλόγων
+Comment[en_GB]=A File And Directory Comparison And Merge Tool
+Comment[es]=Una herramienta para comparar y fusionar archivos y directorios
+Comment[et]=Failide ja kataloogide võrdlemise ja liitmise tööriist
+Comment[eu]=Fitxategiak eta direktorioak alderatzeko eta bateratzeko tresna
+Comment[fi]=Tiedostojen ja hakemistojen vertailu- ja yhdistämistyökalu
+Comment[fr]=Un outil de comparaison et de fusion de fichiers et de dossiers
+Comment[ga]=Uirlis a chuireann comhaid agus comhadlanna i gcomparáid agus a chumascann iad más gá
+Comment[gl]=Unha ferramenta de comparación e fusión de ficheiros e directorios
+Comment[hi]=फ़ाइल तथा डिरेक्ट्री तुलना करने व मिलाने का औजार
+Comment[hne]=फाइल अउ डिरेक्टरी तुलना करे अउ मिलाय के औजार
+Comment[hu]=Egy fájl és könyvtár összehasonlítási és egyesítési eszköz
+Comment[it]=Uno strumento di confronto e fusione di file e cartelle
+Comment[ja]=ファイルやディレクトリの比較/マージを行うツール
+Comment[km]=ការ​ប្រៀបធៀប​ថត និង​ឯកសារ និង​ឧបករណ៌​បញ្ចូល​គ្នា
+Comment[ko]=파일과 디렉터리 비교 및 병합 도구
+Comment[lt]=Failų ir Direktorijų palyginimo ir suliejimo įrankis
+Comment[ml]=ഫയലും അറകളും താരതമ്യം ചെയ്യാനും ലയിപ്പിക്കാനുമുള്ള ഒരു ആയുധം
+Comment[nb]=Et verktøy for å sammelnlikne og flette filer og mapper
+Comment[nds]=En Warktüüch för't Verglieken un Tosamenföhren vun Dateien un Ornern
+Comment[nl]=Een hulpmiddel voor het vergelijken en samenvoegen van bestanden en mappen
+Comment[nn]=Eit program for samanlikning og fletting av filer og mapper
+Comment[pl]=Narzędzie do porównywania i scalania plików i katalogów
+Comment[pt]=Uma Ferramenta de Comparação e Junção de Ficheiros e Pastas
+Comment[pt_BR]=Uma ferramenta de comparação e junção de arquivos e pastas
+Comment[ro]=Un instrument de comparare și îmbinare a fișierelor și directoarelor
+Comment[ru]=Инструмент для сравнения и объединения файлов и каталогов
+Comment[sk]=Nástroj na porovnanie a zlúčenie súborov a adresárov
+Comment[sl]=Orodje za primerjavo in združevanje datotek in map
+Comment[sr]=Алатка за поређење и стапање фајлова и фасцикли
+Comment[sr@ijekavian]=Алатка за поређење и стапање фајлова и фасцикли
+Comment[sr@ijekavianlatin]=Alatka za poređenje i stapanje fajlova i fascikli
+Comment[sr@latin]=Alatka za poređenje i stapanje fajlova i fascikli
+Comment[sv]=Ett jämförelseverktyg för fil- och katalogjämförelser
+Comment[tr]=Bir Dosya Ve Dizin Karşılaştırma Ve Birleştirme Aracı
+Comment[ug]=ھۆججەت ۋە مۇندەرىجە سېلىشتۇرۇش ۋە بىرىكتۈرۈش قورالى
+Comment[uk]=Інструмент для порівняння та з’єднання файлів та тек
+Comment[x-test]=xxA File And Directory Comparison And Merge Toolxx
+Comment[zh_CN]=一个文件和目录的比较和合并工具
+Comment[zh_TW]=一個檔案與目錄比較與合併的工具
+Terminal=false
+Categories=Qt;KDE;Development;
diff --git a/src/pdiff.cpp b/src/pdiff.cpp
new file mode 100644 (file)
index 0000000..d5bc326
--- /dev/null
@@ -0,0 +1,2444 @@
+/***************************************************************************
+ *   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.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "difftextwindow.h"
+#include "directorymergewindow.h"
+#include "fileaccess.h"
+#include "kdiff3.h"
+#include "optiondialog.h"
+#include "progress.h"
+#include "Utils.h"
+#include "DirectoryInfo.h"
+
+#include "mergeresultwindow.h"
+#include "smalldialogs.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <list>
+
+#include <QCheckBox>
+#include <QClipboard>
+#include <QComboBox>
+#include <QDir>
+#include <QDialog>
+#include <QEvent> // QKeyEvent, QDropEvent, QInputEvent
+#include <QFile>
+#include <QLayout>
+#include <QLineEdit>
+#include <QMimeData>
+#include <QPointer>
+#include <QProcess>
+#include <QScrollBar>
+#include <QSplitter>
+#include <QStatusBar>
+#include <QStringList>
+#include <QTextCodec>
+#include <QUrl>
+
+#include <KLocalizedString>
+#include <KShortcutsDialog>
+#include <KMessageBox>
+
+bool g_bIgnoreWhiteSpace = true;
+bool g_bIgnoreTrivialMatches = true;
+
+// Just make sure that all input lines are in the output too, exactly once.
+static void debugLineCheck(Diff3LineList& d3ll, LineRef size, LineRef idx)
+{
+    Diff3LineList::iterator it = d3ll.begin();
+    LineRef i = 0;
+
+    for(it = d3ll.begin(); it != d3ll.end(); ++it)
+    {
+        LineRef l = 0;
+
+        Q_ASSERT(idx >= 1 && idx <= 3);
+        if(idx == 1)
+            l = (*it).lineA;
+        else if(idx == 2)
+            l = (*it).lineB;
+        else if(idx == 3)
+            l = (*it).lineC;
+
+        if(l != -1)
+        {
+            if(l != i)
+            {
+                KMessageBox::error(nullptr, i18n(
+                                          "Data loss error:\n"
+                                          "If it is reproducible please contact the author.\n"),
+                                   i18n("Severe Internal Error"));
+
+                fprintf(stderr, "Severe Internal Error.\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"));
+
+        fprintf(stderr, "Severe Internal Error.\n");
+        ::exit(-1);
+    }
+}
+
+void KDiff3App::mainInit(QSharedPointer<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)
+    {
+        if(m_totalDiffStatus == nullptr)
+            m_totalDiffStatus = QSharedPointer<TotalDiffStatus>::create();
+        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);
+
+    m_diff3LineList.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"));
+
+        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"));
+        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();
+    // Run the diff.
+    if(m_sd3.isEmpty())
+    {
+        pTotalDiffStatus->bBinaryAEqB = m_sd1.isBinaryEqualWith(m_sd2);
+        pp.setInformation(i18n("Diff: A <-> B"));
+
+        runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12, 1, 2,
+                &m_manualDiffHelpList, &m_pOptionDialog->m_options);
+
+        pp.step();
+
+        pp.setInformation(i18n("Linediff: A <-> B"));
+        calcDiff3LineListUsingAB(&m_diffList12, m_diff3LineList);
+        pTotalDiffStatus->bTextAEqB = fineDiff(m_diff3LineList, 1, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay());
+        if(m_sd1.getSizeBytes() == 0) pTotalDiffStatus->bTextAEqB = false;
+
+        pp.step();
+    }
+    else
+    {
+        if(bLoadFiles)
+        {
+            pp.setInformation(i18n("Loading C"));
+            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->bBinaryAEqB = m_sd1.isBinaryEqualWith(m_sd2);
+        pTotalDiffStatus->bBinaryAEqC = m_sd1.isBinaryEqualWith(m_sd3);
+        pTotalDiffStatus->bBinaryBEqC = m_sd3.isBinaryEqualWith(m_sd2);
+
+        pp.setInformation(i18n("Diff: A <-> B"));
+        runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12, 1, 2,
+                &m_manualDiffHelpList, &m_pOptionDialog->m_options);
+        pp.step();
+        pp.setInformation(i18n("Diff: B <-> C"));
+        runDiff(m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList23, 2, 3,
+                &m_manualDiffHelpList, &m_pOptionDialog->m_options);
+        pp.step();
+        pp.setInformation(i18n("Diff: A <-> C"));
+        runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList13, 1, 3,
+                &m_manualDiffHelpList, &m_pOptionDialog->m_options);
+        pp.step();
+
+        calcDiff3LineListUsingAB(&m_diffList12, m_diff3LineList);
+        calcDiff3LineListUsingAC(&m_diffList13, m_diff3LineList);
+        correctManualDiffAlignment(m_diff3LineList, &m_manualDiffHelpList);
+        calcDiff3LineListTrim(m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList);
+
+        if(m_pOptions->m_bDiff3AlignBC)
+        {
+            calcDiff3LineListUsingBC(&m_diffList23, m_diff3LineList);
+            correctManualDiffAlignment(m_diff3LineList, &m_manualDiffHelpList);
+            calcDiff3LineListTrim(m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList);
+        }
+        debugLineCheck(m_diff3LineList, m_sd1.getSizeLines(), 1);
+        debugLineCheck(m_diff3LineList, m_sd2.getSizeLines(), 2);
+        debugLineCheck(m_diff3LineList, m_sd3.getSizeLines(), 3);
+
+        pp.setInformation(i18n("Linediff: A <-> B"));
+        pTotalDiffStatus->bTextAEqB = fineDiff(m_diff3LineList, 1, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay());
+        pp.step();
+        pp.setInformation(i18n("Linediff: B <-> C"));
+        pTotalDiffStatus->bTextBEqC = fineDiff(m_diff3LineList, 2, m_sd2.getLineDataForDisplay(), m_sd3.getLineDataForDisplay());
+        pp.step();
+        pp.setInformation(i18n("Linediff: A <-> C"));
+        pTotalDiffStatus->bTextAEqC = fineDiff(m_diff3LineList, 3, m_sd3.getLineDataForDisplay(), m_sd1.getLineDataForDisplay());
+        pp.step();
+        if(m_sd1.getSizeBytes() == 0) {
+            pTotalDiffStatus->bTextAEqB = false;
+            pTotalDiffStatus->bTextAEqC = false;
+        }
+        if(m_sd2.getSizeBytes() == 0) {
+            pTotalDiffStatus->bTextAEqB = false;
+            pTotalDiffStatus->bTextBEqC = false;
+        }
+    }
+    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());
+    calcWhiteDiff3Lines(m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff());
+    calcDiff3LineVector(m_diff3LineList, m_diff3LineVector);
+
+    // Calc needed lines for display
+    m_neededLines = m_diff3LineList.size();
+
+    QList<int> oldHeights;
+    if(m_pDirectoryMergeSplitter->isVisible())
+        oldHeights = m_pMainSplitter->sizes();
+
+    initView();
+
+    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_pDiffTextWindow2->init(m_sd2.getAliasName(), m_sd2.getEncoding(), m_sd2.getLineEndStyle(),
+                                 m_sd2.getLineDataForDisplay(), m_sd2.getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff);
+        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->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())
+        {
+            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->setValue(m_pDiffVScrollBar->value() + deltaY);
+        m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep());
+    }
+    if(deltaX != 0)
+        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);
+}
+
+// Function uses setMinSize( sizeHint ) before adding the widget.
+// void addWidget(QBoxLayout* layout, QWidget* widget);
+template <class W, class L>
+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::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->m_options);
+    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->m_options, 1, &m_sd1);
+    m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame1);
+    m_pDiffTextWindowFrame2 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), &m_pOptionDialog->m_options, 2, &m_sd2);
+    m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame2);
+    m_pDiffTextWindowFrame3 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), &m_pOptionDialog->m_options, 3, &m_sd3);
+    m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame3);
+    m_pDiffTextWindow1 = m_pDiffTextWindowFrame1->getDiffTextWindow();
+    m_pDiffTextWindow2 = m_pDiffTextWindowFrame2->getDiffTextWindow();
+    m_pDiffTextWindow3 = m_pDiffTextWindowFrame3->getDiffTextWindow();
+    connect(m_pDiffTextWindowFrame1, &DiffTextWindowFrame::fileNameChanged, this, &KDiff3App::slotFileNameChanged);
+    connect(m_pDiffTextWindowFrame2, &DiffTextWindowFrame::fileNameChanged, this, &KDiff3App::slotFileNameChanged);
+    connect(m_pDiffTextWindowFrame3, &DiffTextWindowFrame::fileNameChanged, this, &KDiff3App::slotFileNameChanged);
+
+    connect(m_pDiffTextWindowFrame1, &DiffTextWindowFrame::encodingChanged, this, &KDiff3App::slotEncodingChangedA);
+    connect(m_pDiffTextWindowFrame2, &DiffTextWindowFrame::encodingChanged, this, &KDiff3App::slotEncodingChangedB);
+    connect(m_pDiffTextWindowFrame3, &DiffTextWindowFrame::encodingChanged, this, &KDiff3App::slotEncodingChangedC);
+
+    // 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->m_options);
+    pMergeVLayout->addWidget(m_pMergeResultWindowTitle);
+
+    m_pMergeResultWindow = new MergeResultWindow(m_pMergeWindowFrame, &m_pOptionDialog->m_options, 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<int> 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<int> hSizes;
+    hSizes << 1 << 1 << 1;
+    m_pDiffWindowSplitter->setSizes(hSizes);
+
+    m_pMergeResultWindow->installEventFilter(this);                      // for Cut/Copy/Paste-shortcuts
+    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);
+    connect(m_pDiffTextWindow1, &DiffTextWindow::newSelection, this, &KDiff3App::slotSelectionStart);
+    connect(m_pDiffTextWindow1, &DiffTextWindow::selectionEnd, this, &KDiff3App::slotSelectionEnd);
+    connect(m_pDiffTextWindow1, &DiffTextWindow::scrollDiffTextWindow, this, &KDiff3App::scrollDiffTextWindow);
+    m_pDiffTextWindow1->installEventFilter(this);
+
+    connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow2, &DiffTextWindow::setFirstLine);
+    connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow2, &DiffTextWindow::setHorizScrollOffset);
+    connect(m_pDiffTextWindow2, &DiffTextWindow::newSelection, this, &KDiff3App::slotSelectionStart);
+    connect(m_pDiffTextWindow2, &DiffTextWindow::selectionEnd, this, &KDiff3App::slotSelectionEnd);
+    connect(m_pDiffTextWindow2, &DiffTextWindow::scrollDiffTextWindow, this, &KDiff3App::scrollDiffTextWindow);
+    m_pDiffTextWindow2->installEventFilter(this);
+
+    connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow3, &DiffTextWindow::setFirstLine);
+    connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow3, &DiffTextWindow::setHorizScrollOffset);
+    connect(m_pDiffTextWindow3, &DiffTextWindow::newSelection, this, &KDiff3App::slotSelectionStart);
+    connect(m_pDiffTextWindow3, &DiffTextWindow::selectionEnd, this, &KDiff3App::slotSelectionEnd);
+    connect(m_pDiffTextWindow3, &DiffTextWindow::scrollDiffTextWindow, this, &KDiff3App::scrollDiffTextWindow);
+    m_pDiffTextWindow3->installEventFilter(this);
+
+    MergeResultWindow* p = m_pMergeResultWindow;
+    connect(m_pMergeVScrollBar, &QScrollBar::valueChanged, p, &MergeResultWindow::setFirstLine);
+
+    connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, p, &MergeResultWindow::setHorizScrollOffset);
+    connect(p, &MergeResultWindow::scrollMergeResultWindow, this, &KDiff3App::scrollMergeResultWindow);
+    connect(p, &MergeResultWindow::sourceMask, this, &KDiff3App::sourceMask);
+    connect(p, &MergeResultWindow::resizeSignal, this, &KDiff3App::resizeMergeResultWindow);
+    connect(p, &MergeResultWindow::selectionEnd, this, &KDiff3App::slotSelectionEnd);
+    connect(p, &MergeResultWindow::newSelection, this, &KDiff3App::slotSelectionStart);
+    connect(p, &MergeResultWindow::modifiedChanged, this, &KDiff3App::slotOutputModified);
+    connect(p, &MergeResultWindow::modifiedChanged, m_pMergeResultWindowTitle, &WindowTitleWidget::slotSetModified);
+    connect(p, &MergeResultWindow::updateAvailabilities, this, &KDiff3App::slotUpdateAvailabilities);
+    connect(p, &MergeResultWindow::showPopupMenu, this, &KDiff3App::showPopupMenu);
+    connect(p, &MergeResultWindow::noRelevantChangesDetected, this, &KDiff3App::slotNoRelevantChangesDetected);
+    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);
+}
+
+static int calcManualDiffFirstDiff3LineIdx(const Diff3LineVector& d3lv, const ManualDiffHelpEntry& mdhe)
+{
+    int i;
+    for(i = 0; i < d3lv.size(); ++i)
+    {
+        const Diff3Line& d3l = *d3lv[i];
+        if((mdhe.lineA1 >= 0 && mdhe.lineA1 == d3l.lineA) ||
+           (mdhe.lineB1 >= 0 && mdhe.lineB1 == d3l.lineB) ||
+           (mdhe.lineC1 >= 0 && mdhe.lineC1 == d3l.lineC))
+            return i;
+    }
+    return -1;
+}
+
+// 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 = calcManualDiffFirstDiff3LineIdx(m_diff3LineVector, m_manualDiffHelpList.front());
+    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);
+    // TODO What bug? Seems fixed.
+    // Workaround for a Qt-bug
+    /*QList<QTreeView*> treeViews = findChildren<QTreeView*>();
+    foreach(QTreeView* pTreeView, treeViews)
+    {
+        pTreeView->setUpdatesEnabled(true);
+    }*/
+
+    bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty();
+    QSharedPointer<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->bBinaryAEqB && pTotalDiffStatus->bBinaryAEqC)
+                totalInfo += i18n("All input files are binary equal.");
+            else if(pTotalDiffStatus->bTextAEqB && pTotalDiffStatus->bTextAEqC)
+                totalInfo += i18n("All input files contain the same text, but are not binary equal.");
+            else
+            {
+                if(pTotalDiffStatus->bBinaryAEqB)
+                    totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B"));
+                else if(pTotalDiffStatus->bTextAEqB)
+                    totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("B"));
+                if(pTotalDiffStatus->bBinaryAEqC)
+                    totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C"));
+                else if(pTotalDiffStatus->bTextAEqC)
+                    totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("C"));
+                if(pTotalDiffStatus->bBinaryBEqC)
+                    totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C"));
+                else if(pTotalDiffStatus->bTextBEqC)
+                    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)
+                m_pDiffVScrollBar->setValue(0);
+            else
+                m_pHScrollBar->setValue(0);
+            break;
+        case Qt::Key_End:
+            if(bCtrl)
+                m_pDiffVScrollBar->setValue(m_pDiffVScrollBar->maximum());
+            else
+                m_pHScrollBar->setValue(m_pHScrollBar->maximum());
+            break;
+        default:
+            break;
+    }
+
+    scrollDiffTextWindow(deltaX, deltaY);
+}
+
+bool KDiff3App::eventFilter(QObject* o, QEvent* e)
+{//TODO: Move this into DiffTextWindow::DropEvent
+    if(e->type() == QEvent::Drop)
+    {
+        QDropEvent* pDropEvent = static_cast<QDropEvent*>(e);
+        pDropEvent->accept();
+
+        if(pDropEvent->mimeData()->hasUrls())
+        {
+            QList<QUrl> urlList = pDropEvent->mimeData()->urls();
+            if(canContinue() && !urlList.isEmpty())
+            {
+                raise();
+                QString filename = urlList.first().toLocalFile();
+                if(o == m_pDiffTextWindow1)
+                    m_sd1.setFilename(filename);
+                else if(o == m_pDiffTextWindow2)
+                    m_sd2.setFilename(filename);
+                else if(o == m_pDiffTextWindow3)
+                    m_sd3.setFilename(filename);
+                mainInit();
+            }
+        }
+    }
+
+    return QSplitter::eventFilter(o, e); // standard event processing
+}
+
+void KDiff3App::slotFileOpen()
+{
+    if(!canContinue()) return;
+
+    if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress())
+    {
+        int result = KMessageBox::warningYesNo(this,
+                                               i18n("You are currently doing a directory 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<OpenDialog> d = QPointer<OpenDialog>(new OpenDialog(this,
+                     QDir::toNativeSeparators(m_bDirCompare ? m_sd1.getFilename() : m_sd1.isFromBuffer() ? QString("") : m_sd1.getAliasName()),
+                     QDir::toNativeSeparators(m_bDirCompare ? m_sd2.getFilename() : m_sd2.isFromBuffer() ? QString("") : m_sd2.getAliasName()),
+                     QDir::toNativeSeparators(m_bDirCompare ? m_sd3.getFilename() : m_sd3.isFromBuffer() ? QString("") : m_sd3.getAliasName()),
+                     m_bDirCompare ? m_bDefaultFilename : !m_outputFilename.isEmpty(),
+                     QDir::toNativeSeparators(m_bDefaultFilename ? QString("") : m_outputFilename), &m_pOptionDialog->m_options));
+
+        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 = "";
+
+            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, const QSharedPointer<TotalDiffStatus> &pTotalDiffStatus)
+{
+    if(!canContinue()) 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;
+    }
+
+    bool bDirCompare = m_bDirCompare;
+    improveFilenames(true); // Create new window for KDiff3 for directory comparison.
+
+    if(m_bDirCompare)
+    {
+    }
+    else
+    {
+        m_bDirCompare = bDirCompare; // Don't allow this to change here.
+        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()))
+        {
+            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"));
+        }
+        else
+        {
+            if(m_pDirectoryMergeWindow != nullptr && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked())
+            {
+                slotDirViewToggle();
+            }
+        }
+    }
+    slotStatusMsg(i18n("Ready."));
+}
+
+void KDiff3App::slotFileNameChanged(const QString& fileName, int 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 == 1) {
+        fn1 = fileName;
+        an1 = "";
+    }
+    if(winIdx == 2) {
+        fn2 = fileName;
+        an2 = "";
+    }
+    if(winIdx == 3) {
+        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 if(canContinue())
+    {
+        QStringList errors;
+        bool do_init = false;
+
+        if(m_pDiffTextWindow1->hasFocus())
+        {
+            errors = m_sd1.setData(QApplication::clipboard()->text(QClipboard::Clipboard));
+            do_init = true;
+        }
+        else if(m_pDiffTextWindow2->hasFocus())
+        {
+            errors = m_sd2.setData(QApplication::clipboard()->text(QClipboard::Clipboard));
+            do_init = true;
+        }
+        else if(m_pDiffTextWindow3->hasFocus())
+        {
+            errors = m_sd3.setData(QApplication::clipboard()->text(QClipboard::Clipboard));
+            do_init = true;
+        }
+
+        foreach(const QString& error, errors)
+        {
+            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()
+{
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoCurrent();
+}
+void KDiff3App::slotGoTop()
+{
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoTop();
+}
+void KDiff3App::slotGoBottom()
+{
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoBottom();
+}
+void KDiff3App::slotGoPrevUnsolvedConflict()
+{
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoPrevUnsolvedConflict();
+}
+void KDiff3App::slotGoNextUnsolvedConflict()
+{
+    m_bTimerBlock = false;
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoNextUnsolvedConflict();
+}
+void KDiff3App::slotGoPrevConflict()
+{
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoPrevConflict();
+}
+void KDiff3App::slotGoNextConflict()
+{
+    m_bTimerBlock = false;
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoNextConflict();
+}
+void KDiff3App::slotGoPrevDelta()
+{
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoPrevDelta();
+}
+void KDiff3App::slotGoNextDelta()
+{
+    if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoNextDelta();
+}
+
+void KDiff3App::choose(int 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); }
+
+// bConflictsOnly automatically choose for conflicts only (true) or for everywhere
+static void mergeChooseGlobal(MergeResultWindow* pMRW, int selector, bool bConflictsOnly, bool bWhiteSpaceOnly)
+{
+    if(pMRW)
+    {
+        pMRW->chooseGlobal(selector, bConflictsOnly, bWhiteSpaceOnly);
+    }
+}
+
+void KDiff3App::slotChooseAEverywhere() { mergeChooseGlobal(m_pMergeResultWindow, A, false, false); }
+void KDiff3App::slotChooseBEverywhere() { mergeChooseGlobal(m_pMergeResultWindow, B, false, false); }
+void KDiff3App::slotChooseCEverywhere() { mergeChooseGlobal(m_pMergeResultWindow, C, false, false); }
+void KDiff3App::slotChooseAForUnsolvedConflicts() { mergeChooseGlobal(m_pMergeResultWindow, A, true, false); }
+void KDiff3App::slotChooseBForUnsolvedConflicts() { mergeChooseGlobal(m_pMergeResultWindow, B, true, false); }
+void KDiff3App::slotChooseCForUnsolvedConflicts() { mergeChooseGlobal(m_pMergeResultWindow, C, true, false); }
+void KDiff3App::slotChooseAForUnsolvedWhiteSpaceConflicts() { mergeChooseGlobal(m_pMergeResultWindow, A, true, true); }
+void KDiff3App::slotChooseBForUnsolvedWhiteSpaceConflicts() { mergeChooseGlobal(m_pMergeResultWindow, B, true, true); }
+void KDiff3App::slotChooseCForUnsolvedWhiteSpaceConflicts() { mergeChooseGlobal(m_pMergeResultWindow, C, true, true); }
+
+void KDiff3App::slotAutoSolve()
+{
+    if(m_pMergeResultWindow)
+    {
+        m_pMergeResultWindow->slotAutoSolve();
+        // m_pMergeWindowFrame->show(); incompatible with bPreserveCarriageReturn
+        m_pMergeResultWindow->showNrOfConflicts();
+        slotUpdateAvailabilities();
+    }
+}
+
+void KDiff3App::slotUnsolve()
+{
+    if(m_pMergeResultWindow)
+    {
+        m_pMergeResultWindow->slotUnsolve();
+    }
+}
+
+void KDiff3App::slotMergeHistory()
+{
+    if(m_pMergeResultWindow)
+    {
+        m_pMergeResultWindow->slotMergeHistory();
+    }
+}
+
+void KDiff3App::slotRegExpAutoMerge()
+{
+    if(m_pMergeResultWindow)
+    {
+        m_pMergeResultWindow->slotRegExpAutoMerge();
+    }
+}
+
+void KDiff3App::slotSplitDiff()
+{
+    LineRef firstLine = -1;
+    LineRef lastLine = -1;
+    DiffTextWindow* pDTW = nullptr;
+    if(m_pDiffTextWindow1) {
+        pDTW = m_pDiffTextWindow1;
+        pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
+    }
+    if(firstLine < 0 && m_pDiffTextWindow2) {
+        pDTW = m_pDiffTextWindow2;
+        pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
+    }
+    if(firstLine < 0 && m_pDiffTextWindow3) {
+        pDTW = m_pDiffTextWindow3;
+        pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
+    }
+    if(pDTW && firstLine >= 0 && m_pMergeResultWindow)
+    {
+        pDTW->resetSelection();
+
+        m_pMergeResultWindow->slotSplitDiff(firstLine, lastLine);
+    }
+}
+
+void KDiff3App::slotJoinDiffs()
+{
+    LineRef firstLine = -1;
+    LineRef lastLine = -1;
+    DiffTextWindow* pDTW = nullptr;
+    if(m_pDiffTextWindow1) {
+        pDTW = m_pDiffTextWindow1;
+        pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
+    }
+    if(firstLine < 0 && m_pDiffTextWindow2) {
+        pDTW = m_pDiffTextWindow2;
+        pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
+    }
+    if(firstLine < 0 && m_pDiffTextWindow3) {
+        pDTW = m_pDiffTextWindow3;
+        pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
+    }
+    if(pDTW && firstLine >= 0 && 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);
+    if(m_pDiffTextWindow1 != nullptr)
+    {
+        m_pDiffTextWindow1->setFont(m_pOptions->m_font);
+        m_pDiffTextWindow1->update();
+    }
+    if(m_pDiffTextWindow2 != nullptr)
+    {
+        m_pDiffTextWindow2->setFont(m_pOptions->m_font);
+        m_pDiffTextWindow2->update();
+    }
+    if(m_pDiffTextWindow3 != nullptr)
+    {
+        m_pDiffTextWindow3->setFont(m_pOptions->m_font);
+        m_pDiffTextWindow3->update();
+    }
+    if(m_pMergeResultWindow != nullptr)
+    {
+        m_pMergeResultWindow->setFont(m_pOptions->m_font);
+        m_pMergeResultWindow->update();
+    }
+    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.
+static void mainWindowEnable(QWidget* pWidget, bool bEnable)
+{
+    if(QMainWindow* pWindow = dynamic_cast<QMainWindow*>(pWidget->window()))
+    {
+        QWidget* pStatusBarWidget = pWindow->statusBar();
+        QList<QObject*> children = pWindow->children();
+        for(int i = 0; i < children.count(); ++i)
+        {
+            if(children[i]->isWidgetType())
+            {
+                QWidget* pChildWidget = (QWidget*)children[i];
+                if(pChildWidget != pStatusBarWidget)
+                {
+                    pChildWidget->setEnabled(bEnable);
+                }
+            }
+        }
+    }
+}
+
+void KDiff3App::postRecalcWordWrap()
+{
+    if(!m_bRecalcWordWrapPosted)
+    {
+        m_bRecalcWordWrapPosted = true;
+        mainWindowEnable(window(), false);
+        m_firstD3LIdx = -1;
+        QTimer::singleShot(1 /* ms */, this, &KDiff3App::slotRecalcWordWrap);
+    }
+    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(window(), false);
+
+    m_visibleTextWidthForPrinting = visibleTextWidthForPrinting;
+    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)
+        {
+            Diff3LineList::iterator i;
+            int sumOfLines = 0;
+            for(i = m_diff3LineList.begin(); i != m_diff3LineList.end(); ++i)
+            {
+                Diff3Line& d3l = *i;
+                d3l.linesNeededForDisplay = 1;
+                d3l.sumLinesNeededForDisplay = sumOfLines;
+                sumOfLines += d3l.linesNeededForDisplay;
+            }
+
+            // Let every window calc how many lines will be needed.
+            if(m_pDiffTextWindow1)
+            {
+                m_pDiffTextWindow1->recalcWordWrap(true, 0, m_visibleTextWidthForPrinting);
+            }
+            if(m_pDiffTextWindow2)
+            {
+                m_pDiffTextWindow2->recalcWordWrap(true, 0, m_visibleTextWidthForPrinting);
+            }
+            if(m_pDiffTextWindow3)
+            {
+                m_pDiffTextWindow3->recalcWordWrap(true, 0, m_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 = startRunnables();
+        if(!bRunnablesStarted)
+            slotFinishRecalcWordWrap();
+        else
+        {
+            g_pProgressDialog->setInformation(m_pOptions->m_bWordWrap
+                                                  ? i18n("Word wrap (Cancel disables word wrap)")
+                                                  : i18n("Calculating max width for horizontal scrollbar"),
+                                              false);
+        }
+    }
+}
+
+void KDiff3App::slotFinishRecalcWordWrap()
+{
+    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();
+            QTimer::singleShot(1 /* ms */, this, &KDiff3App::slotRecalcWordWrap); // do it again
+        }
+        else // eResize
+        {
+            QTimer::singleShot(1 /* ms */, this, &KDiff3App::slotRecalcWordWrap); // do it again
+        }
+        return;
+    }
+    else
+    {
+        m_bRecalcWordWrapPosted = false;
+    }
+
+    g_pProgressDialog->setStayHidden(false);
+
+    bool bPrinting = m_visibleTextWidthForPrinting >= 0;
+
+    if(!m_diff3LineList.empty())
+    {
+        if(m_pOptions->m_bWordWrap)
+        {
+            Diff3LineList::iterator i;
+            int sumOfLines = 0;
+            for(i = m_diff3LineList.begin(); i != m_diff3LineList.end(); ++i)
+            {
+                Diff3Line& d3l = *i;
+                d3l.sumLinesNeededForDisplay = sumOfLines;
+                sumOfLines += d3l.linesNeededForDisplay;
+            }
+
+            // Finish the word wrap
+            if(m_pDiffTextWindow1)
+                m_pDiffTextWindow1->recalcWordWrap(true, sumOfLines, m_visibleTextWidthForPrinting);
+            if(m_pDiffTextWindow2)
+                m_pDiffTextWindow2->recalcWordWrap(true, sumOfLines, m_visibleTextWidthForPrinting);
+            if(m_pDiffTextWindow3)
+                m_pDiffTextWindow3->recalcWordWrap(true, sumOfLines, m_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(window(), 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();
+
+    if(m_pDiffTextWindow1 != nullptr)
+        m_pDiffTextWindow1->update();
+    if(m_pDiffTextWindow2 != nullptr)
+        m_pDiffTextWindow2->update();
+    if(m_pDiffTextWindow3 != nullptr)
+        m_pDiffTextWindow3->update();
+    if(m_pMergeResultWindow != nullptr)
+        m_pMergeResultWindow->update();
+    if(m_pOverview != nullptr)
+        m_pOverview->slotRedraw();
+}
+
+void KDiff3App::slotShowLineNumbersToggled()
+{
+    m_pOptions->m_bShowLineNumbers = showLineNumbers->isChecked();
+
+    if(wordWrap->isChecked())
+        recalcWordWrap();
+
+    if(m_pDiffTextWindow1 != nullptr)
+        m_pDiffTextWindow1->update();
+    if(m_pDiffTextWindow2 != nullptr)
+        m_pDiffTextWindow2->update();
+
+    if(m_pDiffTextWindow3 != nullptr)
+        m_pDiffTextWindow3->update();
+}
+
+/// Return true for success, else false
+bool KDiff3App::improveFilenames(bool bCreateNewInstance)
+{
+    m_bDirCompare = false;
+
+    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())
+    {
+        m_bDirCompare = true;
+        if(bCreateNewInstance)
+        {
+            emit createNewInstance(f1.absoluteFilePath(), f2.absoluteFilePath(), f3.absoluteFilePath());
+        }
+        else
+        {
+            FileAccess destDir;
+
+            if(!m_bDefaultFilename) destDir = f4;
+            m_pDirectoryMergeSplitter->show();
+            if(m_pMainWidget != nullptr) m_pMainWidget->hide();
+            setUpdatesEnabled(true);
+
+            bool bSuccess = m_pDirectoryMergeWindow->init(
+                QSharedPointer<DirectoryInfo>(new DirectoryInfo(f1, f2, f3, destDir)),
+                !m_outputFilename.isEmpty());
+
+            m_bDirCompare = true; //FIXME This seems redundant but it might have been reset during full analysis.
+
+            if(bSuccess)
+            {
+                m_sd1.reset();
+                if(m_pDiffTextWindow1 != nullptr) m_pDiffTextWindow1->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false);
+                m_sd2.reset();
+                if(m_pDiffTextWindow2 != nullptr) m_pDiffTextWindow2->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false);
+                m_sd3.reset();
+                if(m_pDiffTextWindow3 != nullptr) m_pDiffTextWindow3->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false);
+            }
+            slotUpdateAvailabilities();
+            return bSuccess;
+        }
+    }
+    return true;
+}
+
+void KDiff3App::slotReload()
+{
+    if(!canContinue()) 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;
+}
+
+void KDiff3App::slotCheckIfCanContinue(bool* pbContinue)
+{
+    if(pbContinue != nullptr) *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 + (int)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 + (int)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 + (int)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 + (int)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())
+    {
+        if(!canContinue()) 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<QWidget*> 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<QWidget*>::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<QWidget*> 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<QWidget*>::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);
+        }
+    }
+}
+
+static void insertManualDiffHelp(ManualDiffHelpList* pManualDiffHelpList, int 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 = pManualDiffHelpList->begin(); i != pManualDiffHelpList->end(); ++i)
+    {
+        int& l1 = i->firstLine(winIdx);
+        int& l2 = i->lastLine(winIdx);
+        if(l1 >= 0 && l2 >= 0)
+        {
+            if((firstLine <= l1 && lastLine >= l1) || (firstLine <= l2 && lastLine >= l2))
+            {
+                // overlap
+                l1 = -1;
+                l2 = -1;
+            }
+            if(firstLine < l1 && lastLine < l1)
+            {
+                // insert before this position
+                pManualDiffHelpList->insert(i, mdhe);
+                break;
+            }
+        }
+    }
+    if(i == pManualDiffHelpList->end())
+    {
+        pManualDiffHelpList->insert(i, mdhe);
+    }
+
+    // Now make the list compact
+    for(int wIdx = 1; wIdx <= 3; ++wIdx)
+    {
+        ManualDiffHelpList::iterator iEmpty = pManualDiffHelpList->begin();
+        for(i = pManualDiffHelpList->begin(); i != pManualDiffHelpList->end(); ++i)
+        {
+            if(iEmpty->firstLine(wIdx) >= 0)
+            {
+                ++iEmpty;
+                continue;
+            }
+            if(i->firstLine(wIdx) >= 0) // Current item is not empty -> move it to the empty place
+            {
+                iEmpty->firstLine(wIdx) = i->firstLine(wIdx);
+                iEmpty->lastLine(wIdx) = i->lastLine(wIdx);
+                i->firstLine(wIdx) = -1;
+                i->lastLine(wIdx) = -1;
+                ++iEmpty;
+            }
+        }
+    }
+    pManualDiffHelpList->remove(ManualDiffHelpEntry()); // Remove all completely empty items.
+}
+
+void KDiff3App::slotAddManualDiffHelp()
+{
+    LineRef firstLine = -1;
+    LineRef lastLine = -1;
+    int winIdx = -1;
+    if(m_pDiffTextWindow1) {
+        m_pDiffTextWindow1->getSelectionRange(&firstLine, &lastLine, eFileCoords);
+        winIdx = 1;
+    }
+    if(firstLine < 0 && m_pDiffTextWindow2) {
+        m_pDiffTextWindow2->getSelectionRange(&firstLine, &lastLine, eFileCoords);
+        winIdx = 2;
+    }
+    if(firstLine < 0 && m_pDiffTextWindow3) {
+        m_pDiffTextWindow3->getSelectionRange(&firstLine, &lastLine, eFileCoords);
+        winIdx = 3;
+    }
+
+    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
+    {
+        insertManualDiffHelp(&m_manualDiffHelpList, 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::slotEncodingChangedA(QTextCodec* c)
+{
+    m_sd1.setEncoding(c);
+    mainInit(nullptr, true, true); // Init with reload
+    slotRefresh();
+}
+
+void KDiff3App::slotEncodingChangedB(QTextCodec* c)
+{
+    m_sd2.setEncoding(c);
+    mainInit(nullptr, true, true); // Init with reload
+    slotRefresh();
+}
+
+void KDiff3App::slotEncodingChangedC(QTextCodec* c)
+{
+    m_sd3.setEncoding(c);
+    mainInit(nullptr, true, true); // Init with reload
+    slotRefresh();
+}
+
+void KDiff3App::slotUpdateAvailabilities()
+{
+    if(m_pMainSplitter == 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_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);
+    autoSolve->setEnabled(bMergeEditorVisible && m_bTripleDiff);
+    unsolve->setEnabled(bMergeEditorVisible);
+    if(!bDirWindowHasFocus)
+    {
+        chooseA->setEnabled(bMergeEditorVisible);
+        chooseB->setEnabled(bMergeEditorVisible);
+        chooseC->setEnabled(bMergeEditorVisible && m_bTripleDiff);
+    }
+    chooseAEverywhere->setEnabled(bMergeEditorVisible);
+    chooseBEverywhere->setEnabled(bMergeEditorVisible);
+    chooseCEverywhere->setEnabled(bMergeEditorVisible && m_bTripleDiff);
+    chooseAForUnsolvedConflicts->setEnabled(bMergeEditorVisible);
+    chooseBForUnsolvedConflicts->setEnabled(bMergeEditorVisible);
+    chooseCForUnsolvedConflicts->setEnabled(bMergeEditorVisible && m_bTripleDiff);
+    chooseAForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible);
+    chooseBForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible);
+    chooseCForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible && m_bTripleDiff);
+    mergeHistory->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);
+
+    goTop->setEnabled(bDiffWindowVisible && m_pMergeResultWindow->isDeltaAboveCurrent());
+    goBottom->setEnabled(bDiffWindowVisible && m_pMergeResultWindow->isDeltaBelowCurrent());
+    goCurrent->setEnabled(bDiffWindowVisible);
+    goPrevUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictAboveCurrent());
+    goNextUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictBelowCurrent());
+    goPrevConflict->setEnabled(bDiffWindowVisible && m_pMergeResultWindow->isConflictAboveCurrent());
+    goNextConflict->setEnabled(bDiffWindowVisible && m_pMergeResultWindow->isConflictBelowCurrent());
+    goPrevDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow->isDeltaAboveCurrent());
+    goNextDelta->setEnabled(bDiffWindowVisible && 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
new file mode 100644 (file)
index 0000000..253a3d2
--- /dev/null
@@ -0,0 +1,541 @@
+/***************************************************************************
+ *   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.                                   *
+ ***************************************************************************/
+
+#include "progress.h"
+#include "common.h"
+
+#include <QApplication>
+#include <QLabel>
+#include <QPointer>
+#include <QProgressBar>
+#include <QPushButton>
+#include <QStatusBar>
+#include <QThread>
+#include <QVBoxLayout>
+
+#include <KIO/Job>
+#include <KLocalizedString>
+
+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)
+    {
+        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)
+        {
+            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 && 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);
+}
+
+// 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 qt_enter_modal(QWidget*);
+void qt_leave_modal(QWidget*);
+
+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.
+    //qt_enter_modal(this);
+    QPointer<QEventLoop> pEventLoop =  QPointer<QEventLoop>(new QEventLoop(this));
+    m_eventLoopStack.push_back(pEventLoop);
+    pEventLoop->exec(); // this function only returns after ProgressDialog::exitEventLoop() is called.
+    pEventLoop.clear();
+    m_eventLoopStack.pop_back();
+    //qt_leave_modal(this);
+}
+
+void ProgressDialog::exitEventLoop()
+{
+    if(m_progressDelayTimer)
+        killTimer(m_progressDelayTimer);
+    m_progressDelayTimer = 0;
+    m_pJob = nullptr;
+    if(!m_eventLoopStack.empty())
+        m_eventLoopStack.back()->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<ProgressLevelData>::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));
+        }
+    }
+}
+
+#include <QTimer>
+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)
+    {
+        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;
+    }
+}
+
+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::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
new file mode 100644 (file)
index 0000000..a65c9de
--- /dev/null
@@ -0,0 +1,139 @@
+/***************************************************************************
+ *   Copyright (C) 2003-2007 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.                                   *
+ ***************************************************************************/
+
+#ifndef PROGRESS_H
+#define PROGRESS_H
+
+#include <QDialog>
+#include <QTime>
+#include <QList>
+
+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 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*) override;
+public 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<qint64> m_current;
+      QAtomicInteger<qint64> m_maxNofSteps;     // when step() is used.
+      double m_dRangeMax;
+      double m_dRangeMin;
+      double m_dSubRangeMax;
+      double m_dSubRangeMin;
+   };
+   QList<ProgressLevelData> m_progressStack;
+
+   int m_progressDelayTimer;
+   int m_delayedHideTimer;
+   int m_delayedHideStatusBarWidgetTimer;
+   QList<QEventLoop*> m_eventLoopStack;
+
+   QProgressBar* m_pProgressBar;
+   QProgressBar* m_pSubProgressBar;
+   QLabel* m_pInformation;
+   QLabel* m_pSubInformation;
+   QLabel* m_pSlowJobInfo;
+   QPushButton* m_pAbortButton;
+   QTime m_t1;
+   QTime m_t2;
+   bool m_bWasCancelled;
+   e_CancelReason m_eCancelReason;
+   KJob* m_pJob;
+   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 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 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
new file mode 100644 (file)
index 0000000..7f0cbb0
--- /dev/null
@@ -0,0 +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.                                   *
+ ***************************************************************************/
+
+#include "selection.h"
+#include "gnudiff_diff.h"
+
+#include <QtGlobal>
+
+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
new file mode 100644 (file)
index 0000000..f19ff15
--- /dev/null
@@ -0,0 +1,82 @@
+/***************************************************************************
+ *   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.                                   *
+ ***************************************************************************/
+#ifndef SELECTION_H
+#define SELECTION_H
+
+#include "gnudiff_diff.h"
+#include "common.h"
+
+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() { return firstPos; };
+  inline int getLastPos() { 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() { 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<lastLine ? (firstLine<0?0:firstPos) : (lastLine<0?0:lastPos);  }
+   int endPos()   { return firstLine==lastLine ? std::max(firstPos,lastPos) :
+                           firstLine<lastLine ? lastPos : firstPos;      }
+};
+
+#endif
diff --git a/src/smalldialogs.cpp b/src/smalldialogs.cpp
new file mode 100644 (file)
index 0000000..a37ac59
--- /dev/null
@@ -0,0 +1,625 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2007 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.           *
+ ***************************************************************************/
+
+#include "smalldialogs.h"
+#include "diff.h"
+#include "options.h"
+#include "kdiff3.h"
+
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QDir>
+#include <QDropEvent>
+#include <QFileDialog>
+#include <QLabel>
+#include <QLayout>
+#include <QLineEdit>
+#include <QMenu>
+#include <QMimeData>
+#include <QPushButton>
+#include <QToolTip>
+#include <QUrl>
+
+#include <KLocalizedString>
+
+// OpenDialog **************************************************************
+
+OpenDialog::OpenDialog(
+    KDiff3App* pParent, const QString& n1, const QString& n2, const QString& n3,
+    bool bMerge, const QString& outputName,  Options* 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("Dir..."), 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("Dir..."), 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("Dir..."), 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("Dir..."), 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<QDragEnterEvent*>(e);
+        d->setAccepted(d->mimeData()->hasUrls());
+        return true;
+    }
+    if(e->type() == QEvent::Drop)
+    {
+        QDropEvent* d = static_cast<QDropEvent*>(e);
+
+        if(!d->mimeData()->hasUrls())
+            return false;
+
+        QList<QUrl> lst = d->mimeData()->urls();
+
+        if(lst.count() > 0)
+        {
+            static_cast<QLineEdit*>(o)->setText(QDir::toNativeSeparators(lst[0].toLocalFile()));
+            static_cast<QLineEdit*>(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 Directory"), currentUrl)
+                       : bSave ? QFileDialog::getSaveFileUrl(this, i18n("Select Output File"), currentUrl, QLatin1Literal("all/allfiles"))
+                               : QFileDialog::getOpenFileUrl(this, i18n("Open File"), currentUrl, QLatin1Literal("all/allfiles"));
+    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)
+{
+    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();
+    }
+}
+
+static void 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) // 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
new file mode 100644 (file)
index 0000000..03050a6
--- /dev/null
@@ -0,0 +1,119 @@
+/***************************************************************************
+ *   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.          *
+ ***************************************************************************/
+
+#ifndef SMALLDIALOGS_H
+#define SMALLDIALOGS_H
+
+#include <QDialog>
+
+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, Options* 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:
+   Options* m_pOptions;
+   void selectURL( QComboBox* pLine, bool bDir, int i, bool bSave );
+   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*);
+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;
+   int currentPos;
+   int currentWindow;
+};
+
+
+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
diff --git a/src/xpm/autoadvance.xpm b/src/xpm/autoadvance.xpm
new file mode 100644 (file)
index 0000000..d499999
--- /dev/null
@@ -0,0 +1,25 @@
+/* XPM */
+static const char *autoadvance[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 3 1",
+". c #0080FF",
+"# c #000000",
+"  c None",
+/* pixels */
+" ##  # # ###  # ",
+"#  # # #  #  # #",
+"#  # # #  #  # #",
+"#### # #  #  # #",
+"#  # ###  #   # ",
+"                ",
+"                ",
+"    ########    ",
+"     #....#     ",
+"      #..#      ",
+"       ##       ",
+"    ########    ",
+"     #....#     ",
+"      #..#      ",
+"       ##       ",
+"                "
+};
diff --git a/src/xpm/currentpos.xpm b/src/xpm/currentpos.xpm
new file mode 100644 (file)
index 0000000..c027e6c
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *currentpos[]={
+"16 16 3 1",
+"  c #0080FF",
+"# c #000000",
+". c None",
+"................",
+"................",
+"................",
+".#............#.",
+".##..........##.",
+".# #........# #.",
+".#  #..##..#  #.",
+".#   ##  ##   #.",
+".#   #    #   #.",
+".#   ##  ##   #.",
+".#  #..##..#  #.",
+".# #........# #.",
+".##..........##.",
+".#............#.",
+"................",
+"................"};
diff --git a/src/xpm/down1arrow.xpm b/src/xpm/down1arrow.xpm
new file mode 100644 (file)
index 0000000..162b692
--- /dev/null
@@ -0,0 +1,25 @@
+/* XPM */
+static const char *down1arrow[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 3 1",
+"  c #0080ff",
+"# c #000000",
+". c None",
+/* pixels */
+"................",
+"................",
+"................",
+"................",
+"................",
+"..############..",
+"...#        #...",
+"....#      #....",
+".....#    #.....",
+"......#  #......",
+".......##.......",
+"................",
+"................",
+"................",
+"................",
+"................"
+};
diff --git a/src/xpm/down2arrow.xpm b/src/xpm/down2arrow.xpm
new file mode 100644 (file)
index 0000000..6f34208
--- /dev/null
@@ -0,0 +1,25 @@
+/* XPM */
+static const char *down2arrow[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 3 1",
+"  c #0080ff",
+"# c #000000",
+". c None",
+/* pixels */
+"................",
+"................",
+"..############..",
+"...#        #...",
+"....#      #....",
+".....#    #.....",
+"......#  #......",
+".......##.......",
+"..############..",
+"...#        #...",
+"....#      #....",
+".....#    #.....",
+"......#  #......",
+".......##.......",
+"................",
+"................"
+};
diff --git a/src/xpm/downend.xpm b/src/xpm/downend.xpm
new file mode 100644 (file)
index 0000000..214bc8b
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *downend[]={
+"16 16 3 1",
+"  c #0080ff",
+"# c #000000",
+". c None",
+"................",
+"................",
+"................",
+"................",
+"................",
+"..############..",
+"...#        #...",
+"....#      #....",
+".....#    #.....",
+"......#  #......",
+".......##.......",
+"..############..",
+"................",
+"................",
+"................",
+"................"};
diff --git a/src/xpm/file.xpm b/src/xpm/file.xpm
new file mode 100644 (file)
index 0000000..faf1472
--- /dev/null
@@ -0,0 +1,24 @@
+/* XPM */
+static const char *file_pm[]={
+"16 16 5 1",
+". c None",
+"# c #000000",
+"c c #c0c0c0",
+"b c #dcdcdc",
+"a c #ffffff",
+"..#########.....",
+"..#aaaaaabb#....",
+"..#aaaaaacab#...",
+"..#aaaaaacaab#..",
+"..#aaaaaac####..",
+"..#aaaaaaaccc#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..############.."};
diff --git a/src/xpm/filenew.xpm b/src/xpm/filenew.xpm
new file mode 100644 (file)
index 0000000..2543c9b
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char * filenew[] = {
+"10 14 5 1",
+"      c None",
+".     c #000000",
+"+     c #FFFFFF",
+"@     c #DCDCDC",
+"#     c #C0C0C0",
+".......   ",
+".++++@@.  ",
+".++++#+@. ",
+".++++#++@.",
+".++++#....",
+".+++++###.",
+".++++++++.",
+".++++++++.",
+".++++++++.",
+".++++++++.",
+".++++++++.",
+".++++++++.",
+".++++++++.",
+".........."};
diff --git a/src/xpm/fileopen.xpm b/src/xpm/fileopen.xpm
new file mode 100644 (file)
index 0000000..880417e
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *fileopen[] = {
+"    16    13        5            1",
+". c #040404",
+"# c #808304",
+"a c None",
+"b c #f3f704",
+"c c #f3f7f3",
+"aaaaaaaaa...aaaa",
+"aaaaaaaa.aaa.a.a",
+"aaaaaaaaaaaaa..a",
+"a...aaaaaaaa...a",
+".bcb.......aaaaa",
+".cbcbcbcbc.aaaaa",
+".bcbcbcbcb.aaaaa",
+".cbcb...........",
+".bcb.#########.a",
+".cb.#########.aa",
+".b.#########.aaa",
+"..#########.aaaa",
+"...........aaaaa"
+};
diff --git a/src/xpm/fileprint.xpm b/src/xpm/fileprint.xpm
new file mode 100644 (file)
index 0000000..6ada912
--- /dev/null
@@ -0,0 +1,24 @@
+/* XPM */
+static const char *fileprint[] = {
+"    16    14        6            1",
+". c #000000",
+"# c #848284",
+"a c #c6c3c6",
+"b c #ffff00",
+"c c #ffffff",
+"d c None",
+"ddddd.........dd",
+"dddd.cccccccc.dd",
+"dddd.c.....c.ddd",
+"ddd.cccccccc.ddd",
+"ddd.c.....c....d",
+"dd.cccccccc.a.a.",
+"d..........a.a..",
+".aaaaaaaaaa.a.a.",
+".............aa.",
+".aaaaaa###aa.a.d",
+".aaaaaabbbaa...d",
+".............a.d",
+"d.aaaaaaaaa.a.dd",
+"dd...........ddd"
+};
diff --git a/src/xpm/filesave.xpm b/src/xpm/filesave.xpm
new file mode 100644 (file)
index 0000000..ed3ea96
--- /dev/null
@@ -0,0 +1,21 @@
+/* XPM */
+static const char *filesave[] = {
+"    14    14        3            1",
+". c #040404",
+"# c #808304",
+"a c #bfc2bf",
+"..............",
+".#.aaaaaaaa.a.",
+".#.aaaaaaaa...",
+".#.aaaaaaaa.#.",
+".#.aaaaaaaa.#.",
+".#.aaaaaaaa.#.",
+".#.aaaaaaaa.#.",
+".##........##.",
+".############.",
+".##.........#.",
+".##......aa.#.",
+".##......aa.#.",
+".##......aa.#.",
+"a............."
+};
diff --git a/src/xpm/folder.xpm b/src/xpm/folder.xpm
new file mode 100644 (file)
index 0000000..7b2edcd
--- /dev/null
@@ -0,0 +1,24 @@
+/* XPM */
+static const char *folder_pm[]={
+"16 16 5 1",
+". c None",
+"# c #040404",
+"c c #808304",
+"a c #f3f704",
+"b c #f3f7f3",
+"................",
+"................",
+"................",
+".###............",
+"#aba#######.....",
+"#babababab#.....",
+"#ababababa#.....",
+"#baba###########",
+"#aba#ccccccccc#.",
+"#ba#ccccccccc#..",
+"#a#ccccccccc#...",
+"##ccccccccc#....",
+"###########.....",
+"................",
+"................",
+"................"};
diff --git a/src/xpm/iconA.xpm b/src/xpm/iconA.xpm
new file mode 100644 (file)
index 0000000..4e44f9f
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *iconA[]={
+"16 16 3 1",
+"  c #0080FF",
+"# c #000000",
+". c None",
+"................",
+"................",
+"......###.......",
+".....#   #......",
+"....#  #  #.....",
+"...#  #.#  #....",
+"...# #...# #....",
+"...# #...# #....",
+"...# ##### #....",
+"...#       #....",
+"...# ##### #....",
+"...# #...# #....",
+"...###...###....",
+"................",
+"................",
+"................"};
diff --git a/src/xpm/iconB.xpm b/src/xpm/iconB.xpm
new file mode 100644 (file)
index 0000000..9405ee8
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *iconB[]={
+"16 16 3 1",
+"  c #0080FF",
+"# c #000000",
+". c None",
+"................",
+"................",
+"...#######......",
+"...#      #.....",
+"...# ####  #....",
+"...# #...# #....",
+"...# ####  #....",
+"...#      #.....",
+"...# ####  #....",
+"...# #...# #....",
+"...# ####  #....",
+"...#      #.....",
+"...#######......",
+"................",
+"................",
+"................"};
diff --git a/src/xpm/iconC.xpm b/src/xpm/iconC.xpm
new file mode 100644 (file)
index 0000000..56b7315
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *iconC[]={
+"16 16 3 1",
+"  c #0080FF",
+"# c #000000",
+". c None",
+"................",
+"................",
+"......####......",
+".....#    #.....",
+"....#  ### #....",
+"...#  #...##....",
+"...# #..........",
+"...# #..........",
+"...# #..........",
+"...# #..........",
+"...#  #...##....",
+"....#  ### #....",
+".....#    #.....",
+"......####......",
+"................",
+"................"};
diff --git a/src/xpm/link_arrow.xpm b/src/xpm/link_arrow.xpm
new file mode 100644 (file)
index 0000000..2ab91e8
--- /dev/null
@@ -0,0 +1,24 @@
+/* XPM */
+static const char *link_arrow[]={
+"16 16 5 1",
+". c None",
+"b c #000000",
+"# c #585858",
+"c c #dcdcdc",
+"a c #ffffff",
+"................",
+"................",
+"................",
+"................",
+"................",
+"................",
+"................",
+"................",
+"########........",
+"#aaaaaab........",
+"#aabbbab........",
+"#aac#bab........",
+"#acbcbab........",
+"#abcaaab........",
+"#aaaaaab........",
+"#bbbbbbb........"};
diff --git a/src/xpm/nextunsolved.xpm b/src/xpm/nextunsolved.xpm
new file mode 100644 (file)
index 0000000..0775687
--- /dev/null
@@ -0,0 +1,23 @@
+/* XPM */
+static const char *nextunsolved[]={
+"16 16 4 1",
+". c None",
+"  c #0080ff",
+"# c #000000",
+"a c #ff0000",
+"..############..",
+"...#        #...",
+"....#      #....",
+".....#    #.....",
+"......#  #......",
+"..############..",
+"...#        #...",
+"....#      #....",
+".....#    #.....",
+"......#  #......",
+"..############..",
+"...#aaaaaaaa#...",
+"....#aaaaaa#....",
+".....#aaaa#.....",
+"......#aa#......",
+".......##......."};
diff --git a/src/xpm/prevunsolved.xpm b/src/xpm/prevunsolved.xpm
new file mode 100644 (file)
index 0000000..d8d175c
--- /dev/null
@@ -0,0 +1,23 @@
+/* XPM */
+static const char *prevunsolved[]={
+"16 16 4 1",
+"  c #0080ff",
+"# c #000000",
+"a c #ff0000",
+". c None",
+".......##.......",
+"......#aa#......",
+".....#aaaa#.....",
+"....#aaaaaa#....",
+"...#aaaaaaaa#...",
+"..############..",
+"......#  #......",
+".....#    #.....",
+"....#      #....",
+"...#        #...",
+"..############..",
+"......#  #......",
+".....#    #.....",
+"....#      #....",
+"...#        #...",
+"..############.."};
diff --git a/src/xpm/reload.xpm b/src/xpm/reload.xpm
new file mode 100644 (file)
index 0000000..d54fec3
--- /dev/null
@@ -0,0 +1,74 @@
+/* XPM */
+static const char *reloadIcon[]={
+"16 16 55 1",
+". c None",
+"e c #25502a",
+"# c #25512b",
+"d c #25522b",
+"g c #26552c",
+"c c #27562e",
+"n c #27582f",
+"b c #28592e",
+"M c #285930",
+"a c #295a2f",
+"q c #295a30",
+"G c #295c31",
+"t c #2a5e31",
+"y c #2b6635",
+"U c #2b6636",
+"Q c #2f703a",
+"H c #327b3d",
+"0 c #36843f",
+"W c #388943",
+"u c #3f7046",
+"r c #42764a",
+"f c #44754b",
+"A c #488653",
+"N c #50995b",
+"K c #529d5f",
+"J c #529f60",
+"m c #53885c",
+"l c #55a161",
+"B c #57a863",
+"R c #5aaa66",
+"I c #5aad69",
+"v c #5baa67",
+"X c #5cb16b",
+"o c #5db469",
+"k c #5eb56c",
+"z c #5eb66b",
+"s c #5fb26d",
+"V c #64b171",
+"Y c #64c274",
+"j c #69c779",
+"Z c #6dc97d",
+"p c #729a77",
+"O c #73c782",
+"i c #7ace89",
+"w c #7bce89",
+"C c #7ecb8b",
+"L c #80d191",
+"h c #80d193",
+"S c #8dd49b",
+"P c #95d8a1",
+"D c #a7ddb1",
+"x c #bde3c2",
+"T c #c0e5c5",
+"E c #daf0de",
+"F c #f9fdf9",
+"................",
+"..#abcde#df.....",
+"..ghhhijklm.....",
+"..nhoooooop.....",
+"..qho....rso....",
+"..tho...uvwxo...",
+"..yhz..ABCDEFo..",
+"gGHhIJJAAKLooo..",
+"MNOPEFo..Qho....",
+".eRSTo...Uho....",
+"..eV.....Uho....",
+"...W.....Qho....",
+"....nXYZihho....",
+"....0ooooooo....",
+"................",
+"................"};
diff --git a/src/xpm/showequalfiles.xpm b/src/xpm/showequalfiles.xpm
new file mode 100644 (file)
index 0000000..9fa2e3b
--- /dev/null
@@ -0,0 +1,23 @@
+/* XPM */
+static const char *showequalfiles[]={
+"16 16 4 1",
+"# c None",
+"a c None",
+". c #000000",
+"b c #00ff00",
+"...........##aaa",
+".bbbb.bbbb.##aaa",
+".bbbb.bbbb.##aaa",
+".bbbb.bbbb.##aaa",
+".bbbb.bbbb.##aaa",
+"...........##aaa",
+"aaaaaaaaaaaaaaaa",
+"................",
+"aaaaaaaaaaaaaaaa",
+"................",
+".bbbb.bbbb.bbbb.",
+".bbbb.bbbb.bbbb.",
+".bbbb.bbbb.bbbb.",
+".bbbb.bbbb.bbbb.",
+"................",
+"aaaaaaaaaaaaaaaa"};
diff --git a/src/xpm/showfilesonlyina.xpm b/src/xpm/showfilesonlyina.xpm
new file mode 100644 (file)
index 0000000..041b54d
--- /dev/null
@@ -0,0 +1,23 @@
+/* XPM */
+static const char *showfilesonlyina[]={
+"16 16 4 1",
+"# c None",
+"a c None",
+". c #000000",
+"b c #00ff00",
+"...........##aaa",
+".bbbb......##aaa",
+".bbbb......##aaa",
+".bbbb......##aaa",
+".bbbb......##aaa",
+"...........##aaa",
+"aaaaaaaaaaaaaaaa",
+"................",
+"aaaaaaaaaaaaaaaa",
+"................",
+".bbbb...........",
+".bbbb...........",
+".bbbb...........",
+".bbbb...........",
+"................",
+"aaaaaaaaaaaaaaaa"};
diff --git a/src/xpm/showfilesonlyinb.xpm b/src/xpm/showfilesonlyinb.xpm
new file mode 100644 (file)
index 0000000..80caaca
--- /dev/null
@@ -0,0 +1,23 @@
+/* XPM */
+static const char *showfilesonlyinb[]={
+"16 16 4 1",
+"# c None",
+"a c None",
+". c #000000",
+"b c #00ff00",
+"...........##aaa",
+"......bbbb.##aaa",
+"......bbbb.##aaa",
+"......bbbb.##aaa",
+"......bbbb.##aaa",
+"...........##aaa",
+"aaaaaaaaaaaaaaaa",
+"................",
+"aaaaaaaaaaaaaaaa",
+"................",
+"......bbbb......",
+"......bbbb......",
+"......bbbb......",
+"......bbbb......",
+"................",
+"aaaaaaaaaaaaaaaa"};
diff --git a/src/xpm/showfilesonlyinc.xpm b/src/xpm/showfilesonlyinc.xpm
new file mode 100644 (file)
index 0000000..5f548a4
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *showfilesonlyinc[]={
+"16 16 3 1",
+". c None",
+"# c #000000",
+"a c #00ff00",
+"................",
+"................",
+"................",
+"................",
+"................",
+"################",
+"###########aaaa#",
+"###########aaaa#",
+"###########aaaa#",
+"###########aaaa#",
+"################",
+"................",
+"................",
+"................",
+"................",
+"................"};
diff --git a/src/xpm/showlinenumbers.xpm b/src/xpm/showlinenumbers.xpm
new file mode 100644 (file)
index 0000000..fb697dc
--- /dev/null
@@ -0,0 +1,21 @@
+/* XPM */
+static const char *showlinenumbers[]={
+"16 16 2 1",
+". c None",
+"# c #000040",
+"................",
+"................",
+"................",
+"................",
+"...#...##..###..",
+"..##..#..#....#.",
+"...#.....#....#.",
+"...#....#...##..",
+"...#...#......#.",
+"...#..#.......#.",
+"..###.####.###..",
+"................",
+"................",
+"................",
+"................",
+"................"};
diff --git a/src/xpm/showwhitespace.xpm b/src/xpm/showwhitespace.xpm
new file mode 100644 (file)
index 0000000..2112e91
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *showwhitespace[]={
+"16 16 3 1",
+". c None",
+"# c #000000",
+"a c #ffffff",
+"................",
+"................",
+"..############..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..#aaaaaaaaaa#..",
+"..############..",
+"................",
+"................"};
diff --git a/src/xpm/showwhitespacechars.xpm b/src/xpm/showwhitespacechars.xpm
new file mode 100644 (file)
index 0000000..0a637ae
--- /dev/null
@@ -0,0 +1,21 @@
+/* XPM */
+static const char *showwhitespacechars[]={
+"16 16 2 1",
+". c None",
+"# c #000040",
+"................",
+"................",
+"................",
+"................",
+"................",
+"................",
+"................",
+"................",
+"................",
+"................",
+".####.####.####.",
+".####.####.####.",
+"................",
+"................",
+"................",
+"................"};
diff --git a/src/xpm/startmerge.xpm b/src/xpm/startmerge.xpm
new file mode 100644 (file)
index 0000000..7162719
--- /dev/null
@@ -0,0 +1,25 @@
+/* XPM */
+static const char *startmerge[]={
+"16 16 6 1",
+". c None",
+"# c #000000",
+"b c #0000ff",
+"c c #00ffff",
+"d c #ff0000",
+"a c #ffff00",
+".......##.......",
+"......#aa#......",
+"......#aa#......",
+"...b.b.##.b.b...",
+"...bb......bb...",
+"...bbb....bbb...",
+".##..........##.",
+"#cc#........#cc#",
+"#cc#........#cc#",
+".##.b.b..b.b.##.",
+".....bb..bb.....",
+"....bbb..bbb....",
+".......##.......",
+"......#dd#......",
+"......#dd#......",
+".......##......."};
diff --git a/src/xpm/up1arrow.xpm b/src/xpm/up1arrow.xpm
new file mode 100644 (file)
index 0000000..3e144ba
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *up1arrow[]={
+"16 16 3 1",
+". c None",
+"# c #000000",
+"a c #0080ff",
+"................",
+"................",
+"................",
+"................",
+"................",
+".......##.......",
+"......#aa#......",
+".....#aaaa#.....",
+"....#aaaaaa#....",
+"...#aaaaaaaa#...",
+"..############..",
+"................",
+"................",
+"................",
+"................",
+"................"};
diff --git a/src/xpm/up2arrow.xpm b/src/xpm/up2arrow.xpm
new file mode 100644 (file)
index 0000000..ebe933b
--- /dev/null
@@ -0,0 +1,25 @@
+/* XPM */
+static const char *up2arrow[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 3 1",
+"  c #0080ff",
+"# c #000000",
+". c None",
+/* pixels */
+"................",
+"................",
+".......##.......",
+"......#  #......",
+".....#    #.....",
+"....#      #....",
+"...#        #...",
+"..############..",
+".......##.......",
+"......#  #......",
+".....#    #.....",
+"....#      #....",
+"...#        #...",
+"..############..",
+"................",
+"................"
+};
diff --git a/src/xpm/upend.xpm b/src/xpm/upend.xpm
new file mode 100644 (file)
index 0000000..167433d
--- /dev/null
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *upend[]={
+"16 16 3 1",
+"  c #0080ff",
+"# c #000000",
+". c None",
+"................",
+"................",
+"................",
+"................",
+"..############..",
+".......##.......",
+"......#  #......",
+".....#    #.....",
+"....#      #....",
+"...#        #...",
+"..############..",
+"................",
+"................",
+"................",
+"................",
+"................"};
diff --git a/test/alignmenttest.cpp b/test/alignmenttest.cpp
new file mode 100644 (file)
index 0000000..ac79cee
--- /dev/null
@@ -0,0 +1,500 @@
+// vim:sw=3:ts=3:expandtab
+
+#include <iostream>
+#include <stdio.h>
+
+#include <QDirIterator>
+#include <QTextCodec>
+#include <QTextStream>
+
+#include "diff.h"
+#include "gnudiff_diff.h"
+#include "options.h"
+#include "progress.h"
+
+#define i18n(s) s
+
+bool verbose = false;
+Options *m_pOptions = NULL;
+ManualDiffHelpList m_manualDiffHelpList;
+
+bool g_bIgnoreWhiteSpace = true;
+bool g_bIgnoreTrivialMatches = true;
+
+
+void printDiffList(const QString caption, const DiffList &diffList)
+{
+   QTextStream out(stdout);
+   DiffList::const_iterator i;
+
+   out << "Printing difflist " << caption << ":" << endl;
+   out << "  nofEquals, diff1, diff2" << endl;
+
+   for(i = diffList.begin(); i != diffList.end(); i++)
+   {
+      out << "  " << i->nofEquals << "," << i->diff1 << "," << i->diff2 << endl;
+   }
+}
+
+void printDiff3List(const Diff3LineList &diff3LineList,
+                   const SourceData &sd1,
+                   const SourceData &sd2,
+                   const SourceData &sd3,
+                   bool forceVerbosity=false)
+{
+   const int columnsize = 30;
+   const int linenumsize = 6;
+   Diff3LineList::const_iterator i;
+   for ( i=diff3LineList.begin(); i!=diff3LineList.end(); ++i )
+   {
+      QTextStream out(stdout);
+      QString lineAText;
+      QString lineBText;
+      QString lineCText;
+
+      const Diff3Line& d3l = *i;
+
+      if(d3l.lineA != -1)
+      {
+         const LineData *pLineData = &sd1.getLineDataForDiff()[d3l.lineA];
+         lineAText = QString(pLineData->pLine, pLineData->size);
+         lineAText.replace(QString("\r"), QString("\\r"));
+         lineAText.replace(QString("\n"), QString("\\n"));
+         lineAText = QString("%1 %2").arg(d3l.lineA, linenumsize).arg(lineAText.left(columnsize - linenumsize - 1));
+      }
+
+      if(d3l.lineB != -1)
+      {
+         const LineData *pLineData = &sd2.getLineDataForDiff()[d3l.lineB];
+         lineBText = QString(pLineData->pLine, pLineData->size);
+         lineBText.replace(QString("\r"), QString("\\r"));
+         lineBText.replace(QString("\n"), QString("\\n"));
+         lineBText = QString("%1 %2").arg(d3l.lineB, linenumsize).arg(lineBText.left(columnsize - linenumsize - 1));
+      }
+
+      if(d3l.lineC != -1)
+      {
+         const LineData *pLineData = &sd3.getLineDataForDiff()[d3l.lineC];
+         lineCText = QString(pLineData->pLine, pLineData->size);
+         lineCText.replace(QString("\r"), QString("\\r"));
+         lineCText.replace(QString("\n"), QString("\\n"));
+         lineCText = QString("%1 %2").arg(d3l.lineC, linenumsize).arg(lineCText.left(columnsize - linenumsize - 1));
+      }
+
+      out << QString("%1 %2 %3").arg(lineAText, -columnsize)
+                                .arg(lineBText, -columnsize)
+                                .arg(lineCText, -columnsize);
+      if(verbose || forceVerbosity)
+      {
+         out << " " << d3l.bAEqB << " " << d3l.bBEqC << " " << d3l.bAEqC;
+      }
+
+      out << endl;
+   }
+}
+
+void printDiff3List(QString caption,
+                   const Diff3LineList &diff3LineList,
+                   const SourceData &sd1,
+                   const SourceData &sd2,
+                   const SourceData &sd3,
+                   bool forceVerbosity=false)
+{
+   QTextStream out(stdout);
+   out << "Printing diff3list " << caption << ":" << endl;
+   printDiff3List(diff3LineList, sd1, sd2, sd3, forceVerbosity);
+}
+
+void determineFileAlignment(SourceData &m_sd1, SourceData &m_sd2, SourceData &m_sd3, Diff3LineList &m_diff3LineList)
+{
+   DiffList m_diffList12;
+   DiffList m_diffList23;
+   DiffList m_diffList13;
+
+   m_diff3LineList.clear();
+
+   // Run the diff.
+   if ( m_sd3.isEmpty() )
+   {
+      runDiff( m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12,1,2,
+               &m_manualDiffHelpList, m_pOptions);
+      calcDiff3LineListUsingAB( &m_diffList12, m_diff3LineList );
+      fineDiff( m_diff3LineList, 1, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay() );
+   }
+   else
+   {
+      runDiff( m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12,1,2,
+               &m_manualDiffHelpList, m_pOptions);
+      runDiff( m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList23,2,3,
+               &m_manualDiffHelpList, m_pOptions);
+      runDiff( m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList13,1,3,
+               &m_manualDiffHelpList, m_pOptions);
+
+      if (verbose)
+      {
+         printDiffList("m_diffList12", m_diffList12);
+         printDiffList("m_diffList23", m_diffList23);
+         printDiffList("m_diffList13", m_diffList13);
+      }
+
+      calcDiff3LineListUsingAB( &m_diffList12, m_diff3LineList );
+      if (verbose) printDiff3List("after calcDiff3LineListUsingAB", m_diff3LineList, m_sd1, m_sd2, m_sd3);
+
+      calcDiff3LineListUsingAC( &m_diffList13, m_diff3LineList );
+      if (verbose) printDiff3List("after calcDiff3LineListUsingAC", m_diff3LineList, m_sd1, m_sd2, m_sd3);
+
+      correctManualDiffAlignment( m_diff3LineList, &m_manualDiffHelpList );
+      calcDiff3LineListTrim( m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList );
+      if (verbose) printDiff3List("after 1st calcDiff3LineListTrim", m_diff3LineList, m_sd1, m_sd2, m_sd3);
+
+      if ( m_pOptions->m_bDiff3AlignBC )
+      {
+         calcDiff3LineListUsingBC( &m_diffList23, m_diff3LineList );
+         if (verbose) printDiff3List("after calcDiff3LineListUsingBC", m_diff3LineList, m_sd1, m_sd2, m_sd3);
+         correctManualDiffAlignment( m_diff3LineList, &m_manualDiffHelpList );
+         calcDiff3LineListTrim( m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList );
+         if (verbose) printDiff3List("after 2nd calcDiff3LineListTrim", m_diff3LineList, m_sd1, m_sd2, m_sd3);
+      }
+
+      fineDiff( m_diff3LineList, 1, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay() );
+      fineDiff( m_diff3LineList, 2, m_sd2.getLineDataForDisplay(), m_sd3.getLineDataForDisplay() );
+      fineDiff( m_diff3LineList, 3, m_sd3.getLineDataForDisplay(), m_sd1.getLineDataForDisplay() );
+   }
+   calcWhiteDiff3Lines( m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff() );
+}
+
+QString getLineFromSourceData(const SourceData &sd, int line)
+{
+   const LineData *pLineData = &sd.getLineDataForDiff()[line];
+   QString lineText = QString(pLineData->pLine, pLineData->size);
+   lineText.replace(QString("\r"), QString("\\r"));
+   lineText.replace(QString("\n"), QString("\\n"));
+   return lineText;
+}
+
+
+void loadExpectedAlignmentFile(QString expectedResultFileName, Diff3LineList &expectedDiff3LineList)
+{
+   Diff3Line d3l;
+
+   expectedDiff3LineList.clear();
+
+   QFile file(expectedResultFileName);
+   QString line;
+   if ( file.open(QIODevice::ReadOnly) )
+   {
+      QTextStream t( &file );
+      while ( !t.atEnd() )
+      {
+         QStringList lst = t.readLine().split(QRegExp("\\s+"));
+         d3l.lineA = lst.at(0).toInt();
+         d3l.lineB = lst.at(1).toInt();
+         d3l.lineC = lst.at(2).toInt();
+
+         expectedDiff3LineList.push_back( d3l );
+      }
+      file.close();
+   }
+}
+
+void writeActualAlignmentFile(QString actualResultFileName, const Diff3LineList &actualDiff3LineList)
+{
+   Diff3LineList::const_iterator p_d3l;
+
+   QFile file(actualResultFileName);
+   if ( file.open(QIODevice::WriteOnly) )
+   {
+      {
+         QTextStream t( &file );
+
+         for(p_d3l = actualDiff3LineList.begin(); p_d3l != actualDiff3LineList.end(); p_d3l++)
+         {
+            t << p_d3l->lineA << " " << p_d3l->lineB << " " << p_d3l->lineC << endl;
+         }
+      }
+      file.close();
+   }
+}
+
+bool dataIsConsistent(int line1, QString &line1Text, int line2, QString &line2Text, bool equal)
+{
+   bool consistent = false;
+
+   if(line1 == -1 || line2 == -1)
+   {
+      consistent = !equal;
+   }
+   else
+   {
+      /* If the equal boolean is true the line content must be the same,
+       * if the line content is different the boolean should be false,
+       * but other than that we can't be sure:
+       * - if the line content is the same the boolean may not be true because
+       *   GNU diff may have put that line as a removal in the first file and
+       *   an addition in the second.
+       * - also the comparison this test does between lines considers all
+       *   whitespace equal, while GNU diff doesn't (for instance U+0020 vs U+00A0)
+       */
+      if(equal)
+      {
+         consistent = (line1Text == line2Text);
+      }
+      else if (line1Text != line2Text)
+      {
+         consistent = !equal;
+      }
+      else
+      {
+         consistent = true;
+      }
+
+   }
+   return consistent;
+}
+
+bool runTest(QString file1, QString file2, QString file3, QString expectedResultFile, QString actualResultFile, int maxLength)
+{
+   Options options;
+   Diff3LineList actualDiff3LineList, expectedDiff3LineList;
+   QTextCodec *p_codec = QTextCodec::codecForName("UTF-8");
+   QTextStream out(stdout);
+
+   options.m_bIgnoreCase = false;
+   options.m_bPreserveCarriageReturn = false;
+   options.m_bDiff3AlignBC = true;
+
+   m_pOptions = &options;
+
+   SourceData m_sd1, m_sd2, m_sd3;
+
+   QString msgprefix = "Running test with ";
+   QString filepattern = QString(file1).replace("_base.", "_*.");
+   QString msgsuffix = QString("...%1").arg("", maxLength - filepattern.length());
+   out << msgprefix << filepattern << msgsuffix;
+   if(verbose)
+   {
+      out << endl;
+   }
+   out.flush();
+
+   m_sd1.setOptions(&options);
+   m_sd1.setFilename(file1);
+   m_sd1.readAndPreprocess(p_codec, false);
+
+   m_sd2.setOptions(&options);
+   m_sd2.setFilename(file2);
+   m_sd2.readAndPreprocess(p_codec, false);
+
+   m_sd3.setOptions(&options);
+   m_sd3.setFilename(file3);
+   m_sd3.readAndPreprocess(p_codec, false);
+
+   determineFileAlignment(m_sd1, m_sd2, m_sd3, actualDiff3LineList);
+
+   loadExpectedAlignmentFile(expectedResultFile, expectedDiff3LineList);
+
+   Diff3LineList::iterator p_actual = actualDiff3LineList.begin();
+   Diff3LineList::iterator p_expected = expectedDiff3LineList.begin();
+   bool equal = true;
+   bool sequenceError = false;
+   bool consistencyError = false;
+
+   equal = (actualDiff3LineList.size() == expectedDiff3LineList.size());
+
+   int latestLineA = -1;
+   int latestLineB = -1;
+   int latestLineC = -1;
+   while(equal && (p_actual != actualDiff3LineList.end()))
+   {
+      /* Check if all line numbers are in sequence */
+      if(p_actual->lineA != -1)
+      {
+         if(p_actual->lineA <= latestLineA)
+         {
+            sequenceError = true;
+         }
+         else
+         {
+            latestLineA = p_actual->lineA;
+         }
+      }
+      if(p_actual->lineB != -1)
+      {
+         if(p_actual->lineB <= latestLineB)
+         {
+            sequenceError = true;
+         }
+         else
+         {
+            latestLineB = p_actual->lineB;
+         }
+      }
+      if(p_actual->lineC != -1)
+      {
+         if(p_actual->lineC <= latestLineC)
+         {
+            sequenceError = true;
+         }
+         else
+         {
+            latestLineC = p_actual->lineC;
+         }
+      }
+
+      /* Check if the booleans that indicate if lines are equal are consistent with the content of the lines */
+      QString lineAText = (p_actual->lineA == -1) ? "" : getLineFromSourceData(m_sd1, p_actual->lineA).simplified().replace(" ", "");
+      QString lineBText = (p_actual->lineB == -1) ? "" : getLineFromSourceData(m_sd2, p_actual->lineB).simplified().replace(" ", "");
+      QString lineCText = (p_actual->lineC == -1) ? "" : getLineFromSourceData(m_sd3, p_actual->lineC).simplified().replace(" ", "");
+
+      if(!dataIsConsistent(p_actual->lineA, lineAText, p_actual->lineB, lineBText, p_actual->bAEqB))
+      {
+         if(verbose) out << "inconsistency: line " << p_actual->lineA << " of A vs line " << p_actual->lineB << " of B" << endl;
+         consistencyError = true;
+      }
+      if(!dataIsConsistent(p_actual->lineB, lineBText, p_actual->lineC, lineCText, p_actual->bBEqC))
+      {
+         if(verbose) out << "inconsistency: line " << p_actual->lineB << " of B vs line " << p_actual->lineC << " of C" << endl;
+         consistencyError = true;
+      }
+      if(!dataIsConsistent(p_actual->lineA, lineAText, p_actual->lineC, lineCText, p_actual->bAEqC))
+      {
+         if(verbose) out << "inconsistency: line " << p_actual->lineA << " of A vs line " << p_actual->lineC << " of C" << endl;
+         consistencyError = true;
+      }
+
+      /* Check if the actual output of the algorithm is equal to the expected output */
+      equal = (p_actual->lineA == p_expected->lineA) &&
+              (p_actual->lineB == p_expected->lineB) &&
+              (p_actual->lineC == p_expected->lineC);
+      p_actual++;
+      p_expected++;
+   }
+
+   if(sequenceError)
+   {
+      out << "NOK" << endl;
+
+      out << "Actual result has incorrectly sequenced line numbers:" << endl;
+      out << "----------------------------------------------------------------------------------------------" << endl;
+      printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3);
+   }
+   else if(consistencyError)
+   {
+      out << "NOK" << endl;
+
+      out << "Actual result has inconsistent equality booleans:" << endl;
+      out << "----------------------------------------------------------------------------------------------" << endl;
+      printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3, true);
+   }
+   else if(equal)
+   {
+      out << "OK" << endl;
+   }
+   else
+   {
+      out << "NOK" << endl;
+
+      writeActualAlignmentFile(actualResultFile, actualDiff3LineList);
+
+      out << "Actual result (written to " << actualResultFile << "):" << endl;
+      out << "----------------------------------------------------------------------------------------------" << endl;
+      printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3);
+      out << "----------------------------------------------------------------------------------------------" << endl;
+      out << "Expected result:" << endl;
+      out << "----------------------------------------------------------------------------------------------" << endl;
+      printDiff3List(expectedDiff3LineList, m_sd1, m_sd2, m_sd3);
+      out << "----------------------------------------------------------------------------------------------" << endl;
+   }
+
+   return equal;
+}
+
+
+QStringList gettestdatafiles(QString testdir)
+{
+   QStringList baseFilePaths;
+   QTextStream out(stdout);
+   QStringList nameFilter;
+   nameFilter << "*_base.*";
+
+   QDir testdatadir(testdir);
+
+   QStringList baseFileNames = testdatadir.entryList(nameFilter, QDir::Files, QDir::Name);
+   QListIterator<QString> file_it(baseFileNames);
+   while(file_it.hasNext())
+   {
+      baseFilePaths.append(testdir + "/" + file_it.next());
+   }
+   out << testdir << ": " << baseFilePaths.size() << " files" << endl;
+
+
+   QStringList subdirs = testdatadir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
+   QListIterator<QString> dir_it(subdirs);
+
+   while (dir_it.hasNext())
+   {
+      QString subdir = dir_it.next();
+      QStringList subdirBaseFilePaths = gettestdatafiles(testdir + "/" + subdir);
+
+      baseFilePaths.append(subdirBaseFilePaths);
+   }
+
+   return baseFilePaths;
+}
+
+
+int main(int argc, char *argv[])
+{
+   bool allOk = true;
+   int maxLength = 0;
+   QTextStream out(stdout);
+   QDir testdatadir("testdata");
+
+   /* Print data at various steps in the algorithm to get an idea where to look for the root cause of a failing test */
+   if((argc == 2) && (!strcmp(argv[1], "-v")))
+   {
+      verbose = true;
+   }
+
+   QStringList baseFiles = gettestdatafiles("testdata");
+   QListIterator<QString> it(baseFiles);
+
+   for (int i = 0; i < baseFiles.size(); i++)
+   {
+      maxLength = std::max(baseFiles.at(i).length(), maxLength);
+   }
+   maxLength += testdatadir.path().length() + 1;
+
+   while (it.hasNext())
+   {
+      QString fileName = it.next();
+
+      QRegExp baseFileRegExp("(.*)_base\\.(.*)");
+      baseFileRegExp.exactMatch(fileName);
+
+      QString prefix = baseFileRegExp.cap(1);
+      QString suffix = baseFileRegExp.cap(2);
+
+      QString contrib1FileName(prefix + "_contrib1." + suffix);
+      QString contrib2FileName(prefix + "_contrib2." + suffix);
+      QString expectedResultFileName(prefix + "_expected_result." + suffix);
+      QString actualResultFileName(prefix + "_actual_result." + suffix);
+
+      if(QFile(contrib1FileName).exists() &&
+         QFile(contrib2FileName).exists() &&
+         QFile(expectedResultFileName).exists())
+      {
+         bool ok = runTest(fileName, contrib1FileName, contrib2FileName, expectedResultFileName, actualResultFileName, maxLength);
+
+         allOk = allOk && ok;
+      }
+      else
+      {
+         out << "Skipping " << fileName << " " << contrib1FileName << " " << contrib2FileName << " " << expectedResultFileName << " " << endl;
+      }
+   }
+
+   out << (allOk ? "All OK" : "Not all OK") << endl;
+
+   return allOk ? 0 : -1;
+}
diff --git a/test/fakefileaccess.cpp b/test/fakefileaccess.cpp
new file mode 100644 (file)
index 0000000..af754ca
--- /dev/null
@@ -0,0 +1,107 @@
+#include <assert.h>
+#include "fileaccess.h"
+
+FileAccess::FileAccess()
+{
+}
+
+FileAccess::FileAccess(const QString& name, bool bWantToWrite)
+{
+  assert(!bWantToWrite);
+
+  m_name = name;
+}
+
+FileAccess::~FileAccess()
+{
+}
+
+//   FileAccess( const QString& name, bool bWantToWrite=false ); // name: local file or dirname or url (when supported)
+//   void setFile( const QString& name, bool bWantToWrite=false );
+//
+bool FileAccess::isValid() const
+{
+  return m_name.length() != 0;
+}
+
+//   bool isFile() const;
+//   bool isDir() const;
+//   bool isSymLink() const;
+bool FileAccess::exists() const
+{
+  
+}
+qint64 FileAccess::size() const
+{
+  
+}
+
+qint64 FileAccess::sizeForReading()
+{
+  
+}
+
+//   bool isReadable() const;
+//   bool isWritable() const;
+//   bool isExecutable() const;
+//   bool isHidden() const;
+//   QString readLink() const;
+//
+//   QDateTime   created()       const;
+//   QDateTime   lastModified()  const;
+//   QDateTime   lastRead()      const;
+//
+//   QString fileName() const; // Just the name-part of the path, without parent directories
+//   QString filePath() const; // The path-string that was used during construction
+QString FileAccess::prettyAbsPath() const
+{
+  
+}
+//   KUrl url() const;
+QString FileAccess::absoluteFilePath() const
+{
+  return "";
+}
+
+bool FileAccess::isLocal() const
+{
+  return true;
+}
+
+bool FileAccess::readFile(void* pDestBuffer, unsigned long maxLength )
+{
+  
+}
+bool FileAccess::writeFile(const void* pSrcBuffer, unsigned long length )
+{
+
+}
+
+//   bool listDir( t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden,
+//                 const QString& filePattern, const QString& fileAntiPattern,
+//                 const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore );
+bool FileAccess::copyFile( const QString& destUrl )
+{
+  
+}
+//   bool createBackup( const QString& bakExtension );
+//
+QString FileAccess::tempFileName()
+{
+  
+}
+
+bool FileAccess::removeTempFile( const QString& )
+{
+  
+}
+
+/*bool FileAccess::removeFile()
+{
+  
+}*/
+bool FileAccess::removeFile( const QString& )
+{
+  
+}
+
diff --git a/test/fakekdiff3_part.cpp b/test/fakekdiff3_part.cpp
new file mode 100644 (file)
index 0000000..2c47b5f
--- /dev/null
@@ -0,0 +1,7 @@
+extern "C"
+{
+    void* init_libkdiff3part()
+    {
+        return 0;
+    }
+}
diff --git a/test/fakeprogressproxy.cpp b/test/fakeprogressproxy.cpp
new file mode 100644 (file)
index 0000000..92c17db
--- /dev/null
@@ -0,0 +1,90 @@
+#include <assert.h>
+#include "progress.h"
+
+void ProgressDialog::delayedHide()
+{
+}
+
+void ProgressDialog::slotAbort()
+{
+}
+
+void ProgressDialog::reject()
+{
+}
+
+void ProgressDialog::timerEvent(QTimerEvent*)
+{
+}
+
+ProgressProxy::ProgressProxy()
+{
+}
+
+ProgressProxy::~ProgressProxy()
+{
+}
+
+void ProgressProxy::setInformation( const QString& info, bool bRedrawUpdate )
+{
+  /* Suppress warning about unused parameters */
+  (void)info;
+  (void)bRedrawUpdate;
+}
+
+void ProgressProxy::setInformation( const QString& info, int current, bool bRedrawUpdate )
+{
+  /* Suppress warning about unused parameters */
+  (void)info;
+  (void)current;
+  (void)bRedrawUpdate;
+}
+
+void ProgressProxy::setCurrent( qint64 current, bool bRedrawUpdate )
+{
+  /* Suppress warning about unused parameters */
+  (void)current;
+  (void)bRedrawUpdate;
+}
+
+void ProgressProxy::step( bool bRedrawUpdate )
+{
+  /* Suppress warning about unused parameters */
+  (void)bRedrawUpdate;
+}
+
+void ProgressProxy::setMaxNofSteps( qint64 dMaxNofSteps )
+{
+  /* Suppress warning about unused parameters */
+  (void)dMaxNofSteps;
+}
+
+
+
+bool ProgressProxy::wasCancelled()
+{
+  return false;
+}
+
+void ProgressProxy::enterEventLoop( KJob* pJob, const QString& jobInfo )
+{
+  /* Suppress warning about unused parameters */
+  (void)pJob;
+  (void)jobInfo;
+}
+
+void ProgressProxy::exitEventLoop()
+{
+}
+
+void ProgressDialog::recalc(bool bUpdate)
+{
+  /* Suppress warning about unused parameters */
+  (void)bUpdate;
+}
+
+QDialog *ProgressProxy::getDialog()
+{
+  return NULL;
+}
+
diff --git a/test/generate_testdata_from_git_merges.py b/test/generate_testdata_from_git_merges.py
new file mode 100755 (executable)
index 0000000..38d2cfb
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+
+import argparse
+import glob
+import os
+import subprocess as sp
+import sys
+
+parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
+                                 description='Generate input files for alignmenttest from the files merged for each merge commit in a git repository.\n\n' +
+                                             'This script finds all merge commits in the clone where it is run, checks which files were modified in both\n' +
+                                             'parents of the merge commit and then finds the common ancestor of these files to get the merge base.\n\n'
+                                             'Example:\n'
+                                             '  cd ~/git/linux\n'
+                                             '  ~/kdiff3/test/%s -d ~/kdiff3/test/testdata/linux\n' % os.path.basename(sys.argv[0]))
+
+parser.add_argument('-d', metavar='destination_path', nargs=1, default=['testdata_from_git/'],
+                    help='specify the directory where to save the test input files. If the directory does not exist it will be created.')
+args = parser.parse_args()
+dirname=args.d[0]
+
+print 'Generating input files in %s ...' % dirname
+sys.stdout.flush()
+
+if not os.path.exists(dirname):
+    os.makedirs(dirname)
+
+merges = sp.check_output('git rev-list --merges --parents master'.split()).strip()
+
+for entry in merges.splitlines():
+    fields = entry.split()
+
+    if len(fields) > 3:
+        print 'merge %s had more than 2 parents: %s' % (fields[0], fields)
+
+    merge, contrib1, contrib2 = fields[:3]
+
+    if glob.glob('%s/%s_*' % (dirname, merge)):
+        print 'skipping merge %s because files for this merge already present' % merge
+        continue
+
+    base = sp.check_output(('git merge-base %s %s' % (contrib1, contrib2)).split()).strip()
+
+    fileschanged1 = sp.check_output(('git diff --name-only %s %s' % (base, contrib1)).split()).strip().splitlines()
+    fileschanged2 = sp.check_output(('git diff --name-only %s %s' % (base, contrib2)).split()).strip().splitlines()
+
+    fileschangedboth = set(fileschanged1) & set(fileschanged2)
+
+    if not fileschangedboth:
+        print 'No files overlapped for merge %s' % merge
+    else:
+        print 'Overlapping files for merge %s with base %s: %s' % (merge, base, fileschangedboth)
+        for filename in fileschangedboth:
+            simplified_filename = filename.replace('/', '_').replace('.', '_')
+
+            try:
+                base_content = sp.check_output(('git show %s:%s' % (base, filename)).split())
+                contrib1_content = sp.check_output(('git show %s:%s' % (contrib1, filename)).split())
+                contrib2_content = sp.check_output(('git show %s:%s' % (contrib2, filename)).split())
+
+                if base_content == contrib1_content or \
+                   base_content == contrib2_content or \
+                   contrib1_content == contrib2_content:
+                   print 'this merge was trivial. Skipping.'
+                else:
+                    basefilename = '%s/%s_%s_base.txt' % (dirname, merge, simplified_filename)
+                    contrib1filename = '%s/%s_%s_contrib1.txt' % (dirname, merge, simplified_filename)
+                    contrib2filename = '%s/%s_%s_contrib2.txt' % (dirname, merge, simplified_filename)
+
+                    for filename, content in [(basefilename, base_content),
+                                              (contrib1filename, contrib1_content),
+                                              (contrib2filename, contrib2_content)]:
+                        with open(filename, 'wb') as f:
+                            f.write(content)
+
+                    with open('%s/%s_%s_expected_result.txt' % (dirname, merge, simplified_filename), 'a') as f:
+                        pass
+
+            except sp.CalledProcessError:
+                print 'error from git show, continuing with next file'
+
+print 'Input files generated.'
+print ''
+print 'To create a reference set of expected_result.txt files, run alignmenttest and copy/move all %s/*_actual_result.txt files to %s/*_expected_result.txt:' % (dirname, dirname)
+print '  ./alignmenttest > /dev/null'
+print '  cd %s' % dirname
+print '  for file in *_actual_result.txt; do mv ${file} ${file/actual/expected}; done'
+print 'If you\'ve already modified the algorithm, you can run the alignment test of an older version of kdiff3 and copy those result files over'
+
diff --git a/test/generate_testdata_from_permutations.py b/test/generate_testdata_from_permutations.py
new file mode 100755 (executable)
index 0000000..d3bf602
--- /dev/null
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+
+import argparse
+import os
+import random
+import sys
+
+dirname = 'testdata/permutations'
+
+defaultlines = ['aaa\n',
+                'bbb\n',
+                'ccc\n',
+                'ddd\n',
+                'eee\n']
+
+# For the lines of the A file only consider removing them because modifying
+# them ("diff") would be equivalent to modifying both B and C, so that will
+# be covered anyway.
+options = [ ('1','1','1'),
+            ('1','1','2'),
+            ('1','1',None),
+            ('1','2','1'),
+            ('1','2','2'),
+            ('1','2','3'),
+            ('1','2',None),
+            (None,'1','1'),
+            (None,'1','2'),
+            (None,'1',None),
+            (None,None,'1') ]
+
+def permutations(nr_of_options, count, currentlist):
+
+    if count == 0:
+        filename = ''.join([format(i, '1x') for i in currentlist])
+
+        baselines = []
+        contrib1lines = []
+        contrib2lines = []
+        for optionindex, defaultline in zip(currentlist, defaultlines):
+            option = options[optionindex]
+
+            if option[0]:
+                baselines.append(defaultline)
+
+            if option[1] == '1':
+                contrib1lines.append(defaultline)
+            elif option[1] == '2':
+                contrib1lines.append('xxx' + defaultline)
+
+            if option[2] == '1':
+                contrib2lines.append(defaultline)
+            elif option[2] == '2':
+                contrib2lines.append('xxx' + defaultline)
+            elif option[2] == '3':
+                contrib2lines.append('yyy' + defaultline)
+
+        with open('%s/perm_%s_base.txt' % (dirname, filename), 'wb') as f:
+            f.writelines(baselines)
+
+        with open('%s/perm_%s_contrib1.txt' % (dirname, filename), 'wb') as f:
+            f.writelines(contrib1lines)
+
+        with open('%s/perm_%s_contrib2.txt' % (dirname, filename), 'wb') as f:
+            f.writelines(contrib2lines)
+
+        with open('%s/perm_%s_expected_result.txt' % (dirname, filename), 'a') as f:
+            pass
+
+    else:
+        optionindices = random.sample(range(len(options)), nr_of_options)
+        for optionindex in optionindices:
+            permutations(nr_of_options, count - 1, [optionindex] + currentlist)
+
+
+parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
+                                 description='Generate input files for alignmenttest in ./testdata/permutations/ containing some or all permutations of 3 sets of 5 lines.\n\n' +
+                                             'Everything is based on a default set of 5 different lines: aaa, bbb, ccc, ddd and eee.\n' +
+                                             'For the base file each line will be either equal to the default set or removed.\n' +
+                                             'For contributor 1 each line will either be equal to the default set, different than the default set (\'xxx\' prepended) or removed.\n' +
+                                             'For contributor 2 each line will either be equal to the default set, equal to contributor 1, different (\'yyy\' prepended) or removed.\n' +
+                                             'This results in %d possible permutations. The -r option can be used to make a smaller \'random\' selection (the same seed is used each time).' % (len(options) ** len(defaultlines)))
+
+parser.add_argument('-r', metavar='num', nargs='?', type=int, default=len(options), const=len(options),
+                    help='instead of generating all %d permutations for each line, generate <num> randomly chosen ones. The number of test cases will become num^5.' % len(options))
+parser.add_argument('-s', metavar='num', nargs='?', type=int, default=0, const=0,
+                    help='specify the seed to use for the random number generator (default=0). This only makes sense when the -r option is specified.')
+args = parser.parse_args()
+
+if not os.path.exists(dirname):
+    os.makedirs(dirname)
+
+print 'Generating input files in %s ...' % dirname
+sys.stdout.flush()
+
+random.seed(args.s)
+permutations(args.r, len(defaultlines), [])
+
+print 'Input files generated.'
+print ''
+print 'To create a reference set of expected_result.txt files, run alignmenttest and copy/move all %s/*_actual_result.txt files to %s/*_expected_result.txt:' % (dirname, dirname)
+print '  ./alignmenttest > /dev/null'
+print '  cd %s' % dirname
+print '  for file in *_actual_result.txt; do mv ${file} ${file/actual/expected}; done'
+print 'If you\'ve already modified the algorithm, you can run the alignment test of an older version of kdiff3 and copy those result files over'
diff --git a/test/testdata/1_simpletest_base.txt b/test/testdata/1_simpletest_base.txt
new file mode 100644 (file)
index 0000000..cfe6960
--- /dev/null
@@ -0,0 +1,2 @@
+same everywhere
+
diff --git a/test/testdata/1_simpletest_contrib1.txt b/test/testdata/1_simpletest_contrib1.txt
new file mode 100644 (file)
index 0000000..9b11be5
--- /dev/null
@@ -0,0 +1,6 @@
+same in b and c
+only in b
+again same in b and c
+same in b and c except for space
+same everywhere
+
diff --git a/test/testdata/1_simpletest_contrib2.txt b/test/testdata/1_simpletest_contrib2.txt
new file mode 100644 (file)
index 0000000..61afd81
--- /dev/null
@@ -0,0 +1,5 @@
+same in b and c
+again same in b and c
+same  in b and c except for space
+same everywhere
+
diff --git a/test/testdata/1_simpletest_expected_result.txt b/test/testdata/1_simpletest_expected_result.txt
new file mode 100644 (file)
index 0000000..609d069
--- /dev/null
@@ -0,0 +1,7 @@
+-1 0 0
+-1 1 -1
+-1 2 1
+-1 3 2
+0 4 3
+1 5 4
+2 6 5
diff --git a/test/testdata/2_prefer_identical_to_space_differences_base.txt b/test/testdata/2_prefer_identical_to_space_differences_base.txt
new file mode 100644 (file)
index 0000000..cdfc83c
--- /dev/null
@@ -0,0 +1,2 @@
+aaa
+
diff --git a/test/testdata/2_prefer_identical_to_space_differences_contrib1.txt b/test/testdata/2_prefer_identical_to_space_differences_contrib1.txt
new file mode 100644 (file)
index 0000000..186af72
--- /dev/null
@@ -0,0 +1,3 @@
+bbb
+    aaa
+
diff --git a/test/testdata/2_prefer_identical_to_space_differences_contrib2.txt b/test/testdata/2_prefer_identical_to_space_differences_contrib2.txt
new file mode 100644 (file)
index 0000000..a49a514
--- /dev/null
@@ -0,0 +1,3 @@
+aaa
+    aaa
+
diff --git a/test/testdata/2_prefer_identical_to_space_differences_expected_result.txt b/test/testdata/2_prefer_identical_to_space_differences_expected_result.txt
new file mode 100644 (file)
index 0000000..a131bb6
--- /dev/null
@@ -0,0 +1,4 @@
+0 0 0
+-1 1 1
+1 2 2
+2 3 3
diff --git a/test/testdata/README b/test/testdata/README
new file mode 100644 (file)
index 0000000..cbd6a9a
--- /dev/null
@@ -0,0 +1,43 @@
+Adding test cases
+-----------------
+
+Each test case consists of 4 files: three files with input data and one
+file that contains the expected vertical alignment chosen by kdiff3.
+
+Test data files should follow this naming convention:
+ *_base.*
+ *_contrib1.*
+ *_contrib2.*
+ *_expected_result.*
+
+The test automatically detects files that follow this convention and executes the
+test on these sets of files in alphabetical order.
+
+
+Specifying expected alignment
+-----------------------------
+
+Each line in the alignment file corresponds to a line in the diff view of
+kdiff3 and consists of the three line numbers from files A, B and C that are
+put on that line in the diff view.
+
+So for instance if file A contains two lines, file B is empty and file C
+contains one line that is the same as the second line in A, kdiff3 may align
+them like this in the diff view:
+
+A:                   B:                 C:
+something                               
+something else                          something else
+
+An alignment file for this alignment would look like this:
+1 -1 -1
+2 -1  1
+
+As you can see -1 is used to indicate that a line in the diff view does not
+contain a line from the input file.
+
+
+--
+Maurice van der Pot
+griffon26@kfk4ever.com
+
diff --git a/windows_installer/COPYING.txt b/windows_installer/COPYING.txt
new file mode 100644 (file)
index 0000000..3f96206
--- /dev/null
@@ -0,0 +1,323 @@
+This package contains 
+
+KDiff3 :               Copyright (C) 2002-2006 Joachim Eibl
+                       License: GNU-General Public License (see below)
+
+Diff-Ext-For-KDiff3 :  Copyright (c) 2003-2006, Sergey Zorin
+                       Extensions for KDiff3 by Joachim Eibl           
+                       License: See bottom
+
+----------------------------------------------------------------------
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+               59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+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.
+\f
+  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.
+\f
+  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
+
+(End of GNU-General Public License)
+
+------------------------------------------------------------------------
+
+Diff-Ext-License:
+
+Diff-Ext: 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  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   OWNER  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/windows_installer/DIFF-EXT-LICENSE.txt b/windows_installer/DIFF-EXT-LICENSE.txt
new file mode 100644 (file)
index 0000000..c05a18d
--- /dev/null
@@ -0,0 +1,25 @@
+Diff-Ext: 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  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   OWNER  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/windows_installer/Kdiff3-64bit.nsi b/windows_installer/Kdiff3-64bit.nsi
new file mode 100644 (file)
index 0000000..17a1cf6
--- /dev/null
@@ -0,0 +1,3 @@
+!define KDIFF3_64BIT
+
+!include "Kdiff3.nsi"
diff --git a/windows_installer/README b/windows_installer/README
new file mode 100644 (file)
index 0000000..5a9fd89
--- /dev/null
@@ -0,0 +1,9 @@
+This directory contains the files needed to create the windows installer.
+
+Note that files that are created by some process are omitted here.
+These are:
+kdiff3.exe (compiler output)
+kdiff3*.qm-files (translation output, see po-directory)
+qt*.qm-files (translation files that come with Qt)
+Changelog.txt (../Changelog converted to dos)
+COPYING (../COPYING converted to dos)
\ No newline at end of file
diff --git a/windows_installer/README_WIN.txt b/windows_installer/README_WIN.txt
new file mode 100644 (file)
index 0000000..5cdc0ca
--- /dev/null
@@ -0,0 +1,123 @@
+KDiff3-Readme for Windows
+=========================
+
+Author: Joachim Eibl  (joachim.eibl@gmx.de)
+Copyright: (C) 2002-2009 by Joachim Eibl
+KDiff3-Version: 0.9.93
+Homepage: http://kdiff3.sourceforge.net
+
+KDiff3 is a program that
+- compares and merges two or three input files or directories,
+- shows the differences line by line and character by character (!),
+- provides an automatic merge-facility and
+- an integrated editor for comfortable solving of merge-conflicts
+- and has an intuitive graphical user interface.
+
+Now KDiff3-strings are translated into some languages by the KDE-I18N-team. 
+(*.qm-files in the KDiff3-directory)
+
+See the Changelog.txt for a list of fixed bugs and new features.
+
+
+Windows-specific information for the precompiled KDiff3 version:
+================================================================
+
+This executable is provided for the convenience of users who don't have a
+compiler at hand.
+
+You may redistribute it under the terms of the GNU GENERAL PUBLIC LICENCE.
+
+Note that there is NO WARRANTY for this program.
+
+Installation:
+- The installer was initially created by Sebastien Fricker (sebastien.fricker@web.de).
+  It is based on the Nullsoft Scriptable Install System (http://nsis.sourceforge.net)
+
+- You can place the directory where you want it. But don't separate the file
+  kdiff3.exe from the others, since they are needed for correct execution.  
+  (Using kdiff3.exe standalone is possible except for translations and help.)
+
+- Integration with WinCVS: When selected the installer sets KDiff3 to be the
+  default diff-tool for WinCVS if available.
+  Registry HKEY_CURRENT_USER\Software\WinCvs\wincvs\CVS settings: "P_Extdiff" and "P_DiffUseExtDiff"
+
+- Integration with TortoiseSVN: When selected the installer sets KDiff3 to be the
+  default diff-tool for TortoiseSVN if available.
+  Registry HKEY_CURRENT_USER\Software\TortoiseSVN: "Diff" and "Merge"
+
+- Integration with Explorer (1): When selected KDiff3 will be added to the "Send To"
+  menu in the context menu. If you then select two files or two directories and
+  choose "Send To"->"KDiff3" then KDiff3 will start and compare the specified files.
+
+- Integration with Explorer (2): When selected Diff-Ext-For-KDiff3 will be installed.
+  This is a Shell-Extension which adds an entry "KDiff3" into the context menu of 
+  Windows Explorer. (e.g. when right-clicking a file or directory) With this it
+  is possible to select files and directories sequentially and in separate directories 
+  for comparison with KDiff3. This is based on Diff-Ext by Sergey Zorin 
+  (http://diff-ext.sourceforge.net) with extensions for KDiff3 by Joachim Eibl. 
+  This extension is not under GPL but under a BSD-style licence. (See file DIFF-EXT-LICENSE.txt.)
+
+- SVN Merge Tool: Allows to use KDiff3 for explicit graphical merges with Subversion.
+  This installation option copies a file diff3_cmd.bat into your Application Data subdirectory.
+  (C:\Documents and Settings\Username\Application Data\Subversion\diff3_cmd.bat)
+  (Installation is disabled by default) 
+
+Since this program was actually developed for GNU/Linux, there might be Windows
+specific problems I don't know of yet. Please write me about problems you encounter.
+
+Known bugs:
+- Links are not handled correctly. (This is because links in Windows are not
+  the same as under Un*x-filesystems.)
+
+Licence:
+    GNU GENERAL PUBLIC LICENSE, Version 2, June 1991
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    For details see file "COPYING".
+
+Exception from the GPL:
+    As a special exception, the copyright holder Joachim Eibl gives permission
+    to link this program with the Qt-library (commercial or non-commercial edition)
+    from Trolltech (www.trolltech.com), and he permits to distribute the resulting
+    executable, without including the source code for the Qt-library in the
+    source distribution.
+
+
+
+Start from commandline:
+- Comparing 2 files:       kdiff3 file1 file2
+- Merging 2 files:         kdiff3 file1 file2 -o outputfile
+- Comparing 3 files:       kdiff3 file1 file2 file3
+- Merging 3 files:         kdiff3 file1 file2 file3 -o outputfile
+     Note that file1 will be treated as base of file2 and file3.
+
+If all files have the same name but are in different directories, you can
+reduce typework by specifying the filename only for the first file. E.g.:
+- Comparing 3 files:     kdiff3 dir1/filename dir2 dir3
+(This also works in the open-dialog.)
+
+- Comparing 2 directories: kdiff3 dir1 dir2
+- Merging 2 directories:   kdiff3 dir1 dir2-o destinationdir
+- Comparing 3 directories: kdiff3 dir1 dir2 dir3
+- Merging 3 directories:   kdiff3 dir1 dir2 dir3 -o destinationdir
+(Please read the documentation about comparing/merging directories,
+especially before you start merging.)
+
+If you start without arguments, then a dialog will appear where you can
+select your files and directories via a filebrowser.
+
+For more documentation, see the help-menu or the subdirectory doc.
+
+Have fun!
diff --git a/windows_installer/diff3_cmd.bat b/windows_installer/diff3_cmd.bat
new file mode 100644 (file)
index 0000000..72c78e0
--- /dev/null
@@ -0,0 +1,36 @@
+@ECHO OFF
+
+REM In file "c:\Documents and Settings\<username>\Application
+REM Data\Subversion\config" you can change that behavior by
+REM modifying "diff3-cmd" line to call KDiff3 instead of
+REM automatic merge. Well, you need a batch file actually (based on
+REM http://svn.collab.net/repos/svn/trunk/contrib/client-side/diff3wrap.bat):
+
+REM Configure your favorite diff3/merge program here.
+if exist "C:\Program Files\KDiff3" (
+  SET DIFF3="C:\Program Files\KDiff3\kdiff3.exe"
+) else (
+  SET DIFF3="C:\Program Files (x86)\KDiff3\kdiff3.exe"
+)
+
+REM Subversion provides the paths we need as the ninth, tenth, and eleventh 
+REM parameters.  But we only have access to nine parameters at a time, so we
+REM shift our nine-parameter window twice to let us get to what we need.
+SHIFT
+SHIFT
+SET MINE=%7
+SET OLDER=%8
+SET YOURS=%9
+
+REM Call the merge command (change the following line to make sense for
+REM your merge program).
+%DIFF3% %OLDER% %MINE% %YOURS% -o merged.txt
+
+REM After performing the merge, this script needs to print the contents
+REM of the merged file to stdout.  Do that in whatever way you see fit.
+REM Return an errorcode of 0 on successful merge, 1 if unresolved conflicts
+REM remain in the result.  Any other errorcode will be treated as fatal.
+
+type merged.txt
+
+del merged.txt
diff --git a/windows_installer/installForAllUsersPage.ini b/windows_installer/installForAllUsersPage.ini
new file mode 100644 (file)
index 0000000..2c46272
--- /dev/null
@@ -0,0 +1,19 @@
+[Settings]
+NumFields=2
+
+[Field 1]
+Type=label
+Text=Install for all users or only for the current user? (Installing for all users requires admin rights.)
+Left=0
+Right=-1
+Top=0
+Bottom=10
+
+[Field 2]
+Type=checkbox
+Text=Install for all users
+Left=0
+Right=-1
+Top=30
+Bottom=40
+State=1
\ No newline at end of file
diff --git a/windows_installer/kdiff3.bmp b/windows_installer/kdiff3.bmp
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/windows_installer/kdiff3.nsi b/windows_installer/kdiff3.nsi
new file mode 100644 (file)
index 0000000..248f08d
--- /dev/null
@@ -0,0 +1,515 @@
+;KDiff3-NSIS configuration
+;Based on Modern User Interface example files
+;Apdapted for KDiff3 by Sebastien Fricker and Joachim Eibl
+;Requires nsis_v2.19
+
+!define KDIFF3_VERSION "1.07.90"
+!define DIFF_EXT32_CLSID "{9F8528E4-AB20-456E-84E5-3CE69D8720F3}"
+!define DIFF_EXT64_CLSID "{34471FFB-4002-438b-8952-E4588D0C0FE9}"
+
+!ifdef KDIFF3_64BIT
+  !define BITS 64
+!else
+  !define BITS 32
+!endif
+
+!define SetupFileName "KDiff3-${BITS}bit-Setup_${KDIFF3_VERSION}.exe"
+
+;--------------------------------
+;Include Modern UI
+
+  !include "MUI.nsh"
+  !include "x64.nsh"
+
+;--------------------------------
+;General
+
+  ;Name and file
+  Name "KDiff3"
+  OutFile ${SetupFileName}
+
+  ;Default installation folder
+  !ifdef KDIFF3_64BIT
+  ;SetRegView 64
+  InstallDir "$PROGRAMFILES64\KDiff3"
+  !else
+  InstallDir "$PROGRAMFILES\KDiff3"
+  !endif
+  ;Get installation folder from registry if available
+  InstallDirRegKey HKCU "Software\KDiff3" ""
+  
+  !addplugindir ".\nsisplugins"
+
+;--------------------------------
+;Variables
+
+  Var MUI_TEMP
+  Var STARTMENU_FOLDER
+  Var DIFF_EXT_CLSID
+  Var DIFF_EXT_ID
+  Var DIFF_EXT_DLL
+  Var DIFF_EXT_OLD_DLL
+  
+;--------------------------------
+;Interface Settings
+
+  !define MUI_ABORTWARNING
+  !define MUI_HEADERIMAGE
+  !define MUI_HEADERIMAGE_BITMAP "kdiff3.bmp" ; optional
+
+;--------------------------------
+;Language Selection Dialog Settings
+
+  ;Remember the installer language
+  !define MUI_LANGDLL_REGISTRY_ROOT "HKCU" 
+  !define MUI_LANGDLL_REGISTRY_KEY "Software\KDiff3" 
+  !define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
+
+;--------------------------------
+;Pages
+
+  ;!insertmacro MUI_PAGE_WELCOME
+  !insertmacro MUI_PAGE_LICENSE $(MUILicense)
+  !insertmacro MUI_PAGE_COMPONENTS
+  !insertmacro MUI_PAGE_DIRECTORY
+  Page custom CustomPageC
+  
+  ;Start Menu Folder Page Configuration
+  !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" 
+  !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\KDiff3" 
+  !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
+  
+  !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
+  
+  !insertmacro MUI_PAGE_INSTFILES
+  
+  !define MUI_FINISHPAGE_RUN KDiff3.exe
+  !define MUI_FINISHPAGE_RUN_NOTCHECKED
+  !define MUI_FINISHPAGE_SHOWREADME README_WIN.txt
+  !define MUI_FINISHPAGE_SHOWREADME_CHECKED
+
+  !insertmacro MUI_PAGE_FINISH
+  
+  !insertmacro MUI_UNPAGE_CONFIRM
+  !insertmacro MUI_UNPAGE_INSTFILES
+
+;--------------------------------
+;Languages
+
+  !insertmacro MUI_LANGUAGE "English" # first language is the default language
+  !insertmacro MUI_LANGUAGE "French"
+  !insertmacro MUI_LANGUAGE "German"
+  !insertmacro MUI_LANGUAGE "Spanish"
+  !insertmacro MUI_LANGUAGE "SimpChinese"
+  !insertmacro MUI_LANGUAGE "TradChinese"
+  !insertmacro MUI_LANGUAGE "Japanese"
+  !insertmacro MUI_LANGUAGE "Korean"
+  !insertmacro MUI_LANGUAGE "Italian"
+  !insertmacro MUI_LANGUAGE "Dutch"
+  !insertmacro MUI_LANGUAGE "Danish"
+  !insertmacro MUI_LANGUAGE "Swedish"
+  !insertmacro MUI_LANGUAGE "Norwegian"
+  !insertmacro MUI_LANGUAGE "Finnish"
+  !insertmacro MUI_LANGUAGE "Greek"
+  !insertmacro MUI_LANGUAGE "Russian"
+  !insertmacro MUI_LANGUAGE "Portuguese"
+  !insertmacro MUI_LANGUAGE "PortugueseBR"
+  !insertmacro MUI_LANGUAGE "Polish"
+  !insertmacro MUI_LANGUAGE "Ukrainian"
+  !insertmacro MUI_LANGUAGE "Czech"
+  !insertmacro MUI_LANGUAGE "Slovak"
+  !insertmacro MUI_LANGUAGE "Croatian"
+  !insertmacro MUI_LANGUAGE "Bulgarian"
+  !insertmacro MUI_LANGUAGE "Hungarian"
+  !insertmacro MUI_LANGUAGE "Thai"
+  !insertmacro MUI_LANGUAGE "Romanian"
+  !insertmacro MUI_LANGUAGE "Latvian"
+  !insertmacro MUI_LANGUAGE "Macedonian"
+  !insertmacro MUI_LANGUAGE "Estonian"
+  !insertmacro MUI_LANGUAGE "Turkish"
+  !insertmacro MUI_LANGUAGE "Lithuanian"
+  !insertmacro MUI_LANGUAGE "Catalan"
+  !insertmacro MUI_LANGUAGE "Slovenian"
+  !insertmacro MUI_LANGUAGE "Serbian"
+  !insertmacro MUI_LANGUAGE "SerbianLatin"
+  !insertmacro MUI_LANGUAGE "Arabic"
+  !insertmacro MUI_LANGUAGE "Farsi"
+  !insertmacro MUI_LANGUAGE "Hebrew"
+  !insertmacro MUI_LANGUAGE "Indonesian"
+  !insertmacro MUI_LANGUAGE "Mongolian"
+  !insertmacro MUI_LANGUAGE "Luxembourgish"
+  !insertmacro MUI_LANGUAGE "Albanian"
+  !insertmacro MUI_LANGUAGE "Breton"
+  !insertmacro MUI_LANGUAGE "Belarusian"
+  !insertmacro MUI_LANGUAGE "Icelandic"
+  !insertmacro MUI_LANGUAGE "Malay"
+  !insertmacro MUI_LANGUAGE "Bosnian"
+  !insertmacro MUI_LANGUAGE "Kurdish"
+
+;--------------------------------
+;License Language String
+
+  LicenseLangString MUILicense ${LANG_ENGLISH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_FRENCH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_GERMAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_SPANISH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_SIMPCHINESE} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_TRADCHINESE} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_JAPANESE} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_KOREAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_ITALIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_DUTCH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_DANISH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_SWEDISH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_NORWEGIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_FINNISH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_GREEK} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_RUSSIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_PORTUGUESE} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_PORTUGUESEBR} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_POLISH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_UKRAINIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_CZECH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_SLOVAK} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_CROATIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_BULGARIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_HUNGARIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_THAI} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_ROMANIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_LATVIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_MACEDONIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_ESTONIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_TURKISH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_LITHUANIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_CATALAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_SLOVENIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_SERBIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_SERBIANLATIN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_ARABIC} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_FARSI} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_HEBREW} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_INDONESIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_MONGOLIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_LUXEMBOURGISH} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_ALBANIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_BRETON} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_BELARUSIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_ICELANDIC} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_MALAY} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_BOSNIAN} "COPYING.txt"
+  LicenseLangString MUILicense ${LANG_KURDISH} "COPYING.txt"
+
+;--------------------------------
+;Reserve Files
+  
+  ;These files should be inserted before other files in the data block
+  ;Keep these lines before any File command
+  ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA)
+  
+  !insertmacro MUI_RESERVEFILE_LANGDLL
+  ReserveFile "installForAllUsersPage.ini"
+  !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
+
+;--------------------------------
+;Variables
+
+  Var INSTALL_FOR_ALL_USERS
+  
+;--------------------------------
+;Installer Sections
+
+Section "Software" SecSoftware
+SectionIn RO
+  ;Read a value from an InstallOptions INI file
+  !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_FOR_ALL_USERS "installForAllUsersPage.ini" "Field 2" "State"
+  
+  ;Set ShellVarContext: Defines if SHCTX points to HKLM or HKCU
+  StrCmp $INSTALL_FOR_ALL_USERS "0" "" +3
+    SetShellVarContext current
+    Goto +2
+    SetShellVarContext all    
+
+  WriteRegStr HKCU "Software\KDiff3" "InstalledForAllUsers" "$INSTALL_FOR_ALL_USERS"
+
+  ; Make the KDiff3 uninstaller visible via "System Settings: Add or Remove Programs", (Systemsteuerung/Software)
+  WriteRegStr SHCTX "Software\KDiff3" "" "$INSTDIR"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\KDiff3" "DisplayName" "KDiff3 (remove only)"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\KDiff3" "UninstallString" '"$INSTDIR\Uninstall.exe"'
+
+
+  SetOutPath "$INSTDIR"
+  
+  ;ADD YOUR OWN FILES HERE...
+    DetailPrint "Writing files"
+    File  "${BITS}bit\kdiff3.exe"
+    File  "${BITS}bit\kdiff3.exe.manifest"
+    File  "${BITS}bit\qt.conf"
+    File "COPYING.txt"
+    File "Readme_Win.txt"
+    File "ChangeLog.txt"
+    SetOutPath "$INSTDIR\bin"
+    File /r "${BITS}bit\bin\*.*"
+    SetOutPath "$INSTDIR"
+    Delete "$INSTDIR\kdiff3-QT4.exe"
+  
+  ;Store installation folder
+  WriteRegStr HKCU "Software\KDiff3" "" $INSTDIR
+  
+  ;Create uninstaller
+  WriteUninstaller "$INSTDIR\Uninstall.exe"
+
+  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+    
+    ;Create shortcuts
+    CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
+    CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\KDiff3.lnk" "$INSTDIR\kdiff3.exe"
+    CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Readme.lnk" "$INSTDIR\Readme_Win.txt"
+    CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\GPL.lnk"    "$INSTDIR\Copying.txt"
+    CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
+    CreateShortCut "$QUICKLAUNCH\KDiff3.lnk" "$INSTDIR\kdiff3.exe"     
+  
+  !insertmacro MUI_STARTMENU_WRITE_END
+
+SectionEnd
+
+Section "Documentation" SecDocumentation
+    DetailPrint "Writing the documentation"
+    SetOutPath "$INSTDIR"
+    File /r doc
+    CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Documentation.lnk" "$INSTDIR\doc\index.html"
+SectionEnd
+
+Section "Translations" SecTranslations
+    DetailPrint "Writing the translation messages"
+    SetOutPath "$INSTDIR"
+    File /r translations
+SectionEnd
+
+Section "Utilities" SecUtilities
+    DetailPrint "Writing the command line utilities (GNU sed, diff, diff3, etc.)"
+    SetOutPath "$INSTDIR\bin"
+    File /r "bin\*.*"
+SectionEnd
+
+SubSection "Integration" SecIntegration
+
+Section "Explorer" SecIntegrationExplorer
+  DetailPrint "Integration to Explorer"
+;  WriteRegStr HKCR "Directory\shell\KDiff3" "" '&KDiff3'
+;  WriteRegStr HKCR "Directory\shell\KDiff3\command" "" '"$INSTDIR\kdiff3.exe" "%1"'
+    CreateShortCut "$SENDTO\KDiff3.lnk" '"$INSTDIR\kdiff3.exe"'
+SectionEnd
+
+Section "Diff-Ext" SecIntegrationDiffExtForKDiff3
+  DetailPrint "Diff-Ext for KDiff3"
+  SetOutPath "$INSTDIR"
+${If} ${RunningX64}
+  StrCpy $DIFF_EXT_CLSID ${DIFF_EXT64_CLSID}
+  StrCpy $DIFF_EXT_DLL "diff_ext_for_kdiff3_64.dll"
+  StrCpy $DIFF_EXT_OLD_DLL "diff_ext_for_kdiff3_64_old.dll"
+  StrCpy $DIFF_EXT_ID "diff-ext-for-kdiff3-64"
+  IfFileExists "$INSTDIR\$DIFF_EXT_OLD_DLL" 0 +2
+     Delete "$INSTDIR\$DIFF_EXT_OLD_DLL"
+
+  IfFileExists "$INSTDIR\$DIFF_EXT_DLL" 0 +2
+     Rename "$INSTDIR\$DIFF_EXT_DLL" "$INSTDIR\$DIFF_EXT_OLD_DLL"
+  File "64bit\diff_ext_for_kdiff3_64.dll"
+
+  SetRegView 64
+
+  WriteRegStr SHCTX "Software\Classes\CLSID\$DIFF_EXT_CLSID"                ""  "$DIFF_EXT_ID"
+  WriteRegStr SHCTX "Software\Classes\CLSID\$DIFF_EXT_CLSID\InProcServer32" ""  "$INSTDIR\$DIFF_EXT_DLL"
+  WriteRegStr SHCTX "Software\Classes\CLSID\$DIFF_EXT_CLSID\InProcServer32" "ThreadingModel" "Apartment"
+  WriteRegStr SHCTX "Software\Classes\*\shellex\ContextMenuHandlers\$DIFF_EXT_ID" "" "$DIFF_EXT_CLSID"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved" "$DIFF_EXT_CLSID" "$DIFF_EXT_ID"
+  WriteRegStr SHCTX "Software\Classes\Folder\shellex\ContextMenuHandlers\$DIFF_EXT_ID" "" "$DIFF_EXT_CLSID"
+  WriteRegStr SHCTX "Software\Classes\Directory\shellex\ContextMenuHandlers\$DIFF_EXT_ID" "" "$DIFF_EXT_CLSID"
+
+  SetRegView 32
+
+${EndIf}
+  StrCpy $DIFF_EXT_CLSID ${DIFF_EXT32_CLSID}
+  StrCpy $DIFF_EXT_DLL "diff_ext_for_kdiff3.dll"
+  StrCpy $DIFF_EXT_OLD_DLL "diff_ext_for_kdiff3_old.dll"
+  StrCpy $DIFF_EXT_ID "diff-ext-for-kdiff3"
+
+  IfFileExists "$INSTDIR\$DIFF_EXT_OLD_DLL" 0 +2
+     Delete "$INSTDIR\$DIFF_EXT_OLD_DLL"
+     
+  IfFileExists "$INSTDIR\$DIFF_EXT_DLL" 0 +2
+     Rename "$INSTDIR\$DIFF_EXT_DLL" "$INSTDIR\$DIFF_EXT_OLD_DLL"
+
+  File "32bit\diff_ext_for_kdiff3.dll"
+
+  SetRegView 64
+
+  WriteRegStr HKCU  "Software\KDiff3\diff-ext" "" ""
+  WriteRegStr SHCTX "Software\KDiff3\diff-ext" "InstallDir" "$INSTDIR"
+  WriteRegStr SHCTX "Software\KDiff3\diff-ext" "diffcommand" "$INSTDIR\kdiff3.exe"
+
+  SetRegView 32
+
+  WriteRegStr SHCTX "Software\Classes\CLSID\$DIFF_EXT_CLSID"                ""  "$DIFF_EXT_ID"
+  WriteRegStr SHCTX "Software\Classes\CLSID\$DIFF_EXT_CLSID\InProcServer32" ""  "$INSTDIR\$DIFF_EXT_DLL"
+  WriteRegStr SHCTX "Software\Classes\CLSID\$DIFF_EXT_CLSID\InProcServer32" "ThreadingModel" "Apartment"
+  WriteRegStr SHCTX "Software\Classes\*\shellex\ContextMenuHandlers\$DIFF_EXT_ID" "" "$DIFF_EXT_CLSID"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved" "$DIFF_EXT_CLSID" "$DIFF_EXT_ID"
+  WriteRegStr SHCTX "Software\Classes\Folder\shellex\ContextMenuHandlers\$DIFF_EXT_ID" "" "$DIFF_EXT_CLSID"
+  WriteRegStr SHCTX "Software\Classes\Directory\shellex\ContextMenuHandlers\$DIFF_EXT_ID" "" "$DIFF_EXT_CLSID"
+
+
+  File "DIFF-EXT-LICENSE.txt"  
+  CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Diff-Ext License.lnk"    "$INSTDIR\DIFF-EXT-LICENSE.txt"
+  
+SectionEnd
+
+Section "WinCVS" SecIntegrationWinCVS
+  DetailPrint "Integration to WinCVS"
+  #MessageBox  MB_OK "If WinCVS is running, please close it before proceeding."
+  WriteRegStr HKCU "Software\WinCvs\wincvs\CVS settings" "P_Extdiff" '$INSTDIR\kdiff3.exe'
+  WriteRegBin HKCU "Software\WinCvs\wincvs\CVS settings" "P_DiffUseExtDiff" 01
+SectionEnd
+
+Section "TortoiseSVN" SecIntegrationTortoiseSVN
+  DetailPrint "Integration to TortoiseSVN"
+  WriteRegStr HKCU "Software\TortoiseSVN\" "Diff" '$INSTDIR\kdiff3.exe %base %mine  --L1 Base --L2 Mine'
+  WriteRegStr HKCU "Software\TortoiseSVN\" "Merge" '$INSTDIR\kdiff3.exe %base %mine %theirs -o %merged --L1 Base --L2 Mine --L3 Theirs'
+SectionEnd
+
+Section /o "SVN Merge tool" SecIntegrationSubversionDiff3Cmd
+  DetailPrint "Integrate diff3_cmd.bat for Subversion"
+  File "diff3_cmd.bat"
+  CreateDirectory '$APPDATA\Subversion'
+  CopyFiles '$INSTDIR\diff3_cmd.bat' '$APPDATA\Subversion'
+SectionEnd
+
+SubSectionEnd
+
+;--------------------------------
+;Installer Functions
+
+Function .onInit
+
+  !insertmacro MUI_LANGDLL_DISPLAY
+  !insertmacro MUI_INSTALLOPTIONS_EXTRACT "installForAllUsersPage.ini"
+
+FunctionEnd
+
+Function CustomPageC
+
+  !insertmacro MUI_HEADER_TEXT "$(TEXT_IO_TITLE)" "$(TEXT_IO_SUBTITLE)"
+  !insertmacro MUI_INSTALLOPTIONS_DISPLAY "installForAllUsersPage.ini"
+
+FunctionEnd
+
+
+;--------------------------------
+;Descriptions
+
+  ;USE A LANGUAGE STRING IF YOU WANT YOUR DESCRIPTIONS TO BE LANGUAGE SPECIFIC
+
+  ;Assign descriptions to sections
+  !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecSoftware} "Main program."
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecDocumentation} "English documentation in HTML-format (Docs for other languages are available on the homepage.)"
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecTranslations}  "Translations for visible strings in many languages. Not needed for US-English."
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecUtilities}  "Command Line Utilities: GNU sed, diff, diff3, etc. precompiled for Windows"
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecIntegration}   "Integrate KDiff3 with certain programs. (See also the Readme for details.)"
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecIntegrationExplorer}  "Integrate KDiff3 with Explorer. Adds an entry for KDiff3 in the Send-To context menu."
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecIntegrationDiffExtForKDiff3}  "Installs Diff-Ext by Sergey Zorin. Adds entries for KDiff3 in Explorer context menu."
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecIntegrationWinCVS}  "Integrate KDiff3 with WinCVS. (Please close WinCVS before proceeding.)"
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecIntegrationTortoiseSVN}  "Integrate KDiff3 with TortoiseSVN."
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecIntegrationSubversionDiff3Cmd}  "Install diff3_cmd.bat for Subversion merge"
+  !insertmacro MUI_FUNCTION_DESCRIPTION_END
+
+;--------------------------------
+;Uninstaller Section
+
+Section "Uninstall"
+  ReadRegStr $INSTALL_FOR_ALL_USERS HKCU "Software\KDiff3" "InstalledForAllUsers"
+  ;Set ShellVarContext: Defines if SHCTX points to HKLM or HKCU
+  StrCmp $INSTALL_FOR_ALL_USERS "0" "" +3
+    SetShellVarContext current
+    Goto +2
+    SetShellVarContext all
+       
+  Delete "$INSTDIR\Uninstall.exe"
+  Delete "$INSTDIR\kdiff3.exe"
+  Delete "$INSTDIR\kdiff3.exe.manifest"
+  Delete "$INSTDIR\qt.conf"
+  Delete "$INSTDIR\COPYING.txt"
+  Delete "$INSTDIR\Readme_Win.txt"
+  Delete "$INSTDIR\ChangeLog.txt"
+  Delete "$INSTDIR\diff_ext_for_kdiff3.dll"
+  Delete "$INSTDIR\diff_ext_for_kdiff3_old.dll"
+  Delete "$INSTDIR\diff_ext_for_kdiff3_64.dll"
+  Delete "$INSTDIR\diff_ext_for_kdiff3_64_old.dll"
+  Delete "$INSTDIR\DIFF-EXT-LICENSE.txt"
+
+  RMDir /r "$INSTDIR\doc"
+  RMDir /r "$INSTDIR\translations"
+  RMDir /r "$INSTDIR\bin"
+  RMDir "$INSTDIR"                   # without /r the dir is only removed if completely empty
+  
+  !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
+    
+  Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
+  Delete "$SMPROGRAMS\$MUI_TEMP\KDiff3.lnk"
+  Delete "$SMPROGRAMS\$MUI_TEMP\KDiff3-Qt4.lnk"
+  Delete "$SMPROGRAMS\$MUI_TEMP\Readme.lnk"
+  Delete "$SMPROGRAMS\$MUI_TEMP\GPL.lnk"
+  Delete "$SMPROGRAMS\$MUI_TEMP\Diff-Ext License.lnk"
+
+  Delete "$SMPROGRAMS\$MUI_TEMP\Documentation.lnk"
+  Delete "$QUICKLAUNCH\KDiff3.lnk"
+  Delete "$SENDTO\KDiff3.lnk"
+  
+  ;Delete empty start menu parent diretories
+  StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
+  startMenuDeleteLoop:
+       ClearErrors
+    RMDir $MUI_TEMP
+    GetFullPathName $MUI_TEMP "$MUI_TEMP\.."
+    
+    IfErrors startMenuDeleteLoopDone
+  
+    StrCmp $MUI_TEMP $SMPROGRAMS startMenuDeleteLoopDone startMenuDeleteLoop
+  startMenuDeleteLoopDone:
+  
+  DeleteRegKey HKCU  "Software\KDiff3"
+  DeleteRegKey SHCTX "Software\KDiff3"
+  DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\KDiff3"
+
+  ; diff_ext_for_kdiff3
+${If} ${RunningX64}
+  StrCpy $DIFF_EXT_CLSID ${DIFF_EXT64_CLSID}
+  StrCpy $DIFF_EXT_ID "diff-ext-for-kdiff3-64"
+  SetRegView 64
+  DeleteRegKey SHCTX "Software\Classes\CLSID\$DIFF_EXT_CLSID"
+  DeleteRegKey SHCTX "Software\Classes\*\shellex\ContextMenuHandlers\$DIFF_EXT_ID"
+  DeleteRegKey SHCTX "Software\Classes\Folder\shellex\ContextMenuHandlers\$DIFF_EXT_ID"
+  DeleteRegKey SHCTX "Software\Classes\Directory\shellex\ContextMenuHandlers\$DIFF_EXT_ID"
+  DeleteRegValue SHCTX "Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved" "$DIFF_EXT_CLSID"
+  SetRegView 32
+${EndIf}
+
+  StrCpy $DIFF_EXT_CLSID ${DIFF_EXT32_CLSID}
+  StrCpy $DIFF_EXT_ID "diff-ext-for-kdiff3"
+  DeleteRegKey SHCTX "Software\Classes\CLSID\$DIFF_EXT_CLSID"
+  DeleteRegKey SHCTX "Software\Classes\*\shellex\ContextMenuHandlers\$DIFF_EXT_ID"
+  DeleteRegKey SHCTX "Software\Classes\Folder\shellex\ContextMenuHandlers\$DIFF_EXT_ID"
+  DeleteRegKey SHCTX "Software\Classes\Directory\shellex\ContextMenuHandlers\$DIFF_EXT_ID"
+  DeleteRegValue SHCTX "Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved" "$DIFF_EXT_CLSID"
+
+SectionEnd
+
+;--------------------------------
+;Uninstaller Functions
+
+Function un.onInit
+
+  !insertmacro MUI_UNGETLANGUAGE
+  
+FunctionEnd