summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am58
-rw-r--r--src/Makefile.qt425
-rw-r--r--src/ccInstHelper.cpp334
-rw-r--r--src/common.cpp341
-rw-r--r--src/common.h113
-rw-r--r--src/diff.cpp1920
-rw-r--r--src/diff.h462
-rw-r--r--src/difftextwindow.cpp1751
-rw-r--r--src/difftextwindow.h135
-rw-r--r--src/directorymergewindow.cpp3048
-rw-r--r--src/directorymergewindow.h362
-rw-r--r--src/fileaccess.cpp1809
-rw-r--r--src/fileaccess.h265
-rw-r--r--src/gnudiff_analyze.cpp873
-rw-r--r--src/gnudiff_diff.h355
-rw-r--r--src/gnudiff_io.cpp559
-rw-r--r--src/gnudiff_system.h123
-rw-r--r--src/gnudiff_xmalloc.cpp88
-rw-r--r--src/hi16-app-kdiff3.pngbin0 -> 219 bytes
-rw-r--r--src/hi32-app-kdiff3.pngbin0 -> 345 bytes
-rw-r--r--src/kdiff3.cpp992
-rw-r--r--src/kdiff3.desktop91
-rw-r--r--src/kdiff3.h410
-rw-r--r--src/kdiff3.icobin0 -> 1078 bytes
-rw-r--r--src/kdiff3.lsm16
-rw-r--r--src/kdiff3.pro57
-rw-r--r--src/kdiff3.rc1
-rw-r--r--src/kdiff3_meta_unload.cpp1
-rw-r--r--src/kdiff3_part.cpp309
-rw-r--r--src/kdiff3_part.h100
-rw-r--r--src/kdiff3_part.rc24
-rw-r--r--src/kdiff3_shell.cpp191
-rw-r--r--src/kdiff3_shell.h74
-rw-r--r--src/kdiff3_shell.rc128
-rw-r--r--src/kdiff3part.desktop18
-rw-r--r--src/kreplacements/README30
-rwxr-xr-xsrc/kreplacements/ShellContextMenu.cpp492
-rw-r--r--src/kreplacements/ShellContextMenu.h60
-rw-r--r--src/kreplacements/kaboutdata.h2
-rw-r--r--src/kreplacements/kaccel.h2
-rw-r--r--src/kreplacements/kaction.h2
-rw-r--r--src/kreplacements/kapplication.h2
-rw-r--r--src/kreplacements/kcmdlineargs.h2
-rw-r--r--src/kreplacements/kcolorbtn.h2
-rw-r--r--src/kreplacements/kconfig.h2
-rw-r--r--src/kreplacements/kdialogbase.h2
-rw-r--r--src/kreplacements/kedittoolbar.h2
-rw-r--r--src/kreplacements/kfiledialog.h2
-rw-r--r--src/kreplacements/kfontdialog.h2
-rw-r--r--src/kreplacements/kiconloader.h2
-rw-r--r--src/kreplacements/kinstance.h2
-rw-r--r--src/kreplacements/kio/global.h2
-rw-r--r--src/kreplacements/kio/job.h2
-rw-r--r--src/kreplacements/kio/jobclasses.h2
-rw-r--r--src/kreplacements/kkeydialog.h2
-rw-r--r--src/kreplacements/klibloader.h2
-rw-r--r--src/kreplacements/klocale.h2
-rw-r--r--src/kreplacements/kmainwindow.h2
-rw-r--r--src/kreplacements/kmenubar.h2
-rw-r--r--src/kreplacements/kmessagebox.h2
-rw-r--r--src/kreplacements/konq_popupmenu.h2
-rw-r--r--src/kreplacements/kparts/factory.h2
-rw-r--r--src/kreplacements/kparts/mainwindow.h2
-rw-r--r--src/kreplacements/kparts/part.h2
-rw-r--r--src/kreplacements/kpopupmenu.h2
-rw-r--r--src/kreplacements/kprinter.h2
-rw-r--r--src/kreplacements/kprogress.h2
-rw-r--r--src/kreplacements/kreplacements.cpp1185
-rw-r--r--src/kreplacements/kreplacements.h505
-rw-r--r--src/kreplacements/kstandarddirs.h2
-rw-r--r--src/kreplacements/kstatusbar.h2
-rw-r--r--src/kreplacements/kstdaction.h2
-rw-r--r--src/kreplacements/ktempfile.h2
-rw-r--r--src/kreplacements/kunload.h2
-rw-r--r--src/kreplacements/kurl.h2
-rw-r--r--src/kreplacements/kurldrag.h2
-rw-r--r--src/lo16-app-kdiff3.pngbin0 -> 219 bytes
-rw-r--r--src/lo32-app-kdiff3.pngbin0 -> 345 bytes
-rw-r--r--src/main.cpp236
-rw-r--r--src/merger.cpp87
-rw-r--r--src/merger.h61
-rw-r--r--src/mergeresultwindow.cpp3222
-rw-r--r--src/mergeresultwindow.h454
-rw-r--r--src/optiondialog.cpp1755
-rw-r--r--src/optiondialog.h229
-rw-r--r--src/pdiff.cpp2268
-rw-r--r--src/smalldialogs.cpp579
-rw-r--r--src/smalldialogs.h120
-rw-r--r--src/version.h2
-rw-r--r--src/xpm/autoadvance.xpm25
-rw-r--r--src/xpm/currentpos.xpm22
-rw-r--r--src/xpm/down1arrow.xpm25
-rw-r--r--src/xpm/down2arrow.xpm25
-rw-r--r--src/xpm/downend.xpm22
-rw-r--r--src/xpm/file.xpm24
-rw-r--r--src/xpm/filenew.xpm22
-rw-r--r--src/xpm/fileopen.xpm22
-rw-r--r--src/xpm/fileprint.xpm24
-rw-r--r--src/xpm/filesave.xpm21
-rw-r--r--src/xpm/folder.xpm24
-rw-r--r--src/xpm/iconA.xpm22
-rw-r--r--src/xpm/iconB.xpm22
-rw-r--r--src/xpm/iconC.xpm22
-rw-r--r--src/xpm/link_arrow.xpm24
-rw-r--r--src/xpm/nextunsolved.xpm23
-rw-r--r--src/xpm/prevunsolved.xpm23
-rw-r--r--src/xpm/reload.xpm74
-rw-r--r--src/xpm/showequalfiles.xpm23
-rw-r--r--src/xpm/showfilesonlyina.xpm23
-rw-r--r--src/xpm/showfilesonlyinb.xpm23
-rw-r--r--src/xpm/showfilesonlyinc.xpm22
-rw-r--r--src/xpm/showlinenumbers.xpm21
-rw-r--r--src/xpm/showwhitespace.xpm22
-rw-r--r--src/xpm/showwhitespacechars.xpm21
-rw-r--r--src/xpm/startmerge.xpm25
-rw-r--r--src/xpm/up1arrow.xpm22
-rw-r--r--src/xpm/up2arrow.xpm25
-rw-r--r--src/xpm/upend.xpm22
118 files changed, 27505 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..151e53d
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,58 @@
+
+# set the include path for X, qt and KDE
+INCLUDES = $(all_includes)
+
+# these are the headers for your project
+noinst_HEADERS = kdiff3_part.h kdiff3_shell.h kdiff3.h common.h diff.h \
+ directorymergewindow.h merger.h optiondialog.h fileaccess.h version.h \
+ smalldialogs.h difftextwindow.h mergeresultwindow.h
+
+# let automoc handle all of the meta source files (moc)
+METASOURCES = AUTO
+
+messages: rc.cpp
+ $(XGETTEXT) *.cpp -o $(podir)/kdiff3.pot
+
+KDE_ICON = kdiff3
+
+# this Makefile creates both a KPart application and a KPart
+#########################################################################
+# APPLICATION SECTION
+#########################################################################
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+bin_PROGRAMS = kdiff3
+
+# the application source, library search path, and link libraries
+kdiff3_SOURCES = main.cpp kdiff3_shell.cpp
+kdiff3_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+kdiff3_LDADD = $(LIB_KPARTS)
+
+# this is where the desktop file will go
+shelldesktopdir = $(kde_appsdir)/Development
+shelldesktop_DATA = kdiff3.desktop
+
+# this is where the shell's XML-GUI resource file goes
+shellrcdir = $(kde_datadir)/kdiff3
+shellrc_DATA = kdiff3_shell.rc
+
+#########################################################################
+# KPART SECTION
+#########################################################################
+kde_module_LTLIBRARIES = libkdiff3part.la
+
+# the Part's source, library search path, and link libraries
+libkdiff3part_la_SOURCES = 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
+libkdiff3part_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries)
+libkdiff3part_la_LIBADD = $(LIB_KDEPRINT) $(LIB_KPARTS) $(LIB_KFILE)
+
+# this is where the desktop file will go
+partdesktopdir = $(kde_servicesdir)
+partdesktop_DATA = kdiff3part.desktop
+
+# this is where the part's XML-GUI resource file goes
+partrcdir = $(kde_datadir)/kdiff3part
+partrc_DATA = kdiff3_part.rc
diff --git a/src/Makefile.qt b/src/Makefile.qt
new file mode 100644
index 0000000..44de44b
--- /dev/null
+++ b/src/Makefile.qt
@@ -0,0 +1,425 @@
+#############################################################################
+# Makefile for building: kdiff3
+# Generated by qmake (1.07a) (Qt 3.3.5) on: Sat Apr 8 20:11:51 2006
+# Project: kdiff3.pro
+# Template: app
+# Command: $(QMAKE) -o Makefile kdiff3.pro
+#############################################################################
+
+####### Compiler, tools and options
+
+CC = gcc
+CXX = g++
+LEX = flex
+YACC = yacc
+CFLAGS = -pipe -O2 -Wall -W -fPIC -D_REENTRANT -DQT_NO_DEBUG -DQT_THREAD_SUPPORT -DQT_SHARED -DQT_TABLET_SUPPORT -D__USE_STD_IOSTREAM
+CXXFLAGS = -pipe -O2 -Wall -W -fPIC -D_REENTRANT -DQT_NO_DEBUG -DQT_THREAD_SUPPORT -DQT_SHARED -DQT_TABLET_SUPPORT -D__USE_STD_IOSTREAM
+LEXFLAGS =
+YACCFLAGS= -d
+INCPATH = -I$(QTDIR)/mkspecs/default -I. -Ikreplacements -I/usr/include -I$(QTDIR)/include
+LINK = g++
+LFLAGS =
+LIBS = $(SUBLIBS) -L/usr/lib/ -L$(QTDIR)/lib/ -L/usr/X11R6/lib/ -lqt-mt -lXext -lX11 -lm -lpthread
+AR = ar cqs
+RANLIB =
+MOC = $(QTDIR)/bin/moc
+UIC = $(QTDIR)/bin/uic
+QMAKE = qmake
+TAR = tar -cf
+GZIP = gzip -9f
+COPY = cp -f
+COPY_FILE= $(COPY)
+COPY_DIR = $(COPY) -r
+INSTALL_FILE= $(COPY_FILE)
+INSTALL_DIR = $(COPY_DIR)
+DEL_FILE = rm -f
+SYMLINK = ln -sf
+DEL_DIR = rmdir
+MOVE = mv -f
+CHK_DIR_EXISTS= test -d
+MKDIR = mkdir -p
+
+####### Output directory
+
+OBJECTS_DIR = ./
+
+####### Files
+
+HEADERS = version.h \
+ diff.h \
+ difftextwindow.h \
+ mergeresultwindow.h \
+ kdiff3.h \
+ merger.h \
+ optiondialog.h \
+ kreplacements/kreplacements.h \
+ directorymergewindow.h \
+ fileaccess.h \
+ kdiff3_shell.h \
+ kdiff3_part.h \
+ smalldialogs.h
+SOURCES = main.cpp \
+ diff.cpp \
+ difftextwindow.cpp \
+ kdiff3.cpp \
+ merger.cpp \
+ mergeresultwindow.cpp \
+ optiondialog.cpp \
+ pdiff.cpp \
+ directorymergewindow.cpp \
+ fileaccess.cpp \
+ smalldialogs.cpp \
+ kdiff3_shell.cpp \
+ kdiff3_part.cpp \
+ gnudiff_analyze.cpp \
+ gnudiff_io.cpp \
+ gnudiff_xmalloc.cpp \
+ common.cpp \
+ kreplacements/kreplacements.cpp \
+ kreplacements/ShellContextMenu.cpp
+OBJECTS = main.o \
+ diff.o \
+ difftextwindow.o \
+ kdiff3.o \
+ merger.o \
+ mergeresultwindow.o \
+ optiondialog.o \
+ pdiff.o \
+ directorymergewindow.o \
+ fileaccess.o \
+ smalldialogs.o \
+ kdiff3_shell.o \
+ kdiff3_part.o \
+ gnudiff_analyze.o \
+ gnudiff_io.o \
+ gnudiff_xmalloc.o \
+ common.o \
+ kreplacements.o \
+ ShellContextMenu.o
+FORMS =
+UICDECLS =
+UICIMPLS =
+SRCMOC = moc_difftextwindow.cpp \
+ moc_mergeresultwindow.cpp \
+ moc_kdiff3.cpp \
+ moc_optiondialog.cpp \
+ kreplacements/moc_kreplacements.cpp \
+ moc_directorymergewindow.cpp \
+ moc_fileaccess.cpp \
+ moc_kdiff3_shell.cpp \
+ moc_kdiff3_part.cpp \
+ moc_smalldialogs.cpp
+OBJMOC = moc_difftextwindow.o \
+ moc_mergeresultwindow.o \
+ moc_kdiff3.o \
+ moc_optiondialog.o \
+ moc_kreplacements.o \
+ moc_directorymergewindow.o \
+ moc_fileaccess.o \
+ moc_kdiff3_shell.o \
+ moc_kdiff3_part.o \
+ moc_smalldialogs.o
+DIST = kdiff3.pro
+QMAKE_TARGET = kdiff3
+DESTDIR =
+TARGET = kdiff3
+
+first: all
+####### Implicit rules
+
+.SUFFIXES: .c .o .cpp .cc .cxx .C
+
+.cpp.o:
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<
+
+.cc.o:
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<
+
+.cxx.o:
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<
+
+.C.o:
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<
+
+.c.o:
+ $(CC) -c $(CFLAGS) $(INCPATH) -o $@ $<
+
+####### Build rules
+
+all: $(TARGET)
+
+$(TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC)
+ $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJMOC) $(OBJCOMP) $(LIBS)
+
+mocables: $(SRCMOC)
+uicables: $(UICDECLS) $(UICIMPLS)
+
+$(MOC):
+ ( cd $(QTDIR)/src/moc && $(MAKE) )
+
+dist:
+ @mkdir -p .tmp/kdiff3 && $(COPY_FILE) --parents $(SOURCES) $(HEADERS) $(FORMS) $(DIST) .tmp/kdiff3/ && ( cd `dirname .tmp/kdiff3` && $(TAR) kdiff3.tar kdiff3 && $(GZIP) kdiff3.tar ) && $(MOVE) `dirname .tmp/kdiff3`/kdiff3.tar.gz . && $(DEL_FILE) -r .tmp/kdiff3
+
+mocclean:
+ -$(DEL_FILE) $(OBJMOC)
+ -$(DEL_FILE) $(SRCMOC)
+
+uiclean:
+
+yaccclean:
+lexclean:
+clean: mocclean
+ -$(DEL_FILE) $(OBJECTS)
+ -$(DEL_FILE) *~ core *.core
+
+
+####### Sub-libraries
+
+distclean: clean
+ -$(DEL_FILE) $(TARGET) $(TARGET)
+
+
+FORCE:
+
+####### Compile
+
+main.o: main.cpp kdiff3_shell.h \
+ version.h \
+ optiondialog.h \
+ common.h
+
+diff.o: diff.cpp diff.h \
+ fileaccess.h \
+ optiondialog.h \
+ common.h
+
+difftextwindow.o: difftextwindow.cpp difftextwindow.h \
+ merger.h \
+ optiondialog.h \
+ diff.h \
+ common.h \
+ fileaccess.h
+
+kdiff3.o: kdiff3.cpp difftextwindow.h \
+ mergeresultwindow.h \
+ kdiff3.h \
+ optiondialog.h \
+ fileaccess.h \
+ kdiff3_part.h \
+ directorymergewindow.h \
+ smalldialogs.h \
+ xpm/downend.xpm \
+ xpm/currentpos.xpm \
+ xpm/down1arrow.xpm \
+ xpm/down2arrow.xpm \
+ xpm/upend.xpm \
+ xpm/up1arrow.xpm \
+ xpm/up2arrow.xpm \
+ xpm/prevunsolved.xpm \
+ xpm/nextunsolved.xpm \
+ xpm/iconA.xpm \
+ xpm/iconB.xpm \
+ xpm/iconC.xpm \
+ xpm/autoadvance.xpm \
+ xpm/showwhitespace.xpm \
+ xpm/showwhitespacechars.xpm \
+ xpm/showlinenumbers.xpm \
+ diff.h \
+ common.h
+
+merger.o: merger.cpp merger.h \
+ diff.h \
+ common.h \
+ fileaccess.h \
+ optiondialog.h
+
+mergeresultwindow.o: mergeresultwindow.cpp mergeresultwindow.h \
+ optiondialog.h \
+ diff.h \
+ common.h \
+ fileaccess.h
+
+optiondialog.o: optiondialog.cpp optiondialog.h \
+ diff.h \
+ smalldialogs.h \
+ common.h \
+ fileaccess.h
+
+pdiff.o: pdiff.cpp difftextwindow.h \
+ mergeresultwindow.h \
+ directorymergewindow.h \
+ smalldialogs.h \
+ kdiff3.h \
+ optiondialog.h \
+ fileaccess.h \
+ gnudiff_diff.h \
+ diff.h \
+ common.h \
+ gnudiff_system.h
+
+directorymergewindow.o: directorymergewindow.cpp directorymergewindow.h \
+ optiondialog.h \
+ xpm/link_arrow.xpm \
+ xpm/file.xpm \
+ xpm/folder.xpm \
+ xpm/startmerge.xpm \
+ xpm/showequalfiles.xpm \
+ xpm/showfilesonlyina.xpm \
+ xpm/showfilesonlyinb.xpm \
+ xpm/showfilesonlyinc.xpm \
+ common.h \
+ fileaccess.h \
+ diff.h
+
+fileaccess.o: fileaccess.cpp fileaccess.h \
+ optiondialog.h \
+ common.h
+
+smalldialogs.o: smalldialogs.cpp smalldialogs.h \
+ optiondialog.h \
+ diff.h \
+ common.h \
+ fileaccess.h
+
+kdiff3_shell.o: kdiff3_shell.cpp kdiff3_shell.h \
+ kdiff3.h \
+ diff.h \
+ common.h \
+ fileaccess.h \
+ optiondialog.h
+
+kdiff3_part.o: kdiff3_part.cpp kdiff3_part.h \
+ kdiff3.h \
+ fileaccess.h \
+ version.h \
+ diff.h \
+ common.h \
+ optiondialog.h
+
+gnudiff_analyze.o: gnudiff_analyze.cpp gnudiff_diff.h \
+ gnudiff_system.h
+
+gnudiff_io.o: gnudiff_io.cpp gnudiff_diff.h \
+ gnudiff_system.h
+
+gnudiff_xmalloc.o: gnudiff_xmalloc.cpp gnudiff_diff.h \
+ gnudiff_system.h
+
+common.o: common.cpp common.h
+
+kreplacements.o: kreplacements/kreplacements.cpp kreplacements/kreplacements.h \
+ common.h \
+ xpm/fileopen.xpm \
+ xpm/filesave.xpm \
+ xpm/fileprint.xpm \
+ kreplacements/kreplacements.moc
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -o kreplacements.o kreplacements/kreplacements.cpp
+
+ShellContextMenu.o: kreplacements/ShellContextMenu.cpp kreplacements/ShellContextMenu.h
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -o ShellContextMenu.o kreplacements/ShellContextMenu.cpp
+
+moc_difftextwindow.o: moc_difftextwindow.cpp difftextwindow.h diff.h \
+ common.h \
+ fileaccess.h \
+ optiondialog.h
+
+moc_mergeresultwindow.o: moc_mergeresultwindow.cpp mergeresultwindow.h diff.h \
+ common.h \
+ fileaccess.h \
+ optiondialog.h
+
+moc_kdiff3.o: moc_kdiff3.cpp kdiff3.h diff.h \
+ common.h \
+ fileaccess.h \
+ optiondialog.h
+
+moc_optiondialog.o: moc_optiondialog.cpp optiondialog.h
+
+moc_kreplacements.o: kreplacements/moc_kreplacements.cpp kreplacements/kreplacements.h common.h
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -o moc_kreplacements.o kreplacements/moc_kreplacements.cpp
+
+moc_directorymergewindow.o: moc_directorymergewindow.cpp directorymergewindow.h common.h \
+ fileaccess.h \
+ diff.h \
+ optiondialog.h
+
+moc_fileaccess.o: moc_fileaccess.cpp fileaccess.h
+
+moc_kdiff3_shell.o: moc_kdiff3_shell.cpp kdiff3_shell.h
+
+moc_kdiff3_part.o: moc_kdiff3_part.cpp kdiff3_part.h
+
+moc_smalldialogs.o: moc_smalldialogs.cpp smalldialogs.h diff.h \
+ common.h \
+ fileaccess.h \
+ optiondialog.h
+
+moc_difftextwindow.cpp: $(MOC) difftextwindow.h
+ $(MOC) difftextwindow.h -o moc_difftextwindow.cpp
+
+moc_mergeresultwindow.cpp: $(MOC) mergeresultwindow.h
+ $(MOC) mergeresultwindow.h -o moc_mergeresultwindow.cpp
+
+moc_kdiff3.cpp: $(MOC) kdiff3.h
+ $(MOC) kdiff3.h -o moc_kdiff3.cpp
+
+moc_optiondialog.cpp: $(MOC) optiondialog.h
+ $(MOC) optiondialog.h -o moc_optiondialog.cpp
+
+kreplacements/moc_kreplacements.cpp: $(MOC) kreplacements/kreplacements.h
+ $(MOC) kreplacements/kreplacements.h -o kreplacements/moc_kreplacements.cpp
+
+moc_directorymergewindow.cpp: $(MOC) directorymergewindow.h
+ $(MOC) directorymergewindow.h -o moc_directorymergewindow.cpp
+
+moc_fileaccess.cpp: $(MOC) fileaccess.h
+ $(MOC) fileaccess.h -o moc_fileaccess.cpp
+
+moc_kdiff3_shell.cpp: $(MOC) kdiff3_shell.h
+ $(MOC) kdiff3_shell.h -o moc_kdiff3_shell.cpp
+
+moc_kdiff3_part.cpp: $(MOC) kdiff3_part.h
+ $(MOC) kdiff3_part.h -o moc_kdiff3_part.cpp
+
+moc_smalldialogs.cpp: $(MOC) smalldialogs.h
+ $(MOC) smalldialogs.h -o moc_smalldialogs.cpp
+
+####### Install
+
+install_documentation: all
+ @$(CHK_DIR_EXISTS) "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/" || $(MKDIR) "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+ -$(INSTALL_DIR) "../doc/da" "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+ -$(INSTALL_DIR) "../doc/de" "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+ -$(INSTALL_DIR) "../doc/en" "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+ -$(INSTALL_DIR) "../doc/et" "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+ -$(INSTALL_DIR) "../doc/fr" "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+ -$(INSTALL_DIR) "../doc/it" "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+ -$(INSTALL_DIR) "../doc/pt" "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+ -$(INSTALL_DIR) "../doc/sv" "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+
+
+uninstall_documentation:
+ -$(DEL_FILE) -r "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/da"
+ -$(DEL_FILE) -r "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/de"
+ -$(DEL_FILE) -r "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/en"
+ -$(DEL_FILE) -r "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/et"
+ -$(DEL_FILE) -r "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/fr"
+ -$(DEL_FILE) -r "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/it"
+ -$(DEL_FILE) -r "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/pt"
+ -$(DEL_FILE) -r "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/sv"
+ -$(DEL_DIR) "$(INSTALL_ROOT)/usr/local/share/doc/kdiff3/"
+
+
+install_target: all
+ @$(CHK_DIR_EXISTS) "$(INSTALL_ROOT)/usr/local/bin/" || $(MKDIR) "$(INSTALL_ROOT)/usr/local/bin/"
+ -$(INSTALL_FILE) "$(QMAKE_TARGET)" "$(INSTALL_ROOT)/usr/local/bin/$(QMAKE_TARGET)"
+
+uninstall_target:
+ -$(DEL_FILE) "$(INSTALL_ROOT)/usr/local/bin/$(QMAKE_TARGET)"
+ -$(DEL_DIR) "$(INSTALL_ROOT)/usr/local/bin/"
+
+
+install: install_documentation install_target
+
+uninstall: uninstall_documentation uninstall_target
+
diff --git a/src/ccInstHelper.cpp b/src/ccInstHelper.cpp
new file mode 100644
index 0000000..055e2d6
--- /dev/null
+++ b/src/ccInstHelper.cpp
@@ -0,0 +1,334 @@
+// uninstallHelper.cpp : Defines the entry point for the console application.
+//
+#include <iostream>
+#include <string>
+#include <vector>
+#include <list>
+#include <windows.h>
+#include <string.h>
+#include <io.h>
+
+//#define __stdcall
+
+#ifndef KREPLACEMENTS_H
+// For compilation download the NSIS source package and modify the following
+// line to point to the exdll.h-file
+#include "C:/Programme/NSIS/Contrib/ExDll/exdll.h"
+#endif
+
+struct ReplacementItem
+{ char* fileType; char* operationType; };
+
+ReplacementItem g_replacementTable[] = {
+ "text_file_delta", "xcompare",
+ "text_file_delta", "xmerge",
+ "whole_copy", "xcompare",
+ "whole_copy", "xmerge",
+ "z_text_file_delta", "xcompare",
+ "z_text_file_delta", "xmerge",
+ "z_whole_copy", "xcompare",
+ "z_whole_copy", "xmerge",
+ "_xml", "xcompare",
+ "_xml", "xmerge",
+ "_xml2", "xcompare",
+ "_xml2", "xmerge",
+ "_rftdef", "xcompare",
+ "_rftmap", "xcompare",
+ "_rftvp", "xcompare",
+ "_xtools", "xcompare",
+ 0,0
+};
+
+struct LineItem
+{
+ std::string fileType;
+ std::string opType;
+ std::string command;
+ std::string fileOpPart;
+};
+
+// Return true if successful, else false
+bool readAndParseMapFile( const std::string& filename, std::list<LineItem>& lineItemList )
+{
+ // Read file
+ FILE* pFile = fopen( filename.c_str(), "r" );
+ if (pFile)
+ {
+ fseek(pFile,0,SEEK_END);
+ int size = ftell(pFile);
+ fseek(pFile,0,SEEK_SET);
+ std::vector<char> buf( size );
+ fread( &buf[0], 1, size, pFile );
+ fclose( pFile );
+
+ // Replace strings
+ int lineStartPos=0;
+ int wordInLine = 0;
+ LineItem lineItem;
+ for( int i=0; i<size; )
+ {
+ if( buf[i] == '\n' || buf[i] == '\r' )
+ {
+ ++i;
+ wordInLine = 0;
+ lineStartPos = i;
+ continue;
+ }
+ if( buf[i] == ' ' || buf[i] == '\t' )
+ {
+ ++i;
+ continue;
+ }
+ else
+ {
+ int wordStartPos = i;
+ if (wordInLine<2)
+ {
+ while ( i<size && !( buf[i] == ' ' || buf[i] == '\t' ) )
+ ++i;
+
+ std::string word( &buf[wordStartPos], i-wordStartPos );
+ if (wordInLine==0)
+ lineItem.fileType = word;
+ else
+ lineItem.opType = word;
+ ++wordInLine;
+ }
+ else
+ {
+ lineItem.fileOpPart = std::string( &buf[lineStartPos], i-lineStartPos );
+ while ( i<size && !( buf[i] == '\n' || buf[i] == '\r' ) )
+ ++i;
+
+ std::string word( &buf[wordStartPos], i-wordStartPos );
+ lineItem.command = word;
+ lineItemList.push_back( lineItem );
+ }
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+}
+
+bool writeMapFile( const std::string& filename, const std::list<LineItem>& lineItemList )
+{
+ FILE* pFile = fopen( filename.c_str(), "w" );
+ if (pFile)
+ {
+ std::list<LineItem>::const_iterator i = lineItemList.begin();
+ for( ; i!=lineItemList.end(); ++i )
+ {
+ const LineItem& li = *i;
+ fprintf( pFile, "%s%s\n", li.fileOpPart.c_str(), li.command.c_str() );
+ }
+ fclose( pFile );
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+}
+
+std::string toUpper( const std::string& s )
+{
+ std::string s2 = s;
+
+ for( unsigned int i=0; i<s.length(); ++i )
+ {
+ s2[i] = toupper( s2[i] );
+ }
+ return s2;
+}
+
+int integrateWithClearCase( const char* subCommand, const char* kdiff3CommandPath )
+{
+ std::string installCommand = subCommand; // "install" or "uninstall" or "existsClearCase"
+ std::string kdiff3Command = kdiff3CommandPath;
+
+ /*
+ std::wstring installCommand = subCommand; // "install" or "uninstall"
+ std::wstring wKDiff3Command = kdiff3CommandPath;
+ std::string kdiff3Command;
+ kdiff3Command.reserve( wKDiff3Command.length()+1 );
+ kdiff3Command.resize( wKDiff3Command.length() );
+ BOOL bUsedDefaultChar = FALSE;
+ int successLen = WideCharToMultiByte( CP_ACP, 0,
+ wKDiff3Command.c_str(), int(wKDiff3Command.length()),
+ &kdiff3Command[0], int(kdiff3Command.length()), 0, &bUsedDefaultChar );
+
+ if ( successLen != kdiff3Command.length() || bUsedDefaultChar )
+ {
+ std::cerr << "KDiff3 command contains characters that don't map to ansi code page.\n"
+ "Aborting clearcase installation.\n"
+ "Try to install KDiff3 in another path that doesn't require special characters.\n";
+ return -1;
+ }
+ */
+
+ // Try to locate cleartool, the clearcase tool in the path
+ char buffer[1000];
+ char* pLastPart = 0;
+ int len = SearchPathA(0, "cleartool.exe", 0, sizeof(buffer)/sizeof(buffer[0]),
+ buffer, &pLastPart );
+ if ( len>0 && len+1<int(sizeof(buffer)/sizeof(buffer[0])) && pLastPart )
+ {
+ pLastPart[-1] = 0;
+ pLastPart = strrchr( buffer, '\\' ); // cd up (because cleartool.exe is in bin subdir)
+ if ( pLastPart )
+ pLastPart[1]=0;
+
+ std::string path( buffer );
+ path += "lib\\mgrs\\map";
+ std::string bakName = path + ".preKDiff3Install";
+
+ if ( installCommand == "existsClearCase")
+ {
+ return 1;
+ }
+ else if ( installCommand == "install")
+ {
+ std::list<LineItem> lineItemList;
+ bool bSuccess = readAndParseMapFile( path, lineItemList );
+ if ( !bSuccess )
+ {
+ std::cerr << "Error reading original map file.\n";
+ return -1;
+ }
+
+ // Create backup
+ if ( access( bakName.c_str(), 0 )!=0 ) // Create backup only if not exists yet
+ {
+ if ( rename( path.c_str(), bakName.c_str() ) )
+ {
+ std::cerr << "Error renaming original map file.\n";
+ return -1;
+ }
+ }
+
+ std::list<LineItem>::iterator i = lineItemList.begin();
+ for( ; i!=lineItemList.end(); ++i )
+ {
+ LineItem& li = *i;
+ for (int j=0;;++j)
+ {
+ ReplacementItem& ri = g_replacementTable[j];
+ if ( ri.fileType==0 || ri.operationType==0 )
+ break;
+ if ( li.fileType == ri.fileType && li.opType == ri.operationType )
+ {
+ li.command = kdiff3Command.c_str();
+ break;
+ }
+ }
+ }
+
+ bSuccess = writeMapFile( path, lineItemList );
+ if ( !bSuccess )
+ {
+ if ( rename( bakName.c_str(), path.c_str() ) )
+ std::cerr << "Error writing new map file, restoring old file also failed.\n";
+ else
+ std::cerr << "Error writing new map file, old file restored.\n";
+
+ return -1;
+ }
+ }
+ else if ( installCommand == "uninstall" )
+ {
+ std::list<LineItem> lineItemList;
+ bool bSuccess = readAndParseMapFile( path, lineItemList );
+ if ( !bSuccess )
+ {
+ std::cerr << "Error reading original map file\n.";
+ return -1;
+ }
+
+ std::list<LineItem> lineItemListBak;
+ bSuccess = readAndParseMapFile( bakName, lineItemListBak );
+ if ( !bSuccess )
+ {
+ std::cerr << "Error reading backup map file.\n";
+ return -1;
+ }
+
+ std::list<LineItem>::iterator i = lineItemList.begin();
+ for( ; i!=lineItemList.end(); ++i )
+ {
+ LineItem& li = *i;
+ if ((int)toUpper(li.command).find("KDIFF3")>=0)
+ {
+ std::list<LineItem>::const_iterator j = lineItemListBak.begin();
+ for (;j!=lineItemListBak.end();++j)
+ {
+ const LineItem& bi = *j; // backup iterator
+ if ( li.fileType == bi.fileType && li.opType == bi.opType )
+ {
+ li.command = bi.command;
+ break;
+ }
+ }
+ }
+ }
+
+ bSuccess = writeMapFile( path, lineItemList );
+ if ( !bSuccess )
+ {
+ std::cerr << "Error writing map file.";
+
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+#ifndef KREPLACEMENTS_H
+
+extern "C"
+void __declspec(dllexport) nsisPlugin(HWND hwndParent, int string_size,
+ char *variables, stack_t **stacktop,
+ extra_parameters *extra)
+{
+ //g_hwndParent=hwndParent;
+
+ EXDLL_INIT();
+ {
+ std::string param1( g_stringsize, ' ' );
+ int retVal = popstring( &param1[0] );
+ if ( retVal == 0 )
+ {
+ std::string param2( g_stringsize, ' ' );
+ retVal = popstring( &param2[0] );
+ if ( retVal == 0 )
+ install( param1.c_str(), param2.c_str() );
+ return;
+ }
+ std::cerr << "Not enough parameters." << std::endl;
+ }
+}
+
+#endif
+/*
+int _tmain(int argc, _TCHAR* argv[])
+{
+ if ( argc<3 )
+ {
+ std::cout << "This program is needed to install/uninstall KDiff3 for clearcase.\n"
+ "It tries to patch the map file (clearcase-subdir\\lib\\mgrs\\map)\n"
+ "Usage 1: ccInstHelper install pathToKdiff3.exe\n"
+ "Usage 2: ccInstHelper uninstall pathToKdiff3.exe\n"
+ "Backups of the original map files are created in the dir of the map file.\n";
+ }
+ else
+ {
+ return install( argv[1], argv[2] );
+ }
+
+ return 0;
+}
+*/
diff --git a/src/common.cpp b/src/common.cpp
new file mode 100644
index 0000000..a05920e
--- /dev/null
+++ b/src/common.cpp
@@ -0,0 +1,341 @@
+/***************************************************************************
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#include "common.h"
+#include <map>
+#include <qfont.h>
+#include <qcolor.h>
+#include <qsize.h>
+#include <qpoint.h>
+#include <qstringlist.h>
+#include <qtextstream.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.eof() )
+ { // until end of file...
+ QString s = ts.readLine(); // line of text excluding '\n'
+ int pos = s.find('=');
+ if( pos > 0 ) // seems not to have a tag
+ {
+ QString key = s.left(pos);
+ QString val = s.mid(pos+1);
+ m_map[key] = val;
+ }
+ }
+}
+/*
+void ValueMap::load( const QString& s )
+{
+ int pos=0;
+ while ( pos<(int)s.length() )
+ { // until end of file...
+ int pos2 = s.find('=', pos);
+ int pos3 = s.find('\n', pos2 );
+ if (pos3<0)
+ pos3=s.length();
+ if( pos2 > 0 ) // seems not to have a tag
+ {
+ QString key = s.mid(pos, pos2-pos);
+ QString val = s.mid(pos2+1, pos3-pos2-1);
+ m_map[key] = val;
+ }
+ pos = pos3;
+ }
+}
+*/
+
+// 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 occurances of "\" (one backslash) will be replaced with "\\" (2 backslashes)
+
+ 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 )
+{
+ 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;
+}
+
+
+
+static QString numStr(int n)
+{
+ QString s;
+ s.setNum( n );
+ return s;
+}
+
+static QString subSection( const QString& s, int idx, char sep )
+{
+ int pos=0;
+ while( idx>0 )
+ {
+ pos = s.find( sep, pos );
+ --idx;
+ if (pos<0) break;
+ ++pos;
+ }
+ if ( pos>=0 )
+ {
+ int pos2 = s.find( sep, pos );
+ if ( pos2>0 )
+ return s.mid(pos, pos2-pos);
+ else
+ return s.mid(pos);
+ }
+
+ return "";
+}
+
+static int num( QString& s, int idx )
+{
+ return subSection( s, idx, ',').toInt();
+}
+
+void ValueMap::writeEntry(const QString& k, const QFont& v )
+{
+ m_map[k] = v.family() + "," + QString::number(v.pointSize()) + "," + (v.bold() ? "bold" : "normal");
+}
+
+void ValueMap::writeEntry(const QString& k, const QColor& v )
+{
+ m_map[k] = numStr(v.red()) + "," + numStr(v.green()) + "," + numStr(v.blue());
+}
+
+void ValueMap::writeEntry(const QString& k, const QSize& v )
+{
+ m_map[k] = numStr(v.width()) + "," + numStr(v.height());
+}
+
+void ValueMap::writeEntry(const QString& k, const QPoint& v )
+{
+ m_map[k] = numStr(v.x()) + "," + numStr(v.y());
+}
+
+void ValueMap::writeEntry(const QString& k, int v )
+{
+ m_map[k] = numStr(v);
+}
+
+void ValueMap::writeEntry(const QString& k, bool v )
+{
+ m_map[k] = numStr(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] = v;
+}
+
+void ValueMap::writeEntry(const QString& k, const QStringList& v, char separator )
+{
+ m_map[k] = safeStringJoin(v, separator);
+}
+
+
+QFont ValueMap::readFontEntry(const QString& k, QFont* defaultVal )
+{
+ QFont f = *defaultVal;
+ std::map<QString,QString>::iterator i = m_map.find( k );
+ if ( i!=m_map.end() )
+ {
+ f.setFamily( subSection( i->second, 0, ',' ) );
+ f.setPointSize( subSection( i->second, 1, ',' ).toInt() );
+ f.setBold( subSection( i->second, 2, ',' )=="bold" );
+ //f.fromString(i->second);
+ }
+
+ return f;
+}
+
+QColor ValueMap::readColorEntry(const QString& k, 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( num(s,0),num(s,1),num(s,2) );
+ }
+
+ return c;
+}
+
+QSize ValueMap::readSizeEntry(const QString& k, 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( num(s,0),num(s,1) );
+ }
+
+ return size;
+}
+
+QPoint ValueMap::readPointEntry(const QString& k, 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( num(s,0),num(s,1) );
+ }
+
+ 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 = (bool)num(s,0);
+ }
+
+ 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 = num(s,0);
+ }
+
+ return ival;
+}
+
+QString ValueMap::readEntry(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, char separator )
+{
+ QStringList strList;
+
+ std::map<QString,QString>::iterator i = m_map.find( k );
+ if ( i!=m_map.end() )
+ {
+ strList = safeStringSplit( i->second, separator );
+ return strList;
+ }
+ else
+ return defaultVal;
+}
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..b78d5bc
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,113 @@
+/***************************************************************************
+ 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 <assert.h>
+
+template< class T >
+T min2( T x, T y )
+{
+ return x<y ? x : y;
+}
+template< class T >
+T max2( T x, T y )
+{
+ return x>y ? x : y;
+}
+
+typedef unsigned char UINT8;
+typedef unsigned short UINT16;
+typedef unsigned int UINT32;
+
+
+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 )
+{
+ assert(minimum<=maximum);
+ if ( d < minimum ) return minimum;
+ if ( d > maximum ) return maximum;
+ return d;
+}
+
+#include <map>
+#include <qstring.h>
+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&, char separator );
+ virtual void writeEntry(const QString&, const QString& );
+ virtual void writeEntry(const QString&, const char* );
+
+ virtual QFont readFontEntry (const QString&, QFont* defaultVal );
+ virtual QColor readColorEntry(const QString&, QColor* defaultVal );
+ virtual QSize readSizeEntry (const QString&, QSize* defaultVal );
+ virtual QPoint readPointEntry(const QString&, QPoint* defaultVal );
+ virtual bool readBoolEntry (const QString&, bool bDefault );
+ virtual int readNumEntry (const QString&, int iDefault );
+ virtual QStringList readListEntry (const QString&, const QStringList& defaultVal, char separator );
+ virtual QString readEntry (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/diff.cpp b/src/diff.cpp
new file mode 100644
index 0000000..7875bc6
--- /dev/null
+++ b/src/diff.cpp
@@ -0,0 +1,1920 @@
+/***************************************************************************
+ 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 <cstdlib>
+#include <stdio.h>
+#include <iostream>
+
+#include "diff.h"
+#include "fileaccess.h"
+#include "optiondialog.h"
+
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qtextcodec.h>
+
+#include <map>
+#include <assert.h>
+#include <ctype.h>
+//using namespace std;
+
+
+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==0 || l2.pLine==0) return false;
+
+ if ( bStrict && g_bIgnoreTrivialMatches )//&& (l1.occurances>=5 || l2.occurances>=5) )
+ 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
+ {
+ if ( l1.size==l2.size && memcmp(p1, p2, l1.size)==0)
+ return true;
+ else
+ return false;
+ }
+}
+
+
+static bool isLineOrBufEnd( const QChar* p, int i, int size )
+{
+ return
+ i>=size // End of file
+ || p[i]=='\n' // 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_pOptionDialog = 0;
+ reset();
+}
+
+SourceData::~SourceData()
+{
+ reset();
+}
+
+void SourceData::reset()
+{
+ m_pEncoding = 0;
+ 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 != 0;
+}
+
+bool SourceData::isValid()
+{
+ return isEmpty() || hasData();
+}
+
+void SourceData::setOptionDialog( OptionDialog* pOptionDialog )
+{
+ m_pOptionDialog = pOptionDialog;
+}
+
+QString SourceData::getFilename()
+{
+ return m_fileAccess.absFilePath();
+}
+
+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::setData( const QString& data )
+{
+ // Create a temp file for preprocessing:
+ if ( m_tempInputFileName.isEmpty() )
+ {
+ m_tempInputFileName = FileAccess::tempFileName();
+ }
+
+ FileAccess f( m_tempInputFileName );
+ bool bSuccess = f.writeFile( QTextCodec::codecForName("UTF-8")->fromUnicode(data), data.length() );
+ if ( !bSuccess )
+ {
+ KMessageBox::error( m_pOptionDialog, i18n("Writing clipboard data to temp file failed.") );
+ return;
+ }
+
+ m_aliasName = i18n("From Clipboard");
+ m_fileAccess = FileAccess(""); // Effect: m_fileAccess.isValid() is false
+}
+
+const LineData* SourceData::getLineDataForDiff() const
+{
+ if ( m_lmppData.m_pBuf==0 )
+ return m_normalData.m_v.size()>0 ? &m_normalData.m_v[0] : 0;
+ else
+ return m_lmppData.m_v.size()>0 ? &m_lmppData.m_v[0] : 0;
+}
+
+const LineData* SourceData::getLineDataForDisplay() const
+{
+ return m_normalData.m_v.size()>0 ? &m_normalData.m_v[0] : 0;
+}
+
+int SourceData::getSizeLines() const
+{
+ return m_normalData.m_vSize;
+}
+
+int SourceData::getSizeBytes() const
+{
+ return m_normalData.m_size;
+}
+
+const char* SourceData::getBuf() const
+{
+ return m_normalData.m_pBuf;
+}
+
+bool SourceData::isText()
+{
+ return m_normalData.m_bIsText;
+}
+
+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 = 0;
+ m_v.clear();
+ m_size = 0;
+ m_vSize = 0;
+ m_bIsText = true;
+}
+
+bool SourceData::FileData::readFile( const QString& filename )
+{
+ reset();
+ if ( filename.isEmpty() ) { return true; }
+
+ FileAccess fa( filename );
+ m_size = fa.sizeForReading();
+ char* pBuf;
+ m_pBuf = pBuf = new char[m_size+100]; // Alloc 100 byte extra: Savety 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 = 0;
+ 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];
+ 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(IO_ReadOnly ) )
+ return false;
+ QTextStream inStream( &in );
+ inStream.setCodec( pCodecIn );
+ //inStream.setAutoDetectUnicode( false ); //not available in Qt3, will always detect UCS2
+
+ QFile out( fileNameOut );
+ if ( ! out.open( IO_WriteOnly ) )
+ return false;
+ QTextStream outStream( &out );
+ outStream.setCodec( pCodecOut );
+
+ QString data = inStream.read();
+ outStream << data;
+
+ return true;
+}
+
+static QTextCodec* detectEncoding( const char* buf, long size, long& skipBytes )
+{
+ if (size>=2)
+ {
+ skipBytes = 0; // In Qt3 UTF-16LE can only be used if autodetected.
+ if (buf[0]=='\xFF' && buf[1]=='\xFE' )
+ return QTextCodec::codecForName( "ISO-10646-UCS2" );// "UTF-16LE"
+
+ if (buf[0]=='\xFE' && buf[1]=='\xFF' )
+ return QTextCodec::codecForName( "ISO-10646-UCS2" );// "UTF-16BE". Qt3 autodetects the difference but has no name for it.
+ }
+ if (size>=3)
+ {
+ skipBytes = 3;
+ if (buf[0]=='\xEF' && buf[1]=='\xBB' && buf[2]=='\xBF' )
+ return QTextCodec::codecForName( "UTF-8-BOM" );
+ }
+ skipBytes = 0;
+ return 0;
+}
+
+QTextCodec* SourceData::detectEncoding( const QString& fileName, QTextCodec* pFallbackCodec )
+{
+ QFile f(fileName);
+ if ( f.open(IO_ReadOnly) )
+ {
+ char buf[4];
+ long size = f.readBlock( buf, sizeof(buf) );
+ long skipBytes = 0;
+ QTextCodec* pCodec = ::detectEncoding( buf, size, skipBytes );
+ if (pCodec)
+ return pCodec;
+ }
+ return pFallbackCodec;
+}
+
+void SourceData::readAndPreprocess( QTextCodec* pEncoding, bool bAutoDetectUnicode )
+{
+ m_pEncoding = pEncoding;
+ QString fileNameIn1;
+ QString fileNameOut1;
+ QString fileNameIn2;
+ QString fileNameOut2;
+
+ bool bTempFileFromClipboard = !m_fileAccess.isValid();
+
+ // Detect the input for the preprocessing operations
+ if ( !bTempFileFromClipboard )
+ {
+ if ( m_fileAccess.isLocal() )
+ {
+ fileNameIn1 = m_fileAccess.absFilePath();
+ }
+ else // File is not local: create a temporary local copy:
+ {
+ if ( m_tempInputFileName.isEmpty() ) { m_tempInputFileName = FileAccess::tempFileName(); }
+
+ m_fileAccess.copyFile(m_tempInputFileName);
+ 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);
+ int fileInSize = faIn.size();
+
+ if ( faIn.exists() ) // fileInSize > 0 )
+ {
+
+#ifdef _WIN32
+ QString catCmd = "type";
+ fileNameIn1.replace( '/', "\\" );
+#else
+ QString catCmd = "cat";
+#endif
+
+ // Run the first preprocessor
+ if ( m_pOptionDialog->m_PreProcessorCmd.isEmpty() )
+ {
+ // No preprocessing: Read the file directly:
+ m_normalData.readFile( fileNameIn1 );
+ }
+ else
+ {
+ QString fileNameInPP = fileNameIn1;
+
+ if ( pEncoding1 != m_pOptionDialog->m_pEncodingPP )
+ {
+ // Before running the preprocessor convert to the format that the preprocessor expects.
+ fileNameInPP = FileAccess::tempFileName();
+ pEncoding1 = m_pOptionDialog->m_pEncodingPP;
+ convertFileEncoding( fileNameIn1, pEncoding, fileNameInPP, pEncoding1 );
+ }
+
+ QString ppCmd = m_pOptionDialog->m_PreProcessorCmd;
+ fileNameOut1 = FileAccess::tempFileName();
+ QString cmd = catCmd + " \"" + fileNameInPP + "\" | " + ppCmd + " >\"" + fileNameOut1+"\"";
+ ::system( encodeString(cmd) );
+ bool bSuccess = m_normalData.readFile( fileNameOut1 );
+ if ( fileInSize >0 && ( !bSuccess || m_normalData.m_size==0 ) )
+ {
+ KMessageBox::error(m_pOptionDialog,
+ i18n("Preprocessing possibly failed. Check this command:\n\n %1"
+ "\n\nThe preprocessing command will be disabled now."
+ ).arg(cmd) );
+ m_pOptionDialog->m_PreProcessorCmd = "";
+ m_normalData.readFile( fileNameIn1 );
+ pEncoding1 = m_pEncoding;
+ }
+ if (fileNameInPP != fileNameIn1)
+ {
+ FileAccess::removeTempFile( fileNameInPP );
+ }
+ }
+
+ // LineMatching Preprocessor
+ if ( ! m_pOptionDialog->m_LineMatchingPreProcessorCmd.isEmpty() )
+ {
+ fileNameIn2 = fileNameOut1.isEmpty() ? fileNameIn1 : fileNameOut1;
+ QString fileNameInPP = fileNameIn2;
+ pEncoding2 = pEncoding1;
+ if ( pEncoding2 != m_pOptionDialog->m_pEncodingPP )
+ {
+ // Before running the preprocessor convert to the format that the preprocessor expects.
+ fileNameInPP = FileAccess::tempFileName();
+ pEncoding2 = m_pOptionDialog->m_pEncodingPP;
+ convertFileEncoding( fileNameIn2, pEncoding1, fileNameInPP, pEncoding2 );
+ }
+
+ QString ppCmd = m_pOptionDialog->m_LineMatchingPreProcessorCmd;
+ fileNameOut2 = FileAccess::tempFileName();
+ QString cmd = catCmd + " \"" + fileNameInPP + "\" | " + ppCmd + " >\"" + fileNameOut2 + "\"";
+ ::system( encodeString(cmd) );
+ bool bSuccess = m_lmppData.readFile( fileNameOut2 );
+ if ( FileAccess(fileNameIn2).size()>0 && ( !bSuccess || m_lmppData.m_size==0 ) )
+ {
+ KMessageBox::error(m_pOptionDialog,
+ i18n("The line-matching-preprocessing possibly failed. Check this command:\n\n %1"
+ "\n\nThe line-matching-preprocessing command will be disabled now."
+ ).arg(cmd) );
+ m_pOptionDialog->m_LineMatchingPreProcessorCmd = "";
+ m_lmppData.readFile( fileNameIn2 );
+ }
+ FileAccess::removeTempFile( fileNameOut2 );
+ if (fileNameInPP != fileNameIn2)
+ {
+ FileAccess::removeTempFile( fileNameInPP );
+ }
+ }
+ else if ( m_pOptionDialog->m_bIgnoreComments || m_pOptionDialog->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();
+ }
+ }
+
+ m_normalData.preprocess( m_pOptionDialog->m_bPreserveCarriageReturn, pEncoding1 );
+ m_lmppData.preprocess( false, pEncoding2 );
+
+ if ( m_lmppData.m_vSize < m_normalData.m_vSize )
+ {
+ // This probably is the fault of the LMPP-Command, but not worth reporting.
+ m_lmppData.m_v.resize( m_normalData.m_vSize );
+ for(int 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[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_pOptionDialog->m_bIgnoreCase )
+ {
+ int i;
+ QChar* pBuf = const_cast<QChar*>(m_lmppData.m_unicodeBuf.unicode());
+ int ucSize = m_lmppData.m_unicodeBuf.length();
+ for(i=0; i<ucSize; ++i)
+ {
+ pBuf[i] = pBuf[i].upper();
+ }
+ }
+
+ // Ignore comments
+ if ( m_pOptionDialog->m_bIgnoreComments )
+ {
+ m_lmppData.removeComments();
+ int vSize = min2(m_normalData.m_vSize, m_lmppData.m_vSize);
+ for(int i=0; i<vSize; ++i )
+ {
+ m_normalData.m_v[i].bContainsPureComment = m_lmppData.m_v[i].bContainsPureComment;
+ }
+ }
+
+ // Remove unneeded temporary files. (A temp file from clipboard must not be deleted.)
+ if ( !bTempFileFromClipboard && !m_tempInputFileName.isEmpty() )
+ {
+ FileAccess::removeTempFile( m_tempInputFileName );
+ m_tempInputFileName = "";
+ }
+
+ if ( !fileNameOut1.isEmpty() )
+ {
+ FileAccess::removeTempFile( fileNameOut1 );
+ fileNameOut1="";
+ }
+}
+
+
+/** Prepare the linedata vector for every input line.*/
+void SourceData::FileData::preprocess( bool bPreserveCR, QTextCodec* pEncoding )
+{
+ //m_unicodeBuf = decodeString( m_pBuf, m_size, eEncoding );
+
+ long skipBytes = 0;
+ QTextCodec* pCodec = ::detectEncoding( m_pBuf, m_size, skipBytes );
+ if ( pCodec != pEncoding )
+ skipBytes=0;
+
+ QByteArray ba;
+ ba.setRawData( m_pBuf+skipBytes, m_size-skipBytes );
+ QTextStream ts( ba, IO_ReadOnly );
+ ts.setCodec( pEncoding);
+ //ts.setAutoDetectUnicode( false );
+ m_unicodeBuf = ts.read();
+ ba.resetRawData( m_pBuf+skipBytes, m_size-skipBytes );
+
+ int ucSize = m_unicodeBuf.length();
+ const QChar* p = m_unicodeBuf.unicode();
+
+ m_bIsText = true;
+ int lines = 1;
+ int i;
+ for( i=0; i<ucSize; ++i )
+ {
+ if ( isLineOrBufEnd(p,i,ucSize) )
+ {
+ ++lines;
+ }
+ if ( p[i]=='\0' )
+ {
+ m_bIsText = false;
+ }
+ }
+
+ m_v.resize( lines+5 );
+ int lineIdx=0;
+ int lineLength=0;
+ bool bNonWhiteFound = false;
+ int whiteLength = 0;
+ for( i=0; i<=ucSize; ++i )
+ {
+ if ( isLineOrBufEnd( p, i, ucSize ) )
+ {
+ 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 + min2(whiteLength,lineLength);
+ m_v[lineIdx].size = lineLength;
+ lineLength = 0;
+ bNonWhiteFound = false;
+ whiteLength = 0;
+ ++lineIdx;
+ }
+ else
+ {
+ ++lineLength;
+
+ if ( ! bNonWhiteFound && isWhite( p[i] ) )
+ ++whiteLength;
+ else
+ bNonWhiteFound = true;
+ }
+ }
+ assert( lineIdx == lines );
+
+ m_vSize = lines;
+}
+
+
+// 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.
+static void checkLineForComments(
+ 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 )
+ {
+ memset( &p[commentStart], ' ', i-commentStart );
+ }
+ 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 )
+ {
+ memset( &p[commentStart], ' ', i-commentStart );
+ }
+ 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;
+ QChar* p = const_cast<QChar*>(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 )
+ {
+ memset( &p[commentStart], ' ', i-commentStart );
+ }
+ break;
+ }
+ }
+ }
+ else
+ {
+ checkLineForComments( p, i, size, bWhite, bCommentInLine, bWithinComment );
+ }
+
+ // end of line
+ 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;
+ }
+
+ 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;
+
+ assert(i3b!=d3ll.end());
+ assert(i3c!=d3ll.end());
+
+ if ( i3b==i3c )
+ {
+ 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 )
+ {
+ 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 )
+ {
+ // Move the disturbing lines up, out of sight.
+ i3 = i3c;
+ while( i3 != i3b )
+ {
+ if ( (*i3).lineB != -1 )
+ {
+ Diff3Line d3l;
+ d3l.lineB = (*i3).lineB;
+ (*i3).lineB = -1;
+ (*i3).bAEqB = false;
+ (*i3).bBEqC = false;
+ d3ll.insert( i3c, d3l );
+ }
+ ++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).bAEqC = false;
+ (*i3b).bBEqC = false;
+ (*i3c).lineB = lineB;
+ (*i3c).bBEqC = true;
+ }
+ }
+ 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 )
+ {
+ // Move the disturbing lines up.
+ i3 = i3b;
+ while( i3 != i3c )
+ {
+ if ( (*i3).lineC != -1 )
+ {
+ Diff3Line d3l;
+ d3l.lineC = (*i3).lineC;
+ (*i3).lineC = -1;
+ (*i3).bAEqC = false;
+ (*i3).bBEqC = false;
+ d3ll.insert( i3b, d3l );
+ }
+ ++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;
+ }
+ }
+ }
+
+ --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==false )
+ {
+ // 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");*/
+}
+
+#ifdef _WIN32
+using ::equal;
+#endif
+
+// 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.
+}
+
+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 winIdxPreferred = 0;
+ 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 );
+ winIdxPreferred = missingWinIdx == 1 ? 2 : 1;
+ }
+ 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.remove( 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;
+ ++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;
+ ++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;
+ ++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;
+
+ (*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;
+
+ (*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;
+
+ (*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.remove( d3l_empty );
+
+/*
+
+ Diff3LineList::iterator it = d3ll.begin();
+ int li=0;
+ for( ; it!=d3ll.end(); ++it, ++li )
+ {
+ printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n",
+ li, (*it).lineA, (*it).lineB, (*it).lineC,
+ (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' );
+
+ }
+*/
+}
+
+void DiffBufferInfo::init( Diff3LineList* pD3ll, const Diff3LineVector* pD3lv,
+ const LineData* pldA, int sizeA, const LineData* pldB, int sizeB, const LineData* pldC, int 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==0 || pldA[(*i3).lineA].whiteLine() || pldA[(*i3).lineA].bContainsPureComment );
+ i3->bWhiteLineB = ( (*i3).lineB == -1 || pldB==0 || pldB[(*i3).lineB].whiteLine() || pldB[(*i3).lineB].bContainsPureComment );
+ i3->bWhiteLineC = ( (*i3).lineC == -1 || pldC==0 || pldC[(*i3).lineC].whiteLine() || pldC[(*i3).lineC].bContainsPureComment );
+ }
+}
+
+// Just make sure that all input lines are in the output too, exactly once.
+void debugLineCheck( Diff3LineList& d3ll, int size, int idx )
+{
+ Diff3LineList::iterator it = d3ll.begin();
+ int i=0;
+
+ for ( it = d3ll.begin(); it!= d3ll.end(); ++it )
+ {
+ int l=0;
+ if (idx==1) l=(*it).lineA;
+ else if (idx==2) l=(*it).lineB;
+ else if (idx==3) l=(*it).lineC;
+ else assert(false);
+
+ if ( l!=-1 )
+ {
+ if( l!=i )
+ {
+ KMessageBox::error(0, i18n(
+ "Data loss error:\n"
+ "If it is reproducable please contact the author.\n"
+ ), i18n("Severe Internal Error") );
+ assert(false);
+ std::cerr << "Severe Internal Error.\n";
+ ::exit(-1);
+ }
+ ++i;
+ }
+ }
+
+ if( size!=i )
+ {
+ KMessageBox::error(0, i18n(
+ "Data loss error:\n"
+ "If it is reproducable please contact the author.\n"
+ ), i18n("Severe Internal Error") );
+ assert(false);
+ std::cerr << "Severe Internal Error.\n";
+ ::exit(-1);
+ }
+}
+
+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, int size1, const T* p2, int 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==false )
+ {
+ 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;
+ }
+
+#ifndef NDEBUG
+ // Verify difflist
+ {
+ int l1=0;
+ int 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 )
+ if( l1!=size1 || l2!=size2 )
+ assert( false );
+ }
+#endif
+}
+
+void fineDiff(
+ Diff3LineList& diff3LineList,
+ int selector,
+ const LineData* v1,
+ const LineData* v2,
+ bool& bTextsTotalEqual
+ )
+{
+ // Finetuning: Diff each line with deltas
+ ProgressProxy pp;
+ int maxSearchLength=500;
+ Diff3LineList::iterator i;
+ int k1=0;
+ int k2=0;
+ bTextsTotalEqual = true;
+ int listSize = diff3LineList.size();
+ int listIdx = 0;
+ for( i= diff3LineList.begin(); i!= diff3LineList.end(); ++i)
+ {
+ 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; }
+ else assert(false);
+ 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;
+ }
+ }
+
+ 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; }
+ else assert(false);
+ }
+
+ if ( (v1[k1].bContainsPureComment || v1[k1].whiteLine()) && (v2[k2].bContainsPureComment || v2[k2].whiteLine()))
+ {
+ if (selector==1){ i->bAEqB = true; }
+ else if (selector==2){ i->bBEqC = true; }
+ else if (selector==3){ i->bAEqC = true; }
+ else assert(false);
+ }
+ }
+ ++listIdx;
+ pp.setCurrent(double(listIdx)/listSize);
+ }
+}
+
+
+// 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);
+ }
+ assert( j==(int)d3lv.size() );
+}
+
+
diff --git a/src/diff.h b/src/diff.h
new file mode 100644
index 0000000..1101752
--- /dev/null
+++ b/src/diff.h
@@ -0,0 +1,462 @@
+/***************************************************************************
+ 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 <qwidget.h>
+#include <qpixmap.h>
+#include <qtimer.h>
+#include <qframe.h>
+#include <qtextstream.h>
+#include <qpainter.h>
+#include <list>
+#include <vector>
+#include <assert.h>
+#include "common.h"
+#include "fileaccess.h"
+
+class OptionDialog;
+
+// 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
+{
+ int nofEquals;
+
+ int diff1;
+ int diff2;
+
+ Diff(int eq, int d1, int d2){nofEquals=eq; diff1=d1; diff2=d2; }
+};
+
+typedef std::list<Diff> DiffList;
+
+struct LineData
+{
+ const QChar* pLine;
+ const QChar* pFirstNonWhiteChar;
+ int size;
+
+ LineData(){ pLine=0; pFirstNonWhiteChar=0; size=0; /*occurances=0;*/ bContainsPureComment=false; }
+ int width(int tabSize) const; // Calcs width considering tabs.
+ //int occurances;
+ 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;
+ int m_sizeA;
+ int m_sizeB;
+ int m_sizeC;
+ const Diff3LineList* m_pDiff3LineList;
+ const Diff3LineVector* m_pDiff3LineVector;
+ void init( Diff3LineList* d3ll, const Diff3LineVector* d3lv,
+ const LineData* pldA, int sizeA, const LineData* pldB, int sizeB, const LineData* pldC, int sizeC );
+};
+
+struct Diff3Line
+{
+ int lineA;
+ int lineB;
+ int 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=0; pFineBC=0; pFineCA=0;
+ linesNeededForDisplay=1;
+ sumLinesNeededForDisplay=0;
+ bWhiteLineA=false; bWhiteLineB=false; bWhiteLineC=false;
+ m_pDiffBufferInfo=0;
+ }
+
+ ~Diff3Line()
+ {
+ if (pFineAB!=0) delete pFineAB;
+ if (pFineBC!=0) delete pFineBC;
+ if (pFineCA!=0) delete pFineCA;
+ pFineAB=0; pFineBC=0; pFineCA=0;
+ }
+
+ bool operator==( const Diff3Line& d3l )
+ {
+ 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
+ {
+ assert( m_pDiffBufferInfo!=0 );
+ 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 0;
+ }
+ QString getString( int src ) const
+ {
+ const LineData* pld = getLineData(src);
+ if ( pld )
+ return QString( pld->pLine, pld->size);
+ else
+ return QString();
+ }
+ int getLineInFile( int src ) const
+ {
+ if ( src == 1 ) return lineA;
+ if ( src == 2 ) return lineB;
+ if ( src == 3 ) return lineC;
+ return -1;
+ }
+};
+
+
+class Diff3LineList : public std::list<Diff3Line>
+{
+};
+class Diff3LineVector : public std::vector<Diff3Line*>
+{
+};
+
+class Diff3WrapLine
+{
+public:
+ Diff3Line* pD3L;
+ int diff3LineIndex;
+ int wrapLineOffset;
+ int wrapLineLength;
+};
+
+typedef std::vector<Diff3WrapLine> Diff3WrapLineVector;
+
+
+class TotalDiffStatus
+{
+public:
+ TotalDiffStatus(){ reset(); }
+ void reset() {bBinaryAEqC=false; bBinaryBEqC=false; bBinaryAEqB=false;
+ bTextAEqC=false; bTextBEqC=false; bTextAEqB=false;
+ nofUnsolvedConflicts=0; nofSolvedConflicts=0;
+ nofWhitespaceConflicts=0;
+ }
+ bool bBinaryAEqC;
+ bool bBinaryBEqC;
+ bool bBinaryAEqB;
+
+ bool bTextAEqC;
+ bool bTextBEqC;
+ bool bTextAEqB;
+
+ 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; }
+ int lineA1;
+ int lineA2;
+ int lineB1;
+ int lineB2;
+ int lineC1;
+ int lineC2;
+ int& firstLine( int winIdx )
+ {
+ return winIdx==1 ? lineA1 : (winIdx==2 ? lineB1 : lineC1 );
+ }
+ int& lastLine( int winIdx )
+ {
+ return winIdx==1 ? lineA2 : (winIdx==2 ? lineB2 : lineC2 );
+ }
+ bool isLineInRange( int 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* pDiffListBC,
+ Diff3LineList& d3ll
+ );
+
+void calcDiff3LineListUsingBC(
+ const DiffList* pDiffListBC,
+ Diff3LineList& d3ll
+ );
+
+void correctManualDiffAlignment( Diff3LineList& d3ll, ManualDiffHelpList* pManualDiffHelpList );
+
+class SourceData
+{
+public:
+ SourceData();
+ ~SourceData();
+
+ void setOptionDialog( OptionDialog* pOptionDialog );
+
+ int getSizeLines() const;
+ int getSizeBytes() const;
+ const char* getBuf() const;
+ const LineData* getLineDataForDisplay() const;
+ const LineData* getLineDataForDiff() const;
+
+ void setFilename(const QString& filename);
+ void setFileAccess( const FileAccess& fa );
+ //FileAccess& getFileAccess();
+ QString getFilename();
+ void setAliasName(const QString& a);
+ QString getAliasName();
+ bool isEmpty(); // File was set
+ bool hasData(); // Data was readable
+ bool isText(); // is it pure text (vs. binary data)
+ bool isFromBuffer(); // was it set via setData() (vs. setFileAccess() or setFilename())
+ void setData( const QString& data );
+ bool isValid(); // Either no file is specified or reading was successful
+
+ void readAndPreprocess(QTextCodec* pEncoding, bool bAutoDetectUnicode );
+ bool saveNormalDataAs( const QString& fileName );
+
+ bool isBinaryEqualWith( const SourceData& other ) const;
+
+ void reset();
+
+ QTextCodec* getEncoding() const { return m_pEncoding; }
+
+private:
+ QTextCodec* detectEncoding( const QString& fileName, QTextCodec* pFallbackCodec );
+ QString m_aliasName;
+ FileAccess m_fileAccess;
+ OptionDialog* m_pOptionDialog;
+ QString m_tempInputFileName;
+
+ struct FileData
+ {
+ FileData(){ m_pBuf=0; m_size=0; m_vSize=0; m_bIsText=false; }
+ ~FileData(){ reset(); }
+ const char* m_pBuf;
+ int m_size;
+ int m_vSize; // Nr of lines in m_pBuf1 and size of m_v1, m_dv12 and m_dv13
+ QString m_unicodeBuf;
+ std::vector<LineData> m_v;
+ bool m_bIsText;
+ bool readFile( const QString& filename );
+ bool writeFile( const QString& filename );
+ void preprocess(bool bPreserveCR, QTextCodec* pEncoding );
+ void reset();
+ void removeComments();
+ void copyBufFrom( const FileData& src );
+ };
+ 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 );
+
+void debugLineCheck( Diff3LineList& d3ll, int size, int idx );
+
+class QStatusBar;
+
+
+class Selection
+{
+public:
+ Selection(){ reset(); oldLastLine=-1; lastLine=-1; oldFirstLine=-1; }
+ int firstLine;
+ int firstPos;
+ int lastLine;
+ int lastPos;
+ int oldLastLine;
+ int oldFirstLine;
+ bool bSelectionContainsData;
+ bool isEmpty() { return firstLine==-1 || (firstLine==lastLine && firstPos==lastPos) || bSelectionContainsData==false;}
+ void reset(){
+ oldFirstLine=firstLine;
+ oldLastLine =lastLine;
+ firstLine=-1;
+ lastLine=-1;
+ bSelectionContainsData = false;
+ }
+ void start( int l, int p ) { firstLine = l; firstPos = p; }
+ void end( int l, int p ) {
+ if ( oldLastLine == -1 )
+ oldLastLine = lastLine;
+ lastLine = l;
+ lastPos = p;
+ }
+ bool within( int l, int p );
+
+ bool lineWithin( int l );
+ int firstPosInLine(int l);
+ int lastPosInLine(int l);
+ int beginLine(){
+ if (firstLine<0 && lastLine<0) return -1;
+ return max2(0,min2(firstLine,lastLine));
+ }
+ int endLine(){
+ if (firstLine<0 && lastLine<0) return -1;
+ return max2(firstLine,lastLine);
+ }
+ int beginPos() { return firstLine==lastLine ? min2(firstPos,lastPos) :
+ firstLine<lastLine ? (firstLine<0?0:firstPos) : (lastLine<0?0:lastPos); }
+ int endPos() { return firstLine==lastLine ? max2(firstPos,lastPos) :
+ firstLine<lastLine ? lastPos : firstPos; }
+};
+
+class OptionDialog;
+
+QCString encodeString( const QString& s );
+
+
+// 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(const 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 )
+ {
+ TextDirection td = (m_factor==1 || bAdapt == false) ? LTR : RTL;
+ QPainter::drawText( m_xOffset-m_fontWidth*s.length() + m_factor*x, y, s, -1, td );
+ }
+
+ void drawLine( int x1, int y1, int x2, int y2 )
+ {
+ QPainter::drawLine( m_xOffset + m_factor*x1, y1, m_xOffset + m_factor*x2, y2 );
+ }
+};
+
+void fineDiff(
+ Diff3LineList& diff3LineList,
+ int selector,
+ const LineData* v1,
+ const LineData* v2,
+ bool& bTextsTotalEqual
+ );
+
+
+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
index 0000000..79d55ed
--- /dev/null
+++ b/src/difftextwindow.cpp
@@ -0,0 +1,1751 @@
+/***************************************************************************
+ difftextwindow.cpp - description
+ -------------------
+ begin : Mon Apr 8 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 "difftextwindow.h"
+#include "merger.h"
+#include <qpainter.h>
+#include <assert.h>
+#include <qpixmap.h>
+#include <qstatusbar.h>
+#include <qapplication.h>
+#include <qtooltip.h>
+#include <qfont.h>
+#include <qstringlist.h>
+#include <qlineedit.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qtextcodec.h>
+#include <optiondialog.h>
+#include <math.h>
+#include <cstdlib>
+#include <qdragobject.h>
+#include <klocale.h>
+#include <kfiledialog.h>
+
+class DiffTextWindowData
+{
+public:
+ DiffTextWindowData( DiffTextWindow* p )
+ {
+ m_pDiffTextWindow = p;
+ m_bPaintingAllowed = false;
+ m_pLineData = 0;
+ m_size = 0;
+ m_bWordWrap = false;
+ m_delayedDrawTimer = 0;
+ m_pDiff3LineVector = 0;
+ m_pManualDiffHelpList = 0;
+ m_pOptionDialog = 0;
+ m_fastSelectorLine1 = 0;
+ m_fastSelectorNofLines = 0;
+ m_bTriple = 0;
+ m_winIdx = 0;
+ m_firstLine = 0;
+ m_oldFirstLine = 0;
+ m_oldFirstColumn = 0;
+ m_firstColumn = 0;
+ m_lineNumberWidth = 0;
+ m_pStatusBar = 0;
+ m_scrollDeltaX = 0;
+ m_scrollDeltaY = 0;
+ m_bMyUpdate = false;
+ m_bSelectionInProgress = false;
+ }
+ DiffTextWindow* m_pDiffTextWindow;
+ DiffTextWindowFrame* m_pDiffTextWindowFrame;
+
+ 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;
+
+ OptionDialog* m_pOptionDialog;
+ 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_oldFirstColumn;
+ int m_firstColumn;
+ int m_lineNumberWidth;
+
+ 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;
+};
+
+DiffTextWindow::DiffTextWindow(
+ DiffTextWindowFrame* pParent,
+ QStatusBar* pStatusBar,
+ OptionDialog* pOptionDialog,
+ int winIdx
+ )
+ : QWidget(pParent, 0, Qt::WResizeNoErase | Qt::WRepaintNoErase)
+{
+ d = new DiffTextWindowData(this);
+ d->m_pDiffTextWindowFrame = pParent;
+ setFocusPolicy( ClickFocus );
+ setAcceptDrops( true );
+
+ d->m_pOptionDialog = pOptionDialog;
+ init( 0, 0, 0, 0, 0, false );
+
+ setMinimumSize(QSize(20,20));
+
+ d->m_pStatusBar = pStatusBar;
+ d->m_bPaintingAllowed = true;
+ d->m_bWordWrap = false;
+ d->m_winIdx = winIdx;
+
+ setFont(d->m_pOptionDialog->m_font);
+}
+
+DiffTextWindow::~DiffTextWindow()
+{
+ delete d;
+}
+
+void DiffTextWindow::init(
+ const QString& filename,
+ 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_firstColumn = 0;
+ d->m_oldFirstColumn = -1;
+ 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_selection.reset();
+ d->m_selection.oldFirstLine = -1; // reset is not enough here.
+ d->m_selection.oldLastLine = -1;
+ d->m_selection.lastLine = -1;
+
+ update();
+ d->m_pDiffTextWindowFrame->init();
+}
+
+void DiffTextWindow::reset()
+{
+ d->m_pLineData=0;
+ d->m_size=0;
+ d->m_pDiff3LineVector=0;
+ 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->accept( QUriDrag::canDecode(e) || QTextDrag::canDecode(e) );
+ // Note that the corresponding drop is handled in KDiff3App::eventFilter().
+}
+
+
+void DiffTextWindow::setFirstLine(int firstLine)
+{
+ int fontHeight = fontMetrics().height();
+
+ int newFirstLine = max2(0,firstLine);
+
+ int deltaY = fontHeight * ( d->m_firstLine - newFirstLine );
+
+ d->m_firstLine = newFirstLine;
+
+ if ( d->m_bSelectionInProgress && d->m_selection.firstLine != -1 )
+ {
+ int line, pos;
+ convertToLinePos( d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos );
+ d->m_selection.end( line, pos );
+ update();
+ }
+ else
+ {
+ QWidget::scroll( 0, deltaY );
+ }
+ d->m_pDiffTextWindowFrame->setFirstLine( d->m_firstLine );
+}
+
+int DiffTextWindow::getFirstLine()
+{
+ return d->m_firstLine;
+}
+
+void DiffTextWindow::setFirstColumn(int firstCol)
+{
+ int fontWidth = fontMetrics().width('W');
+ int xOffset = d->leftInfoWidth() * fontWidth;
+
+ int newFirstColumn = max2(0,firstCol);
+
+ int deltaX = fontWidth * ( d->m_firstColumn - newFirstColumn );
+
+ d->m_firstColumn = newFirstColumn;
+
+ QRect r( xOffset, 0, width()-xOffset, height() );
+
+ if ( d->m_pOptionDialog->m_bRightToLeftLanguage )
+ {
+ deltaX = -deltaX;
+ r = QRect( width()-1-xOffset, 0, -(width()-xOffset), height() ).normalize();
+ }
+
+ if ( d->m_bSelectionInProgress && d->m_selection.firstLine != -1 )
+ {
+ int line, pos;
+ convertToLinePos( d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos );
+ d->m_selection.end( line, pos );
+ update();
+ }
+ else
+ {
+ QWidget::scroll( deltaX, 0, r );
+ }
+}
+
+int DiffTextWindow::getNofColumns()
+{
+ if (d->m_bWordWrap)
+ {
+ return getNofVisibleColumns();
+ }
+ else
+ {
+ int nofColumns = 0;
+ for( int i = 0; i< d->m_size; ++i )
+ {
+ if ( d->m_pLineData[i].width( d->m_pOptionDialog->m_tabSize ) > nofColumns )
+ nofColumns = d->m_pLineData[i].width( d->m_pOptionDialog->m_tabSize );
+ }
+ return nofColumns;
+ }
+}
+
+int DiffTextWindow::getNofLines()
+{
+ return d->m_bWordWrap ? d->m_diff3WrapLineVector.size() :
+ d->m_pDiff3LineVector->size();
+}
+
+
+int DiffTextWindow::convertLineToDiff3LineIdx( int line )
+{
+ if ( d->m_bWordWrap && d->m_diff3WrapLineVector.size()>0 )
+ return d->m_diff3WrapLineVector[ min2( line, (int)d->m_diff3WrapLineVector.size()-1 ) ].diff3LineIndex;
+ else
+ return line;
+}
+
+int DiffTextWindow::convertDiff3LineIdxToLine( int d3lIdx )
+{
+ if ( d->m_bWordWrap && d->m_pDiff3LineVector!=0 && d->m_pDiff3LineVector->size()>0 )
+ return (*d->m_pDiff3LineVector)[ min2( 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 )
+ {
+ scroll( 0, newFirstLine - d->m_firstLine );
+ }
+
+ update();
+ }
+}
+
+
+void DiffTextWindow::showStatusLine(int line )
+{
+ int d3lIdx = convertLineToDiff3LineIdx( line );
+ if(d3lIdx >= 0 && d3lIdx<(int)d->m_pDiff3LineVector->size() )
+ {
+ const Diff3Line* pD3l = (*d->m_pDiff3LineVector)[d3lIdx];
+ if ( pD3l != 0 )
+ {
+ int l = pD3l->getLineInFile( d->m_winIdx );
+
+ QString s;
+ if ( l!=-1 )
+ s.sprintf("File %s: Line %d", d->m_filename.ascii(), l+1 );
+ else
+ s.sprintf("File %s: Line not available", d->m_filename.ascii() );
+ if (d->m_pStatusBar!=0) d->m_pStatusBar->message(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 );
+ if ( pos < d->m_firstColumn )
+ {
+ emit setFastSelectorLine( convertLineToDiff3LineIdx(line) );
+ d->m_selection.firstLine = -1; // 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, max2( 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_pOptionDialog->m_tabSize );
+
+ resetSelection();
+ d->m_selection.start( line, convertToPosOnScreen( s, pos1, d->m_pOptionDialog->m_tabSize ) );
+ d->m_selection.end( line, convertToPosOnScreen( s, pos2, d->m_pOptionDialog->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 )
+ {
+ killTimer(d->m_delayedDrawTimer);
+ d->m_delayedDrawTimer = 0;
+ if (d->m_selection.firstLine != -1 )
+ {
+ 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.firstLine != -1 )
+ {
+ d->m_selection.end( line, pos );
+
+ showStatusLine( line );
+
+ // Scroll because mouse moved out of the window
+ const QFontMetrics& fm = fontMetrics();
+ int fontWidth = fm.width('W');
+ int deltaX=0;
+ int deltaY=0;
+ if ( ! d->m_pOptionDialog->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.height());
+ if ( e->y() > height() ) deltaY = +1 + sqr( e->y() - height() ) / sqr(fm.height());
+ if ( deltaX != 0 && d->m_scrollDeltaX!=deltaX || deltaY!= 0 && d->m_scrollDeltaY!=deltaY )
+ {
+ d->m_scrollDeltaX = deltaX;
+ d->m_scrollDeltaY = deltaY;
+ emit scroll( deltaX, deltaY );
+ 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)
+{
+ 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().height();
+
+ if ( d->m_selection.oldLastLine != -1 )
+ {
+ int lastLine;
+ int firstLine;
+ if ( d->m_selection.oldFirstLine != -1 )
+ {
+ firstLine = min3( d->m_selection.oldFirstLine, d->m_selection.lastLine, d->m_selection.oldLastLine );
+ lastLine = max3( d->m_selection.oldFirstLine, d->m_selection.lastLine, d->m_selection.oldLastLine );
+ }
+ else
+ {
+ firstLine = min2( d->m_selection.lastLine, d->m_selection.oldLastLine );
+ lastLine = max2( d->m_selection.lastLine, d->m_selection.oldLastLine );
+ }
+ int y1 = ( firstLine - d->m_firstLine ) * fontHeight;
+ int y2 = min2( height(), ( lastLine - d->m_firstLine + 1 ) * fontHeight );
+
+ if ( y1<height() && y2>0 )
+ {
+ QRect invalidRect = QRect( 0, y1, width(), y2-y1 );
+ update( invalidRect );
+ }
+ }
+
+ d->m_bMyUpdate = false;
+ }
+
+ if ( d->m_scrollDeltaX != 0 || d->m_scrollDeltaY != 0 )
+ {
+ d->m_selection.end( d->m_selection.lastLine + d->m_scrollDeltaY, d->m_selection.lastPos + d->m_scrollDeltaX );
+ emit scroll( 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.height();
+ int fontWidth = fm.width('W');
+ int xOffset = ( d->leftInfoWidth() - d->m_firstColumn ) * fontWidth;
+
+ int yOffset = - d->m_firstLine * fontHeight;
+
+ line = ( y - yOffset ) / fontHeight;
+ if ( ! d->m_pOptionDialog->m_bRightToLeftLanguage )
+ pos = ( x - xOffset ) / fontWidth;
+ else
+ pos = ( (width() - 1 - x) - xOffset ) / fontWidth;
+}
+
+int Selection::firstPosInLine(int l)
+{
+ assert( firstLine != -1 );
+
+ int l1 = firstLine;
+ int 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(int l)
+{
+ assert( firstLine != -1 );
+
+ int l1 = firstLine;
+ int 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( int l, int p )
+{
+ if ( firstLine == -1 ) return false;
+ int l1 = firstLine;
+ int 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( int l )
+{
+ if ( firstLine == -1 ) return false;
+ int l1 = firstLine;
+ int l2 = lastLine;
+
+ if ( l1>l2 ){ std::swap(l1,l2); }
+
+ return ( l1 <= l && l <= l2 );
+}
+
+
+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();
+ QFont diffFont = normalFont;
+ diffFont.setItalic( m_pOptionDialog->m_bItalicForDeltas );
+ const QFontMetrics& fm = p.fontMetrics();
+ int fontHeight = fm.height();
+ int fontAscent = fm.ascent();
+ int fontDescent = fm.descent();
+ int fontWidth = fm.width('W');
+
+ int xOffset = (leftInfoWidth() - m_firstColumn)*fontWidth;
+ int yOffset = (line-m_firstLine) * fontHeight;
+
+ QRect lineRect( 0, 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_pOptionDialog->m_bgColor;
+ QColor diffBgColor = m_pOptionDialog->m_diffBgColor;
+
+ if ( bFastSelectionRange )
+ {
+ bgColor = m_pOptionDialog->m_currentRangeBgColor;
+ diffBgColor = m_pOptionDialog->m_currentRangeDiffBgColor;
+ }
+
+ if ( yOffset+fontHeight<invalidRect.top() || invalidRect.bottom() < yOffset-fontHeight )
+ return;
+
+ int changed = whatChanged;
+ if ( pLineDiff1 != 0 ) changed |= 1;
+ if ( pLineDiff2 != 0 ) changed |= 2;
+
+ QColor c = m_pOptionDialog->m_fgColor;
+ if ( changed == 2 ) {
+ c = m_cDiff2;
+ } else if ( changed == 1 ) {
+ c = m_cDiff1;
+ } else if ( changed == 3 ) {
+ c = m_cDiffBoth;
+ }
+
+ p.fillRect( leftInfoWidth()*fontWidth, yOffset, deviceWidth, fontHeight, bgColor );
+
+ if (pld!=0)
+ {
+ // First calculate the "changed" information for each character.
+ int i=0;
+ std::vector<UINT8> charChanged( pld->size );
+ if ( pLineDiff1!=0 || pLineDiff2 != 0 )
+ {
+ Merger merger( pLineDiff1, pLineDiff2 );
+ while( ! merger.isEndReached() && i<pld->size )
+ {
+ if ( i < pld->size )
+ {
+ charChanged[i] = merger.whatChanged();
+ ++i;
+ }
+ merger.next();
+ }
+ }
+
+ QString s=" ";
+ // Convert tabs
+ int outPos = 0;
+
+ QString lineString( pld->pLine, pld->size );
+ int lineLength = m_bWordWrap ? wrapLineOffset+wrapLineLength : lineString.length();
+
+ for( i=wrapLineOffset; i<lineLength; ++i )
+ {
+ int spaces = 1;
+
+ if ( lineString[i]=='\t' )
+ {
+ spaces = tabber( outPos, m_pOptionDialog->m_tabSize );
+ s[0] = ' ';
+ }
+ else
+ {
+ s[0] = lineString[i];
+ }
+
+ QColor c = m_pOptionDialog->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_pOptionDialog->m_fgColor && whatChanged2==0 && !m_pOptionDialog->m_bShowWhiteSpace )
+ {
+ // The user doesn't want to see highlighted white space.
+ c = m_pOptionDialog->m_fgColor;
+ }
+
+ QRect outRect( xOffset + fontWidth*outPos, yOffset, fontWidth*spaces, fontHeight );
+ if ( m_pOptionDialog->m_bRightToLeftLanguage )
+ outRect = QRect( deviceWidth-1-(xOffset + fontWidth*outPos), yOffset, -fontWidth*spaces, fontHeight ).normalize();
+ if ( invalidRect.intersects( outRect ) )
+ {
+ if( !m_selection.within( line, outPos ) )
+ {
+
+ if( c!=m_pOptionDialog->m_fgColor )
+ {
+ QColor lightc = diffBgColor;
+ p.fillRect( xOffset + fontWidth*outPos, yOffset,
+ fontWidth*spaces, fontHeight, lightc );
+ p.setFont(diffFont);
+ }
+
+ p.setPen( c );
+ if ( s[0]==' ' && c!=m_pOptionDialog->m_fgColor && charChanged[i]!=0 )
+ {
+ if ( m_pOptionDialog->m_bShowWhiteSpaceCharacters && m_pOptionDialog->m_bShowWhiteSpace)
+ {
+ p.fillRect( xOffset + fontWidth*outPos, yOffset+fontAscent,
+ fontWidth*spaces-1, fontDescent, c ); // QT3
+ //fontWidth*spaces-1, fontDescent, c ); // QT4
+ }
+ }
+ else
+ {
+ p.drawText( xOffset + fontWidth*outPos, yOffset + fontAscent, s );
+ }
+ p.setFont(normalFont);
+ }
+ else
+ {
+ p.fillRect( xOffset + fontWidth*outPos, yOffset,
+ fontWidth*(spaces), fontHeight, m_pDiffTextWindow->colorGroup().highlight() );
+
+ p.setPen( m_pDiffTextWindow->colorGroup().highlightedText() );
+ p.drawText( xOffset + fontWidth*outPos, yOffset + fontAscent, s );
+
+ m_selection.bSelectionContainsData = true;
+ }
+ }
+
+ outPos += spaces;
+ }
+
+ if( m_selection.lineWithin( line ) && m_selection.lineWithin( line+1 ) )
+ {
+ p.fillRect( xOffset + fontWidth*outPos, yOffset,
+ deviceWidth, fontHeight, m_pDiffTextWindow->colorGroup().highlight() );
+ }
+ }
+
+ p.fillRect( 0, yOffset, leftInfoWidth()*fontWidth, fontHeight, m_pOptionDialog->m_bgColor );
+
+ xOffset = (m_lineNumberWidth+2)*fontWidth;
+ int xLeft = m_lineNumberWidth*fontWidth;
+ p.setPen( m_pOptionDialog->m_fgColor );
+ if ( pld!=0 )
+ {
+ if ( m_pOptionDialog->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 )
+ {
+ p.setPen( QPen( m_pOptionDialog->m_fgColor, 0, bWrapLine ? Qt::DotLine : Qt::SolidLine) );
+ p.drawLine( xOffset +1, yOffset, xOffset +1, yOffset+fontHeight-1 );
+ p.setPen( QPen( m_pOptionDialog->m_fgColor, 0, Qt::SolidLine) );
+ }
+ }
+ if ( c!=m_pOptionDialog->m_fgColor && whatChanged2==0 )//&& whatChanged==0 )
+ {
+ if ( m_pOptionDialog->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_pOptionDialog->m_fgColor ? bgColor : c );
+ }
+
+ if ( bFastSelectionRange )
+ {
+ p.fillRect( xOffset + fontWidth-1, yOffset, 3, fontHeight, m_pOptionDialog->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_pOptionDialog->m_manualHelpRangeColor );
+ break;
+ }
+ }
+}
+
+void DiffTextWindow::paintEvent( QPaintEvent* e )
+{
+ if ( d->m_pDiff3LineVector==0 || ! d->m_bPaintingAllowed ||
+ ( d->m_diff3WrapLineVector.empty() && d->m_bWordWrap ) )
+ return;
+
+ QRect invalidRect = e->rect();
+ if ( invalidRect.isEmpty() )
+ return;
+
+ bool bOldSelectionContainsData = d->m_selection.bSelectionContainsData;
+ d->m_selection.bSelectionContainsData = false;
+
+ int endLine = min2( d->m_firstLine + getNofVisibleLines()+2, getNofLines() );
+
+ //if ( invalidRect.size()==size() )
+ { // double buffering, obsolete with Qt4
+ QPainter painter(this); // Remove for Qt4
+ QPixmap pixmap( invalidRect.size() );// Remove for Qt4
+
+ MyPainter p( &pixmap, d->m_pOptionDialog->m_bRightToLeftLanguage, width(), fontMetrics().width('W') ); // For Qt4 change pixmap to this
+
+ p.translate( -invalidRect.x(), -invalidRect.y() );// Remove for Qt4
+
+ p.setFont( font() );
+ p.QPainter::fillRect( invalidRect, d->m_pOptionDialog->m_bgColor );
+
+ d->draw( p, invalidRect, width(), d->m_firstLine, endLine );
+ // p.drawLine( m_invalidRect.x(), m_invalidRect.y(), m_invalidRect.right(), m_invalidRect.bottom() ); // For test only
+ p.end();
+
+ painter.drawPixmap( invalidRect.x(), invalidRect.y(), pixmap );// Remove for Qt4
+ }
+// else
+// { // no double buffering
+// MyPainter p( this, d->m_pOptionDialog->m_bRightToLeftLanguage, width(), fontMetrics().width('W') );
+// p.setFont( font() );
+// p.QPainter::fillRect( invalidRect, d->m_pOptionDialog->m_bgColor );
+// d->draw( p, invalidRect, width(), d->m_firstLine, endLine );
+// }
+
+
+ d->m_oldFirstLine = d->m_firstLine;
+ d->m_oldFirstColumn = d->m_firstColumn;
+ d->m_selection.oldLastLine = -1;
+ if ( d->m_selection.oldFirstLine !=-1 )
+ d->m_selection.oldFirstLine = -1;
+
+ if( !bOldSelectionContainsData && d->m_selection.bSelectionContainsData )
+ emit newSelection();
+}
+
+void DiffTextWindow::print( MyPainter& p, const QRect&, int firstLine, int nofLinesPerPage )
+{
+ if ( d->m_pDiff3LineVector==0 || ! d->m_bPaintingAllowed ||
+ ( d->m_diff3WrapLineVector.empty() && d->m_bWordWrap ) )
+ return;
+ resetSelection();
+// MyPainter p( this, d->m_pOptionDialog->m_bRightToLeftLanguage, width(), fontMetrics().width('W') );
+ int oldFirstLine = d->m_firstLine;
+ d->m_firstLine = firstLine;
+ QRect invalidRect = QRect(0,0,QCOORD_MAX,QCOORD_MAX);
+ QColor bgColor = d->m_pOptionDialog->m_bgColor;
+ d->m_pOptionDialog->m_bgColor = Qt::white;
+ d->draw( p, invalidRect, p.window().width(), firstLine, min2(firstLine+nofLinesPerPage,getNofLines()) );
+ d->m_pOptionDialog->m_bgColor = bgColor;
+ d->m_firstLine = oldFirstLine;
+}
+
+void DiffTextWindowData::draw( MyPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine )
+{
+ m_lineNumberWidth = m_pOptionDialog->m_bShowLineNumbers ? (int)log10((double)m_size)+1 : 0;
+
+ if ( m_winIdx==1 )
+ {
+ m_cThis = m_pOptionDialog->m_colorA;
+ m_cDiff1 = m_pOptionDialog->m_colorB;
+ m_cDiff2 = m_pOptionDialog->m_colorC;
+ }
+ if ( m_winIdx==2 )
+ {
+ m_cThis = m_pOptionDialog->m_colorB;
+ m_cDiff1 = m_pOptionDialog->m_colorC;
+ m_cDiff2 = m_pOptionDialog->m_colorA;
+ }
+ if ( m_winIdx==3 )
+ {
+ m_cThis = m_pOptionDialog->m_colorC;
+ m_cDiff1 = m_pOptionDialog->m_colorA;
+ m_cDiff2 = m_pOptionDialog->m_colorB;
+ }
+ m_cDiffBoth = m_pOptionDialog->m_colorForConflict; // Conflict color
+
+ p.setPen( m_cThis );
+
+ for ( int line = beginLine; line<endLine; ++line )
+ {
+ int wrapLineOffset=0;
+ int wrapLineLength=0;
+ const Diff3Line* d3l =0;
+ 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 ? 0 : &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;
+ 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 )
+ {
+ int d3LIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(line);
+ return getString( d3LIdx ).mid( m_diff3WrapLineVector[line].wrapLineOffset, m_diff3WrapLineVector[line].wrapLineLength );
+ }
+ 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 );
+ 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);
+ }
+ else assert(false);
+}
+
+
+
+void DiffTextWindow::resizeEvent( QResizeEvent* e )
+{
+ QSize s = e->size();
+ QFontMetrics fm = fontMetrics();
+ int visibleLines = s.height()/fm.height()-2;
+ int visibleColumns = s.width()/fm.width('W') - d->leftInfoWidth();
+ emit resizeSignal( visibleColumns, visibleLines );
+ QWidget::resizeEvent(e);
+}
+
+int DiffTextWindow::getNofVisibleLines()
+{
+ QFontMetrics fm = fontMetrics();
+ int fmh = fm.height();
+ int h = height();
+ return h/fmh -1;//height()/fm.height()-2;
+}
+
+int DiffTextWindow::getNofVisibleColumns()
+{
+ QFontMetrics fm = fontMetrics();
+ return width()/fm.width('W') - d->leftInfoWidth();
+}
+
+QString DiffTextWindow::getSelection()
+{
+ 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];
+ 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; }
+ else assert(false);
+
+ 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 );
+ }
+
+ // Consider tabs
+ int outPos = 0;
+ for( int i=0; i<size; ++i )
+ {
+ int spaces = 1;
+ if ( lineString[i]=='\t' )
+ {
+ spaces = tabber( outPos, d->m_pOptionDialog->m_tabSize );
+ }
+
+ if( d->m_selection.within( line, outPos ) )
+ {
+ selectionString += lineString[i];
+ }
+
+ outPos += spaces;
+ }
+
+ if( d->m_selection.within( line, outPos ) &&
+ !( d->m_bWordWrap && it+1<vectorSize && d3l == d->m_diff3WrapLineVector[it+1].pD3L )
+ )
+ {
+ #ifdef _WIN32
+ 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.find( s, startPos, bCaseSensitive );
+ 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_pOptionDialog->m_tabSize);
+ }
+
+ if ( d->m_bWordWrap && d->m_pDiff3LineVector!=0 )
+ {
+ 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_pOptionDialog->m_tabSize ) );
+ d->m_selection.end( lastWrapLine, convertToPosOnScreen( s2, wrapEndPos, d->m_pOptionDialog->m_tabSize ) );
+ l=firstWrapLine;
+ p=wrapStartPos;
+ }
+ else
+ {
+ d->m_selection.start( firstLine, convertToPosOnScreen( d->getString(firstLine), startPos, d->m_pOptionDialog->m_tabSize ) );
+ d->m_selection.end( lastLine, convertToPosOnScreen( d->getString(lastLine), endPos, d->m_pOptionDialog->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 );
+}
+
+// Returns the number of wrapped lines
+// if pWrappedLines != 0 then the stringlist will contain the wrapped lines.
+int wordWrap( const QString& origLine, int nofColumns, Diff3WrapLine* pDiff3WrapLine )
+{
+ if (nofColumns<=0)
+ nofColumns = 1;
+
+ int nofNeededLines = 0;
+ int length = origLine.length();
+
+ if (length==0)
+ {
+ nofNeededLines = 1;
+ if( pDiff3WrapLine )
+ {
+ pDiff3WrapLine->wrapLineOffset=0;
+ pDiff3WrapLine->wrapLineLength=0;
+ }
+ }
+ else
+ {
+ int pos = 0;
+
+ while ( pos < length )
+ {
+ int wrapPos = pos + nofColumns;
+
+ if ( length-pos <= nofColumns )
+ {
+ wrapPos = length;
+ }
+ else
+ {
+ int wsPos = max2( origLine.findRev( ' ', wrapPos ), origLine.findRev( '\t', wrapPos ) );
+
+ if ( wsPos > pos )
+ {
+ // Wrap line at wsPos
+ wrapPos = wsPos;
+ }
+ }
+
+ if ( pDiff3WrapLine )
+ {
+ pDiff3WrapLine->wrapLineOffset = pos;
+ pDiff3WrapLine->wrapLineLength = wrapPos-pos;
+ ++pDiff3WrapLine;
+ }
+
+ pos = wrapPos;
+
+ ++nofNeededLines;
+ }
+ }
+ return nofNeededLines;
+}
+
+void DiffTextWindow::convertSelectionToD3LCoords()
+{
+ if ( d->m_pDiff3LineVector==0 || ! 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_pOptionDialog->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_pOptionDialog->m_tabSize );
+ convertLineCoordsToD3LCoords( d->m_selection.endLine(), lastPosInText, lastD3LIdx, lastD3LPos );
+
+ //d->m_selection.reset();
+ d->m_selection.start( firstD3LIdx, firstD3LPos );
+ d->m_selection.end( lastD3LIdx, lastD3LPos );
+}
+
+void DiffTextWindow::recalcWordWrap( bool bWordWrap, int wrapLineVectorSize, int nofVisibleColumns )
+{
+ if ( d->m_pDiff3LineVector==0 || ! d->m_bPaintingAllowed || !isVisible() )
+ {
+ d->m_bWordWrap = bWordWrap;
+ if (!bWordWrap) d->m_diff3WrapLineVector.resize( 0 );
+ return;
+ }
+
+ d->m_bWordWrap = bWordWrap;
+
+ if ( bWordWrap )
+ {
+ d->m_diff3WrapLineVector.resize( wrapLineVectorSize );
+
+ if (nofVisibleColumns<0)
+ nofVisibleColumns = getNofVisibleColumns();
+ else
+ nofVisibleColumns-= d->leftInfoWidth();
+ int i;
+ int wrapLineIdx = 0;
+ int size = d->m_pDiff3LineVector->size();
+ for( i=0; i<size; ++i )
+ {
+ QString s = d->getString( i );
+ int linesNeeded = wordWrap( s, nofVisibleColumns, wrapLineVectorSize==0 ? 0 : &d->m_diff3WrapLineVector[wrapLineIdx] );
+ Diff3Line& d3l = *(*d->m_pDiff3LineVector)[i];
+ if ( d3l.linesNeededForDisplay<linesNeeded )
+ {
+ 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 = min2( d->m_firstLine, wrapLineVectorSize-1 );
+ d->m_firstColumn = 0;
+ d->m_pDiffTextWindowFrame->setFirstLine( d->m_firstLine );
+ }
+ }
+ else
+ {
+ d->m_diff3WrapLineVector.resize( 0 );
+ }
+
+ 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.reset();
+ d->m_selection.start( firstLine, convertToPosOnScreen( d->getLineString( firstLine ), firstPos, d->m_pOptionDialog->m_tabSize ) );
+ d->m_selection.end( lastLine, convertToPosOnScreen( d->getLineString( lastLine ),lastPos, d->m_pOptionDialog->m_tabSize ) );
+ }
+}
+
+
+class DiffTextWindowFrameData
+{
+public:
+ DiffTextWindow* m_pDiffTextWindow;
+ QLineEdit* m_pFileSelection;
+ QPushButton* m_pBrowseButton;
+ OptionDialog* m_pOptionDialog;
+ QLabel* m_pLabel;
+ QLabel* m_pTopLine;
+ QWidget* m_pTopLineWidget;
+};
+
+DiffTextWindowFrame::DiffTextWindowFrame( QWidget* pParent, QStatusBar* pStatusBar, OptionDialog* pOptionDialog, int winIdx )
+ : QWidget( pParent )
+{
+ d = new DiffTextWindowFrameData;
+ d->m_pOptionDialog = pOptionDialog;
+ 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,SIGNAL(clicked()), this, SLOT(slotBrowseButtonClicked()));
+ connect(d->m_pFileSelection,SIGNAL(returnPressed()), this, SLOT(slotReturnPressed()));
+
+ d->m_pLabel = new QLabel("A:",d->m_pTopLineWidget);
+ d->m_pTopLine = new QLabel(d->m_pTopLineWidget);
+ d->m_pDiffTextWindow = 0;
+ d->m_pDiffTextWindow = new DiffTextWindow( this, pStatusBar, pOptionDialog, winIdx );
+ QHBoxLayout* pHL = new QHBoxLayout(d->m_pTopLineWidget);
+ pHL->setMargin(2);
+ 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 );
+
+ QVBoxLayout* pVL = new QVBoxLayout( this, 0, 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 = pDTW->d->m_filename ;
+ d->m_pFileSelection->setText( QDir::convertSeparators(s) );
+ QString winId = pDTW->d->m_winIdx==1 ?
+ ( pDTW->d->m_bTriple?"A (Base)":"A") :
+ ( pDTW->d->m_winIdx==2 ? "B" : "C" );
+ d->m_pLabel->setText( winId + ":" );
+ }
+}
+
+// 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)pDTW->d->m_size)+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_pOptionDialog->m_bgColor;
+ QColor c2 = pDTW->d->m_cThis;
+ QPalette p = d->m_pTopLineWidget->palette();
+ if ( e->type()==QEvent::FocusOut )
+ std::swap(c1,c2);
+
+ p.setColor(QColorGroup::Background, c2);
+ d->m_pTopLineWidget->setPalette( p );
+ d->m_pBrowseButton->setPalette( p );
+ d->m_pFileSelection->setPalette( p );
+
+ p.setColor(QColorGroup::Foreground, c1);
+ d->m_pLabel->setPalette( p );
+ d->m_pTopLine->setPalette( p );
+ }
+ if (o == d->m_pFileSelection && e->type()==QEvent::Drop)
+ {
+ QDropEvent* d = static_cast<QDropEvent*>(e);
+
+ if ( QUriDrag::canDecode( d ) )
+ {
+ QStringList lst;
+ QUriDrag::decodeLocalFiles( d, lst );
+
+ if ( lst.count() > 0 )
+ {
+ static_cast<QLineEdit*>(o)->setText( lst[0] );
+ static_cast<QLineEdit*>(o)->setFocus();
+ emit fileNameChanged( lst[0], pDTW->d->m_winIdx );
+ return true;
+ }
+ }
+ /* The following lines work for Qt>4.1 but not for 4.0.x*/
+ /*if ( d->mimeData()->hasUrls() )
+ {
+ QList<QUrl> lst = d->mimeData()->urls();
+ if ( !lst.empty() )
+ {
+ static_cast<QLineEdit*>(o)->setText( lst[0].toLocalFile() );
+ static_cast<QLineEdit*>(o)->setFocus();
+ emit fileNameChanged( lst[0], 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();
+
+ KURL newURL = KFileDialog::getOpenURL( current, 0, this);
+ if ( !newURL.isEmpty() )
+ {
+ DiffTextWindow* pDTW = d->m_pDiffTextWindow;
+ emit fileNameChanged( newURL.url(), pDTW->d->m_winIdx );
+ }
+}
+
+QCString encodeString( const QString& s )
+{
+ QTextCodec* c = QTextCodec::codecForLocale();
+ if (c!=0)
+ return c->fromUnicode( s );
+ else
+ return QCString( s.latin1() );
+}
+
+#include "difftextwindow.moc"
diff --git a/src/difftextwindow.h b/src/difftextwindow.h
new file mode 100644
index 0000000..8620b28
--- /dev/null
+++ b/src/difftextwindow.h
@@ -0,0 +1,135 @@
+/***************************************************************************
+ difftextwindow.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 DIFFTEXTWINDOW_H
+#define DIFFTEXTWINDOW_H
+
+#include "diff.h"
+
+#include <qwidget.h>
+
+class QStatusBar;
+class OptionDialog;
+class DiffTextWindowData;
+class DiffTextWindowFrame;
+
+class DiffTextWindow : public QWidget
+{
+ Q_OBJECT
+public:
+ DiffTextWindow(
+ DiffTextWindowFrame* pParent,
+ QStatusBar* pStatusBar,
+ OptionDialog* pOptionDialog,
+ int winIdx
+ );
+ ~DiffTextWindow();
+ void init(
+ const QString& fileName,
+ 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 getNofColumns();
+ int getNofLines();
+ int getNofVisibleLines();
+ int getNofVisibleColumns();
+
+ 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 nofVisibleColumns );
+ void print( MyPainter& painter, const QRect& r, int firstLine, int nofLinesPerPage );
+signals:
+ void resizeSignal( int nofVisibleColumns, int nofVisibleLines );
+ void scroll( int deltaX, int deltaY );
+ void newSelection();
+ void selectionEnd();
+ void setFastSelectorLine( int line );
+ void gotFocus();
+ void lineClicked( int winIdx, int line );
+
+public slots:
+ void setFirstLine( int line );
+ void setFirstColumn( int col );
+ void resetSelection();
+ void setFastSelectorRange( int line1, int nofLines );
+
+protected:
+ virtual void mousePressEvent ( QMouseEvent * );
+ virtual void mouseReleaseEvent ( QMouseEvent * );
+ virtual void mouseMoveEvent ( QMouseEvent * );
+ virtual void mouseDoubleClickEvent ( QMouseEvent * e );
+
+ virtual void paintEvent( QPaintEvent* );
+ virtual void dragEnterEvent( QDragEnterEvent* e );
+ virtual void focusInEvent( QFocusEvent* e );
+
+ virtual void resizeEvent( QResizeEvent* );
+ virtual void timerEvent(QTimerEvent*);
+
+private:
+ DiffTextWindowData* d;
+ void showStatusLine( int line );
+ friend class DiffTextWindowFrame;
+};
+
+
+class DiffTextWindowFrameData;
+
+class DiffTextWindowFrame : public QWidget
+{
+ Q_OBJECT
+public:
+ DiffTextWindowFrame( QWidget* pParent, QStatusBar* pStatusBar, OptionDialog* pOptionDialog, int winIdx );
+ ~DiffTextWindowFrame();
+ DiffTextWindow* getDiffTextWindow();
+ void init();
+ void setFirstLine(int firstLine);
+signals:
+ void fileNameChanged(const QString&, int);
+protected:
+ bool eventFilter( QObject*, QEvent* );
+private slots:
+ void slotReturnPressed();
+ void slotBrowseButtonClicked();
+private:
+ DiffTextWindowFrameData* d;
+};
+
+
+#endif
+
diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp
new file mode 100644
index 0000000..48d7904
--- /dev/null
+++ b/src/directorymergewindow.cpp
@@ -0,0 +1,3048 @@
+/***************************************************************************
+ directorymergewindow.cpp
+ -----------------
+ begin : Sat Oct 19 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 "directorymergewindow.h"
+#include "optiondialog.h"
+#include <vector>
+#include <map>
+
+#include <qdir.h>
+#include <qapplication.h>
+#include <qpixmap.h>
+#include <qimage.h>
+#include <kpopupmenu.h>
+#include <kaction.h>
+#include <qregexp.h>
+#include <qmessagebox.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qtable.h>
+#include <qsplitter.h>
+#include <qtextedit.h>
+#include <qprogressdialog.h>
+#include <kmessagebox.h>
+#include <kfiledialog.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <iostream>
+#include <assert.h>
+//#include <konq_popupmenu.h>
+
+static bool conflictingFileTypes(MergeFileInfos& mfi);
+/*
+class StatusInfo : public QListView
+{
+public:
+ StatusInfo(QWidget* pParent) : QListView( pParent, "StatusInfo", Qt::WShowModal )
+ {
+ addColumn("");
+ setSorting(-1); //disable sorting
+ }
+
+ QListViewItem* m_pLast;
+ QListViewItem* last()
+ {
+ if (firstChild()==0) return 0;
+ else return m_pLast;
+ }
+
+ void addText(const QString& s )
+ {
+ if (firstChild()==0) m_pLast = new QListViewItem( this, s );
+ else m_pLast = new QListViewItem( this, last(), s );
+ }
+};
+*/
+class StatusInfo : public QTextEdit
+{
+public:
+ StatusInfo(QWidget* pParent) : QTextEdit( pParent, "StatusInfo" )
+ {
+ setWFlags(Qt::WShowModal);
+ setWordWrap(QTextEdit::NoWrap);
+ setReadOnly(true);
+ //showMaximized();
+ }
+
+ bool isEmpty(){ return text().isEmpty(); }
+
+ void addText(const QString& s )
+ {
+ append(s);
+ }
+
+ void show()
+ {
+ scrollToBottom();
+ QTextEdit::show();
+ }
+};
+
+
+class TempRemover
+{
+public:
+ TempRemover( const QString& origName, FileAccess& fa );
+ ~TempRemover();
+ QString name() { return m_name; }
+ bool success() { return m_bSuccess; }
+private:
+ QString m_name;
+ bool m_bTemp;
+ bool m_bSuccess;
+};
+TempRemover::TempRemover(const QString& origName, FileAccess& fa)
+{
+ if ( fa.isLocal() )
+ {
+ m_name = origName;
+ m_bTemp = false;
+ m_bSuccess = true;
+ }
+ else
+ {
+ m_name = FileAccess::tempFileName();
+ m_bSuccess = fa.copyFile( m_name );
+ m_bTemp = m_bSuccess;
+ }
+}
+TempRemover::~TempRemover()
+{
+ if ( m_bTemp && ! m_name.isEmpty() )
+ FileAccess::removeTempFile(m_name);
+}
+
+void DirectoryMergeWindow::fastFileComparison(
+ FileAccess& fi1, FileAccess& fi2,
+ bool& bEqual, bool& bError, QString& status )
+{
+ ProgressProxy pp;
+ status = "";
+ bEqual = false;
+ bError = true;
+
+ if ( !m_bFollowFileLinks )
+ {
+ if ( fi1.isSymLink() != fi2.isSymLink() )
+ {
+ status = i18n("Mix of links and normal files.");
+ return;
+ }
+ else if ( fi1.isSymLink() && fi2.isSymLink() )
+ {
+ bError = false;
+ bEqual = fi1.readLink() == fi2.readLink();
+ status = i18n("Link: ");
+ return;
+ }
+ }
+
+ if ( fi1.size()!=fi2.size() )
+ {
+ bEqual = false;
+ status = i18n("Size. ");
+ return;
+ }
+ else if ( m_pOptions->m_bDmTrustSize )
+ {
+ bEqual = true;
+ return;
+ }
+
+ if ( m_pOptions->m_bDmTrustDate )
+ {
+ bEqual = ( fi1.lastModified() == fi2.lastModified() && fi1.size()==fi2.size() );
+ bError = false;
+ status = i18n("Date & Size: ");
+ return;
+ }
+
+ if ( m_pOptions->m_bDmTrustDateFallbackToBinary )
+ {
+ bEqual = ( fi1.lastModified() == fi2.lastModified() && fi1.size()==fi2.size() );
+ if ( bEqual )
+ {
+ bError = false;
+ status = i18n("Date & Size: ");
+ return;
+ }
+ }
+
+ QString fileName1 = fi1.absFilePath();
+ QString fileName2 = fi2.absFilePath();
+ TempRemover tr1( fileName1, fi1 );
+ if ( !tr1.success() )
+ {
+ status = i18n("Creating temp copy of %1 failed.").arg(fileName1);
+ return;
+ }
+ TempRemover tr2( fileName2, fi2 );
+ if ( !tr2.success() )
+ {
+ status = i18n("Creating temp copy of %1 failed.").arg(fileName2);
+ return;
+ }
+
+ std::vector<char> buf1(100000);
+ std::vector<char> buf2(buf1.size());
+
+ QFile file1( tr1.name() );
+
+ if ( ! file1.open(IO_ReadOnly) )
+ {
+ status = i18n("Opening %1 failed.").arg(fileName1);
+ return;
+ }
+
+ QFile file2( tr2.name() );
+
+ if ( ! file2.open(IO_ReadOnly) )
+ {
+ status = i18n("Opening %1 failed.").arg(fileName2);
+ return;
+ }
+
+ pp.setInformation( i18n("Comparing file..."), 0, false );
+ typedef QFile::Offset t_FileSize;
+ t_FileSize fullSize = file1.size();
+ t_FileSize sizeLeft = fullSize;
+
+ while( sizeLeft>0 && ! pp.wasCancelled() )
+ {
+ int len = min2( sizeLeft, (t_FileSize)buf1.size() );
+ if( len != file1.readBlock( &buf1[0], len ) )
+ {
+ status = i18n("Error reading from %1").arg(fileName1);
+ return;
+ }
+
+ if( len != file2.readBlock( &buf2[0], len ) )
+ {
+ status = i18n("Error reading from %1").arg(fileName2);
+ return;
+ }
+
+ if ( memcmp( &buf1[0], &buf2[0], len ) != 0 )
+ {
+ bError = false;
+ return;
+ }
+ sizeLeft-=len;
+ pp.setCurrent(double(fullSize-sizeLeft)/fullSize, false );
+ }
+
+ // If the program really arrives here, then the files are really equal.
+ bError = false;
+ bEqual = true;
+}
+
+
+
+
+
+static int s_nameCol = 0;
+static int s_ACol = 1;
+static int s_BCol = 2;
+static int s_CCol = 3;
+static int s_OpCol = 4;
+static int s_OpStatusCol = 5;
+static int s_UnsolvedCol = 6; // Nr of unsolved conflicts (for 3 input files)
+static int s_SolvedCol = 7; // Nr of auto-solvable conflicts (for 3 input files)
+static int s_NonWhiteCol = 8; // Nr of nonwhite deltas (for 2 input files)
+static int s_WhiteCol = 9; // Nr of white deltas (for 2 input files)
+DirectoryMergeWindow::DirectoryMergeWindow( QWidget* pParent, OptionDialog* pOptions, KIconLoader* pIconLoader )
+ : QListView( pParent )
+{
+ connect( this, SIGNAL(doubleClicked(QListViewItem*)), this, SLOT(onDoubleClick(QListViewItem*)));
+ connect( this, SIGNAL(returnPressed(QListViewItem*)), this, SLOT(onDoubleClick(QListViewItem*)));
+ connect( this, SIGNAL( mouseButtonPressed(int,QListViewItem*,const QPoint&, int)),
+ this, SLOT( onClick(int,QListViewItem*,const QPoint&, int)) );
+ connect( this, SIGNAL(contextMenuRequested(QListViewItem*,const QPoint &,int)),
+ this, SLOT( slotShowContextMenu(QListViewItem*,const QPoint &,int)));
+ connect( this, SIGNAL(selectionChanged(QListViewItem*)), this, SLOT(onSelectionChanged(QListViewItem*)));
+ m_pOptions = pOptions;
+ m_pIconLoader = pIconLoader;
+ m_pDirectoryMergeInfo = 0;
+ m_bAllowResizeEvents = true;
+ m_bSimulatedMergeStarted=false;
+ m_bRealMergeStarted=false;
+ m_bError = false;
+ m_bSyncMode = false;
+ m_pStatusInfo = new StatusInfo(0);
+ m_pStatusInfo->hide();
+ m_bScanning = false;
+ m_pSelection1Item = 0;
+ m_pSelection2Item = 0;
+ m_pSelection3Item = 0;
+ m_bCaseSensitive = true;
+
+ addColumn(i18n("Name"));
+ addColumn("A");
+ addColumn("B");
+ addColumn("C");
+ addColumn(i18n("Operation"));
+ addColumn(i18n("Status"));
+ addColumn(i18n("Unsolved"));
+ addColumn(i18n("Solved"));
+ addColumn(i18n("Nonwhite"));
+ addColumn(i18n("White"));
+
+ setColumnAlignment( s_UnsolvedCol, Qt::AlignRight );
+ setColumnAlignment( s_SolvedCol, Qt::AlignRight );
+ setColumnAlignment( s_NonWhiteCol, Qt::AlignRight );
+ setColumnAlignment( s_WhiteCol, Qt::AlignRight );
+}
+
+DirectoryMergeWindow::~DirectoryMergeWindow()
+{
+}
+
+
+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"), i18n("Rescan"), i18n("Continue Merging") );
+ if ( result!=KMessageBox::Yes )
+ return;
+ }
+
+ init( m_dirA, m_dirB, m_dirC, m_dirDest, m_bDirectoryMerge, true );
+}
+
+// Copy pm2 onto pm1, but preserve the alpha value from pm1 where pm2 is transparent.
+static QPixmap pixCombiner( const QPixmap* pm1, const QPixmap* pm2 )
+{
+ QImage img1 = pm1->convertToImage().convertDepth(32);
+ QImage img2 = pm2->convertToImage().convertDepth(32);
+
+ for (int y = 0; y < img1.height(); y++)
+ {
+ Q_UINT32 *line1 = reinterpret_cast<Q_UINT32 *>(img1.scanLine(y));
+ Q_UINT32 *line2 = reinterpret_cast<Q_UINT32 *>(img2.scanLine(y));
+ for (int x = 0; x < img1.width(); x++)
+ {
+ if ( qAlpha( line2[x] ) >0 )
+ line1[x] = (line2[x] | 0xff000000);
+ }
+ }
+ QPixmap pix;
+ pix.convertFromImage(img1);
+ return pix;
+}
+
+// like pixCombiner but let the pm1 color shine through
+static QPixmap pixCombiner2( const QPixmap* pm1, const QPixmap* pm2 )
+{
+ QImage img1 = pm1->convertToImage().convertDepth(32);
+ QImage img2 = pm2->convertToImage().convertDepth(32);
+
+ for (int y = 0; y < img1.height(); y++)
+ {
+ Q_UINT32 *line1 = reinterpret_cast<Q_UINT32 *>(img1.scanLine(y));
+ Q_UINT32 *line2 = reinterpret_cast<Q_UINT32 *>(img2.scanLine(y));
+ for (int x = 0; x < img1.width(); x++)
+ {
+ if ( qAlpha( line2[x] ) >0 )
+ {
+ int r = ( qRed( line1[x] ) + qRed( line2[x] ))/2;
+ int g = ( qGreen( line1[x] ) + qGreen( line2[x] ))/2;
+ int b = ( qBlue( line1[x] ) + qBlue( line2[x] ))/2;
+ line1[x] = qRgba( r,g,b, 0xff );
+ }
+ }
+ }
+ QPixmap pix;
+ pix.convertFromImage(img1);
+ return pix;
+}
+
+static void calcDirStatus( bool bThreeDirs, DirMergeItem* i, int& nofFiles,
+ int& nofDirs, int& nofEqualFiles, int& nofManualMerges )
+{
+ if ( i->m_pMFI->m_bDirA || i->m_pMFI->m_bDirB || i->m_pMFI->m_bDirC )
+ {
+ ++nofDirs;
+ }
+ else
+ {
+ ++nofFiles;
+ if ( i->m_pMFI->m_bEqualAB && (!bThreeDirs || i->m_pMFI->m_bEqualAC ))
+ {
+ ++nofEqualFiles;
+ }
+ else
+ {
+ if ( i->m_pMFI->m_eMergeOperation==eMergeABCToDest || i->m_pMFI->m_eMergeOperation==eMergeABToDest )
+ ++nofManualMerges;
+ }
+ }
+ for( QListViewItem* p = i->firstChild(); p!=0; p = p->nextSibling() )
+ calcDirStatus( bThreeDirs, static_cast<DirMergeItem*>(p), nofFiles, nofDirs, nofEqualFiles, nofManualMerges );
+}
+
+static QString sortString(const QString& s, bool bCaseSensitive)
+{
+ if (bCaseSensitive)
+ return s;
+ else
+ return s.upper();
+}
+
+struct t_ItemInfo
+{
+ bool bExpanded;
+ bool bOperationComplete;
+ QString status;
+ e_MergeOperation eMergeOperation;
+};
+
+bool DirectoryMergeWindow::init
+ (
+ FileAccess& dirA,
+ FileAccess& dirB,
+ FileAccess& dirC,
+ FileAccess& dirDest,
+ bool bDirectoryMerge,
+ bool bReload
+ )
+{
+ if ( m_pOptions->m_bDmFullAnalysis )
+ {
+ // A full analysis uses the same ressources that a normal text-diff/merge uses.
+ // So make sure that the user saves his data first.
+ bool bCanContinue=false;
+ checkIfCanContinue( &bCanContinue );
+ if ( !bCanContinue )
+ return false;
+ startDiffMerge("","","","","","","",0); // hide main window
+ }
+
+ show();
+
+ std::map<QString,t_ItemInfo> expandedDirsMap;
+
+ if ( bReload )
+ {
+ // Remember expandes items
+ QListViewItemIterator it( this );
+ while ( it.current() )
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>( it.current() );
+ t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->m_subPath ];
+ ii.bExpanded = pDMI->isOpen();
+ 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_pSelection1Item = 0;
+ m_pSelection2Item = 0;
+ m_pSelection3Item = 0;
+ m_bCaseSensitive = m_pOptions->m_bDmCaseSensitiveFilenameComparison;
+
+ clear();
+
+ m_mergeItemList.clear();
+ m_currentItemForOperation = m_mergeItemList.end();
+
+ m_dirA = dirA;
+ m_dirB = dirB;
+ m_dirC = dirC;
+ m_dirDest = dirDest;
+
+ if ( !bReload )
+ {
+ m_pDirShowIdenticalFiles->setChecked(true);
+ m_pDirShowDifferentFiles->setChecked(true);
+ m_pDirShowFilesOnlyInA->setChecked(true);
+ m_pDirShowFilesOnlyInB->setChecked(true);
+ m_pDirShowFilesOnlyInC->setChecked(true);
+ }
+
+ // 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 ( !m_dirA.isDir() || !m_dirB.isDir() ||
+ (m_dirC.isValid() && !m_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").arg(m_dirA.prettyAbsPath()); }
+
+ if ( !dirB.isDir() )
+ { text += i18n("Dir B \"%1\" does not exist or is not a directory.\n").arg(m_dirB.prettyAbsPath()); }
+
+ if ( m_dirC.isValid() && !m_dirC.isDir() )
+ { text += i18n("Dir C \"%1\" does not exist or is not a directory.\n").arg(m_dirC.prettyAbsPath()); }
+
+ KMessageBox::sorry( this, text, i18n("Directory Open Error") );
+ return false;
+ }
+
+ if ( m_dirC.isValid() &&
+ (m_dirDest.prettyAbsPath() == m_dirA.prettyAbsPath() || m_dirDest.prettyAbsPath()==m_dirB.prettyAbsPath() ) )
+ {
+ KMessageBox::error(this,
+ 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;
+ statusBarMessage(i18n("Scanning directories..."));
+
+ m_bSyncMode = m_pOptions->m_bDmSyncMode && !m_dirC.isValid() && !m_dirDest.isValid();
+
+ if ( m_dirDest.isValid() )
+ m_dirDestInternal = m_dirDest;
+ else
+ m_dirDestInternal = m_dirC.isValid() ? m_dirC : m_dirB;
+
+ QString origCurrentDirectory = QDir::currentDirPath();
+
+ m_fileMergeMap.clear();
+ t_DirectoryList::iterator i;
+
+ // calc how many directories will be read:
+ double nofScans = ( m_dirA.isValid() ? 1 : 0 )+( m_dirB.isValid() ? 1 : 0 )+( m_dirC.isValid() ? 1 : 0 );
+ int currentScan = 0;
+
+ setColumnWidthMode(s_UnsolvedCol, QListView::Manual);
+ setColumnWidthMode(s_SolvedCol, QListView::Manual);
+ setColumnWidthMode(s_WhiteCol, QListView::Manual);
+ setColumnWidthMode(s_NonWhiteCol, QListView::Manual);
+ if ( !m_pOptions->m_bDmFullAnalysis )
+ {
+ setColumnWidth( s_WhiteCol, 0 );
+ setColumnWidth( s_NonWhiteCol, 0 );
+ setColumnWidth( s_UnsolvedCol, 0 );
+ setColumnWidth( s_SolvedCol, 0 );
+ }
+ else if ( m_dirC.isValid() )
+ {
+ setColumnWidth(s_WhiteCol, 50 );
+ setColumnWidth(s_NonWhiteCol, 50 );
+ setColumnWidth(s_UnsolvedCol, 50 );
+ setColumnWidth(s_SolvedCol, 50 );
+ }
+ else
+ {
+ setColumnWidth(s_WhiteCol, 50 );
+ setColumnWidth(s_NonWhiteCol, 50 );
+ setColumnWidth(s_UnsolvedCol, 50 );
+ setColumnWidth(s_SolvedCol, 0 );
+ }
+
+ bool bListDirSuccessA = true;
+ bool bListDirSuccessB = true;
+ bool bListDirSuccessC = true;
+ if ( m_dirA.isValid() )
+ {
+ pp.setInformation(i18n("Reading Directory A"));
+ pp.setSubRangeTransformation(currentScan/nofScans, (currentScan+1)/nofScans);
+ ++currentScan;
+
+ t_DirectoryList dirListA;
+ bListDirSuccessA = m_dirA.listDir( &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);
+
+ for (i=dirListA.begin(); i!=dirListA.end();++i )
+ {
+ MergeFileInfos& mfi = m_fileMergeMap[sortString(i->filePath(), m_bCaseSensitive)];
+ //std::cout <<i->filePath()<<std::endl;
+ mfi.m_bExistsInA = true;
+ mfi.m_fileInfoA = *i;
+ }
+ }
+
+ if ( m_dirB.isValid() )
+ {
+ pp.setInformation(i18n("Reading Directory B"));
+ pp.setSubRangeTransformation(currentScan/nofScans, (currentScan+1)/nofScans);
+ ++currentScan;
+
+ t_DirectoryList dirListB;
+ bListDirSuccessB = m_dirB.listDir( &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);
+
+ for (i=dirListB.begin(); i!=dirListB.end();++i )
+ {
+ MergeFileInfos& mfi = m_fileMergeMap[sortString(i->filePath(), m_bCaseSensitive)];
+ mfi.m_bExistsInB = true;
+ mfi.m_fileInfoB = *i;
+ }
+ }
+
+ e_MergeOperation eDefaultMergeOp;
+ if ( m_dirC.isValid() )
+ {
+ pp.setInformation(i18n("Reading Directory C"));
+ pp.setSubRangeTransformation(currentScan/nofScans, (currentScan+1)/nofScans);
+ ++currentScan;
+
+ t_DirectoryList dirListC;
+ bListDirSuccessC = m_dirC.listDir( &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);
+
+ for (i=dirListC.begin(); i!=dirListC.end();++i )
+ {
+ MergeFileInfos& mfi = m_fileMergeMap[sortString(i->filePath(),m_bCaseSensitive)];
+ mfi.m_bExistsInC = true;
+ mfi.m_fileInfoC = *i;
+ }
+
+ eDefaultMergeOp = eMergeABCToDest;
+ }
+ else
+ eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest;
+
+ bool bContinue = true;
+ if ( !bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC )
+ {
+ QString s = i18n("Some subdirectories were not readable in");
+ if ( !bListDirSuccessA ) s += "\nA: " + m_dirA.prettyAbsPath();
+ if ( !bListDirSuccessB ) s += "\nB: " + m_dirB.prettyAbsPath();
+ if ( !bListDirSuccessC ) s += "\nC: " + m_dirC.prettyAbsPath();
+ s+="\n";
+ s+= i18n("Check the permissions of the subdirectories.");
+ bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel( this, s );
+ }
+
+ if ( bContinue )
+ {
+ prepareListView(pp);
+
+ for( QListViewItem* p = firstChild(); p!=0; p = p->nextSibling() )
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>( p );
+ calcSuggestedOperation( *pDMI->m_pMFI, eDefaultMergeOp );
+ }
+ }
+ else
+ {
+ setSelected( 0, true );
+ }
+
+ QDir::setCurrent(origCurrentDirectory);
+
+ // Try to improve the view a little bit.
+ QWidget* pParent = parentWidget();
+ QSplitter* pSplitter = static_cast<QSplitter*>(pParent);
+ if (pSplitter!=0)
+ {
+ QValueList<int> sizes = pSplitter->sizes();
+ int total = sizes[0] + sizes[1];
+ sizes[0]=total*6/10;
+ sizes[1]=total - sizes[0];
+ pSplitter->setSizes( sizes );
+ }
+
+ m_bScanning = false;
+ statusBarMessage(i18n("Ready."));
+
+ if ( bContinue )
+ {
+ // Generate a status report
+ int nofFiles=0;
+ int nofDirs=0;
+ int nofEqualFiles=0;
+ int nofManualMerges=0;
+ for( QListViewItem* p = firstChild(); p!=0; p = p->nextSibling() )
+ calcDirStatus( m_dirC.isValid(), static_cast<DirMergeItem*>(p),
+ nofFiles, nofDirs, nofEqualFiles, nofManualMerges );
+
+ QString s;
+ s = i18n("Directory Comparison Status") + "\n\n" +
+ i18n("Number of subdirectories:") +" "+ QString::number(nofDirs) + "\n"+
+ i18n("Number of equal files:") +" "+ QString::number(nofEqualFiles) + "\n"+
+ i18n("Number of different files:") +" "+ QString::number(nofFiles-nofEqualFiles);
+
+ if ( m_dirC.isValid() )
+ s += "\n" + i18n("Number of manual merges:") +" "+ QString::number(nofManualMerges);
+ KMessageBox::information( this, s );
+ setSelected( firstChild(), true );
+ }
+
+ updateFileVisibilities();
+ if ( bReload )
+ {
+ // Remember expandes items
+ QListViewItemIterator it( this );
+ while ( it.current() )
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>( it.current() );
+ std::map<QString,t_ItemInfo>::iterator i = expandedDirsMap.find( pDMI->m_pMFI->m_subPath );
+ if ( i!=expandedDirsMap.end() )
+ {
+ t_ItemInfo& ii = i->second;
+ pDMI->setOpen( ii.bExpanded );
+ pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false );
+ pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete;
+ pDMI->setText( s_OpStatusCol, ii.status );
+ }
+ ++it;
+ }
+ }
+ return true;
+}
+
+
+
+void DirectoryMergeWindow::slotChooseAEverywhere(){ setAllMergeOperations( eCopyAToDest ); }
+
+void DirectoryMergeWindow::slotChooseBEverywhere(){ setAllMergeOperations( eCopyBToDest ); }
+
+void DirectoryMergeWindow::slotChooseCEverywhere(){ setAllMergeOperations( eCopyCToDest ); }
+
+void DirectoryMergeWindow::slotAutoChooseEverywhere()
+{
+ e_MergeOperation eDefaultMergeOp = m_dirC.isValid() ? eMergeABCToDest :
+ m_bSyncMode ? eMergeToAB : eMergeABToDest;
+ setAllMergeOperations(eDefaultMergeOp );
+}
+
+void DirectoryMergeWindow::slotNoOpEverywhere(){ setAllMergeOperations(eNoOperation); }
+
+static void setListViewItemOpen( QListViewItem* p, bool bOpen )
+{
+ for( QListViewItem* pChild = p->firstChild(); pChild!=0; pChild = pChild->nextSibling() )
+ setListViewItemOpen( pChild, bOpen );
+
+ p->setOpen( bOpen );
+}
+
+void DirectoryMergeWindow::slotFoldAllSubdirs()
+{
+ for( QListViewItem* p = firstChild(); p!=0; p = p->nextSibling() )
+ setListViewItemOpen( p, false );
+}
+
+void DirectoryMergeWindow::slotUnfoldAllSubdirs()
+{
+ for( QListViewItem* p = firstChild(); p!=0; p = p->nextSibling() )
+ setListViewItemOpen( p, true );
+}
+
+static void setMergeOperation( QListViewItem* pLVI, e_MergeOperation eMergeOp )
+{
+ if ( pLVI==0 ) return;
+
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(pLVI);
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+
+ mfi.setMergeOperation(eMergeOp );
+}
+
+// Merge current item (merge mode)
+void DirectoryMergeWindow::slotCurrentDoNothing() { setMergeOperation(currentItem(), eNoOperation ); }
+void DirectoryMergeWindow::slotCurrentChooseA() { setMergeOperation(currentItem(), m_bSyncMode ? eCopyAToB : eCopyAToDest ); }
+void DirectoryMergeWindow::slotCurrentChooseB() { setMergeOperation(currentItem(), m_bSyncMode ? eCopyBToA : eCopyBToDest ); }
+void DirectoryMergeWindow::slotCurrentChooseC() { setMergeOperation(currentItem(), eCopyCToDest ); }
+void DirectoryMergeWindow::slotCurrentMerge()
+{
+ bool bThreeDirs = m_dirC.isValid();
+ setMergeOperation(currentItem(), bThreeDirs ? eMergeABCToDest : eMergeABToDest );
+}
+void DirectoryMergeWindow::slotCurrentDelete() { setMergeOperation(currentItem(), eDeleteFromDest ); }
+// Sync current item
+void DirectoryMergeWindow::slotCurrentCopyAToB() { setMergeOperation(currentItem(), eCopyAToB ); }
+void DirectoryMergeWindow::slotCurrentCopyBToA() { setMergeOperation(currentItem(), eCopyBToA ); }
+void DirectoryMergeWindow::slotCurrentDeleteA() { setMergeOperation(currentItem(), eDeleteA ); }
+void DirectoryMergeWindow::slotCurrentDeleteB() { setMergeOperation(currentItem(), eDeleteB ); }
+void DirectoryMergeWindow::slotCurrentDeleteAAndB() { setMergeOperation(currentItem(), eDeleteAB ); }
+void DirectoryMergeWindow::slotCurrentMergeToA() { setMergeOperation(currentItem(), eMergeToA ); }
+void DirectoryMergeWindow::slotCurrentMergeToB() { setMergeOperation(currentItem(), eMergeToB ); }
+void DirectoryMergeWindow::slotCurrentMergeToAAndB() { setMergeOperation(currentItem(), eMergeToAB ); }
+
+
+void DirectoryMergeWindow::keyPressEvent( QKeyEvent* e )
+{
+ if ( (e->state() & Qt::ControlButton)!=0 )
+ {
+ bool bThreeDirs = m_dirC.isValid();
+
+ QListViewItem* lvi = currentItem();
+ DirMergeItem* pDMI = lvi==0 ? 0 : static_cast<DirMergeItem*>(lvi);
+ MergeFileInfos* pMFI = pDMI==0 ? 0 : pDMI->m_pMFI;
+
+ if ( pMFI==0 ) return;
+ bool bMergeMode = bThreeDirs || !m_bSyncMode;
+ bool bFTConflict = pMFI==0 ? false : conflictingFileTypes(*pMFI);
+
+ if ( bMergeMode )
+ {
+ switch(e->key())
+ {
+ case Key_1: if(pMFI->m_bExistsInA){ slotCurrentChooseA(); } return;
+ case Key_2: if(pMFI->m_bExistsInB){ slotCurrentChooseB(); } return;
+ case Key_3: if(pMFI->m_bExistsInC){ slotCurrentChooseC(); } return;
+ case Key_Space: slotCurrentDoNothing(); return;
+ case Key_4: if ( !bFTConflict ) { slotCurrentMerge(); } return;
+ case Key_Delete: slotCurrentDelete(); return;
+ default: break;
+ }
+ }
+ else
+ {
+ switch(e->key())
+ {
+ case Key_1: if(pMFI->m_bExistsInA){ slotCurrentCopyAToB(); } return;
+ case Key_2: if(pMFI->m_bExistsInB){ slotCurrentCopyBToA(); } return;
+ case Key_Space: slotCurrentDoNothing(); return;
+ case Key_4: if ( !bFTConflict ) { slotCurrentMergeToAAndB(); } return;
+ case Key_Delete: if( pMFI->m_bExistsInA && pMFI->m_bExistsInB ) slotCurrentDeleteAAndB();
+ else if( pMFI->m_bExistsInA ) slotCurrentDeleteA();
+ else if( pMFI->m_bExistsInB ) slotCurrentDeleteB();
+ return;
+ default: break;
+ }
+ }
+ }
+
+ QListView::keyPressEvent(e);
+}
+
+void DirectoryMergeWindow::focusInEvent(QFocusEvent*)
+{
+ updateAvailabilities();
+}
+void DirectoryMergeWindow::focusOutEvent(QFocusEvent*)
+{
+ updateAvailabilities();
+}
+
+void DirectoryMergeWindow::setAllMergeOperations( e_MergeOperation eDefaultOperation )
+{
+ if ( KMessageBox::Yes == KMessageBox::warningYesNo(this,
+ i18n("This affects all merge operations."),
+ i18n("Changing All Merge Operations"),i18n("C&ontinue"), i18n("&Cancel") ) )
+ {
+ for( QListViewItem* p = firstChild(); p!=0; p = p->nextSibling() )
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>( p );
+ calcSuggestedOperation( *pDMI->m_pMFI, eDefaultOperation );
+ }
+ }
+}
+
+
+void DirectoryMergeWindow::compareFilesAndCalcAges( MergeFileInfos& mfi )
+{
+ std::map<QDateTime,int> dateMap;
+
+ if( mfi.m_bExistsInA )
+ {
+ mfi.m_bLinkA = mfi.m_fileInfoA.isSymLink();
+ mfi.m_bDirA = mfi.m_fileInfoA.isDir();
+ dateMap[ mfi.m_fileInfoA.lastModified() ] = 0;
+ }
+ if( mfi.m_bExistsInB )
+ {
+ mfi.m_bLinkB = mfi.m_fileInfoB.isSymLink();
+ mfi.m_bDirB = mfi.m_fileInfoB.isDir();
+ dateMap[ mfi.m_fileInfoB.lastModified() ] = 1;
+ }
+ if( mfi.m_bExistsInC )
+ {
+ mfi.m_bLinkC = mfi.m_fileInfoC.isSymLink();
+ mfi.m_bDirC = mfi.m_fileInfoC.isDir();
+ dateMap[ mfi.m_fileInfoC.lastModified() ] = 2;
+ }
+
+ if ( m_pOptions->m_bDmFullAnalysis )
+ {
+ if( mfi.m_bExistsInA && mfi.m_bDirA || mfi.m_bExistsInB && mfi.m_bDirB || mfi.m_bExistsInC && mfi.m_bDirC )
+ {
+ // If any input is a directory, don't start any comparison.
+ mfi.m_bEqualAB=mfi.m_bExistsInA && mfi.m_bExistsInB;
+ mfi.m_bEqualAC=mfi.m_bExistsInA && mfi.m_bExistsInC;
+ mfi.m_bEqualBC=mfi.m_bExistsInB && mfi.m_bExistsInC;
+ }
+ else
+ {
+ emit startDiffMerge(
+ mfi.m_bExistsInA ? mfi.m_fileInfoA.absFilePath() : QString(""),
+ mfi.m_bExistsInB ? mfi.m_fileInfoB.absFilePath() : QString(""),
+ mfi.m_bExistsInC ? mfi.m_fileInfoC.absFilePath() : QString(""),
+ "",
+ "","","",&mfi.m_totalDiffStatus
+ );
+ int nofNonwhiteConflicts = mfi.m_totalDiffStatus.nofUnsolvedConflicts +
+ mfi.m_totalDiffStatus.nofSolvedConflicts - mfi.m_totalDiffStatus.nofWhitespaceConflicts;
+
+ if (m_pOptions->m_bDmWhiteSpaceEqual && nofNonwhiteConflicts == 0)
+ {
+ mfi.m_bEqualAB = mfi.m_bExistsInA && mfi.m_bExistsInB;
+ mfi.m_bEqualAC = mfi.m_bExistsInA && mfi.m_bExistsInC;
+ mfi.m_bEqualBC = mfi.m_bExistsInB && mfi.m_bExistsInC;
+ }
+ else
+ {
+ mfi.m_bEqualAB = mfi.m_totalDiffStatus.bBinaryAEqB;
+ mfi.m_bEqualBC = mfi.m_totalDiffStatus.bBinaryBEqC;
+ mfi.m_bEqualAC = mfi.m_totalDiffStatus.bBinaryAEqC;
+ }
+ }
+ }
+ else
+ {
+ bool bError;
+ QString eqStatus;
+ if( mfi.m_bExistsInA && mfi.m_bExistsInB )
+ {
+ if( mfi.m_bDirA ) mfi.m_bEqualAB=true;
+ else fastFileComparison( mfi.m_fileInfoA, mfi.m_fileInfoB, mfi.m_bEqualAB, bError, eqStatus );
+ }
+ if( mfi.m_bExistsInA && mfi.m_bExistsInC )
+ {
+ if( mfi.m_bDirA ) mfi.m_bEqualAC=true;
+ else fastFileComparison( mfi.m_fileInfoA, mfi.m_fileInfoC, mfi.m_bEqualAC, bError, eqStatus );
+ }
+ if( mfi.m_bExistsInB && mfi.m_bExistsInC )
+ {
+ if (mfi.m_bEqualAB && mfi.m_bEqualAC)
+ mfi.m_bEqualBC = true;
+ else
+ {
+ if( mfi.m_bDirB ) mfi.m_bEqualBC=true;
+ else fastFileComparison( mfi.m_fileInfoB, mfi.m_fileInfoC, mfi.m_bEqualBC, bError, eqStatus );
+ }
+ }
+ }
+
+ if (mfi.m_bLinkA!=mfi.m_bLinkB) mfi.m_bEqualAB=false;
+ if (mfi.m_bLinkA!=mfi.m_bLinkC) mfi.m_bEqualAC=false;
+ if (mfi.m_bLinkB!=mfi.m_bLinkC) mfi.m_bEqualBC=false;
+
+ if (mfi.m_bDirA!=mfi.m_bDirB) mfi.m_bEqualAB=false;
+ if (mfi.m_bDirA!=mfi.m_bDirC) mfi.m_bEqualAC=false;
+ if (mfi.m_bDirB!=mfi.m_bDirC) mfi.m_bEqualBC=false;
+
+ 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.m_bExistsInC && mfi.m_ageC==eNotThere )
+ {
+ mfi.m_ageC = (e_Age)age; ++age;
+ mfi.m_bConflictingAges = true;
+ }
+ if ( mfi.m_bExistsInB && mfi.m_ageB==eNotThere )
+ {
+ mfi.m_ageB = (e_Age)age; ++age;
+ mfi.m_bConflictingAges = true;
+ }
+ if ( mfi.m_bExistsInA && 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;
+ }
+}
+
+static QPixmap* s_pm_dir;
+static QPixmap* s_pm_file;
+
+static QPixmap* pmNotThere;
+static QPixmap* pmNew;
+static QPixmap* pmOld;
+static QPixmap* pmMiddle;
+
+static QPixmap* pmLink;
+
+static QPixmap* pmDirLink;
+static QPixmap* pmFileLink;
+
+static QPixmap* pmNewLink;
+static QPixmap* pmOldLink;
+static QPixmap* pmMiddleLink;
+
+static QPixmap* pmNewDir;
+static QPixmap* pmMiddleDir;
+static QPixmap* pmOldDir;
+
+static QPixmap* pmNewDirLink;
+static QPixmap* pmMiddleDirLink;
+static QPixmap* pmOldDirLink;
+
+
+static QPixmap colorToPixmap(QColor c)
+{
+ QPixmap pm(16,16);
+ QPainter p(&pm);
+ p.setPen( Qt::black );
+ p.setBrush( c );
+ p.drawRect(0,0,pm.width(),pm.height());
+ return pm;
+}
+
+static void initPixmaps( QColor newest, QColor oldest, QColor middle, QColor notThere )
+{
+ if (pmNew==0)
+ {
+ pmNotThere = new QPixmap;
+ pmNew = new QPixmap;
+ pmOld = new QPixmap;
+ pmMiddle = new QPixmap;
+
+ #include "xpm/link_arrow.xpm"
+ 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);
+}
+
+
+static void setOnePixmap( QListViewItem* pLVI, int col, e_Age eAge, bool bLink, bool bDir )
+{
+ static QPixmap* ageToPm[]= { pmNew, pmMiddle, pmOld, pmNotThere, s_pm_file };
+ static QPixmap* ageToPmLink[]= { pmNewLink, pmMiddleLink, pmOldLink, pmNotThere, pmFileLink };
+ static QPixmap* ageToPmDir[]= { pmNewDir, pmMiddleDir, pmOldDir, pmNotThere, s_pm_dir };
+ static QPixmap* ageToPmDirLink[]={ pmNewDirLink, pmMiddleDirLink, pmOldDirLink, pmNotThere, pmDirLink };
+
+ QPixmap** ppPm = bDir ? ( bLink ? ageToPmDirLink : ageToPmDir ):
+ ( bLink ? ageToPmLink : ageToPm );
+
+ pLVI->setPixmap( col, *ppPm[eAge] );
+}
+
+static void setPixmaps( MergeFileInfos& mfi, bool bCheckC )
+{
+ setOnePixmap( mfi.m_pDMI, s_nameCol, eAgeEnd,
+ mfi.m_bLinkA || mfi.m_bLinkB || mfi.m_bLinkC,
+ mfi.m_bDirA || mfi.m_bDirB || mfi.m_bDirC
+ );
+
+ if ( mfi.m_bDirA || mfi.m_bDirB || mfi.m_bDirC )
+ {
+ mfi.m_ageA=eNotThere;
+ mfi.m_ageB=eNotThere;
+ mfi.m_ageC=eNotThere;
+ int age = eNew;
+ if ( mfi.m_bExistsInC )
+ {
+ 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.m_bExistsInB && mfi.m_ageB==eNotThere )
+ {
+ mfi.m_ageB = (e_Age)age;
+ if (mfi.m_bEqualAB) mfi.m_ageA = (e_Age)age;
+ ++age;
+ }
+ if ( mfi.m_bExistsInA && 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;
+ }
+ }
+
+ setOnePixmap( mfi.m_pDMI, s_ACol, mfi.m_ageA, mfi.m_bLinkA, mfi.m_bDirA );
+ setOnePixmap( mfi.m_pDMI, s_BCol, mfi.m_ageB, mfi.m_bLinkB, mfi.m_bDirB );
+ if ( bCheckC )
+ setOnePixmap( mfi.m_pDMI, s_CCol, mfi.m_ageC, mfi.m_bLinkC, mfi.m_bDirC );
+}
+
+// Iterate through the complete tree. Start by specifying QListView::firstChild().
+static QListViewItem* treeIterator( QListViewItem* p, bool bVisitChildren=true, bool bFindInvisible=false )
+{
+ if( p!=0 )
+ {
+ do
+ {
+ if ( bVisitChildren && p->firstChild() != 0 ) p = p->firstChild();
+ else if ( p->nextSibling() !=0 ) p = p->nextSibling();
+ else
+ {
+ p = p->parent();
+ while ( p!=0 )
+ {
+ if( p->nextSibling()!=0 ) { p = p->nextSibling(); break; }
+ else { p = p->parent(); }
+ }
+ }
+ }
+ while( p && !(p->isVisible() || bFindInvisible) );
+ }
+ return p;
+}
+
+void DirectoryMergeWindow::prepareListView( ProgressProxy& pp )
+{
+ static bool bFirstTime = true;
+ if (bFirstTime)
+ {
+ #include "xpm/file.xpm"
+ #include "xpm/folder.xpm"
+ s_pm_dir = new QPixmap( m_pIconLoader->loadIcon("folder", KIcon::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 );
+ bFirstTime=false;
+ }
+
+ clear();
+ initPixmaps( m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor,
+ m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor );
+
+ setRootIsDecorated( true );
+
+ bool bCheckC = m_dirC.isValid();
+
+ std::map<QString, MergeFileInfos>::iterator j;
+ int nrOfFiles = m_fileMergeMap.size();
+ int currentIdx = 1;
+ QTime t;
+ t.start();
+ for( j=m_fileMergeMap.begin(); j!=m_fileMergeMap.end(); ++j )
+ {
+ MergeFileInfos& mfi = j->second;
+
+ mfi.m_subPath = mfi.m_fileInfoA.exists() ? mfi.m_fileInfoA.filePath() :
+ mfi.m_fileInfoB.exists() ? mfi.m_fileInfoB.filePath() :
+ mfi.m_fileInfoC.exists() ? mfi.m_fileInfoC.filePath() :
+ QString("");
+
+ // const QString& fileName = j->first;
+ const QString& fileName = mfi.m_subPath;
+
+ pp.setInformation(
+ i18n("Processing ") + QString::number(currentIdx) +" / "+ QString::number(nrOfFiles)
+ +"\n" + fileName, double(currentIdx) / nrOfFiles, false );
+ if ( pp.wasCancelled() ) break;
+ ++currentIdx;
+
+
+ // The comparisons and calculations for each file take place here.
+ compareFilesAndCalcAges( mfi );
+
+ bool bEqual = bCheckC ? mfi.m_bEqualAB && mfi.m_bEqualAC : mfi.m_bEqualAB;
+ //bool bDir = mfi.m_bDirA || mfi.m_bDirB || mfi.m_bDirC;
+
+ //if ( m_pOptions->m_bDmShowOnlyDeltas && !bDir && bEqual )
+ // continue;
+
+ // Get dirname from fileName: Search for "/" from end:
+ int pos = fileName.findRev('/');
+ 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
+ {
+ new DirMergeItem( this, filePart, &mfi );
+ }
+ else
+ {
+ MergeFileInfos& dirMfi = m_fileMergeMap[sortString(dirPart, m_bCaseSensitive)]; // parent
+ assert(dirMfi.m_pDMI!=0);
+ new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi );
+ mfi.m_pParent = &dirMfi;
+
+ if ( !bEqual ) // Set all parents to "not equal"
+ {
+ MergeFileInfos* p = mfi.m_pParent;
+ while(p!=0)
+ {
+ bool bChange = false;
+ if ( !mfi.m_bEqualAB && p->m_bEqualAB ){ p->m_bEqualAB = false; bChange=true; }
+ if ( !mfi.m_bEqualAC && p->m_bEqualAC ){ p->m_bEqualAC = false; bChange=true; }
+ if ( !mfi.m_bEqualBC && p->m_bEqualBC ){ p->m_bEqualBC = false; bChange=true; }
+
+ if ( bChange )
+ setPixmaps( *p, bCheckC );
+ else
+ break;
+
+ p = p->m_pParent;
+ }
+ }
+ }
+
+ setPixmaps( mfi, bCheckC );
+ }
+
+ /*if ( m_pOptions->m_bDmShowOnlyDeltas )
+ {
+ // Remove all equals. (Search tree depth first)
+ QListViewItem* p = firstChild();
+ while( p!=0 && firstChild() != 0 )
+ {
+ QListViewItem* pParent = p->parent();
+ QListViewItem* pNextSibling = p->nextSibling();
+
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(p);
+ bool bDirEqual = bCheckC ? pDMI->m_pMFI->m_bEqualAB && pDMI->m_pMFI->m_bEqualAC
+ : pDMI->m_pMFI->m_bEqualAB;
+ if ( pDMI!=0 && pDMI->m_pMFI->m_bDirA && bDirEqual )
+ {
+ delete p;
+ p=0;
+ }
+
+ if ( p!=0 && p->firstChild() != 0 ) p = p->firstChild();
+ else if ( pNextSibling!=0 ) p = pNextSibling;
+ else
+ {
+ p=pParent;
+ while ( p!=0 )
+ {
+ if( p->nextSibling()!=0 ) { p = p->nextSibling(); break; }
+ else { p = p->parent(); }
+ }
+ }
+ }
+ }*/
+}
+
+static bool conflictingFileTypes(MergeFileInfos& mfi)
+{
+ // Now check if file/dir-types fit.
+ if ( mfi.m_bLinkA || mfi.m_bLinkB || mfi.m_bLinkC )
+ {
+ if ( mfi.m_bExistsInA && ! mfi.m_bLinkA ||
+ mfi.m_bExistsInB && ! mfi.m_bLinkB ||
+ mfi.m_bExistsInC && ! mfi.m_bLinkC )
+ {
+ return true;
+ }
+ }
+
+ if ( mfi.m_bDirA || mfi.m_bDirB || mfi.m_bDirC )
+ {
+ if ( mfi.m_bExistsInA && ! mfi.m_bDirA ||
+ mfi.m_bExistsInB && ! mfi.m_bDirB ||
+ mfi.m_bExistsInC && ! mfi.m_bDirC )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+void DirectoryMergeWindow::calcSuggestedOperation( MergeFileInfos& mfi, e_MergeOperation eDefaultMergeOp )
+{
+ bool bCheckC = m_dirC.isValid();
+ bool bCopyNewer = m_pOptions->m_bDmCopyNewer;
+ bool bOtherDest = !( m_dirDestInternal.absFilePath() == m_dirA.absFilePath() ||
+ m_dirDestInternal.absFilePath() == m_dirB.absFilePath() ||
+ bCheckC && m_dirDestInternal.absFilePath() == m_dirC.absFilePath() );
+
+ if ( eDefaultMergeOp == eMergeABCToDest && !bCheckC ) { eDefaultMergeOp = eMergeABToDest; }
+ if ( eDefaultMergeOp == eMergeToAB && bCheckC ) { assert(false); }
+
+ if ( eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB ||
+ eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB )
+ {
+ if ( !bCheckC )
+ {
+ if ( mfi.m_bEqualAB )
+ {
+ mfi.setMergeOperation( bOtherDest ? eCopyBToDest : eNoOperation );
+ }
+ else if ( mfi.m_bExistsInA && mfi.m_bExistsInB )
+ {
+ if ( !bCopyNewer || mfi.m_bDirA )
+ mfi.setMergeOperation( eDefaultMergeOp );
+ else if ( bCopyNewer && mfi.m_bConflictingAges )
+ {
+ mfi.setMergeOperation( eConflictingAges );
+ }
+ else
+ {
+ if ( mfi.m_ageA == eNew )
+ mfi.setMergeOperation( eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest );
+ else
+ mfi.setMergeOperation( eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest );
+ }
+ }
+ else if ( !mfi.m_bExistsInA && mfi.m_bExistsInB )
+ {
+ if ( eDefaultMergeOp==eMergeABToDest ) mfi.setMergeOperation( eCopyBToDest );
+ else if ( eDefaultMergeOp==eMergeToB ) mfi.setMergeOperation( eNoOperation );
+ else mfi.setMergeOperation( eCopyBToA );
+ }
+ else if ( mfi.m_bExistsInA && !mfi.m_bExistsInB )
+ {
+ if ( eDefaultMergeOp==eMergeABToDest ) mfi.setMergeOperation( eCopyAToDest );
+ else if ( eDefaultMergeOp==eMergeToA ) mfi.setMergeOperation( eNoOperation );
+ else mfi.setMergeOperation( eCopyAToB );
+ }
+ else //if ( !mfi.m_bExistsInA && !mfi.m_bExistsInB )
+ {
+ mfi.setMergeOperation( eNoOperation ); assert(false);
+ }
+ }
+ else
+ {
+ if ( mfi.m_bEqualAB && mfi.m_bEqualAC )
+ {
+ mfi.setMergeOperation( bOtherDest ? eCopyCToDest : eNoOperation );
+ }
+ else if ( mfi.m_bExistsInA && mfi.m_bExistsInB && mfi.m_bExistsInC)
+ {
+ if ( mfi.m_bEqualAB )
+ mfi.setMergeOperation( eCopyCToDest );
+ else if ( mfi.m_bEqualAC )
+ mfi.setMergeOperation( eCopyBToDest );
+ else if ( mfi.m_bEqualBC )
+ mfi.setMergeOperation( eCopyCToDest );
+ else
+ mfi.setMergeOperation( eMergeABCToDest );
+ }
+ else if ( mfi.m_bExistsInA && mfi.m_bExistsInB && !mfi.m_bExistsInC )
+ {
+ if ( mfi.m_bEqualAB )
+ mfi.setMergeOperation( eDeleteFromDest );
+ else
+ mfi.setMergeOperation( eCopyBToDest );
+ }
+ else if ( mfi.m_bExistsInA && !mfi.m_bExistsInB && mfi.m_bExistsInC )
+ {
+ if ( mfi.m_bEqualAC )
+ mfi.setMergeOperation( eDeleteFromDest );
+ else
+ mfi.setMergeOperation( eCopyCToDest );
+ }
+ else if ( !mfi.m_bExistsInA && mfi.m_bExistsInB && mfi.m_bExistsInC )
+ {
+ if ( mfi.m_bEqualBC )
+ mfi.setMergeOperation( eCopyCToDest );
+ else
+ mfi.setMergeOperation( eMergeABCToDest );
+ }
+ else if ( !mfi.m_bExistsInA && !mfi.m_bExistsInB && mfi.m_bExistsInC )
+ {
+ mfi.setMergeOperation( eCopyCToDest );
+ }
+ else if ( !mfi.m_bExistsInA && mfi.m_bExistsInB && !mfi.m_bExistsInC )
+ {
+ mfi.setMergeOperation( eCopyBToDest );
+ }
+ else if ( mfi.m_bExistsInA && !mfi.m_bExistsInB && !mfi.m_bExistsInC)
+ {
+ mfi.setMergeOperation( eDeleteFromDest );
+ }
+ else //if ( !mfi.m_bExistsInA && !mfi.m_bExistsInB && !mfi.m_bExistsInC )
+ {
+ mfi.setMergeOperation( eNoOperation ); assert(false);
+ }
+ }
+
+ // Now check if file/dir-types fit.
+ if ( conflictingFileTypes(mfi) )
+ {
+ mfi.setMergeOperation( eConflictingFileTypes );
+ }
+ }
+ else
+ {
+ e_MergeOperation eMO = eDefaultMergeOp;
+ switch ( eDefaultMergeOp )
+ {
+ case eConflictingFileTypes:
+ case eConflictingAges:
+ case eDeleteA:
+ case eDeleteB:
+ case eDeleteAB:
+ case eDeleteFromDest:
+ case eNoOperation: break;
+ case eCopyAToB: if ( !mfi.m_bExistsInA ) { eMO = eDeleteB; } break;
+ case eCopyBToA: if ( !mfi.m_bExistsInB ) { eMO = eDeleteA; } break;
+ case eCopyAToDest: if ( !mfi.m_bExistsInA ) { eMO = eDeleteFromDest; } break;
+ case eCopyBToDest: if ( !mfi.m_bExistsInB ) { eMO = eDeleteFromDest; } break;
+ case eCopyCToDest: if ( !mfi.m_bExistsInC ) { eMO = eDeleteFromDest; } break;
+
+ case eMergeToA:
+ case eMergeToB:
+ case eMergeToAB:
+ case eMergeABCToDest:
+ case eMergeABToDest:
+ default:
+ assert(false);
+ }
+ mfi.setMergeOperation( eMO );
+ }
+}
+
+void DirectoryMergeWindow::onDoubleClick( QListViewItem* lvi )
+{
+ if (lvi==0) return;
+
+ if ( m_bDirectoryMerge )
+ mergeCurrentFile();
+ else
+ compareCurrentFile();
+}
+
+void DirectoryMergeWindow::onSelectionChanged( QListViewItem* lvi )
+{
+ if ( lvi==0 ) return;
+
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(lvi);
+
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+ assert( mfi.m_pDMI==pDMI );
+
+ m_pDirectoryMergeInfo->setInfo( m_dirA, m_dirB, m_dirC, m_dirDestInternal, mfi );
+}
+
+void DirectoryMergeWindow::onClick( int button, QListViewItem* lvi, const QPoint& p, int c )
+{
+ if ( lvi==0 ) return;
+
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(lvi);
+
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+ assert( mfi.m_pDMI==pDMI );
+
+ if ( c==s_OpCol )
+ {
+ bool bThreeDirs = m_dirC.isValid();
+
+ KPopupMenu m(this);
+ if ( bThreeDirs )
+ {
+ m_pDirCurrentDoNothing->plug(&m);
+ int count=0;
+ if ( mfi.m_bExistsInA ) { m_pDirCurrentChooseA->plug(&m); ++count; }
+ if ( mfi.m_bExistsInB ) { m_pDirCurrentChooseB->plug(&m); ++count; }
+ if ( mfi.m_bExistsInC ) { m_pDirCurrentChooseC->plug(&m); ++count; }
+ if ( !conflictingFileTypes(mfi) && count>1 ) m_pDirCurrentMerge->plug(&m);
+ m_pDirCurrentDelete->plug(&m);
+ }
+ else if ( m_bSyncMode )
+ {
+ m_pDirCurrentSyncDoNothing->plug(&m);
+ if ( mfi.m_bExistsInA ) m_pDirCurrentSyncCopyAToB->plug(&m);
+ if ( mfi.m_bExistsInB ) m_pDirCurrentSyncCopyBToA->plug(&m);
+ if ( mfi.m_bExistsInA ) m_pDirCurrentSyncDeleteA->plug(&m);
+ if ( mfi.m_bExistsInB ) m_pDirCurrentSyncDeleteB->plug(&m);
+ if ( mfi.m_bExistsInA && mfi.m_bExistsInB )
+ {
+ m_pDirCurrentSyncDeleteAAndB->plug(&m);
+ if ( !conflictingFileTypes(mfi))
+ {
+ m_pDirCurrentSyncMergeToA->plug(&m);
+ m_pDirCurrentSyncMergeToB->plug(&m);
+ m_pDirCurrentSyncMergeToAAndB->plug(&m);
+ }
+ }
+ }
+ else
+ {
+ m_pDirCurrentDoNothing->plug(&m);
+ if ( mfi.m_bExistsInA ) { m_pDirCurrentChooseA->plug(&m); }
+ if ( mfi.m_bExistsInB ) { m_pDirCurrentChooseB->plug(&m); }
+ if ( !conflictingFileTypes(mfi) && mfi.m_bExistsInA && mfi.m_bExistsInB ) m_pDirCurrentMerge->plug(&m);
+ m_pDirCurrentDelete->plug(&m);
+ }
+
+ m.exec( p );
+ }
+ else if ( c == s_ACol || c==s_BCol || c==s_CCol )
+ {
+ QString itemPath;
+ if ( c == s_ACol && mfi.m_bExistsInA ){ itemPath = fullNameA(mfi); }
+ else if ( c == s_BCol && mfi.m_bExistsInB ){ itemPath = fullNameB(mfi); }
+ else if ( c == s_CCol && mfi.m_bExistsInC ){ itemPath = fullNameC(mfi); }
+
+ if (!itemPath.isEmpty())
+ {
+ selectItemAndColumn( pDMI, c, button==Qt::RightButton );
+ }
+ }
+}
+
+void DirectoryMergeWindow::slotShowContextMenu(QListViewItem* lvi,const QPoint & p,int c)
+{
+ if ( lvi==0 ) return;
+
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(lvi);
+
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+ assert( mfi.m_pDMI==pDMI );
+ if ( c == s_ACol || c==s_BCol || c==s_CCol )
+ {
+ QString itemPath;
+ if ( c == s_ACol && mfi.m_bExistsInA ){ itemPath = fullNameA(mfi); }
+ else if ( c == s_BCol && mfi.m_bExistsInB ){ itemPath = fullNameB(mfi); }
+ else if ( c == s_CCol && mfi.m_bExistsInC ){ itemPath = fullNameC(mfi); }
+
+ if (!itemPath.isEmpty())
+ {
+ selectItemAndColumn(pDMI, c, true);
+ KPopupMenu m(this);
+ m_pDirCompareExplicit->plug(&m);
+ m_pDirMergeExplicit->plug(&m);
+
+#ifndef _WIN32
+ m.exec( p );
+#else
+ void showShellContextMenu( const QString&, QPoint, QWidget*, QPopupMenu* );
+ showShellContextMenu( itemPath, p, this, &m );
+#endif
+ }
+ }
+}
+
+static QString getFileName( DirMergeItem* pDMI, int column )
+{
+ if ( pDMI != 0 )
+ {
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+ return column == s_ACol ? mfi.m_fileInfoA.absFilePath() :
+ column == s_BCol ? mfi.m_fileInfoB.absFilePath() :
+ column == s_CCol ? mfi.m_fileInfoC.absFilePath() :
+ QString("");
+ }
+ return "";
+}
+
+static bool isDir( DirMergeItem* pDMI, int column )
+{
+ if ( pDMI != 0 )
+ {
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+ return column == s_ACol ? mfi.m_bDirA :
+ column == s_BCol ? mfi.m_bDirB :
+ mfi.m_bDirC;
+ }
+ return false;
+}
+
+
+void DirectoryMergeWindow::selectItemAndColumn(DirMergeItem* pDMI, int c, bool bContextMenu)
+{
+ if ( bContextMenu && (
+ pDMI==m_pSelection1Item && c==m_selection1Column ||
+ pDMI==m_pSelection2Item && c==m_selection2Column ||
+ pDMI==m_pSelection3Item && c==m_selection3Column ) )
+ return;
+
+ DirMergeItem* pOld1=m_pSelection1Item;
+ DirMergeItem* pOld2=m_pSelection2Item;
+ DirMergeItem* pOld3=m_pSelection3Item;
+
+ bool bReset = false;
+
+ if ( m_pSelection1Item )
+ {
+ if (isDir( m_pSelection1Item, m_selection1Column )!=isDir( pDMI, c ))
+ bReset = true;
+ }
+
+ if ( bReset || m_pSelection3Item!=0 ||
+ pDMI==m_pSelection1Item && c==m_selection1Column ||
+ pDMI==m_pSelection2Item && c==m_selection2Column ||
+ pDMI==m_pSelection3Item && c==m_selection3Column)
+ {
+ m_pSelection1Item = 0;
+ m_pSelection2Item = 0;
+ m_pSelection3Item = 0;
+ }
+ else if ( m_pSelection1Item==0 )
+ {
+ m_pSelection1Item = pDMI;
+ m_selection1Column = c;
+ m_pSelection2Item = 0;
+ m_pSelection3Item = 0;
+ }
+ else if ( m_pSelection2Item==0 )
+ {
+ m_pSelection2Item = pDMI;
+ m_selection2Column = c;
+ m_pSelection3Item = 0;
+ }
+ else if ( m_pSelection3Item==0 )
+ {
+ m_pSelection3Item = pDMI;
+ m_selection3Column = c;
+ }
+ if (pOld1) repaintItem( pOld1 );
+ if (pOld2) repaintItem( pOld2 );
+ if (pOld3) repaintItem( pOld3 );
+ if (m_pSelection1Item) repaintItem( m_pSelection1Item );
+ if (m_pSelection2Item) repaintItem( m_pSelection2Item );
+ if (m_pSelection3Item) repaintItem( m_pSelection3Item );
+ emit updateAvailabilities();
+}
+
+// Since Qt 2.3.0 doesn't allow the specification of a compare operator, this trick emulates it.
+#if QT_VERSION==230
+#define DIRSORT(x) ( pMFI->m_bDirA ? " " : "" )+x
+#else
+#define DIRSORT(x) x
+#endif
+
+DirMergeItem::DirMergeItem( QListView* pParent, const QString& fileName, MergeFileInfos* pMFI )
+: QListViewItem( pParent, DIRSORT( fileName ), "","","", i18n("To do."), "" )
+{
+ init(pMFI);
+}
+
+DirMergeItem::DirMergeItem( DirMergeItem* pParent, const QString& fileName, MergeFileInfos* pMFI )
+: QListViewItem( pParent, DIRSORT( fileName ), "","","", i18n("To do."), "" )
+{
+ init(pMFI);
+}
+
+
+void DirMergeItem::init(MergeFileInfos* pMFI)
+{
+ pMFI->m_pDMI = this;
+ m_pMFI = pMFI;
+ TotalDiffStatus& tds = pMFI->m_totalDiffStatus;
+ if ( m_pMFI->m_bDirA || m_pMFI->m_bDirB || m_pMFI->m_bDirC )
+ {
+ }
+ else
+ {
+ setText( s_UnsolvedCol, QString::number( tds.nofUnsolvedConflicts ) );
+ setText( s_SolvedCol, QString::number( tds.nofSolvedConflicts ) );
+ setText( s_NonWhiteCol, QString::number( tds.nofUnsolvedConflicts + tds.nofSolvedConflicts - tds.nofWhitespaceConflicts ) );
+ setText( s_WhiteCol, QString::number( tds.nofWhitespaceConflicts ) );
+ }
+}
+
+int DirMergeItem::compare(QListViewItem *i, int col, bool ascending) const
+{
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(i);
+ bool bDir1 = m_pMFI->m_bDirA || m_pMFI->m_bDirB || m_pMFI->m_bDirC;
+ bool bDir2 = pDMI->m_pMFI->m_bDirA || pDMI->m_pMFI->m_bDirB || pDMI->m_pMFI->m_bDirC;
+ if ( m_pMFI==0 || pDMI->m_pMFI==0 || bDir1 == bDir2 )
+ {
+ if(col==s_UnsolvedCol || col==s_SolvedCol || col==s_NonWhiteCol || col==s_WhiteCol)
+ return key(col,ascending).toInt() > i->key(col,ascending).toInt() ? -1 : 1;
+ else
+ return QListViewItem::compare( i, col, ascending );
+ }
+ else
+ return bDir1 ? -1 : 1;
+}
+
+void DirMergeItem::paintCell(QPainter * p, const QColorGroup & cg, int column, int width, int align )
+{
+ if (column == s_ACol || column == s_BCol || column == s_CCol )
+ {
+ const QPixmap* icon = pixmap(column);
+ if ( icon )
+ {
+ int yOffset = (height() - icon->height()) / 2;
+ p->fillRect( 0, 0, width, height(), cg.base() );
+ p->drawPixmap( 2, yOffset, *icon );
+ if ( listView() )
+ {
+ DirectoryMergeWindow* pDMW = static_cast<DirectoryMergeWindow*>(listView());
+ int i = this==pDMW->m_pSelection1Item && column == pDMW->m_selection1Column ? 1 :
+ this==pDMW->m_pSelection2Item && column == pDMW->m_selection2Column ? 2 :
+ this==pDMW->m_pSelection3Item && column == pDMW->m_selection3Column ? 3 :
+ 0;
+ if ( i!=0 )
+ {
+ OptionDialog* pOD = pDMW->m_pOptions;
+ QColor c ( i==1 ? pOD->m_colorA : i==2 ? pOD->m_colorB : pOD->m_colorC );
+ p->setPen( c );// highlight() );
+ p->drawRect( 2, yOffset, icon->width(), icon->height());
+ p->setPen( QPen( c, 0, Qt::DotLine) );
+ p->drawRect( 1, yOffset-1, icon->width()+2, icon->height()+2);
+ p->setPen( cg.background() );
+ QString s( QChar('A'+i-1) );
+ p->drawText( 2 + (icon->width() - p->fontMetrics().width(s))/2,
+ yOffset + (icon->height() + p->fontMetrics().ascent())/2-1,
+ s );
+ }
+ else
+ {
+ p->setPen( cg.background() );
+ p->drawRect( 1, yOffset-1, icon->width()+2, icon->height()+2);
+ }
+ }
+ return;
+ }
+ }
+ QListViewItem::paintCell(p,cg,column,width,align);
+}
+
+DirMergeItem::~DirMergeItem()
+{
+ m_pMFI->m_pDMI = 0;
+}
+
+void MergeFileInfos::setMergeOperation( e_MergeOperation eMOp, bool bRecursive )
+{
+ if ( eMOp != m_eMergeOperation )
+ {
+ m_bOperationComplete = false;
+ m_pDMI->setText( s_OpStatusCol, "" );
+ }
+
+ m_eMergeOperation = eMOp;
+ QString s;
+ bool bDir = m_bDirA || m_bDirB || m_bDirC;
+ if( m_pDMI!=0 )
+ {
+ switch( m_eMergeOperation )
+ {
+ case eNoOperation: s=""; m_pDMI->setText(s_OpCol,""); break;
+ case eCopyAToB: s=i18n("Copy A to B"); break;
+ case eCopyBToA: s=i18n("Copy B to A"); break;
+ case eDeleteA: s=i18n("Delete A"); break;
+ case eDeleteB: s=i18n("Delete B"); break;
+ case eDeleteAB: s=i18n("Delete A & B"); break;
+ case eMergeToA: s=i18n("Merge to A"); break;
+ case eMergeToB: s=i18n("Merge to B"); break;
+ case eMergeToAB: s=i18n("Merge to A & B"); break;
+ case eCopyAToDest: s="A"; break;
+ case eCopyBToDest: s="B"; break;
+ case eCopyCToDest: s="C"; break;
+ case eDeleteFromDest: s=i18n("Delete (if exists)"); break;
+ case eMergeABCToDest: s= bDir ? i18n("Merge") : i18n("Merge (manual)"); break;
+ case eMergeABToDest: s= bDir ? i18n("Merge") : i18n("Merge (manual)"); break;
+ case eConflictingFileTypes: s=i18n("Error: Conflicting File Types"); break;
+ case eConflictingAges: s=i18n("Error: Dates are equal but files are not."); break;
+ default: assert(false); break;
+ }
+ m_pDMI->setText(s_OpCol,s);
+
+ if ( bRecursive )
+ {
+ e_MergeOperation eChildrenMergeOp = m_eMergeOperation;
+ if ( eChildrenMergeOp == eConflictingFileTypes ) eChildrenMergeOp = eMergeABCToDest;
+ QListViewItem* p = m_pDMI->firstChild();
+ while ( p!=0 )
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>( p );
+ DirectoryMergeWindow* pDMW = static_cast<DirectoryMergeWindow*>( p->listView() );
+ pDMW->calcSuggestedOperation( *pDMI->m_pMFI, eChildrenMergeOp );
+ p = p->nextSibling();
+ }
+ }
+ }
+}
+
+void DirectoryMergeWindow::compareCurrentFile()
+{
+ if (!canContinue()) return;
+
+ if ( m_bRealMergeStarted )
+ {
+ KMessageBox::sorry(this,i18n("This operation is currently not possible."),i18n("Operation Not Possible"));
+ return;
+ }
+
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>( selectedItem() );
+ if ( pDMI != 0 )
+ {
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+ if ( !(mfi.m_bDirA || mfi.m_bDirB || mfi.m_bDirC) )
+ {
+ emit startDiffMerge(
+ mfi.m_bExistsInA ? mfi.m_fileInfoA.absFilePath() : QString(""),
+ mfi.m_bExistsInB ? mfi.m_fileInfoB.absFilePath() : QString(""),
+ mfi.m_bExistsInC ? mfi.m_fileInfoC.absFilePath() : QString(""),
+ "",
+ "","","",0
+ );
+ }
+ }
+ emit updateAvailabilities();
+}
+
+
+void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles()
+{
+ if ( ! isDir(m_pSelection1Item,m_selection1Column) && !canContinue() ) return;
+
+ if ( m_bRealMergeStarted )
+ {
+ KMessageBox::sorry(this,i18n("This operation is currently not possible."),i18n("Operation Not Possible"));
+ return;
+ }
+
+ emit startDiffMerge(
+ getFileName( m_pSelection1Item, m_selection1Column ),
+ getFileName( m_pSelection2Item, m_selection2Column ),
+ getFileName( m_pSelection3Item, m_selection3Column ),
+ "",
+ "","","",0
+ );
+ m_pSelection1Item=0;
+ m_pSelection2Item=0;
+ m_pSelection3Item=0;
+
+ emit updateAvailabilities();
+ triggerUpdate();
+}
+
+void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles()
+{
+ if ( ! isDir(m_pSelection1Item,m_selection1Column) && !canContinue() ) return;
+
+ if ( m_bRealMergeStarted )
+ {
+ KMessageBox::sorry(this,i18n("This operation is currently not possible."),i18n("Operation Not Possible"));
+ return;
+ }
+
+ QString fn1 = getFileName( m_pSelection1Item, m_selection1Column );
+ QString fn2 = getFileName( m_pSelection2Item, m_selection2Column );
+ QString fn3 = getFileName( m_pSelection3Item, m_selection3Column );
+
+ emit startDiffMerge( fn1, fn2, fn3,
+ fn3.isEmpty() ? fn2 : fn3,
+ "","","",0
+ );
+ m_pSelection1Item=0;
+ m_pSelection2Item=0;
+ m_pSelection3Item=0;
+
+ emit updateAvailabilities();
+ triggerUpdate();
+}
+
+bool DirectoryMergeWindow::isFileSelected()
+{
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>( selectedItem() );
+ if ( pDMI != 0 )
+ {
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+ return ! (mfi.m_bDirA || mfi.m_bDirB || mfi.m_bDirC || conflictingFileTypes(mfi) );
+ }
+ return false;
+}
+
+void DirectoryMergeWindow::mergeResultSaved(const QString& fileName)
+{
+ DirMergeItem* pCurrentItemForOperation = (m_mergeItemList.empty() || m_currentItemForOperation==m_mergeItemList.end() )
+ ? 0
+ : *m_currentItemForOperation;
+
+ if ( pCurrentItemForOperation!=0 && pCurrentItemForOperation->m_pMFI==0 )
+ {
+ KMessageBox::error( this, i18n("This should never happen: \n\nmergeResultSaved: m_pMFI=0\n\nIf you know how to reproduce this, please contact the program author."),i18n("Program Error") );
+ return;
+ }
+ if ( pCurrentItemForOperation!=0 && fileName == fullNameDest(*pCurrentItemForOperation->m_pMFI) )
+ {
+ if ( pCurrentItemForOperation->m_pMFI->m_eMergeOperation==eMergeToAB )
+ {
+ MergeFileInfos& mfi = *pCurrentItemForOperation->m_pMFI;
+ bool bSuccess = copyFLD( fullNameB(mfi), fullNameA(mfi) );
+ if (!bSuccess)
+ {
+ KMessageBox::error(this, i18n("An error occurred while copying.\n"), i18n("Error") );
+ m_pStatusInfo->setCaption(i18n("Merge Error"));
+ m_pStatusInfo->show();
+ //if ( m_pStatusInfo->firstChild()!=0 )
+ // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() );
+ m_bError = true;
+ pCurrentItemForOperation->setText( s_OpStatusCol, i18n("Error.") );
+ mfi.m_eMergeOperation = eCopyBToA;
+ return;
+ }
+ }
+ pCurrentItemForOperation->setText( s_OpStatusCol, i18n("Done.") );
+ pCurrentItemForOperation->m_pMFI->m_bOperationComplete = true;
+ if ( m_mergeItemList.size()==1 )
+ {
+ m_mergeItemList.clear();
+ m_bRealMergeStarted=false;
+ }
+ }
+
+ emit updateAvailabilities();
+}
+
+bool DirectoryMergeWindow::canContinue()
+{
+ bool bCanContinue=false;
+ checkIfCanContinue( &bCanContinue );
+ if ( bCanContinue && !m_bError )
+ {
+ DirMergeItem* pCurrentItemForOperation =
+ (m_mergeItemList.empty() || m_currentItemForOperation==m_mergeItemList.end() ) ? 0 : *m_currentItemForOperation;
+
+ if ( pCurrentItemForOperation!=0 && ! pCurrentItemForOperation->m_pMFI->m_bOperationComplete )
+ {
+ pCurrentItemForOperation->setText( s_OpStatusCol, i18n("Not saved.") );
+ pCurrentItemForOperation->m_pMFI->m_bOperationComplete = true;
+ if ( m_mergeItemList.size()==1 )
+ {
+ m_mergeItemList.clear();
+ m_bRealMergeStarted=false;
+ }
+ }
+ }
+ return bCanContinue;
+}
+
+bool DirectoryMergeWindow::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 = fullNameB(mfi); break;
+ case eMergeToA:
+ case eDeleteA:
+ case eCopyBToA: destName = fullNameA(mfi); break;
+ case eMergeABToDest:
+ case eMergeABCToDest:
+ case eCopyAToDest:
+ case eCopyBToDest:
+ case eCopyCToDest:
+ case eDeleteFromDest: destName = fullNameDest(mfi); break;
+ default:
+ KMessageBox::error( this, i18n("Unknown merge operation. (This must never happen!)"), i18n("Error") );
+ assert(false);
+ }
+
+ bool bSuccess = false;
+ bSingleFileMerge = false;
+ switch( mfi.m_eMergeOperation )
+ {
+ case eNoOperation: bSuccess = true; break;
+ case eCopyAToDest:
+ case eCopyAToB: bSuccess = copyFLD( fullNameA(mfi), destName ); break;
+ case eCopyBToDest:
+ case eCopyBToA: bSuccess = copyFLD( fullNameB(mfi), destName ); break;
+ case eCopyCToDest: bSuccess = copyFLD( fullNameC(mfi), destName ); break;
+ case eDeleteFromDest:
+ case eDeleteA:
+ case eDeleteB: bSuccess = deleteFLD( destName, bCreateBackups ); break;
+ case eDeleteAB: bSuccess = deleteFLD( fullNameA(mfi), bCreateBackups ) &&
+ deleteFLD( fullNameB(mfi), bCreateBackups ); break;
+ case eMergeABToDest:
+ case eMergeToA:
+ case eMergeToAB:
+ case eMergeToB: bSuccess = mergeFLD( fullNameA(mfi), fullNameB(mfi), "",
+ destName, bSingleFileMerge );
+ break;
+ case eMergeABCToDest:bSuccess = mergeFLD(
+ mfi.m_bExistsInA ? fullNameA(mfi) : QString(""),
+ mfi.m_bExistsInB ? fullNameB(mfi) : QString(""),
+ mfi.m_bExistsInC ? fullNameC(mfi) : QString(""),
+ destName, bSingleFileMerge );
+ break;
+ default:
+ KMessageBox::error( this, i18n("Unknown merge operation."), i18n("Error") );
+ assert(false);
+ }
+
+ return bSuccess;
+}
+
+
+// Check if the merge can start, and prepare the m_mergeItemList which then contains all
+// items that must be merged.
+void DirectoryMergeWindow::prepareMergeStart( QListViewItem* pBegin, QListViewItem* pEnd, bool bVerbose )
+{
+ if ( bVerbose )
+ {
+ int status = KMessageBox::warningYesNoCancel(this,
+ 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"), i18n("Do It"), 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 (pBegin == 0)
+ return;
+
+ for( QListViewItem* p = pBegin; p!= pEnd; p = treeIterator( p ) )
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(p);
+
+ if ( pDMI && ! pDMI->m_pMFI->m_bOperationComplete )
+ {
+ m_mergeItemList.push_back(pDMI);
+
+ if (pDMI!=0 && pDMI->m_pMFI->m_eMergeOperation == eConflictingFileTypes )
+ {
+ ensureItemVisible( pDMI );
+ setSelected( pDMI, true );
+ KMessageBox::error(this, i18n("The highlighted item has a different type in the different directories. Select what to do."), i18n("Error"));
+ m_mergeItemList.clear();
+ m_bRealMergeStarted=false;
+ return;
+ }
+ if (pDMI!=0 && pDMI->m_pMFI->m_eMergeOperation == eConflictingAges )
+ {
+ ensureItemVisible( pDMI );
+ setSelected( pDMI, true );
+ KMessageBox::error(this, i18n("The modification dates of the file are equal but the files are not. Select what to do."), i18n("Error"));
+ m_mergeItemList.clear();
+ m_bRealMergeStarted=false;
+ return;
+ }
+ }
+ }
+
+ m_currentItemForOperation = m_mergeItemList.begin();
+ return;
+}
+
+void DirectoryMergeWindow::slotRunOperationForCurrentItem()
+{
+ if ( ! canContinue() ) return;
+
+ bool bVerbose = false;
+ if ( m_mergeItemList.empty() )
+ {
+ QListViewItem* pBegin = currentItem();
+ QListViewItem* pEnd = treeIterator(pBegin,false,false); // find next visible sibling (no children)
+
+ prepareMergeStart( pBegin, pEnd, bVerbose );
+ mergeContinue(true, bVerbose);
+ }
+ else
+ mergeContinue(false, bVerbose);
+}
+
+void DirectoryMergeWindow::slotRunOperationForAllItems()
+{
+ if ( ! canContinue() ) return;
+
+ bool bVerbose = true;
+ if ( m_mergeItemList.empty() )
+ {
+ QListViewItem* pBegin = firstChild();
+
+ prepareMergeStart( pBegin, 0, bVerbose );
+ mergeContinue(true, bVerbose);
+ }
+ else
+ mergeContinue(false, bVerbose);
+}
+
+void DirectoryMergeWindow::mergeCurrentFile()
+{
+ if (!canContinue()) return;
+
+ if ( 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() )
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>( selectedItem() );
+ if ( pDMI != 0 )
+ {
+ MergeFileInfos& mfi = *pDMI->m_pMFI;
+ m_mergeItemList.clear();
+ m_mergeItemList.push_back( pDMI );
+ m_currentItemForOperation=m_mergeItemList.begin();
+ bool bDummy=false;
+ mergeFLD(
+ mfi.m_bExistsInA ? mfi.m_fileInfoA.absFilePath() : QString(""),
+ mfi.m_bExistsInB ? mfi.m_fileInfoB.absFilePath() : QString(""),
+ mfi.m_bExistsInC ? mfi.m_fileInfoC.absFilePath() : QString(""),
+ fullNameDest(mfi),
+ bDummy
+ );
+ }
+ }
+ emit updateAvailabilities();
+}
+
+
+// When bStart is true then m_currentItemForOperation must still be processed.
+// When bVerbose is true then a messagebox will tell when the merge is complete.
+void DirectoryMergeWindow::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 )
+ {
+ DirMergeItem* pDMI = *i;
+ ++nrOfItems;
+ if ( pDMI->m_pMFI->m_bOperationComplete )
+ ++nrOfCompletedItems;
+ if ( pDMI->m_pMFI->m_bSimOpComplete )
+ ++nrOfCompletedSimItems;
+ }
+
+ m_pStatusInfo->hide();
+ m_pStatusInfo->clear();
+
+ DirMergeItem* pCurrentItemForOperation = m_currentItemForOperation==m_mergeItemList.end() ? 0 : *m_currentItemForOperation;
+
+ bool bContinueWithCurrentItem = bStart; // true for first item, else false
+ bool bSkipItem = false;
+ if ( !bStart && m_bError && pCurrentItemForOperation!=0 )
+ {
+ int status = KMessageBox::warningYesNoCancel(this,
+ 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"), i18n("Continue With Last Item"), i18n("Skip Item") );
+ if (status==KMessageBox::Yes) bContinueWithCurrentItem = true;
+ else if (status==KMessageBox::No ) bSkipItem = true;
+ else return;
+ m_bError = false;
+ }
+
+ bool bSuccess = true;
+ bool bSingleFileMerge = false;
+ bool bSim = m_bSimulatedMergeStarted;
+ while( bSuccess )
+ {
+ if ( pCurrentItemForOperation==0 )
+ {
+ m_mergeItemList.clear();
+ m_bRealMergeStarted=false;
+ break;
+ }
+
+ if ( pCurrentItemForOperation!=0 && !bContinueWithCurrentItem )
+ {
+ if ( bSim )
+ {
+ if( pCurrentItemForOperation->firstChild()==0 )
+ {
+ pCurrentItemForOperation->m_pMFI->m_bSimOpComplete = true;
+ }
+ }
+ else
+ {
+ if( pCurrentItemForOperation->firstChild()==0 )
+ {
+ if( !pCurrentItemForOperation->m_pMFI->m_bOperationComplete )
+ {
+ pCurrentItemForOperation->setText( s_OpStatusCol, bSkipItem ? i18n("Skipped.") : i18n("Done.") );
+ pCurrentItemForOperation->m_pMFI->m_bOperationComplete = true;
+ bSkipItem = false;
+ }
+ }
+ else
+ {
+ pCurrentItemForOperation->setText( s_OpStatusCol, i18n("In progress...") );
+ }
+ }
+ }
+
+ if ( ! bContinueWithCurrentItem )
+ {
+ // Depth first
+ QListViewItem* pPrevItem = pCurrentItemForOperation;
+ ++m_currentItemForOperation;
+ pCurrentItemForOperation = m_currentItemForOperation==m_mergeItemList.end() ? 0 : *m_currentItemForOperation;
+ if ( (pCurrentItemForOperation==0 || pCurrentItemForOperation->parent()!=pPrevItem->parent()) && pPrevItem->parent()!=0 )
+ {
+ // Check if the parent may be set to "Done"
+ QListViewItem* pParent = pPrevItem->parent();
+ bool bDone = true;
+ while ( bDone && pParent!=0 )
+ {
+ for( QListViewItem* p = pParent->firstChild(); p!=0; p=p->nextSibling() )
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(p);
+ if ( !bSim && ! pDMI->m_pMFI->m_bOperationComplete || bSim && pDMI->m_pMFI->m_bSimOpComplete )
+ {
+ bDone=false;
+ break;
+ }
+ }
+ if ( bDone )
+ {
+ if (bSim)
+ static_cast<DirMergeItem*>(pParent)->m_pMFI->m_bSimOpComplete = bDone;
+ else
+ {
+ pParent->setText( s_OpStatusCol, i18n("Done.") );
+ static_cast<DirMergeItem*>(pParent)->m_pMFI->m_bOperationComplete = bDone;
+ }
+ }
+ pParent = pParent->parent();
+ }
+ }
+ }
+
+ if ( pCurrentItemForOperation == 0 ) // end?
+ {
+ if ( m_bRealMergeStarted )
+ {
+ if (bVerbose)
+ {
+ KMessageBox::information( this, i18n("Merge operation complete."), i18n("Merge Complete") );
+ }
+ m_bRealMergeStarted = false;
+ m_pStatusInfo->setCaption(i18n("Merge Complete"));
+ }
+ if ( m_bSimulatedMergeStarted )
+ {
+ m_bSimulatedMergeStarted = false;
+ for( QListViewItem* p=firstChild(); p!=0; p=treeIterator(p) )
+ {
+ static_cast<DirMergeItem*>(p)->m_pMFI->m_bSimOpComplete = false;
+ }
+ m_pStatusInfo->setCaption(i18n("Simulated merge complete: Check if you agree with the proposed operations."));
+ m_pStatusInfo->show();
+ }
+ //g_pProgressDialog->hide();
+ m_mergeItemList.clear();
+ m_bRealMergeStarted=false;
+ return;
+ }
+
+ MergeFileInfos& mfi = *pCurrentItemForOperation->m_pMFI;
+
+ pp.setInformation( mfi.m_subPath,
+ bSim ? double(nrOfCompletedSimItems)/nrOfItems : double(nrOfCompletedItems)/nrOfItems,
+ false // bRedrawUpdate
+ );
+ //g_pProgressDialog->show();
+
+ bSuccess = executeMergeOperation( mfi, bSingleFileMerge ); // Here the real operation happens.
+
+ if ( bSuccess )
+ {
+ if(bSim) ++nrOfCompletedSimItems;
+ else ++nrOfCompletedItems;
+ bContinueWithCurrentItem = false;
+ }
+
+ if( pp.wasCancelled() )
+ break;
+ } // end while
+
+ //g_pProgressDialog->hide();
+
+ setCurrentItem( pCurrentItemForOperation );
+ ensureItemVisible( pCurrentItemForOperation );
+ if ( !bSuccess && !bSingleFileMerge )
+ {
+ KMessageBox::error(this, i18n("An error occurred. Press OK to see detailed information.\n"), i18n("Error") );
+ m_pStatusInfo->setCaption(i18n("Merge Error"));
+ m_pStatusInfo->show();
+ //if ( m_pStatusInfo->firstChild()!=0 )
+ // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() );
+ m_bError = true;
+ pCurrentItemForOperation->setText( s_OpStatusCol, i18n("Error.") );
+ }
+ else
+ {
+ m_bError = false;
+ }
+ emit updateAvailabilities();
+
+ if ( m_currentItemForOperation==m_mergeItemList.end() )
+ {
+ m_mergeItemList.clear();
+ m_bRealMergeStarted=false;
+ }
+}
+
+void DirectoryMergeWindow::allowResizeEvents(bool bAllowResizeEvents )
+{
+ m_bAllowResizeEvents = bAllowResizeEvents;
+}
+
+void DirectoryMergeWindow::resizeEvent( QResizeEvent* e )
+{
+ if (m_bAllowResizeEvents)
+ QListView::resizeEvent(e);
+}
+
+bool DirectoryMergeWindow::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.").arg(name) );
+ return false;
+ }
+ }
+ else
+ {
+ if ( fi.isDir() && !fi.isSymLink() )
+ m_pStatusInfo->addText(i18n("delete directory recursively( %1 )").arg(name));
+ else
+ m_pStatusInfo->addText(i18n("delete( %1 )").arg(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.absFilePath(), false );
+ if (!bSuccess) break;
+ }
+ if (bSuccess)
+ {
+ bSuccess = FileAccess::removeDir( name );
+ if ( !bSuccess )
+ {
+ m_pStatusInfo->addText( i18n("Error: rmdir( %1 ) operation failed.").arg(name));
+ return false;
+ }
+ }
+ }
+ else
+ {
+ bool bSuccess = FileAccess::removeFile( name );
+ if ( !bSuccess )
+ {
+ m_pStatusInfo->addText( i18n("Error: delete operation failed.") );
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool DirectoryMergeWindow::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.findRev('/');
+ 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)").arg(nameA).arg(nameB).arg(nameC).arg(nameDest));
+ if ( m_bSimulatedMergeStarted )
+ {
+ m_pStatusInfo->addText(i18n(" Note: After a manual merge the user should continue by pressing F7.") );
+ return true;
+ }
+
+ bSingleFileMerge = true;
+ (*m_currentItemForOperation)->setText( s_OpStatusCol, i18n("In progress...") );
+ ensureItemVisible( *m_currentItemForOperation );
+
+ emit startDiffMerge( nameA, nameB, nameC, nameDest, "","","",0 );
+
+ return false;
+}
+
+bool DirectoryMergeWindow::copyFLD( const QString& srcName, const QString& destName )
+{
+ if ( srcName == destName )
+ return true;
+
+ if ( FileAccess(destName, true).exists() )
+ {
+ bool bSuccess = deleteFLD( destName, m_pOptions->m_bDmCreateBakFiles );
+ if ( !bSuccess )
+ {
+ m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed."
+ "Deleting existing destination failed.").arg(srcName).arg(destName));
+ return false;
+ }
+ }
+
+ FileAccess fi( srcName );
+
+ if ( fi.isSymLink() && (fi.isDir() && !m_bFollowDirLinks || !fi.isDir() && !m_bFollowFileLinks) )
+ {
+ m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )").arg(srcName).arg(destName));
+#ifdef _WIN32
+ // What are links?
+#else
+ 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;
+ }
+ QString linkTarget = fi.readLink();
+ bool bSuccess = FileAccess::symLink( linkTarget, destName );
+ if (!bSuccess)
+ m_pStatusInfo->addText(i18n("Error: copyLink failed."));
+ return bSuccess;
+#endif
+ }
+
+ if ( fi.isDir() )
+ {
+ bool bSuccess = makeDir( destName );
+ return bSuccess;
+ }
+
+ int pos=destName.findRev('/');
+ if ( pos>0 )
+ {
+ QString parentName = destName.left(pos);
+ bool bSuccess = makeDir(parentName, true /*quiet*/);
+ if (!bSuccess)
+ return false;
+ }
+
+ m_pStatusInfo->addText(i18n("copy( %1 -> %2 )").arg(srcName).arg(destName));
+
+ if ( m_bSimulatedMergeStarted )
+ {
+ return true;
+ }
+
+ FileAccess faSrc ( srcName );
+ bool 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::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." ).arg(srcName).arg(destName));
+ return false;
+ }
+ }
+
+ m_pStatusInfo->addText(i18n("rename( %1 -> %2 )").arg(srcName).arg(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::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." ).arg(name));
+ return false;
+ }
+ }
+
+ int pos=name.findRev('/');
+ 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 )").arg(name));
+
+ if ( m_bSimulatedMergeStarted )
+ {
+ return true;
+ }
+
+ bool bSuccess = FileAccess::makeDir( name );
+ if ( bSuccess == false )
+ {
+ m_pStatusInfo->addText( i18n("Error while creating directory.") );
+ return false;
+ }
+ return true;
+}
+
+
+DirectoryMergeInfo::DirectoryMergeInfo( QWidget* pParent )
+: QFrame(pParent)
+{
+ QVBoxLayout *topLayout = new QVBoxLayout( this );
+
+ QGridLayout *grid = new QGridLayout( topLayout );
+ grid->setColStretch(1,10);
+
+ int line=0;
+
+ m_pA = new QLabel("A",this); grid->addWidget( m_pA,line, 0 );
+ m_pInfoA = new QLabel(this); grid->addWidget( m_pInfoA,line,1 ); ++line;
+ m_pB = new QLabel("B",this); grid->addWidget( m_pB,line, 0 );
+ m_pInfoB = new QLabel(this); grid->addWidget( m_pInfoB,line,1 ); ++line;
+ m_pC = new QLabel("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 QListView(this); topLayout->addWidget( m_pInfoList );
+ m_pInfoList->addColumn(i18n("Dir"));
+ m_pInfoList->addColumn(i18n("Type"));
+ m_pInfoList->addColumn(i18n("Size"));
+ m_pInfoList->addColumn(i18n("Attr"));
+ m_pInfoList->addColumn(i18n("Last Modification"));
+ m_pInfoList->addColumn(i18n("Link-Destination"));
+ setMinimumSize( 100,100 );
+
+ m_pInfoList->installEventFilter(this);
+}
+
+bool DirectoryMergeInfo::eventFilter(QObject*o, QEvent* e)
+{
+ if ( e->type()==QEvent::FocusIn && o==m_pInfoList )
+ emit gotFocus();
+ return false;
+}
+
+static void addListViewItem( QListView* pListView, const QString& dir,
+ const QString& basePath, FileAccess& fi )
+{
+ if ( basePath.isEmpty() )
+ {
+ return;
+ }
+ else
+ {
+ if ( fi.exists() )
+ {
+#if QT_VERSION==230
+ QString dateString = fi.lastModified().toString();
+#else
+ QString dateString = fi.lastModified().toString("yyyy-MM-dd hh:mm:ss");
+#endif
+
+ new QListViewItem(
+ pListView,
+ dir,
+ QString( fi.isDir() ? i18n("Dir") : i18n("File") ) + (fi.isSymLink() ? "-Link" : ""),
+ QString::number(fi.size()),
+ QString(fi.isReadable() ? "r" : " ") + (fi.isWritable()?"w" : " ")
+#ifdef _WIN32
+ /*Future: Use GetFileAttributes()*/,
+#else
+ + (fi.isExecutable()?"x" : " "),
+#endif
+ dateString,
+ QString(fi.isSymLink() ? (" -> " + fi.readLink()) : QString(""))
+ );
+ }
+ else
+ {
+ new QListViewItem(
+ pListView,
+ 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.absFilePath()==dirDest.absFilePath() )
+ {
+ 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.absFilePath()==dirDest.absFilePath() )
+ {
+ m_pB->setText( i18n("B (Dest): ") ); bHideDest=true;
+ }
+ else
+ m_pB->setText( "B: " );
+ m_pInfoB->setText( dirB.prettyAbsPath() );
+
+ if ( dirC.absFilePath()==dirDest.absFilePath() )
+ {
+ 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( m_pInfoList, "A", dirA.prettyAbsPath(), mfi.m_fileInfoA );
+ addListViewItem( m_pInfoList, "B", dirB.prettyAbsPath(), mfi.m_fileInfoB );
+ addListViewItem( m_pInfoList, "C", dirC.prettyAbsPath(), mfi.m_fileInfoC );
+ if (!bHideDest)
+ {
+ FileAccess fiDest( dirDest.prettyAbsPath() + "/" + mfi.m_subPath, true );
+ addListViewItem( m_pInfoList, i18n("Dest"), dirDest.prettyAbsPath(), fiDest );
+ }
+}
+
+QTextStream& operator<<( QTextStream& ts, MergeFileInfos& mfi )
+{
+ ts << "{\n";
+ ValueMap vm;
+ vm.writeEntry( "SubPath", mfi.m_subPath );
+ vm.writeEntry( "ExistsInA", mfi.m_bExistsInA );
+ vm.writeEntry( "ExistsInB", mfi.m_bExistsInB );
+ vm.writeEntry( "ExistsInC", mfi.m_bExistsInC );
+ vm.writeEntry( "EqualAB", mfi.m_bEqualAB );
+ vm.writeEntry( "EqualAC", mfi.m_bEqualAC );
+ vm.writeEntry( "EqualBC", mfi.m_bEqualBC );
+ //DirMergeItem* m_pDMI;
+ //MergeFileInfos* m_pParent;
+ vm.writeEntry( "MergeOperation", (int) mfi.m_eMergeOperation );
+ vm.writeEntry( "DirA", mfi.m_bDirA );
+ vm.writeEntry( "DirB", mfi.m_bDirB );
+ vm.writeEntry( "DirC", mfi.m_bDirC );
+ vm.writeEntry( "LinkA", mfi.m_bLinkA );
+ vm.writeEntry( "LinkB", mfi.m_bLinkB );
+ vm.writeEntry( "LinkC", mfi.m_bLinkC );
+ vm.writeEntry( "OperationComplete", mfi.m_bOperationComplete );
+ //bool m_bSimOpComplete );
+
+ 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!
+
+ //FileAccess m_fileInfoA;
+ //FileAccess m_fileInfoB;
+ //FileAccess m_fileInfoC;
+
+ //TotalDiffStatus m_totalDiffStatus;
+
+ vm.save(ts);
+
+ ts << "}\n";
+
+ return ts;
+}
+
+void DirectoryMergeWindow::slotSaveMergeState()
+{
+ //slotStatusMsg(i18n("Saving Directory Merge State ..."));
+
+ //QString s = KFileDialog::getSaveURL( QDir::currentDirPath(), 0, this, i18n("Save As...") ).url();
+ QString s = KFileDialog::getSaveFileName( QDir::currentDirPath(), 0, this, i18n("Save Directory Merge State As...") );
+ if(!s.isEmpty())
+ {
+ m_dirMergeStateFilename = s;
+
+
+ QFile file(m_dirMergeStateFilename);
+ bool bSuccess = file.open( IO_WriteOnly );
+ if ( bSuccess )
+ {
+ QTextStream ts( &file );
+
+ QListViewItemIterator it( this );
+ while ( it.current() ) {
+ DirMergeItem* item = static_cast<DirMergeItem*>(it.current());
+ MergeFileInfos* pMFI = item->m_pMFI;
+ ts << *pMFI;
+ ++it;
+ }
+ }
+ }
+
+ //slotStatusMsg(i18n("Ready."));
+
+}
+
+void DirectoryMergeWindow::slotLoadMergeState()
+{
+}
+
+void DirectoryMergeWindow::updateFileVisibilities()
+{
+ bool bShowIdentical = m_pDirShowIdenticalFiles->isChecked();
+ bool bShowDifferent = m_pDirShowDifferentFiles->isChecked();
+ bool bShowOnlyInA = m_pDirShowFilesOnlyInA->isChecked();
+ bool bShowOnlyInB = m_pDirShowFilesOnlyInB->isChecked();
+ bool bShowOnlyInC = m_pDirShowFilesOnlyInC->isChecked();
+ bool bThreeDirs = m_dirC.isValid();
+ m_pSelection1Item = 0;
+ m_pSelection2Item = 0;
+ m_pSelection3Item = 0;
+
+ QListViewItem* p = firstChild();
+ while(p)
+ {
+ DirMergeItem* pDMI = static_cast<DirMergeItem*>(p);
+ MergeFileInfos* pMFI = pDMI->m_pMFI;
+ bool bDir = pMFI->m_bDirA || pMFI->m_bDirB || pMFI->m_bDirC;
+ bool bExistsEverywhere = pMFI->m_bExistsInA && pMFI->m_bExistsInB && (pMFI->m_bExistsInC || !bThreeDirs);
+ int existCount = int(pMFI->m_bExistsInA) + int(pMFI->m_bExistsInB) + int(pMFI->m_bExistsInC);
+ bool bVisible =
+ ( bShowIdentical && bExistsEverywhere && pMFI->m_bEqualAB && (pMFI->m_bEqualAC || !bThreeDirs) )
+ || ( (bShowDifferent||bDir) && existCount>=2 && (!pMFI->m_bEqualAB || !(pMFI->m_bEqualAC || !bThreeDirs)))
+ || ( bShowOnlyInA && pMFI->m_bExistsInA && !pMFI->m_bExistsInB && !pMFI->m_bExistsInC )
+ || ( bShowOnlyInB && !pMFI->m_bExistsInA && pMFI->m_bExistsInB && !pMFI->m_bExistsInC )
+ || ( bShowOnlyInC && !pMFI->m_bExistsInA && !pMFI->m_bExistsInB && pMFI->m_bExistsInC );
+
+ QString fileName = pMFI->m_subPath.section( '/', -1 );
+ bVisible = bVisible && (
+ bDir && ! wildcardMultiMatch( m_pOptions->m_DmDirAntiPattern, fileName, m_bCaseSensitive )
+ || wildcardMultiMatch( m_pOptions->m_DmFilePattern, fileName, m_bCaseSensitive )
+ && !wildcardMultiMatch( m_pOptions->m_DmFileAntiPattern, fileName, m_bCaseSensitive ) );
+
+ p->setVisible(bVisible);
+ p = treeIterator( p, true, true );
+ }
+}
+
+void DirectoryMergeWindow::slotShowIdenticalFiles() { m_pOptions->m_bDmShowIdenticalFiles=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( QObject* pKDiff3App, KActionCollection* ac )
+{
+#include "xpm/startmerge.xpm"
+#include "xpm/showequalfiles.xpm"
+#include "xpm/showfilesonlyina.xpm"
+#include "xpm/showfilesonlyinb.xpm"
+#include "xpm/showfilesonlyinc.xpm"
+ DirectoryMergeWindow* p = this;
+
+ m_pDirStartOperation = new KAction(i18n("Start/Continue Directory Merge"), Qt::Key_F7, p, SLOT(slotRunOperationForAllItems()), ac, "dir_start_operation");
+ m_pDirRunOperationForCurrentItem = new KAction(i18n("Run Operation for Current Item"), Qt::Key_F6, p, SLOT(slotRunOperationForCurrentItem()), ac, "dir_run_operation_for_current_item");
+ m_pDirCompareCurrent = new KAction(i18n("Compare Selected File"), 0, p, SLOT(compareCurrentFile()), ac, "dir_compare_current");
+ m_pDirMergeCurrent = new KAction(i18n("Merge Current File"), QIconSet(QPixmap(startmerge)), 0, pKDiff3App, SLOT(slotMergeCurrentFile()), ac, "merge_current");
+ m_pDirFoldAll = new KAction(i18n("Fold All Subdirs"), 0, p, SLOT(slotFoldAllSubdirs()), ac, "dir_fold_all");
+ m_pDirUnfoldAll = new KAction(i18n("Unfold All Subdirs"), 0, p, SLOT(slotUnfoldAllSubdirs()), ac, "dir_unfold_all");
+ m_pDirRescan = new KAction(i18n("Rescan"), Qt::SHIFT+Qt::Key_F5, p, SLOT(reload()), ac, "dir_rescan");
+ m_pDirSaveMergeState = 0; //new KAction(i18n("Save Directory Merge State ..."), 0, p, SLOT(slotSaveMergeState()), ac, "dir_save_merge_state");
+ m_pDirLoadMergeState = 0; //new KAction(i18n("Load Directory Merge State ..."), 0, p, SLOT(slotLoadMergeState()), ac, "dir_load_merge_state");
+ m_pDirChooseAEverywhere = new KAction(i18n("Choose A for All Items"), 0, p, SLOT(slotChooseAEverywhere()), ac, "dir_choose_a_everywhere");
+ m_pDirChooseBEverywhere = new KAction(i18n("Choose B for All Items"), 0, p, SLOT(slotChooseBEverywhere()), ac, "dir_choose_b_everywhere");
+ m_pDirChooseCEverywhere = new KAction(i18n("Choose C for All Items"), 0, p, SLOT(slotChooseCEverywhere()), ac, "dir_choose_c_everywhere");
+ m_pDirAutoChoiceEverywhere = new KAction(i18n("Auto-Choose Operation for All Items"), 0, p, SLOT(slotAutoChooseEverywhere()), ac, "dir_autochoose_everywhere");
+ m_pDirDoNothingEverywhere = new KAction(i18n("No Operation for All Items"), 0, p, SLOT(slotNoOpEverywhere()), ac, "dir_nothing_everywhere");
+
+// m_pDirSynchronizeDirectories = new KToggleAction(i18n("Synchronize Directories"), 0, this, SLOT(slotSynchronizeDirectories()), ac, "dir_synchronize_directories");
+// m_pDirChooseNewerFiles = new KToggleAction(i18n("Copy Newer Files Instead of Merging"), 0, this, SLOT(slotChooseNewerFiles()), ac, "dir_choose_newer_files");
+
+ m_pDirShowIdenticalFiles = new KToggleAction(i18n("Show Identical Files"), QIconSet(QPixmap(showequalfiles)), 0, this, SLOT(slotShowIdenticalFiles()), ac, "dir_show_identical_files");
+ m_pDirShowDifferentFiles = new KToggleAction(i18n("Show Different Files"), 0, this, SLOT(slotShowDifferentFiles()), ac, "dir_show_different_files");
+ m_pDirShowFilesOnlyInA = new KToggleAction(i18n("Show Files only in A"), QIconSet(QPixmap(showfilesonlyina)), 0, this, SLOT(slotShowFilesOnlyInA()), ac, "dir_show_files_only_in_a");
+ m_pDirShowFilesOnlyInB = new KToggleAction(i18n("Show Files only in B"), QIconSet(QPixmap(showfilesonlyinb)), 0, this, SLOT(slotShowFilesOnlyInB()), ac, "dir_show_files_only_in_b");
+ m_pDirShowFilesOnlyInC = new KToggleAction(i18n("Show Files only in C"), QIconSet(QPixmap(showfilesonlyinc)), 0, this, SLOT(slotShowFilesOnlyInC()), ac, "dir_show_files_only_in_c");
+
+ m_pDirShowIdenticalFiles->setChecked( m_pOptions->m_bDmShowIdenticalFiles );
+
+ m_pDirCompareExplicit = new KAction(i18n("Compare Explicitly Selected Files"), 0, p, SLOT(slotCompareExplicitlySelectedFiles()), ac, "dir_compare_explicitly_selected_files");
+ m_pDirMergeExplicit = new KAction(i18n("Merge Explicitly Selected Files"), 0, p, SLOT(slotMergeExplicitlySelectedFiles()), ac, "dir_merge_explicitly_selected_files");
+
+ m_pDirCurrentDoNothing = new KAction(i18n("Do Nothing"), 0, p, SLOT(slotCurrentDoNothing()), ac, "dir_current_do_nothing");
+ m_pDirCurrentChooseA = new KAction(i18n("A"), 0, p, SLOT(slotCurrentChooseA()), ac, "dir_current_choose_a");
+ m_pDirCurrentChooseB = new KAction(i18n("B"), 0, p, SLOT(slotCurrentChooseB()), ac, "dir_current_choose_b");
+ m_pDirCurrentChooseC = new KAction(i18n("C"), 0, p, SLOT(slotCurrentChooseC()), ac, "dir_current_choose_c");
+ m_pDirCurrentMerge = new KAction(i18n("Merge"), 0, p, SLOT(slotCurrentMerge()), ac, "dir_current_merge");
+ m_pDirCurrentDelete = new KAction(i18n("Delete (if exists)"), 0, p, SLOT(slotCurrentDelete()), ac, "dir_current_delete");
+
+ m_pDirCurrentSyncDoNothing = new KAction(i18n("Do Nothing"), 0, p, SLOT(slotCurrentDoNothing()), ac, "dir_current_sync_do_nothing");
+ m_pDirCurrentSyncCopyAToB = new KAction(i18n("Copy A to B"), 0, p, SLOT(slotCurrentCopyAToB()), ac, "dir_current_sync_copy_a_to_b" );
+ m_pDirCurrentSyncCopyBToA = new KAction(i18n("Copy B to A"), 0, p, SLOT(slotCurrentCopyBToA()), ac, "dir_current_sync_copy_b_to_a" );
+ m_pDirCurrentSyncDeleteA = new KAction(i18n("Delete A"), 0, p, SLOT(slotCurrentDeleteA()), ac,"dir_current_sync_delete_a");
+ m_pDirCurrentSyncDeleteB = new KAction(i18n("Delete B"), 0, p, SLOT(slotCurrentDeleteB()), ac,"dir_current_sync_delete_b");
+ m_pDirCurrentSyncDeleteAAndB = new KAction(i18n("Delete A && B"), 0, p, SLOT(slotCurrentDeleteAAndB()), ac,"dir_current_sync_delete_a_and_b");
+ m_pDirCurrentSyncMergeToA = new KAction(i18n("Merge to A"), 0, p, SLOT(slotCurrentMergeToA()), ac,"dir_current_sync_merge_to_a");
+ m_pDirCurrentSyncMergeToB = new KAction(i18n("Merge to B"), 0, p, SLOT(slotCurrentMergeToB()), ac,"dir_current_sync_merge_to_b");
+ m_pDirCurrentSyncMergeToAAndB = new KAction(i18n("Merge to A && B"), 0, p, SLOT(slotCurrentMergeToAAndB()), ac,"dir_current_sync_merge_to_a_and_b");
+
+
+}
+
+
+void DirectoryMergeWindow::updateAvailabilities( bool bDirCompare, bool bDiffWindowVisible,
+ KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC )
+{
+ m_pDirStartOperation->setEnabled( bDirCompare );
+ m_pDirRunOperationForCurrentItem->setEnabled( bDirCompare );
+ m_pDirFoldAll->setEnabled( bDirCompare );
+ m_pDirUnfoldAll->setEnabled( bDirCompare );
+
+ m_pDirCompareCurrent->setEnabled( bDirCompare && isVisible() && isFileSelected() );
+
+ m_pDirMergeCurrent->setEnabled( bDirCompare && isVisible() && isFileSelected()
+ || bDiffWindowVisible );
+
+ m_pDirRescan->setEnabled( bDirCompare );
+
+ m_pDirAutoChoiceEverywhere->setEnabled( bDirCompare && isVisible() );
+ m_pDirDoNothingEverywhere->setEnabled( bDirCompare && isVisible() );
+ m_pDirChooseAEverywhere->setEnabled( bDirCompare && isVisible() );
+ m_pDirChooseBEverywhere->setEnabled( bDirCompare && isVisible() );
+ m_pDirChooseCEverywhere->setEnabled( bDirCompare && isVisible() );
+
+ bool bThreeDirs = m_dirC.isValid();
+
+ QListViewItem* lvi = currentItem();
+ DirMergeItem* pDMI = lvi==0 ? 0 : static_cast<DirMergeItem*>(lvi);
+ MergeFileInfos* pMFI = pDMI==0 ? 0 : pDMI->m_pMFI;
+
+ bool bItemActive = bDirCompare && isVisible() && pMFI!=0;// && hasFocus();
+ bool bMergeMode = bThreeDirs || !m_bSyncMode;
+ bool bFTConflict = pMFI==0 ? false : conflictingFileTypes(*pMFI);
+
+ bool bDirWindowHasFocus = isVisible() && hasFocus();
+
+ m_pDirShowIdenticalFiles->setEnabled( bDirCompare && isVisible() );
+ m_pDirShowDifferentFiles->setEnabled( bDirCompare && isVisible() );
+ m_pDirShowFilesOnlyInA->setEnabled( bDirCompare && isVisible() );
+ m_pDirShowFilesOnlyInB->setEnabled( bDirCompare && isVisible() );
+ m_pDirShowFilesOnlyInC->setEnabled( bDirCompare && isVisible() && bThreeDirs );
+
+ m_pDirCompareExplicit->setEnabled( bDirCompare && isVisible() && m_pSelection2Item!=0 );
+ m_pDirMergeExplicit->setEnabled( bDirCompare && isVisible() && m_pSelection2Item!=0 );
+
+ m_pDirCurrentDoNothing->setEnabled( bItemActive && bMergeMode );
+ m_pDirCurrentChooseA->setEnabled( bItemActive && bMergeMode && pMFI->m_bExistsInA );
+ m_pDirCurrentChooseB->setEnabled( bItemActive && bMergeMode && pMFI->m_bExistsInB );
+ m_pDirCurrentChooseC->setEnabled( bItemActive && bMergeMode && pMFI->m_bExistsInC );
+ m_pDirCurrentMerge->setEnabled( bItemActive && bMergeMode && !bFTConflict );
+ m_pDirCurrentDelete->setEnabled( bItemActive && bMergeMode );
+ if ( bDirWindowHasFocus )
+ {
+ chooseA->setEnabled( bItemActive && pMFI->m_bExistsInA );
+ chooseB->setEnabled( bItemActive && pMFI->m_bExistsInB );
+ chooseC->setEnabled( bItemActive && pMFI->m_bExistsInC );
+ chooseA->setChecked( false );
+ chooseB->setChecked( false );
+ chooseC->setChecked( false );
+ }
+
+ m_pDirCurrentSyncDoNothing->setEnabled( bItemActive && !bMergeMode );
+ m_pDirCurrentSyncCopyAToB->setEnabled( bItemActive && !bMergeMode && pMFI->m_bExistsInA );
+ m_pDirCurrentSyncCopyBToA->setEnabled( bItemActive && !bMergeMode && pMFI->m_bExistsInB );
+ m_pDirCurrentSyncDeleteA->setEnabled( bItemActive && !bMergeMode && pMFI->m_bExistsInA );
+ m_pDirCurrentSyncDeleteB->setEnabled( bItemActive && !bMergeMode && pMFI->m_bExistsInB );
+ m_pDirCurrentSyncDeleteAAndB->setEnabled( bItemActive && !bMergeMode && pMFI->m_bExistsInB && pMFI->m_bExistsInB );
+ m_pDirCurrentSyncMergeToA->setEnabled( bItemActive && !bMergeMode && !bFTConflict );
+ m_pDirCurrentSyncMergeToB->setEnabled( bItemActive && !bMergeMode && !bFTConflict );
+ m_pDirCurrentSyncMergeToAAndB->setEnabled( bItemActive && !bMergeMode && !bFTConflict );
+}
+
+
+#include "directorymergewindow.moc"
diff --git a/src/directorymergewindow.h b/src/directorymergewindow.h
new file mode 100644
index 0000000..77b09fd
--- /dev/null
+++ b/src/directorymergewindow.h
@@ -0,0 +1,362 @@
+/***************************************************************************
+ directorymergewindow.h
+ -------------------
+ begin : Sat Oct 19 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 DIRECTORY_MERGE_WINDOW_H
+#define DIRECTORY_MERGE_WINDOW_H
+
+#include <qfileinfo.h>
+#include <qlistview.h>
+#include <qtimer.h>
+#include <qdir.h>
+#include <list>
+#include <map>
+#include "common.h"
+#include "fileaccess.h"
+#include "diff.h" //TotalDiffStatus
+
+class OptionDialog;
+class KIconLoader;
+class StatusInfo;
+class DirectoryMergeInfo;
+class OneDirectoryInfo;
+class QLabel;
+class KAction;
+class KToggleAction;
+class KActionCollection;
+class TotalDiffStatus;
+
+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
+ eConflictingAges // Equal age but files are not!
+};
+
+class DirMergeItem;
+
+enum e_Age { eNew, eMiddle, eOld, eNotThere, eAgeEnd };
+
+class MergeFileInfos
+{
+public:
+ MergeFileInfos(){ m_bEqualAB=false; m_bEqualAC=false; m_bEqualBC=false;
+ m_pDMI=0; m_pParent=0;
+ m_bExistsInA=false;m_bExistsInB=false;m_bExistsInC=false;
+ m_bDirA=false; m_bDirB=false; m_bDirC=false;
+ m_bLinkA=false; m_bLinkB=false; m_bLinkC=false;
+ m_bOperationComplete=false; m_bSimOpComplete = false;
+ m_eMergeOperation=eNoOperation;
+ m_ageA = eNotThere; m_ageB=eNotThere; m_ageC=eNotThere;
+ m_bConflictingAges=false; }
+ bool operator>( const MergeFileInfos& );
+ QString m_subPath;
+
+ bool m_bExistsInA;
+ bool m_bExistsInB;
+ bool m_bExistsInC;
+ bool m_bEqualAB;
+ bool m_bEqualAC;
+ bool m_bEqualBC;
+ DirMergeItem* m_pDMI;
+ MergeFileInfos* m_pParent;
+ e_MergeOperation m_eMergeOperation;
+ void setMergeOperation( e_MergeOperation eMOp, bool bRecursive=true );
+ bool m_bDirA;
+ bool m_bDirB;
+ bool m_bDirC;
+ bool m_bLinkA;
+ bool m_bLinkB;
+ bool m_bLinkC;
+ bool m_bOperationComplete;
+ bool m_bSimOpComplete;
+ e_Age m_ageA;
+ e_Age m_ageB;
+ e_Age m_ageC;
+ bool m_bConflictingAges; // Equal age but files are not!
+
+ FileAccess m_fileInfoA;
+ FileAccess m_fileInfoB;
+ FileAccess m_fileInfoC;
+
+ TotalDiffStatus m_totalDiffStatus;
+};
+
+class DirMergeItem : public QListViewItem
+{
+public:
+ DirMergeItem( QListView* pParent, const QString&, MergeFileInfos*);
+ DirMergeItem( DirMergeItem* pParent, const QString&, MergeFileInfos*);
+ ~DirMergeItem();
+ MergeFileInfos* m_pMFI;
+ virtual int compare(QListViewItem *i, int col, bool ascending) const;
+ virtual void paintCell(QPainter * p, const QColorGroup & cg, int column, int width, int align );
+ void init(MergeFileInfos* pMFI);
+};
+
+class DirectoryMergeWindow : public QListView
+{
+ Q_OBJECT
+public:
+ DirectoryMergeWindow( QWidget* pParent, OptionDialog* pOptions, KIconLoader* pIconLoader );
+ ~DirectoryMergeWindow();
+ void setDirectoryMergeInfo(DirectoryMergeInfo* p){ m_pDirectoryMergeInfo=p; }
+ bool init(
+ FileAccess& dirA,
+ FileAccess& dirB,
+ FileAccess& dirC,
+ FileAccess& dirDest,
+ bool bDirectoryMerge,
+ bool bReload = false
+ );
+ bool isFileSelected();
+ void allowResizeEvents(bool bAllowResizeEvents);
+ bool isDirectoryMergeInProgress() { return m_bRealMergeStarted; }
+ int totalColumnWidth();
+ bool isSyncMode() { return m_bSyncMode; }
+ bool isScanning() { return m_bScanning; }
+ void initDirectoryMergeActions( QObject* pKDiff3App, KActionCollection* ac );
+ void updateAvailabilities( bool bDirCompare, bool bDiffWindowVisible,
+ KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC );
+ void updateFileVisibilities();
+
+ virtual void keyPressEvent( QKeyEvent* e );
+ virtual void focusInEvent( QFocusEvent* e );
+ virtual void focusOutEvent( QFocusEvent* e );
+
+ QString getDirNameA(){ return m_dirA.prettyAbsPath(); }
+ QString getDirNameB(){ return m_dirB.prettyAbsPath(); }
+ QString getDirNameC(){ return m_dirC.prettyAbsPath(); }
+ QString getDirNameDest(){ return m_dirDest.prettyAbsPath(); }
+
+public 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();
+
+protected:
+ void mergeContinue( bool bStart, bool bVerbose );
+ void resizeEvent(QResizeEvent* e);
+ bool m_bAllowResizeEvents;
+
+ void prepareListView(ProgressProxy& pp);
+ void calcSuggestedOperation( MergeFileInfos& mfi, e_MergeOperation eDefaultOperation );
+ void setAllMergeOperations( e_MergeOperation eDefaultOperation );
+ friend class MergeFileInfos;
+
+ bool canContinue();
+ void prepareMergeStart( QListViewItem* pBegin, QListViewItem* pEnd, bool bVerbose );
+ bool executeMergeOperation( MergeFileInfos& mfi, bool& bSingleFileMerge );
+
+ void scanDirectory( const QString& dirName, t_DirectoryList& dirList );
+ void scanLocalDirectory( const QString& dirName, t_DirectoryList& dirList );
+ void fastFileComparison( FileAccess& fi1, FileAccess& fi2,
+ bool& bEqual, bool& bError, QString& status );
+ void compareFilesAndCalcAges( MergeFileInfos& mfi );
+
+ QString fullNameA( const MergeFileInfos& mfi )
+ { return mfi.m_bExistsInA ? mfi.m_fileInfoA.absFilePath() : m_dirA.absFilePath() + "/" + mfi.m_subPath; }
+ QString fullNameB( const MergeFileInfos& mfi )
+ { return mfi.m_bExistsInB ? mfi.m_fileInfoB.absFilePath() : m_dirB.absFilePath() + "/" + mfi.m_subPath; }
+ QString fullNameC( const MergeFileInfos& mfi )
+ { return mfi.m_bExistsInC ? mfi.m_fileInfoC.absFilePath() : m_dirC.absFilePath() + "/" + mfi.m_subPath; }
+ QString fullNameDest( const MergeFileInfos& mfi )
+ { if ( m_dirDestInternal.prettyAbsPath() == m_dirC.prettyAbsPath() ) return fullNameC(mfi);
+ else if ( m_dirDestInternal.prettyAbsPath() == m_dirB.prettyAbsPath() ) return fullNameB(mfi);
+ else return m_dirDestInternal.absFilePath() + "/" + mfi.m_subPath;
+ }
+
+ 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 );
+
+ FileAccess m_dirA;
+ FileAccess m_dirB;
+ FileAccess m_dirC;
+ FileAccess m_dirDest;
+ FileAccess m_dirDestInternal;
+
+ QString m_dirMergeStateFilename;
+
+ std::map<QString, MergeFileInfos> 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_bScanning; // true while in init()
+
+ OptionDialog* m_pOptions;
+ KIconLoader* m_pIconLoader;
+ DirectoryMergeInfo* m_pDirectoryMergeInfo;
+ StatusInfo* m_pStatusInfo;
+
+ typedef std::list<DirMergeItem*> MergeItemList;
+ MergeItemList m_mergeItemList;
+ MergeItemList::iterator m_currentItemForOperation;
+
+ DirMergeItem* m_pSelection1Item;
+ int m_selection1Column;
+ DirMergeItem* m_pSelection2Item;
+ int m_selection2Column;
+ DirMergeItem* m_pSelection3Item;
+ int m_selection3Column;
+ void selectItemAndColumn(DirMergeItem* pDMI, int c, bool bContextMenu);
+ friend class DirMergeItem;
+
+ KAction* m_pDirStartOperation;
+ KAction* m_pDirRunOperationForCurrentItem;
+ KAction* m_pDirCompareCurrent;
+ KAction* m_pDirMergeCurrent;
+ KAction* m_pDirRescan;
+ KAction* m_pDirChooseAEverywhere;
+ KAction* m_pDirChooseBEverywhere;
+ KAction* m_pDirChooseCEverywhere;
+ KAction* m_pDirAutoChoiceEverywhere;
+ KAction* m_pDirDoNothingEverywhere;
+ KAction* m_pDirFoldAll;
+ KAction* m_pDirUnfoldAll;
+
+ KToggleAction* m_pDirShowIdenticalFiles;
+ KToggleAction* m_pDirShowDifferentFiles;
+ KToggleAction* m_pDirShowFilesOnlyInA;
+ KToggleAction* m_pDirShowFilesOnlyInB;
+ KToggleAction* m_pDirShowFilesOnlyInC;
+
+ KToggleAction* m_pDirSynchronizeDirectories;
+ KToggleAction* m_pDirChooseNewerFiles;
+
+ KAction* m_pDirCompareExplicit;
+ KAction* m_pDirMergeExplicit;
+
+ KAction* m_pDirCurrentDoNothing;
+ KAction* m_pDirCurrentChooseA;
+ KAction* m_pDirCurrentChooseB;
+ KAction* m_pDirCurrentChooseC;
+ KAction* m_pDirCurrentMerge;
+ KAction* m_pDirCurrentDelete;
+
+ KAction* m_pDirCurrentSyncDoNothing;
+ KAction* m_pDirCurrentSyncCopyAToB;
+ KAction* m_pDirCurrentSyncCopyBToA;
+ KAction* m_pDirCurrentSyncDeleteA;
+ KAction* m_pDirCurrentSyncDeleteB;
+ KAction* m_pDirCurrentSyncDeleteAAndB;
+ KAction* m_pDirCurrentSyncMergeToA;
+ KAction* m_pDirCurrentSyncMergeToB;
+ KAction* m_pDirCurrentSyncMergeToAAndB;
+
+ KAction* m_pDirSaveMergeState;
+ KAction* m_pDirLoadMergeState;
+signals:
+ void startDiffMerge(QString fn1,QString fn2, QString fn3, QString ofn, QString,QString,QString,TotalDiffStatus*);
+ void checkIfCanContinue( bool* pbContinue );
+ void updateAvailabilities();
+ void statusBarMessage( const QString& msg );
+protected slots:
+ void onDoubleClick( QListViewItem* lvi );
+ void onClick( int button, QListViewItem* lvi, const QPoint&, int c );
+ void slotShowContextMenu(QListViewItem* lvi,const QPoint &,int c);
+ void onSelectionChanged(QListViewItem* lvi);
+};
+
+class DirectoryMergeInfo : public QFrame
+{
+ Q_OBJECT
+public:
+ DirectoryMergeInfo( QWidget* pParent );
+ void setInfo(
+ const FileAccess& APath,
+ const FileAccess& BPath,
+ const FileAccess& CPath,
+ const FileAccess& DestPath,
+ MergeFileInfos& mfi );
+ QListView* getInfoList() {return m_pInfoList;}
+ virtual bool eventFilter( QObject* o, QEvent* e );
+signals:
+ void gotFocus();
+private:
+ QLabel* m_pInfoA;
+ QLabel* m_pInfoB;
+ QLabel* m_pInfoC;
+ QLabel* m_pInfoDest;
+
+ QLabel* m_pA;
+ QLabel* m_pB;
+ QLabel* m_pC;
+ QLabel* m_pDest;
+
+ QListView* m_pInfoList;
+};
+
+
+#endif
diff --git a/src/fileaccess.cpp b/src/fileaccess.cpp
new file mode 100644
index 0000000..8d4ce17
--- /dev/null
+++ b/src/fileaccess.cpp
@@ -0,0 +1,1809 @@
+/***************************************************************************
+ * Copyright (C) 2003 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 "fileaccess.h"
+#include <iostream>
+#include <cstdlib>
+#include <kio/global.h>
+#include <kmessagebox.h>
+#include "optiondialog.h"
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qapplication.h>
+#include <qpushbutton.h>
+
+#include <qeventloop.h>
+
+#include "common.h"
+#include <ktempfile.h>
+#include <qdir.h>
+#include <qregexp.h>
+#include <qtextstream.h>
+#include <vector>
+#include <klocale.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+#include <sys/utime.h>
+#include <io.h>
+#include <windows.h>
+#include <process.h>
+#else
+#include <unistd.h> // Needed for creating symbolic links via symlink().
+#include <utime.h>
+#endif
+
+
+ProgressDialog* g_pProgressDialog=0;
+
+
+FileAccess::FileAccess( const QString& name, bool bWantToWrite )
+{
+ setFile( name, bWantToWrite );
+}
+
+FileAccess::FileAccess()
+{
+ m_bValidData = false;
+ m_size = 0;
+ m_creationTime = QDateTime();
+ m_accessTime = QDateTime();
+ m_modificationTime = QDateTime();
+ m_bReadable = false;
+ m_bWritable = false;
+ m_bExecutable = false;
+ m_bLocal = false;
+ m_bHidden = false;
+ m_bExists = false;
+ m_bFile = false;
+ m_bDir = false;
+ m_bSymLink = false;
+}
+
+FileAccess::~FileAccess()
+{
+ if( !m_localCopy.isEmpty() )
+ {
+ removeTempFile( m_localCopy );
+ }
+}
+
+void FileAccess::setFile( const QString& name, bool bWantToWrite )
+{
+ m_url = KURL::fromPathOrURL( name );
+ m_bValidData = false;
+
+ m_size = 0;
+ m_creationTime = QDateTime();
+ m_accessTime = QDateTime();
+ m_modificationTime = QDateTime();
+ m_bReadable = false;
+ m_bWritable = false;
+ m_bExecutable = false;
+ m_bHidden = false;
+ m_bExists = false;
+ m_bFile = false;
+ m_bDir = false;
+ m_bSymLink = false;
+ m_linkTarget = "";
+ m_fileType = -1;
+ m_bLocal = true;
+
+ // Note: Checking if the filename-string is empty is necessary for Win95/98/ME.
+ // The isFile() / isDir() queries would cause the program to crash.
+ // (This is a Win95-bug which has been corrected only in WinNT/2000/XP.)
+ if ( !name.isEmpty() )
+ {
+ // FileAccess tries to detect if the given name is an URL or a local file.
+ // This is a problem if the filename looks like an URL (i.e. contains a colon ':').
+ // e.g. "file:f.txt" is a valid filename.
+ // Most of the time it is sufficient to check if the file exists locally.
+ // 2 Problems remain:
+ // 1. When the local file exists and the remote location is wanted nevertheless. (unlikely)
+ // 2. When the local file doesn't exist and should be written to.
+
+ bool bExistsLocal = QDir().exists(name);
+ if ( m_url.isLocalFile() || !m_url.isValid() || bExistsLocal ) // assuming that invalid means relative
+ {
+ QString localName = name;
+ if ( !bExistsLocal && m_url.isLocalFile() && name.left(5).lower()=="file:" )
+ {
+ localName = m_url.path(); // I want the path without preceding "file:"
+ }
+ QFileInfo fi( localName );
+#if defined(Q_WS_WIN)
+ // On some windows machines in a network this takes very long.
+ // and it's not so important anyway.
+ m_bReadable = true;
+ m_bWritable = true; // in certain situations this might become a problem though
+ m_bExecutable = false;
+#else
+ m_bReadable = fi.isReadable();
+ m_bWritable = fi.isWritable();
+ m_bExecutable = fi.isExecutable();
+#endif
+ m_creationTime = fi.created();
+ m_bHidden = fi.isHidden();
+ m_modificationTime = fi.lastModified();
+ m_accessTime = fi.lastRead();
+ m_size = fi.size();
+ m_bSymLink = fi.isSymLink();
+ m_bFile = fi.isFile();
+ m_bDir = fi.isDir();
+ m_bExists = fi.exists();
+ m_name = fi.fileName();
+ m_path = fi.filePath();
+ m_absFilePath= fi.absFilePath();
+ if ( m_bSymLink ) m_linkTarget = fi.readLink();
+ m_bLocal = true;
+ m_bValidData = true;
+ if ( ! m_url.isValid() )
+ {
+ m_url.setPath( m_absFilePath );
+ }
+
+ if ( !m_bExists && m_absFilePath.contains("@@") )
+ {
+ // Try reading a clearcase file
+ m_localCopy = FileAccess::tempFileName();
+ QString cmd = "cleartool get -to \"" + m_localCopy + "\" \"" + m_absFilePath + "\"";
+ ::system( cmd.local8Bit() );
+
+ QFileInfo fi( m_localCopy );
+#if defined(Q_WS_WIN)
+ m_bReadable = true;//fi.isReadable();
+ m_bWritable = true;//fi.isWritable();
+ m_bExecutable = false;//fi.isExecutable();
+#else
+ m_bReadable = fi.isReadable();
+ m_bWritable = fi.isWritable();
+ m_bExecutable = fi.isExecutable();
+#endif
+ m_creationTime = fi.created();
+ m_bHidden = fi.isHidden();
+ m_modificationTime = fi.lastModified();
+ m_accessTime = fi.lastRead();
+ m_size = fi.size();
+ m_bSymLink = fi.isSymLink();
+ m_bFile = fi.isFile();
+ m_bDir = fi.isDir();
+ m_bExists = fi.exists();
+ }
+ }
+ else
+ {
+ m_absFilePath = name;
+ m_name = m_url.fileName();
+ m_bLocal = false;
+
+ FileAccessJobHandler jh( this ); // A friend, which writes to the parameters of this class!
+ jh.stat(2/*all details*/, bWantToWrite); // returns bSuccess, ignored
+
+ m_path = name;
+ 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::addPath( const QString& txt )
+{
+ if ( m_url.isValid() )
+ {
+ m_url.addPath( txt );
+ setFile( m_url.url() ); // reinitialise
+ }
+ else
+ {
+ QString slash = (txt.isEmpty() || txt[0]=='/') ? "" : "/";
+ setFile( absFilePath() + 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 permisson
+ S_IXOTH 00001 others have execute permission
+*/
+
+#ifdef KREPLACEMENTS_H
+void FileAccess::setUdsEntry( const KIO::UDSEntry& ){} // not needed if KDE is not available
+#else
+void FileAccess::setUdsEntry( const KIO::UDSEntry& e )
+{
+ KIO::UDSEntry::const_iterator ei;
+ long acc = 0;
+ long fileType = 0;
+ for( ei=e.begin(); ei!=e.end(); ++ei )
+ {
+ const KIO::UDSAtom& a = *ei;
+ switch( a.m_uds )
+ {
+ case KIO::UDS_SIZE : m_size = a.m_long; break;
+ case KIO::UDS_USER : m_user = a.m_str; break;
+ case KIO::UDS_GROUP : m_group = a.m_str; break;
+ case KIO::UDS_NAME : m_path = a.m_str; break; // During listDir the relative path is given here.
+ case KIO::UDS_MODIFICATION_TIME : m_modificationTime.setTime_t( a.m_long ); break;
+ case KIO::UDS_ACCESS_TIME : m_accessTime.setTime_t( a.m_long ); break;
+ case KIO::UDS_CREATION_TIME : m_creationTime.setTime_t( a.m_long ); break;
+ case KIO::UDS_LINK_DEST : m_linkTarget = a.m_str; break;
+ case KIO::UDS_ACCESS :
+ {
+ acc = a.m_long;
+ m_bReadable = (acc & S_IRUSR)!=0;
+ m_bWritable = (acc & S_IWUSR)!=0;
+ m_bExecutable = (acc & S_IXUSR)!=0;
+ break;
+ }
+ case KIO::UDS_FILE_TYPE :
+ {
+ fileType = a.m_long;
+ m_bDir = ( fileType & S_IFMT ) == S_IFDIR;
+ m_bFile = ( fileType & S_IFMT ) == S_IFREG;
+ m_bSymLink = ( fileType & S_IFMT ) == S_IFLNK;
+ m_bExists = fileType != 0;
+ m_fileType = fileType;
+ break;
+ }
+
+ case KIO::UDS_URL : // m_url = KURL( a.str );
+ break;
+ case KIO::UDS_MIME_TYPE : break;
+ case KIO::UDS_GUESSED_MIME_TYPE : break;
+ case KIO::UDS_XML_PROPERTIES : break;
+ default: break;
+ }
+ }
+
+ m_bExists = acc!=0 || fileType!=0;
+
+ m_bLocal = false;
+ m_bValidData = true;
+ m_bSymLink = !m_linkTarget.isEmpty();
+ if ( m_name.isEmpty() )
+ {
+ int pos = m_path.findRev('/') + 1;
+ m_name = m_path.mid( pos );
+ }
+ m_bHidden = m_name[0]=='.';
+}
+#endif
+
+
+bool FileAccess::isValid() const { return m_bValidData; }
+bool FileAccess::isFile() const { return m_bFile; }
+bool FileAccess::isDir() const { return m_bDir; }
+bool FileAccess::isSymLink() const { return m_bSymLink; }
+bool FileAccess::exists() const { return m_bExists; }
+long FileAccess::size() const { return m_size; }
+KURL FileAccess::url() const { return m_url; }
+bool FileAccess::isLocal() const { return m_bLocal; }
+bool FileAccess::isReadable() const { return m_bReadable; }
+bool FileAccess::isWritable() const { return m_bWritable; }
+bool FileAccess::isExecutable() const { return m_bExecutable; }
+bool FileAccess::isHidden() const { return m_bHidden; }
+QString FileAccess::readLink() const { return m_linkTarget; }
+QString FileAccess::absFilePath() const{ return m_absFilePath; } // Full abs path
+QString FileAccess::fileName() const { return m_name; } // Just the name-part of the path, without parent directories
+QString FileAccess::filePath() const { return m_path; } // The path-string that was used during construction
+QString FileAccess::prettyAbsPath() const { return isLocal() ? m_absFilePath : m_url.prettyURL(); }
+
+QDateTime FileAccess::created() const
+{
+ return ( m_creationTime.isValid() ? m_creationTime : m_modificationTime );
+}
+
+QDateTime FileAccess::lastModified() const
+{
+ return m_modificationTime;
+}
+
+QDateTime FileAccess::lastRead() const
+{
+ return ( m_accessTime.isValid() ? m_accessTime : m_modificationTime );
+}
+
+static bool interruptableReadFile( QFile& f, void* pDestBuffer, unsigned long maxLength )
+{
+ ProgressProxy pp;
+ const unsigned long maxChunkSize = 100000;
+ unsigned long i=0;
+ while( i<maxLength )
+ {
+ unsigned long nextLength = min2( maxLength-i, maxChunkSize );
+ unsigned long reallyRead = f.readBlock( (char*)pDestBuffer+i, nextLength );
+ if ( reallyRead != nextLength )
+ {
+ return false;
+ }
+ i+=reallyRead;
+
+ pp.setCurrent( double(i)/maxLength );
+ if ( pp.wasCancelled() ) return false;
+ }
+ return true;
+}
+
+bool FileAccess::readFile( void* pDestBuffer, unsigned long maxLength )
+{
+ if ( !m_localCopy.isEmpty() )
+ {
+ QFile f( m_localCopy );
+ if ( f.open( IO_ReadOnly ) )
+ return interruptableReadFile(f, pDestBuffer, maxLength);// maxLength == f.readBlock( (char*)pDestBuffer, maxLength );
+ }
+ else if (m_bLocal)
+ {
+ QFile f( filePath() );
+
+ if ( f.open( IO_ReadOnly ) )
+ return interruptableReadFile(f, pDestBuffer, maxLength); //maxLength == f.readBlock( (char*)pDestBuffer, maxLength );
+ }
+ else
+ {
+ FileAccessJobHandler jh( this );
+ return jh.get( pDestBuffer, maxLength );
+ }
+ return false;
+}
+
+bool FileAccess::writeFile( const void* pSrcBuffer, unsigned long length )
+{
+ ProgressProxy pp;
+ if (m_bLocal)
+ {
+ QFile f( filePath() );
+ if ( f.open( IO_WriteOnly ) )
+ {
+ const unsigned long maxChunkSize = 100000;
+ unsigned long i=0;
+ while( i<length )
+ {
+ unsigned long nextLength = min2( length-i, maxChunkSize );
+ unsigned long reallyWritten = f.writeBlock( (char*)pSrcBuffer+i, nextLength );
+ if ( reallyWritten != nextLength )
+ {
+ return false;
+ }
+ i+=reallyWritten;
+
+ pp.setCurrent( double(i)/length );
+ if ( pp.wasCancelled() ) return false;
+ }
+ f.close();
+#ifndef _WIN32
+ if ( isExecutable() ) // value is true if the old file was executable
+ {
+ // Preserve attributes
+ struct stat srcFileStatus;
+ int statResult = ::stat( filePath().ascii(), &srcFileStatus );
+ if (statResult==0)
+ {
+ ::chmod ( filePath().ascii(), srcFileStatus.st_mode | S_IXUSR );
+ }
+ }
+#endif
+
+ 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( absFilePath() );
+ }
+ else
+ {
+ FileAccessJobHandler jh( this );
+ return jh.removeFile( absFilePath() );
+ }
+}
+
+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::tempFileName()
+{
+ #ifdef KREPLACEMENTS_H
+
+ QString fileName;
+ #ifdef _WIN32
+ QString tmpDir = getenv("TEMP");
+ #else
+ QString tmpDir = "/tmp";
+ #endif
+ for(int i=0; ;++i)
+ {
+ // short filenames for WIN98 because for system() the command must not exceed 120 characters.
+ #ifdef _WIN32
+ if ( QApplication::winVersion() & Qt::WV_DOS_based ) // Win95, 98, ME
+ fileName = tmpDir + "\\" + QString::number(i);
+ else
+ fileName = tmpDir + "/kdiff3_" + QString::number(_getpid()) + "_" + QString::number(i) +".tmp";
+ #else
+ fileName = tmpDir + "/kdiff3_" + QString::number(getpid()) + "_" + QString::number(i) +".tmp";
+ #endif
+ if ( ! FileAccess::exists(fileName) &&
+ QFile(fileName).open(IO_WriteOnly) ) // open, truncate and close the file, true if successful
+ {
+ break;
+ }
+ }
+ return QDir::convertSeparators(fileName+".2");
+
+ #else // using KDE
+
+ KTempFile tmpFile;
+ //tmpFile.setAutoDelete( true ); // We only want the name. Delete the precreated file immediately.
+ tmpFile.close();
+ return tmpFile.name()+".2";
+
+ #endif
+}
+
+bool FileAccess::removeTempFile( const QString& name ) // static
+{
+ if (name.endsWith(".2"))
+ FileAccess(name.left(name.length()-2)).removeFile();
+ return FileAccess(name).removeFile();
+}
+
+
+bool FileAccess::makeDir( const QString& dirName )
+{
+ FileAccessJobHandler fh(0);
+ return fh.mkDir( dirName );
+}
+
+bool FileAccess::removeDir( const QString& dirName )
+{
+ FileAccessJobHandler fh(0);
+ return fh.rmDir( dirName );
+}
+
+bool FileAccess::symLink( const QString& linkTarget, const QString& linkLocation )
+{
+#ifdef _WIN32
+ return false;
+#else
+ return 0==::symlink( linkTarget.ascii(), linkLocation.ascii() );
+ //FileAccessJobHandler fh(0);
+ //return fh.symLink( linkTarget, linkLocation );
+#endif
+}
+
+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.
+long FileAccess::sizeForReading()
+{
+ if ( m_size == 0 && !isLocal() )
+ {
+ // Size couldn't be determined. Copy the file to a local temp place.
+ QString localCopy = tempFileName();
+ bool bSuccess = copyFile( localCopy );
+ if ( bSuccess )
+ {
+ QFileInfo fi( localCopy );
+ m_size = fi.size();
+ m_localCopy = localCopy;
+ return m_size;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ return m_size;
+}
+
+QString FileAccess::getStatusText()
+{
+ return m_statusText;
+}
+
+QString FileAccess::cleanDirPath( const QString& path ) // static
+{
+ KURL url(path);
+ if ( url.isLocalFile() || ! url.isValid() )
+ {
+ return QDir().cleanDirPath( 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 = absFilePath() + bakExtension;
+ FileAccess bakFile( bakName, true /*bWantToWrite*/ );
+ if ( bakFile.exists() )
+ {
+ bool bSuccess = bakFile.removeFile();
+ if ( !bSuccess )
+ {
+ m_statusText = i18n("While trying to make a backup, deleting an older backup failed. \nFilename: ") + bakName;
+ return false;
+ }
+ }
+ bool bSuccess = rename( bakName );
+ if (!bSuccess)
+ {
+ m_statusText = i18n("While trying to make a backup, renaming failed. \nFilenames: ") +
+ absFilePath() + " -> " + bakName;
+ return false;
+ }
+ }
+ return true;
+}
+
+FileAccessJobHandler::FileAccessJobHandler( FileAccess* pFileAccess )
+{
+ m_pFileAccess = pFileAccess;
+ m_bSuccess = false;
+}
+
+bool FileAccessJobHandler::stat( int detail, bool bWantToWrite )
+{
+ m_bSuccess = false;
+ m_pFileAccess->m_statusText = QString();
+ KIO::StatJob* pStatJob = KIO::stat( m_pFileAccess->m_url, ! bWantToWrite, detail, false );
+
+ connect( pStatJob, SIGNAL(result(KIO::Job*)), this, SLOT(slotStatResult(KIO::Job*)));
+
+ g_pProgressDialog->enterEventLoop( pStatJob, i18n("Getting file status: %1").arg(m_pFileAccess->prettyAbsPath()) );
+
+ return m_bSuccess;
+}
+
+void FileAccessJobHandler::slotStatResult(KIO::Job* pJob)
+{
+ if ( pJob->error() )
+ {
+ //pJob->showErrorDialog(g_pProgressDialog);
+ m_pFileAccess->m_bExists = false;
+ m_bSuccess = true;
+ }
+ else
+ {
+ m_bSuccess = true;
+
+ m_pFileAccess->m_bValidData = true;
+ const KIO::UDSEntry e = static_cast<KIO::StatJob*>(pJob)->statResult();
+
+ m_pFileAccess->setUdsEntry( e );
+ }
+
+ g_pProgressDialog->exitEventLoop();
+}
+
+
+bool FileAccessJobHandler::get(void* pDestBuffer, long maxLength )
+{
+ ProgressProxy pp; // Implicitly used in slotPercent()
+ if ( maxLength>0 && !pp.wasCancelled() )
+ {
+ KIO::TransferJob* pJob = KIO::get( m_pFileAccess->m_url, false /*reload*/, false );
+ m_transferredBytes = 0;
+ m_pTransferBuffer = (char*)pDestBuffer;
+ m_maxLength = maxLength;
+ m_bSuccess = false;
+ m_pFileAccess->m_statusText = QString();
+
+ connect( pJob, SIGNAL(result(KIO::Job*)), this, SLOT(slotSimpleJobResult(KIO::Job*)));
+ connect( pJob, SIGNAL(data(KIO::Job*,const QByteArray &)), this, SLOT(slotGetData(KIO::Job*, const QByteArray&)));
+ connect( pJob, SIGNAL(percent(KIO::Job*,unsigned long)), this, SLOT(slotPercent(KIO::Job*, unsigned long)));
+
+ g_pProgressDialog->enterEventLoop( pJob, i18n("Reading file: %1").arg(m_pFileAccess->prettyAbsPath()) );
+ return m_bSuccess;
+ }
+ else
+ return true;
+}
+
+void FileAccessJobHandler::slotGetData( KIO::Job* pJob, const QByteArray& newData )
+{
+ if ( pJob->error() )
+ {
+ pJob->showErrorDialog(g_pProgressDialog);
+ }
+ else
+ {
+ long length = min2( long(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 )
+{
+ if ( maxLength>0 )
+ {
+ KIO::TransferJob* pJob = KIO::put( m_pFileAccess->m_url, permissions, bOverwrite, bResume, false );
+ m_transferredBytes = 0;
+ m_pTransferBuffer = (char*)pSrcBuffer;
+ m_maxLength = maxLength;
+ m_bSuccess = false;
+ m_pFileAccess->m_statusText = QString();
+
+ connect( pJob, SIGNAL(result(KIO::Job*)), this, SLOT(slotPutJobResult(KIO::Job*)));
+ connect( pJob, SIGNAL(dataReq(KIO::Job*, QByteArray&)), this, SLOT(slotPutData(KIO::Job*, QByteArray&)));
+ connect( pJob, SIGNAL(percent(KIO::Job*,unsigned long)), this, SLOT(slotPercent(KIO::Job*, unsigned long)));
+
+ g_pProgressDialog->enterEventLoop( pJob, i18n("Writing file: %1").arg(m_pFileAccess->prettyAbsPath()) );
+ return m_bSuccess;
+ }
+ else
+ return true;
+}
+
+void FileAccessJobHandler::slotPutData( KIO::Job* pJob, QByteArray& data )
+{
+ if ( pJob->error() )
+ {
+ pJob->showErrorDialog(g_pProgressDialog);
+ }
+ else
+ {
+ long maxChunkSize = 100000;
+ long length = min2( maxChunkSize, m_maxLength - m_transferredBytes );
+ bool bSuccess = data.resize( length );
+ if ( bSuccess )
+ {
+ if ( length>0 )
+ {
+ ::memcpy( data.data(), m_pTransferBuffer + m_transferredBytes, data.size() );
+ m_transferredBytes += length;
+ }
+ }
+ else
+ {
+ KMessageBox::error( g_pProgressDialog, i18n("Out of memory") );
+ data.resize(0);
+ m_bSuccess = false;
+ }
+ }
+}
+
+void FileAccessJobHandler::slotPutJobResult(KIO::Job* pJob)
+{
+ if ( pJob->error() )
+ {
+ pJob->showErrorDialog(g_pProgressDialog);
+ }
+ else
+ {
+ m_bSuccess = (m_transferredBytes == m_maxLength); // Special success condition
+ }
+ g_pProgressDialog->exitEventLoop(); // Close the dialog, return from exec()
+}
+
+bool FileAccessJobHandler::mkDir( const QString& dirName )
+{
+ KURL dirURL = KURL::fromPathOrURL( dirName );
+ if ( dirName.isEmpty() )
+ return false;
+ else if ( dirURL.isLocalFile() )
+ {
+ return QDir().mkdir( dirURL.path() );
+ }
+ else
+ {
+ m_bSuccess = false;
+ KIO::SimpleJob* pJob = KIO::mkdir( dirURL );
+ connect( pJob, SIGNAL(result(KIO::Job*)), this, SLOT(slotSimpleJobResult(KIO::Job*)));
+
+ g_pProgressDialog->enterEventLoop( pJob, i18n("Making directory: %1").arg(dirName) );
+ return m_bSuccess;
+ }
+}
+
+bool FileAccessJobHandler::rmDir( const QString& dirName )
+{
+ KURL dirURL = KURL::fromPathOrURL( dirName );
+ 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, SIGNAL(result(KIO::Job*)), this, SLOT(slotSimpleJobResult(KIO::Job*)));
+
+ g_pProgressDialog->enterEventLoop(pJob, i18n("Removing directory: %1").arg(dirName));
+ return m_bSuccess;
+ }
+}
+
+bool FileAccessJobHandler::removeFile( const QString& fileName )
+{
+ if ( fileName.isEmpty() )
+ return false;
+ else
+ {
+ m_bSuccess = false;
+ KIO::SimpleJob* pJob = KIO::file_delete( KURL::fromPathOrURL(fileName), false );
+ connect( pJob, SIGNAL(result(KIO::Job*)), this, SLOT(slotSimpleJobResult(KIO::Job*)));
+
+ g_pProgressDialog->enterEventLoop( pJob, i18n("Removing file: %1").arg(fileName) );
+ return m_bSuccess;
+ }
+}
+
+bool FileAccessJobHandler::symLink( const QString& linkTarget, const QString& linkLocation )
+{
+ if ( linkTarget.isEmpty() || linkLocation.isEmpty() )
+ return false;
+ else
+ {
+ m_bSuccess = false;
+ KIO::CopyJob* pJob = KIO::link( KURL::fromPathOrURL(linkTarget), KURL::fromPathOrURL(linkLocation), false );
+ connect( pJob, SIGNAL(result(KIO::Job*)), this, SLOT(slotSimpleJobResult(KIO::Job*)));
+
+ g_pProgressDialog->enterEventLoop( pJob,
+ i18n("Creating symbolic link: %1 -> %2").arg(linkLocation).arg(linkTarget) );
+ return m_bSuccess;
+ }
+}
+
+bool FileAccessJobHandler::rename( const QString& dest )
+{
+ if ( dest.isEmpty() )
+ return false;
+
+ KURL kurl = KURL::fromPathOrURL( dest );
+ if ( !kurl.isValid() )
+ kurl = KURL::fromPathOrURL( QDir().absFilePath(dest) ); // assuming that invalid means relative
+
+ if ( m_pFileAccess->isLocal() && kurl.isLocalFile() )
+ {
+ return QDir().rename( m_pFileAccess->absFilePath(), kurl.path() );
+ }
+ else
+ {
+ bool bOverwrite = false;
+ bool bResume = false;
+ bool bShowProgress = false;
+ int permissions=-1;
+ m_bSuccess = false;
+ KIO::FileCopyJob* pJob = KIO::file_move( m_pFileAccess->m_url, kurl, permissions, bOverwrite, bResume, bShowProgress );
+ connect( pJob, SIGNAL(result(KIO::Job*)), this, SLOT(slotSimpleJobResult(KIO::Job*)));
+ connect( pJob, SIGNAL(percent(KIO::Job*,unsigned long)), this, SLOT(slotPercent(KIO::Job*, unsigned long)));
+
+ g_pProgressDialog->enterEventLoop( pJob,
+ i18n("Renaming file: %1 -> %2").arg(m_pFileAccess->prettyAbsPath()).arg(dest) );
+ return m_bSuccess;
+ }
+}
+
+void FileAccessJobHandler::slotSimpleJobResult(KIO::Job* pJob)
+{
+ if ( pJob->error() )
+ {
+ pJob->showErrorDialog(g_pProgressDialog);
+ }
+ else
+ {
+ m_bSuccess = true;
+ }
+ g_pProgressDialog->exitEventLoop(); // Close the dialog, return from exec()
+}
+
+
+// Copy local or remote files.
+bool FileAccessJobHandler::copyFile( const QString& dest )
+{
+ ProgressProxy pp;
+ KURL destUrl = KURL::fromPathOrURL( dest );
+ m_pFileAccess->m_statusText = QString();
+ if ( ! m_pFileAccess->isLocal() || ! destUrl.isLocalFile() ) // if either url is nonlocal
+ {
+ bool bOverwrite = false;
+ bool bResume = false;
+ bool bShowProgress = false;
+ int permissions = (m_pFileAccess->isExecutable()?0111:0)+(m_pFileAccess->isWritable()?0222:0)+(m_pFileAccess->isReadable()?0444:0);
+ m_bSuccess = false;
+ KIO::FileCopyJob* pJob = KIO::file_copy ( m_pFileAccess->m_url, destUrl, permissions, bOverwrite, bResume, bShowProgress );
+ connect( pJob, SIGNAL(result(KIO::Job*)), this, SLOT(slotSimpleJobResult(KIO::Job*)));
+ connect( pJob, SIGNAL(percent(KIO::Job*,unsigned long)), this, SLOT(slotPercent(KIO::Job*, unsigned long)));
+ g_pProgressDialog->enterEventLoop( pJob,
+ i18n("Copying file: %1 -> %2").arg(m_pFileAccess->prettyAbsPath()).arg(dest) );
+
+ return m_bSuccess;
+ // Note that the KIO-slave preserves the original date, if this is supported.
+ }
+
+ // Both files are local:
+ QString srcName = m_pFileAccess->absFilePath();
+ QString destName = dest;
+ QFile srcFile( srcName );
+ QFile destFile( destName );
+ bool bReadSuccess = srcFile.open( IO_ReadOnly );
+ if ( bReadSuccess == false )
+ {
+ m_pFileAccess->m_statusText = i18n("Error during file copy operation: Opening file for reading failed. Filename: %1").arg(srcName);
+ return false;
+ }
+ bool bWriteSuccess = destFile.open( IO_WriteOnly );
+ if ( bWriteSuccess == false )
+ {
+ m_pFileAccess->m_statusText = i18n("Error during file copy operation: Opening file for writing failed. Filename: %1").arg(destName);
+ return false;
+ }
+
+#if QT_VERSION==230
+ typedef long Q_LONG;
+#endif
+ std::vector<char> buffer(100000);
+ Q_LONG bufSize = buffer.size();
+ Q_LONG srcSize = srcFile.size();
+ while ( srcSize > 0 && !pp.wasCancelled() )
+ {
+ Q_LONG readSize = srcFile.readBlock( &buffer[0], min2( srcSize, bufSize ) );
+ if ( readSize==-1 || readSize==0 )
+ {
+ m_pFileAccess->m_statusText = i18n("Error during file copy operation: Reading failed. Filename: %1").arg(srcName);
+ return false;
+ }
+ srcSize -= readSize;
+ while ( readSize > 0 )
+ {
+ Q_LONG writeSize = destFile.writeBlock( &buffer[0], readSize );
+ if ( writeSize==-1 || writeSize==0 )
+ {
+ m_pFileAccess->m_statusText = i18n("Error during file copy operation: Writing failed. Filename: %1").arg(destName);
+ return false;
+ }
+ readSize -= writeSize;
+ }
+ destFile.flush();
+ pp.setCurrent( (double)(srcFile.size()-srcSize)/srcFile.size(), false );
+ }
+ srcFile.close();
+ destFile.close();
+
+ // Update the times of the destFile
+#ifdef _WIN32
+ struct _stat srcFileStatus;
+ int statResult = ::_stat( srcName.ascii(), &srcFileStatus );
+ if (statResult==0)
+ {
+ _utimbuf destTimes;
+ destTimes.actime = srcFileStatus.st_atime;/* time of last access */
+ destTimes.modtime = srcFileStatus.st_mtime;/* time of last modification */
+
+ _utime ( destName.ascii(), &destTimes );
+ _chmod ( destName.ascii(), srcFileStatus.st_mode );
+ }
+#else
+ struct stat srcFileStatus;
+ int statResult = ::stat( srcName.ascii(), &srcFileStatus );
+ if (statResult==0)
+ {
+ utimbuf destTimes;
+ destTimes.actime = srcFileStatus.st_atime;/* time of last access */
+ destTimes.modtime = srcFileStatus.st_mtime;/* time of last modification */
+
+ utime ( destName.ascii(), &destTimes );
+ chmod ( destName.ascii(), srcFileStatus.st_mode );
+ }
+#endif
+ return true;
+}
+
+bool wildcardMultiMatch( const QString& wildcard, const QString& testString, bool bCaseSensitive )
+{
+ QStringList sl = QStringList::split( ";", wildcard );
+
+ for ( QStringList::Iterator it = sl.begin(); it != sl.end(); ++it )
+ {
+ QRegExp pattern( *it, bCaseSensitive, true /*wildcard mode*/);
+ if ( pattern.exactMatch( testString ) )
+ return true;
+ }
+
+ return false;
+}
+
+
+// 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 Andr�Woebeking <Woebbeking at web.de>
+// Modifications for KDiff3 by Joachim Eibl
+class CvsIgnoreList
+{
+public:
+ CvsIgnoreList(){}
+ void init(FileAccess& dir, bool bUseLocalCvsIgnore );
+ bool matches(const QString& fileName, bool bCaseSensitive ) const;
+
+private:
+ void addEntriesFromString(const QString& str);
+ void addEntriesFromFile(const QString& name);
+ void addEntry(const QString& entry);
+
+ QStringList m_exactPatterns;
+ QStringList m_startPatterns;
+ QStringList m_endPatterns;
+ QStringList m_generalPatterns;
+};
+
+
+void CvsIgnoreList::init( FileAccess& dir, bool bUseLocalCvsIgnore )
+{
+ 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 _$* *$";
+
+ addEntriesFromString(QString::fromLatin1(ignorestr));
+ addEntriesFromFile(QDir::homeDirPath() + "/.cvsignore");
+ addEntriesFromString(QString::fromLocal8Bit(::getenv("CVSIGNORE")));
+
+ if (bUseLocalCvsIgnore)
+ {
+ FileAccess file(dir);
+ file.addPath( ".cvsignore" );
+ int size = file.exists() ? file.sizeForReading() : 0;
+ if ( size>0 )
+ {
+ char* buf=new char[size];
+ if (buf!=0)
+ {
+ 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.find(' ', 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(IO_ReadOnly) )
+ {
+ QTextStream stream(&file);
+ while( !stream.eof() )
+ {
+ 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.constref(0) == QChar('*') )
+ {
+ m_endPatterns.append( pattern.right( pattern.length() - 1) );
+ }
+ else if (pattern.constref(pattern.length() - 1) == QChar('*'))
+ {
+ m_startPatterns.append( pattern.left( pattern.length() - 1) );
+ }
+ else
+ {
+ m_generalPatterns.append(pattern.local8Bit());
+ }
+ }
+ else
+ {
+ m_generalPatterns.append(pattern.local8Bit());
+ }
+ }
+ 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.find(text) != m_exactPatterns.end())
+ {
+ 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, true /*wildcard mode*/);
+#if QT_VERSION==230
+ int len=0;
+ if ( pattern.match( text, 0, &len )!=-1 && len==text.length())
+ return true;
+#else
+ if ( pattern.exactMatch( text ) )
+ return true;
+#endif
+ }
+
+ return false;
+}
+
+static QString nicePath( const QFileInfo& fi )
+{
+ QString fp = fi.filePath();
+ if ( fp.length()>2 && fp[0] == '.' && fp[1] == '/' )
+ {
+ return fp.mid(2);
+ }
+ return fp;
+}
+
+static bool cvsIgnoreExists( t_DirectoryList* pDirList )
+{
+ t_DirectoryList::iterator i;
+ for( i = pDirList->begin(); i!=pDirList->end(); ++i )
+ {
+ if ( i->fileName()==".cvsignore" )
+ return true;
+ }
+ return false;
+}
+
+bool FileAccessJobHandler::listDir( t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern,
+ const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore )
+{
+ ProgressProxy 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: ") + m_pFileAccess->absFilePath(), 0, false );
+
+ if( m_pFileAccess->isLocal() )
+ {
+ QString currentPath = QDir::currentDirPath();
+ m_bSuccess = QDir::setCurrent( m_pFileAccess->absFilePath() );
+ if ( m_bSuccess )
+ {
+#ifndef _WIN32
+ m_bSuccess = true;
+ QDir dir( "." );
+
+ dir.setSorting( QDir::Name | QDir::DirsFirst );
+ dir.setFilter( QDir::Files | QDir::Dirs | QDir::Hidden );
+ dir.setMatchAllDirs( true );
+
+ const QFileInfoList *fiList = dir.entryInfoList();
+ if ( fiList == 0 )
+ {
+ // No Permission to read directory or other error.
+ m_bSuccess = false;
+ }
+ else
+ {
+ QFileInfoListIterator it( *fiList ); // create list iterator
+ for ( ; it.current() != 0; ++it ) // for each file...
+ {
+ QFileInfo* fi = it.current();
+ if ( fi->fileName() == "." || fi->fileName()==".." )
+ continue;
+
+ pDirList->push_back( FileAccess( nicePath(*fi) ) );
+ }
+ }
+#else
+ QString pattern ="*.*";
+ WIN32_FIND_DATA findData;
+ WIN32_FIND_DATAA& findDataA=*(WIN32_FIND_DATAA*)&findData; // Needed for Win95
+
+ HANDLE searchHandle = QT_WA_INLINE(
+ FindFirstFile( (TCHAR*)pattern.ucs2(), &findData ),
+ FindFirstFileA( pattern.local8Bit(), &findDataA )
+ );
+
+ if ( searchHandle != INVALID_HANDLE_VALUE )
+ {
+ QString absPath = m_pFileAccess->absFilePath();
+ QString relPath = m_pFileAccess->filePath();
+ bool bFirst=true;
+ while( ! pp.wasCancelled() )
+ {
+ if (!bFirst)
+ {
+ if ( ! QT_WA_INLINE(
+ FindNextFile(searchHandle,&findData),
+ FindNextFileA(searchHandle,&findDataA)) )
+ break;
+ }
+ bFirst = false;
+ FileAccess fa;
+ fa.m_size = findData.nFileSizeLow ;//+ findData.nFileSizeHigh;
+
+ FILETIME ft;
+ SYSTEMTIME t;
+ FileTimeToLocalFileTime( &findData.ftLastWriteTime, &ft ); FileTimeToSystemTime(&ft,&t);
+ fa.m_modificationTime = QDateTime( QDate(t.wYear, t.wMonth, t.wDay), QTime(t.wHour, t.wMinute, t.wSecond) );
+ FileTimeToLocalFileTime( &findData.ftLastAccessTime, &ft ); FileTimeToSystemTime(&ft,&t);
+ fa.m_accessTime = QDateTime( QDate(t.wYear, t.wMonth, t.wDay), QTime(t.wHour, t.wMinute, t.wSecond) );
+ FileTimeToLocalFileTime( &findData.ftCreationTime, &ft ); FileTimeToSystemTime(&ft,&t);
+ fa.m_creationTime = QDateTime( QDate(t.wYear, t.wMonth, t.wDay), QTime(t.wHour, t.wMinute, t.wSecond) );
+
+ int a = findData.dwFileAttributes;
+ fa.m_bWritable = ( a & FILE_ATTRIBUTE_READONLY) == 0;
+ fa.m_bDir = ( a & FILE_ATTRIBUTE_DIRECTORY ) != 0;
+ fa.m_bFile = !fa.m_bDir;
+ fa.m_bHidden = ( a & FILE_ATTRIBUTE_HIDDEN) != 0;
+
+ fa.m_bExecutable = false; // Useless on windows
+ fa.m_bExists = true;
+ fa.m_bReadable = true;
+ fa.m_bLocal = true;
+ fa.m_bValidData = true;
+ fa.m_bSymLink = false;
+ fa.m_fileType = 0;
+
+ fa.m_name = QT_WA_INLINE(
+ QString::fromUcs2((const ushort*)findData.cFileName),
+ QString::fromLocal8Bit(findDataA.cFileName)
+ );
+
+ fa.m_path = fa.m_name;
+ fa.m_absFilePath = absPath + "/" + fa.m_name;
+ fa.m_url.setPath( fa.m_absFilePath );
+ if ( fa.m_name!="." && fa.m_name!=".." )
+ pDirList->push_back( fa );
+ }
+ FindClose( searchHandle );
+ }
+ else
+ {
+ QDir::setCurrent( currentPath ); // restore current path
+ return false;
+ }
+#endif
+ }
+ QDir::setCurrent( currentPath ); // restore current path
+ }
+ else
+ {
+ bool bShowProgress = false;
+
+ KIO::ListJob* pListJob=0;
+ pListJob = KIO::listDir( m_pFileAccess->m_url, bShowProgress, true /*bFindHidden*/ );
+
+ m_bSuccess = false;
+ if ( pListJob!=0 )
+ {
+ connect( pListJob, SIGNAL( entries( KIO::Job *, const KIO::UDSEntryList& ) ),
+ this, SLOT( slotListDirProcessNewEntries( KIO::Job *, const KIO::UDSEntryList& )) );
+ connect( pListJob, SIGNAL( result( KIO::Job* )),
+ this, SLOT( slotSimpleJobResult(KIO::Job*) ) );
+
+ connect( pListJob, SIGNAL( infoMessage(KIO::Job*, const QString&)),
+ this, SLOT( slotListDirInfoMessage(KIO::Job*, const QString&) ));
+
+ // This line makes the transfer via fish unreliable.:-(
+ //connect( pListJob, SIGNAL(percent(KIO::Job*,unsigned long)), this, SLOT(slotPercent(KIO::Job*, unsigned long)));
+
+ g_pProgressDialog->enterEventLoop( pListJob,
+ i18n("Listing directory: %1").arg(m_pFileAccess->prettyAbsPath()) );
+ }
+ }
+
+ CvsIgnoreList cvsIgnoreList;
+ if ( bUseCvsIgnore )
+ {
+ cvsIgnoreList.init( *m_pFileAccess, cvsIgnoreExists(pDirList) );
+ }
+#ifdef _WIN32
+ bool bCaseSensitive = false;
+#else
+ bool bCaseSensitive = true;
+#endif
+
+ // Now remove all entries that don't match:
+ t_DirectoryList::iterator i;
+ for( i = pDirList->begin(); i!=pDirList->end(); )
+ {
+ t_DirectoryList::iterator i2=i;
+ ++i2;
+ QString fn = i->fileName();
+ if ( (!bFindHidden && i->isHidden() )
+ ||
+ (i->isFile() &&
+ ( !wildcardMultiMatch( filePattern, i->fileName(), bCaseSensitive ) ||
+ wildcardMultiMatch( fileAntiPattern, i->fileName(), bCaseSensitive ) ) )
+ ||
+ (i->isDir() && wildcardMultiMatch( dirAntiPattern, i->fileName(), bCaseSensitive ) )
+ ||
+ cvsIgnoreList.matches( i->fileName(), bCaseSensitive )
+ )
+ {
+ // Remove it
+ pDirList->erase( i );
+ i = i2;
+ }
+ else
+ {
+ ++i;
+ }
+ }
+
+ if ( bRecursive )
+ {
+ t_DirectoryList subDirsList;
+
+ t_DirectoryList::iterator i;
+ 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 );
+
+ t_DirectoryList::iterator j;
+ for( j = dirList.begin(); j!=dirList.end(); ++j )
+ {
+ j->m_path = i->fileName() + "/" + j->m_path;
+ }
+
+ // 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 )
+{
+ KURL parentUrl( m_pFileAccess->m_absFilePath );
+
+ KIO::UDSEntryList::ConstIterator i;
+ for ( i=l.begin(); i!=l.end(); ++i )
+ {
+ const KIO::UDSEntry& e = *i;
+ FileAccess fa;
+ fa.setUdsEntry( e );
+
+ if ( fa.filePath() != "." && fa.filePath() != ".." )
+ {
+ fa.m_url = parentUrl;
+ fa.m_url.addPath( fa.filePath() );
+ fa.m_absFilePath = fa.m_url.url();
+ m_pDirList->push_back( fa );
+ }
+ }
+}
+
+void FileAccessJobHandler::slotListDirInfoMessage( KIO::Job*, const QString& msg )
+{
+ g_pProgressDialog->setInformation( msg, 0.0 );
+}
+
+void FileAccessJobHandler::slotPercent( KIO::Job*, unsigned long percent )
+{
+ g_pProgressDialog->setCurrent( percent/100.0 );
+}
+
+
+ProgressDialog::ProgressDialog( QWidget* pParent )
+: QDialog( pParent, 0, true )
+{
+ m_bStayHidden = false;
+ QVBoxLayout* layout = new QVBoxLayout(this);
+
+ m_pInformation = new QLabel( " ", this );
+ layout->addWidget( m_pInformation );
+
+ m_pProgressBar = new KProgress(1000, this);
+ layout->addWidget( m_pProgressBar );
+
+ m_pSubInformation = new QLabel( " ", this);
+ layout->addWidget( m_pSubInformation );
+
+ m_pSubProgressBar = new KProgress(1000, this);
+ layout->addWidget( m_pSubProgressBar );
+
+ m_pSlowJobInfo = new QLabel( " ", this);
+ layout->addWidget( m_pSlowJobInfo );
+
+ QHBoxLayout* hlayout = new QHBoxLayout( layout );
+ hlayout->addStretch(1);
+ m_pAbortButton = new QPushButton( i18n("&Cancel"), this);
+ hlayout->addWidget( m_pAbortButton );
+ connect( m_pAbortButton, SIGNAL(clicked()), this, SLOT(slotAbort()) );
+
+ m_progressDelayTimer = 0;
+ resize( 400, 100 );
+ m_t1.start();
+ m_t2.start();
+ m_bWasCancelled = false;
+ m_pJob = 0;
+}
+
+void ProgressDialog::setStayHidden( bool bStayHidden )
+{
+ m_bStayHidden = bStayHidden;
+}
+
+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, double dCurrent, bool bRedrawUpdate )
+{
+ if ( m_progressStack.empty() )
+ return;
+ ProgressLevelData& pld = m_progressStack.back();
+ pld.m_dCurrent = dCurrent;
+ int level = m_progressStack.size();
+ if ( level==1 )
+ {
+ m_pInformation->setText( info );
+ m_pSubInformation->setText("");
+ }
+ 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( "" );
+ }
+ else if ( level==2 )
+ {
+ m_pSubInformation->setText( info );
+ }
+ recalc(bRedrawUpdate);
+}
+
+void ProgressDialog::setMaxNofSteps( int maxNofSteps )
+{
+ if ( m_progressStack.empty() )
+ return;
+ ProgressLevelData& pld = m_progressStack.back();
+ pld.m_maxNofSteps = maxNofSteps;
+ pld.m_dCurrent = 0;
+}
+
+void ProgressDialog::step( bool bRedrawUpdate )
+{
+ if ( m_progressStack.empty() )
+ return;
+ ProgressLevelData& pld = m_progressStack.back();
+ pld.m_dCurrent += 1.0/pld.m_maxNofSteps;
+ recalc(bRedrawUpdate);
+}
+
+void ProgressDialog::setCurrent( double dSubCurrent, bool bRedrawUpdate )
+{
+ if ( m_progressStack.empty() )
+ return;
+ ProgressLevelData& pld = m_progressStack.back();
+ pld.m_dCurrent = dSubCurrent;
+ 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_dCurrent = 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( KIO::Job* pJob, const QString& jobInfo )
+{
+ m_pJob = pJob;
+ m_pSlowJobInfo->setText("");
+ m_currentJobInfo = jobInfo;
+ killTimer( m_progressDelayTimer );
+ m_progressDelayTimer = startTimer( 3000 ); /* 3 s delay */
+
+ // instead of using exec() the eventloop is entered and exited often without hiding/showing the window.
+#if QT_VERSION==230
+ //qApp->enter_loop();
+#else
+ qt_enter_modal(this);
+ qApp->eventLoop()->enterLoop();
+ qt_leave_modal(this);
+#endif
+}
+
+void ProgressDialog::exitEventLoop()
+{
+ killTimer( m_progressDelayTimer );
+ m_progressDelayTimer = 0;
+ m_pJob = 0;
+ qApp->eventLoop()->exitLoop();
+}
+
+void ProgressDialog::recalc( bool bUpdate )
+{
+ killTimer( m_progressDelayTimer );
+ 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->setProgress( 0 );
+ m_pSubProgressBar->setProgress( 0 );
+ }
+ else
+ {
+ std::list<ProgressLevelData>::iterator i = m_progressStack.begin();
+ m_pProgressBar->setProgress( int( 1000.0 * ( i->m_dCurrent * (i->m_dRangeMax - i->m_dRangeMin) + i->m_dRangeMin ) ) );
+ ++i;
+ if ( i!=m_progressStack.end() )
+ m_pSubProgressBar->setProgress( int( 1000.0 * ( i->m_dCurrent * (i->m_dRangeMax - i->m_dRangeMin) + i->m_dRangeMin ) ) );
+ else
+ m_pSubProgressBar->setProgress( int( 1000.0 * m_progressStack.front().m_dSubRangeMin ) );
+ }
+
+ if ( !m_bStayHidden && !isVisible() )
+ show();
+ qApp->processEvents();
+ m_t1.restart();
+ }
+}
+
+
+#include <qtimer.h>
+void ProgressDialog::show()
+{
+ killTimer( m_progressDelayTimer );
+ m_progressDelayTimer = 0;
+ if ( !isVisible() && (parentWidget()==0 || parentWidget()->isVisible()) )
+ {
+#if QT_VERSION==230
+ QWidget::show();
+#else
+ QDialog::show();
+#endif
+ }
+}
+
+void ProgressDialog::hide()
+{
+ killTimer( m_progressDelayTimer );
+ m_progressDelayTimer = 0;
+ // Calling QDialog::hide() directly doesn't always work. (?)
+ QTimer::singleShot( 100, this, SLOT(delayedHide()) );
+}
+
+void ProgressDialog::delayedHide()
+{
+ if (m_pJob!=0)
+ {
+ m_pJob->kill(false);
+ m_pJob = 0;
+ }
+ QDialog::hide();
+ m_pInformation->setText( "" );
+
+ //m_progressStack.clear();
+
+ m_pProgressBar->setProgress( 0 );
+ m_pSubProgressBar->setProgress( 0 );
+ m_pSubInformation->setText("");
+ m_pSlowJobInfo->setText("");
+}
+
+void ProgressDialog::reject()
+{
+ m_bWasCancelled = true;
+ QDialog::reject();
+}
+
+void ProgressDialog::slotAbort()
+{
+ reject();
+}
+
+bool ProgressDialog::wasCancelled()
+{
+ if( m_t2.elapsed()>100 )
+ {
+ qApp->processEvents();
+ m_t2.restart();
+ }
+ return m_bWasCancelled;
+}
+
+
+void ProgressDialog::timerEvent(QTimerEvent*)
+{
+ if( !isVisible() )
+ {
+ show();
+ }
+ m_pSlowJobInfo->setText( m_currentJobInfo );
+}
+
+
+ProgressProxy::ProgressProxy()
+{
+ g_pProgressDialog->push();
+}
+
+ProgressProxy::~ProgressProxy()
+{
+ g_pProgressDialog->pop(false);
+}
+
+void ProgressProxy::setInformation( const QString& info, bool bRedrawUpdate )
+{
+ g_pProgressDialog->setInformation( info, bRedrawUpdate );
+}
+
+void ProgressProxy::setInformation( const QString& info, double dCurrent, bool bRedrawUpdate )
+{
+ g_pProgressDialog->setInformation( info, dCurrent, bRedrawUpdate );
+}
+
+void ProgressProxy::setCurrent( double dCurrent, bool bRedrawUpdate )
+{
+ g_pProgressDialog->setCurrent( dCurrent, bRedrawUpdate );
+}
+
+void ProgressProxy::step( bool bRedrawUpdate )
+{
+ g_pProgressDialog->step( bRedrawUpdate );
+}
+
+void ProgressProxy::setMaxNofSteps( int maxNofSteps )
+{
+ g_pProgressDialog->setMaxNofSteps( maxNofSteps );
+}
+
+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 );
+}
+
+
+
+
+
+#include "fileaccess.moc"
diff --git a/src/fileaccess.h b/src/fileaccess.h
new file mode 100644
index 0000000..77b70bf
--- /dev/null
+++ b/src/fileaccess.h
@@ -0,0 +1,265 @@
+/***************************************************************************
+ * 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 FILEACCESS_H
+#define FILEACCESS_H
+
+#include <qdialog.h>
+#include <qfileinfo.h>
+#include <kprogress.h>
+#include <kio/job.h>
+#include <kio/jobclasses.h>
+#include <kurldrag.h>
+#include <list>
+#include <qstring.h>
+#include <qdatetime.h>
+
+bool wildcardMultiMatch( const QString& wildcard, const QString& testString, bool bCaseSensitive );
+
+class t_DirectoryList;
+
+class FileAccess
+{
+public:
+ 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 isValid() const;
+ bool isFile() const;
+ bool isDir() const;
+ bool isSymLink() const;
+ bool exists() const;
+ long size() const; // Size as returned by stat().
+ long 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 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 prettyAbsPath() const;
+ KURL url() const;
+ QString absFilePath() const;
+
+ bool isLocal() const;
+
+ bool readFile(void* pDestBuffer, unsigned long maxLength );
+ bool 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 copyFile( const QString& destUrl );
+ bool createBackup( const QString& bakExtension );
+
+ static QString tempFileName();
+ 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 cleanDirPath( 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();
+private:
+ void setUdsEntry( const KIO::UDSEntry& e );
+ KURL m_url;
+ bool m_bLocal;
+ bool m_bValidData;
+
+ unsigned long m_size;
+ QDateTime m_modificationTime;
+ QDateTime m_accessTime;
+ QDateTime m_creationTime;
+ bool m_bReadable;
+ bool m_bWritable;
+ bool m_bExecutable;
+ bool m_bExists;
+ bool m_bFile;
+ bool m_bDir;
+ bool m_bSymLink;
+ bool m_bHidden;
+ long m_fileType; // for testing only
+
+ QString m_linkTarget;
+ QString m_user;
+ QString m_group;
+ QString m_name;
+ QString m_path;
+ QString m_absFilePath;
+ QString m_localCopy;
+ QString m_statusText; // Might contain an error string, when the last operation didn't succeed.
+
+ friend class FileAccessJobHandler;
+};
+
+class t_DirectoryList : public std::list<FileAccess>
+{};
+
+
+class FileAccessJobHandler : public QObject
+{
+ Q_OBJECT
+public:
+ 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 QString& dirName );
+ bool symLink( const QString& linkTarget, const QString& linkLocation );
+
+private:
+ FileAccess* m_pFileAccess;
+ bool m_bSuccess;
+
+ // Data needed during Job
+ long m_transferredBytes;
+ char* m_pTransferBuffer; // Needed during get or put
+ long m_maxLength;
+
+ QString m_filePattern;
+ QString m_fileAntiPattern;
+ QString m_dirAntiPattern;
+ t_DirectoryList* m_pDirList;
+ bool m_bFindHidden;
+ bool m_bRecursive;
+ bool m_bFollowDirLinks;
+
+ bool scanLocalDirectory( const QString& dirName, t_DirectoryList* dirList );
+
+private slots:
+ void slotStatResult( KIO::Job* );
+ void slotSimpleJobResult( KIO::Job* pJob );
+ void slotPutJobResult( KIO::Job* pJob );
+
+ void slotGetData(KIO::Job*,const QByteArray&);
+ void slotPutData(KIO::Job*, QByteArray&);
+
+ void slotListDirInfoMessage( KIO::Job*, const QString& msg );
+ void slotListDirProcessNewEntries( KIO::Job *, const KIO::UDSEntryList& l );
+
+ void slotPercent( KIO::Job* pJob, unsigned long percent );
+};
+
+class ProgressDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ ProgressDialog( QWidget* pParent );
+
+ void setStayHidden( bool bStayHidden );
+ void setInformation( const QString& info, bool bRedrawUpdate=true );
+ void setInformation( const QString& info, double dCurrent, bool bRedrawUpdate=true );
+ void setCurrent( double dCurrent, bool bRedrawUpdate=true );
+ void step( bool bRedrawUpdate=true );
+ void setMaxNofSteps( int dMaxNofSteps );
+ 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( KIO::Job* pJob, const QString& jobInfo );
+
+ bool wasCancelled();
+ void show();
+ void hide();
+
+ virtual void timerEvent(QTimerEvent*);
+private:
+
+ struct ProgressLevelData
+ {
+ ProgressLevelData()
+ {
+ m_dCurrent=0; m_maxNofSteps=1; m_dRangeMin=0; m_dRangeMax=1;
+ m_dSubRangeMin = 0; m_dSubRangeMax = 1;
+ }
+ double m_dCurrent;
+ int m_maxNofSteps; // when step() is used.
+ double m_dRangeMax;
+ double m_dRangeMin;
+ double m_dSubRangeMax;
+ double m_dSubRangeMin;
+ };
+ std::list<ProgressLevelData> m_progressStack;
+
+ int m_progressDelayTimer;
+
+ KProgress* m_pProgressBar;
+ KProgress* m_pSubProgressBar;
+ QLabel* m_pInformation;
+ QLabel* m_pSubInformation;
+ QLabel* m_pSlowJobInfo;
+ QPushButton* m_pAbortButton;
+ void recalc(bool bRedrawUpdate);
+ QTime m_t1;
+ QTime m_t2;
+ bool m_bWasCancelled;
+ KIO::Job* m_pJob;
+ QString m_currentJobInfo; // Needed if the job doesn't stop after a reasonable time.
+ bool m_bStayHidden;
+protected:
+ virtual void reject();
+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:
+ ProgressProxy();
+ ~ProgressProxy();
+
+ void setInformation( const QString& info, bool bRedrawUpdate=true );
+ void setInformation( const QString& info, double dCurrent, bool bRedrawUpdate=true );
+ void setCurrent( double dCurrent, bool bRedrawUpdate=true );
+ void step( bool bRedrawUpdate=true );
+ void setMaxNofSteps( int dMaxNofSteps );
+ bool wasCancelled();
+ void setRangeTransformation( double dMin, double dMax );
+ void setSubRangeTransformation( double dMin, double dMax );
+private:
+};
+
+extern ProgressDialog* g_pProgressDialog;
+
+
+
+#endif
+
diff --git a/src/gnudiff_analyze.cpp b/src/gnudiff_analyze.cpp
new file mode 100644
index 0000000..ea49b4e
--- /dev/null
+++ b/src/gnudiff_analyze.cpp
@@ -0,0 +1,873 @@
+/* Analyze file differences for GNU DIFF.
+
+ Modified for KDiff3 by Joachim Eibl 2003.
+ The original file was part of GNU DIFF.
+
+ Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1998, 2001, 2002
+ Free Software Foundation, Inc.
+
+ GNU DIFF is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ GNU DIFF is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING.
+ If not, write to the Free Software Foundation,
+ 51 Franklin Steet, 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 "gnudiff_diff.h"
+//#include <error.h>
+#include <stdlib.h>
+
+static lin *xvec, *yvec; /* Vectors being compared. */
+static lin *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 lin *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 lin 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
+{
+ lin 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. */
+
+lin
+GnuDiff::diag (lin xoff, lin xlim, lin yoff, lin ylim, bool find_minimal,
+ struct partition *part)
+{
+ lin *const fd = fdiag; /* Give the compiler a chance. */
+ lin *const bd = bdiag; /* Additional help for the compiler. */
+ lin const *const xv = xvec; /* Still more help for the compiler. */
+ lin const *const yv = yvec; /* And more and more . . . */
+ lin const dmin = xoff - ylim; /* Minimum valid diagonal. */
+ lin const dmax = xlim - yoff; /* Maximum valid diagonal. */
+ lin const fmid = xoff - yoff; /* Center diagonal of top-down search. */
+ lin const bmid = xlim - ylim; /* Center diagonal of bottom-up search. */
+ lin fmin = fmid, fmax = fmid; /* Limits of top-down search. */
+ lin bmin = bmid, bmax = bmid; /* Limits of bottom-up search. */
+ lin 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)
+ {
+ lin d; /* Active diagonal. */
+ bool big_snake = 0;
+
+ /* 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)
+ {
+ lin 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 = 1;
+ fd[d] = x;
+ if (odd && bmin <= d && d <= bmax && bd[d] <= x)
+ {
+ part->xmid = x;
+ part->ymid = y;
+ part->lo_minimal = part->hi_minimal = 1;
+ return 2 * c - 1;
+ }
+ }
+
+ /* Similarly extend the bottom-up search. */
+ bmin > dmin ? bd[--bmin - 1] = LIN_MAX : ++bmin;
+ bmax < dmax ? bd[++bmax + 1] = LIN_MAX : --bmax;
+ for (d = bmax; d >= bmin; d -= 2)
+ {
+ lin 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 = 1;
+ bd[d] = x;
+ if (!odd && fmin <= d && d <= fmax && x <= fd[d])
+ {
+ part->xmid = x;
+ part->ymid = y;
+ part->lo_minimal = part->hi_minimal = 1;
+ 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)
+ {
+ lin best;
+
+ best = 0;
+ for (d = fmax; d >= fmin; d -= 2)
+ {
+ lin dd = d - fmid;
+ lin x = fd[d];
+ lin y = x - d;
+ lin 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 = 1;
+ part->hi_minimal = 0;
+ return 2 * c - 1;
+ }
+
+ best = 0;
+ for (d = bmax; d >= bmin; d -= 2)
+ {
+ lin dd = d - bmid;
+ lin x = bd[d];
+ lin y = x - d;
+ lin 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 = 0;
+ part->hi_minimal = 1;
+ 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)
+ {
+ lin fxybest, fxbest;
+ lin bxybest, bxbest;
+
+ fxbest = bxbest = 0; /* Pacify `gcc -Wall'. */
+
+ /* Find forward diagonal that maximizes X + Y. */
+ fxybest = -1;
+ for (d = fmax; d >= fmin; d -= 2)
+ {
+ lin x = MIN (fd[d], xlim);
+ lin 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 = LIN_MAX;
+ for (d = bmax; d >= bmin; d -= 2)
+ {
+ lin x = MAX (xoff, bd[d]);
+ lin 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 = 1;
+ part->hi_minimal = 0;
+ }
+ else
+ {
+ part->xmid = bxbest;
+ part->ymid = bxybest - bxbest;
+ part->lo_minimal = 0;
+ part->hi_minimal = 1;
+ }
+ 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 (lin xoff, lin xlim, lin yoff, lin ylim, bool find_minimal)
+{
+ lin * const xv = xvec; /* Help the compiler. */
+ lin * 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++]] = 1;
+ else if (yoff == ylim)
+ while (xoff < xlim)
+ files[0].changed[files[0].realindexes[xoff++]] = 1;
+ else
+ {
+ lin 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;
+ lin i;
+ char *discarded[2];
+ lin *equiv_count[2];
+ lin *p;
+
+ /* Allocate our results. */
+ p = (lin*)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 = (lin*)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];
+ lin *counts = equiv_count[1 - f];
+ lin *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 < (lin)end; i++)
+ {
+ lin nmatch;
+ if (equivs[i] == 0)
+ continue;
+ nmatch = counts[equivs[i]];
+ if (nmatch == 0)
+ discards[i] = 1;
+ else if (nmatch > (lin)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++)
+ {
+ lin end = filevec[f].buffered_lines;
+ register 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. */
+ register lin j;
+ lin length;
+ lin 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
+ {
+ register lin consec;
+ lin minimum = 1;
+ lin 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];
+ lin end = filevec[f].buffered_lines;
+ lin 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] = 1;
+ 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;
+ lin const *equivs = filevec[f].equivs;
+ lin i = 0;
+ lin j = 0;
+ lin i_end = filevec[f].buffered_lines;
+
+ while (1)
+ {
+ lin 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] = 1;
+ changed[--i] = 0;
+ 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++] = 0;
+ changed[i++] = 1;
+ 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] = 1;
+ changed[--i] = 0;
+ 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 (lin line0, lin line1, lin deleted, lin 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 = 0;
+ bool *changed0 = filevec[0].changed;
+ bool *changed1 = filevec[1].changed;
+ lin len0 = filevec[0].buffered_lines;
+ lin len1 = filevec[1].buffered_lines;
+
+ /* Note that changedN[len0] does exist, and is 0. */
+
+ lin i0 = 0, i1 = 0;
+
+ while (i0 < len0 || i1 < len1)
+ {
+ if (changed0[i0] | changed1[i1])
+ {
+ lin 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 = 0;
+ bool *changed0 = filevec[0].changed;
+ bool *changed1 = filevec[1].changed;
+ lin 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])
+ {
+ lin 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)
+{
+ lin diags;
+ int f;
+ //struct change *e, *p;
+ struct change *script;
+ int changes;
+
+ 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 = (lin*)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);
+
+ changes = (script != 0);
+
+ 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
index 0000000..930424e
--- /dev/null
+++ b/src/gnudiff_diff.h
@@ -0,0 +1,355 @@
+/* Shared definitions for GNU DIFF
+
+ Modified for KDiff3 by Joachim Eibl 2003, 2004, 2005.
+ The original file was part of GNU DIFF.
+
+ Copyright (C) 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1998, 2001,
+ 2002 Free Software Foundation, Inc.
+
+ GNU DIFF is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ GNU DIFF is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING.
+ If not, write to the Free Software Foundation,
+ 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifndef GNUDIFF_DIFF_H
+#define GNUDIFF_DIFF_H
+
+#include "gnudiff_system.h"
+
+#include <stdio.h>
+#include <qstring.h>
+
+#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
+};
+
+/* 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. */
+lin 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;
+
+
+/* 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 */
+ lin inserted; /* # lines of file 1 changed here. */
+ lin deleted; /* # lines of file 0 changed here. */
+ lin line0; /* Line number of 1st deleted line. */
+ lin 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. */
+ lin 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]. */
+ lin 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. */
+ lin *equivs;
+
+ /* Vector, like the previous one except that
+ the elements for discarded lines have been squeezed out. */
+ lin *undiscarded;
+
+ /* Vector mapping virtual line numbers (not counting discarded lines)
+ to real ones (counting those lines). Both are origin-0. */
+ lin *realindexes;
+
+ /* Total number of nondiscarded lines. */
+ lin 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. */
+ lin 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];
+
+/* Stdio stream to output diffs to. */
+
+FILE *outfile;
+
+/* 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 );
+lin translate_line_number (struct file_data const *, lin);
+struct change *find_change (struct change *);
+struct change *find_reverse_change (struct change *);
+void *zalloc (size_t);
+enum changes analyze_hunk (struct change *, lin *, lin *, lin *, lin *);
+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 *, lin, lin, long *, long *);
+
+/* version.c */
+//extern const QChar version_string[];
+
+private:
+ // gnudiff_analyze.cpp
+ lin diag (lin xoff, lin xlim, lin yoff, lin ylim, bool find_minimal, struct partition *part);
+ void compareseq (lin xoff, lin xlim, lin yoff, lin ylim, bool find_minimal);
+ void discard_confusing_lines (struct file_data filevec[]);
+ void shift_boundaries (struct file_data filevec[]);
+ struct change * add_change (lin line0, lin line1, lin deleted, lin 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
index 0000000..922bce7
--- /dev/null
+++ b/src/gnudiff_io.cpp
@@ -0,0 +1,559 @@
+/* File I/O for GNU DIFF.
+
+ Modified for KDiff3 by Joachim Eibl 2003, 2004, 2005.
+ The original file was part of GNU DIFF.
+
+ Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1998, 2001, 2002
+ Free Software Foundation, Inc.
+
+ GNU DIFF is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ GNU DIFF is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING.
+ If not, write to the Free Software Foundation,
+ 51 Franklin Steet, 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
+{
+ lin 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 lin *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 lin equivs_index;
+
+/* Number of elements allocated in the array `equivs'. */
+static lin 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->lower() == t2->lower() )
+ 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;
+ lin i, *bucket;
+ size_t length;
+
+ /* Cache often-used quantities in local variables to help the compiler. */
+ const QChar **linbuf = current->linbuf;
+ lin alloc_lines = current->alloc_lines;
+ lin line = 0;
+ lin linbuf_base = current->linbuf_base;
+ lin *cureqs = (lin*)xmalloc (alloc_lines * sizeof *cureqs);
+ struct equivclass *eqs = equivs;
+ lin eqs_index = equivs_index;
+ lin 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 && (c = *p) != '\n' )
+ {
+ if (! (isWhite(c) || bIgnoreNumbers && (c.isDigit() || c=='-' || c=='.' ) ))
+ h = HASH (h, c.lower().unicode());
+ ++p;
+ }
+ break;
+
+ default:
+ while ( p<bufend && (c = *p) != '\n' )
+ {
+ h = HASH (h, c.lower().unicode());
+ ++p;
+ }
+ break;
+ }
+ else
+ switch (ignore_white_space)
+ {
+ case IGNORE_ALL_SPACE:
+ while ( p<bufend && (c = *p) != '\n')
+ {
+ if (! (isWhite(c)|| bIgnoreNumbers && (c.isDigit() || c=='-' || c=='.' ) ))
+ h = HASH (h, c.unicode());
+ ++p;
+ }
+ break;
+
+ default:
+ while ( p<bufend && (c = *p) != '\n')
+ {
+ 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 ((lin)(PTRDIFF_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 ((lin)(PTRDIFF_MAX / 3) <= alloc_lines
+ || (lin)(PTRDIFF_MAX / sizeof *cureqs) <= 2 * alloc_lines - linbuf_base
+ || (lin)(PTRDIFF_MAX / sizeof *linbuf) <= alloc_lines - linbuf_base)
+ xalloc_die ();
+ alloc_lines = 2 * alloc_lines - linbuf_base;
+ cureqs =(lin*) 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 ((lin)(PTRDIFF_MAX / 3) <= alloc_lines
+ || (lin)(PTRDIFF_MAX / sizeof *cureqs) <= 2 * alloc_lines - linbuf_base
+ || (lin)(PTRDIFF_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 && *p++ != '\n')
+ 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 lin
+guess_lines (lin n, size_t s, size_t t)
+{
+ size_t guessed_bytes_per_line = n < 10 ? 32 : s / (n - 1);
+ lin guessed_lines = MAX (1, t / guessed_bytes_per_line);
+ return MIN (guessed_lines, (lin)(PTRDIFF_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 && (p0[-1] != '\n' ))
+ 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 && *p0++ != '\n')
+ 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;
+ lin alloc_lines0, alloc_lines1;
+ lin buffered_prefix, prefix_count, prefix_mask;
+ lin middle_guess, suffix_guess;
+ if (no_diff_means_no_output
+ && context < (lin)(LIN_MAX / 4) && context < (lin)(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;
+ lin 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)
+ {
+ lin l = lines++ & prefix_mask;
+ if (l == alloc_lines0)
+ {
+ if ((lin)(PTRDIFF_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 && *p0++ != '\n' )
+ 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
+ || (lin)(PTRDIFF_MAX / sizeof *linbuf1) <= alloc_lines1)
+ xalloc_die ();
+ linbuf1 = (const QChar**)xmalloc (alloc_lines1 * sizeof(*linbuf1));
+
+ lin 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*/)
+{
+ int i;
+
+ find_identical_ends (filevec);
+
+ equivs_alloc = filevec[0].alloc_lines + filevec[1].alloc_lines + 1;
+ if ((lin)(PTRDIFF_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; 1 << i < equivs_alloc / 3; i++)
+ continue;
+ nbuckets = ((size_t) 1 << i) - prime_offset[i];
+ if (PTRDIFF_MAX / sizeof *buckets <= nbuckets)
+ xalloc_die ();
+ buckets = (lin*)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 0;
+}
diff --git a/src/gnudiff_system.h b/src/gnudiff_system.h
new file mode 100644
index 0000000..1d2286e
--- /dev/null
+++ b/src/gnudiff_system.h
@@ -0,0 +1,123 @@
+/* System dependent declarations.
+
+ Modified for KDiff3 by Joachim Eibl 2003.
+ The original file was part of GNU DIFF.
+
+ Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1998, 2001, 2002
+ Free Software Foundation, Inc.
+
+ GNU DIFF is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ GNU DIFF is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING.
+ If not, write to the Free Software Foundation,
+ 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifndef GNUDIFF_SYSTEM_H
+#define GNUDIFF_SYSTEM_H
+
+//#include <config.h>
+
+
+
+/* Don't bother to support K&R C compilers any more; it's not worth
+ the trouble. These macros prevent some library modules from being
+ compiled in K&R C mode. */
+#define PARAMS(Args) Args
+#define PROTOTYPES 1
+
+/* Verify a requirement at compile-time (unlike assert, which is runtime). */
+#define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; }
+
+
+/* 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
+#ifndef TYPE_MINIMUM
+# define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \
+ ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) \
+ : (t) 0))
+#endif
+#ifndef TYPE_MAXIMUM
+# define TYPE_MAXIMUM(t) ((t) (~ (t) 0 - TYPE_MINIMUM (t)))
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+# include <stdlib.h>
+#ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+#endif
+#if !EXIT_FAILURE
+# undef EXIT_FAILURE /* Sony NEWS-OS 4.0C defines EXIT_FAILURE to 0. */
+# define EXIT_FAILURE 1
+#endif
+#define EXIT_TROUBLE 2
+
+#include <limits.h>
+#ifndef SSIZE_MAX
+# define SSIZE_MAX TYPE_MAXIMUM (ssize_t)
+#endif
+
+#ifndef PTRDIFF_MAX
+# define PTRDIFF_MAX TYPE_MAXIMUM (ptrdiff_t)
+#endif
+#ifndef SIZE_MAX
+# define SIZE_MAX TYPE_MAXIMUM (size_t)
+#endif
+#ifndef UINTMAX_MAX
+# define UINTMAX_MAX TYPE_MAXIMUM (uintmax_t)
+#endif
+
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+
+/* CTYPE_DOMAIN (C) is nonzero if the unsigned char C can safely be given
+ as an argument to <ctype.h> macros like `isspace'. */
+# define CTYPE_DOMAIN(c) 1
+#define ISPRINT(c) (CTYPE_DOMAIN (c) && isprint (c))
+#define ISSPACE(c) (CTYPE_DOMAIN (c) && isspace (c))
+
+# define TOLOWER(c) tolower (c)
+
+/* ISDIGIT differs from isdigit, as follows:
+ - Its arg may be any int or unsigned int; it need not be an unsigned char.
+ - It's guaranteed to evaluate its argument exactly once.
+ - It's typically faster.
+ POSIX 1003.1-2001 says that only '0' through '9' are digits.
+ Prefer ISDIGIT to isdigit unless it's important to use the locale's
+ definition of `digit' even when the host does not conform to POSIX. */
+#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
+
+#undef MIN
+#undef MAX
+#define MIN(a, b) ((a) <= (b) ? (a) : (b))
+#define MAX(a, b) ((a) >= (b) ? (a) : (b))
+
+
+/* The integer type of a line number. Since files are read into main
+ memory, ptrdiff_t should be wide enough. */
+
+typedef ptrdiff_t lin;
+#define LIN_MAX PTRDIFF_MAX
+verify (lin_is_signed, TYPE_SIGNED (lin));
+verify (lin_is_wide_enough, sizeof (ptrdiff_t) <= sizeof (lin));
+verify (lin_is_printable_as_long, sizeof (lin) <= sizeof (long));
+
+#endif
diff --git a/src/gnudiff_xmalloc.cpp b/src/gnudiff_xmalloc.cpp
new file mode 100644
index 0000000..858faba
--- /dev/null
+++ b/src/gnudiff_xmalloc.cpp
@@ -0,0 +1,88 @@
+/* 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#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) PARAMS ((void)) = 0;
+void (*xalloc_fail_func)(void) = 0;
+
+
+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 == 0)
+ 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 == 0)
+ 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/hi16-app-kdiff3.png b/src/hi16-app-kdiff3.png
new file mode 100644
index 0000000..50e3397
--- /dev/null
+++ b/src/hi16-app-kdiff3.png
Binary files differ
diff --git a/src/hi32-app-kdiff3.png b/src/hi32-app-kdiff3.png
new file mode 100644
index 0000000..cd269b2
--- /dev/null
+++ b/src/hi32-app-kdiff3.png
Binary files differ
diff --git a/src/kdiff3.cpp b/src/kdiff3.cpp
new file mode 100644
index 0000000..5bc2102
--- /dev/null
+++ b/src/kdiff3.cpp
@@ -0,0 +1,992 @@
+/***************************************************************************
+ kdiff3.cpp - description
+ -------------------
+ begin : Don Jul 11 12:31:29 CEST 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 "difftextwindow.h"
+#include "mergeresultwindow.h"
+
+#include <iostream>
+
+// include files for QT
+#include <qdir.h>
+#include <qprinter.h>
+#include <qpainter.h>
+#include <qsplitter.h>
+#include <qlineedit.h>
+#include <qcheckbox.h>
+#include <qpushbutton.h>
+#include <qpopupmenu.h>
+#include <qlabel.h>
+#include <qtextedit.h>
+#include <qlayout.h>
+#include <qpaintdevicemetrics.h>
+
+// include files for KDE
+#include <kiconloader.h>
+#include <kmessagebox.h>
+#include <kfiledialog.h>
+#include <kmenubar.h>
+#include <kstatusbar.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kstdaction.h>
+#include <kcmdlineargs.h>
+#include <kprinter.h>
+//#include <kkeydialog.h>
+
+// application specific includes
+#include "kdiff3.h"
+#include "optiondialog.h"
+#include "fileaccess.h"
+#include "kdiff3_part.h"
+#include "directorymergewindow.h"
+#include "smalldialogs.h"
+
+#define ID_STATUS_MSG 1
+
+KActionCollection* KDiff3App::actionCollection()
+{
+ if ( m_pKDiff3Shell==0 )
+ return m_pKDiff3Part->actionCollection();
+ else
+ return m_pKDiff3Shell->actionCollection();
+}
+
+KStatusBar* KDiff3App::statusBar()
+{
+ if ( m_pKDiff3Shell==0 )
+ return 0;
+ else
+ return m_pKDiff3Shell->statusBar();
+}
+
+KToolBar* KDiff3App::toolBar(const char* toolBarId )
+{
+ if ( m_pKDiff3Shell==0 )
+ return 0;
+ else
+ return m_pKDiff3Shell->toolBar( toolBarId );
+}
+
+bool KDiff3App::isPart()
+{
+ return m_pKDiff3Shell==0;
+}
+
+bool KDiff3App::isFileSaved()
+{
+ return m_bFileSaved;
+}
+
+KDiff3App::KDiff3App(QWidget* pParent, const char* name, KDiff3Part* pKDiff3Part )
+ :QSplitter(pParent, name) //previously KMainWindow
+{
+ m_pKDiff3Part = pKDiff3Part;
+ m_pKDiff3Shell = dynamic_cast<KParts::MainWindow*>(pParent);
+
+ setCaption( "KDiff3" );
+
+ m_pMainSplitter = 0;
+ m_pDirectoryMergeWindow = 0;
+ m_pCornerWidget = 0;
+ m_pMainWidget = 0;
+ m_pDiffTextWindow1 = 0;
+ m_pDiffTextWindow2 = 0;
+ m_pDiffTextWindow3 = 0;
+ m_pDiffTextWindowFrame1 = 0;
+ m_pDiffTextWindowFrame2 = 0;
+ m_pDiffTextWindowFrame3 = 0;
+ m_pDiffWindowSplitter = 0;
+ m_pOverview = 0;
+ m_bTripleDiff = false;
+ m_pMergeResultWindow = 0;
+ m_pMergeWindowFrame = 0;
+ m_bOutputModified = false;
+ m_bFileSaved = false;
+ m_bTimerBlock = false;
+ m_pHScrollBar = 0;
+
+ // Needed before any file operations via FileAccess happen.
+ if (!g_pProgressDialog)
+ {
+ g_pProgressDialog = new ProgressDialog(0);
+ g_pProgressDialog->setStayHidden( true );
+ }
+
+ // All default values must be set before calling readOptions().
+ m_pOptionDialog = new OptionDialog( m_pKDiff3Shell!=0, this );
+ connect( m_pOptionDialog, SIGNAL(applyClicked()), this, SLOT(slotRefresh()) );
+
+ m_pOptionDialog->readOptions( isPart() ? m_pKDiff3Part->instance()->config() : kapp->config() );
+
+ // Option handling: Only when pParent==0 (no parent)
+ KCmdLineArgs *args = isPart() ? 0 : KCmdLineArgs::parsedArgs();
+
+ if (args)
+ {
+ QString s;
+ QString title;
+ if ( args->isSet("confighelp") )
+ {
+ s = m_pOptionDialog->calcOptionHelp();
+ title = i18n("Current Configuration:");
+ }
+ else
+ {
+ s = m_pOptionDialog->parseOptions( args->getOptionList("cs") );
+ title = i18n("Config Option Error:");
+ }
+ if (!s.isEmpty())
+ {
+#ifdef _WIN32
+ // A windows program has no console
+ //KMessageBox::information(0, s,i18n("KDiff3-Usage"));
+ QDialog* pDialog = new QDialog(this,"",true,Qt::WDestructiveClose);
+ pDialog->setCaption(title);
+ QVBoxLayout* pVBoxLayout = new QVBoxLayout( pDialog );
+ QTextEdit* pTextEdit = new QTextEdit(pDialog);
+ pTextEdit->setText(s);
+ pTextEdit->setReadOnly(true);
+ pTextEdit->setWordWrap(QTextEdit::NoWrap);
+ pVBoxLayout->addWidget(pTextEdit);
+ pDialog->resize(600,400);
+ pDialog->exec();
+#else
+ std::cerr << title.latin1() << std::endl;
+ std::cerr << s.latin1() << std::endl;
+#endif
+ exit(1);
+ }
+ }
+
+ m_sd1.setOptionDialog(m_pOptionDialog);
+ m_sd2.setOptionDialog(m_pOptionDialog);
+ m_sd3.setOptionDialog(m_pOptionDialog);
+
+ if (args!=0)
+ {
+ m_outputFilename = args->getOption("output");
+ if ( m_outputFilename.isEmpty() )
+ m_outputFilename = args->getOption("out");
+ }
+
+ m_bAutoFlag = args!=0 && args->isSet("auto");
+ m_bAutoMode = m_bAutoFlag || m_pOptionDialog->m_bAutoSaveAndQuitOnMergeWithoutConflicts;
+ if ( m_bAutoMode && m_outputFilename.isEmpty() )
+ {
+ if ( m_bAutoFlag )
+ {
+ //KMessageBox::information(this, i18n("Option --auto used, but no output file specified."));
+ std::cerr << i18n("Option --auto used, but no output file specified.").ascii()<<std::endl;
+ }
+ m_bAutoMode = false;
+ }
+ g_pProgressDialog->setStayHidden( m_bAutoMode );
+
+ if ( m_outputFilename.isEmpty() && args!=0 && args->isSet("merge") )
+ {
+ m_outputFilename = "unnamed.txt";
+ m_bDefaultFilename = true;
+ }
+ else
+ m_bDefaultFilename = false;
+
+ g_bAutoSolve = args!=0 && !args->isSet("qall"); // Note that this is effective only once.
+
+ if ( args!=0 )
+ {
+ m_sd1.setFilename( args->getOption("base") );
+ if ( m_sd1.isEmpty() )
+ {
+ if ( args->count() > 0 ) m_sd1.setFilename( args->url(0).url() ); // args->arg(0)
+ if ( args->count() > 1 ) m_sd2.setFilename( args->url(1).url() );
+ if ( args->count() > 2 ) m_sd3.setFilename( args->url(2).url() );
+ }
+ else
+ {
+ if ( args->count() > 0 ) m_sd2.setFilename( args->url(0).url() );
+ if ( args->count() > 1 ) m_sd3.setFilename( args->url(1).url() );
+ }
+
+
+ QCStringList aliasList = args->getOptionList("fname");
+ QCStringList::Iterator ali = aliasList.begin();
+
+ QString an1 = args->getOption("L1");
+ if ( !an1.isEmpty() ) { m_sd1.setAliasName(an1); }
+ else if ( ali != aliasList.end() ) { m_sd1.setAliasName(*ali); ++ali; }
+
+ QString an2 = args->getOption("L2");
+ if ( !an2.isEmpty() ) { m_sd2.setAliasName(an2); }
+ else if ( ali != aliasList.end() ) { m_sd2.setAliasName(*ali); ++ali; }
+
+ QString an3 = args->getOption("L3");
+ if ( !an3.isEmpty() ) { m_sd3.setAliasName(an3); }
+ else if ( ali != aliasList.end() ) { m_sd3.setAliasName(*ali); ++ali; }
+ }
+ ///////////////////////////////////////////////////////////////////
+ // call inits to invoke all other construction parts
+ initActions(actionCollection());
+ initStatusBar();
+
+ m_pFindDialog = new FindDialog( this );
+ connect( m_pFindDialog, SIGNAL(findNext()), this, SLOT(slotEditFindNext()));
+
+ autoAdvance->setChecked( m_pOptionDialog->m_bAutoAdvance );
+ showWhiteSpaceCharacters->setChecked( m_pOptionDialog->m_bShowWhiteSpaceCharacters );
+ showWhiteSpace->setChecked( m_pOptionDialog->m_bShowWhiteSpace );
+ showWhiteSpaceCharacters->setEnabled( m_pOptionDialog->m_bShowWhiteSpace );
+ showLineNumbers->setChecked( m_pOptionDialog->m_bShowLineNumbers );
+ wordWrap->setChecked( m_pOptionDialog->m_bWordWrap );
+ if ( ! isPart() )
+ {
+ viewToolBar->setChecked( m_pOptionDialog->m_bShowToolBar );
+ viewStatusBar->setChecked( m_pOptionDialog->m_bShowStatusBar );
+ slotViewToolBar();
+ slotViewStatusBar();
+ if( toolBar("mainToolBar")!=0 )
+ toolBar("mainToolBar")->setBarPos( (KToolBar::BarPosition) m_pOptionDialog->m_toolBarPos );
+/* QSize size = m_pOptionDialog->m_geometry;
+ QPoint pos = m_pOptionDialog->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; //new QSplitter(this);
+ m_pMainSplitter->setOrientation( Vertical );
+// setCentralWidget( m_pMainSplitter );
+ m_pDirectoryMergeSplitter = new QSplitter( m_pMainSplitter );
+ m_pDirectoryMergeSplitter->setOrientation( Horizontal );
+ m_pDirectoryMergeWindow = new DirectoryMergeWindow( m_pDirectoryMergeSplitter, m_pOptionDialog,
+ KApplication::kApplication()->iconLoader() );
+ m_pDirectoryMergeInfo = new DirectoryMergeInfo( m_pDirectoryMergeSplitter );
+ m_pDirectoryMergeWindow->setDirectoryMergeInfo( m_pDirectoryMergeInfo );
+ connect( m_pDirectoryMergeWindow, SIGNAL(startDiffMerge(QString,QString,QString,QString,QString,QString,QString,TotalDiffStatus*)),
+ this, SLOT( slotFileOpen2(QString,QString,QString,QString,QString,QString,QString,TotalDiffStatus*)));
+ connect( m_pDirectoryMergeWindow, SIGNAL(selectionChanged()), this, SLOT(slotUpdateAvailabilities()));
+ connect( m_pDirectoryMergeWindow, SIGNAL(currentChanged(QListViewItem*)), this, SLOT(slotUpdateAvailabilities()));
+ connect( m_pDirectoryMergeWindow, SIGNAL(checkIfCanContinue(bool*)), this, SLOT(slotCheckIfCanContinue(bool*)));
+ connect( m_pDirectoryMergeWindow, SIGNAL(updateAvailabilities()), this, SLOT(slotUpdateAvailabilities()));
+ connect( m_pDirectoryMergeWindow, SIGNAL(statusBarMessage(const QString&)), this, SLOT(slotStatusMsg(const QString&)));
+
+ m_pDirectoryMergeWindow->initDirectoryMergeActions( this, actionCollection() );
+
+ if ( args!=0 ) args->clear(); // Free up some memory.
+
+ if (m_pKDiff3Shell==0)
+ {
+ completeInit();
+ }
+}
+
+
+void KDiff3App::completeInit( const QString& fn1, const QString& fn2, const QString& fn3 )
+{
+ if (m_pKDiff3Shell!=0)
+ {
+ QSize size=m_pOptionDialog->m_geometry;
+ QPoint pos=m_pOptionDialog->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)
+ {
+ if ( m_pOptionDialog->m_bMaximised )
+ m_pKDiff3Shell->showMaximized();
+ 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 )
+ {
+ std::cerr << i18n("Option --auto ignored for directory comparison.").ascii()<<std::endl;
+ m_bAutoMode = false;
+ }
+ if (!m_bDirCompare)
+ {
+ m_pDirectoryMergeSplitter->hide();
+
+ init( m_bAutoMode );
+ if ( m_bAutoMode )
+ {
+ SourceData* pSD=0;
+ if ( m_sd3.isEmpty() )
+ {
+ if ( m_totalDiffStatus.bBinaryAEqB ){ pSD = &m_sd1; }
+ }
+ else
+ {
+ if ( m_totalDiffStatus.bBinaryBEqC ){ pSD = &m_sd3; } // B==C (assume A is old)
+ else if ( m_totalDiffStatus.bBinaryAEqB ){ pSD = &m_sd3; } // assuming C has changed
+ else if ( m_totalDiffStatus.bBinaryAEqC ){ pSD = &m_sd2; } // assuming B has changed
+ }
+
+ if ( pSD!=0 )
+ {
+ // Save this file directly, not via the merge result window.
+ bool bSuccess = false;
+ FileAccess fa( m_outputFilename );
+ if ( m_pOptionDialog->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 )
+ {
+ bool bSuccess = m_pMergeResultWindow->saveDocument( m_pMergeResultWindowTitle->getFileName(), m_pMergeResultWindowTitle->getEncoding() );
+ if ( bSuccess ) ::exit(0);
+ }
+ }
+ }
+ m_bAutoMode = false;
+
+ if (m_pKDiff3Shell)
+ {
+ if ( m_pOptionDialog->m_bMaximised )
+ m_pKDiff3Shell->showMaximized();
+ else
+ m_pKDiff3Shell->show();
+ }
+
+ g_pProgressDialog->setStayHidden( false );
+
+ if (statusBar() !=0 )
+ statusBar()->setSizeGripEnabled(true);
+
+ slotClipboardChanged(); // For initialisation.
+
+ slotUpdateAvailabilities();
+
+ if ( ! m_bDirCompare && m_pKDiff3Shell!=0 )
+ {
+ 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()
+{
+
+}
+
+void KDiff3App::initActions( KActionCollection* ac )
+{
+ if (ac==0) KMessageBox::error(0, "actionCollection==0");
+
+ fileOpen = KStdAction::open(this, SLOT(slotFileOpen()), ac);
+ fileOpen->setStatusText(i18n("Opens documents for comparison..."));
+
+ fileReload = new KAction(i18n("Reload"), /*QIconSet(QPixmap(reloadIcon)),*/ Key_F5, this, SLOT(slotReload()), ac, "file_reload");
+
+ fileSave = KStdAction::save(this, SLOT(slotFileSave()), ac);
+ fileSave->setStatusText(i18n("Saves the merge result. All conflicts must be solved!"));
+ fileSaveAs = KStdAction::saveAs(this, SLOT(slotFileSaveAs()), ac);
+ fileSaveAs->setStatusText(i18n("Saves the current document as..."));
+ filePrint = KStdAction::print(this, SLOT(slotFilePrint()), ac);
+ filePrint->setStatusText(i18n("Print the differences"));
+ fileQuit = KStdAction::quit(this, SLOT(slotFileQuit()), ac);
+ fileQuit->setStatusText(i18n("Quits the application"));
+ editCut = KStdAction::cut(this, SLOT(slotEditCut()), ac);
+ editCut->setStatusText(i18n("Cuts the selected section and puts it to the clipboard"));
+ editCopy = KStdAction::copy(this, SLOT(slotEditCopy()), ac);
+ editCopy->setStatusText(i18n("Copies the selected section to the clipboard"));
+ editPaste = KStdAction::paste(this, SLOT(slotEditPaste()), ac);
+ editPaste->setStatusText(i18n("Pastes the clipboard contents to actual position"));
+ editSelectAll = KStdAction::selectAll(this, SLOT(slotEditSelectAll()), ac);
+ editSelectAll->setStatusText(i18n("Select everything in current window"));
+ editFind = KStdAction::find(this, SLOT(slotEditFind()), ac);
+ editFind->setStatusText(i18n("Search for a string"));
+ editFindNext = KStdAction::findNext(this, SLOT(slotEditFindNext()), ac);
+ editFindNext->setStatusText(i18n("Search again for the string"));
+ viewToolBar = KStdAction::showToolbar(this, SLOT(slotViewToolBar()), ac);
+ viewToolBar->setStatusText(i18n("Enables/disables the toolbar"));
+ viewStatusBar = KStdAction::showStatusbar(this, SLOT(slotViewStatusBar()), ac);
+ viewStatusBar->setStatusText(i18n("Enables/disables the statusbar"));
+ KStdAction::keyBindings(this, SLOT(slotConfigureKeys()), ac);
+ KAction* pAction = KStdAction::preferences(this, SLOT(slotConfigure()), ac );
+ if ( isPart() )
+ pAction->setText(i18n("Configure KDiff3..."));
+
+
+#include "xpm/downend.xpm"
+#include "xpm/currentpos.xpm"
+#include "xpm/down1arrow.xpm"
+#include "xpm/down2arrow.xpm"
+#include "xpm/upend.xpm"
+#include "xpm/up1arrow.xpm"
+#include "xpm/up2arrow.xpm"
+#include "xpm/prevunsolved.xpm"
+#include "xpm/nextunsolved.xpm"
+#include "xpm/iconA.xpm"
+#include "xpm/iconB.xpm"
+#include "xpm/iconC.xpm"
+#include "xpm/autoadvance.xpm"
+#include "xpm/showwhitespace.xpm"
+#include "xpm/showwhitespacechars.xpm"
+#include "xpm/showlinenumbers.xpm"
+//#include "reload.xpm"
+
+ goCurrent = new KAction(i18n("Go to Current Delta"), QIconSet(QPixmap(currentpos)), CTRL+Key_Space, this, SLOT(slotGoCurrent()), ac, "go_current");
+ goTop = new KAction(i18n("Go to First Delta"), QIconSet(QPixmap(upend)), 0, this, SLOT(slotGoTop()), ac, "go_top");
+ goBottom = new KAction(i18n("Go to Last Delta"), QIconSet(QPixmap(downend)), 0, this, SLOT(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 = new KAction(i18n("Go to Previous Delta"), QIconSet(QPixmap(up1arrow)), CTRL+Key_Up, this, SLOT(slotGoPrevDelta()), ac, "go_prev_delta");
+ goPrevDelta->setToolTip( goPrevDelta->text() + omitsWhitespace );
+ goNextDelta = new KAction(i18n("Go to Next Delta"), QIconSet(QPixmap(down1arrow)), CTRL+Key_Down, this, SLOT(slotGoNextDelta()), ac, "go_next_delta");
+ goNextDelta->setToolTip( goNextDelta->text() + omitsWhitespace );
+ goPrevConflict = new KAction(i18n("Go to Previous Conflict"), QIconSet(QPixmap(up2arrow)), CTRL+Key_PageUp, this, SLOT(slotGoPrevConflict()), ac, "go_prev_conflict");
+ goPrevConflict->setToolTip( goPrevConflict->text() + omitsWhitespace );
+ goNextConflict = new KAction(i18n("Go to Next Conflict"), QIconSet(QPixmap(down2arrow)), CTRL+Key_PageDown, this, SLOT(slotGoNextConflict()), ac, "go_next_conflict");
+ goNextConflict->setToolTip( goNextConflict->text() + omitsWhitespace );
+ goPrevUnsolvedConflict = new KAction(i18n("Go to Previous Unsolved Conflict"), QIconSet(QPixmap(prevunsolved)), 0, this, SLOT(slotGoPrevUnsolvedConflict()), ac, "go_prev_unsolved_conflict");
+ goPrevUnsolvedConflict->setToolTip( goPrevUnsolvedConflict->text() + includeWhitespace );
+ goNextUnsolvedConflict = new KAction(i18n("Go to Next Unsolved Conflict"), QIconSet(QPixmap(nextunsolved)), 0, this, SLOT(slotGoNextUnsolvedConflict()), ac, "go_next_unsolved_conflict");
+ goNextUnsolvedConflict->setToolTip( goNextUnsolvedConflict->text() + includeWhitespace );
+ chooseA = new KToggleAction(i18n("Select Line(s) From A"), QIconSet(QPixmap(iconA)), CTRL+Key_1, this, SLOT(slotChooseA()), ac, "merge_choose_a");
+ chooseB = new KToggleAction(i18n("Select Line(s) From B"), QIconSet(QPixmap(iconB)), CTRL+Key_2, this, SLOT(slotChooseB()), ac, "merge_choose_b");
+ chooseC = new KToggleAction(i18n("Select Line(s) From C"), QIconSet(QPixmap(iconC)), CTRL+Key_3, this, SLOT(slotChooseC()), ac, "merge_choose_c");
+ autoAdvance = new KToggleAction(i18n("Automatically Go to Next Unsolved Conflict After Source Selection"), QIconSet(QPixmap(autoadvance)), 0, this, SLOT(slotAutoAdvanceToggled()), ac, "merge_autoadvance");
+
+ showWhiteSpaceCharacters = new KToggleAction(i18n("Show Space && Tabulator Characters for Differences"), QIconSet(QPixmap(showwhitespacechars)), 0, this, SLOT(slotShowWhiteSpaceToggled()), ac, "diff_show_whitespace_characters");
+ showWhiteSpace = new KToggleAction(i18n("Show White Space"), QIconSet(QPixmap(showwhitespace)), 0, this, SLOT(slotShowWhiteSpaceToggled()), ac, "diff_show_whitespace");
+
+ showLineNumbers = new KToggleAction(i18n("Show Line Numbers"), QIconSet(QPixmap(showlinenumbers)), 0, this, SLOT(slotShowLineNumbersToggled()), ac, "diff_showlinenumbers");
+ chooseAEverywhere = new KAction(i18n("Choose A Everywhere"), CTRL+SHIFT+Key_1, this, SLOT(slotChooseAEverywhere()), ac, "merge_choose_a_everywhere");
+ chooseBEverywhere = new KAction(i18n("Choose B Everywhere"), CTRL+SHIFT+Key_2, this, SLOT(slotChooseBEverywhere()), ac, "merge_choose_b_everywhere");
+ chooseCEverywhere = new KAction(i18n("Choose C Everywhere"), CTRL+SHIFT+Key_3, this, SLOT(slotChooseCEverywhere()), ac, "merge_choose_c_everywhere");
+ chooseAForUnsolvedConflicts = new KAction(i18n("Choose A for All Unsolved Conflicts"), 0, this, SLOT(slotChooseAForUnsolvedConflicts()), ac, "merge_choose_a_for_unsolved_conflicts");
+ chooseBForUnsolvedConflicts = new KAction(i18n("Choose B for All Unsolved Conflicts"), 0, this, SLOT(slotChooseBForUnsolvedConflicts()), ac, "merge_choose_b_for_unsolved_conflicts");
+ chooseCForUnsolvedConflicts = new KAction(i18n("Choose C for All Unsolved Conflicts"), 0, this, SLOT(slotChooseCForUnsolvedConflicts()), ac, "merge_choose_c_for_unsolved_conflicts");
+ chooseAForUnsolvedWhiteSpaceConflicts = new KAction(i18n("Choose A for All Unsolved Whitespace Conflicts"), 0, this, SLOT(slotChooseAForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_a_for_unsolved_whitespace_conflicts");
+ chooseBForUnsolvedWhiteSpaceConflicts = new KAction(i18n("Choose B for All Unsolved Whitespace Conflicts"), 0, this, SLOT(slotChooseBForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_b_for_unsolved_whitespace_conflicts");
+ chooseCForUnsolvedWhiteSpaceConflicts = new KAction(i18n("Choose C for All Unsolved Whitespace Conflicts"), 0, this, SLOT(slotChooseCForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_c_for_unsolved_whitespace_conflicts");
+ autoSolve = new KAction(i18n("Automatically Solve Simple Conflicts"), 0, this, SLOT(slotAutoSolve()), ac, "merge_autosolve");
+ unsolve = new KAction(i18n("Set Deltas to Conflicts"), 0, this, SLOT(slotUnsolve()), ac, "merge_autounsolve");
+ mergeRegExp = new KAction(i18n("Run Regular Expression Auto Merge"), 0, this, SLOT(slotRegExpAutoMerge()),ac, "merge_regexp_automerge" );
+ mergeHistory = new KAction(i18n("Automatically Solve History Conflicts"), 0, this, SLOT(slotMergeHistory()), ac, "merge_versioncontrol_history" );
+ splitDiff = new KAction(i18n("Split Diff At Selection"), 0, this, SLOT(slotSplitDiff()), ac, "merge_splitdiff");
+ joinDiffs = new KAction(i18n("Join Selected Diffs"), 0, this, SLOT(slotJoinDiffs()), ac, "merge_joindiffs");
+
+ showWindowA = new KToggleAction(i18n("Show Window A"), 0, this, SLOT(slotShowWindowAToggled()), ac, "win_show_a");
+ showWindowB = new KToggleAction(i18n("Show Window B"), 0, this, SLOT(slotShowWindowBToggled()), ac, "win_show_b");
+ showWindowC = new KToggleAction(i18n("Show Window C"), 0, this, SLOT(slotShowWindowCToggled()), ac, "win_show_c");
+ winFocusNext = new KAction(i18n("Focus Next Window"), ALT+Key_Right, this, SLOT(slotWinFocusNext()), ac, "win_focus_next");
+
+ overviewModeNormal = new KToggleAction(i18n("Normal Overview"), 0, this, SLOT(slotOverviewNormal()), ac, "diff_overview_normal");
+ overviewModeAB = new KToggleAction(i18n("A vs. B Overview"), 0, this, SLOT(slotOverviewAB()), ac, "diff_overview_ab");
+ overviewModeAC = new KToggleAction(i18n("A vs. C Overview"), 0, this, SLOT(slotOverviewAC()), ac, "diff_overview_ac");
+ overviewModeBC = new KToggleAction(i18n("B vs. C Overview"), 0, this, SLOT(slotOverviewBC()), ac, "diff_overview_bc");
+ wordWrap = new KToggleAction(i18n("Word Wrap Diff Windows"), 0, this, SLOT(slotWordWrapToggled()), ac, "diff_wordwrap");
+ addManualDiffHelp = new KAction(i18n("Add Manual Diff Alignment"), Qt::CTRL+Qt::Key_Y, this, SLOT(slotAddManualDiffHelp()), ac, "diff_add_manual_diff_help");
+ clearManualDiffHelpList = new KAction(i18n("Clear All Manual Diff Alignments"), Qt::CTRL+Qt::SHIFT+Qt::Key_Y, this, SLOT(slotClearManualDiffHelpList()), ac, "diff_clear_manual_diff_help_list");
+
+#ifdef _WIN32
+ new KAction(i18n("Focus Next Window"), Qt::CTRL+Qt::Key_Tab, this, SLOT(slotWinFocusNext()), ac, "win_focus_next", false, false);
+#endif
+ winFocusPrev = new KAction(i18n("Focus Prev Window"), Qt::ALT+Qt::Key_Left, this, SLOT(slotWinFocusPrev()), ac, "win_focus_prev");
+ winToggleSplitOrientation = new KAction(i18n("Toggle Split Orientation"), 0, this, SLOT(slotWinToggleSplitterOrientation()), ac, "win_toggle_split_orientation");
+
+ dirShowBoth = new KToggleAction(i18n("Dir && Text Split Screen View"), 0, this, SLOT(slotDirShowBoth()), ac, "win_dir_show_both");
+ dirShowBoth->setChecked( true );
+ dirViewToggle = new KAction(i18n("Toggle Between Dir && Text View"), 0, this, SLOT(slotDirViewToggle()), actionCollection(), "win_dir_view_toggle");
+
+ m_pMergeEditorPopupMenu = new QPopupMenu( this );
+ chooseA->plug( m_pMergeEditorPopupMenu );
+ chooseB->plug( m_pMergeEditorPopupMenu );
+ chooseC->plug( m_pMergeEditorPopupMenu );
+}
+
+
+void KDiff3App::showPopupMenu( const QPoint& point )
+{
+ m_pMergeEditorPopupMenu->popup( point );
+}
+
+void KDiff3App::initStatusBar()
+{
+ ///////////////////////////////////////////////////////////////////
+ // STATUSBAR
+ if (statusBar() !=0 )
+ statusBar()->message( i18n("Ready.") );
+}
+
+void KDiff3App::saveOptions( KConfig* config )
+{
+ if ( !m_bAutoMode )
+ {
+ if (!isPart())
+ {
+ m_pOptionDialog->m_bMaximised = m_pKDiff3Shell->isMaximized();
+ if( ! m_pKDiff3Shell->isMaximized() && m_pKDiff3Shell->isVisible() )
+ {
+ m_pOptionDialog->m_geometry = m_pKDiff3Shell->size();
+ m_pOptionDialog->m_position = m_pKDiff3Shell->pos();
+ }
+ if ( toolBar("mainToolBar")!=0 )
+ m_pOptionDialog->m_toolBarPos = (int) toolBar("mainToolBar")->barPos();
+ }
+
+ m_pOptionDialog->saveOptions( config );
+ }
+}
+
+
+
+
+bool KDiff3App::queryClose()
+{
+ saveOptions( isPart() ? m_pKDiff3Part->instance()->config() : kapp->config() );
+
+ if(m_bOutputModified)
+ {
+ int result = KMessageBox::warningYesNoCancel(this,
+ i18n("The merge result hasn't been saved."),
+ i18n("Warning"), i18n("Save && Quit"), 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"), i18n("Quit"), 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() );
+ 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 = KFileDialog::getSaveURL( QDir::currentDirPath(), 0, this, i18n("Save As...") ).url();
+ if(!s.isEmpty())
+ {
+ m_outputFilename = s;
+ m_pMergeResultWindowTitle->setFileName( m_outputFilename );
+ bool bSuccess = m_pMergeResultWindow->saveDocument( m_outputFilename, m_pMergeResultWindowTitle->getEncoding() );
+ if ( bSuccess )
+ {
+ m_bOutputModified = false;
+ if ( m_bDirCompare )
+ m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename);
+ }
+ //setCaption(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, 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 (unsigned int p=0; p<headerText.length(); )
+ {
+ QString s = headerText.mid(p);
+ unsigned 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.resetXForm();
+}
+
+void KDiff3App::slotFilePrint()
+{
+ if ( !m_pDiffTextWindow1 )
+ return;
+
+ KPrinter printer;
+
+ int firstSelectionD3LIdx = -1;
+ int lastSelectionD3LIdx = -1;
+ if ( m_pDiffTextWindow1 ) { 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); }
+#ifdef KREPLACEMENTS_H // Currently PrintSelection is not supported in KDEs print dialog.
+ if ( firstSelectionD3LIdx>=0 )
+ {
+ printer.setOptionEnabled(KPrinter::PrintSelection,true);
+ }
+#endif
+
+ printer.setPageSelection(KPrinter::ApplicationSide);
+ printer.setMinMax(1,10000);
+ printer.setCurrentPage(10000);
+
+ 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 ( printer.setup( this ) )
+ {
+ slotStatusMsg( i18n( "Printing..." ) );
+ // create a painter to paint on the printer object
+ MyPainter painter( 0, m_pOptionDialog->m_bRightToLeftLanguage, width(), fontMetrics().width('W') );
+
+ // start painting
+ if( !painter.begin( &printer ) ) { // paint on printer
+ slotStatusMsg( i18n( "Printing aborted." ) );
+ return;
+ }
+ QPaintDeviceMetrics metrics( painter.device() );
+ int dpiy = metrics.logicalDpiY();
+ int columnDistance = (int) ( (0.5/2.54)*dpiy ); // 0.5 cm between the columns
+
+ int columns = m_bTripleDiff ? 3 : 2;
+ int columnWidth = ( metrics.width() - (columns-1)*columnDistance ) / columns;
+
+ QFont f = m_pOptionDialog->m_font;
+ f.setPointSizeFloat(f.pointSizeFloat()-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, metrics.width(), metrics.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.height();
+ int charactersPerLine = columnWidth / fm.width("W");
+ if ( m_pOptionDialog->m_bWordWrap )
+ {
+ // For printing the lines are wrapped differently (this invalidates the first line)
+ recalcWordWrap( charactersPerLine );
+ }
+
+ int totalNofLines = max2(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines());
+ if ( m_bTripleDiff && m_pDiffTextWindow3)
+ totalNofLines = max2(totalNofLines, m_pDiffTextWindow3->getNofLines());
+
+ QValueList<int> pageList = printer.pageList();
+
+ bool bPrintCurrentPage=false;
+ bool bFirstPrintedPage = false;
+
+ bool bPrintSelection = false;
+ int totalNofPages = (totalNofLines+linesPerPage-1) / linesPerPage;
+ int line=-1;
+ int selectionEndLine = -1;
+
+#ifdef KREPLACEMENTS_H
+ if ( printer.printRange()==KPrinter::AllPages )
+ {
+ pageList.clear();
+ for(int i=0; i<totalNofPages; ++i)
+ {
+ pageList.push_back(i+1);
+ }
+ }
+
+ if ( printer.printRange()==KPrinter::Selection )
+#else
+ if ( !pageList.empty() && pageList.front()==9999 )
+#endif
+ {
+ 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;
+
+ QValueList<int>::iterator pageListIt = pageList.begin();
+ for(;;)
+ {
+ 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_pOptionDialog->m_colorA );
+ QString headerText1 = m_sd1.getAliasName() + ", "+topLineText+": " + QString::number(m_pDiffTextWindow1->calcTopLineInFile(line)+1);
+ printDiffTextWindow( painter, view1, headerText1, m_pDiffTextWindow1, line, linesPerPage, m_pOptionDialog->m_fgColor );
+
+ painter.setPen( m_pOptionDialog->m_colorB );
+ QString headerText2 = m_sd2.getAliasName() + ", "+topLineText+": " + QString::number(m_pDiffTextWindow2->calcTopLineInFile(line)+1);
+ printDiffTextWindow( painter, view2, headerText2, m_pDiffTextWindow2, line, linesPerPage, m_pOptionDialog->m_fgColor );
+
+ if ( m_bTripleDiff && m_pDiffTextWindow3 )
+ {
+ painter.setPen( m_pOptionDialog->m_colorC );
+ QString headerText3 = m_sd3.getAliasName() + ", "+topLineText+": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line)+1);
+ printDiffTextWindow( painter, view3, headerText3, m_pDiffTextWindow3, line, linesPerPage, m_pOptionDialog->m_fgColor );
+ }
+ painter.setClipping(false);
+
+ painter.setPen( m_pOptionDialog->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_pOptionDialog->m_bWordWrap )
+ {
+ recalcWordWrap();
+ m_pDiffVScrollBar->setValue( m_pDiffTextWindow1->convertDiff3LineIdxToLine( currentFirstD3LIdx ) );
+ }
+
+ slotStatusMsg( i18n( "Printing completed." ) );
+ }
+ else
+ {
+ slotStatusMsg( i18n( "Printing aborted." ) );
+ }
+}
+
+void KDiff3App::slotFileQuit()
+{
+ slotStatusMsg(i18n("Exiting..."));
+
+ if( !queryClose() )
+ return; // Don't quit
+
+ KApplication::exit( isFileSaved() ? 0 : 1 );
+}
+
+
+
+void KDiff3App::slotViewToolBar()
+{
+ slotStatusMsg(i18n("Toggling toolbar..."));
+ m_pOptionDialog->m_bShowToolBar = viewToolBar->isChecked();
+ ///////////////////////////////////////////////////////////////////
+ // turn Toolbar on or off
+ if ( toolBar("mainToolBar") !=0 )
+ {
+ if(!m_pOptionDialog->m_bShowToolBar)
+ {
+ toolBar("mainToolBar")->hide();
+ }
+ else
+ {
+ toolBar("mainToolBar")->show();
+ }
+ }
+
+ slotStatusMsg(i18n("Ready."));
+}
+
+void KDiff3App::slotViewStatusBar()
+{
+ slotStatusMsg(i18n("Toggle the statusbar..."));
+ m_pOptionDialog->m_bShowStatusBar = viewStatusBar->isChecked();
+ ///////////////////////////////////////////////////////////////////
+ //turn Statusbar on or off
+ if (statusBar() !=0 )
+ {
+ if(!viewStatusBar->isChecked())
+ {
+ statusBar()->hide();
+ }
+ else
+ {
+ statusBar()->show();
+ }
+ }
+
+ slotStatusMsg(i18n("Ready."));
+}
+
+
+void KDiff3App::slotStatusMsg(const QString &text)
+{
+ ///////////////////////////////////////////////////////////////////
+ // change status message permanently
+ if (statusBar() !=0 )
+ {
+ statusBar()->clear();
+ statusBar()->message( text );
+ }
+}
+
+
+
+
+#include "kdiff3.moc"
diff --git a/src/kdiff3.desktop b/src/kdiff3.desktop
new file mode 100644
index 0000000..8901094
--- /dev/null
+++ b/src/kdiff3.desktop
@@ -0,0 +1,91 @@
+
+[Desktop Entry]
+Encoding=UTF-8
+Name=KDiff3
+Name[hi]=के-डिफ3
+Name[sv]=Kdiff3
+Name[ta]=கேடிஃப்3
+Name[xx]=xxKDiff3xx
+GenericName=Diff/Patch Frontend
+GenericName[ar]=واجهة أمامية لبرامج Diff/Patch
+GenericName[bg]=Интерфейс на Diff/Patch
+GenericName[bs]=Interfejs za Diff/Patch
+GenericName[ca]=Interfície per a diff/patch
+GenericName[cs]=Rozhraní pro Diff/Patch
+GenericName[cy]=Blaen Gwahaniaethau/Clytiau
+GenericName[da]=Forende for diff/patch
+GenericName[de]=Graphische Oberfläche zu Diff/Patch
+GenericName[el]=Σύστημα υποστήριξης χρήστη για τα Diff/Patch
+GenericName[es]=Interfaz Diff/Patch
+GenericName[et]=Võrdlemise ja liitmise rakendus
+GenericName[fr]=Interface graphique à diff / patch
+GenericName[ga]=Comhéadan Diff/Patch
+GenericName[gl]=Interface para Diff/Patch
+GenericName[hi]=डिफ/पैच फ्रन्टएण्ड
+GenericName[hu]=Grafikus diff/patch
+GenericName[is]=Diff/Patch viðmót
+GenericName[it]=Interfaccia per diff/patch
+GenericName[ja]=Diff/Patch フロントエンド
+GenericName[lt]=Diff/Patch naudotojo sąsaja
+GenericName[ms]=Bahagian Depan Beza/Tampal
+GenericName[nb]=Endrings-/lappeprogram
+GenericName[nl]=Een schil voor Diff/Patch
+GenericName[pa]=Diff/Patch ਮੁੱਖ
+GenericName[pl]=Interfejs do programów Diff/Patch
+GenericName[pt]=Interface do Diff/Patch
+GenericName[pt_BR]=Interface do Diff/Patch
+GenericName[ru]=Графический интерфейс к Diff и Patch
+GenericName[sk]=Rozhranie pre diff/patch
+GenericName[sr]=Интерфејс за diff и patch
+GenericName[sr@Latn]=Interfejs za diff i patch
+GenericName[sv]=Jämförelse- och programfixgränssnitt
+GenericName[ta]= Diff/Patch Frontend
+GenericName[tg]=Интерфейси графикӣ ба Diff ва Patch
+GenericName[tr]=Diff/Patch Arayüzü
+GenericName[uk]=Інтерфейс до diff/patch
+GenericName[xx]=xxDiff/Patch Frontendxx
+GenericName[zh_CN]=Diff/Patch 前端
+GenericName[zu]=Diff/PatchIsiqalo sokugcina
+Exec=kdiff3 %i %m -caption "%c"
+Icon=kdiff3
+Type=Application
+Categories=Qt;KDE;Development;
+DocPath=kdiff3/index.html
+Comment=A File And Directory Comparison And Merge Tool
+Comment[ar]=أداة مقارنة و دمج ملفات و دلائل (مجلًدات )
+Comment[bg]=Инструмент за сравняване и сливане на файлове и директории
+Comment[bs]=Alat za upoređivanje i spajanje datoteka i direktorija
+Comment[ca]=Una eina per a comparar i fusionar fitxers o directoris
+Comment[cs]=Nástroj pro porovnávání a slučování souborů a adresářů
+Comment[da]=Et indfletningsværktøj for filer og mapper
+Comment[de]=Programm zum Vergleichen und Zusammenführen von Dateien und Ordnern
+Comment[el]=Ένα εργαλείο σύγκρισης και συγχώνευσης αρχείων και καταλόγων
+Comment[es]=Una herramienta para mezclar y comparar archivos y directorios
+Comment[et]=Failide ja kataloogide võrdlemise ja liitmise tööriist
+Comment[fr]=Un outil de comparaison et de fusion de fichiers et dossiers
+Comment[ga]=Uirlis a chuireann comhaid agus comhadlanna i gcomparáid agus a chumascann iad más gá
+Comment[gl]=Comparazón e Unificazón de Ficheiros e Cartafoles
+Comment[hi]=एक फाइल तथा डिरेक्ट्री तुलना तथा विलीन उपकरण
+Comment[hu]=Segédprogram fájlok, könyvtárak összehasonlításához
+Comment[is]=Skráa og möppu samanburðar og sameiningartól
+Comment[it]=Uno strumento di confronto e unione di file e directory
+Comment[ja]=ファイル/ディレクトリの比較/統合ツール
+Comment[ka]=ფაილთა და საქაღალდეთა შედარების და შერწყმის ხელსაწყო
+Comment[ms]=Perbandingan Fail Dan Direktori Dan Alatan Gabungan
+Comment[nb]=Et verktøy for å sammenlikne og slå sammen filer og mapper
+Comment[nl]=Hulpmiddel voor het vergelijken en samenvoegen van bestanden en mappen
+Comment[pa]=ਇੱਕ ਫਾਇਲ ਅਤੇ ਡਾਇਰੈਕਟਰੀ ਤੁਲਨਾ ਅਤੇ ਮਿਲਾਨ ਸੰਦ ਹੈ
+Comment[pl]=Narzędzie do porównywania oraz łączenia 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 Diretórios
+Comment[ru]=Утилита сравнения и объединения файлов и каталогов
+Comment[sk]=Nástroj pre porovnanie a spájanie súborov a priečinkov
+Comment[sr]=Алат за поређење и стапање фајлова и директоријума
+Comment[sr@Latn]=Alat za poređenje i stapanje fajlova i direktorijuma
+Comment[sv]=Ett jämförelseverktyg för fil- och katalogjämförelser
+Comment[tg]=Асбоби баробаркунӣ ва пайванди файлҳо ва каталогҳо
+Comment[tr]=Bir Dosya Ve Klasör Karşılaştırma Ve Birleştirme Aracı
+Comment[uk]=Засіб-утиліта для порівняння і поєднання файлів та каталогів
+Comment[xx]=xxA File And Directory Comparison And Merge Toolxx
+Comment[zh_CN]=一个文件和目录的比较及合并工具
+Terminal=false
diff --git a/src/kdiff3.h b/src/kdiff3.h
new file mode 100644
index 0000000..72b1a79
--- /dev/null
+++ b/src/kdiff3.h
@@ -0,0 +1,410 @@
+/***************************************************************************
+ kdiff3.h - description
+ -------------------
+ begin : Don Jul 11 12:31:29 CEST 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 KDIFF3_H
+#define KDIFF3_H
+
+#include "diff.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// include files for Qt
+#include <qdialog.h>
+#include <qsplitter.h>
+#include <qscrollbar.h>
+
+// include files for KDE
+#include <kapplication.h>
+#include <kmainwindow.h>
+#include <kaccel.h>
+#include <kaction.h>
+#include <kurl.h>
+#include <kparts/mainwindow.h>
+
+
+// forward declaration of the KDiff3 classes
+class OptionDialog;
+class FindDialog;
+class ManualDiffHelpDialog;
+class DiffTextWindow;
+class DiffTextWindowFrame;
+class MergeResultWindow;
+class WindowTitleWidget;
+class Overview;
+
+class QScrollBar;
+class QComboBox;
+class QLineEdit;
+class QCheckBox;
+class QSplitter;
+
+
+class KDiff3Part;
+class DirectoryMergeWindow;
+class DirectoryMergeInfo;
+
+
+class ReversibleScrollBar : public QScrollBar
+{
+ Q_OBJECT
+ bool* m_pbRightToLeftLanguage;
+ int m_realVal;
+public:
+ ReversibleScrollBar( Orientation o, QWidget* pParent, bool* pbRightToLeftLanguage )
+ : QScrollBar( o, pParent )
+ {
+ m_pbRightToLeftLanguage=pbRightToLeftLanguage;
+ m_realVal=0;
+ connect( this, SIGNAL(valueChanged(int)), this, SLOT(slotValueChanged(int)));
+ }
+ void setAgain(){ setValue(m_realVal); }
+public slots:
+ void slotValueChanged(int i)
+ {
+ m_realVal = i;
+ if(m_pbRightToLeftLanguage && *m_pbRightToLeftLanguage)
+ m_realVal = maxValue()-(i-minValue());
+ emit valueChanged2(m_realVal);
+ }
+ void setValue(int i)
+ {
+ if(m_pbRightToLeftLanguage && *m_pbRightToLeftLanguage)
+ QScrollBar::setValue( maxValue()-(i-minValue()) );
+ else
+ QScrollBar::setValue( i );
+ }
+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 char* name, KDiff3Part* pKDiff3Part );
+ ~KDiff3App();
+
+ 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( KConfig* );
+
+ /** read general Options again and initialize all variables like the recent file list */
+ void readOptions( KConfig* );
+
+ // Finish initialisation (virtual, so that it can be called from the shell too.)
+ virtual void completeInit(const QString& fn1="", const QString& fn2="", const QString& fn3="");
+
+ /** 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();
+
+ signals:
+ void createNewInstance( const QString& fn1, const QString& fn2, const QString& fn3 );
+ protected:
+ 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 slots:
+
+ /** open a file and load it into the document*/
+ void slotFileOpen();
+ void slotFileOpen2( QString fn1, QString fn2, QString fn3, QString ofn,
+ QString an1, QString an2, QString an3, 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);
+
+ private:
+ /** the configuration object of the application */
+ //KConfig *config;
+
+ // KAction pointers to enable/disable actions
+ KAction* fileOpen;
+ KAction* fileSave;
+ KAction* fileSaveAs;
+ KAction* filePrint;
+ KAction* fileQuit;
+ KAction* fileReload;
+ KAction* editCut;
+ KAction* editCopy;
+ KAction* editPaste;
+ KAction* editSelectAll;
+ KToggleAction* viewToolBar;
+ KToggleAction* viewStatusBar;
+
+////////////////////////////////////////////////////////////////////////
+// Special KDiff3 specific stuff starts here
+ KAction *editFind;
+ KAction *editFindNext;
+
+ KAction *goCurrent;
+ KAction *goTop;
+ KAction *goBottom;
+ KAction *goPrevUnsolvedConflict;
+ KAction *goNextUnsolvedConflict;
+ KAction *goPrevConflict;
+ KAction *goNextConflict;
+ KAction *goPrevDelta;
+ KAction *goNextDelta;
+ KToggleAction *chooseA;
+ KToggleAction *chooseB;
+ KToggleAction *chooseC;
+ KToggleAction *autoAdvance;
+ KToggleAction *wordWrap;
+ KAction* splitDiff;
+ KAction* joinDiffs;
+ KAction* addManualDiffHelp;
+ KAction* clearManualDiffHelpList;
+ KToggleAction *showWhiteSpaceCharacters;
+ KToggleAction *showWhiteSpace;
+ KToggleAction *showLineNumbers;
+ KAction* chooseAEverywhere;
+ KAction* chooseBEverywhere;
+ KAction* chooseCEverywhere;
+ KAction* chooseAForUnsolvedConflicts;
+ KAction* chooseBForUnsolvedConflicts;
+ KAction* chooseCForUnsolvedConflicts;
+ KAction* chooseAForUnsolvedWhiteSpaceConflicts;
+ KAction* chooseBForUnsolvedWhiteSpaceConflicts;
+ KAction* chooseCForUnsolvedWhiteSpaceConflicts;
+ KAction* autoSolve;
+ KAction* unsolve;
+ KAction* mergeHistory;
+ KAction* mergeRegExp;
+ KToggleAction *showWindowA;
+ KToggleAction *showWindowB;
+ KToggleAction *showWindowC;
+ KAction *winFocusNext;
+ KAction *winFocusPrev;
+ KAction* winToggleSplitOrientation;
+ KToggleAction *dirShowBoth;
+ KAction *dirViewToggle;
+ KToggleAction *overviewModeNormal;
+ KToggleAction *overviewModeAB;
+ KToggleAction *overviewModeAC;
+ KToggleAction *overviewModeBC;
+
+
+ QPopupMenu* 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;
+
+ 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_maxWidth;
+ int m_DTWHeight;
+ bool m_bOutputModified;
+ bool m_bFileSaved;
+ bool m_bTimerBlock; // Synchronisation
+
+ OptionDialog* m_pOptionDialog;
+ FindDialog* m_pFindDialog;
+
+ void init( bool bAuto=false, TotalDiffStatus* pTotalDiffStatus=0, bool bLoadFiles=true );
+
+ virtual bool eventFilter( QObject* o, QEvent* e );
+ virtual void resizeEvent(QResizeEvent*);
+
+ bool improveFilenames(bool bCreateNewInstance);
+
+ bool runDiff( const LineData* p1, int size1, const LineData* p2, int size2, DiffList& diffList, int winIdx1, int winIdx2 );
+ bool runDiff( const LineData* p1, int size1, const LineData* p2, int size2, DiffList& diffList );
+ bool canContinue();
+
+ void choose(int choice);
+
+ KActionCollection* actionCollection();
+ KStatusBar* statusBar();
+ KToolBar* toolBar(const char*);
+ KDiff3Part* m_pKDiff3Part;
+ KParts::MainWindow* m_pKDiff3Shell;
+ bool m_bAutoFlag;
+ bool m_bAutoMode;
+ void recalcWordWrap(int nofVisibleColumns=-1);
+
+public slots:
+ void resizeDiffTextWindow(int newWidth, int newHeight);
+ void resizeMergeResultWindow();
+ void slotRecalcWordWrap();
+
+ 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 slotAfterFirstPaint();
+ 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();
+};
+
+#endif // KDIFF3_H
diff --git a/src/kdiff3.ico b/src/kdiff3.ico
new file mode 100644
index 0000000..a10847b
--- /dev/null
+++ b/src/kdiff3.ico
Binary files differ
diff --git a/src/kdiff3.lsm b/src/kdiff3.lsm
new file mode 100644
index 0000000..3eedb77
--- /dev/null
+++ b/src/kdiff3.lsm
@@ -0,0 +1,16 @@
+Begin3
+Title: kdiff3 -- Comparison and Merge of Files and Directories
+Version: 0.9.89
+Entered-date:
+Description: Tool for comparison and merge of two or three files or directories
+Keywords: KDE Qt
+Author: Joachim Eibl <joachim at gmx.de>
+Maintained-by: Joachim Eibl <joachim at gmx.de>
+Home-page: http://kdiff3.sourceforge.net
+Alternate-site: http://extragear.kde.org/apps/kdiff3
+Primary-site: http://sourceforge.net/project/showfiles.php?group_id=58666
+ xxxxxx kdiff3-0.9.89.tar.gz
+ xxx kdiff3-0.9.89.lsm
+Platform: Linux. Needs Qt, runs even better on KDE
+Copying-policy: GPL
+End
diff --git a/src/kdiff3.pro b/src/kdiff3.pro
new file mode 100644
index 0000000..71ff682
--- /dev/null
+++ b/src/kdiff3.pro
@@ -0,0 +1,57 @@
+TEMPLATE = app
+# When unresolved items remain during linking: Try adding "shared" in the CONFIG.
+CONFIG += qt warn_on thread release
+HEADERS = version.h \
+ diff.h \
+ difftextwindow.h \
+ mergeresultwindow.h \
+ kdiff3.h \
+ merger.h \
+ optiondialog.h \
+ kreplacements/kreplacements.h \
+ directorymergewindow.h \
+ fileaccess.h \
+ kdiff3_shell.h \
+ kdiff3_part.h \
+ smalldialogs.h
+SOURCES = main.cpp \
+ diff.cpp \
+ difftextwindow.cpp \
+ kdiff3.cpp \
+ merger.cpp \
+ mergeresultwindow.cpp \
+ optiondialog.cpp \
+ pdiff.cpp \
+ directorymergewindow.cpp \
+ fileaccess.cpp \
+ smalldialogs.cpp \
+ kdiff3_shell.cpp \
+ kdiff3_part.cpp \
+ gnudiff_analyze.cpp \
+ gnudiff_io.cpp \
+ gnudiff_xmalloc.cpp \
+ common.cpp \
+ kreplacements/kreplacements.cpp \
+ kreplacements/ShellContextMenu.cpp
+TARGET = kdiff3
+INCLUDEPATH += . ./kreplacements
+
+win32 {
+# QMAKE_CXXFLAGS_DEBUG -= -Zi
+# QMAKE_CXXFLAGS_DEBUG += -GX -GR -Z7 /FR -DQT_NO_ASCII_CAST
+# QMAKE_LFLAGS_DEBUG += /PDB:NONE
+# QMAKE_CXXFLAGS_RELEASE += -GX -GR -DNDEBUG -DQT_NO_ASCII_CAST
+
+ QMAKE_CXXFLAGS_DEBUG += -DQT_NO_ASCII_CAST
+ QMAKE_CXXFLAGS_RELEASE += -DNDEBUG -DQT_NO_ASCII_CAST
+ RC_FILE = kdiff3.rc
+}
+unix {
+ documentation.path = /usr/local/share/doc/kdiff3
+ documentation.files = ../doc/*
+
+ INSTALLS += documentation
+
+ target.path = /usr/local/bin
+ INSTALLS += target
+}
diff --git a/src/kdiff3.rc b/src/kdiff3.rc
new file mode 100644
index 0000000..5b38078
--- /dev/null
+++ b/src/kdiff3.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "kdiff3.ico"
diff --git a/src/kdiff3_meta_unload.cpp b/src/kdiff3_meta_unload.cpp
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/kdiff3_meta_unload.cpp
@@ -0,0 +1 @@
+
diff --git a/src/kdiff3_part.cpp b/src/kdiff3_part.cpp
new file mode 100644
index 0000000..799673e
--- /dev/null
+++ b/src/kdiff3_part.cpp
@@ -0,0 +1,309 @@
+/***************************************************************************
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#include "kdiff3_part.h"
+
+#include <kinstance.h>
+#include <kaction.h>
+#include <kstdaction.h>
+#include <kfiledialog.h>
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include "kdiff3.h"
+#include "fileaccess.h"
+
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <iostream>
+
+#include "version.h"
+
+KDiff3Part::KDiff3Part( QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name )
+ : KParts::ReadOnlyPart(parent, name)
+{
+ // we need an instance
+ setInstance( KDiff3PartFactory::instance() );
+
+ // this should be your custom internal widget
+ m_widget = new KDiff3App( parentWidget, widgetName, this );
+
+ // This hack is necessary to avoid a crash when the program terminates.
+ m_bIsShell = dynamic_cast<KParts::MainWindow*>(parentWidget)!=0;
+
+ // notify the part that this is our internal widget
+ setWidget(m_widget);
+
+ // create our actions
+ //KStdAction::open(this, SLOT(fileOpen()), actionCollection());
+ //KStdAction::saveAs(this, SLOT(fileSaveAs()), actionCollection());
+ //KStdAction::save(this, SLOT(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!=0 && ! m_bIsShell )
+ {
+ m_widget->saveOptions( m_widget->isPart() ? instance()->config() : kapp->config() );
+ }
+}
+
+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
+ KAction *save = actionCollection()->action(KStdAction::stdName(KStdAction::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() )
+ {
+ unsigned int pos = lineStart.length();
+ while ( pos<str.length() && (str[pos]==' ' || str[pos]=='\t') ) ++pos;
+ unsigned int pos2 = str.length()-1;
+ while ( pos2>pos )
+ {
+ while (pos2>pos && str[pos2]!=' ' && str[pos2]!='\t') --pos2;
+ fileName = str.mid( pos, pos2-pos );
+ std::cerr << "KDiff3: " << fileName.latin1() << std::endl;
+ if ( FileAccess(fileName).exists() ) break;
+ --pos2;
+ }
+
+ int vpos = str.findRev("\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
+ std::cerr << "KDiff3: " << m_file.latin1() << std::endl;
+ QFile file(m_file);
+ if (file.open(IO_ReadOnly) == false)
+ 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.eof() && (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("Couldn't find files for comparison."));
+ return false;
+ }
+
+ FileAccess f1(fileName1);
+ FileAccess f2(fileName2);
+
+ if ( f1.exists() && f2.exists() && fileName1!=fileName2 )
+ {
+ m_widget->slotFileOpen2( fileName1, fileName2, "", "", "", "", "", 0 );
+ return true;
+ }
+ else if ( version1.isEmpty() && f1.exists() )
+ {
+ // Normal patch
+ // patch -f -u --ignore-whitespace -i [inputfile] -o [outfile] [patchfile]
+ QString tempFileName = FileAccess::tempFileName();
+ QString cmd = "patch -f -u --ignore-whitespace -i \"" + m_file +
+ "\" -o \""+tempFileName + "\" \"" + fileName1+ "\"";
+
+ ::system( cmd.ascii() );
+
+ m_widget->slotFileOpen2( fileName1, tempFileName, "", "",
+ "", version2.isEmpty() ? fileName2 : "REV:"+version2+":"+fileName2, "", 0 ); // alias names
+// std::cerr << "KDiff3: f1:" << fileName1.latin1() <<"<->"<<tempFileName.latin1()<< std::endl;
+ FileAccess::removeTempFile( tempFileName );
+ }
+ else if ( version2.isEmpty() && f2.exists() )
+ {
+ // Reverse patch
+ // patch -f -u -R --ignore-whitespace -i [inputfile] -o [outfile] [patchfile]
+ QString tempFileName = FileAccess::tempFileName();
+ QString cmd = "patch -f -u -R --ignore-whitespace -i \"" + m_file +
+ "\" -o \""+tempFileName + "\" \"" + fileName2+"\"";
+
+ ::system( cmd.ascii() );
+
+ m_widget->slotFileOpen2( tempFileName, fileName2, "", "",
+ version1.isEmpty() ? fileName1 : "REV:"+version1+":"+fileName1, "", "", 0 ); // alias name
+// std::cerr << "KDiff3: f2:" << fileName2.latin1() <<"<->"<<tempFileName.latin1()<< std::endl;
+ FileAccess::removeTempFile( tempFileName );
+ }
+ else if ( !version1.isEmpty() && !version2.isEmpty() )
+ {
+ std::cerr << "KDiff3: f1/2:" << fileName1.latin1() <<"<->"<<fileName2.latin1()<< std::endl;
+ // Assuming that files are on CVS: Try to get them
+ // cvs update -p -r [REV] [FILE] > [OUTPUTFILE]
+
+ QString tempFileName1 = FileAccess::tempFileName();
+ QString cmd1 = "cvs update -p -r " + version1 + " \"" + fileName1 + "\" >\""+tempFileName1+"\"";
+ ::system( cmd1.ascii() );
+
+ QString tempFileName2 = FileAccess::tempFileName();
+ QString cmd2 = "cvs update -p -r " + version2 + " \"" + fileName2 + "\" >\""+tempFileName2+"\"";
+ ::system( cmd2.ascii() );
+
+ m_widget->slotFileOpen2( tempFileName1, tempFileName2, "", "",
+ "REV:"+version1+":"+fileName1,
+ "REV:"+version2+":"+fileName2,
+ "", 0
+ );
+
+// std::cerr << "KDiff3: f1/2:" << tempFileName1.latin1() <<"<->"<<tempFileName2.latin1()<< std::endl;
+ FileAccess::removeTempFile( tempFileName1 );
+ FileAccess::removeTempFile( tempFileName2 );
+ return true;
+ }
+ else
+ {
+ KMessageBox::sorry(m_widget, i18n("Couldn't find files for comparison."));
+ }
+
+ return true;
+}
+
+bool KDiff3Part::saveFile()
+{
+/* // if we aren't read-write, return immediately
+ if (isReadWrite() == false)
+ return false;
+
+ // m_file is always local, so we use QFile
+ QFile file(m_file);
+ 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
+}
+
+
+// It's usually safe to leave the factory code alone.. with the
+// notable exception of the KAboutData data
+#include <kaboutdata.h>
+#include <klocale.h>
+
+KInstance* KDiff3PartFactory::s_instance = 0L;
+KAboutData* KDiff3PartFactory::s_about = 0L;
+
+KDiff3PartFactory::KDiff3PartFactory()
+ : KParts::Factory()
+{
+}
+
+KDiff3PartFactory::~KDiff3PartFactory()
+{
+ delete s_instance;
+ delete s_about;
+
+ s_instance = 0L;
+}
+
+KParts::Part* KDiff3PartFactory::createPartObject( QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name,
+ const char *classname, const QStringList&/*args*/ )
+{
+ // Create an instance of our Part
+ KDiff3Part* obj = new KDiff3Part( parentWidget, widgetName, parent, name );
+
+ // See if we are to be read-write or not
+ if (QCString(classname) == "KParts::ReadOnlyPart")
+ obj->setReadWrite(false);
+
+ return obj;
+}
+
+KInstance* KDiff3PartFactory::instance()
+{
+ if( !s_instance )
+ {
+ s_about = new KAboutData("kdiff3part", I18N_NOOP("KDiff3Part"), VERSION);
+ s_about->addAuthor("Joachim Eibl", 0, "joachim.eibl at gmx.de");
+ s_instance = new KInstance(s_about);
+ }
+ return s_instance;
+}
+
+extern "C"
+{
+ void* init_libkdiff3part()
+ {
+ return new KDiff3PartFactory;
+ }
+}
+
+// Suppress warning with --enable-final
+#undef VERSION
+
+#include "kdiff3_part.moc"
diff --git a/src/kdiff3_part.h b/src/kdiff3_part.h
new file mode 100644
index 0000000..86b58d2
--- /dev/null
+++ b/src/kdiff3_part.h
@@ -0,0 +1,100 @@
+/***************************************************************************
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#ifndef _KDIFF3PART_H_
+#define _KDIFF3PART_H_
+
+#include <kparts/part.h>
+#include <kparts/factory.h>
+
+class QWidget;
+class QPainter;
+class KURL;
+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::ReadOnlyPart
+{
+ Q_OBJECT
+public:
+ /**
+ * Default constructor
+ */
+ KDiff3Part(QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name);
+
+ /**
+ * Destructor
+ */
+ virtual ~KDiff3Part();
+
+ /**
+ * This is a virtual function inherited from KParts::ReadWritePart.
+ * A shell will use this to inform this Part if it should act
+ * read-only
+ */
+ virtual void setReadWrite(bool rw);
+
+ /**
+ * Reimplemented to disable and enable Save action
+ */
+ virtual void setModified(bool modified);
+
+protected:
+ /**
+ * This must be implemented by each part
+ */
+ virtual bool openFile();
+
+ /**
+ * This must be implemented by each read-write part
+ */
+ virtual bool saveFile();
+
+private:
+ KDiff3App* m_widget;
+ bool m_bIsShell;
+};
+
+class KInstance;
+class KAboutData;
+
+class KDiff3PartFactory : public KParts::Factory
+{
+ Q_OBJECT
+public:
+ KDiff3PartFactory();
+ virtual ~KDiff3PartFactory();
+ virtual KParts::Part* createPartObject( QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name,
+ const char *classname, const QStringList &args );
+ static KInstance* instance();
+
+private:
+ static KInstance* s_instance;
+ static KAboutData* s_about;
+};
+
+#endif // _KDIFF3PART_H_
diff --git a/src/kdiff3_part.rc b/src/kdiff3_part.rc
new file mode 100644
index 0000000..72ae4c4
--- /dev/null
+++ b/src/kdiff3_part.rc
@@ -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
index 0000000..dd83a69
--- /dev/null
+++ b/src/kdiff3_shell.cpp
@@ -0,0 +1,191 @@
+/***************************************************************************
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#include "kdiff3_shell.h"
+#include "kdiff3.h"
+
+#include <kkeydialog.h>
+#include <kfiledialog.h>
+#include <kconfig.h>
+#include <kurl.h>
+
+#include <kedittoolbar.h>
+
+#include <kaction.h>
+#include <kstdaction.h>
+
+#include <klibloader.h>
+#include <kmessagebox.h>
+#include <kstatusbar.h>
+#include <klocale.h>
+
+#include <iostream>
+
+KDiff3Shell::KDiff3Shell(bool bCompleteInit)
+ : KParts::MainWindow( 0L, "kdiff3" )
+{
+ m_bUnderConstruction = true;
+ // set the shell's ui resource file
+ setXMLFile("kdiff3_shell.rc");
+
+ // and a status bar
+ statusBar()->show();
+
+ // this routine will find and load our Part. it finds the Part by
+ // name which is a bad idea usually.. but it's alright in this
+ // case since our Part is made for this Shell
+ KLibFactory *factory = KLibLoader::self()->factory("libkdiff3part");
+ if (factory)
+ {
+ // now that the Part is loaded, we cast it to a Part to get
+ // our hands on it
+ m_part = static_cast<KParts::ReadWritePart *>(factory->create(this,
+ "kdiff3_part", "KParts::ReadWritePart" ));
+
+ if (m_part)
+ {
+ // and integrate the part's GUI with the shell's
+ createGUI(m_part);
+
+ // tell the KParts::MainWindow that this is indeed the main widget
+ setCentralWidget(m_part->widget());
+
+ if (bCompleteInit)
+ ((KDiff3App*)m_part->widget())->completeInit();
+ connect(((KDiff3App*)m_part->widget()), SIGNAL(createNewInstance(const QString&, const QString&, const QString&)), this, SLOT(slotNewInstance(const QString&, const QString&, const QString&)));
+ }
+ }
+ 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 find our 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();
+ KApplication::exit( bFileSaved ? 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()
+{
+ KKeyDialog::configure(actionCollection(), "kdiff3_shell.rc");
+}
+
+void KDiff3Shell::optionsConfigureToolbars()
+{
+#if defined(KDE_MAKE_VERSION)
+# if KDE_VERSION >= KDE_MAKE_VERSION(3,1,0)
+ saveMainWindowSettings(KGlobal::config(), autoSaveGroup());
+# else
+ saveMainWindowSettings(KGlobal::config() );
+# endif
+#else
+ saveMainWindowSettings(KGlobal::config() );
+#endif
+
+ // use the standard toolbar editor
+ KEditToolbar dlg(factory());
+ connect(&dlg, SIGNAL(newToolbarConfig()),
+ this, SLOT(applyNewToolbarConfig()));
+ dlg.exec();
+}
+
+void KDiff3Shell::applyNewToolbarConfig()
+{
+#if defined(KDE_MAKE_VERSION)
+# if KDE_VERSION >= KDE_MAKE_VERSION(3,1,0)
+ applyMainWindowSettings(KGlobal::config(), autoSaveGroup());
+# else
+ applyMainWindowSettings(KGlobal::config());
+# endif
+#else
+ applyMainWindowSettings(KGlobal::config());
+#endif
+}
+
+void KDiff3Shell::slotNewInstance( const QString& fn1, const QString& fn2, const QString& fn3 )
+{
+ 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
index 0000000..42a324b
--- /dev/null
+++ b/src/kdiff3_shell.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#ifndef _KDIFF3SHELL_H_
+#define _KDIFF3SHELL_H_
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kapplication.h>
+#include <kparts/mainwindow.h>
+
+class KToggleAction;
+
+/**
+ * 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
+ */
+ KDiff3Shell(bool bCompleteInit=true);
+
+ /**
+ * Default Destructor
+ */
+ virtual ~KDiff3Shell();
+
+ bool queryClose();
+ bool queryExit();
+ virtual void closeEvent(QCloseEvent*e);
+
+private 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;
+
+ 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
index 0000000..bf1cb19
--- /dev/null
+++ b/src/kdiff3_shell.rc
@@ -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>&amp;Movement</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>&amp;Merge</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"><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
index 0000000..70ca73e
--- /dev/null
+++ b/src/kdiff3part.desktop
@@ -0,0 +1,18 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=KDiff3Part
+Name[cs]=Komponenta KDiff3
+Name[fr]=Composant KDiff3
+Name[hi]=के-डिफ3पार्ट
+Name[nb]=KDiff3-del
+Name[pt_BR]=Componente KDiff3
+Name[sv]=Kdiff3-del
+Name[ta]=கேடிஃப்3 பகுதி
+Name[tg]=ҚисмиKDiff3
+Name[uk]=Комопнент KDiff3
+Name[xx]=xxKDiff3Partxx
+Name[zh_CN]=KDiff3 组件
+MimeType=text/x-diff
+ServiceTypes=KParts/ReadOnlyPart,KParts/ReadWritePart
+X-KDE-Library=libkdiff3part
+Type=Service
diff --git a/src/kreplacements/README b/src/kreplacements/README
new file mode 100644
index 0000000..fd9411c
--- /dev/null
+++ b/src/kreplacements/README
@@ -0,0 +1,30 @@
+About the "kreplacements"-directory:
+====================================
+
+I want to be able to compile and use KDiff3 without KDE too.
+Since KDiff3 is a KDE program, which uses many KDE-classes and
+functions there must be some replacement.
+
+In many cases this is just the corresponding Qt-class, but often
+I wrote something myself. For several very KDE-specific functions
+there is no real replacement, but only stub functions that allow
+the program to compile and link.
+
+This stuff is not intended to be used for anything else but KDiff3.
+Think of it rather as a big hack, that only has the advantage
+that I need not mess up the normal program with many ugly
+#ifdef/#endif-clauses.
+
+Most include files in this directory only include kreplacements.h
+where the actual declarations are. The implementions are in
+kreplacements.cpp.
+
+The *.moc-files are dummies. The new KDE-philosophy seems to be
+that *.moc-files are directly included into the sources.
+The Qt-philosophy still is to generate moc*.cpp files which will
+be compiled seperately. With these dummy-moc-files both versions
+can be compiled.
+
+
+Joachim
+(2003-10-02)
diff --git a/src/kreplacements/ShellContextMenu.cpp b/src/kreplacements/ShellContextMenu.cpp
new file mode 100755
index 0000000..e1a6d2d
--- /dev/null
+++ b/src/kreplacements/ShellContextMenu.cpp
@@ -0,0 +1,492 @@
+/***************************************************************************
+ ShellContextMenu.cpp - description
+ -------------------
+ begin : Sat Mar 4 2006
+ copyright : (C) 2005-2007 by Joachim Eibl
+ email : 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+// ShellContextMenu.cpp: Implementierung der Klasse CShellContextMenu.
+//
+//////////////////////////////////////////////////////////////////////
+#ifdef _WIN32
+#include <windows.h>
+#include <shlobj.h>
+#include <malloc.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qwidget.h>
+#include <qdir.h>
+#include <qpopupmenu.h>
+#include "ShellContextMenu.h"
+
+#ifdef _DEBUG
+#undef THIS_FILE
+static char THIS_FILE[]=__FILE__;
+#define new DEBUG_NEW
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// Konstruktion/Destruktion
+//////////////////////////////////////////////////////////////////////
+
+#define MIN_ID 100
+#define MAX_ID 10000
+
+
+void showShellContextMenu( const QString& itemPath, QPoint pt, QWidget* pParentWidget, QPopupMenu* pMenu )
+{
+ CShellContextMenu scm;
+ scm.SetObjects(QDir::convertSeparators(itemPath));
+ int id = scm.ShowContextMenu (pParentWidget, pt, pMenu);
+ if (id>=1)
+ pMenu->activateItemAt(id-1);
+}
+
+IContextMenu2 * g_IContext2 = NULL;
+IContextMenu3 * g_IContext3 = NULL;
+
+CShellContextMenu::CShellContextMenu()
+{
+ m_psfFolder = NULL;
+ m_pidlArray = NULL;
+ m_hMenu = NULL;
+}
+
+CShellContextMenu::~CShellContextMenu()
+{
+ // free all allocated datas
+ if (m_psfFolder && bDelete)
+ m_psfFolder->Release ();
+ m_psfFolder = NULL;
+ FreePIDLArray (m_pidlArray);
+ m_pidlArray = NULL;
+
+ if (m_hMenu)
+ DestroyMenu( m_hMenu );
+}
+
+
+
+// this functions determines which version of IContextMenu is avaibale for those objects (always the highest one)
+// and returns that interface
+BOOL CShellContextMenu::GetContextMenu (void ** ppContextMenu, int & iMenuType)
+{
+ *ppContextMenu = NULL;
+ LPCONTEXTMENU icm1 = NULL;
+
+ if ( m_psfFolder==0 )
+ return FALSE;
+ // first we retrieve the normal IContextMenu interface (every object should have it)
+ m_psfFolder->GetUIObjectOf (NULL, nItems, (LPCITEMIDLIST *) m_pidlArray, IID_IContextMenu, NULL, (void**) &icm1);
+
+ if (icm1)
+ { // since we got an IContextMenu interface we can now obtain the higher version interfaces via that
+ if (icm1->QueryInterface (IID_IContextMenu3, ppContextMenu) == NOERROR)
+ iMenuType = 3;
+ else if (icm1->QueryInterface (IID_IContextMenu2, ppContextMenu) == NOERROR)
+ iMenuType = 2;
+
+ if (*ppContextMenu)
+ icm1->Release(); // we can now release version 1 interface, cause we got a higher one
+ else
+ {
+ iMenuType = 1;
+ *ppContextMenu = icm1; // since no higher versions were found
+ } // redirect ppContextMenu to version 1 interface
+ }
+ else
+ return (FALSE); // something went wrong
+
+ return (TRUE); // success
+}
+
+
+LRESULT CALLBACK CShellContextMenu::HookWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_MENUCHAR: // only supported by IContextMenu3
+ if (g_IContext3)
+ {
+ LRESULT lResult = 0;
+ g_IContext3->HandleMenuMsg2 (message, wParam, lParam, &lResult);
+ return (lResult);
+ }
+ break;
+
+ case WM_DRAWITEM:
+ case WM_MEASUREITEM:
+ if (wParam)
+ break; // if wParam != 0 then the message is not menu-related
+
+ case WM_INITMENUPOPUP:
+ if (g_IContext2)
+ g_IContext2->HandleMenuMsg (message, wParam, lParam);
+ else // version 3
+ g_IContext3->HandleMenuMsg (message, wParam, lParam);
+ return (message == WM_INITMENUPOPUP ? 0 : TRUE); // inform caller that we handled WM_INITPOPUPMENU by ourself
+ break;
+
+ default:
+ break;
+ }
+
+ // call original WndProc of window to prevent undefined bevhaviour of window
+ return ::CallWindowProc ((WNDPROC) GetProp ( hWnd, TEXT ("OldWndProc")), hWnd, message, wParam, lParam);
+}
+
+
+UINT CShellContextMenu::ShowContextMenu(QWidget * pParentWidget, QPoint pt, QPopupMenu* pMenu )
+{
+ HWND hWnd = pParentWidget->winId();
+ int iMenuType = 0; // to know which version of IContextMenu is supported
+ LPCONTEXTMENU pContextMenu; // common pointer to IContextMenu and higher version interface
+
+ if (!GetContextMenu ((void**) &pContextMenu, iMenuType))
+ return (0); // something went wrong
+
+ if (!m_hMenu)
+ {
+ DestroyMenu( m_hMenu );
+ m_hMenu = CreatePopupMenu ();
+ }
+
+ UINT_PTR i;
+ for( i=0; i<pMenu->count(); ++i )
+ {
+ QString s = pMenu->text(pMenu->idAt(i));
+ if (!s.isEmpty())
+ AppendMenuW( m_hMenu, MF_STRING, i+1, (LPCWSTR)s.ucs2() );
+ }
+ AppendMenuW( m_hMenu, MF_SEPARATOR, i+1, L"" );
+
+ // lets fill the our popupmenu
+ pContextMenu->QueryContextMenu (m_hMenu, GetMenuItemCount (m_hMenu), MIN_ID, MAX_ID, CMF_NORMAL | CMF_EXPLORE);
+
+ // subclass window to handle menurelated messages in CShellContextMenu
+ WNDPROC OldWndProc;
+ if (iMenuType > 1) // only subclass if its version 2 or 3
+ {
+ OldWndProc = (WNDPROC) SetWindowLong (hWnd, GWL_WNDPROC, (DWORD) HookWndProc);
+ if (iMenuType == 2)
+ g_IContext2 = (LPCONTEXTMENU2) pContextMenu;
+ else // version 3
+ g_IContext3 = (LPCONTEXTMENU3) pContextMenu;
+ }
+ else
+ OldWndProc = NULL;
+
+ UINT idCommand = TrackPopupMenu (m_hMenu,TPM_RETURNCMD | TPM_LEFTALIGN, pt.x(), pt.y(), 0, pParentWidget->winId(), 0);
+
+ if (OldWndProc) // unsubclass
+ SetWindowLong (hWnd, GWL_WNDPROC, (DWORD) OldWndProc);
+
+ if (idCommand >= MIN_ID && idCommand <= MAX_ID) // see if returned idCommand belongs to shell menu entries
+ {
+ InvokeCommand (pContextMenu, idCommand - MIN_ID); // execute related command
+ idCommand = 0;
+ }
+
+ pContextMenu->Release();
+ g_IContext2 = NULL;
+ g_IContext3 = NULL;
+
+ return (idCommand);
+}
+
+
+void CShellContextMenu::InvokeCommand (LPCONTEXTMENU pContextMenu, UINT idCommand)
+{
+ CMINVOKECOMMANDINFO cmi = {0};
+ cmi.cbSize = sizeof (CMINVOKECOMMANDINFO);
+ cmi.lpVerb = (LPSTR) MAKEINTRESOURCE (idCommand);
+ cmi.nShow = SW_SHOWNORMAL;
+
+ pContextMenu->InvokeCommand (&cmi);
+}
+
+
+void CShellContextMenu::SetObjects(const QString& strObject)
+{
+ // only one object is passed
+ QStringList strArray;
+ strArray << strObject; // create a CStringArray with one element
+
+ SetObjects (strArray); // and pass it to SetObjects (CStringArray &strArray)
+ // for further processing
+}
+
+
+void CShellContextMenu::SetObjects(const QStringList &strList)
+{
+ // free all allocated datas
+ if (m_psfFolder && bDelete)
+ m_psfFolder->Release ();
+ m_psfFolder = NULL;
+ FreePIDLArray (m_pidlArray);
+ m_pidlArray = NULL;
+
+ // get IShellFolder interface of Desktop (root of shell namespace)
+ IShellFolder * psfDesktop = NULL;
+ SHGetDesktopFolder (&psfDesktop); // needed to obtain full qualified pidl
+
+ // ParseDisplayName creates a PIDL from a file system path relative to the IShellFolder interface
+ // but since we use the Desktop as our interface and the Desktop is the namespace root
+ // that means that it's a fully qualified PIDL, which is what we need
+ LPITEMIDLIST pidl = NULL;
+
+ psfDesktop->ParseDisplayName (NULL, 0, (LPOLESTR)strList[0].ucs2(), NULL, &pidl, NULL);
+
+ // now we need the parent IShellFolder interface of pidl, and the relative PIDL to that interface
+ LPITEMIDLIST pidlItem = NULL; // relative pidl
+ SHBindToParentEx (pidl, IID_IShellFolder, (void **) &m_psfFolder, NULL);
+ free (pidlItem);
+ // get interface to IMalloc (need to free the PIDLs allocated by the shell functions)
+ LPMALLOC lpMalloc = NULL;
+ SHGetMalloc (&lpMalloc);
+ lpMalloc->Free (pidl);
+
+ // now we have the IShellFolder interface to the parent folder specified in the first element in strArray
+ // since we assume that all objects are in the same folder (as it's stated in the MSDN)
+ // we now have the IShellFolder interface to every objects parent folder
+
+ IShellFolder * psfFolder = NULL;
+ nItems = strList.size ();
+ for (int i = 0; i < nItems; i++)
+ {
+ pidl=0;
+ psfDesktop->ParseDisplayName (NULL, 0, (LPOLESTR)strList[i].ucs2(), NULL, &pidl, NULL);
+ if (pidl)
+ {
+ m_pidlArray = (LPITEMIDLIST *) realloc (m_pidlArray, (i + 1) * sizeof (LPITEMIDLIST));
+ // get relative pidl via SHBindToParent
+ SHBindToParentEx (pidl, IID_IShellFolder, (void **) &psfFolder, (LPCITEMIDLIST *) &pidlItem);
+ m_pidlArray[i] = CopyPIDL (pidlItem); // copy relative pidl to pidlArray
+ free (pidlItem);
+ lpMalloc->Free (pidl); // free pidl allocated by ParseDisplayName
+ psfFolder->Release ();
+ }
+ }
+ lpMalloc->Release ();
+ psfDesktop->Release ();
+
+ bDelete = TRUE; // indicates that m_psfFolder should be deleted by CShellContextMenu
+}
+
+
+// only one full qualified PIDL has been passed
+void CShellContextMenu::SetObjects(LPITEMIDLIST /*pidl*/)
+{
+/*
+ // free all allocated datas
+ if (m_psfFolder && bDelete)
+ m_psfFolder->Release ();
+ m_psfFolder = NULL;
+ FreePIDLArray (m_pidlArray);
+ m_pidlArray = NULL;
+
+ // full qualified PIDL is passed so we need
+ // its parent IShellFolder interface and its relative PIDL to that
+ LPITEMIDLIST pidlItem = NULL;
+ SHBindToParent ((LPCITEMIDLIST) pidl, IID_IShellFolder, (void **) &m_psfFolder, (LPCITEMIDLIST *) &pidlItem);
+
+ m_pidlArray = (LPITEMIDLIST *) malloc (sizeof (LPITEMIDLIST)); // allocate ony for one elemnt
+ m_pidlArray[0] = CopyPIDL (pidlItem);
+
+
+ // now free pidlItem via IMalloc interface (but not m_psfFolder, that we need later
+ LPMALLOC lpMalloc = NULL;
+ SHGetMalloc (&lpMalloc);
+ lpMalloc->Free (pidlItem);
+ lpMalloc->Release();
+
+ nItems = 1;
+ bDelete = TRUE; // indicates that m_psfFolder should be deleted by CShellContextMenu
+*/
+}
+
+
+// IShellFolder interface with a relative pidl has been passed
+void CShellContextMenu::SetObjects(IShellFolder *psfFolder, LPITEMIDLIST pidlItem)
+{
+ // free all allocated datas
+ if (m_psfFolder && bDelete)
+ m_psfFolder->Release ();
+ m_psfFolder = NULL;
+ FreePIDLArray (m_pidlArray);
+ m_pidlArray = NULL;
+
+ m_psfFolder = psfFolder;
+
+ m_pidlArray = (LPITEMIDLIST *) malloc (sizeof (LPITEMIDLIST));
+ m_pidlArray[0] = CopyPIDL (pidlItem);
+
+ nItems = 1;
+ bDelete = FALSE; // indicates wheter m_psfFolder should be deleted by CShellContextMenu
+}
+
+void CShellContextMenu::SetObjects(IShellFolder * psfFolder, LPITEMIDLIST *pidlArray, int nItemCount)
+{
+ // free all allocated datas
+ if (m_psfFolder && bDelete)
+ m_psfFolder->Release ();
+ m_psfFolder = NULL;
+ FreePIDLArray (m_pidlArray);
+ m_pidlArray = NULL;
+
+ m_psfFolder = psfFolder;
+
+ m_pidlArray = (LPITEMIDLIST *) malloc (nItemCount * sizeof (LPITEMIDLIST));
+
+ for (int i = 0; i < nItemCount; i++)
+ m_pidlArray[i] = CopyPIDL (pidlArray[i]);
+
+ nItems = nItemCount;
+ bDelete = FALSE; // indicates wheter m_psfFolder should be deleted by CShellContextMenu
+}
+
+
+void CShellContextMenu::FreePIDLArray(LPITEMIDLIST *pidlArray)
+{
+ if (!pidlArray)
+ return;
+
+ int iSize = _msize (pidlArray) / sizeof (LPITEMIDLIST);
+
+ for (int i = 0; i < iSize; i++)
+ free (pidlArray[i]);
+ free (pidlArray);
+}
+
+
+LPITEMIDLIST CShellContextMenu::CopyPIDL (LPCITEMIDLIST pidl, int cb)
+{
+ if (cb == -1)
+ cb = GetPIDLSize (pidl); // Calculate size of list.
+
+ LPITEMIDLIST pidlRet = (LPITEMIDLIST) calloc (cb + sizeof (USHORT), sizeof (BYTE));
+ if (pidlRet)
+ CopyMemory(pidlRet, pidl, cb);
+
+ return (pidlRet);
+}
+
+
+UINT CShellContextMenu::GetPIDLSize (LPCITEMIDLIST pidl)
+{
+ if (!pidl)
+ return 0;
+ int nSize = 0;
+ LPITEMIDLIST pidlTemp = (LPITEMIDLIST) pidl;
+ while (pidlTemp->mkid.cb)
+ {
+ nSize += pidlTemp->mkid.cb;
+ pidlTemp = (LPITEMIDLIST) (((LPBYTE) pidlTemp) + pidlTemp->mkid.cb);
+ }
+ return nSize;
+}
+
+HMENU CShellContextMenu::GetMenu()
+{
+ if (!m_hMenu)
+ {
+ m_hMenu = CreatePopupMenu(); // create the popupmenu (its empty)
+ }
+ return (m_hMenu);
+}
+
+
+// this is workaround function for the Shell API Function SHBindToParent
+// SHBindToParent is not available under Win95/98
+HRESULT CShellContextMenu::SHBindToParentEx (LPCITEMIDLIST pidl, REFIID riid, VOID **ppv, LPCITEMIDLIST *ppidlLast)
+{
+ HRESULT hr = 0;
+ if (!pidl || !ppv)
+ return E_POINTER;
+
+ int nCount = GetPIDLCount (pidl);
+ if (nCount == 0) // desktop pidl of invalid pidl
+ return E_POINTER;
+
+ IShellFolder * psfDesktop = NULL;
+ SHGetDesktopFolder (&psfDesktop);
+ if (nCount == 1) // desktop pidl
+ {
+ if ((hr = psfDesktop->QueryInterface(riid, ppv)) == S_OK)
+ {
+ if (ppidlLast)
+ *ppidlLast = CopyPIDL (pidl);
+ }
+ psfDesktop->Release ();
+ return hr;
+ }
+
+ LPBYTE pRel = GetPIDLPos (pidl, nCount - 1);
+ LPITEMIDLIST pidlParent = NULL;
+ pidlParent = CopyPIDL (pidl, pRel - (LPBYTE) pidl);
+ IShellFolder * psfFolder = NULL;
+
+ if ((hr = psfDesktop->BindToObject (pidlParent, NULL, IID_IShellFolder, (void **) &psfFolder)) != S_OK)
+ {
+ free (pidlParent);
+ psfDesktop->Release ();
+ return hr;
+ }
+ if ((hr = psfFolder->QueryInterface (riid, ppv)) == S_OK)
+ {
+ if (ppidlLast)
+ *ppidlLast = CopyPIDL ((LPCITEMIDLIST) pRel);
+ }
+ free (pidlParent);
+ psfFolder->Release ();
+ psfDesktop->Release ();
+ return hr;
+}
+
+
+LPBYTE CShellContextMenu::GetPIDLPos (LPCITEMIDLIST pidl, int nPos)
+{
+ if (!pidl)
+ return 0;
+ int nCount = 0;
+
+ BYTE * pCur = (BYTE *) pidl;
+ while (((LPCITEMIDLIST) pCur)->mkid.cb)
+ {
+ if (nCount == nPos)
+ return pCur;
+ nCount++;
+ pCur += ((LPCITEMIDLIST) pCur)->mkid.cb; // + sizeof(pidl->mkid.cb);
+ }
+ if (nCount == nPos)
+ return pCur;
+ return NULL;
+}
+
+
+int CShellContextMenu::GetPIDLCount (LPCITEMIDLIST pidl)
+{
+ if (!pidl)
+ return 0;
+
+ int nCount = 0;
+ BYTE* pCur = (BYTE *) pidl;
+ while (((LPCITEMIDLIST) pCur)->mkid.cb)
+ {
+ nCount++;
+ pCur += ((LPCITEMIDLIST) pCur)->mkid.cb;
+ }
+ return nCount;
+}
+
+#endif
+
diff --git a/src/kreplacements/ShellContextMenu.h b/src/kreplacements/ShellContextMenu.h
new file mode 100644
index 0000000..ae67483
--- /dev/null
+++ b/src/kreplacements/ShellContextMenu.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+ ShellContextMenu.h - description
+ -------------------
+ begin : Sat Mar 4 2006
+ copyright : (C) 2005-2007 by Joachim Eibl
+ email : 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+// ShellContextMenu.h: Schnittstelle fr die Klasse CShellContextMenu.
+//
+//////////////////////////////////////////////////////////////////////
+
+#ifndef SHELLCONTEXTMENU_H
+#define SHELLCONTEXTMENU_H
+
+/////////////////////////////////////////////////////////////////////
+// class to show shell contextmenu of files/folders/shell objects
+// developed by R. Engels 2003
+/////////////////////////////////////////////////////////////////////
+
+class CShellContextMenu
+{
+public:
+ HMENU GetMenu ();
+ void SetObjects (IShellFolder * psfFolder, LPITEMIDLIST pidlItem);
+ void SetObjects (IShellFolder * psfFolder, LPITEMIDLIST * pidlArray, int nItemCount);
+ void SetObjects (LPITEMIDLIST pidl);
+ void SetObjects (const QString& strObject);
+ void SetObjects (const QStringList& strList);
+ UINT ShowContextMenu (QWidget* pParent, QPoint pt, QPopupMenu* pMenu);
+ CShellContextMenu();
+ virtual ~CShellContextMenu();
+
+private:
+ int nItems;
+ BOOL bDelete;
+ HMENU m_hMenu;
+ IShellFolder * m_psfFolder;
+ LPITEMIDLIST * m_pidlArray;
+
+ void InvokeCommand (LPCONTEXTMENU pContextMenu, UINT idCommand);
+ BOOL GetContextMenu (void ** ppContextMenu, int & iMenuType);
+ HRESULT SHBindToParentEx (LPCITEMIDLIST pidl, REFIID riid, VOID **ppv, LPCITEMIDLIST *ppidlLast);
+ static LRESULT CALLBACK HookWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
+ void FreePIDLArray (LPITEMIDLIST * pidlArray);
+ LPITEMIDLIST CopyPIDL (LPCITEMIDLIST pidl, int cb = -1);
+ UINT GetPIDLSize (LPCITEMIDLIST pidl);
+ LPBYTE GetPIDLPos (LPCITEMIDLIST pidl, int nPos);
+ int GetPIDLCount (LPCITEMIDLIST pidl);
+};
+
+#endif
diff --git a/src/kreplacements/kaboutdata.h b/src/kreplacements/kaboutdata.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kaboutdata.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kaccel.h b/src/kreplacements/kaccel.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kaccel.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kaction.h b/src/kreplacements/kaction.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kaction.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kapplication.h b/src/kreplacements/kapplication.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kapplication.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kcmdlineargs.h b/src/kreplacements/kcmdlineargs.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kcmdlineargs.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kcolorbtn.h b/src/kreplacements/kcolorbtn.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kcolorbtn.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kconfig.h b/src/kreplacements/kconfig.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kconfig.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kdialogbase.h b/src/kreplacements/kdialogbase.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kdialogbase.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kedittoolbar.h b/src/kreplacements/kedittoolbar.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kedittoolbar.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kfiledialog.h b/src/kreplacements/kfiledialog.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kfiledialog.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kfontdialog.h b/src/kreplacements/kfontdialog.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kfontdialog.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kiconloader.h b/src/kreplacements/kiconloader.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kiconloader.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kinstance.h b/src/kreplacements/kinstance.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kinstance.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kio/global.h b/src/kreplacements/kio/global.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kio/global.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kio/job.h b/src/kreplacements/kio/job.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kio/job.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kio/jobclasses.h b/src/kreplacements/kio/jobclasses.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kio/jobclasses.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kkeydialog.h b/src/kreplacements/kkeydialog.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kkeydialog.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/klibloader.h b/src/kreplacements/klibloader.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/klibloader.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/klocale.h b/src/kreplacements/klocale.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/klocale.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kmainwindow.h b/src/kreplacements/kmainwindow.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kmainwindow.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kmenubar.h b/src/kreplacements/kmenubar.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kmenubar.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kmessagebox.h b/src/kreplacements/kmessagebox.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kmessagebox.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/konq_popupmenu.h b/src/kreplacements/konq_popupmenu.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/konq_popupmenu.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kparts/factory.h b/src/kreplacements/kparts/factory.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kparts/factory.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kparts/mainwindow.h b/src/kreplacements/kparts/mainwindow.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kparts/mainwindow.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kparts/part.h b/src/kreplacements/kparts/part.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kparts/part.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kpopupmenu.h b/src/kreplacements/kpopupmenu.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kpopupmenu.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kprinter.h b/src/kreplacements/kprinter.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kprinter.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kprogress.h b/src/kreplacements/kprogress.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kprogress.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kreplacements.cpp b/src/kreplacements/kreplacements.cpp
new file mode 100644
index 0000000..95885a5
--- /dev/null
+++ b/src/kreplacements/kreplacements.cpp
@@ -0,0 +1,1185 @@
+/***************************************************************************
+ kreplacements.cpp - description
+ -------------------
+ begin : Sat Aug 3 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 "kreplacements.h"
+#include "common.h"
+
+#include <assert.h>
+
+#include <qnamespace.h>
+#include <qmessagebox.h>
+#include <qpopupmenu.h>
+#include <qmenubar.h>
+#include <qpainter.h>
+#include <qcolordialog.h>
+#include <qfontdialog.h>
+#include <qlabel.h>
+#include <qtextbrowser.h>
+#include <qtextstream.h>
+#include <qlayout.h>
+#include <qdockarea.h>
+
+#include <vector>
+#include <iostream>
+#include <algorithm>
+
+
+static QString s_copyright;
+static QString s_email;
+static QString s_description;
+static QString s_appName;
+static QString s_version;
+static QString s_homepage;
+static KAboutData* s_pAboutData;
+
+
+#ifdef _WIN32
+#include <process.h>
+#include <windows.h>
+#endif
+
+static void showHelp()
+{
+ #ifdef _WIN32
+ char buf[200];
+ int r= SearchPathA( 0, ".", 0, sizeof(buf), buf, 0 );
+
+ QString exePath;
+ if (r!=0) { exePath = buf; }
+ else { exePath = "."; }
+
+ QFileInfo helpFile( exePath + "\\doc\\en\\index.html" );
+ if ( ! helpFile.exists() ) { helpFile.setFile( exePath + "\\..\\doc\\en\\index.html" ); }
+ if ( ! helpFile.exists() ) { helpFile.setFile( exePath + "\\doc\\index.html" ); }
+ if ( ! helpFile.exists() ) { helpFile.setFile( exePath + "\\..\\doc\\index.html" ); }
+ if ( ! helpFile.exists() )
+ {
+ QMessageBox::warning( 0, "KDiff3 documentation not found",
+ "Couldn't find the documentation. \n\n"
+ "The documentation can also be found at the homepage:\n\n "
+ " http://kdiff3.sourceforge.net/");
+ return;
+ }
+
+ HINSTANCE hi = FindExecutableA( helpFile.fileName().ascii(), helpFile.dirPath(true).ascii(), buf );
+ if ( int(hi)<=32 )
+ {
+ static QTextBrowser* pBrowser = 0;
+ if (pBrowser==0)
+ {
+ pBrowser = new QTextBrowser( 0 );
+ pBrowser->setMinimumSize( 600, 400 );
+ }
+ pBrowser->setSource(helpFile.filePath());
+ pBrowser->show();
+ }
+ else
+ {
+ QFileInfo prog( buf );
+ _spawnlp( _P_NOWAIT , prog.filePath().ascii(), prog.fileName().ascii(), ("\"file:///"+helpFile.absFilePath()+"\"").ascii(), NULL );
+ }
+
+ #else
+ static QTextBrowser* pBrowser = 0;
+ if (pBrowser==0)
+ {
+ pBrowser = new QTextBrowser( 0 );
+ pBrowser->setMinimumSize( 600, 400 );
+ }
+ pBrowser->setSource("/usr/local/share/doc/kdiff3/en/index.html");
+ pBrowser->show();
+ #endif
+}
+
+QString getTranslationDir()
+{
+ #ifdef _WIN32
+ char buf[200];
+ int r= SearchPathA( 0, ".", 0, sizeof(buf), buf, 0 );
+
+ QString exePath;
+ if (r!=0) { exePath = buf; }
+ else { exePath = "."; }
+ return exePath+"/translations";
+ #else
+ return ".";
+ #endif
+}
+
+// static
+void KMessageBox::error( QWidget* parent, const QString& text, const QString& caption )
+{
+ QMessageBox::critical( parent, caption, text );
+}
+
+int KMessageBox::warningContinueCancel( QWidget* parent, const QString& text, const QString& caption,
+ const QString& button1 )
+{
+ return 0 == QMessageBox::warning( parent, caption, text, button1, "Cancel" ) ? Continue : Cancel;
+}
+
+void KMessageBox::sorry( QWidget* parent, const QString& text, const QString& caption )
+{
+ QMessageBox::information( parent, caption, text );
+}
+
+void KMessageBox::information( QWidget* parent, const QString& text, const QString& caption )
+{
+ QMessageBox::information( parent, caption, text );
+}
+
+int KMessageBox::warningYesNo( QWidget* parent, const QString& text, const QString& caption,
+ const QString& button1, const QString& button2 )
+{
+ return 0 == QMessageBox::warning( parent, caption, text, button1, button2, QString::null, 1, 1 ) ? Yes : No;
+}
+
+int KMessageBox::warningYesNoCancel( QWidget* parent, const QString& text, const QString& caption,
+ const QString& button1, const QString& button2 )
+{
+ int val = QMessageBox::warning( parent, caption, text,
+ button1, button2, i18n("Cancel") );
+ if ( val==0 ) return Yes;
+ if ( val==1 ) return No;
+ else return Cancel;
+}
+
+
+KDialogBase::KDialogBase( int, const QString& caption, int, int, QWidget* parent, const char* name,
+ bool /*modal*/, bool )
+: QTabDialog( parent, name, true /* modal */ )
+{
+ setCaption( caption );
+ setDefaultButton();
+ setHelpButton();
+ setCancelButton();
+ //setApplyButton();
+ setOkButton();
+ setDefaultButton();
+
+ connect( this, SIGNAL( defaultButtonPressed() ), this, SLOT(slotDefault()) );
+ connect( this, SIGNAL( helpButtonPressed() ), this, SLOT(slotHelp()));
+ connect( this, SIGNAL( applyButtonPressed() ), this, SLOT( slotApply() ));
+}
+
+KDialogBase::~KDialogBase()
+{
+}
+
+void KDialogBase::incInitialSize ( const QSize& )
+{
+}
+
+void KDialogBase::setHelp(const QString&, const QString& )
+{
+}
+
+
+int KDialogBase::BarIcon(const QString& /*iconName*/, int )
+{
+ return 0; // Not used for replacement.
+}
+
+
+QVBox* KDialogBase::addVBoxPage( const QString& name, const QString& /*info*/, int )
+{
+ QVBox* p = new QVBox(this, name.ascii());
+ addTab( p, name );
+ return p;
+}
+
+QFrame* KDialogBase::addPage( const QString& name, const QString& /*info*/, int )
+{
+ QFrame* p = new QFrame( this, name.ascii() );
+ addTab( p, name );
+ return p;
+}
+
+int KDialogBase::spacingHint()
+{
+ return 5;
+}
+
+static bool s_inAccept = false;
+static bool s_bAccepted = false;
+void KDialogBase::accept()
+{
+ if( ! s_inAccept )
+ {
+ s_bAccepted = false;
+ s_inAccept = true;
+ slotOk();
+ s_inAccept = false;
+ if ( s_bAccepted )
+ QTabDialog::accept();
+ }
+ else
+ {
+ s_bAccepted = true;
+ }
+}
+
+void KDialogBase::slotDefault( )
+{
+}
+void KDialogBase::slotOk()
+{
+}
+void KDialogBase::slotCancel( )
+{
+}
+void KDialogBase::slotApply( )
+{
+ emit applyClicked();
+}
+void KDialogBase::slotHelp( )
+{
+ showHelp();
+}
+
+KURL KFileDialog::getSaveURL( const QString &startDir,
+ const QString &filter,
+ QWidget *parent, const QString &caption)
+{
+ QString s = QFileDialog::getSaveFileName(startDir, filter, parent, 0, caption);
+ return KURL(s);
+}
+
+KURL KFileDialog::getOpenURL( const QString & startDir,
+ const QString & filter,
+ QWidget * parent,
+ const QString & caption )
+{
+ QString s = QFileDialog::getOpenFileName(startDir, filter, parent, 0, caption);
+ return KURL(s);
+}
+
+KURL KFileDialog::getExistingURL( const QString & startDir,
+ QWidget * parent,
+ const QString & caption)
+{
+ QString s = QFileDialog::getExistingDirectory(startDir, parent, 0, caption);
+ return KURL(s);
+}
+
+QString KFileDialog::getSaveFileName (const QString &startDir,
+ const QString &filter,
+ QWidget *parent,
+ const QString &caption)
+{
+ return QFileDialog::getSaveFileName( startDir, filter, parent, 0, caption );
+}
+
+
+KToolBar::BarPosition KToolBar::barPos()
+{
+ if ( m_pMainWindow->leftDock()->hasDockWindow(this) ) return Left;
+ if ( m_pMainWindow->rightDock()->hasDockWindow(this) ) return Right;
+ if ( m_pMainWindow->topDock()->hasDockWindow(this) ) return Top;
+ if ( m_pMainWindow->bottomDock()->hasDockWindow(this) ) return Bottom;
+ return Top;
+}
+
+void KToolBar::setBarPos(BarPosition bp)
+{
+ if ( bp == Left ) m_pMainWindow->moveDockWindow( this, DockLeft );
+ else if ( bp == Right ) m_pMainWindow->moveDockWindow( this, DockRight );
+ else if ( bp == Bottom ) m_pMainWindow->moveDockWindow( this, DockBottom );
+ else if ( bp == Top ) m_pMainWindow->moveDockWindow( this, DockTop );
+}
+
+KToolBar::KToolBar( QMainWindow* parent )
+: QToolBar( parent )
+{
+ m_pMainWindow = parent;
+}
+
+
+KMainWindow::KMainWindow( QWidget* parent, const char* name )
+: QMainWindow( parent, name ), m_actionCollection(this)
+{
+ fileMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("&File"), fileMenu);
+ editMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("&Edit"), editMenu);
+ directoryMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("&Directory"), directoryMenu);
+ dirCurrentItemMenu = 0;
+ dirCurrentSyncItemMenu = 0;
+ movementMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("&Movement"), movementMenu);
+ diffMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("D&iffview"), diffMenu);
+ mergeMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("&Merge"), mergeMenu);
+ windowsMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("&Window"), windowsMenu);
+ settingsMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("&Settings"), settingsMenu);
+ helpMenu = new QPopupMenu();
+ menuBar()->insertItem(i18n("&Help"), helpMenu);
+
+ m_pToolBar = new KToolBar(this);
+
+ memberList = new QList<KMainWindow>;
+ memberList->append(this);
+}
+
+KToolBar* KMainWindow::toolBar(const QString&)
+{
+ return m_pToolBar;
+}
+
+KActionCollection* KMainWindow::actionCollection()
+{
+ return &m_actionCollection;
+}
+
+void KMainWindow::createGUI()
+{
+ KStdAction::help(this, SLOT(slotHelp()), actionCollection());
+ KStdAction::about(this, SLOT(slotAbout()), actionCollection());
+ KStdAction::aboutQt(actionCollection());
+}
+
+void KMainWindow::slotAbout()
+{
+ QTabDialog d;
+ d.setCaption("About " + s_appName);
+ QTextBrowser* tb1 = new QTextBrowser(&d);
+ tb1->setWordWrap( QTextEdit::NoWrap );
+ tb1->setText(
+ s_appName + " Version " + s_version +
+ "\n\n" + s_description +
+ "\n\n" + s_copyright +
+ "\n\nHomepage: " + s_homepage +
+ "\n\nLicence: GNU GPL Version 2"
+ );
+ d.addTab(tb1,i18n("&About"));
+
+ std::list<KAboutData::AboutDataEntry>::iterator i;
+
+ QString s2;
+ for( i=s_pAboutData->m_authorList.begin(); i!=s_pAboutData->m_authorList.end(); ++i )
+ {
+ if ( !i->m_name.isEmpty() ) s2 += i->m_name + "\n";
+ if ( !i->m_task.isEmpty() ) s2 += " " + i->m_task + "\n";
+ if ( !i->m_email.isEmpty() ) s2 += " " + i->m_email + "\n";
+ if ( !i->m_weblink.isEmpty() ) s2 += " " + i->m_weblink + "\n";
+ s2 += "\n";
+ }
+ QTextBrowser* tb2 = new QTextBrowser(&d);
+ tb2->setWordWrap( QTextEdit::NoWrap );
+ tb2->setText(s2);
+ d.addTab(tb2,i18n("A&uthor"));
+
+ QString s3;
+ for( i=s_pAboutData->m_creditList.begin(); i!=s_pAboutData->m_creditList.end(); ++i )
+ {
+ if ( !i->m_name.isEmpty() ) s3 += i->m_name + "\n";
+ if ( !i->m_task.isEmpty() ) s3 += " " + i->m_task + "\n";
+ if ( !i->m_email.isEmpty() ) s3 += " " + i->m_email + "\n";
+ if ( !i->m_weblink.isEmpty() ) s3 += " " + i->m_weblink + "\n";
+ s3 += "\n";
+ }
+ QTextBrowser* tb3 = new QTextBrowser(&d);
+ tb3->setWordWrap( QTextEdit::NoWrap );
+ tb3->setText(s3);
+ d.addTab(tb3,i18n("&Thanks To"));
+
+ d.resize(400,300);
+ d.exec();
+/*
+ QMessageBox::information(
+ this,
+ "About " + s_appName,
+ s_appName + " Version " + s_version +
+ "\n\n" + s_description +
+ "\n\n" + s_copyright +
+ "\n\nHomepage: " + s_homepage +
+ "\n\nLicence: GNU GPL Version 2"
+ );
+*/
+}
+
+void KMainWindow::slotHelp()
+{
+ showHelp();
+}
+
+
+QString KStandardDirs::findResource(const QString& resource, const QString& /*appName*/)
+{
+ if (resource=="config")
+ {
+ QString home = QDir::homeDirPath();
+ return home + "/.kdiff3rc";
+ }
+ return QString();
+}
+
+KConfig::KConfig()
+{
+}
+
+void KConfig::readConfigFile( const QString& configFileName )
+{
+ if ( !configFileName.isEmpty() )
+ {
+ m_fileName = configFileName;
+ }
+ else
+ {
+ m_fileName = KStandardDirs().findResource("config","kdiff3rc");
+ }
+
+ QFile f( m_fileName );
+ if ( f.open(IO_ReadOnly) )
+ { // file opened successfully
+ QTextStream t( &f ); // use a text stream
+ load(t);
+ f.close();
+ }
+}
+
+KConfig::~KConfig()
+{
+ QFile f(m_fileName);
+ if ( f.open( IO_WriteOnly | IO_Translate ) )
+ { // file opened successfully
+ QTextStream t( &f ); // use a text stream
+ save(t);
+ f.close();
+ }
+}
+
+void KConfig::setGroup(const QString&)
+{
+}
+
+void KAction::init(QObject* receiver, const char* slot, KActionCollection* actionCollection,
+ const char* name, bool bToggle, bool bMenu)
+{
+ QString n(name);
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ if( slot!=0 )
+ {
+ if (!bToggle)
+ connect(this, SIGNAL(activated()), receiver, slot);
+ else
+ {
+ connect(this, SIGNAL(toggled(bool)), receiver, slot);
+ }
+ }
+
+ if (bMenu)
+ {
+ if( n[0]=='g') addTo( p->movementMenu );
+ else if( n.left(16)=="dir_current_sync")
+ {
+ if ( p->dirCurrentItemMenu==0 )
+ {
+ p->dirCurrentItemMenu = new QPopupMenu();
+ p->directoryMenu->insertItem(i18n("Current Item Merge Operation"), p->dirCurrentItemMenu);
+ p->dirCurrentSyncItemMenu = new QPopupMenu();
+ p->directoryMenu->insertItem(i18n("Current Item Sync Operation"), p->dirCurrentSyncItemMenu);
+ }
+ addTo( p->dirCurrentItemMenu );
+ }
+ else if( n.left(11)=="dir_current")
+ {
+ if ( p->dirCurrentItemMenu==0 )
+ {
+ p->dirCurrentItemMenu = new QPopupMenu();
+ p->directoryMenu->insertItem(i18n("Current Item Merge Operation"), p->dirCurrentItemMenu);
+ p->dirCurrentSyncItemMenu = new QPopupMenu();
+ p->directoryMenu->insertItem(i18n("Current Item Sync Operation"), p->dirCurrentSyncItemMenu);
+ }
+ addTo( p->dirCurrentSyncItemMenu );
+ }
+ else if( n.left(4)=="diff") addTo( p->diffMenu );
+ else if( name[0]=='d') addTo( p->directoryMenu );
+ else if( name[0]=='f') addTo( p->fileMenu );
+ else if( name[0]=='w') addTo( p->windowsMenu );
+ else addTo( p->mergeMenu );
+ }
+}
+
+
+KAction::KAction(const QString& text, const QIconSet& icon, int accel,
+ QObject* receiver, const char* slot, KActionCollection* actionCollection,
+ const char* name, bool bToggle, bool bMenu
+ )
+: QAction ( text, icon, text, accel, actionCollection->m_pMainWindow, name, bToggle )
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ if ( !icon.isNull() && p ) this->addTo( p->m_pToolBar );
+
+ init(receiver,slot,actionCollection,name,bToggle,bMenu);
+}
+
+KAction::KAction(const QString& text, int accel,
+ QObject* receiver, const char* slot, KActionCollection* actionCollection,
+ const char* name, bool bToggle, bool bMenu
+ )
+: QAction ( text, text, accel, actionCollection->m_pMainWindow, name, bToggle )
+{
+ init(receiver,slot,actionCollection,name,bToggle,bMenu);
+}
+
+void KAction::setStatusText(const QString&)
+{
+}
+
+void KAction::plug(QPopupMenu* menu)
+{
+ addTo(menu);
+}
+
+
+KToggleAction::KToggleAction(const QString& text, const QIconSet& icon, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bMenu)
+: KAction( text, icon, accel, receiver, slot, actionCollection, name, true, bMenu)
+{
+}
+
+KToggleAction::KToggleAction(const QString& text, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bMenu)
+: KAction( text, accel, receiver, slot, actionCollection, name, true, bMenu)
+{
+}
+
+KToggleAction::KToggleAction(const QString& text, const QIconSet& icon, int accel, KActionCollection* actionCollection, const char* name, bool bMenu)
+: KAction( text, icon, accel, 0, 0, actionCollection, name, true, bMenu)
+{
+}
+
+void KToggleAction::setChecked(bool bChecked)
+{
+ blockSignals( true );
+ setOn( bChecked );
+ blockSignals( false );
+}
+
+bool KToggleAction::isChecked()
+{
+ return isOn();
+}
+
+
+
+//static
+KAction* KStdAction::open( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ #include "../xpm/fileopen.xpm"
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Open"), QIconSet(QPixmap(fileopen)), Qt::CTRL+Qt::Key_O, parent, slot, actionCollection, "open", false, false);
+ if(p){ a->addTo( p->fileMenu ); }
+ return a;
+}
+
+KAction* KStdAction::save( QWidget* parent, const char* slot, KActionCollection* actionCollection )
+{
+ #include "../xpm/filesave.xpm"
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Save"), QIconSet(QPixmap(filesave)), Qt::CTRL+Qt::Key_S, parent, slot, actionCollection, "save", false, false);
+ if(p){ a->addTo( p->fileMenu ); }
+ return a;
+}
+
+KAction* KStdAction::saveAs( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Save As..."), 0, parent, slot, actionCollection, "saveas", false, false);
+ if(p) a->addTo( p->fileMenu );
+ return a;
+}
+
+KAction* KStdAction::print( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ #include "../xpm/fileprint.xpm"
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Print..."), QIconSet(QPixmap(fileprint)),Qt::CTRL+Qt::Key_P, parent, slot, actionCollection, "print", false, false);
+ if(p) a->addTo( p->fileMenu );
+ return a;
+}
+
+KAction* KStdAction::quit( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Quit"), Qt::CTRL+Qt::Key_Q, parent, slot, actionCollection, "quit", false, false);
+ if(p) a->addTo( p->fileMenu );
+ return a;
+}
+
+KAction* KStdAction::cut( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Cut"), Qt::CTRL+Qt::Key_X, parent, slot, actionCollection, "cut", false, false );
+ if(p) a->addTo( p->editMenu );
+ return a;
+}
+
+KAction* KStdAction::copy( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Copy"), Qt::CTRL+Qt::Key_C, parent, slot, actionCollection, "copy", false, false );
+ if(p) a->addTo( p->editMenu );
+ return a;
+}
+
+KAction* KStdAction::paste( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Paste"), Qt::CTRL+Qt::Key_V, parent, slot, actionCollection, "paste", false, false );
+ if(p) a->addTo( p->editMenu );
+ return a;
+}
+
+KAction* KStdAction::selectAll( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Select All"), Qt::CTRL+Qt::Key_A, parent, slot, actionCollection, "selectall", false, false );
+ if(p) a->addTo( p->editMenu );
+ return a;
+}
+
+KToggleAction* KStdAction::showToolbar( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KToggleAction* a = new KToggleAction( i18n("Show Toolbar"), 0, parent, slot, actionCollection, "showtoolbar", false );
+ if(p) a->addTo( p->settingsMenu );
+ return a;
+}
+
+KToggleAction* KStdAction::showStatusbar( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KToggleAction* a = new KToggleAction( i18n("Show &Statusbar"), 0, parent, slot, actionCollection, "showstatusbar", false );
+ if(p) a->addTo( p->settingsMenu );
+ return a;
+}
+
+KAction* KStdAction::preferences( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("&Configure %1...").arg("KDiff3"), 0, parent, slot, actionCollection, "settings", false, false );
+ if(p) a->addTo( p->settingsMenu );
+ return a;
+}
+KAction* KStdAction::keyBindings( QWidget*, const char*, KActionCollection*)
+{
+ return 0;
+}
+
+KAction* KStdAction::about( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("About")+" KDiff3", 0, parent, slot, actionCollection, "about_kdiff3", false, false );
+ if(p) a->addTo( p->helpMenu );
+ return a;
+}
+
+KAction* KStdAction::aboutQt( KActionCollection* actionCollection )
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("About")+" Qt", 0, qApp, SLOT(aboutQt()), actionCollection, "about_qt", false, false );
+ if(p) a->addTo( p->helpMenu );
+ return a;
+}
+
+KAction* KStdAction::help( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Help"), Qt::Key_F1, parent, slot, actionCollection, "help", false, false );
+ if(p) a->addTo( p->helpMenu );
+ return a;
+}
+KAction* KStdAction::find( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Find"), Qt::CTRL+Qt::Key_F, parent, slot, actionCollection, "find", false, false );
+ if(p) a->addTo( p->editMenu );
+ return a;
+}
+
+KAction* KStdAction::findNext( QWidget* parent, const char* slot, KActionCollection* actionCollection)
+{
+ KMainWindow* p = actionCollection->m_pMainWindow;
+ KAction* a = new KAction( i18n("Find Next"), Qt::Key_F3, parent, slot, actionCollection, "findNext", false, false );
+ if(p) a->addTo( p->editMenu );
+ return a;
+}
+
+
+
+
+KFontChooser::KFontChooser( QWidget* pParent, const QString& /*name*/, bool, const QStringList&, bool, int )
+: QWidget(pParent)
+{
+ m_pParent = pParent;
+ QVBoxLayout* pLayout = new QVBoxLayout( this );
+ m_pSelectFont = new QPushButton(i18n("Select Font"), this );
+ connect(m_pSelectFont, SIGNAL(clicked()), this, SLOT(slotSelectFont()));
+ pLayout->addWidget(m_pSelectFont);
+
+ m_pLabel = new QLabel( "", this );
+ m_pLabel->setFont( m_font );
+ m_pLabel->setMinimumWidth(200);
+ m_pLabel->setText( "The quick brown fox jumps over the river\n"
+ "but the little red hen escapes with a shiver.\n"
+ ":-)");
+ pLayout->addWidget(m_pLabel);
+}
+
+QFont KFontChooser::font()
+{
+ return m_font;//QFont("courier",10);
+}
+
+void KFontChooser::setFont( const QFont& font, bool )
+{
+ m_font = font;
+ m_pLabel->setFont( m_font );
+ //update();
+}
+
+void KFontChooser::slotSelectFont()
+{
+ for(;;)
+ {
+ bool bOk;
+ m_font = QFontDialog::getFont(&bOk, m_font );
+ m_pLabel->setFont( m_font );
+ QFontMetrics fm(m_font);
+
+ // Variable width font.
+ if ( fm.width('W')!=fm.width('i') )
+ {
+ int result = KMessageBox::warningYesNo(m_pParent, i18n(
+ "You selected a variable width font.\n\n"
+ "Because this program doesn't handle variable width fonts\n"
+ "correctly, you might experience problems while editing.\n\n"
+ "Do you want to continue or do you want to select another font."),
+ i18n("Incompatible font."),
+ i18n("Continue at my own risk"), i18n("Select another font"));
+ if (result==KMessageBox::Yes)
+ return;
+ }
+ else
+ return;
+ }
+}
+
+
+KColorButton::KColorButton(QWidget* parent)
+: QPushButton(parent)
+{
+ connect( this, SIGNAL(clicked()), this, SLOT(slotClicked()));
+}
+
+QColor KColorButton::color()
+{
+ return m_color;
+}
+
+void KColorButton::setColor( const QColor& color )
+{
+ m_color = color;
+ update();
+}
+
+void KColorButton::paintEvent( QPaintEvent* e )
+{
+ QPushButton::paintEvent(e);
+ QPainter p(this);
+
+ int w = width();
+ int h = height();
+ p.fillRect( 10, 5, w-20, h-10, m_color );
+ p.drawRect( 10, 5, w-20, h-10 );
+}
+
+void KColorButton::slotClicked()
+{
+ // Under Windows ChooseColor() should be used. (Nicer if few colors exist.)
+ QColor c = QColorDialog::getColor ( m_color, this );
+ if ( c.isValid() ) m_color = c;
+ update();
+}
+
+KPrinter::KPrinter()
+{
+}
+QValueList<int> KPrinter::pageList()
+{
+ QValueList<int> vl;
+ int to = toPage();
+ for(int i=fromPage(); i<=to; ++i)
+ {
+ vl.push_back(i);
+ }
+ return vl;
+}
+void KPrinter::setCurrentPage(int)
+{
+}
+void KPrinter::setPageSelection(e_PageSelection)
+{
+}
+
+
+QPixmap KIconLoader::loadIcon( const QString&, int )
+{
+ return QPixmap();
+}
+
+KAboutData::KAboutData( const QString& /*name*/, const QString& appName, const QString& version,
+ const QString& description, int,
+ const QString& copyright, int, const QString& homepage, const QString& email)
+{
+ s_copyright = copyright;
+ s_email = email;
+ s_appName = appName;
+ s_description = description;
+ s_version = version;
+ s_homepage = homepage;
+}
+
+KAboutData::KAboutData( const QString& /*name*/, const QString& /*appName*/, const QString& /*version*/ )
+{
+}
+
+void KAboutData::addAuthor(const char* name, const char* task, const char* email, const char* weblink)
+{
+ m_authorList.push_back( AboutDataEntry( name, task, email, weblink) );
+}
+
+void KAboutData::addCredit(const char* name, const char* task, const char* email, const char* weblink)
+{
+ m_creditList.push_back( AboutDataEntry( name, task, email, weblink) );
+}
+
+/* Option structure: e.g.:
+ { "m", 0, 0 },
+ { "merge", I18N_NOOP("Automatically merge the input."), 0 },
+ { "o", 0, 0 },
+ { "output file", I18N_NOOP("Output file. Implies -m. E.g.: -o newfile.txt"), 0 },
+ { "+[File1]", I18N_NOOP("file1 to open (base)"), 0 },
+ { "+[File2]", I18N_NOOP("file2 to open"), 0 },
+ { "+[File3]", I18N_NOOP("file3 to open"), 0 },
+*/
+////////////////
+static KCmdLineArgs s_cmdLineArgs;
+static int s_argc;
+static char** s_argv;
+static KCmdLineOptions* s_pOptions;
+
+static std::vector<QCStringList> s_vOption;
+static std::vector<const char*> s_vArg;
+
+KCmdLineArgs* KCmdLineArgs::parsedArgs() // static
+{
+ return &s_cmdLineArgs;
+}
+
+void KCmdLineArgs::init( int argc, char**argv, KAboutData* pAboutData ) // static
+{
+ s_argc = argc;
+ s_argv = argv;
+ s_pAboutData = pAboutData;
+}
+
+void KCmdLineArgs::addCmdLineOptions( KCmdLineOptions* options ) // static
+{
+ s_pOptions = options;
+}
+
+int KCmdLineArgs::count()
+{
+ return s_vArg.size();
+}
+
+QString KCmdLineArgs::arg(int idx)
+{
+ return QString::fromLocal8Bit( s_vArg[idx] );
+}
+
+void KCmdLineArgs::clear()
+{
+}
+
+QString KCmdLineArgs::getOption( const QString& s )
+{
+ // Find the option
+ int j=0;
+ for( j=0; j<(int)s_vOption.size(); ++j )
+ {
+ const char* optName = s_pOptions[j].name;
+ const char* pos = strchr( optName,' ' );
+ int len = pos==0 ? strlen( optName ) : pos - optName;
+
+ if( s == (const char*)( QCString( optName, len+1) ) )
+ {
+ return s_vOption[j].isEmpty() ? QString() : s_vOption[j].last();
+ }
+ }
+ assert(false);
+ return QString();
+}
+
+QCStringList KCmdLineArgs::getOptionList( const QString& s )
+{
+ // Find the option
+ int j=0;
+ for( j=0; j<(int)s_vOption.size(); ++j )
+ {
+ const char* optName = s_pOptions[j].name;
+ const char* pos = strchr( optName,' ' );
+ int len = pos==0 ? strlen( optName ) : pos - optName;
+
+ if( s == (const char*)( QCString( optName, len+1) ) )
+ {
+ return s_vOption[j];
+ }
+ }
+
+ assert(false);
+ return QCStringList();
+}
+
+bool KCmdLineArgs::isSet(const QString& s)
+{
+ // Find the option
+ int j=0;
+ for( j=0; j<(int)s_vOption.size(); ++j )
+ {
+ const char* optName = s_pOptions[j].name;
+ if( s == QString( optName ) )
+ {
+ return ! s_vOption[j].isEmpty();
+ }
+ }
+ assert(false);
+ return false;
+}
+
+///////////////////
+KApplication* kapp;
+
+KApplication::KApplication()
+: QApplication( s_argc,s_argv )
+{
+ kapp = this;
+
+ int nofOptions=0;
+ int nofArgs=0;
+ int i=0;
+ while( s_pOptions[i].name != 0 )
+ {
+ if ( s_pOptions[i].name[0]=='[' )
+ nofArgs++;
+ else
+ nofOptions++;
+
+ ++i;
+ }
+
+ // First find the option "-config" or "--config" to allow loading of options
+ QString configFileName;
+ for( i=1; i<s_argc-1; ++i )
+ {
+ QString arg = s_argv[i];
+ if ( arg == "-config" || arg == "--config" )
+ {
+ configFileName = s_argv[i+1];
+ }
+ }
+ m_config.readConfigFile(configFileName);
+
+ QStringList ignorableCmdLineOptionsList = m_config.readListEntry("IgnorableCmdLineOptions", QString("-u;-query;-html;-abort"), '|');
+ QString ignorableCmdLineOptions;
+ if ( !ignorableCmdLineOptionsList.isEmpty() )
+ ignorableCmdLineOptions = ignorableCmdLineOptionsList.front() + ";";
+
+ s_vOption.resize(nofOptions);
+
+ for( i=1; i<s_argc; ++i )
+ {
+ if ( s_argv[i][0]=='-' ) // An option
+ {
+ if ( ignorableCmdLineOptions.contains(QString(s_argv[i])+";") )
+ continue;
+ // Find the option
+ int j=0;
+ for( j=0; j<nofOptions; ++j )
+ {
+ const char* optName = s_pOptions[j].name;
+ const char* pos = strchr( optName,' ' );
+ int len = pos==0 ? strlen( optName ) : pos - optName;
+ int len2 = strlen(s_argv[i]);
+
+ if( len>0 && ( s_argv[i][1]=='-' && len2-2==len && memcmp( &s_argv[i][2], optName, len )==0 ||
+ len2-1==len && memcmp( &s_argv[i][1], optName, len )==0 ))
+ {
+ if (s_pOptions[j].description == 0) // alias, because without description.
+ {
+ ++j;
+ optName = s_pOptions[j].name;
+ pos = strchr( optName,' ' );
+ }
+ if (pos!=0){ ++i; s_vOption[j].append(s_argv[i]); } //use param
+ else { s_vOption[j].append("1"); } //set state
+ break;
+ }
+ }
+ if (j==nofOptions)
+ {
+ QString s;
+ s = QString("Unknown option: ") + s_argv[i] + "\n";
+ s += "If KDiff3 should ignore this option, run KDiff3 normally and edit\n"
+ "the \"Command line options to ignore\" in the \"Integration Settings\".\n\n";
+
+ s += "KDiff3-Usage when starting via commandline: \n";
+ s += "- Comparing 2 files:\t\tkdiff3 file1 file2\n";
+ s += "- Merging 2 files: \t\tkdiff3 file1 file2 -o outputfile\n";
+ s += "- Comparing 3 files:\t\tkdiff3 file1 file2 file3\n";
+ s += "- Merging 3 files: \t\tkdiff3 file1 file2 file3 -o outputfile\n";
+ s += " Note that file1 will be treated as base of file2 and file3.\n";
+ s += "\n";
+ s += "If you start without arguments, then a dialog will appear\n";
+ s += "where you can select your files via a filebrowser.\n";
+ s += "\n";
+
+ s += "Options:\n";
+
+ j=0;
+ int pos=s.length();
+ for( j=0; j<nofOptions; ++j )
+ {
+ if ( s_pOptions[j].description!=0 )
+ {
+ if (s_pOptions[j].name[0]!='+')
+ {
+ s += "-";
+ if ( strlen(s_pOptions[j].name)>1 ) s += "-";
+ }
+ s += s_pOptions[j].name;
+ s += QString().fill(' ', minMaxLimiter( 20 - ((int)s.length()-pos), 3, 20 ) );
+ s += s_pOptions[j].description;
+ s +="\n";
+ pos=s.length();
+ }
+ else
+ {
+ s += "-";
+ if ( strlen(s_pOptions[j].name)>1 ) s += "-";
+ s += s_pOptions[j].name;
+ s += ", ";
+ }
+ }
+
+ s += "\n"+i18n("For more documentation, see the help-menu or the subdirectory doc.")+"\n";
+#ifdef _WIN32
+ // A windows program has no console
+ if ( 0==QMessageBox::information(0, i18n("KDiff3-Usage"), s, i18n("Ignore"),i18n("Exit") ) )
+ continue;
+#else
+ std::cerr << s.latin1() << std::endl;
+#endif
+
+ ::exit(-1);
+ }
+ }
+ else
+ s_vArg.push_back( s_argv[i] );
+ }
+}
+
+KConfig* KApplication::config()
+{
+ return &m_config;
+}
+
+bool KApplication::isRestored()
+{
+ return false;
+}
+
+KApplication* KApplication::kApplication()
+{
+ return kapp;
+}
+
+KIconLoader* KApplication::iconLoader()
+{
+ return &m_iconLoader;
+}
+
+
+namespace KIO
+{
+ SimpleJob* mkdir( KURL ){return 0;}
+ SimpleJob* rmdir( KURL ){return 0;}
+ SimpleJob* file_delete( KURL, bool ){return 0;}
+ FileCopyJob* file_move( KURL, KURL, int, bool, bool, bool ) {return 0;}
+ FileCopyJob* file_copy( KURL, KURL, int, bool, bool, bool ) {return 0;}
+ CopyJob* link( KURL, KURL, bool ) {return 0;}
+ ListJob* listRecursive( KURL, bool, bool ){return 0;}
+ ListJob* listDir( KURL, bool, bool ){return 0;}
+ StatJob* stat( KURL, bool, int, bool ){return 0;}
+ TransferJob* get( KURL, bool, bool ){return (TransferJob*)0;}
+ TransferJob* put( KURL, int, bool, bool, bool ){return (TransferJob*)0;}
+};
+
+KActionCollection* KParts::Part::actionCollection()
+{
+ return 0;
+}
+
+KApplication* KParts::Part::instance()
+{
+ return kapp;
+}
+
+
+KLibLoader* KLibLoader::self()
+{
+ static KLibLoader ll;
+ return &ll;
+}
+
+extern "C" void* init_libkdiff3part();
+KLibFactory* KLibLoader::factory(QString const&)
+{
+ return (KLibFactory*) init_libkdiff3part();
+}
+
+QObject* KLibFactory::create(QObject* pParent, const QString& name, const QString& classname )
+{
+ KParts::Factory* f = dynamic_cast<KParts::Factory*>(this);
+ if (f!=0)
+ return f->createPartObject( (QWidget*)pParent, name.ascii(),
+ pParent, name.ascii(),
+ classname.ascii(), QStringList() );
+ else
+ return 0;
+}
+
+
+
+
+#include "kreplacements.moc"
diff --git a/src/kreplacements/kreplacements.h b/src/kreplacements/kreplacements.h
new file mode 100644
index 0000000..1402d51
--- /dev/null
+++ b/src/kreplacements/kreplacements.h
@@ -0,0 +1,505 @@
+/***************************************************************************
+ kreplacements.h - description
+ -------------------
+ begin : Sat Aug 3 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 KREPLACEMENTS_H
+#define KREPLACEMENTS_H
+
+#include "common.h"
+
+#include <qobject.h>
+#include <qtabdialog.h>
+#include <qmainwindow.h>
+#include <qaction.h>
+#include <qfiledialog.h>
+#include <qapplication.h>
+#include <qvbox.h>
+#include <qpushbutton.h>
+#include <qstatusbar.h>
+#include <qtoolbar.h>
+#include <qprogressbar.h>
+#include <qpopupmenu.h>
+#include <qstringlist.h>
+#include <qprinter.h>
+
+#include <map>
+#include <list>
+
+QString getTranslationDir();
+
+class KMainWindow;
+
+class KURL
+{
+public:
+ KURL(){}
+ KURL(const QString& s){ m_s = s; }
+ static KURL fromPathOrURL( const QString& s ){ return KURL(s); }
+ QString url() const { return m_s; }
+ bool isEmpty() const { return m_s.isEmpty(); }
+ QString prettyURL() const { return m_s; }
+ bool isLocalFile() const { return true; }
+ bool isValid() const { return true; }
+ QString path() const { return m_s; }
+ void setPath( const QString& s ){ m_s=s; }
+ QString fileName() const { return m_s; } // not really needed
+ void addPath( const QString& s ){ m_s += "/" + s; }
+private:
+ QString m_s;
+};
+
+class KMessageBox
+{
+public:
+ static void error( QWidget* parent, const QString& text, const QString& caption=QString() );
+ static int warningContinueCancel( QWidget* parent, const QString& text, const QString& caption=QString(),
+ const QString& button1=QString("Continue") );
+ static void sorry( QWidget* parent, const QString& text, const QString& caption=QString() );
+ static void information( QWidget* parent, const QString& text, const QString& caption=QString() );
+ static int warningYesNo( QWidget* parent, const QString& text, const QString& caption,
+ const QString& button1, const QString& button2 );
+ static int warningYesNoCancel(
+ QWidget* parent, const QString& text, const QString& caption,
+ const QString& button1, const QString& button2 );
+
+ enum {Cancel=-1, No=0, Yes=1, Continue=1};
+};
+
+#define i18n(x) QObject::tr(x)
+#define I18N_NOOP(x) x
+#define RESTORE(x)
+#define _UNLOAD(x)
+
+typedef QPopupMenu KPopupMenu;
+
+class KDialogBase : public QTabDialog
+{
+ Q_OBJECT
+public:
+ KDialogBase( int, const QString& caption, int, int, QWidget* parent, const char* name,
+ bool /*modal*/, bool );
+ ~KDialogBase();
+
+ void incInitialSize ( const QSize& );
+ void setHelp(const QString& helpfilename, const QString& );
+ enum {IconList, Help, Default, Apply, Ok, Cancel };
+
+ int BarIcon(const QString& iconName, int );
+
+ QVBox* addVBoxPage( const QString& name, const QString& info, int );
+ QFrame* addPage( const QString& name, const QString& info, int );
+ int spacingHint();
+
+ virtual void accept();
+signals:
+ void applyClicked();
+
+protected slots:
+ virtual void slotOk( void );
+ virtual void slotApply( void );
+ virtual void slotHelp( void );
+ virtual void slotCancel( void );
+ virtual void slotDefault( void );
+};
+
+class KFileDialog : public QFileDialog
+{
+public:
+ static KURL getSaveURL( const QString &startDir=QString::null,
+ const QString &filter=QString::null,
+ QWidget *parent=0, const QString &caption=QString::null);
+ static KURL getOpenURL( const QString & startDir = QString::null,
+ const QString & filter = QString::null,
+ QWidget * parent = 0,
+ const QString & caption = QString::null );
+ static KURL getExistingURL( const QString & startDir = QString::null,
+ QWidget * parent = 0,
+ const QString & caption = QString::null );
+ static QString getSaveFileName (const QString &startDir=QString::null,
+ const QString &filter=QString::null,
+ QWidget *parent=0,
+ const QString &caption=QString::null);
+};
+
+typedef QStatusBar KStatusBar;
+
+class KToolBar : public QToolBar
+{
+public:
+ KToolBar(QMainWindow* parent);
+
+ enum BarPosition {Top, Bottom, Left, Right};
+ BarPosition barPos();
+ void setBarPos(BarPosition);
+private:
+ QMainWindow* m_pMainWindow;
+};
+
+class KActionCollection
+{
+public:
+ KMainWindow* m_pMainWindow;
+ KActionCollection( KMainWindow* p){ m_pMainWindow=p; }
+};
+
+class KKeyDialog
+{
+public:
+ static void configure(void*, QWidget*){}
+ static void configureKeys(KActionCollection*, const QString&){}
+ static void configure(KActionCollection*, const QString&){}
+};
+
+namespace KParts
+{
+ class ReadWritePart;
+}
+
+class KMainWindow : public QMainWindow
+{
+ Q_OBJECT
+private:
+ KStatusBar m_statusBar;
+ KActionCollection m_actionCollection;
+protected:
+ virtual bool queryClose() = 0;
+ virtual bool queryExit() = 0;
+public:
+ QPopupMenu* fileMenu;
+ QPopupMenu* editMenu;
+ QPopupMenu* directoryMenu;
+ QPopupMenu* dirCurrentItemMenu;
+ QPopupMenu* dirCurrentSyncItemMenu;
+ QPopupMenu* movementMenu;
+ QPopupMenu* mergeMenu;
+ QPopupMenu* diffMenu;
+ QPopupMenu* windowsMenu;
+ QPopupMenu* settingsMenu;
+ QPopupMenu* helpMenu;
+
+ KToolBar* m_pToolBar;
+
+ KMainWindow( QWidget* parent, const char* name );
+ KToolBar* toolBar(const QString& s = QString::null);
+ KActionCollection* actionCollection();
+ void createGUI();
+ void createGUI(KParts::ReadWritePart*){createGUI();}
+
+ QList<KMainWindow>* memberList;
+public slots:
+ void slotHelp();
+ void slotAbout();
+};
+
+class KConfig : public ValueMap
+{
+ QString m_fileName;
+public:
+ KConfig();
+ ~KConfig();
+ void readConfigFile(const QString& configFileName);
+
+ void setGroup(const QString&);
+};
+
+class KAction : public QAction
+{
+ Q_OBJECT
+public:
+ KAction(const QString& text, const QIconSet& icon, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bToggle=false, bool bMenu=true);
+ KAction(const QString& text, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bToggle=false, bool bMenu=true);
+ void init(QObject* receiver, const char* slot, KActionCollection* actionCollection,
+ const char* name, bool bToggle, bool bMenu);
+ void setStatusText(const QString&);
+ void plug(QPopupMenu*);
+};
+
+class KToggleAction : public KAction
+{
+public:
+ KToggleAction(const QString& text, const QIconSet& icon, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bMenu=true);
+ KToggleAction(const QString& text, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bMenu=true);
+ KToggleAction(const QString& text, const QIconSet& icon, int accel, KActionCollection* actionCollection, const char* name, bool bMenu=true);
+ void setChecked(bool);
+ bool isChecked();
+};
+
+
+class KStdAction
+{
+public:
+ static KAction* open( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* save( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* saveAs( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* print( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* quit( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* cut( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* copy( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* paste( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* selectAll( QWidget* parent, const char* slot, KActionCollection* );
+ static KToggleAction* showToolbar( QWidget* parent, const char* slot, KActionCollection* );
+ static KToggleAction* showStatusbar( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* preferences( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* about( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* aboutQt( KActionCollection* );
+ static KAction* help( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* find( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* findNext( QWidget* parent, const char* slot, KActionCollection* );
+ static KAction* keyBindings( QWidget* parent, const char* slot, KActionCollection* );
+};
+
+class KIcon
+{
+public:
+ enum {SizeMedium,Small};
+};
+
+class KFontChooser : public QWidget
+{
+ Q_OBJECT
+ QFont m_font;
+ QPushButton* m_pSelectFont;
+ QLabel* m_pLabel;
+ QWidget* m_pParent;
+public:
+ KFontChooser( QWidget* pParent, const QString& name, bool, const QStringList&, bool, int );
+ QFont font();
+ void setFont( const QFont&, bool );
+private slots:
+ void slotSelectFont();
+};
+
+class KColorButton : public QPushButton
+{
+ Q_OBJECT
+ QColor m_color;
+public:
+ KColorButton(QWidget* parent);
+ QColor color();
+ void setColor(const QColor&);
+ virtual void paintEvent(QPaintEvent* e);
+public slots:
+ void slotClicked();
+};
+
+class KPrinter : public QPrinter
+{
+public:
+ KPrinter();
+ enum e_PageSelection {ApplicationSide};
+ QValueList<int> pageList();
+ void setCurrentPage(int);
+ void setPageSelection(e_PageSelection);
+};
+
+class KStandardDirs
+{
+public:
+ QString findResource(const QString& resource, const QString& appName);
+};
+
+struct KCmdLineOptions
+{
+ const char* name;
+ const char* description;
+ int def;
+};
+
+#define KCmdLineLastOption {0,0,0}
+
+class KAboutData
+{
+public:
+ KAboutData( const QString& name, const QString& appName, const QString& version,
+ const QString& description, int licence,
+ const QString& copyright, int w, const QString& homepage, const QString& email);
+ KAboutData( const QString& name, const QString& appName, const QString& version );
+ void addAuthor(const char* name=0, const char* task=0, const char* email=0, const char* weblink=0);
+ void addCredit(const char* name=0, const char* task=0, const char* email=0, const char* weblink=0);
+ enum { License_GPL };
+
+ struct AboutDataEntry
+ {
+ AboutDataEntry(const QString& name, const QString& task, const QString& email, const QString& weblink)
+ : m_name(name), m_task(task), m_email(email), m_weblink(weblink)
+ {}
+ QString m_name;
+ QString m_task;
+ QString m_email;
+ QString m_weblink;
+ };
+
+ std::list<AboutDataEntry> m_authorList;
+ std::list<AboutDataEntry> m_creditList;
+};
+
+typedef QValueList<QCString> QCStringList;
+
+class KCmdLineArgs
+{
+public:
+ static KCmdLineArgs* parsedArgs();
+ static void init( int argc, char**argv, KAboutData* );
+ static void addCmdLineOptions( KCmdLineOptions* options ); // Add our own options.
+
+ int count();
+ QString arg(int);
+ KURL url(int i){ return KURL(arg(i)); }
+ void clear();
+ QString getOption(const QString&);
+ QCStringList getOptionList( const QString& );
+ bool isSet(const QString&);
+};
+
+class KIconLoader
+{
+public:
+ QPixmap loadIcon(const QString& name, int);
+};
+
+class KApplication : public QApplication
+{
+ KConfig m_config;
+ KIconLoader m_iconLoader;
+public:
+ KApplication();
+ static KApplication* kApplication();
+ KIconLoader* iconLoader();
+ KConfig* config();
+ bool isRestored();
+};
+
+extern KApplication* kapp;
+
+class KLibFactory : public QObject
+{
+ Q_OBJECT
+public:
+ QObject* create(QObject*,const QString&,const QString&);
+};
+
+class KLibLoader
+{
+public:
+ static KLibLoader* self();
+ KLibFactory* factory(const QString&);
+};
+
+class KEditToolbar : public QDialog
+{
+public:
+ KEditToolbar( int ){}
+};
+
+class KGlobal
+{
+public:
+ static KConfig* config() { return 0; }
+};
+
+namespace KIO
+{
+ enum UDSEntry {};
+ typedef QValueList<UDSEntry> UDSEntryList;
+ class Job : public QObject
+ {
+ public:
+ void kill(bool){}
+ bool error() {return false;}
+ void showErrorDialog( QWidget* ) {}
+ };
+ class SimpleJob : public Job {};
+ SimpleJob* mkdir( KURL );
+ SimpleJob* rmdir( KURL );
+ SimpleJob* file_delete( KURL, bool );
+ class FileCopyJob : public Job {};
+ FileCopyJob* file_move( KURL, KURL, int, bool, bool, bool );
+ FileCopyJob* file_copy( KURL, KURL, int, bool, bool, bool );
+ class CopyJob : public Job {};
+ CopyJob* link( KURL, KURL, bool );
+ class ListJob : public Job {};
+ ListJob* listRecursive( KURL, bool, bool );
+ ListJob* listDir( KURL, bool, bool );
+ class StatJob : public Job {
+ public: UDSEntry statResult(){ return (UDSEntry)0; }
+ };
+ StatJob* stat( KURL, bool, int, bool );
+ class TransferJob : public Job {};
+ TransferJob* get( KURL, bool, bool );
+ TransferJob* put( KURL, int, bool, bool, bool );
+};
+
+typedef QProgressBar KProgress;
+
+class KInstance : public QObject
+{
+public:
+ KInstance(KAboutData*){}
+};
+
+namespace KParts
+{
+ class MainWindow : public KMainWindow
+ {
+ public:
+ MainWindow( QWidget* parent, const char* name ) : KMainWindow(parent,name) {}
+ void setXMLFile(const QString&){}
+ void setAutoSaveSettings(){}
+ void saveMainWindowSettings(KConfig*){}
+ void applyMainWindowSettings(KConfig*){}
+ int factory(){return 0;}
+ };
+
+ class Part : public QObject
+ {
+ public:
+ KActionCollection* actionCollection();
+ KApplication* instance();
+ void setWidget( QWidget* w ){ m_pWidget=w; }
+ QWidget* widget(){return m_pWidget;}
+ void setXMLFile(const QString&){}
+ private:
+ QWidget* m_pWidget;
+ };
+
+ class ReadOnlyPart : public Part
+ {
+ public:
+ ReadOnlyPart(){}
+ ReadOnlyPart(QObject*,const QCString&){}
+ void setInstance( KInstance* ){}
+ QString m_file;
+ };
+
+ class ReadWritePart : public ReadOnlyPart
+ {
+ public:
+ ReadWritePart(QObject*,const QCString&){}
+ void setReadWrite(bool){}
+ };
+
+ class Factory : public KLibFactory
+ {
+ Q_OBJECT
+ public:
+ virtual KParts::Part* createPartObject( QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name,
+ const char *classname, const QStringList &args )=0;
+ };
+};
+#endif
+
+
diff --git a/src/kreplacements/kstandarddirs.h b/src/kreplacements/kstandarddirs.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kstandarddirs.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kstatusbar.h b/src/kreplacements/kstatusbar.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kstatusbar.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kstdaction.h b/src/kreplacements/kstdaction.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kstdaction.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/ktempfile.h b/src/kreplacements/ktempfile.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/ktempfile.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kunload.h b/src/kreplacements/kunload.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kunload.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kurl.h b/src/kreplacements/kurl.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kurl.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/kreplacements/kurldrag.h b/src/kreplacements/kurldrag.h
new file mode 100644
index 0000000..53443ee
--- /dev/null
+++ b/src/kreplacements/kurldrag.h
@@ -0,0 +1,2 @@
+#include "kreplacements.h"
+
diff --git a/src/lo16-app-kdiff3.png b/src/lo16-app-kdiff3.png
new file mode 100644
index 0000000..50e3397
--- /dev/null
+++ b/src/lo16-app-kdiff3.png
Binary files differ
diff --git a/src/lo32-app-kdiff3.png b/src/lo32-app-kdiff3.png
new file mode 100644
index 0000000..cd269b2
--- /dev/null
+++ b/src/lo32-app-kdiff3.png
Binary files differ
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..6e19b58
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,236 @@
+/***************************************************************************
+ main.cpp - Where everything starts.
+ -------------------
+ begin : Don Jul 11 12:31:29 CEST 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 <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include "kdiff3_shell.h"
+#include <kstandarddirs.h>
+#include "version.h"
+#include <qtextcodec.h>
+#include <qfile.h>
+#include <qtextstream.h>
+#include <vector>
+
+#ifdef KREPLACEMENTS_H
+#include "optiondialog.h"
+#endif
+#include "common.h"
+
+static const char *description =
+ I18N_NOOP("Tool for Comparison and Merge of Files and Directories");
+
+static KCmdLineOptions options[] =
+{
+ { "m", 0, 0 },
+ { "merge", I18N_NOOP("Merge the input."), 0 },
+ { "b", 0, 0 },
+ { "base file", I18N_NOOP("Explicit base file. For compatibility with certain tools."), 0 },
+ { "o", 0, 0 },
+ { "output file", I18N_NOOP("Output file. Implies -m. E.g.: -o newfile.txt"), 0 },
+ { "out file", I18N_NOOP("Output file, again. (For compatibility with certain tools.)"), 0 },
+ { "auto", I18N_NOOP("No GUI if all conflicts are auto-solvable. (Needs -o file)"), 0 },
+ { "qall", I18N_NOOP("Don't solve conflicts automatically. (For compatibility...)"), 0 },
+ { "L1 alias1", I18N_NOOP("Visible name replacement for input file 1 (base)."), 0 },
+ { "L2 alias2", I18N_NOOP("Visible name replacement for input file 2."), 0 },
+ { "L3 alias3", I18N_NOOP("Visible name replacement for input file 3."), 0 },
+ { "L", 0, 0 },
+ { "fname alias", I18N_NOOP("Alternative visible name replacement. Supply this once for every input."), 0 },
+ { "cs string", I18N_NOOP("Override a config setting. Use once for every setting. E.g.: --cs \"AutoAdvance=1\""), 0 },
+ { "confighelp", I18N_NOOP("Show list of config settings and current values."), 0 },
+ { "config file", I18N_NOOP("Use a different config file."), 0 }
+};
+static KCmdLineOptions options2[] =
+{
+ { "+[File1]", I18N_NOOP("file1 to open (base, if not specified via --base)"), 0 },
+ { "+[File2]", I18N_NOOP("file2 to open"), 0 },
+ { "+[File3]", I18N_NOOP("file3 to open"), 0 }
+};
+
+
+void initialiseCmdLineArgs(std::vector<KCmdLineOptions>& vOptions, QStringList& ignorableOptions)
+{
+ vOptions.insert( vOptions.end(), options, (KCmdLineOptions*)((char*)options+sizeof(options)));
+ QString configFileName = KStandardDirs().findResource("config","kdiff3rc");
+ QFile configFile( configFileName );
+ if ( configFile.open( IO_ReadOnly ) )
+ {
+ QTextStream ts( &configFile );
+ while(!ts.atEnd())
+ {
+ QString line = ts.readLine();
+ if ( line.startsWith("IgnorableCmdLineOptions=") )
+ {
+ int pos = line.find('=');
+ if (pos>=0)
+ {
+ QString s = line.mid(pos+1);
+ QStringList sl = QStringList::split( '|', s );
+ if (!sl.isEmpty())
+ {
+ ignorableOptions = QStringList::split( ';', sl.front() );
+ for (QStringList::iterator i=ignorableOptions.begin(); i!=ignorableOptions.end(); ++i)
+ {
+ KCmdLineOptions ignoreOption;
+ (*i).remove('-');
+ if (!(*i).isEmpty())
+ {
+ ignoreOption.name = (*i).latin1();
+ ignoreOption.description = I18N_NOOP("Ignored. (User defined.)");
+ ignoreOption.def = 0;
+ vOptions.push_back(ignoreOption);
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ vOptions.insert(vOptions.end(),options2,(KCmdLineOptions*)((char*)options2+sizeof(options2)));
+
+ KCmdLineOptions last = KCmdLineLastOption;
+ vOptions.push_back(last);
+ KCmdLineArgs::addCmdLineOptions( &vOptions[0] ); // Add our own options.
+}
+
+
+#ifdef _WIN32
+#include <process.h>
+// This command checks the comm
+static bool isOptionUsed(const QString& s, int argc, char* argv[])
+{
+ for(int j=0; j<argc; ++j )
+ {
+ if( "-"+s == argv[j] || "--"+s==argv[j] )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+
+
+int main(int argc, char *argv[])
+{
+#ifdef _WIN32
+ /* KDiff3 can be used as replacement for the text-diff and merge tool provided by
+ Clearcase. This is experimental and so far has only been tested under Windows.
+
+ There are two ways to use KDiff3 with clearcase
+ - The file lib/mgrs/map contains the list of compare/merge tasks on one side and
+ the tool on the other. Originally this contains only clearcase tools, but you can
+ edit this file and put kdiff3 there instead. (Recommended method)
+ - Exchange the original program with KDiff3: (Hackish, no fine control)
+ 1. In the Clearcase "bin"-directory rename "cleardiffmrg.exe" to "cleardiffmrg_orig.exe".
+ 2. Copy kdiff3.exe into that "bin"-directory and rename it to "cleardiffmrg.exe".
+ (Also copy the other files that are needed by KDiff3 there.)
+ Now when a file comparison or merge is done by Clearcase then of course KDiff3 will be
+ run instead.
+ If the commandline contains the option "-directory" then KDiff3 can't do it but will
+ run "cleardiffmrg_orig.exe" instead.
+ */
+
+ // Write all args into a temporary file. Uncomment this for debugging purposes.
+ /*
+ FILE* f = fopen("c:\\t.txt","w");
+ for(int i=0; i< argc; ++i)
+ fprintf(f,"Arg %d: %s\n", i, argv[i]);
+
+ // Call orig cleardiffmrg.exe to see what result it returns.
+ int result=0;
+ result = ::_spawnvp(_P_WAIT , "C:\\Programme\\Rational\\ClearCase\\bin\\cleardiffmrg.exe", argv );
+ fprintf(f,"Result: %d\n", result );
+ fclose(f);
+ return result;
+ */
+
+ // KDiff3 can replace cleardiffmrg from clearcase. But not all functions.
+ if ( isOptionUsed( "directory", argc,argv ) )
+ {
+ return ::_spawnvp(_P_WAIT , "cleardiffmrg_orig", argv );
+ }
+
+#endif
+ //QApplication::setColorSpec( QApplication::ManyColor ); // Grab all 216 colors
+
+ KAboutData aboutData( "kdiff3", I18N_NOOP("KDiff3"),
+ VERSION, description, KAboutData::License_GPL,
+ "(c) 2002-2007 Joachim Eibl", 0, "http://kdiff3.sourceforge.net/", "joachim.eibl" "@" "gmx.de");
+ aboutData.addAuthor("Joachim Eibl",0, "joachim.eibl" "@" "gmx.de");
+ aboutData.addCredit("Eike Sauer", "Bugfixes, Debian package maintainer" );
+ aboutData.addCredit("Sebastien Fricker", "Windows installer" );
+ aboutData.addCredit("Stephan Binner", "i18n-help", "binner" "@" "kde.org" );
+ aboutData.addCredit("Stefan Partheymueller", "Clipboard-patch" );
+ aboutData.addCredit("David Faure", "KIO-Help", "faure" "@" "kde.org" );
+ aboutData.addCredit("Bernd Gehrmann", "Class CvsIgnoreList from Cervisia" );
+ aboutData.addCredit("Andre Woebbeking", "Class StringMatcher" );
+ aboutData.addCredit("Michael Denio", "Directory Equality-Coloring patch");
+ aboutData.addCredit("Manfred Koehler", "Fix for slow startup on Windows");
+ aboutData.addCredit("Sergey Zorin", "Diff Ext for Windows");
+ aboutData.addCredit("Paul Eggert, Mike Haertel, David Hayes, Richard Stallman, Len Tower", "GNU-Diffutils");
+ aboutData.addCredit("Tino Boellsterling, Timothy Mee", "Intensive test, use and feedback");
+ aboutData.addCredit("Michael Schmidt", "Mac support");
+
+ aboutData.addCredit(I18N_NOOP("+ Many thanks to those who reported bugs and contributed ideas!"));
+
+ KCmdLineArgs::init( argc, argv, &aboutData );
+ std::vector<KCmdLineOptions> vOptions;
+ QStringList ignorableOptions;
+ initialiseCmdLineArgs(vOptions, ignorableOptions);
+
+ KApplication app;
+
+#ifdef KREPLACEMENTS_H
+ QString locale;
+
+ locale = app.config()->readEntry("Language", "Auto");
+ int spacePos = locale.find(' ');
+ if (spacePos>0) locale = locale.left(spacePos);
+ QTranslator kdiff3Translator( 0 );
+ QTranslator qtTranslator( 0 );
+ if (locale != "en_orig")
+ {
+ if ( locale == "Auto" || locale.isEmpty() )
+ locale = QTextCodec::locale();
+
+ QString translationDir = getTranslationDir();
+ kdiff3Translator.load( QString("kdiff3_")+locale, translationDir );
+ app.installTranslator( &kdiff3Translator );
+
+ qtTranslator.load( QString("qt_")+locale, translationDir );
+ app.installTranslator( &qtTranslator );
+ }
+#endif
+
+ if (app.isRestored())
+ {
+ RESTORE(KDiff3Shell);
+ }
+ else
+ {
+ new KDiff3Shell();
+ }
+
+ int retVal = app.exec();
+ return retVal;
+}
+
+// Suppress warning with --enable-final
+#undef VERSION
diff --git a/src/merger.cpp b/src/merger.cpp
new file mode 100644
index 0000000..371548c
--- /dev/null
+++ b/src/merger.cpp
@@ -0,0 +1,87 @@
+/***************************************************************************
+ merger.cpp - description
+ -------------------
+ begin : Sun Mar 24 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 "merger.h"
+#include <assert.h>
+#include <iostream>
+#include <iomanip>
+
+Merger::Merger( const DiffList* pDiffListAB, const DiffList* pDiffListCA )
+: md1( pDiffListAB, 0 ), md2( pDiffListCA, 1 )
+{
+}
+
+
+Merger::MergeData::MergeData( const DiffList* p, int i )
+: d(0,0,0)
+{
+ idx=i;
+ pDiffList = p;
+ if ( p!=0 )
+ {
+ it=p->begin();
+ update();
+ }
+}
+
+bool Merger::MergeData::eq()
+{
+ return pDiffList==0 || d.nofEquals > 0;
+}
+
+bool Merger::MergeData::isEnd()
+{
+ return ( pDiffList==0 || ( 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!=0 && 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
index 0000000..3712a2f
--- /dev/null
+++ b/src/merger.h
@@ -0,0 +1,61 @@
+/***************************************************************************
+ merger.h - description
+ -------------------
+ begin : Sun Mar 24 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 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
index 0000000..0e0aad9
--- /dev/null
+++ b/src/mergeresultwindow.cpp
@@ -0,0 +1,3222 @@
+/***************************************************************************
+ 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 "optiondialog.h"
+
+#include <qpainter.h>
+#include <qapplication.h>
+#include <qclipboard.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qcursor.h>
+#include <qpopupmenu.h>
+#include <qstatusbar.h>
+#include <qregexp.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qcombobox.h>
+#include <qlayout.h>
+#include <qtextcodec.h>
+#include <qdragobject.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <iostream>
+
+int g_bAutoSolve = true;
+
+#undef leftInfoWidth
+#define leftInfoWidth 3
+
+MergeResultWindow::MergeResultWindow(
+ QWidget* pParent,
+ OptionDialog* pOptionDialog,
+ QStatusBar* pStatusBar
+ )
+: QWidget( pParent, 0, WRepaintNoErase )
+{
+ setFocusPolicy( QWidget::ClickFocus );
+
+ m_firstLine = 0;
+ m_firstColumn = 0;
+ m_nofColumns = 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 = 0;
+ m_pldB = 0;
+ m_pldC = 0;
+ m_sizeA = 0;
+ m_sizeB = 0;
+ m_sizeC = 0;
+
+ m_pDiff3LineList = 0;
+ m_pTotalDiffStatus = 0;
+ m_pStatusBar = pStatusBar;
+
+ m_pOptionDialog = pOptionDialog;
+ m_bPaintingAllowed = false;
+ m_delayedDrawTimer = 0;
+
+ m_cursorXPos=0;
+ m_cursorOldXPos=0;
+ m_cursorYPos=0;
+ m_bCursorOn = true;
+ m_bCursorUpdate = false;
+ connect( &m_cursorTimer, SIGNAL(timeout()), this, SLOT( slotCursorUpdate() ) );
+ m_cursorTimer.start( 500 /*ms*/, true /*single shot*/ );
+ m_selection.reset();
+
+ setMinimumSize( QSize(20,20) );
+ setFont( m_pOptionDialog->m_font );
+}
+
+void MergeResultWindow::init(
+ const LineData* pLineDataA, int sizeA,
+ const LineData* pLineDataB, int sizeB,
+ const LineData* pLineDataC, int sizeC,
+ const Diff3LineList* pDiff3LineList,
+ TotalDiffStatus* pTotalDiffStatus
+ )
+{
+ m_firstLine = 0;
+ m_firstColumn = 0;
+ m_nofColumns = 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_cursorOldXPos=0;
+ m_cursorYPos=0;
+
+ merge( g_bAutoSolve, -1 );
+ g_bAutoSolve = true;
+ update();
+ updateSourceMask();
+
+ int wsc;
+ int nofUnsolved = getNrOfUnsolvedConflicts(&wsc);
+ if (m_pStatusBar)
+ m_pStatusBar->message( i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)")
+ .arg(nofUnsolved).arg(wsc) );
+}
+
+void MergeResultWindow::reset()
+{
+ m_pDiff3LineList = 0;
+ m_pTotalDiffStatus = 0;
+ m_pldA = 0;
+ m_pldB = 0;
+ m_pldC = 0;
+}
+
+// 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 == 0 )
+ {
+ 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 == 0 && d.pFineBC == 0 && d.pFineCA == 0)
+ {
+ mergeDetails = eNoChange; src = A;
+ }
+ else if( d.pFineAB == 0 && d.pFineBC != 0 && d.pFineCA != 0 )
+ {
+ mergeDetails = eCChanged; src = C;
+ }
+ else if( d.pFineAB != 0 && d.pFineBC != 0 && d.pFineCA == 0 )
+ {
+ mergeDetails = eBChanged; src = B;
+ }
+ else if( d.pFineAB != 0 && d.pFineBC == 0 && d.pFineCA != 0 )
+ {
+ mergeDetails = eBCChangedAndEqual; src = C;
+ }
+ else if( d.pFineAB != 0 && d.pFineBC != 0 && d.pFineCA != 0 )
+ {
+ mergeDetails = eBCChanged; bConflict = true;
+ }
+ else
+ assert(false);
+ }
+ else if ( d.lineA!=-1 && d.lineB!=-1 && d.lineC==-1 )
+ {
+ if( d.pFineAB != 0 )
+ {
+ 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 != 0 )
+ {
+ 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 != 0 )
+ {
+ 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
+ assert(false);
+}
+
+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.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"), i18n("C&ontinue"), i18n("&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==0 );
+
+ // Automatic solving for only whitespace changes.
+ if ( ml.bConflict &&
+ ( m_pldC==0 && (d.bAEqB || d.bWhiteLineA && d.bWhiteLineB) ||
+ m_pldC!=0 && (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() ? 0 : &m_mergeLineList.back();
+
+ bool bSame = back!=0 && sameKindCheck( ml, *back );
+ if( bSame )
+ {
+ ++back->srcRangeLength;
+ if ( back->bWhiteSpaceConflict && !ml.bWhiteSpaceConflict )
+ back->bWhiteSpaceConflict = false;
+ }
+ else
+ {
+ if (back!=0 && back->bWhiteSpaceConflict )
+ {
+ if ( m_pldC==0 && m_pOptionDialog->m_whiteSpace2FileMergeDefault != 0 ) // Only two inputs
+ {
+ back->srcSelect = m_pOptionDialog->m_whiteSpace2FileMergeDefault;
+ back->bConflict = false;
+ }
+ else if ( m_pldC!=0 && m_pOptionDialog->m_whiteSpace3FileMergeDefault != 0 )
+ {
+ back->srcSelect = m_pOptionDialog->m_whiteSpace3FileMergeDefault;
+ back->bConflict = false;
+ }
+ }
+ 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==0 || ! back->bConflict || !bSame )
+ {
+ MergeLine& tmpBack = m_mergeLineList.back();
+ MergeEditLine mel(ml.id3l);
+ mel.setConflict();
+ tmpBack.mergeEditLineList.push_back(mel);
+ }
+ }
+ }
+
+ if ( !bAutoSolve )
+ {
+ // 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 );
+
+ int 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.
+
+ int oldSrcLine = -1;
+ int oldSrc = -1;
+ MergeEditLineList::iterator melIt;
+ for( melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); )
+ {
+ MergeEditLine& mel = *melIt;
+ int melsrc = mel.src();
+
+ int 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_pOptionDialog->m_bRunHistoryAutoMergeOnMergeStart )
+ slotMergeHistory();
+ if ( m_pOptionDialog->m_bRunRegExpAutoMergeOnMergeStart )
+ slotRegExpAutoMerge();
+ if ( m_pldC != 0 && ! 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->nofUnsolvedConflicts = nrOfUnsolvedConflicts;
+ m_pTotalDiffStatus->nofSolvedConflicts = nrOfSolvedConflicts;
+ m_pTotalDiffStatus->nofWhitespaceConflicts = nrOfWhiteSpaceConflicts;
+
+
+ m_cursorXPos=0;
+ m_cursorOldXPos=0;
+ m_cursorYPos=0;
+ //m_firstLine = 0; // Must not set line/column without scrolling there
+ //m_firstColumn = 0;
+
+ setModified(false);
+
+ m_currentMergeLineIt = m_mergeLineList.begin();
+ slotGoTop();
+
+ updateAvailabilities();
+ update();
+}
+
+void MergeResultWindow::setFirstLine(int firstLine)
+{
+ m_firstLine = max2(0,firstLine);
+ update();
+}
+
+void MergeResultWindow::setFirstColumn(int firstCol)
+{
+ m_firstColumn = max2(0,firstCol);
+ update();
+}
+
+int MergeResultWindow::getNofColumns()
+{
+ return m_nofColumns;
+}
+
+int MergeResultWindow::getNofLines()
+{
+ return m_totalSize;
+}
+
+int MergeResultWindow::getNofVisibleColumns()
+{
+ QFontMetrics fm = fontMetrics();
+ return width()/fm.width('W')-4;
+}
+
+int MergeResultWindow::getNofVisibleLines()
+{
+ QFontMetrics fm = fontMetrics();
+ return (height()-3)/fm.height()-2;
+}
+
+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 )
+{
+ assert( eDir==eUp || eDir==eDown );
+ MergeLineList::iterator i = m_currentMergeLineIt;
+ bool bSkipWhiteConflicts = ! m_pOptionDialog->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 == false || 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 == false || 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_pOptionDialog->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_pOptionDialog->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_pOptionDialog->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_pOptionDialog->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!=0)
+ *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!=0 )
+ ++ *pNrOfWhiteSpaceConflicts;
+ }
+ }
+
+ return nrOfUnsolvedConflicts;
+}
+
+void MergeResultWindow::showNrOfConflicts()
+{
+ 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").arg("A").arg("B");
+ else if ( m_pTotalDiffStatus->bTextAEqB ) totalInfo += i18n("Files %1 and %2 have equal text.\n").arg("A").arg("B");
+ if ( m_pTotalDiffStatus->bBinaryAEqC ) totalInfo += i18n("Files %1 and %2 are binary equal.\n").arg("A").arg("C");
+ else if ( m_pTotalDiffStatus->bTextAEqC ) totalInfo += i18n("Files %1 and %2 have equal text.\n").arg("A").arg("C");
+ if ( m_pTotalDiffStatus->bBinaryBEqC ) totalInfo += i18n("Files %1 and %2 are binary equal.\n").arg("B").arg("C");
+ else if ( m_pTotalDiffStatus->bTextBEqC ) totalInfo += i18n("Files %1 and %2 have equal text.\n").arg("B").arg("C");
+ }
+
+ int nrOfUnsolvedConflicts = getNrOfUnsolvedConflicts();
+
+ KMessageBox::information( this,
+ i18n("Total number of conflicts: ") + QString::number(nrOfConflicts) +
+ i18n("\nNr of automatically solved conflicts: ") + QString::number(nrOfConflicts-nrOfUnsolvedConflicts) +
+ i18n("\nNr of unsolved conflicts: ") + QString::number(nrOfUnsolvedConflicts) +
+ "\n"+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 )
+ {
+ scroll( 0, newFirstLine - m_firstLine );
+ }
+
+ if ( m_selection.isEmpty() )
+ {
+ m_cursorXPos = 0;
+ m_cursorOldXPos = 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;
+
+ int 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;
+ }
+
+ update();
+ updateSourceMask();
+ emit updateAvailabilities();
+ int wsc;
+ int nofUnsolved = getNrOfUnsolvedConflicts(&wsc);
+ m_pStatusBar->message( i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)")
+ .arg(nofUnsolved).arg(wsc) );
+}
+
+// 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();
+ int wsc;
+ int nofUnsolved = getNrOfUnsolvedConflicts(&wsc);
+ m_pStatusBar->message( i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)")
+ .arg(nofUnsolved).arg(wsc) );
+}
+
+void MergeResultWindow::slotAutoSolve()
+{
+ resetSelection();
+ merge( true, -1 );
+ setModified( true );
+ update();
+ int wsc;
+ int nofUnsolved = getNrOfUnsolvedConflicts(&wsc);
+ m_pStatusBar->message( i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)")
+ .arg(nofUnsolved).arg(wsc) );
+}
+
+void MergeResultWindow::slotUnsolve()
+{
+ resetSelection();
+ merge( false, -1 );
+ setModified( true );
+ update();
+ int wsc;
+ int nofUnsolved = getNrOfUnsolvedConflicts(&wsc);
+ m_pStatusBar->message( i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)")
+ .arg(nofUnsolved).arg(wsc) );
+}
+
+static QString calcHistoryLead(const QString& s )
+{
+ // Return the start of the line until the first white char after the first non white char.
+ unsigned 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 ""; // 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.isNull() || historyLead == calcHistoryLead(sA) ) &&
+ (sB.isNull() || historyLead == calcHistoryLead(sB) ) &&
+ (!bThreeFiles || sC.isNull() || 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 = QStringList::split(',', keyOrder );
+ 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.find('|')<0 || groupRegExp.find('(')>=0 )
+ {
+ bool 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 = QStringList::split( '|', groupRegExp );
+ int idx = sl.findIndex( 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_pOptionDialog->m_historyStartRegExp;
+ ++id3l; // Skip line with "$Log ... $"
+ QRegExp newHistoryEntry = m_pOptionDialog->m_historyEntryStartRegExp;
+ QStringList parenthesesGroups;
+ findParenthesesGroups( m_pOptionDialog->m_historyEntryStartRegExp, parenthesesGroups );
+ QString key;
+ MergeEditLineList melList;
+ bool bPrevLineIsEmpty = true;
+ bool bUseRegExp = !m_pOptionDialog->m_historyEntryStartRegExp.isEmpty();
+ for(; id3l != iHistoryEnd; ++id3l )
+ {
+ const LineData* pld = id3l->getLineData(src);
+ if ( !pld ) continue;
+ QString s( pld->pLine, pld->size );
+ if (historyLead.isNull()) historyLead = calcHistoryLead(s);
+ QString sLine = s.mid(historyLead.length());
+ if ( ( !bUseRegExp && !sLine.stripWhiteSpace().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_pOptionDialog->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.stripWhiteSpace().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( m_pOptionDialog->m_historyStartRegExp, m_pldC!=0, 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==0)
+ {
+ 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_pOptionDialog->m_bHistoryMergeSorting && ! m_pOptionDialog->m_historyEntryStartSortKeyOrder.isEmpty() &&
+ ! m_pOptionDialog->m_historyEntryStartRegExp.isEmpty();
+
+ if ( m_pOptionDialog->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!=0, iD3LHistoryEnd ) )
+ historyMap.erase(hMapIt);
+ else
+ break;
+ }
+ }
+ else
+ {
+ while ( ! hitList.empty() )
+ {
+ HistoryMap::iterator hMapIt = hitList.back();
+ if( hMapIt->second.staysInPlace( m_pldC!=0, 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 == 0 ? 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_pOptionDialog->m_maxNofHistoryEntries )
+ break;
+ ++historyCount;
+ HistoryMapEntry& hme = hmit->second;
+ MergeEditLineList& mell = hme.choice(m_pldC!=0);
+ 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_pOptionDialog->m_maxNofHistoryEntries )
+ break;
+ ++historyCount;
+ HistoryMapEntry& hme = (*hlit)->second;
+ MergeEditLineList& mell = hme.choice(m_pldC!=0);
+ if (!mell.empty())
+ iMLLStart->mergeEditLineList.splice( iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end() );
+ }
+ }
+ setFastSelector( iMLLStart );
+ update();
+ }
+}
+
+void MergeResultWindow::slotRegExpAutoMerge()
+{
+ if ( m_pOptionDialog->m_autoMergeRegExp.isEmpty() )
+ return;
+
+ QRegExp vcsKeywords = m_pOptionDialog->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==0 || vcsKeywords.exactMatch( id3l->getString(C) )))
+ {
+ MergeEditLine& mel = *i->mergeEditLineList.begin();
+ mel.setSource( m_pldC==0 ? 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==0 || 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)
+{
+ 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.lastLine + m_scrollDeltaY, m_selection.lastPos + m_scrollDeltaX );
+ emit scroll( 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 = 0;
+ 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 ];
+
+ if ( pld == 0 )
+ {
+ // assert(false); This is no error.
+ return QString();
+ }
+
+ return QString( pld->pLine, pld->size );
+ }
+ else
+ {
+ return m_str;
+ }
+ return 0;
+}
+
+/// Converts the cursor-posOnScreen into a text index, considering tabulators.
+int convertToPosInText( const QString& s, int posOnScreen, int tabSize )
+{
+ 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 )
+{
+ 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;
+}
+
+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.height();
+ int fontWidth = fm.width("W");
+ int fontAscent = fm.ascent();
+
+ int topLineYOffset = 0;
+ int xOffset = fontWidth * leftInfoWidth;
+
+ int yOffset = ( line-m_firstLine ) * fontHeight;
+ if ( yOffset < 0 || yOffset > height() )
+ return;
+
+ yOffset += topLineYOffset;
+
+ QString srcName = " ";
+ if ( bUserModified ) srcName = "m";
+ else if ( srcSelect == A && mergeDetails != eNoChange ) srcName = "A";
+ else if ( srcSelect == B ) srcName = "B";
+ else if ( srcSelect == C ) srcName = "C";
+
+ if ( rangeMark & 4 )
+ {
+ p.fillRect( xOffset, yOffset, width(), fontHeight, m_pOptionDialog->m_currentRangeBgColor );
+ }
+
+ if( (srcSelect > 0 || bUserModified ) && !bLineRemoved )
+ {
+ 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_pOptionDialog->m_tabSize );
+ for( int j=0; j<spaces; ++j )
+ s+=' ';
+ }
+ else
+ {
+ s+=str[i];
+ }
+ outPos += spaces;
+ }
+
+ if ( m_selection.lineWithin( line ) )
+ {
+ int firstPosInLine = convertToPosOnScreen( str, convertToPosInText( str, m_selection.firstPosInLine(line), m_pOptionDialog->m_tabSize ),m_pOptionDialog->m_tabSize );
+ int lastPosInLine = convertToPosOnScreen( str, convertToPosInText( str, m_selection.lastPosInLine(line), m_pOptionDialog->m_tabSize ), m_pOptionDialog->m_tabSize );
+ int lengthInLine = max2(0,lastPosInLine - firstPosInLine);
+ if (lengthInLine>0) m_selection.bSelectionContainsData = true;
+
+ if ( lengthInLine < int(s.length()) )
+ { // Draw a normal line first
+ p.setPen( m_pOptionDialog->m_fgColor );
+ p.drawText( xOffset, yOffset+fontAscent, s.mid(m_firstColumn), true );
+ }
+ int firstPosInLine2 = max2( firstPosInLine, m_firstColumn );
+ int lengthInLine2 = max2(0,lastPosInLine - firstPosInLine2);
+
+ if( m_selection.lineWithin( line+1 ) )
+ p.fillRect( xOffset + fontWidth*(firstPosInLine2-m_firstColumn), yOffset,
+ width(), fontHeight, colorGroup().highlight() );
+ else if ( lengthInLine2>0 )
+ p.fillRect( xOffset + fontWidth*(firstPosInLine2-m_firstColumn), yOffset,
+ fontWidth*lengthInLine2, fontHeight, colorGroup().highlight() );
+
+ p.setPen( colorGroup().highlightedText() );
+ p.drawText( xOffset + fontWidth*(firstPosInLine2-m_firstColumn), yOffset+fontAscent,
+ s.mid(firstPosInLine2,lengthInLine2), true );
+ }
+ else
+ {
+ p.setPen( m_pOptionDialog->m_fgColor );
+ p.drawText( xOffset, yOffset+fontAscent, s.mid(m_firstColumn), true );
+ }
+
+ p.setPen( m_pOptionDialog->m_fgColor );
+ if ( m_cursorYPos==line )
+ {
+ m_cursorXPos = minMaxLimiter( m_cursorXPos, 0, outPos );
+ m_cursorXPos = convertToPosOnScreen( str, convertToPosInText( str, m_cursorXPos, m_pOptionDialog->m_tabSize ),m_pOptionDialog->m_tabSize );
+ }
+
+ p.drawText( 1, yOffset+fontAscent, srcName, true );
+ }
+ else if ( bLineRemoved )
+ {
+ p.setPen( m_pOptionDialog->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_pOptionDialog->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 assert(false);
+
+ xOffset -= fontWidth;
+ p.setPen( m_pOptionDialog->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_pOptionDialog->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();
+ }
+}
+
+void MergeResultWindow::paintEvent( QPaintEvent* )
+{
+ if (m_pDiff3LineList==0 || !m_bPaintingAllowed) return;
+
+ bool bOldSelectionContainsData = m_selection.bSelectionContainsData;
+ const QFontMetrics& fm = fontMetrics();
+ int fontHeight = fm.height();
+ int fontWidth = fm.width("W");
+ int fontAscent = fm.ascent();
+
+ if ( !m_bCursorUpdate ) // Don't redraw everything for blinking cursor?
+ {
+ m_selection.bSelectionContainsData = false;
+ if ( size() != m_pixmap.size() )
+ m_pixmap.resize(size());
+
+ MyPainter p(&m_pixmap, m_pOptionDialog->m_bRightToLeftLanguage, width(), fontWidth);
+ p.setFont( font() );
+ p.QPainter::fillRect( rect(), m_pOptionDialog->m_bgColor );
+
+ //int visibleLines = height() / fontHeight;
+
+ int lastVisibleLine = m_firstLine + getNofVisibleLines() + 5;
+ int nofColumns = 0;
+ 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 );
+ if ( convertToPosOnScreen(s,s.length(),m_pOptionDialog->m_tabSize) >nofColumns)
+ nofColumns = s.length();
+
+ writeLine( p, line, s, mel.src(), ml.mergeDetails, rangeMark,
+ mel.isModified(), mel.isRemoved(), ml.bWhiteSpaceConflict );
+ }
+ ++line;
+ }
+ }
+ }
+
+ if ( line != m_nofLines || nofColumns != m_nofColumns )
+ {
+ m_nofLines = line;
+ assert( m_nofLines == m_totalSize );
+
+ m_nofColumns = nofColumns;
+ emit resizeSignal();
+ }
+
+ p.end();
+ }
+
+ QPainter painter(this);
+
+ int topLineYOffset = 0;
+ int xOffset = fontWidth * leftInfoWidth;
+ int yOffset = ( m_cursorYPos - m_firstLine ) * fontHeight + topLineYOffset;
+ int xCursor = ( m_cursorXPos - m_firstColumn ) * fontWidth + xOffset;
+
+ if ( !m_bCursorUpdate )
+ painter.drawPixmap(0,0, m_pixmap);
+ else
+ {
+ if (!m_pOptionDialog->m_bRightToLeftLanguage)
+ painter.drawPixmap(xCursor-2, yOffset, m_pixmap,
+ xCursor-2, yOffset, 5, fontAscent+2 );
+ else
+ painter.drawPixmap(width()-1-4-(xCursor-2), yOffset, m_pixmap,
+ width()-1-4-(xCursor-2), yOffset, 5, fontAscent+2 );
+ m_bCursorUpdate = false;
+ }
+ painter.end();
+
+ if ( m_bCursorOn && hasFocus() && m_cursorYPos>=m_firstLine )
+ {
+ MyPainter painter(this, m_pOptionDialog->m_bRightToLeftLanguage, width(), fontWidth);
+ int topLineYOffset = 0;
+ int xOffset = fontWidth * leftInfoWidth;
+
+ int yOffset = ( m_cursorYPos-m_firstLine ) * fontHeight + topLineYOffset;
+
+ int xCursor = ( m_cursorXPos - m_firstColumn ) * fontWidth + xOffset;
+
+ painter.setPen( m_pOptionDialog->m_fgColor );
+
+ painter.drawLine( xCursor, yOffset, xCursor, yOffset+fontAscent );
+ painter.drawLine( xCursor-2, yOffset, xCursor+2, yOffset );
+ painter.drawLine( xCursor-2, yOffset+fontAscent+1, xCursor+2, yOffset+fontAscent+1 );
+ }
+
+ if( !bOldSelectionContainsData && m_selection.bSelectionContainsData )
+ emit newSelection();
+}
+
+void MergeResultWindow::updateSourceMask()
+{
+ int srcMask=0;
+ int enabledMask = 0;
+ if( !hasFocus() || m_pDiff3LineList==0 || !m_bPaintingAllowed || m_currentMergeLineIt == m_mergeLineList.end() )
+ {
+ srcMask = 0;
+ enabledMask = 0;
+ }
+ else
+ {
+ enabledMask = m_pldC==0 ? 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);
+}
+
+void MergeResultWindow::convertToLinePos( int x, int y, int& line, int& pos )
+{
+ const QFontMetrics& fm = fontMetrics();
+ int fontHeight = fm.height();
+ int fontWidth = fm.width('W');
+ int xOffset = (leftInfoWidth-m_firstColumn)*fontWidth;
+ int topLineYOffset = 0;
+
+ int yOffset = topLineYOffset - m_firstLine * fontHeight;
+
+ line = min2( ( y - yOffset ) / fontHeight, m_totalSize-1 );
+ if ( ! m_pOptionDialog->m_bRightToLeftLanguage )
+ pos = ( x - xOffset ) / fontWidth;
+ else
+ pos = ( (width() - 1 - x) - xOffset ) / fontWidth;
+}
+
+void MergeResultWindow::mousePressEvent ( QMouseEvent* e )
+{
+ m_bCursorOn = true;
+
+ int line;
+ int pos;
+ convertToLinePos( e->x(), e->y(), line, pos );
+
+ bool bLMB = e->button() == Qt::LeftButton;
+ bool bMMB = e->button() == Qt::MidButton;
+ bool bRMB = e->button() == Qt::RightButton;
+
+ if ( bLMB && pos < m_firstColumn || bRMB ) // Fast range selection
+ {
+ m_cursorXPos = 0;
+ m_cursorOldXPos = 0;
+ m_cursorYPos = max2(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)
+ {
+ showPopupMenu( QCursor::pos() );
+ }
+ }
+ else if ( bLMB ) // Normal cursor placement
+ {
+ pos = max2(pos,0);
+ line = max2(line,0);
+ if ( e->state() & Qt::ShiftButton )
+ {
+ if (m_selection.firstLine==-1)
+ 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_cursorOldXPos = pos;
+ m_cursorYPos = line;
+
+ update();
+ //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar );
+ }
+ else if ( bMMB ) // Paste clipboard
+ {
+ pos = max2(pos,0);
+ line = max2(line,0);
+
+ m_selection.reset();
+ m_cursorXPos = pos;
+ m_cursorOldXPos = pos;
+ m_cursorYPos = line;
+
+ pasteClipboard( true );
+ }
+}
+
+void MergeResultWindow::mouseDoubleClickEvent( QMouseEvent* e )
+{
+ if ( e->button() == Qt::LeftButton )
+ {
+ int line;
+ int pos;
+ convertToLinePos( e->x(), e->y(), line, pos );
+ m_cursorXPos = pos;
+ m_cursorOldXPos = pos;
+ m_cursorYPos = line;
+
+ // Get the string data of the current line
+
+ MergeLineList::iterator mlIt;
+ MergeEditLineList::iterator melIt;
+ calcIteratorFromLineNr( line, mlIt, melIt );
+ QString s = melIt->getString( this );
+
+ if ( !s.isEmpty() )
+ {
+ int pos1, pos2;
+
+ calcTokenPos( s, pos, pos1, pos2, m_pOptionDialog->m_tabSize );
+
+ resetSelection();
+ m_selection.start( line, convertToPosOnScreen( s, pos1, m_pOptionDialog->m_tabSize ) );
+ m_selection.end( line, convertToPosOnScreen( s, pos2, m_pOptionDialog->m_tabSize ) );
+
+ update();
+ // emit selectionEnd() happens in the mouseReleaseEvent.
+ }
+ }
+}
+
+void MergeResultWindow::mouseReleaseEvent ( QMouseEvent * e )
+{
+ if ( e->button() == Qt::LeftButton )
+ {
+ killTimer(m_delayedDrawTimer);
+ m_delayedDrawTimer = 0;
+
+ if (m_selection.firstLine != -1 )
+ {
+ emit selectionEnd();
+ }
+ }
+}
+
+void MergeResultWindow::mouseMoveEvent ( QMouseEvent * e )
+{
+ int line;
+ int pos;
+ convertToLinePos( e->x(), e->y(), line, pos );
+ m_cursorXPos = pos;
+ m_cursorOldXPos = pos;
+ m_cursorYPos = line;
+ if (m_selection.firstLine != -1 )
+ {
+ 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('W');
+ int topLineYOffset = 0;
+ int deltaX=0;
+ int deltaY=0;
+ if ( ! m_pOptionDialog->m_bRightToLeftLanguage )
+ {
+ if ( e->x() < leftInfoWidth*fontWidth ) deltaX=-1;
+ if ( e->x() > width() ) deltaX=+1;
+ }
+ else
+ {
+ if ( e->x() > width()-1-leftInfoWidth*fontWidth ) 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 scroll( deltaX, deltaY );
+ }
+ }
+}
+
+
+void MergeResultWindow::slotCursorUpdate()
+{
+ m_cursorTimer.stop();
+ m_bCursorOn = !m_bCursorOn;
+
+ if ( isVisible() )
+ {
+ m_bCursorUpdate = true;
+
+ const QFontMetrics& fm = fontMetrics();
+ int fontWidth = fm.width("W");
+ int topLineYOffset = 0;
+ int xOffset = fontWidth * leftInfoWidth;
+ int yOffset = ( m_cursorYPos - m_firstLine ) * fm.height() + topLineYOffset;
+ int xCursor = ( m_cursorXPos - m_firstColumn ) * fontWidth + xOffset;
+
+ if (!m_pOptionDialog->m_bRightToLeftLanguage)
+ repaint( xCursor-2, yOffset, 5, fm.ascent()+2 );
+ else
+ repaint( width()-1-4-(xCursor-2), yOffset, 5, fm.ascent()+2 );
+
+ m_bCursorUpdate=false;
+ }
+
+ m_cursorTimer.start(500,true);
+}
+
+
+void MergeResultWindow::wheelEvent( QWheelEvent* e )
+{
+ int d = -e->delta()*QApplication::wheelScrollLines()/120;
+ e->accept();
+ scroll( 0, min2(d, getNofVisibleLines()) );
+}
+
+
+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_pOptionDialog->m_tabSize );
+
+ bool bCtrl = ( e->state() & Qt::ControlButton ) != 0 ;
+ bool bShift = ( e->state() & Qt::ShiftButton ) != 0 ;
+ #ifdef _WIN32
+ bool bAlt = ( e->state() & Qt::AltButton ) != 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.mid( 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.mid( 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_pOptionDialog->m_bAutoIndentation )
+ { // calc last indentation
+ MergeLineList::iterator mlIt1 = mlIt;
+ MergeEditLineList::iterator melIt1 = melIt;
+ for(;;) {
+ const QString s = melIt1->getString(this);
+ if ( !s.isEmpty() ) {
+ unsigned 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_pOptionDialog->m_bRightToLeftLanguage ) // operator^: XOR
+ {
+ if ( !bCtrl )
+ {
+ --x;
+ if(x<0 && y>0){--y; x=INT_MAX;}
+ }
+ else
+ {
+ while( x>0 && (str[x-1]==' ' || str[x-1]=='\t') ) --x;
+ while( x>0 && (str[x-1]!=' ' && str[x-1]!='\t') ) --x;
+ }
+ }
+ else
+ {
+ if ( !bCtrl )
+ {
+ ++x; if(x>(int)str.length() && y<m_totalSize-1){ ++y; x=0; }
+ }
+
+ else
+ {
+ while( x<(int)str.length() && (str[x]==' ' || str[x]=='\t') ) ++x;
+ while( x<(int)str.length() && (str[x]!=' ' && str[x]!='\t') ) ++x;
+ }
+ }
+ 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_pOptionDialog->m_bReplaceTabs )
+ {
+ int spaces = (m_cursorXPos / m_pOptionDialog->m_tabSize + 1)*m_pOptionDialog->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 newFirstColumn = m_firstColumn;
+
+ if ( y<m_firstLine )
+ newFirstLine = y;
+ else if ( y > m_firstLine + getNofVisibleLines() )
+ newFirstLine = y - getNofVisibleLines();
+
+ if (bYMoveKey)
+ x=convertToPosInText( str, m_cursorOldXPos, m_pOptionDialog->m_tabSize );
+
+ int xOnScreen = convertToPosOnScreen( str, x, m_pOptionDialog->m_tabSize );
+ if ( xOnScreen<m_firstColumn )
+ newFirstColumn = xOnScreen;
+ else if ( xOnScreen > m_firstColumn + getNofVisibleColumns() )
+ newFirstColumn = xOnScreen - getNofVisibleColumns();
+
+ if ( bShift )
+ {
+ if (m_selection.firstLine==-1)
+ m_selection.start( m_cursorYPos, m_cursorXPos );
+
+ m_selection.end( y, xOnScreen );
+ }
+ else
+ m_selection.reset();
+
+ m_cursorYPos = y;
+ m_cursorXPos = xOnScreen;
+ if ( ! bYMoveKey )
+ m_cursorOldXPos = m_cursorXPos;
+
+ m_bCursorOn = false;
+
+ if ( newFirstLine!=m_firstLine || newFirstColumn!=m_firstColumn )
+ {
+ m_bCursorOn = true;
+ scroll( newFirstColumn-m_firstColumn, newFirstLine-m_firstLine );
+ return;
+ }
+
+ m_bCursorOn = true;
+ update();
+}
+
+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;
+ }
+ }
+ }
+ assert(false);
+}
+
+
+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( unsigned int i=0; i<str.length(); ++i )
+ {
+ int spaces = 1;
+ if ( str[i]=='\t' )
+ {
+ spaces = tabber( outPos, m_pOptionDialog->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 _WIN32
+ 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.firstLine!=-1 && m_selection.bSelectionContainsData )
+ {
+ deleteSelection();
+ y = m_cursorYPos;
+ calcIteratorFromLineNr( y, mlIt, melIt );
+ s = melIt->getString( this );
+ x = convertToPosInText( s, m_cursorXPos, m_pOptionDialog->m_tabSize );
+ return true;
+ }
+ return false;
+}
+
+void MergeResultWindow::deleteSelection()
+{
+ if ( m_selection.firstLine==-1 || !m_selection.bSelectionContainsData )
+ {
+ return;
+ }
+ 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_pOptionDialog->m_tabSize );
+ firstLineString = lineString.left( pos );
+ }
+
+ if ( line==lastLine )
+ {
+ // This is the last line in the selection
+ int pos = convertToPosInText( lineString, lastPosInLine, m_pOptionDialog->m_tabSize );
+ firstLineString += lineString.mid( pos ); // rest of line
+ melItFirst->setString( firstLineString );
+ }
+
+ if ( line!=firstLine )
+ {
+ // 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_cursorOldXPos = m_cursorXPos;
+
+ m_selection.reset();
+}
+
+void MergeResultWindow::pasteClipboard( bool bFromSelection )
+{
+ if (m_selection.firstLine != -1 )
+ 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_pOptionDialog->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_pOptionDialog->m_tabSize );
+ m_cursorOldXPos = m_cursorXPos;
+
+ 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 )
+{
+ // Are still conflicts somewhere?
+ if ( getNrOfUnsolvedConflicts()>0 )
+ {
+ KMessageBox::error( this,
+ i18n("Not all conflicts are solved yet.\n"
+ "File not saved.\n"),
+ i18n("Conflicts Left"));
+ return false;
+ }
+
+ update();
+
+ FileAccess file( fileName, true /*bWantToWrite*/ );
+ if ( m_pOptionDialog->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, IO_WriteOnly);
+ 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 ( m_pOptionDialog->m_lineEndStyle == eLineEndDos )
+ { str.prepend("\r\n"); }
+ else
+ { str.prepend("\n"); }
+ }
+
+ textOutStream << str;
+ ++line;
+ }
+ }
+ }
+ 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.find( s, startPos, bCaseSensitive );
+ 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_pOptionDialog->m_tabSize ) );
+ m_selection.end( lastLine, convertToPosOnScreen( getString(lastLine), endPos, m_pOptionDialog->m_tabSize ) );
+ update();
+}
+
+Overview::Overview( QWidget* pParent, OptionDialog* pOptions )
+: QWidget( pParent, 0, WRepaintNoErase )
+{
+ m_pDiff3LineList = 0;
+ m_pOptions = pOptions;
+ m_bTripleDiff = false;
+ m_eOverviewMode = eOMNormal;
+ m_nofLines = 1;
+ m_bPaintingAllowed = false;
+ setFixedWidth(20);
+}
+
+void Overview::init( Diff3LineList* pDiff3LineList, bool bTripleDiff )
+{
+ m_pDiff3LineList = pDiff3LineList;
+ m_bTripleDiff = bTripleDiff;
+ m_pixmap.resize( QSize(0,0) ); // make sure that a redraw happens
+ update();
+}
+
+void Overview::reset()
+{
+ m_pDiff3LineList = 0;
+}
+
+void Overview::slotRedraw()
+{
+ m_pixmap.resize( 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 / max2(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: assert(false); 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;
+ }
+ }
+
+ if (!bWhiteSpaceChange || m_pOptions->m_bShowWhiteSpace )
+ {
+ // Make sure that lines with conflict are not overwritten.
+ if ( c == m_pOptions->m_colorForConflict )
+ {
+ p.fillRect(x+1, oldY, w, max2(1,y-oldY), bWhiteSpaceChange ? QBrush(c,Qt::Dense4Pattern) : QBrush(c) );
+ oldConflictY = oldY;
+ }
+ else if ( c!=m_pOptions->m_bgColor && oldY>oldConflictY )
+ {
+ p.fillRect(x+1, oldY, w, max2(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==0 || !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.resize( 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(OptionDialog* pOptionDialog, QWidget* pParent)
+:QWidget(pParent)
+{
+ m_pOptionDialog = pOptionDialog;
+ //setAutoFillBackground(true);
+
+ QHBoxLayout* pHLayout = new QHBoxLayout(this);
+ pHLayout->setMargin(2);
+ pHLayout->setSpacing(2);
+
+ m_pLabel = new QLabel(i18n("Output")+":", this);
+ pHLayout->addWidget( m_pLabel );
+
+ m_pFileNameLineEdit = new QLineEdit(this);
+ 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, SIGNAL(clicked()), this, SLOT(slotBrowseButtonClicked()));
+
+ m_pModifiedLabel = new QLabel(i18n("[Modified]"),this);
+ pHLayout->addWidget( m_pModifiedLabel );
+ m_pModifiedLabel->setMinimumSize( m_pModifiedLabel->sizeHint() );
+ m_pModifiedLabel->setText("");
+
+ pHLayout->addStretch(1);
+
+ m_pEncodingLabel = new QLabel(i18n("Encoding for saving")+":",this);
+ pHLayout->addWidget( m_pEncodingLabel );
+
+ m_pEncodingSelector = new QComboBox(this);
+ pHLayout->addWidget( m_pEncodingSelector, 3 );
+ setEncodings(0,0,0);
+}
+
+void WindowTitleWidget::setFileName( const QString& fileName )
+{
+ m_pFileNameLineEdit->setText( QDir::convertSeparators(fileName) );
+}
+
+QString WindowTitleWidget::getFileName()
+{
+ return m_pFileNameLineEdit->text();
+}
+
+void WindowTitleWidget::setEncodings( QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC )
+{
+ m_pEncodingSelector->clear();
+ m_codecMap.clear();
+
+ // First sort codec names:
+ std::map<QString, QTextCodec*> names;
+ int i;
+ for(i=0;;++i)
+ {
+ QTextCodec* c = QTextCodec::codecForIndex(i);
+ if ( c==0 ) break;
+ else names[QString(c->name())]=c;
+ }
+
+ i=0;
+ if ( pCodecForA )
+ {
+ m_pEncodingSelector->insertItem( i18n("Codec from") + " A: " + pCodecForA->name(), i );
+ m_codecMap[i]=pCodecForA;
+ ++i;
+ }
+ if ( pCodecForB )
+ {
+ m_pEncodingSelector->insertItem( i18n("Codec from") + " B: " + pCodecForB->name(), i );
+ m_codecMap[i]=pCodecForB;
+ ++i;
+ }
+ if ( pCodecForC )
+ {
+ m_pEncodingSelector->insertItem( i18n("Codec from") + " C: " + pCodecForC->name(), i );
+ m_codecMap[i]=pCodecForC;
+ ++i;
+ }
+
+ std::map<QString, QTextCodec*>::iterator it;
+ for(it=names.begin();it!=names.end();++it)
+ {
+ m_pEncodingSelector->insertItem( it->first, i );
+ m_codecMap[i]=it->second;
+ ++i;
+ }
+ m_pEncodingSelector->setMinimumSize( m_pEncodingSelector->sizeHint() );
+
+ if ( pCodecForC && pCodecForB && pCodecForA )
+ {
+ if ( pCodecForA == pCodecForB )
+ m_pEncodingSelector->setCurrentItem( 2 ); // C
+ else if ( pCodecForA == pCodecForC )
+ m_pEncodingSelector->setCurrentItem( 1 ); // B
+ else
+ m_pEncodingSelector->setCurrentItem( 2 ); // C
+ }
+ else if ( pCodecForA && pCodecForB )
+ m_pEncodingSelector->setCurrentItem( 1 ); // B
+ else
+ m_pEncodingSelector->setCurrentItem( 0 );
+}
+
+QTextCodec* WindowTitleWidget::getEncoding()
+{
+ return m_codecMap[ m_pEncodingSelector->currentItem() ];
+}
+
+void WindowTitleWidget::setEncoding(QTextCodec* pEncoding)
+{
+ m_pEncodingSelector->setCurrentText( QString( pEncoding->name() ) );
+}
+
+//void WindowTitleWidget::slotBrowseButtonClicked()
+//{
+// QString current = m_pFileNameLineEdit->text();
+//
+// KURL 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_pOptionDialog->m_fgColor;
+ QColor c2 = Qt::lightGray;
+ if ( e->type()==QEvent::FocusOut )
+ c2 = m_pOptionDialog->m_bgColor;
+
+ p.setColor(QColorGroup::Background, c2);
+ setPalette( p );
+
+ p.setColor(QColorGroup::Foreground, 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 ( QUriDrag::canDecode( d ) )
+ {
+ QStringList lst;
+ QUriDrag::decodeLocalFiles( d, lst );
+
+ if ( lst.count() > 0 )
+ {
+ static_cast<QLineEdit*>(o)->setText( lst[0] );
+ 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
index 0000000..fdc4b5c
--- /dev/null
+++ b/src/mergeresultwindow.h
@@ -0,0 +1,454 @@
+/***************************************************************************
+ mergeresultwindow.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 MERGERESULTWINDOW_H
+#define MERGERESULTWINDOW_H
+
+#include "diff.h"
+
+#include <qwidget.h>
+#include <qpixmap.h>
+#include <qtimer.h>
+
+class QPainter;
+
+class Overview : public QWidget
+{
+ Q_OBJECT
+public:
+ Overview( QWidget* pParent, OptionDialog* 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 slots:
+ void setFirstLine(int firstLine);
+ void slotRedraw();
+signals:
+ void setLine(int);
+private:
+ const Diff3LineList* m_pDiff3LineList;
+ OptionDialog* m_pOptions;
+ bool m_bTripleDiff;
+ int m_firstLine;
+ int m_pageHeight;
+ QPixmap m_pixmap;
+ bool m_bPaintingAllowed;
+ e_OverviewMode m_eOverviewMode;
+ int m_nofLines;
+
+ virtual void paintEvent( QPaintEvent* e );
+ virtual void mousePressEvent( QMouseEvent* e );
+ virtual void mouseMoveEvent( QMouseEvent* e );
+ 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,
+ OptionDialog* pOptionDialog,
+ QStatusBar* pStatusBar
+ );
+
+ void init(
+ const LineData* pLineDataA, int sizeA,
+ const LineData* pLineDataB, int sizeB,
+ const LineData* pLineDataC, int sizeC,
+ const Diff3LineList* pDiff3LineList,
+ TotalDiffStatus* pTotalDiffStatus
+ );
+
+ void reset();
+
+ bool saveDocument( const QString& fileName, QTextCodec* pEncoding );
+ int getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts=0);
+ void choose(int selector);
+ void chooseGlobal(int selector, bool bConflictsOnly, bool bWhiteSpaceOnly );
+
+ int getNofColumns();
+ int getNofLines();
+ int getNofVisibleColumns();
+ 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 slots:
+ void setFirstLine(int firstLine);
+ void setFirstColumn(int firstCol);
+
+ 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();
+
+signals:
+ void scroll( 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 );
+
+ OptionDialog* m_pOptionDialog;
+
+ const LineData* m_pldA;
+ const LineData* m_pldB;
+ const LineData* m_pldC;
+ int m_sizeA;
+ int m_sizeB;
+ int m_sizeC;
+
+ const Diff3LineList* m_pDiff3LineList;
+ TotalDiffStatus* m_pTotalDiffStatus;
+
+ bool m_bPaintingAllowed;
+ int m_delayedDrawTimer;
+ Overview::e_OverviewMode m_eOverviewMode;
+
+private:
+ class MergeEditLine
+ {
+ public:
+ MergeEditLine(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.isNull(); }
+ 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.isNull() || (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=0; }
+ 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); }
+ 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 = 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(0); // Force size-recalc after splice, because splice doesn't handle size-tracking
+ setTotalSizePtr(0);
+ BASE::splice( destPos, srcList, srcFirst, srcLast );
+ srcList.setTotalSizePtr( pTotalSize );
+ setTotalSizePtr( pTotalSize );
+ }
+
+ void setTotalSizePtr(int* pTotalSize)
+ {
+ if ( pTotalSize==0 && m_pTotalSize!=0 ) { *m_pTotalSize -= size(); }
+ else if ( pTotalSize!=0 && m_pTotalSize==0 ) { *pTotalSize += size(); }
+ m_pTotalSize = pTotalSize;
+ }
+ int* getTotalSizePtr()
+ {
+ return m_pTotalSize;
+ }
+
+ private:
+ void ds(int deltaSize)
+ {
+ m_size+=deltaSize;
+ if (m_pTotalSize!=0) *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 );
+
+ virtual void paintEvent( QPaintEvent* e );
+
+
+ void myUpdate(int afterMilliSecs);
+ virtual void timerEvent(QTimerEvent*);
+ 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);
+ void convertToLinePos( int x, int y, int& line, int& pos );
+ virtual void mousePressEvent ( QMouseEvent* e );
+ virtual void mouseDoubleClickEvent ( QMouseEvent* e );
+ virtual void mouseReleaseEvent ( QMouseEvent * );
+ virtual void mouseMoveEvent ( QMouseEvent * );
+ virtual void resizeEvent( QResizeEvent* e );
+ virtual void keyPressEvent( QKeyEvent* e );
+ virtual void wheelEvent( QWheelEvent* e );
+ virtual void focusInEvent( QFocusEvent* e );
+ virtual bool focusNextPrevChild(bool){return false;}
+
+ QPixmap m_pixmap;
+ int m_firstLine;
+ int m_firstColumn;
+ int m_nofColumns;
+ int m_nofLines;
+ int m_totalSize; //Same as m_nofLines, but calculated differently
+ 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_cursorYPos;
+ int m_cursorOldXPos;
+ 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 slots:
+ void deleteSelection();
+ void pasteClipboard(bool bFromSelection);
+private 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_pEncodingLabel;
+ QComboBox* m_pEncodingSelector;
+ OptionDialog* m_pOptionDialog;
+ std::map<int, QTextCodec*> m_codecMap;
+public:
+ WindowTitleWidget(OptionDialog* pOptionDialog, QWidget* pParent );
+ QTextCodec* getEncoding();
+ void setFileName(const QString& fileName );
+ QString getFileName();
+ void setEncodings( QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC );
+ void setEncoding( QTextCodec* pCodec );
+
+ bool eventFilter( QObject* o, QEvent* e );
+public slots:
+ void slotSetModified( bool bModified );
+//private slots:
+// void slotBrowseButtonClicked();
+
+};
+
+#endif
+
diff --git a/src/optiondialog.cpp b/src/optiondialog.cpp
new file mode 100644
index 0000000..698763e
--- /dev/null
+++ b/src/optiondialog.cpp
@@ -0,0 +1,1755 @@
+/*
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qfont.h>
+#include <qframe.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qvbox.h>
+#include <qvalidator.h>
+#include <qtooltip.h>
+#include <qtextcodec.h>
+#include <qradiobutton.h>
+#include <qvbuttongroup.h>
+#include <qsettings.h>
+
+#include <kapplication.h>
+#include <kcolorbtn.h>
+#include <kfontdialog.h> // For KFontChooser
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <kmainwindow.h> //For ktoolbar.h
+
+//#include <kkeydialog.h>
+#include <map>
+
+#include "optiondialog.h"
+#include "diff.h"
+#include "smalldialogs.h"
+
+#include <iostream>
+
+#ifndef KREPLACEMENTS_H
+#include <kglobalsettings.h>
+#endif
+
+static QString s_historyEntryStartRegExpToolTip;
+static QString s_historyEntryStartSortKeyOrderToolTip;
+static QString s_autoMergeRegExpToolTip;
+static QString s_historyStartRegExpToolTip;
+
+void OptionDialog::addOptionItem(OptionItem* p)
+{
+ m_optionItemList.push_back(p);
+}
+
+class OptionItem
+{
+public:
+ OptionItem( OptionDialog* pOptionDialog, const QString& saveName )
+ {
+ assert(pOptionDialog!=0);
+ pOptionDialog->addOptionItem( this );
+ m_saveName = saveName;
+ }
+ virtual ~OptionItem(){}
+ virtual void setToDefault()=0;
+ virtual void setToCurrent()=0;
+ virtual void apply()=0;
+ virtual void write(ValueMap*)=0;
+ virtual void read(ValueMap*)=0;
+ QString getSaveName(){return m_saveName;}
+protected:
+ QString m_saveName;
+};
+
+class OptionCheckBox : public QCheckBox, public OptionItem
+{
+public:
+ OptionCheckBox( QString text, bool bDefaultVal, const QString& saveName, bool* pbVar,
+ QWidget* pParent, OptionDialog* pOD )
+ : QCheckBox( text, pParent ), OptionItem( pOD, saveName )
+ {
+ m_pbVar = pbVar;
+ m_bDefaultVal = bDefaultVal;
+ }
+ void setToDefault(){ setChecked( m_bDefaultVal ); }
+ void setToCurrent(){ setChecked( *m_pbVar ); }
+ void apply() { *m_pbVar = isChecked(); }
+ void write(ValueMap* config){ config->writeEntry(m_saveName, *m_pbVar ); }
+ void read (ValueMap* config){ *m_pbVar = config->readBoolEntry( m_saveName, *m_pbVar ); }
+private:
+ OptionCheckBox( const OptionCheckBox& ); // private copy constructor without implementation
+ bool* m_pbVar;
+ bool m_bDefaultVal;
+};
+
+class OptionRadioButton : public QRadioButton, public OptionItem
+{
+public:
+ OptionRadioButton( QString text, bool bDefaultVal, const QString& saveName, bool* pbVar,
+ QWidget* pParent, OptionDialog* pOD )
+ : QRadioButton( text, pParent ), OptionItem( pOD, saveName )
+ {
+ m_pbVar = pbVar;
+ m_bDefaultVal = bDefaultVal;
+ }
+ void setToDefault(){ setChecked( m_bDefaultVal ); }
+ void setToCurrent(){ setChecked( *m_pbVar ); }
+ void apply() { *m_pbVar = isChecked(); }
+ void write(ValueMap* config){ config->writeEntry(m_saveName, *m_pbVar ); }
+ void read (ValueMap* config){ *m_pbVar = config->readBoolEntry( m_saveName, *m_pbVar ); }
+private:
+ OptionRadioButton( const OptionRadioButton& ); // private copy constructor without implementation
+ bool* m_pbVar;
+ bool m_bDefaultVal;
+};
+
+
+template<class T>
+class OptionT : public OptionItem
+{
+public:
+ OptionT( const T& defaultVal, const QString& saveName, T* pVar, OptionDialog* pOD )
+ : OptionItem( pOD, saveName )
+ {
+ m_pVar = pVar;
+ *m_pVar = defaultVal;
+ }
+ OptionT( const QString& saveName, T* pVar, OptionDialog* pOD )
+ : OptionItem( pOD, saveName )
+ {
+ m_pVar = pVar;
+ }
+ void setToDefault(){}
+ void setToCurrent(){}
+ void apply() {}
+ void write(ValueMap* vm){ writeEntry( vm, m_saveName, *m_pVar ); }
+ void read (ValueMap* vm){ readEntry ( vm, m_saveName, *m_pVar ); }
+private:
+ OptionT( const OptionT& ); // private copy constructor without implementation
+ T* m_pVar;
+};
+
+template <class T> void writeEntry(ValueMap* vm, const QString& saveName, const T& v ) { vm->writeEntry( saveName, v ); }
+static void writeEntry(ValueMap* vm, const QString& saveName, const QStringList& v ) { vm->writeEntry( saveName, v, '|' ); }
+
+static void readEntry(ValueMap* vm, const QString& saveName, bool& v ) { v = vm->readBoolEntry( saveName, v ); }
+static void readEntry(ValueMap* vm, const QString& saveName, int& v ) { v = vm->readNumEntry( saveName, v ); }
+static void readEntry(ValueMap* vm, const QString& saveName, QSize& v ) { v = vm->readSizeEntry( saveName, &v ); }
+static void readEntry(ValueMap* vm, const QString& saveName, QPoint& v ) { v = vm->readPointEntry( saveName, &v ); }
+static void readEntry(ValueMap* vm, const QString& saveName, QStringList& v ){ v = vm->readListEntry( saveName, QStringList(), '|' ); }
+
+typedef OptionT<bool> OptionToggleAction;
+typedef OptionT<int> OptionNum;
+typedef OptionT<QPoint> OptionPoint;
+typedef OptionT<QSize> OptionSize;
+typedef OptionT<QStringList> OptionStringList;
+
+class OptionFontChooser : public KFontChooser, public OptionItem
+{
+public:
+ OptionFontChooser( const QFont& defaultVal, const QString& saveName, QFont* pbVar, QWidget* pParent, OptionDialog* pOD )
+ :KFontChooser( pParent,"font",true/*onlyFixed*/,QStringList(),false,6 ),
+ OptionItem( pOD, saveName )
+ {
+ m_pbVar = pbVar;
+ *m_pbVar = defaultVal;
+ m_default = defaultVal;
+ }
+ void setToDefault(){ setFont( m_default, true /*only fixed*/ ); }
+ void setToCurrent(){ setFont( *m_pbVar, true /*only fixed*/ ); }
+ void apply() { *m_pbVar = font();}
+ void write(ValueMap* config){ config->writeEntry(m_saveName, *m_pbVar ); }
+ void read (ValueMap* config){ *m_pbVar = config->readFontEntry( m_saveName, m_pbVar ); }
+private:
+ OptionFontChooser( const OptionToggleAction& ); // private copy constructor without implementation
+ QFont* m_pbVar;
+ QFont m_default;
+};
+
+class OptionColorButton : public KColorButton, public OptionItem
+{
+public:
+ OptionColorButton( QColor defaultVal, const QString& saveName, QColor* pVar, QWidget* pParent, OptionDialog* pOD )
+ : KColorButton( pParent ), OptionItem( pOD, saveName )
+ {
+ m_pVar = pVar;
+ m_defaultVal = defaultVal;
+ }
+ void setToDefault(){ setColor( m_defaultVal ); }
+ void setToCurrent(){ setColor( *m_pVar ); }
+ void apply() { *m_pVar = color(); }
+ void write(ValueMap* config){ config->writeEntry(m_saveName, *m_pVar ); }
+ void read (ValueMap* config){ *m_pVar = config->readColorEntry( m_saveName, m_pVar ); }
+private:
+ OptionColorButton( const OptionColorButton& ); // private copy constructor without implementation
+ QColor* m_pVar;
+ QColor m_defaultVal;
+};
+
+class OptionLineEdit : public QComboBox, public OptionItem
+{
+public:
+ OptionLineEdit( const QString& defaultVal, const QString& saveName, QString* pVar,
+ QWidget* pParent, OptionDialog* pOD )
+ : QComboBox( pParent ), OptionItem( pOD, saveName )
+ {
+ setMinimumWidth(50);
+ setEditable(true);
+ m_pVar = pVar;
+ m_defaultVal = defaultVal;
+ m_list.push_back(defaultVal);
+ insertText();
+ }
+ void setToDefault(){ setCurrentText( m_defaultVal ); }
+ void setToCurrent(){ setCurrentText( *m_pVar ); }
+ void apply() { *m_pVar = currentText(); insertText(); }
+ void write(ValueMap* config){ config->writeEntry( m_saveName, m_list, '|' ); }
+ void read (ValueMap* config){
+ m_list = config->readListEntry( m_saveName, m_defaultVal, '|' );
+ if ( !m_list.empty() ) *m_pVar = m_list.front();
+ clear();
+ insertStringList(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.remove( current );
+ m_list.push_front( current );
+ clear();
+ if ( m_list.size()>10 )
+ m_list.erase( m_list.at(10),m_list.end() );
+ insertStringList(m_list);
+ }
+ OptionLineEdit( const OptionLineEdit& ); // private copy constructor without implementation
+ QString* m_pVar;
+ QString m_defaultVal;
+ QStringList m_list;
+};
+
+#if defined QT_NO_VALIDATOR
+#error No validator
+#endif
+class OptionIntEdit : public QLineEdit, public OptionItem
+{
+public:
+ OptionIntEdit( int defaultVal, const QString& saveName, int* pVar, int rangeMin, int rangeMax,
+ QWidget* pParent, OptionDialog* pOD )
+ : QLineEdit( pParent ), OptionItem( pOD, saveName )
+ {
+ m_pVar = pVar;
+ m_defaultVal = defaultVal;
+ QIntValidator* v = new QIntValidator(this);
+ v->setRange( rangeMin, rangeMax );
+ setValidator( v );
+ }
+ void setToDefault(){ QString s; s.setNum(m_defaultVal); setText( s ); }
+ void setToCurrent(){ QString s; s.setNum(*m_pVar); setText( s ); }
+ void apply() { const QIntValidator* v=static_cast<const QIntValidator*>(validator());
+ *m_pVar = minMaxLimiter( text().toInt(), v->bottom(), v->top());
+ setText( QString::number(*m_pVar) ); }
+ void write(ValueMap* config){ config->writeEntry(m_saveName, *m_pVar ); }
+ void read (ValueMap* config){ *m_pVar = config->readNumEntry( m_saveName, *m_pVar ); }
+private:
+ OptionIntEdit( const OptionIntEdit& ); // private copy constructor without implementation
+ int* m_pVar;
+ int m_defaultVal;
+};
+
+class OptionComboBox : public QComboBox, public OptionItem
+{
+public:
+ OptionComboBox( int defaultVal, const QString& saveName, int* pVarNum,
+ QWidget* pParent, OptionDialog* pOD )
+ : QComboBox( pParent ), OptionItem( pOD, saveName )
+ {
+ setMinimumWidth(50);
+ m_pVarNum = pVarNum;
+ m_pVarStr = 0;
+ m_defaultVal = defaultVal;
+ setEditable(false);
+ }
+ OptionComboBox( int defaultVal, const QString& saveName, QString* pVarStr,
+ QWidget* pParent, OptionDialog* pOD )
+ : QComboBox( pParent ), OptionItem( pOD, saveName )
+ {
+ m_pVarNum = 0;
+ m_pVarStr = pVarStr;
+ m_defaultVal = defaultVal;
+ setEditable(false);
+ }
+ void setToDefault()
+ {
+ setCurrentItem( m_defaultVal );
+ if (m_pVarStr!=0){ *m_pVarStr=currentText(); }
+ }
+ void setToCurrent()
+ {
+ if (m_pVarNum!=0) setCurrentItem( *m_pVarNum );
+ else setText( *m_pVarStr );
+ }
+ void apply()
+ {
+ if (m_pVarNum!=0){ *m_pVarNum = currentItem(); }
+ else { *m_pVarStr = currentText(); }
+ }
+ void write(ValueMap* config)
+ {
+ if (m_pVarStr!=0) config->writeEntry(m_saveName, *m_pVarStr );
+ else config->writeEntry(m_saveName, *m_pVarNum );
+ }
+ void read (ValueMap* config)
+ {
+ if (m_pVarStr!=0) setText( config->readEntry( m_saveName, currentText() ) );
+ else *m_pVarNum = config->readNumEntry( m_saveName, *m_pVarNum );
+ }
+private:
+ OptionComboBox( const OptionIntEdit& ); // private copy constructor without implementation
+ int* m_pVarNum;
+ QString* m_pVarStr;
+ 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 ( text(i)==s )
+ {
+ if (m_pVarNum!=0) *m_pVarNum = i;
+ if (m_pVarStr!=0) *m_pVarStr = s;
+ setCurrentItem(i);
+ return;
+ }
+ }
+ }
+};
+
+class OptionEncodingComboBox : public QComboBox, public OptionItem
+{
+ std::vector<QTextCodec*> m_codecVec;
+ QTextCodec** m_ppVarCodec;
+public:
+ OptionEncodingComboBox( const QString& saveName, QTextCodec** ppVarCodec,
+ QWidget* pParent, OptionDialog* pOD )
+ : QComboBox( pParent ), OptionItem( pOD, 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;
+ int i;
+ for(i=0;;++i)
+ {
+ QTextCodec* c = QTextCodec::codecForIndex(i);
+ if ( c==0 ) break;
+ else names[QString(c->name()).upper()]=c;
+ }
+
+ std::map<QString, QTextCodec*>::iterator it;
+ for(it=names.begin();it!=names.end();++it)
+ {
+ insertCodec( "", it->second );
+ }
+
+ QToolTip::add( this, i18n(
+ "Change this if non-ASCII characters are not displayed correctly."
+ ));
+ }
+ void insertCodec( const QString& visibleCodecName, QTextCodec* c )
+ {
+ if (c!=0)
+ {
+ for( unsigned int i=0; i<m_codecVec.size(); ++i )
+ {
+ if ( c==m_codecVec[i] )
+ return; // don't insert any codec twice
+ }
+ insertItem( visibleCodecName.isEmpty() ? QString(c->name()) : visibleCodecName+" ("+c->name()+")", m_codecVec.size() );
+ m_codecVec.push_back( c );
+ }
+ }
+ void setToDefault()
+ {
+ QString defaultName = QTextCodec::codecForLocale()->name();
+ for(int i=0;i<count();++i)
+ {
+ if (defaultName==text(i) &&
+ m_codecVec[i]==QTextCodec::codecForLocale())
+ {
+ setCurrentItem(i);
+ if (m_ppVarCodec!=0){ *m_ppVarCodec=m_codecVec[i]; }
+ return;
+ }
+ }
+
+ setCurrentItem( 0 );
+ if (m_ppVarCodec!=0){ *m_ppVarCodec=m_codecVec[0]; }
+ }
+ void setToCurrent()
+ {
+ if (m_ppVarCodec!=0)
+ {
+ for(unsigned int i=0; i<m_codecVec.size(); ++i)
+ {
+ if ( *m_ppVarCodec==m_codecVec[i] )
+ {
+ setCurrentItem( i );
+ break;
+ }
+ }
+ }
+ }
+ void apply()
+ {
+ if (m_ppVarCodec!=0){ *m_ppVarCodec = m_codecVec[ currentItem() ]; }
+ }
+ void write(ValueMap* config)
+ {
+ if (m_ppVarCodec!=0) config->writeEntry(m_saveName, (*m_ppVarCodec)->name() );
+ }
+ void read (ValueMap* config)
+ {
+ QString codecName = config->readEntry( m_saveName, m_codecVec[ currentItem() ]->name() );
+ for(unsigned int i=0; i<m_codecVec.size(); ++i)
+ {
+ if ( codecName == m_codecVec[i]->name() )
+ {
+ setCurrentItem( i );
+ if (m_ppVarCodec!=0) *m_ppVarCodec = m_codecVec[i];
+ break;
+ }
+ }
+ }
+};
+
+
+OptionDialog::OptionDialog( bool bShowDirMergeSettings, QWidget *parent, char *name )
+ :KDialogBase( IconList, i18n("Configure"), Help|Default|Apply|Ok|Cancel,
+ Ok, parent, name, true /*modal*/, 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();
+}
+
+OptionDialog::~OptionDialog( void )
+{
+}
+
+void OptionDialog::setupOtherOptions()
+{
+ new OptionToggleAction( false, "AutoAdvance", &m_bAutoAdvance, this );
+ new OptionToggleAction( true, "ShowWhiteSpaceCharacters", &m_bShowWhiteSpaceCharacters, this );
+ new OptionToggleAction( true, "ShowWhiteSpace", &m_bShowWhiteSpace, this );
+ new OptionToggleAction( false, "ShowLineNumbers", &m_bShowLineNumbers, this );
+ new OptionToggleAction( true, "HorizDiffWindowSplitting", &m_bHorizDiffWindowSplitting, this );
+ new OptionToggleAction( false, "WordWrap", &m_bWordWrap, this );
+
+ new OptionToggleAction( true, "ShowIdenticalFiles", &m_bDmShowIdenticalFiles, this );
+
+ new OptionToggleAction( true, "Show Toolbar", &m_bShowToolBar, this );
+ new OptionToggleAction( true, "Show Statusbar", &m_bShowStatusBar, this );
+
+ new OptionNum( (int)KToolBar::Top, "ToolBarPos", &m_toolBarPos, this );
+ new OptionSize( QSize(600,400),"Geometry", &m_geometry, this );
+ new OptionPoint( QPoint(0,22), "Position", &m_position, this );
+ new OptionToggleAction( false, "WindowStateMaximised", &m_bMaximised, this );
+
+ new OptionStringList( "RecentAFiles", &m_recentAFiles, this );
+ new OptionStringList( "RecentBFiles", &m_recentBFiles, this );
+ new OptionStringList( "RecentCFiles", &m_recentCFiles, this );
+ new OptionStringList( "RecentOutputFiles", &m_recentOutputFiles, this );
+}
+
+void OptionDialog::setupFontPage( void )
+{
+ QFrame *page = addPage( i18n("Font"), i18n("Editor & Diff Output Font" ),
+ BarIcon("fonts", KIcon::SizeMedium ) );
+
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 5, spacingHint() );
+
+ QFont defaultFont =
+#ifdef _WIN32
+ QFont("Courier New", 10 );
+#elif defined( KREPLACEMENTS_H )
+ QFont("Courier", 10 );
+#else
+ KGlobalSettings::fixedFont();
+#endif
+
+ OptionFontChooser* pFontChooser = new OptionFontChooser( defaultFont, "Font", &m_font, page, this );
+ topLayout->addWidget( pFontChooser );
+
+ QGridLayout *gbox = new QGridLayout( 1, 2 );
+ topLayout->addLayout( gbox );
+ int line=0;
+
+ OptionCheckBox* pItalicDeltas = new OptionCheckBox( i18n("Italic font for deltas"), false, "ItalicForDeltas", &m_bItalicForDeltas, page, this );
+ gbox->addMultiCellWidget( pItalicDeltas, line, line, 0, 1 );
+ QToolTip::add( pItalicDeltas, 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( void )
+{
+ QFrame *page = addPage( i18n("Color"), i18n("Colors Settings"),
+ BarIcon("colorize", KIcon::SizeMedium ) );
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 5, spacingHint() );
+
+ QGridLayout *gbox = new QGridLayout( 7, 2 );
+ gbox->setColStretch(1,5);
+ topLayout->addLayout(gbox);
+
+ QLabel* label;
+ int line = 0;
+
+ int depth = QColor::numBitPlanes();
+ 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_fgColor, page, this );
+ label = new QLabel( pFgColor, i18n("Foreground color:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pFgColor, line, 1 );
+ ++line;
+
+ OptionColorButton* pBgColor = new OptionColorButton( Qt::white, "BgColor", &m_bgColor, page, this );
+ label = new QLabel( pBgColor, i18n("Background color:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pBgColor, line, 1 );
+
+ ++line;
+
+ OptionColorButton* pDiffBgColor = new OptionColorButton(
+ bLowColor ? Qt::lightGray : qRgb(224,224,224), "DiffBgColor", &m_diffBgColor, page, this );
+ label = new QLabel( pDiffBgColor, i18n("Diff background color:"), page );
+ 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_colorA, page, this );
+ label = new QLabel( pColorA, i18n("Color A:"), page );
+ 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_colorB, page, this );
+ label = new QLabel( pColorB, i18n("Color B:"), page );
+ 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_colorC, page, this );
+ label = new QLabel( pColorC, i18n("Color C:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pColorC, line, 1 );
+ ++line;
+
+ OptionColorButton* pColorForConflict = new OptionColorButton( Qt::red, "ColorForConflict", &m_colorForConflict, page, this );
+ label = new QLabel( pColorForConflict, i18n("Conflict color:"), page );
+ 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_currentRangeBgColor, page, this );
+ label = new QLabel( pColor, i18n("Current range background color:"), page );
+ 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_currentRangeDiffBgColor, page, this );
+ label = new QLabel( pColor, i18n("Current range diff background color:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pColor, line, 1 );
+ ++line;
+
+ pColor = new OptionColorButton( qRgb(0xff,0xd0,0x80), "ManualAlignmentRangeColor", &m_manualHelpRangeColor, page, this );
+ label = new QLabel( pColor, i18n("Color for manually aligned difference ranges:"), page );
+ 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_newestFileColor, page, this );
+ label = new QLabel( pColor, i18n("Newest file color:"), page );
+ 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.");
+ QToolTip::add( label, dirColorTip );
+ ++line;
+
+ pColor = new OptionColorButton( qRgb(0xf0,0,0), "OldestFileColor", &m_oldestFileColor, page, this );
+ label = new QLabel( pColor, i18n("Oldest file color:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pColor, line, 1 );
+ QToolTip::add( label, dirColorTip );
+ ++line;
+
+ pColor = new OptionColorButton( qRgb(0xc0,0xc0,0), "MidAgeFileColor", &m_midAgeFileColor, page, this );
+ label = new QLabel( pColor, i18n("Middle age file color:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pColor, line, 1 );
+ QToolTip::add( label, dirColorTip );
+ ++line;
+
+ pColor = new OptionColorButton( qRgb(0,0,0), "MissingFileColor", &m_missingFileColor, page, this );
+ label = new QLabel( pColor, i18n("Color for missing files:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pColor, line, 1 );
+ QToolTip::add( label, dirColorTip );
+ ++line;
+
+ topLayout->addStretch(10);
+}
+
+
+void OptionDialog::setupEditPage( void )
+{
+ QFrame *page = addPage( i18n("Editor"), i18n("Editor Behavior"),
+ BarIcon("edit", KIcon::SizeMedium ) );
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 5, spacingHint() );
+
+ QGridLayout *gbox = new QGridLayout( 4, 2 );
+ gbox->setColStretch(1,5);
+ topLayout->addLayout( gbox );
+ QLabel* label;
+ int line=0;
+
+ OptionCheckBox* pReplaceTabs = new OptionCheckBox( i18n("Tab inserts spaces"), false, "ReplaceTabs", &m_bReplaceTabs, page, this );
+ gbox->addMultiCellWidget( pReplaceTabs, line, line, 0, 1 );
+ QToolTip::add( pReplaceTabs, 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_tabSize, 1, 100, page, this );
+ label = new QLabel( pTabSize, i18n("Tab size:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pTabSize, line, 1 );
+ ++line;
+
+ OptionCheckBox* pAutoIndentation = new OptionCheckBox( i18n("Auto indentation"), true, "AutoIndentation", &m_bAutoIndentation, page, this );
+ gbox->addMultiCellWidget( pAutoIndentation, line, line, 0, 1 );
+ QToolTip::add( pAutoIndentation, 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_bAutoCopySelection, page, this );
+ gbox->addMultiCellWidget( pAutoCopySelection, line, line, 0, 1 );
+ QToolTip::add( pAutoCopySelection, i18n(
+ "On: Any selection is immediately written to the clipboard.\n"
+ "Off: You must explicitely copy e.g. via Ctrl-C."
+ ));
+ ++line;
+
+ label = new QLabel( i18n("Line end style:"), page );
+ gbox->addWidget( label, line, 0 );
+ #ifdef _WIN32
+ int defaultLineEndStyle = eLineEndDos;
+ #else
+ int defaultLineEndStyle = eLineEndUnix;
+ #endif
+ OptionComboBox* pLineEndStyle = new OptionComboBox( defaultLineEndStyle, "LineEndStyle", &m_lineEndStyle, page, this );
+ gbox->addWidget( pLineEndStyle, line, 1 );
+ pLineEndStyle->insertItem( "Unix", eLineEndUnix );
+ pLineEndStyle->insertItem( "Dos/Windows", eLineEndDos );
+ QToolTip::add( label, 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( void )
+{
+ QFrame *page = addPage( i18n("Diff"), i18n("Diff Settings"),
+ BarIcon("misc", KIcon::SizeMedium ) );
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 5, spacingHint() );
+
+ QGridLayout *gbox = new QGridLayout( 3, 2 );
+ gbox->setColStretch(1,5);
+ topLayout->addLayout( gbox );
+ int line=0;
+
+ QLabel* label=0;
+
+// OptionCheckBox* pPreserveCarriageReturn = new OptionCheckBox( i18n("Preserve carriage return"), false, "PreserveCarriageReturn", &m_bPreserveCarriageReturn, page, this );
+// gbox->addMultiCellWidget( pPreserveCarriageReturn, line, line, 0, 1 );
+// QToolTip::add( pPreserveCarriageReturn, i18n(
+// "Show carriage return characters '\\r' if they exist.\n"
+// "Helps to compare files that were modified under different operating systems.")
+// );
+// ++line;
+ QString treatAsWhiteSpace = " ("+i18n("Treat as white space.")+")";
+
+ OptionCheckBox* pIgnoreNumbers = new OptionCheckBox( i18n("Ignore numbers")+treatAsWhiteSpace, false, "IgnoreNumbers", &m_bIgnoreNumbers, page, this );
+ gbox->addMultiCellWidget( pIgnoreNumbers, line, line, 0, 1 );
+ QToolTip::add( pIgnoreNumbers, 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")+treatAsWhiteSpace, false, "IgnoreComments", &m_bIgnoreComments, page, this );
+ gbox->addMultiCellWidget( pIgnoreComments, line, line, 0, 1 );
+ QToolTip::add( pIgnoreComments, i18n( "Treat C/C++ comments like white space.")
+ );
+ ++line;
+
+ OptionCheckBox* pIgnoreCase = new OptionCheckBox( i18n("Ignore case")+treatAsWhiteSpace, false, "IgnoreCase", &m_bIgnoreCase, page, this );
+ gbox->addMultiCellWidget( pIgnoreCase, line, line, 0, 1 );
+ QToolTip::add( pIgnoreCase, 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_PreProcessorCmd, page, this );
+ gbox->addWidget( pLE, line, 1 );
+ QToolTip::add( label, 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_LineMatchingPreProcessorCmd, page, this );
+ gbox->addWidget( pLE, line, 1 );
+ QToolTip::add( label, 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_bTryHard, page, this );
+ gbox->addMultiCellWidget( pTryHard, line, line, 0, 1 );
+ QToolTip::add( pTryHard, i18n(
+ "Enables the --minimal option for the external diff.\n"
+ "The analysis of big files will be much slower.")
+ );
+ ++line;
+
+ topLayout->addStretch(10);
+}
+
+void OptionDialog::setupMergePage( void )
+{
+ QFrame *page = addPage( i18n("Merge"), i18n("Merge Settings"),
+ BarIcon("misc", KIcon::SizeMedium ) );
+ QVBoxLayout *topLayout = new QVBoxLayout( page );
+ topLayout->setMargin( 5 );
+ topLayout->setSpacing( spacingHint() );
+
+ QGridLayout *gbox = new QGridLayout();
+ gbox->setColStretch(1,5);
+ topLayout->addLayout( gbox );
+ int line=0;
+
+ QLabel* label=0;
+
+ label = new QLabel( i18n("Auto advance delay (ms):"), page );
+ gbox->addWidget( label, line, 0 );
+ OptionIntEdit* pAutoAdvanceDelay = new OptionIntEdit( 500, "AutoAdvanceDelay", &m_autoAdvanceDelay, 0, 2000, page, this );
+ gbox->addWidget( pAutoAdvanceDelay, line, 1 );
+ QToolTip::add( label,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;
+
+ label = new QLabel( i18n("White space 2-file merge default:"), page );
+ gbox->addWidget( label, line, 0 );
+ OptionComboBox* pWhiteSpace2FileMergeDefault = new OptionComboBox( 0, "WhiteSpace2FileMergeDefault", &m_whiteSpace2FileMergeDefault, page, this );
+ gbox->addWidget( pWhiteSpace2FileMergeDefault, line, 1 );
+ pWhiteSpace2FileMergeDefault->insertItem( i18n("Manual Choice"), 0 );
+ pWhiteSpace2FileMergeDefault->insertItem( "A", 1 );
+ pWhiteSpace2FileMergeDefault->insertItem( "B", 2 );
+ QToolTip::add( label, 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_whiteSpace3FileMergeDefault, page, this );
+ gbox->addWidget( pWhiteSpace3FileMergeDefault, line, 1 );
+ pWhiteSpace3FileMergeDefault->insertItem( i18n("Manual Choice"), 0 );
+ pWhiteSpace3FileMergeDefault->insertItem( "A", 1 );
+ pWhiteSpace3FileMergeDefault->insertItem( "B", 2 );
+ pWhiteSpace3FileMergeDefault->insertItem( "C", 3 );
+ QToolTip::add( label, i18n(
+ "Allow the merge algorithm to automatically select an input for "
+ "white-space-only changes." )
+ );
+ ++line;
+
+ QGroupBox* pGroupBox = new QGroupBox( 2, Qt::Horizontal, i18n("Automatic Merge Regular Expression"), page);
+ gbox->addMultiCellWidget( pGroupBox, line,line,0,1);
+ ++line;
+ {
+ QWidget* page = new QWidget( pGroupBox );
+ QGridLayout* gbox = new QGridLayout( page, 2, 2, spacingHint() );
+ gbox->setColStretch(1,10);
+ int 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_autoMergeRegExp, page, this );
+ gbox->addWidget( m_pAutoMergeRegExpLineEdit, line, 1 );
+ 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.");
+ QToolTip::add( label, s_autoMergeRegExpToolTip );
+ ++line;
+
+ OptionCheckBox* pAutoMergeRegExp = new OptionCheckBox( i18n("Run regular expression auto merge on merge start"), false, "RunRegExpAutoMergeOnMergeStart", &m_bRunRegExpAutoMergeOnMergeStart, page, this );
+ gbox->addMultiCellWidget( pAutoMergeRegExp, line, line, 0, 1 );
+ QToolTip::add( pAutoMergeRegExp, i18n( "Run the merge for auto merge regular expressions\n"
+ "immediately when a merge starts.\n"));
+ ++line;
+ }
+
+ pGroupBox = new QGroupBox( 2, Qt::Horizontal, i18n("Version Control History Merging"), page);
+ gbox->addMultiCellWidget( pGroupBox, line,line,0,1);
+ ++line;
+ {
+ QWidget* page = new QWidget( pGroupBox );
+ QGridLayout* gbox = new QGridLayout( page, 2, 2, spacingHint() );
+ gbox->setColStretch(1,10);
+ int line = 0;
+
+ label = new QLabel( i18n("History start regular expression:"), page );
+ gbox->addWidget( label, line, 0 );
+ m_pHistoryStartRegExpLineEdit = new OptionLineEdit( ".*\\$Log.*\\$.*", "HistoryStartRegExp", &m_historyStartRegExp, page, this );
+ gbox->addWidget( m_pHistoryStartRegExpLineEdit, line, 1 );
+ 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.*\\$.*\"");
+ QToolTip::add( label, 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_historyEntryStartRegExp, page, this );
+ gbox->addWidget( m_pHistoryEntryStartRegExpLineEdit, line, 1 );
+ 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.");
+ QToolTip::add( label, s_historyEntryStartRegExpToolTip );
+ ++line;
+
+ m_pHistoryMergeSorting = new OptionCheckBox( i18n("History merge sorting"), false, "HistoryMergeSorting", &m_bHistoryMergeSorting, page, this );
+ gbox->addMultiCellWidget( m_pHistoryMergeSorting, line, line, 0, 1 );
+ QToolTip::add( m_pHistoryMergeSorting, 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_historyEntryStartSortKeyOrder, page, this );
+ gbox->addWidget( m_pHistorySortKeyOrderLineEdit, line, 1 );
+ s_historyEntryStartSortKeyOrderToolTip = i18n("Each 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.");
+ QToolTip::add( label, s_historyEntryStartSortKeyOrderToolTip );
+ m_pHistorySortKeyOrderLineEdit->setEnabled(false);
+ connect( m_pHistoryMergeSorting, SIGNAL(toggled(bool)), m_pHistorySortKeyOrderLineEdit, SLOT(setEnabled(bool)));
+ ++line;
+
+ m_pHistoryAutoMerge = new OptionCheckBox( i18n("Merge version control history on merge start"), false, "RunHistoryAutoMergeOnMergeStart", &m_bRunHistoryAutoMergeOnMergeStart, page, this );
+ gbox->addMultiCellWidget( m_pHistoryAutoMerge, line, line, 0, 1 );
+ QToolTip::add( m_pHistoryAutoMerge, i18n("Run version control history automerge on merge start.") );
+ ++line;
+
+ OptionIntEdit* pMaxNofHistoryEntries = new OptionIntEdit( -1, "MaxNofHistoryEntries", &m_maxNofHistoryEntries, -1, 1000, page, this );
+ label = new QLabel( pMaxNofHistoryEntries, i18n("Max number of history entries:"), page );
+ gbox->addWidget( label, line, 0 );
+ gbox->addWidget( pMaxNofHistoryEntries, line, 1 );
+ QToolTip::add( pMaxNofHistoryEntries, 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, SIGNAL(clicked()), this, SLOT(slotHistoryMergeRegExpTester()));
+ ++line;
+
+ label = new QLabel( i18n("Irrelevant merge command:"), page );
+ gbox->addWidget( label, line, 0 );
+ OptionLineEdit* pLE = new OptionLineEdit( "", "IrrelevantMergeCmd", &m_IrrelevantMergeCmd, page, this );
+ gbox->addWidget( pLE, line, 1 );
+ QToolTip::add( label, 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_bAutoSaveAndQuitOnMergeWithoutConflicts, page, this );
+ gbox->addMultiCellWidget( pAutoSaveAndQuit, line, line, 0, 1 );
+ QToolTip::add( pAutoSaveAndQuit, i18n("When KDiff3 was started for a file-merge from the commandline 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( void )
+{
+ QFrame *page = addPage( i18n("Directory Merge"), i18n("Directory Merge"),
+ BarIcon("folder", KIcon::SizeMedium ) );
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 5, spacingHint() );
+
+ QGridLayout *gbox = new QGridLayout( 11, 2 );
+ gbox->setColStretch(1,5);
+ topLayout->addLayout( gbox );
+ int line=0;
+
+ OptionCheckBox* pRecursiveDirs = new OptionCheckBox( i18n("Recursive directories"), true, "RecursiveDirs", &m_bDmRecursiveDirs, page, this );
+ gbox->addMultiCellWidget( pRecursiveDirs, line, line, 0, 1 );
+ QToolTip::add( pRecursiveDirs, 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_DmFilePattern, page, this );
+ gbox->addWidget( pFilePattern, line, 1 );
+ QToolTip::add( label, 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", "FileAntiPattern", &m_DmFileAntiPattern, page, this );
+ gbox->addWidget( pFileAntiPattern, line, 1 );
+ QToolTip::add( label, 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", "DirAntiPattern", &m_DmDirAntiPattern, page, this );
+ gbox->addWidget( pDirAntiPattern, line, 1 );
+ QToolTip::add( label, 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_bDmUseCvsIgnore, page, this );
+ gbox->addMultiCellWidget( pUseCvsIgnore, line, line, 0, 1 );
+ QToolTip::add( pUseCvsIgnore, 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_bDmFindHidden, page, this );
+ gbox->addMultiCellWidget( pFindHidden, line, line, 0, 1 );
+#ifdef _WIN32
+ QToolTip::add( pFindHidden, i18n("Finds files and directories with the hidden attribute.") );
+#else
+ QToolTip::add( pFindHidden, i18n("Finds files and directories starting with '.'.") );
+#endif
+ ++line;
+
+ OptionCheckBox* pFollowFileLinks = new OptionCheckBox( i18n("Follow file links"), false, "FollowFileLinks", &m_bDmFollowFileLinks, page, this );
+ gbox->addMultiCellWidget( pFollowFileLinks, line, line, 0, 1 );
+ QToolTip::add( pFollowFileLinks, 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_bDmFollowDirLinks, page, this );
+ gbox->addMultiCellWidget( pFollowDirLinks, line, line, 0, 1 );
+ QToolTip::add( pFollowDirLinks, i18n(
+ "On: Compare the directory the link points to.\n"
+ "Off: Compare the links."
+ ));
+ ++line;
+
+ //OptionCheckBox* pShowOnlyDeltas = new OptionCheckBox( i18n("List only deltas"),false,"ListOnlyDeltas", &m_bDmShowOnlyDeltas, page, this );
+ //gbox->addMultiCellWidget( pShowOnlyDeltas, line, line, 0, 1 );
+ //QToolTip::add( pShowOnlyDeltas, i18n(
+ // "Files and directories without change will not appear in the list."));
+ //++line;
+
+#ifdef _WIN32
+ bool bCaseSensitiveFilenameComparison = false;
+#else
+ bool bCaseSensitiveFilenameComparison = true;
+#endif
+ OptionCheckBox* pCaseSensitiveFileNames = new OptionCheckBox( i18n("Case sensitive filename comparison"),bCaseSensitiveFilenameComparison,"CaseSensitiveFilenameComparison", &m_bDmCaseSensitiveFilenameComparison, page, this );
+ gbox->addMultiCellWidget( pCaseSensitiveFileNames, line, line, 0, 1 );
+ QToolTip::add( pCaseSensitiveFileNames, 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;
+
+ QVButtonGroup* pBG = new QVButtonGroup(i18n("File Comparison Mode"),page);
+ gbox->addMultiCellWidget( pBG, line, line, 0, 1 );
+ ++line;
+
+ OptionRadioButton* pBinaryComparison = new OptionRadioButton( i18n("Binary comparison"), true, "BinaryComparison", &m_bDmBinaryComparison, pBG, this );
+ QToolTip::add( pBinaryComparison, i18n("Binary comparison of each file. (Default)") );
+
+ OptionRadioButton* pFullAnalysis = new OptionRadioButton( i18n("Full analysis"), false, "FullAnalysis", &m_bDmFullAnalysis, pBG, this );
+ QToolTip::add( pFullAnalysis, i18n("Do a full analysis and show statistics information in extra columns.\n"
+ "(Slower than a binary comparison, much slower for binary files.)") );
+
+ OptionRadioButton* pTrustDate = new OptionRadioButton( i18n("Trust the size and modification date (unsafe)"), false, "TrustDate", &m_bDmTrustDate, pBG, this );
+ QToolTip::add( pTrustDate, 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.") );
+
+ OptionRadioButton* pTrustDateFallbackToBinary = new OptionRadioButton( i18n("Trust the size and date, but use binary comparison if date doesn't match (unsafe)"), false, "TrustDateFallbackToBinary", &m_bDmTrustDateFallbackToBinary, pBG, this );
+ QToolTip::add( pTrustDateFallbackToBinary, i18n("Assume that files are equal if the modification date and file length are equal.\n"
+ "If the date isn't equal but the sizes are, use binary comparison.\n"
+ "Useful for big directories or slow networks.") );
+
+ OptionRadioButton* pTrustSize = new OptionRadioButton( i18n("Trust the size (unsafe)"), false, "TrustSize", &m_bDmTrustSize, pBG, this );
+ QToolTip::add( pTrustSize, 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.") );
+
+ // Some two Dir-options: Affects only the default actions.
+ OptionCheckBox* pSyncMode = new OptionCheckBox( i18n("Synchronize directories"), false,"SyncMode", &m_bDmSyncMode, page, this );
+ gbox->addMultiCellWidget( pSyncMode, line, line, 0, 1 );
+ QToolTip::add( pSyncMode, 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_bDmWhiteSpaceEqual, page, this );
+ gbox->addMultiCellWidget( pWhiteSpaceDiffsEqual, line, line, 0, 1 );
+ QToolTip::add( pWhiteSpaceDiffsEqual, i18n(
+ "If files differ only by white space consider them equal.\n"
+ "This is only active when full analysis is chosen." ) );
+ connect(pFullAnalysis, SIGNAL(toggled(bool)), pWhiteSpaceDiffsEqual, SLOT(setEnabled(bool)));
+ pWhiteSpaceDiffsEqual->setEnabled(false);
+ ++line;
+
+ OptionCheckBox* pCopyNewer = new OptionCheckBox( i18n("Copy newer instead of merging (unsafe)"), false, "CopyNewer", &m_bDmCopyNewer, page, this );
+ gbox->addMultiCellWidget( pCopyNewer, line, line, 0, 1 );
+ QToolTip::add( pCopyNewer, i18n(
+ "Don't 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_bDmCreateBakFiles, page, this );
+ gbox->addMultiCellWidget( pCreateBakFiles, line, line, 0, 1 );
+ QToolTip::add( pCreateBakFiles, i18n(
+ "When 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()).upper(), 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
+class Utf8BOMCodec : public QTextCodec
+{
+ public:
+ const char * name () const { return "UTF-8-BOM"; }
+ int mibEnum () const { return 2123; }
+ int heuristicContentMatch(const char*, int) const { return 0; }
+ class UTF8BOMEncoder : public QTextEncoder
+ {
+ bool bBOMAdded;
+ public:
+ UTF8BOMEncoder() { bBOMAdded=false; }
+ QCString fromUnicode(const QString& uc, int& lenInOut )
+ {
+ QCString r;
+ if (!bBOMAdded)
+ {
+ r += "\xEF\xBB\xBF";
+ bBOMAdded=true;
+ }
+ r += uc.utf8();
+ lenInOut = r.length();
+ return r;
+ }
+ };
+ QTextEncoder* makeEncoder() const
+ {
+ return new UTF8BOMEncoder;
+ }
+
+ class UTF8BOMDecoder : public QTextDecoder
+ {
+ QTextDecoder *m_pDecoder;
+ public:
+ UTF8BOMDecoder() { m_pDecoder = QTextCodec::codecForName("UTF-8")->makeDecoder(); }
+ ~UTF8BOMDecoder() {
+ delete m_pDecoder;
+ }
+ QString toUnicode( const char* p, int len)
+ {
+ return m_pDecoder->toUnicode( p, len );
+ }
+ };
+ QTextDecoder* makeDecoder() const
+ {
+ return new UTF8BOMDecoder;
+ }
+};
+
+void OptionDialog::setupRegionalPage( void )
+{
+ new Utf8BOMCodec();
+
+ QFrame *page = addPage( i18n("Regional Settings"), i18n("Regional Settings"),
+ BarIcon("locale"/*"charset"*/, KIcon::SizeMedium ) );
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 5, spacingHint() );
+
+ QGridLayout *gbox = new QGridLayout( 3, 2 );
+ gbox->setColStretch(1,5);
+ topLayout->addLayout( gbox );
+ int line=0;
+
+ QLabel* label;
+
+#ifdef KREPLACEMENTS_H
+
+static char* countryMap[]={
+"af Afrikaans",
+"ar Arabic",
+"az Azerbaijani",
+"be Belarusian",
+"bg Bulgarian",
+"bn Bengali",
+"bo Tibetan",
+"br Breton",
+"bs Bosnian",
+"ca Catalan",
+"cs Czech",
+"cy Welsh",
+"da Danish",
+"de German",
+"el Greek",
+"en_GB British English",
+"eo Esperanto",
+"es Spanish",
+"et Estonian",
+"eu Basque",
+"fa Farsi (Persian)",
+"fi Finnish",
+"fo Faroese",
+"fr French",
+"ga Irish Gaelic",
+"gl Galician",
+"gu Gujarati",
+"he Hebrew",
+"hi Hindi",
+"hr Croatian",
+"hsb Upper Sorbian",
+"hu Hungarian",
+"id Indonesian",
+"is Icelandic",
+"it Italian",
+"ja Japanese",
+"ka Georgian",
+"ko Korean",
+"ku Kurdish",
+"lo Lao",
+"lt Lithuanian",
+"lv Latvian",
+"mi Maori",
+"mk Macedonian",
+"mn Mongolian",
+"ms Malay",
+"mt Maltese",
+"nb Norwegian Bookmal",
+"nds Low Saxon",
+"nl Dutch",
+"nn Norwegian Nynorsk",
+"nso Northern Sotho",
+"oc Occitan",
+"pl Polish",
+"pt Portuguese",
+"pt_BR Brazilian Portuguese",
+"ro Romanian",
+"ru Russian",
+"rw Kinyarwanda",
+"se Northern Sami",
+"sk Slovak",
+"sl Slovenian",
+"sq Albanian",
+"sr Serbian",
+"sr@Latn Serbian",
+"ss Swati",
+"sv Swedish",
+"ta Tamil",
+"tg Tajik",
+"th Thai",
+"tr Turkish",
+"uk Ukrainian",
+"uz Uzbek",
+"ven Venda",
+"vi Vietnamese",
+"wa Walloon",
+"xh Xhosa",
+"zh_CN Chinese Simplified",
+"zh_TW Chinese Traditional",
+"zu Zulu"
+};
+
+ label = new QLabel( i18n("Language (restart required)"), page );
+ gbox->addWidget( label, line, 0 );
+ OptionComboBox* pLanguage = new OptionComboBox( 0, "Language", &m_language, page, this );
+ gbox->addWidget( pLanguage, line, 1 );
+ pLanguage->insertItem( "Auto", 0 ); // Must not translate, won't work otherwise!
+ pLanguage->insertItem( "en_orig" );
+
+ // Read directory: Find all kdiff3_*.qm-files and insert the found files here selection
+ FileAccess fa( getTranslationDir() );
+ t_DirectoryList dirList;
+ fa.listDir( &dirList, false, false, "kdiff3_*.qm", "", "*", false, false );
+ t_DirectoryList::iterator i;
+ for( i=dirList.begin(); i!=dirList.end(); ++i)
+ {
+ QString fileName = i->fileName();
+ // Skip the "kdiff3_" and omit the .qm
+ QString languageId = fileName.mid(7, fileName.length()-10 );
+
+ unsigned int countryIdx=0;
+ for(countryIdx=0; countryIdx< sizeof(countryMap)/sizeof(countryMap[0]); ++countryIdx )
+ {
+ QString fullName = countryMap[countryIdx];
+ if ( languageId+" " == fullName.left(languageId.length()+1) )
+ {
+ languageId += " (" + fullName.mid(languageId.length()+1) + ")";
+ }
+ }
+
+ pLanguage->insertItem( languageId );
+ }
+
+ QToolTip::add( label, i18n(
+ "Choose the language of the GUI-strings or \"Auto\".\n"
+ "For a change of language to take place, quit and restart KDiff3.")
+ );
+ ++line;
+/*
+ label = new QLabel( i18n("Codec for file contents"), page );
+ gbox->addWidget( label, line, 0 );
+ OptionComboBox* pFileCodec = new OptionComboBox( 0, "FileCodec", &m_fileCodec, page, this );
+ gbox->addWidget( pFileCodec, line, 1 );
+ insertCodecs( pFileCodec );
+ QToolTip::add( label, i18n(
+ "Choose the codec that should be used for your input files\n"
+ "or \"Auto\" if unsure." )
+ );
+ ++line;
+*/
+#endif
+
+ m_pSameEncoding = new OptionCheckBox( i18n("Use the same encoding for everything:"), true, "SameEncoding", &m_bSameEncoding, page, this );
+ gbox->addMultiCellWidget( m_pSameEncoding, line, line, 0, 1 );
+ QToolTip::add( m_pSameEncoding, 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 ") + "\"" + 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_pEncodingA, page, this );
+ 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 encoding is not detected then the selected encoding will be used as fallback.\n"
+ "(Unicode detection depends on the first bytes of a file - the byte order mark \"BOM\".)"
+ );
+ m_pAutoDetectUnicodeA = new OptionCheckBox( i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeA", &m_bAutoDetectUnicodeA, page, this );
+ gbox->addWidget( m_pAutoDetectUnicodeA, line, 2 );
+ QToolTip::add( m_pAutoDetectUnicodeA, autoDetectToolTip );
+ ++line;
+
+ label = new QLabel( i18n("File Encoding for B:"), page );
+ gbox->addWidget( label, line, 0 );
+ m_pEncodingBComboBox = new OptionEncodingComboBox( "EncodingForB", &m_pEncodingB, page, this );
+ gbox->addWidget( m_pEncodingBComboBox, line, 1 );
+ m_pAutoDetectUnicodeB = new OptionCheckBox( i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeB", &m_bAutoDetectUnicodeB, page, this );
+ gbox->addWidget( m_pAutoDetectUnicodeB, line, 2 );
+ QToolTip::add( m_pAutoDetectUnicodeB, autoDetectToolTip );
+ ++line;
+
+ label = new QLabel( i18n("File Encoding for C:"), page );
+ gbox->addWidget( label, line, 0 );
+ m_pEncodingCComboBox = new OptionEncodingComboBox( "EncodingForC", &m_pEncodingC, page, this );
+ gbox->addWidget( m_pEncodingCComboBox, line, 1 );
+ m_pAutoDetectUnicodeC = new OptionCheckBox( i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeC", &m_bAutoDetectUnicodeC, page, this );
+ gbox->addWidget( m_pAutoDetectUnicodeC, line, 2 );
+ QToolTip::add( m_pAutoDetectUnicodeC, 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_pEncodingOut, page, this );
+ gbox->addWidget( m_pEncodingOutComboBox, line, 1 );
+ m_pAutoSelectOutEncoding = new OptionCheckBox( i18n("Auto Select"), true, "AutoSelectOutEncoding", &m_bAutoSelectOutEncoding, page, this );
+ gbox->addWidget( m_pAutoSelectOutEncoding, line, 2 );
+ QToolTip::add( m_pAutoSelectOutEncoding, 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_pEncodingPP, page, this );
+ gbox->addWidget( m_pEncodingPPComboBox, line, 1 );
+ ++line;
+
+ connect(m_pSameEncoding, SIGNAL(toggled(bool)), this, SLOT(slotEncodingChanged()));
+ connect(m_pEncodingAComboBox, SIGNAL(activated(int)), this, SLOT(slotEncodingChanged()));
+ connect(m_pAutoDetectUnicodeA, SIGNAL(toggled(bool)), this, SLOT(slotEncodingChanged()));
+ connect(m_pAutoSelectOutEncoding, SIGNAL(toggled(bool)), this, SLOT(slotEncodingChanged()));
+
+ OptionCheckBox* pRightToLeftLanguage = new OptionCheckBox( i18n("Right To Left Language"), false, "RightToLeftLanguage", &m_bRightToLeftLanguage, page, this );
+ gbox->addMultiCellWidget( pRightToLeftLanguage, line, line, 0, 1 );
+ QToolTip::add( pRightToLeftLanguage, i18n(
+ "Some languages are read from right to left.\n"
+ "This setting will change the viewer and editor accordingly."));
+ ++line;
+
+
+ topLayout->addStretch(10);
+}
+
+#ifdef _WIN32
+#include "ccInstHelper.cpp"
+#endif
+
+void OptionDialog::setupIntegrationPage( void )
+{
+ QFrame *page = addPage( i18n("Integration"), i18n("Integration Settings"),
+ BarIcon("launch"/*"charset"*/, KIcon::SizeMedium ) );
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 5, spacingHint() );
+
+ QGridLayout *gbox = new QGridLayout( 3, 3 );
+ gbox->setColStretch(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_ignorableCmdLineOptions, page, this );
+ gbox->addMultiCellWidget( pIgnorableCmdLineOptions, line, line, 1,2 );
+ QToolTip::add( label, 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;
+
+#ifdef _WIN32
+ QPushButton* pIntegrateWithClearCase = new QPushButton( i18n("Integrate with ClearCase"), page);
+ gbox->addWidget( pIntegrateWithClearCase, line, 0 );
+ QToolTip::add( pIntegrateWithClearCase, i18n(
+ "Integrate with Rational ClearCase from IBM.\n"
+ "Modifies the \"map\" file in ClearCase-subdir \"lib/mgrs\"\n"
+ "(Only enabled when ClearCase \"bin\" directory is in the path.)"));
+ connect(pIntegrateWithClearCase, SIGNAL(clicked()),this, SLOT(slotIntegrateWithClearCase()) );
+ pIntegrateWithClearCase->setEnabled( integrateWithClearCase( "existsClearCase", "" )!=0 );
+
+ QPushButton* pRemoveClearCaseIntegration = new QPushButton( i18n("Remove ClearCase Integration"), page);
+ gbox->addWidget( pRemoveClearCaseIntegration, line, 1 );
+ QToolTip::add( pRemoveClearCaseIntegration, i18n(
+ "Restore the old \"map\" file from before doing the Clearcase integration."));
+ connect(pRemoveClearCaseIntegration, SIGNAL(clicked()),this, SLOT(slotRemoveClearCaseIntegration()) );
+ pRemoveClearCaseIntegration->setEnabled( integrateWithClearCase( "existsClearCase", "" )!=0 );
+
+ ++line;
+#endif
+
+ topLayout->addStretch(10);
+}
+
+void OptionDialog::slotIntegrateWithClearCase()
+{
+#ifdef _WIN32
+ char kdiff3CommandPath[1000];
+ GetModuleFileNameA( 0, kdiff3CommandPath, sizeof(kdiff3CommandPath)-1 );
+ integrateWithClearCase( "install", kdiff3CommandPath );
+#endif
+}
+
+void OptionDialog::slotRemoveClearCaseIntegration()
+{
+#ifdef _WIN32
+ char kdiff3CommandPath[1000];
+ GetModuleFileNameA( 0, kdiff3CommandPath, sizeof(kdiff3CommandPath)-1 );
+ integrateWithClearCase( "uninstall", kdiff3CommandPath );
+#endif
+}
+
+void OptionDialog::slotEncodingChanged()
+{
+ if ( m_pSameEncoding->isChecked() )
+ {
+ m_pEncodingBComboBox->setEnabled( false );
+ m_pEncodingBComboBox->setCurrentItem( m_pEncodingAComboBox->currentItem() );
+ m_pEncodingCComboBox->setEnabled( false );
+ m_pEncodingCComboBox->setCurrentItem( m_pEncodingAComboBox->currentItem() );
+ m_pEncodingOutComboBox->setEnabled( false );
+ m_pEncodingOutComboBox->setCurrentItem( m_pEncodingAComboBox->currentItem() );
+ m_pEncodingPPComboBox->setEnabled( false );
+ m_pEncodingPPComboBox->setCurrentItem( m_pEncodingAComboBox->currentItem() );
+ m_pAutoDetectUnicodeB->setEnabled( false );
+ m_pAutoDetectUnicodeB->setChecked( m_pAutoDetectUnicodeA->isChecked() );
+ m_pAutoDetectUnicodeC->setEnabled( false );
+ m_pAutoDetectUnicodeC->setChecked( m_pAutoDetectUnicodeA->isChecked() );
+ m_pAutoSelectOutEncoding->setEnabled( false );
+ m_pAutoSelectOutEncoding->setChecked( m_pAutoDetectUnicodeA->isChecked() );
+ }
+ 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->isChecked() );
+ }
+}
+
+void OptionDialog::setupKeysPage( void )
+{
+ //QVBox *page = addVBoxPage( i18n("Keys"), i18n("KeyDialog" ),
+ // BarIcon("fonts", KIcon::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( void )
+{
+ slotApply();
+
+ // My system returns variable width fonts even though I
+ // disabled this. Even QFont::fixedPitch() doesn't work.
+ QFontMetrics fm(m_font);
+ if ( fm.width('W')!=fm.width('i') )
+ {
+ int result = KMessageBox::warningYesNo(this, i18n(
+ "You selected a variable width font.\n\n"
+ "Because this program doesn't handle variable width fonts\n"
+ "correctly, you might experience problems while editing.\n\n"
+ "Do you want to continue or do you want to select another font."),
+ i18n("Incompatible Font"),
+ i18n("Continue at Own Risk"), i18n("Select Another Font"));
+ if (result==KMessageBox::No)
+ return;
+ }
+
+ accept();
+}
+
+
+/** Copy the values from the widgets to the public variables.*/
+void OptionDialog::slotApply( void )
+{
+ std::list<OptionItem*>::iterator i;
+ for(i=m_optionItemList.begin(); i!=m_optionItemList.end(); ++i)
+ {
+ (*i)->apply();
+ }
+
+ emit applyClicked();
+
+#ifdef _WIN32
+ QString locale = m_language;
+ if ( locale == "Auto" || locale.isEmpty() )
+ locale = locale = QTextCodec::locale();
+ int spacePos = locale.find(' ');
+ if (spacePos>0) locale = locale.left(spacePos);
+ QSettings settings;
+ settings.setPath("KDiff3", "diff-ext", QSettings::User );
+ settings.writeEntry( "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<OptionItem*>::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<OptionItem*>::iterator i;
+ for(i=m_optionItemList.begin(); i!=m_optionItemList.end(); ++i)
+ {
+ (*i)->setToCurrent();
+ }
+
+ slotEncodingChanged();
+}
+
+class ConfigValueMap : public ValueMap
+{
+private:
+ KConfig* m_pConfig;
+public:
+ ConfigValueMap( KConfig* pConfig ) { m_pConfig = pConfig; }
+
+ void writeEntry(const QString& s, const QFont& v ){ m_pConfig->writeEntry(s,v); }
+ void writeEntry(const QString& s, const QColor& v ){ m_pConfig->writeEntry(s,v); }
+ void writeEntry(const QString& s, const QSize& v ){ m_pConfig->writeEntry(s,v); }
+ void writeEntry(const QString& s, const QPoint& v ){ m_pConfig->writeEntry(s,v); }
+ void writeEntry(const QString& s, int v ) { m_pConfig->writeEntry(s,v); }
+ void writeEntry(const QString& s, bool v ) { m_pConfig->writeEntry(s,v); }
+ void writeEntry(const QString& s, const QStringList& v, char separator ){ m_pConfig->writeEntry(s,v,separator); }
+ void writeEntry(const QString& s, const QString& v ){ m_pConfig->writeEntry(s,v); }
+ void writeEntry(const QString& s, const char* v ) { m_pConfig->writeEntry(s,v); }
+
+ QFont readFontEntry (const QString& s, QFont* defaultVal ) { return m_pConfig->readFontEntry(s,defaultVal); }
+ QColor readColorEntry(const QString& s, QColor* defaultVal ){ return m_pConfig->readColorEntry(s,defaultVal); }
+ QSize readSizeEntry (const QString& s, QSize* defaultVal ) { return m_pConfig->readSizeEntry(s,defaultVal); }
+ QPoint readPointEntry(const QString& s, QPoint* defaultVal) { return m_pConfig->readPointEntry(s,defaultVal); }
+ bool readBoolEntry (const QString& s, bool defaultVal ) { return m_pConfig->readBoolEntry(s,defaultVal); }
+ int readNumEntry (const QString& s, int defaultVal ) { return m_pConfig->readNumEntry(s,defaultVal); }
+ QStringList readListEntry (const QString& s, const QStringList& def, char separator ) { return m_pConfig->readListEntry(s.latin1(),def,separator); }
+ QString readEntry (const QString& s, const QString& defaultVal){ return m_pConfig->readEntry(s,defaultVal); }
+};
+
+void OptionDialog::saveOptions( KConfig* config )
+{
+ // No i18n()-Translations here!
+
+ config->setGroup("KDiff3 Options");
+
+ ConfigValueMap cvm(config);
+ std::list<OptionItem*>::iterator i;
+ for(i=m_optionItemList.begin(); i!=m_optionItemList.end(); ++i)
+ {
+ (*i)->write(&cvm);
+ }
+}
+
+void OptionDialog::readOptions( KConfig* config )
+{
+ // No i18n()-Translations here!
+
+ config->setGroup("KDiff3 Options");
+
+ ConfigValueMap cvm(config);
+ std::list<OptionItem*>::iterator i;
+ for(i=m_optionItemList.begin(); i!=m_optionItemList.end(); ++i)
+ {
+ (*i)->read(&cvm);
+ }
+
+ setState();
+}
+
+void OptionDialog::slotHelp( void )
+{
+ KDialogBase::slotHelp();
+}
+
+QString OptionDialog::parseOptions( const QCStringList& optionList )
+{
+ QString result;
+ QCStringList::const_iterator i;
+ for ( i=optionList.begin(); i!=optionList.end(); ++i )
+ {
+ QString s = *i;
+
+ int pos = s.find('=');
+ if( pos > 0 ) // seems not to have a tag
+ {
+ QString key = s.left(pos);
+ QString val = s.mid(pos+1);
+ std::list<OptionItem*>::iterator j;
+ bool bFound = false;
+ for(j=m_optionItemList.begin(); j!=m_optionItemList.end(); ++j)
+ {
+ if ( (*j)->getSaveName()==key )
+ {
+ 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<OptionItem*>::iterator j;
+ for(j=m_optionItemList.begin(); j!=m_optionItemList.end(); ++j)
+ {
+ (*j)->write( &config );
+ }
+ return config.getAsString();
+}
+
+void OptionDialog::slotHistoryMergeRegExpTester()
+{
+ RegExpTester dlg(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->setCurrentText( dlg.autoMergeRegExp() );
+ m_pHistoryStartRegExpLineEdit->setCurrentText( dlg.historyStartRegExp() );
+ m_pHistoryEntryStartRegExpLineEdit->setCurrentText( dlg.historyEntryStartRegExp() );
+ m_pHistorySortKeyOrderLineEdit->setCurrentText( dlg.historySortKeyOrder() );
+ }
+}
+
+
+#include "optiondialog.moc"
diff --git a/src/optiondialog.h b/src/optiondialog.h
new file mode 100644
index 0000000..98ea0ff
--- /dev/null
+++ b/src/optiondialog.h
@@ -0,0 +1,229 @@
+
+/*
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef OPTION_DIALOG_H
+#define OPTION_DIALOG_H
+
+class QCheckBox;
+class QLabel;
+class QLineEdit;
+class KColorButton;
+class KFontChooser;
+class KConfig;
+
+#include <kdialogbase.h>
+#include <qstringlist.h>
+#include <list>
+#include <kcmdlineargs.h>
+
+class OptionItem;
+class OptionCheckBox;
+class OptionEncodingComboBox;
+class OptionLineEdit;
+class KKeyDialog;
+
+enum e_LineEndStyle
+{
+ eLineEndUnix=0,
+ eLineEndDos
+};
+
+class OptionDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+
+ OptionDialog( bool bShowDirMergeSettings, QWidget *parent = 0, char *name = 0 );
+ ~OptionDialog( void );
+ QString parseOptions( const QCStringList& optionList );
+ QString calcOptionHelp();
+
+ // Some settings are not available in the option dialog:
+ QSize m_geometry;
+ QPoint m_position;
+ bool m_bMaximised;
+ bool m_bShowToolBar;
+ bool m_bShowStatusBar;
+ int m_toolBarPos;
+
+ // These are the results of the option dialog.
+ QFont m_font;
+ bool m_bItalicForDeltas;
+
+ 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;
+ int m_lineEndStyle;
+
+ bool m_bPreserveCarriageReturn;
+ bool m_bTryHard;
+ bool m_bShowWhiteSpaceCharacters;
+ bool m_bShowWhiteSpace;
+ bool m_bShowLineNumbers;
+ bool m_bHorizDiffWindowSplitting;
+
+ 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_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;
+ QString m_DmFilePattern;
+ QString m_DmFileAntiPattern;
+ QString m_DmDirAntiPattern;
+
+ QString m_language;
+ bool m_bRightToLeftLanguage;
+
+ QString m_ignorableCmdLineOptions;
+ bool m_bIntegrateWithClearCase;
+
+ void saveOptions(KConfig* config);
+ void readOptions(KConfig* config);
+
+ void setState(); // Must be called before calling exec();
+
+ void addOptionItem(OptionItem*);
+ KKeyDialog* m_pKeyDialog;
+protected slots:
+ virtual void slotDefault( void );
+ virtual void slotOk( void );
+ virtual void slotApply( void );
+ virtual void slotHelp( void );
+
+ void slotEncodingChanged();
+ void slotHistoryMergeRegExpTester();
+ void slotIntegrateWithClearCase();
+ void slotRemoveClearCaseIntegration();
+private:
+ void resetToDefaults();
+
+ std::list<OptionItem*> m_optionItemList;
+
+ 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();
+};
+
+
+
+
+#endif
+
+
+
+
+
+
+
diff --git a/src/pdiff.cpp b/src/pdiff.cpp
new file mode 100644
index 0000000..abb26bc
--- /dev/null
+++ b/src/pdiff.cpp
@@ -0,0 +1,2268 @@
+/***************************************************************************
+ pdiff.cpp - Implementation for class KDiff3App
+ ---------------
+ begin : Mon March 18 20:04:50 CET 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 "difftextwindow.h"
+#include "mergeresultwindow.h"
+#include "directorymergewindow.h"
+#include "smalldialogs.h"
+
+#include <iostream>
+#include <algorithm>
+#include <ctype.h>
+#include <qaccel.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kfontdialog.h>
+#include <kstatusbar.h>
+#include <kkeydialog.h>
+
+#include <qclipboard.h>
+#include <qscrollbar.h>
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qsplitter.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qvbuttongroup.h>
+#include <qdragobject.h>
+#include <qlineedit.h>
+#include <qcombobox.h>
+#include <assert.h>
+
+#include "kdiff3.h"
+#include "optiondialog.h"
+#include "fileaccess.h"
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "gnudiff_diff.h"
+
+bool g_bIgnoreWhiteSpace = true;
+bool g_bIgnoreTrivialMatches = true;
+
+
+bool KDiff3App::runDiff( const LineData* p1, int size1, const LineData* p2, int size2, DiffList& diffList )
+{
+ ProgressProxy pp;
+ static GnuDiff gnuDiff; // All values are initialized with zeros.
+
+ pp.setCurrent(0);
+
+ diffList.clear();
+ if ( p1[0].pLine==0 || p2[0].pLine==0 || size1==0 || size2==0 )
+ {
+ Diff d( 0,0,0);
+ if ( p1[0].pLine==0 && p2[0].pLine==0 && 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 = 0;
+ 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 = m_pOptionDialog->m_bIgnoreNumbers;
+ gnuDiff.minimal = m_pOptionDialog->m_bTryHard;
+ gnuDiff.ignore_case = false;
+ GnuDiff::change* script = gnuDiff.diff_2_files( &comparisonInput );
+
+ int equalLinesAtStart = comparisonInput.file[0].prefix_lines;
+ int currentLine1 = 0;
+ int currentLine2 = 0;
+ GnuDiff::change* p=0;
+ for (GnuDiff::change* e = script; e; e = p)
+ {
+ Diff d(0,0,0);
+ d.nofEquals = e->line0 - currentLine1;
+ 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 = min2(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;
+
+ int nofEquals = min2(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;
+ }
+ */
+ }
+ }
+
+#ifndef NDEBUG
+ // Verify difflist
+ {
+ int l1=0;
+ int 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 )
+ if( l1!=size1 || l2!=size2 )
+ assert( false );
+ }
+#endif
+
+ pp.setCurrent(1.0);
+
+ return true;
+}
+
+bool KDiff3App::runDiff( const LineData* p1, int size1, const LineData* p2, int size2, DiffList& diffList,
+ int winIdx1, int winIdx2 )
+{
+ diffList.clear();
+ DiffList diffList2;
+
+ int l1begin = 0;
+ int l2begin = 0;
+ ManualDiffHelpList::const_iterator i;
+ for( i = m_manualDiffHelpList.begin(); i!=m_manualDiffHelpList.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 );
+ 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 );
+ diffList.splice( diffList.end(), diffList2 );
+ l1begin = l1end;
+ l2begin = l2end;
+ }
+ }
+ }
+ runDiff( p1+l1begin, size1-l1begin, p2+l2begin, size2-l2begin, diffList2 );
+ diffList.splice( diffList.end(), diffList2 );
+ return true;
+}
+
+void KDiff3App::init( bool bAuto, TotalDiffStatus* pTotalDiffStatus, bool bLoadFiles )
+{
+ ProgressProxy pp;
+ // 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 == 0;
+ if (pTotalDiffStatus==0)
+ pTotalDiffStatus = &m_totalDiffStatus;
+
+ bool bPreserveCarriageReturn = m_pOptionDialog->m_bPreserveCarriageReturn;
+
+ bool bVisibleMergeResultWindow = ! m_outputFilename.isEmpty();
+ if ( bVisibleMergeResultWindow && bGUI )
+ {
+ bPreserveCarriageReturn = false;
+
+ QString msg;
+
+ if ( !m_pOptionDialog->m_PreProcessorCmd.isEmpty() )
+ {
+ msg += "- " + i18n("PreprocessorCmd: ") + m_pOptionDialog->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"),
+ i18n("Use These Options During Merge"), i18n("Disable Unsafe Options")
+ );
+
+ if (result == KMessageBox::No )
+ {
+ m_pOptionDialog->m_PreProcessorCmd = "";
+ }
+ }
+ }
+
+ // Because of the progressdialog paintevents can occur, but data is invalid,
+ // so painting must be suppressed.
+ if (m_pDiffTextWindow1) m_pDiffTextWindow1->setPaintingAllowed( false );
+ if (m_pDiffTextWindow2) m_pDiffTextWindow2->setPaintingAllowed( false );
+ if (m_pDiffTextWindow3) m_pDiffTextWindow3->setPaintingAllowed( false );
+ if (m_pOverview) m_pOverview->setPaintingAllowed( false );
+ if (m_pMergeResultWindow) m_pMergeResultWindow->setPaintingAllowed( false );
+
+ 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"));
+ m_sd1.readAndPreprocess(m_pOptionDialog->m_pEncodingA, m_pOptionDialog->m_bAutoDetectUnicodeA );
+ pp.step();
+
+ pp.setInformation(i18n("Loading B"));
+ m_sd2.readAndPreprocess(m_pOptionDialog->m_pEncodingB, m_pOptionDialog->m_bAutoDetectUnicodeB );
+ pp.step();
+ }
+ else
+ {
+ if( m_sd3.isEmpty() )
+ pp.setMaxNofSteps( 2 ); // 1 comparison, 1 finediff
+ else
+ pp.setMaxNofSteps( 6 ); // 3 comparisons, 3 finediffs
+ }
+
+ 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 );
+
+ pp.step();
+
+ pp.setInformation(i18n("Linediff: A <-> B"));
+ calcDiff3LineListUsingAB( &m_diffList12, m_diff3LineList );
+ fineDiff( m_diff3LineList, 1, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay(), pTotalDiffStatus->bTextAEqB );
+ if ( m_sd1.getSizeBytes()==0 ) pTotalDiffStatus->bTextAEqB=false;
+
+ pp.step();
+ }
+ else
+ {
+ if (bLoadFiles)
+ {
+ pp.setInformation(i18n("Loading C"));
+ m_sd3.readAndPreprocess(m_pOptionDialog->m_pEncodingC, m_pOptionDialog->m_bAutoDetectUnicodeC );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+
+ 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"));
+ fineDiff( m_diff3LineList, 1, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay(), pTotalDiffStatus->bTextAEqB );
+ pp.step();
+ pp.setInformation(i18n("Linediff: B <-> C"));
+ fineDiff( m_diff3LineList, 2, m_sd2.getLineDataForDisplay(), m_sd3.getLineDataForDisplay(), pTotalDiffStatus->bTextBEqC );
+ pp.step();
+ pp.setInformation(i18n("Linediff: A <-> C"));
+ fineDiff( m_diff3LineList, 3, m_sd3.getLineDataForDisplay(), m_sd1.getLineDataForDisplay(), pTotalDiffStatus->bTextAEqC );
+ 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();
+
+ m_pDirectoryMergeWindow->allowResizeEvents(false);
+ initView();
+ if ( !bGUI )
+ {
+ m_pMainWidget->hide();
+ }
+ m_pDirectoryMergeWindow->allowResizeEvents(true);
+
+ m_bTripleDiff = ! m_sd3.isEmpty();
+
+ m_pMergeResultWindowTitle->setEncodings( m_sd1.getEncoding(), m_sd2.getEncoding(), m_sd3.getEncoding() );
+ if ( ! m_pOptionDialog->m_bAutoSelectOutEncoding )
+ m_pMergeResultWindowTitle->setEncoding( m_pOptionDialog->m_pEncodingOut );
+
+ if ( bGUI )
+ {
+ const ManualDiffHelpList* pMDHL = &m_manualDiffHelpList;
+ m_pDiffTextWindow1->init( m_sd1.getAliasName(),
+ m_sd1.getLineDataForDisplay(), m_sd1.getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff );
+ m_pDiffTextWindow2->init( m_sd2.getAliasName(),
+ m_sd2.getLineDataForDisplay(), m_sd2.getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff );
+ m_pDiffTextWindow3->init( m_sd3.getAliasName(),
+ m_sd3.getLineDataForDisplay(), m_sd3.getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff );
+
+ if (m_bTripleDiff) m_pDiffTextWindowFrame3->show();
+ else m_pDiffTextWindowFrame3->hide();
+ }
+
+ m_bOutputModified = bVisibleMergeResultWindow;
+
+ m_pMergeResultWindow->init(
+ m_sd1.getLineDataForDisplay(), m_sd1.getSizeLines(),
+ m_sd2.getLineDataForDisplay(), m_sd2.getSizeLines(),
+ m_bTripleDiff ? m_sd3.getLineDataForDisplay() : 0, 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();
+ return;
+ }
+
+ m_pOverview->init(&m_diff3LineList, m_bTripleDiff );
+ m_pDiffVScrollBar->setValue( 0 );
+ m_pHScrollBar->setValue( 0 );
+ m_pMergeVScrollBar->setValue( 0 );
+
+ m_pDiffTextWindow1->setPaintingAllowed( true );
+ m_pDiffTextWindow2->setPaintingAllowed( true );
+ m_pDiffTextWindow3->setPaintingAllowed( true );
+ m_pOverview->setPaintingAllowed( true );
+ m_pMergeResultWindow->setPaintingAllowed( true );
+
+
+ if ( !bVisibleMergeResultWindow )
+ m_pMergeWindowFrame->hide();
+ else
+ m_pMergeWindowFrame->show();
+
+ // Calc max width for display
+ m_maxWidth = max2( m_pDiffTextWindow1->getNofColumns(), m_pDiffTextWindow2->getNofColumns() );
+ m_maxWidth = max2( m_maxWidth, m_pDiffTextWindow3->getNofColumns() );
+ m_maxWidth += 5;
+
+ // Try to create a meaningful but not too long caption
+ if ( !isPart() )
+ {
+ // 1. If the filenames are equal then show only one filename
+ QString caption;
+ QString a1 = m_sd1.getAliasName();
+ QString a2 = m_sd2.getAliasName();
+ QString a3 = m_sd3.getAliasName();
+ QString f1, f2, f3;
+ int p1,p2,p3;
+ if ( !a1.isEmpty() && (p1=a1.findRev('/'))>=0 )
+ f1 = a1.mid( p1+1 );
+ if ( !a2.isEmpty() && (p2=a2.findRev('/'))>=0 )
+ f2 = a2.mid( p2+1 );
+ if ( !a3.isEmpty() && (p3=a3.findRev('/'))>=0 )
+ f3 = a3.mid( p3+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("") : QString(".../")+f1 );
+ caption += QString(caption.isEmpty() || f2.isEmpty() ? "" : " <-> ") + ( f2.isEmpty()? QString("") : QString(".../")+f2 );
+ caption += QString(caption.isEmpty() || f3.isEmpty() ? "" : " <-> ") + ( f3.isEmpty()? QString("") : QString(".../")+f3 ) ;
+ }
+
+ m_pKDiff3Shell->setCaption( caption.isEmpty() ? QString("KDiff3") : caption+QString(" - KDiff3"));
+ }
+
+ if ( bLoadFiles )
+ {
+ if ( bVisibleMergeResultWindow && !bAuto )
+ m_pMergeResultWindow->showNrOfConflicts();
+ else if ( !bAuto &&
+ // 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" ).arg("A").arg("B");
+ else if ( pTotalDiffStatus->bTextAEqB ) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n").arg("A").arg("B");
+ if ( pTotalDiffStatus->bBinaryAEqC ) totalInfo += i18n("Files %1 and %2 are binary equal.\n" ).arg("A").arg("C");
+ else if ( pTotalDiffStatus->bTextAEqC ) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n").arg("A").arg("C");
+ if ( pTotalDiffStatus->bBinaryBEqC ) totalInfo += i18n("Files %1 and %2 are binary equal.\n" ).arg("B").arg("C");
+ else if ( pTotalDiffStatus->bTextBEqC ) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n").arg("B").arg("C");
+ }
+
+ if ( !totalInfo.isEmpty() )
+ KMessageBox::information( this, totalInfo );
+ }
+
+ if ( bVisibleMergeResultWindow && (!m_sd1.isText() || !m_sd2.isText() || !m_sd3.isText()) )
+ {
+ KMessageBox::information( this, i18n(
+ "Some inputfiles don't seem to be pure textfiles.\n"
+ "Note that the KDiff3-merge was not meant for binary data.\n"
+ "Continue at your own risk.") );
+ }
+ }
+
+ QTimer::singleShot( 10, this, SLOT(slotAfterFirstPaint()) );
+
+ if ( bVisibleMergeResultWindow && m_pMergeResultWindow )
+ {
+ m_pMergeResultWindow->setFocus();
+ }
+ else if(m_pDiffTextWindow1)
+ {
+ m_pDiffTextWindow1->setFocus();
+ }
+}
+
+
+void KDiff3App::resizeDiffTextWindow(int newWidth, int newHeight)
+{
+ m_DTWHeight = newHeight;
+
+ recalcWordWrap();
+
+ m_pDiffVScrollBar->setRange(0, max2(0, m_neededLines+1 - newHeight) );
+ m_pDiffVScrollBar->setPageStep( newHeight );
+ m_pOverview->setRange( m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep() );
+
+ // The second window has a somewhat inverse width
+
+ m_pHScrollBar->setRange(0, max2(0, m_maxWidth - newWidth) );
+ m_pHScrollBar->setPageStep( newWidth );
+}
+
+void KDiff3App::resizeMergeResultWindow()
+{
+ MergeResultWindow* p = m_pMergeResultWindow;
+ m_pMergeVScrollBar->setRange(0, max2(0, p->getNofLines() - p->getNofVisibleLines()) );
+ m_pMergeVScrollBar->setPageStep( p->getNofVisibleLines() );
+
+ // The second window has a somewhat inverse width
+// m_pHScrollBar->setRange(0, max2(0, m_maxWidth - newWidth) );
+// m_pHScrollBar->setPageStep( newWidth );
+}
+
+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->setChecked( (srcMask & 1) != 0 );
+ chooseB->setChecked( (srcMask & 2) != 0 );
+ chooseC->setChecked( (srcMask & 4) != 0 );
+ 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(max2(s.width(),0),max2(s.height(),0) ) );
+ layout->addWidget( widget );
+}
+
+void KDiff3App::initView()
+{
+ // set the main widget here
+ QValueList<int> oldHeights;
+ if ( m_pDirectoryMergeSplitter->isVisible() )
+ {
+ oldHeights = m_pMainSplitter->sizes();
+ }
+
+ if ( m_pMainWidget != 0 )
+ {
+ return;
+ //delete m_pMainWidget;
+ }
+ m_pMainWidget = new QWidget(m_pMainSplitter);
+
+ QVBoxLayout* pVLayout = new QVBoxLayout(m_pMainWidget,0,0);
+
+ QSplitter* pVSplitter = new QSplitter( m_pMainWidget );
+ pVSplitter->setOrientation( Qt::Vertical );
+ pVLayout->addWidget( pVSplitter );
+
+ QWidget* pDiffWindowFrame = new QWidget( pVSplitter );
+ QHBoxLayout* pDiffHLayout = new QHBoxLayout( pDiffWindowFrame,0,0 );
+
+ m_pDiffWindowSplitter = new QSplitter( pDiffWindowFrame );
+ m_pDiffWindowSplitter->setOrientation( m_pOptionDialog->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical );
+ pDiffHLayout->addWidget( m_pDiffWindowSplitter );
+
+ m_pOverview = new Overview( pDiffWindowFrame, m_pOptionDialog );
+ pDiffHLayout->addWidget(m_pOverview);
+ connect( m_pOverview, SIGNAL(setLine(int)), this, SLOT(setDiff3Line(int)) );
+ //connect( m_pOverview, SIGNAL(afterFirstPaint()), this, SLOT(slotAfterFirstPaint()));
+
+ m_pDiffVScrollBar = new QScrollBar( Qt::Vertical, pDiffWindowFrame );
+ pDiffHLayout->addWidget( m_pDiffVScrollBar );
+
+ m_pDiffTextWindowFrame1 = new DiffTextWindowFrame( m_pDiffWindowSplitter, statusBar(), m_pOptionDialog, 1 );
+ m_pDiffTextWindowFrame2 = new DiffTextWindowFrame( m_pDiffWindowSplitter, statusBar(), m_pOptionDialog, 2 );
+ m_pDiffTextWindowFrame3 = new DiffTextWindowFrame( m_pDiffWindowSplitter, statusBar(), m_pOptionDialog, 3 );
+ m_pDiffTextWindow1 = m_pDiffTextWindowFrame1->getDiffTextWindow();
+ m_pDiffTextWindow2 = m_pDiffTextWindowFrame2->getDiffTextWindow();
+ m_pDiffTextWindow3 = m_pDiffTextWindowFrame3->getDiffTextWindow();
+ connect(m_pDiffTextWindowFrame1, SIGNAL(fileNameChanged(const QString&,int)), this, SLOT(slotFileNameChanged(const QString&,int)));
+ connect(m_pDiffTextWindowFrame2, SIGNAL(fileNameChanged(const QString&,int)), this, SLOT(slotFileNameChanged(const QString&,int)));
+ connect(m_pDiffTextWindowFrame3, SIGNAL(fileNameChanged(const QString&,int)), this, SLOT(slotFileNameChanged(const QString&,int)));
+
+ // Merge window
+ m_pMergeWindowFrame = new QWidget( pVSplitter );
+ QHBoxLayout* pMergeHLayout = new QHBoxLayout( m_pMergeWindowFrame,0,0 );
+
+ QVBoxLayout* pMergeVLayout = new QVBoxLayout();
+ pMergeHLayout->addLayout( pMergeVLayout, 1 );
+
+ m_pMergeResultWindowTitle = new WindowTitleWidget(m_pOptionDialog, m_pMergeWindowFrame);
+ pMergeVLayout->addWidget( m_pMergeResultWindowTitle );
+
+ m_pMergeResultWindow = new MergeResultWindow( m_pMergeWindowFrame, m_pOptionDialog, statusBar() );
+ pMergeVLayout->addWidget( m_pMergeResultWindow, 1 );
+
+ m_pMergeVScrollBar = new QScrollBar( Qt::Vertical, m_pMergeWindowFrame );
+ pMergeHLayout->addWidget( m_pMergeVScrollBar );
+
+ autoAdvance->setEnabled(true);
+
+ QValueList<int> sizes = pVSplitter->sizes();
+ int total = sizes[0] + sizes[1];
+ sizes[0]=total/2; sizes[1]=total/2;
+ pVSplitter->setSizes( sizes );
+
+ m_pMergeResultWindow->installEventFilter( this ); // for Cut/Copy/Paste-shortcuts
+ m_pMergeResultWindow->installEventFilter( m_pMergeResultWindowTitle ); // for focus tracking
+
+ QHBoxLayout* pHScrollBarLayout = new QHBoxLayout( pVLayout );
+ m_pHScrollBar = new ReversibleScrollBar( Qt::Horizontal, m_pMainWidget, &m_pOptionDialog->m_bRightToLeftLanguage );
+ pHScrollBarLayout->addWidget( m_pHScrollBar );
+ m_pCornerWidget = new QWidget( m_pMainWidget );
+ pHScrollBarLayout->addWidget( m_pCornerWidget );
+
+
+ connect( m_pDiffVScrollBar, SIGNAL(valueChanged(int)), m_pOverview, SLOT(setFirstLine(int)));
+ connect( m_pDiffVScrollBar, SIGNAL(valueChanged(int)), m_pDiffTextWindow1, SLOT(setFirstLine(int)));
+ connect( m_pHScrollBar, SIGNAL(valueChanged2(int)), m_pDiffTextWindow1, SLOT(setFirstColumn(int)));
+ connect( m_pDiffTextWindow1, SIGNAL(newSelection()), this, SLOT(slotSelectionStart()));
+ connect( m_pDiffTextWindow1, SIGNAL(selectionEnd()), this, SLOT(slotSelectionEnd()));
+ connect( m_pDiffTextWindow1, SIGNAL(scroll(int,int)), this, SLOT(scrollDiffTextWindow(int,int)));
+ m_pDiffTextWindow1->installEventFilter( this );
+
+ connect( m_pDiffVScrollBar, SIGNAL(valueChanged(int)), m_pDiffTextWindow2, SLOT(setFirstLine(int)));
+ connect( m_pHScrollBar, SIGNAL(valueChanged2(int)), m_pDiffTextWindow2, SLOT(setFirstColumn(int)));
+ connect( m_pDiffTextWindow2, SIGNAL(newSelection()), this, SLOT(slotSelectionStart()));
+ connect( m_pDiffTextWindow2, SIGNAL(selectionEnd()), this, SLOT(slotSelectionEnd()));
+ connect( m_pDiffTextWindow2, SIGNAL(scroll(int,int)), this, SLOT(scrollDiffTextWindow(int,int)));
+ m_pDiffTextWindow2->installEventFilter( this );
+
+ connect( m_pDiffVScrollBar, SIGNAL(valueChanged(int)), m_pDiffTextWindow3, SLOT(setFirstLine(int)));
+ connect( m_pHScrollBar, SIGNAL(valueChanged2(int)), m_pDiffTextWindow3, SLOT(setFirstColumn(int)));
+ connect( m_pDiffTextWindow3, SIGNAL(newSelection()), this, SLOT(slotSelectionStart()));
+ connect( m_pDiffTextWindow3, SIGNAL(selectionEnd()), this, SLOT(slotSelectionEnd()));
+ connect( m_pDiffTextWindow3, SIGNAL(scroll(int,int)), this, SLOT(scrollDiffTextWindow(int,int)));
+ m_pDiffTextWindow3->installEventFilter( this );
+
+
+ MergeResultWindow* p = m_pMergeResultWindow;
+ connect( m_pMergeVScrollBar, SIGNAL(valueChanged(int)), p, SLOT(setFirstLine(int)));
+
+ connect( m_pHScrollBar, SIGNAL(valueChanged2(int)), p, SLOT(setFirstColumn(int)));
+ connect( p, SIGNAL(scroll(int,int)), this, SLOT(scrollMergeResultWindow(int,int)));
+ connect( p, SIGNAL(sourceMask(int,int)), this, SLOT(sourceMask(int,int)));
+ connect( p, SIGNAL( resizeSignal() ),this, SLOT(resizeMergeResultWindow()));
+ connect( p, SIGNAL( selectionEnd() ), this, SLOT( slotSelectionEnd() ) );
+ connect( p, SIGNAL( newSelection() ), this, SLOT( slotSelectionStart() ) );
+ connect( p, SIGNAL( modifiedChanged(bool) ), this, SLOT( slotOutputModified(bool) ) );
+ connect( p, SIGNAL( modifiedChanged(bool) ), m_pMergeResultWindowTitle, SLOT( slotSetModified(bool) ) );
+ connect( p, SIGNAL( updateAvailabilities() ), this, SLOT( slotUpdateAvailabilities() ) );
+ connect( p, SIGNAL( showPopupMenu(const QPoint&) ), this, SLOT(showPopupMenu(const QPoint&)));
+ connect( p, SIGNAL( noRelevantChangesDetected() ), this, SLOT(slotNoRelevantChangesDetected()));
+ sourceMask(0,0);
+
+
+ connect( p, SIGNAL(setFastSelectorRange(int,int)), m_pDiffTextWindow1, SLOT(setFastSelectorRange(int,int)));
+ connect( p, SIGNAL(setFastSelectorRange(int,int)), m_pDiffTextWindow2, SLOT(setFastSelectorRange(int,int)));
+ connect( p, SIGNAL(setFastSelectorRange(int,int)), m_pDiffTextWindow3, SLOT(setFastSelectorRange(int,int)));
+ connect(m_pDiffTextWindow1, SIGNAL(setFastSelectorLine(int)), p, SLOT(slotSetFastSelectorLine(int)));
+ connect(m_pDiffTextWindow2, SIGNAL(setFastSelectorLine(int)), p, SLOT(slotSetFastSelectorLine(int)));
+ connect(m_pDiffTextWindow3, SIGNAL(setFastSelectorLine(int)), p, SLOT(slotSetFastSelectorLine(int)));
+ connect(m_pDiffTextWindow1, SIGNAL(gotFocus()), p, SLOT(updateSourceMask()));
+ connect(m_pDiffTextWindow2, SIGNAL(gotFocus()), p, SLOT(updateSourceMask()));
+ connect(m_pDiffTextWindow3, SIGNAL(gotFocus()), p, SLOT(updateSourceMask()));
+ connect(m_pDirectoryMergeInfo, SIGNAL(gotFocus()), p, SLOT(updateSourceMask()));
+
+ connect( m_pDiffTextWindow1, SIGNAL( resizeSignal(int,int) ),this, SLOT(resizeDiffTextWindow(int,int)));
+ // The following two connects cause the wordwrap to be recalced thrice, just to make sure. Better than forgetting one.
+ connect( m_pDiffTextWindow2, SIGNAL( resizeSignal(int,int) ),this, SLOT(slotRecalcWordWrap()));
+ connect( m_pDiffTextWindow3, SIGNAL( resizeSignal(int,int) ),this, SLOT(slotRecalcWordWrap()));
+
+ m_pDiffTextWindow1->setFocus();
+ m_pMainWidget->setMinimumSize(50,50);
+ 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];
+ }
+ m_pMainSplitter->setSizes( oldHeights );
+ }
+ m_pCornerWidget->setFixedSize( m_pDiffVScrollBar->width(), m_pHScrollBar->height() );
+ //show();
+ m_pMainWidget->show();
+ showWindowA->setChecked( true );
+ showWindowB->setChecked( true );
+ showWindowC->setChecked( true );
+}
+
+static int calcManualDiffFirstDiff3LineIdx( const Diff3LineVector& d3lv, const ManualDiffHelpEntry& mdhe )
+{
+ unsigned 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;
+}
+
+void KDiff3App::slotAfterFirstPaint()
+{
+ int newHeight = m_pDiffTextWindow1->getNofVisibleLines();
+ int newWidth = m_pDiffTextWindow1->getNofVisibleColumns();
+ m_DTWHeight = newHeight;
+
+ recalcWordWrap();
+
+ m_pDiffVScrollBar->setRange(0, max2(0, m_neededLines+1 - newHeight) );
+ m_pDiffVScrollBar->setPageStep( newHeight );
+ m_pOverview->setRange( m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep() );
+
+ // The second window has a somewhat inverse width
+ m_pHScrollBar->setRange(0, max2(0, m_maxWidth - newWidth) );
+ m_pHScrollBar->setPageStep( newWidth );
+
+ 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( max2(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();
+}
+
+void KDiff3App::resizeEvent(QResizeEvent* e)
+{
+ QSplitter::resizeEvent(e);
+ if (m_pCornerWidget)
+ m_pCornerWidget->setFixedSize( m_pDiffVScrollBar->width(), m_pHScrollBar->height() );
+}
+
+
+bool KDiff3App::eventFilter( QObject* o, QEvent* e )
+{
+ if( o == m_pMergeResultWindow )
+ {
+ if ( e->type() == QEvent::KeyPress )
+ { // key press
+ QKeyEvent *k = (QKeyEvent*)e;
+ if (k->key()==Qt::Key_Insert && (k->state() & Qt::ControlButton)!=0 )
+ {
+ slotEditCopy();
+ return true;
+ }
+ if (k->key()==Qt::Key_Insert && (k->state() & Qt::ShiftButton)!=0 )
+ {
+ slotEditPaste();
+ return true;
+ }
+ if (k->key()==Qt::Key_Delete && (k->state() & Qt::ShiftButton)!=0 )
+ {
+ slotEditCut();
+ return true;
+ }
+ }
+ return QSplitter::eventFilter( o, e ); // standard event processing
+ }
+
+ if ( e->type() == QEvent::KeyPress ) // key press
+ {
+ QKeyEvent *k = (QKeyEvent*)e;
+
+ bool bCtrl = (k->state() & Qt::ControlButton) != 0;
+ if (k->key()==Qt::Key_Insert && bCtrl )
+ {
+ slotEditCopy();
+ return true;
+ }
+ if (k->key()==Qt::Key_Insert && (k->state() & Qt::ShiftButton)!=0 )
+ {
+ slotEditPaste();
+ return true;
+ }
+ int deltaX=0;
+ int deltaY=0;
+ int pageSize = m_DTWHeight;
+ switch( k->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->maxValue() );
+ else m_pHScrollBar->setValue( m_pHScrollBar->maxValue() );
+ break;
+ default: break;
+ }
+
+ scrollDiffTextWindow( deltaX, deltaY );
+
+ return true; // eat event
+ }
+ else if (e->type() == QEvent::Wheel ) // wheel event
+ {
+ QWheelEvent *w = (QWheelEvent*)e;
+ w->accept();
+
+ int deltaX=0;
+
+ int d=w->delta();
+ int deltaY = -d/120 * QApplication::wheelScrollLines();
+
+ scrollDiffTextWindow( deltaX, deltaY );
+ return true;
+ }
+ else if (e->type() == QEvent::Drop )
+ {
+ QDropEvent* pDropEvent = static_cast<QDropEvent*>(e);
+ pDropEvent->accept();
+
+ if ( QUriDrag::canDecode(pDropEvent) )
+ {
+#ifdef KREPLACEMENTS_H
+ QStringList stringList;
+ QUriDrag::decodeLocalFiles( pDropEvent, stringList );
+ if ( canContinue() && !stringList.isEmpty() )
+ {
+ raise();
+ QString filename = stringList.first();
+ 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 );
+ init();
+ }
+#else
+ KURL::List urlList;
+ KURLDrag::decode( pDropEvent, urlList );
+ if ( canContinue() && !urlList.isEmpty() )
+ {
+ raise();
+ FileAccess fa( urlList.first().url() );
+ if ( o == m_pDiffTextWindow1 ) m_sd1.setFileAccess( fa );
+ else if ( o == m_pDiffTextWindow2 ) m_sd2.setFileAccess( fa );
+ else if ( o == m_pDiffTextWindow3 ) m_sd3.setFileAccess( fa );
+ init();
+ }
+#endif
+ }
+ else if ( QTextDrag::canDecode(pDropEvent) )
+ {
+ QString text;
+ bool bDecodeSuccess = QTextDrag::decode( pDropEvent, text );
+ if ( bDecodeSuccess && canContinue() )
+ {
+ raise();
+ if ( o == m_pDiffTextWindow1 ) m_sd1.setData(text);
+ else if ( o == m_pDiffTextWindow2 ) m_sd2.setData(text);
+ else if ( o == m_pDiffTextWindow3 ) m_sd3.setData(text);
+ init();
+ }
+ }
+
+ return true;
+ }
+ 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"), i18n("Abort"), i18n("Continue Merging") );
+ if ( result!=KMessageBox::Yes )
+ return;
+ }
+
+
+ slotStatusMsg(i18n("Opening files..."));
+
+ for(;;)
+ {
+
+ OpenDialog d(this,
+ QDir::convertSeparators( m_bDirCompare ? m_pDirectoryMergeWindow->getDirNameA() : m_sd1.isFromBuffer() ? QString("") : m_sd1.getAliasName() ),
+ QDir::convertSeparators( m_bDirCompare ? m_pDirectoryMergeWindow->getDirNameB() : m_sd2.isFromBuffer() ? QString("") : m_sd2.getAliasName() ),
+ QDir::convertSeparators( m_bDirCompare ? m_pDirectoryMergeWindow->getDirNameC() : m_sd3.isFromBuffer() ? QString("") : m_sd3.getAliasName() ),
+ m_bDirCompare ? ! m_pDirectoryMergeWindow->getDirNameDest().isEmpty() : !m_outputFilename.isEmpty(),
+ QDir::convertSeparators( m_bDirCompare ? m_pDirectoryMergeWindow->getDirNameDest() : m_bDefaultFilename ? QString("") : m_outputFilename ),
+ SLOT(slotConfigure()), m_pOptionDialog );
+
+ /*OpenDialog d(this,
+ m_sd1.isFromBuffer() ? QString("") : m_sd1.getAliasName(),
+ m_sd2.isFromBuffer() ? QString("") : m_sd2.getAliasName(),
+ m_sd3.isFromBuffer() ? QString("") : m_sd3.getAliasName(),
+ !m_outputFilename.isEmpty(),
+ m_bDefaultFilename ? QString("") : m_outputFilename,
+ SLOT(slotConfigure()), m_pOptionDialog );*/
+ 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!=0 )
+ {
+ m_pMainWidget->hide();
+ }
+ break;
+ }
+ else
+ {
+ m_pDirectoryMergeSplitter->hide();
+ init();
+
+ 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(QString fn1, QString fn2, QString fn3, QString ofn,
+ QString an1, QString an2, QString an3, TotalDiffStatus* pTotalDiffStatus )
+{
+ if ( !canContinue() ) return;
+
+ if(fn1=="" && fn2=="" && fn3=="" && ofn=="" && m_pMainWidget!=0 )
+ {
+ 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.
+ init( false, pTotalDiffStatus );
+
+ if ( pTotalDiffStatus!=0 )
+ 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!=0 && 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, 0 );
+}
+
+
+void KDiff3App::slotEditCut()
+{
+ slotStatusMsg(i18n("Cutting selection..."));
+
+ QString s;
+ if ( m_pMergeResultWindow!=0 )
+ {
+ s = m_pMergeResultWindow->getSelection();
+ m_pMergeResultWindow->deleteSelection();
+
+ m_pMergeResultWindow->update();
+ }
+
+ if ( !s.isNull() )
+ {
+ QApplication::clipboard()->setText( s, QClipboard::Clipboard );
+ }
+
+ slotStatusMsg(i18n("Ready."));
+}
+
+void KDiff3App::slotEditCopy()
+{
+ slotStatusMsg(i18n("Copying selection to clipboard..."));
+ QString s;
+ if ( m_pDiffTextWindow1!=0 ) s = m_pDiffTextWindow1->getSelection();
+ if ( s.isNull() && m_pDiffTextWindow2!=0 ) s = m_pDiffTextWindow2->getSelection();
+ if ( s.isNull() && m_pDiffTextWindow3!=0 ) s = m_pDiffTextWindow3->getSelection();
+ if ( s.isNull() && m_pMergeResultWindow!=0 ) s = m_pMergeResultWindow->getSelection();
+ if ( !s.isNull() )
+ {
+ QApplication::clipboard()->setText( s, QClipboard::Clipboard );
+ }
+
+ slotStatusMsg(i18n("Ready."));
+}
+
+void KDiff3App::slotEditPaste()
+{
+ slotStatusMsg(i18n("Inserting clipboard contents..."));
+
+ if ( m_pMergeResultWindow!=0 && m_pMergeResultWindow->isVisible() )
+ {
+ m_pMergeResultWindow->pasteClipboard(false);
+ }
+ else if ( canContinue() )
+ {
+ if ( m_pDiffTextWindow1->hasFocus() )
+ {
+ m_sd1.setData( QApplication::clipboard()->text(QClipboard::Clipboard) );
+ init();
+ }
+ else if ( m_pDiffTextWindow2->hasFocus() )
+ {
+ m_sd2.setData( QApplication::clipboard()->text(QClipboard::Clipboard) );
+ init();
+ }
+ else if ( m_pDiffTextWindow3->hasFocus() )
+ {
+ m_sd3.setData( QApplication::clipboard()->text(QClipboard::Clipboard) );
+ init();
+ }
+ }
+
+ slotStatusMsg(i18n("Ready."));
+}
+
+void KDiff3App::slotEditSelectAll()
+{
+ int l=0,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_pOptionDialog->m_autoAdvanceDelay, this, SLOT( 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()
+{
+ int firstLine = -1;
+ int lastLine = -1;
+ DiffTextWindow* pDTW=0;
+ 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()
+{
+ int firstLine = -1;
+ int lastLine = -1;
+ DiffTextWindow* pDTW=0;
+ 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->incInitialSize ( QSize(0,40) );
+ m_pOptionDialog->exec();
+ slotRefresh();
+}
+
+void KDiff3App::slotConfigureKeys()
+{
+ KKeyDialog::configure(actionCollection(), this);
+}
+
+void KDiff3App::slotRefresh()
+{
+ if (m_pDiffTextWindow1!=0)
+ {
+ m_pDiffTextWindow1->setFont(m_pOptionDialog->m_font);
+ m_pDiffTextWindow1->update();
+ }
+ if (m_pDiffTextWindow2!=0)
+ {
+ m_pDiffTextWindow2->setFont(m_pOptionDialog->m_font);
+ m_pDiffTextWindow2->update();
+ }
+ if (m_pDiffTextWindow3!=0)
+ {
+ m_pDiffTextWindow3->setFont(m_pOptionDialog->m_font);
+ m_pDiffTextWindow3->update();
+ }
+ if (m_pMergeResultWindow!=0)
+ {
+ m_pMergeResultWindow->setFont(m_pOptionDialog->m_font);
+ m_pMergeResultWindow->update();
+ }
+ if (m_pHScrollBar!=0)
+ {
+ m_pHScrollBar->setAgain();
+ }
+ if ( m_pDiffWindowSplitter!=0 )
+ {
+ m_pDiffWindowSplitter->setOrientation( m_pOptionDialog->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_pOptionDialog->m_bAutoCopySelection )
+ {
+ slotEditCopy();
+ }
+ else
+ {
+ QClipboard *clipBoard = QApplication::clipboard();
+
+ if (clipBoard->supportsSelection ())
+ {
+ QString s;
+ if ( m_pDiffTextWindow1!=0 ) s = m_pDiffTextWindow1->getSelection();
+ if ( s.isNull() && m_pDiffTextWindow2!=0 ) s = m_pDiffTextWindow2->getSelection();
+ if ( s.isNull() && m_pDiffTextWindow3!=0 ) s = m_pDiffTextWindow3->getSelection();
+ if ( s.isNull() && m_pMergeResultWindow!=0 ) s = m_pMergeResultWindow->getSelection();
+ if ( !s.isNull() )
+ {
+ clipBoard->setText( s, QClipboard::Selection );
+ }
+ }
+ }
+}
+
+void KDiff3App::slotClipboardChanged()
+{
+ QString s = QApplication::clipboard()->text();
+ //editPaste->setEnabled(!s.isEmpty());
+}
+
+void KDiff3App::slotOutputModified(bool bModified)
+{
+ if ( bModified && !m_bOutputModified )
+ {
+ m_bOutputModified=true;
+ slotUpdateAvailabilities();
+ }
+}
+
+void KDiff3App::slotAutoAdvanceToggled()
+{
+ m_pOptionDialog->m_bAutoAdvance = autoAdvance->isChecked();
+}
+
+void KDiff3App::slotWordWrapToggled()
+{
+ m_pOptionDialog->m_bWordWrap = wordWrap->isChecked();
+ recalcWordWrap();
+}
+
+void KDiff3App::slotRecalcWordWrap()
+{
+ recalcWordWrap();
+}
+
+void KDiff3App::recalcWordWrap(int nofVisibleColumns) // nofVisibleColumns is >=0 only for printing, otherwise the really visible width is used
+{
+ bool bPrinting = nofVisibleColumns>=0;
+ int firstD3LIdx = 0;
+ if( m_pDiffTextWindow1 )
+ 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();
+
+
+ if ( !m_diff3LineList.empty() && m_pOptionDialog->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,nofVisibleColumns);
+ if ( m_pDiffTextWindow2 )
+ m_pDiffTextWindow2->recalcWordWrap(true,0,nofVisibleColumns);
+ if ( m_pDiffTextWindow3 )
+ m_pDiffTextWindow3->recalcWordWrap(true,0,nofVisibleColumns);
+
+ sumOfLines=0;
+ for ( i=m_diff3LineList.begin(); i!=m_diff3LineList.end(); ++i )
+ {
+ Diff3Line& d3l = *i;
+ d3l.sumLinesNeededForDisplay = sumOfLines;
+ sumOfLines += d3l.linesNeededForDisplay;
+ }
+
+ // Finish the initialisation:
+ if ( m_pDiffTextWindow1 )
+ m_pDiffTextWindow1->recalcWordWrap(true,sumOfLines,nofVisibleColumns);
+ if ( m_pDiffTextWindow2 )
+ m_pDiffTextWindow2->recalcWordWrap(true,sumOfLines,nofVisibleColumns);
+ if ( m_pDiffTextWindow3 )
+ m_pDiffTextWindow3->recalcWordWrap(true,sumOfLines,nofVisibleColumns);
+
+ m_neededLines = sumOfLines;
+ }
+ 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);
+ }
+ if (bPrinting)
+ return;
+
+ m_pOverview->slotRedraw();
+ if ( m_pDiffTextWindow1 )
+ {
+ m_pDiffTextWindow1->setFirstLine( m_pDiffTextWindow1->convertDiff3LineIdxToLine( firstD3LIdx ) );
+ m_pDiffTextWindow1->update();
+ }
+ if ( m_pDiffTextWindow2 )
+ {
+ m_pDiffTextWindow2->setFirstLine( m_pDiffTextWindow2->convertDiff3LineIdxToLine( firstD3LIdx ) );
+ m_pDiffTextWindow2->update();
+ }
+ if ( m_pDiffTextWindow3 )
+ {
+ m_pDiffTextWindow3->setFirstLine( m_pDiffTextWindow3->convertDiff3LineIdxToLine( firstD3LIdx ) );
+ m_pDiffTextWindow3->update();
+ }
+
+ m_pDiffVScrollBar->setRange(0, max2(0, m_neededLines+1 - m_DTWHeight) );
+ if ( m_pDiffTextWindow1 )
+ {
+ m_pDiffVScrollBar->setValue( m_pDiffTextWindow1->convertDiff3LineIdxToLine( firstD3LIdx ) );
+
+ m_maxWidth = max3( m_pDiffTextWindow1->getNofColumns(),
+ m_pDiffTextWindow2->getNofColumns(),
+ m_pDiffTextWindow3->getNofColumns() ) + (m_pOptionDialog->m_bWordWrap ? 0 : 5);
+
+ m_pHScrollBar->setRange(0, max2( 0, m_maxWidth - m_pDiffTextWindow1->getNofVisibleColumns() ) );
+ m_pHScrollBar->setPageStep( m_pDiffTextWindow1->getNofVisibleColumns() );
+ m_pHScrollBar->setValue(0);
+ }
+}
+
+void KDiff3App::slotShowWhiteSpaceToggled()
+{
+ m_pOptionDialog->m_bShowWhiteSpaceCharacters = showWhiteSpaceCharacters->isChecked();
+ m_pOptionDialog->m_bShowWhiteSpace = showWhiteSpace->isChecked();
+ showWhiteSpaceCharacters->setEnabled( showWhiteSpace->isChecked() );
+ if ( m_pDiffTextWindow1!=0 )
+ m_pDiffTextWindow1->update();
+ if ( m_pDiffTextWindow2!=0 )
+ m_pDiffTextWindow2->update();
+ if ( m_pDiffTextWindow3!=0 )
+ m_pDiffTextWindow3->update();
+ if ( m_pOverview!=0 )
+ m_pOverview->slotRedraw();
+}
+
+void KDiff3App::slotShowLineNumbersToggled()
+{
+ m_pOptionDialog->m_bShowLineNumbers = showLineNumbers->isChecked();
+ if ( m_pDiffTextWindow1!=0 )
+ m_pDiffTextWindow1->update();
+ if ( m_pDiffTextWindow2!=0 )
+ m_pDiffTextWindow2->update();
+ if ( m_pDiffTextWindow3!=0 )
+ 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.absFilePath();
+ }
+ }
+ else if ( f1.isDir() )
+ {
+ m_bDirCompare = true;
+ if (bCreateNewInstance)
+ {
+ emit createNewInstance( f1.absFilePath(), f2.absFilePath(), f3.absFilePath() );
+ }
+ else
+ {
+ FileAccess destDir;
+ if (!m_bDefaultFilename) destDir = f4;
+ m_pDirectoryMergeSplitter->show();
+ if (m_pMainWidget!=0) m_pMainWidget->hide();
+
+ bool bSuccess = m_pDirectoryMergeWindow->init(
+ f1, f2, f3,
+ destDir, // Destdirname
+ !m_outputFilename.isEmpty()
+ );
+
+ m_bDirCompare = true; // This seems redundant but it might have been reset during full analysis.
+
+ if (bSuccess)
+ {
+ m_sd1.reset();
+ if (m_pDiffTextWindow1!=0) m_pDiffTextWindow1->init(0,0,0,0,0,false);
+ m_sd2.reset();
+ if (m_pDiffTextWindow2!=0) m_pDiffTextWindow2->init(0,0,0,0,0,false);
+ m_sd3.reset();
+ if (m_pDiffTextWindow3!=0) m_pDiffTextWindow3->init(0,0,0,0,0,false);
+ }
+ slotUpdateAvailabilities();
+ return bSuccess;
+ }
+ }
+ return true;
+}
+
+void KDiff3App::slotReload()
+{
+ if ( !canContinue() ) return;
+
+ init();
+}
+
+bool KDiff3App::canContinue()
+{
+ // First test if anything must be saved.
+ if(m_bOutputModified)
+ {
+ int result = KMessageBox::warningYesNoCancel(this,
+ i18n("The merge result hasn't been saved."),
+ i18n("Warning"), i18n("Save && Continue"), 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!=0) *pbContinue = canContinue();
+}
+
+
+void KDiff3App::slotDirShowBoth()
+{
+ if( dirShowBoth->isChecked() )
+ {
+ if ( m_bDirCompare )
+ m_pDirectoryMergeSplitter->show();
+ else
+ m_pDirectoryMergeSplitter->hide();
+
+ if ( m_pMainWidget!=0 )
+ m_pMainWidget->show();
+ }
+ else
+ {
+ if ( m_pMainWidget!=0 )
+ {
+ 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!=0)
+ m_pMainWidget->hide();
+ }
+ else
+ {
+ if (m_pMainWidget!=0)
+ {
+ m_pDirectoryMergeSplitter->hide();
+ m_pMainWidget->show();
+ }
+ }
+ }
+ slotUpdateAvailabilities();
+}
+
+void KDiff3App::slotShowWindowAToggled()
+{
+ if ( m_pDiffTextWindow1!=0 )
+ {
+ if ( showWindowA->isChecked() ) m_pDiffTextWindowFrame1->show();
+ else m_pDiffTextWindowFrame1->hide();
+ slotUpdateAvailabilities();
+ }
+}
+
+void KDiff3App::slotShowWindowBToggled()
+{
+ if ( m_pDiffTextWindow2!=0 )
+ {
+ if ( showWindowB->isChecked() ) m_pDiffTextWindowFrame2->show();
+ else m_pDiffTextWindowFrame2->hide();
+ slotUpdateAvailabilities();
+ }
+}
+
+void KDiff3App::slotShowWindowCToggled()
+{
+ if ( m_pDiffTextWindow3!=0 )
+ {
+ if ( showWindowC->isChecked() ) m_pDiffTextWindowFrame3->show();
+ else m_pDiffTextWindowFrame3->hide();
+ slotUpdateAvailabilities();
+ }
+}
+
+void KDiff3App::slotEditFind()
+{
+ m_pFindDialog->currentLine = 0;
+ m_pFindDialog->currentPos = 0;
+ m_pFindDialog->currentWindow = 1;
+
+ 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();
+
+ int d3vLine = m_pFindDialog->currentLine;
+ int posInLine = m_pFindDialog->currentPos;
+ int l=0;
+ int p=0;
+ if ( m_pFindDialog->currentWindow == 1 )
+ {
+ if ( m_pFindDialog->m_pSearchInA->isChecked() && m_pDiffTextWindow1!=0 &&
+ 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( max2( 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!=0 &&
+ 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( max2( 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!=0 &&
+ 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( max2( 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!=0 && 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( max2( 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 != 0 && 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;
+ }
+ }
+ init();
+ }
+}
+
+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!=0 )
+ {
+ m_pDiffWindowSplitter->setOrientation(
+ m_pDiffWindowSplitter->orientation()==Qt::Vertical ? Qt::Horizontal : Qt::Vertical
+ );
+
+ m_pOptionDialog->m_bHorizDiffWindowSplitting = m_pDiffWindowSplitter->orientation()==Qt::Horizontal;
+ }
+}
+
+void KDiff3App::slotOverviewNormal()
+{
+ m_pOverview->setOverviewMode( Overview::eOMNormal );
+ m_pMergeResultWindow->setOverviewMode( Overview::eOMNormal );
+ slotUpdateAvailabilities();
+}
+
+void KDiff3App::slotOverviewAB()
+{
+ m_pOverview->setOverviewMode( Overview::eOMAvsB );
+ m_pMergeResultWindow->setOverviewMode( Overview::eOMAvsB );
+ slotUpdateAvailabilities();
+}
+
+void KDiff3App::slotOverviewAC()
+{
+ m_pOverview->setOverviewMode( Overview::eOMAvsC );
+ m_pMergeResultWindow->setOverviewMode( Overview::eOMAvsC );
+ slotUpdateAvailabilities();
+}
+
+void KDiff3App::slotOverviewBC()
+{
+ m_pOverview->setOverviewMode( Overview::eOMBvsC );
+ 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_pOptionDialog->m_IrrelevantMergeCmd.isEmpty())
+ {
+ QString cmd = m_pOptionDialog->m_IrrelevantMergeCmd + " \"" + m_sd1.getAliasName()+ "\" \"" + m_sd2.getAliasName() + "\" \"" + m_sd3.getAliasName();
+ ::system( cmd.local8Bit() );
+ }
+ }
+}
+
+static void insertManualDiffHelp( ManualDiffHelpList* pManualDiffHelpList, int winIdx, int firstLine, int 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()
+{
+ int firstLine = -1;
+ int 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
+ {
+ /*
+ ManualDiffHelpEntry mdhe;
+ if (!m_manualDiffHelpList.empty()) mdhe = m_manualDiffHelpList.front();
+ if ( winIdx==1 ) { mdhe.lineA1 = firstLine; mdhe.lineA2 = lastLine; }
+ if ( winIdx==2 ) { mdhe.lineB1 = firstLine; mdhe.lineB2 = lastLine; }
+ if ( winIdx==3 ) { mdhe.lineC1 = firstLine; mdhe.lineC2 = lastLine; }
+ m_manualDiffHelpList.clear();
+ m_manualDiffHelpList.push_back( mdhe );
+ */
+
+ insertManualDiffHelp( &m_manualDiffHelpList, winIdx, firstLine, lastLine );
+
+ init( false, 0, false ); // Init without reload
+ slotRefresh();
+ }
+}
+
+void KDiff3App::slotClearManualDiffHelpList()
+{
+ m_manualDiffHelpList.clear();
+ init( false, 0, false ); // Init without reload
+ slotRefresh();
+}
+
+void KDiff3App::slotUpdateAvailabilities()
+{
+ bool bTextDataAvailable = ( m_sd1.hasData() || m_sd2.hasData() || m_sd3.hasData() );
+
+ if( dirShowBoth->isChecked() )
+ {
+ if ( m_bDirCompare )
+ m_pDirectoryMergeSplitter->show();
+ else
+ m_pDirectoryMergeSplitter->hide();
+
+ if ( m_pMainWidget!=0 && !m_pMainWidget->isVisible() &&
+ bTextDataAvailable && !m_pDirectoryMergeWindow->isScanning()
+ )
+ m_pMainWidget->show();
+ }
+
+
+ bool bDiffWindowVisible = m_pMainWidget != 0 && m_pMainWidget->isVisible();
+ bool bMergeEditorVisible = m_pMergeWindowFrame !=0 && m_pMergeWindowFrame->isVisible();
+
+ m_pDirectoryMergeWindow->updateAvailabilities( m_bDirCompare, bDiffWindowVisible, chooseA, chooseB, chooseC );
+
+ dirShowBoth->setEnabled( m_bDirCompare );
+ dirViewToggle->setEnabled(
+ m_bDirCompare &&
+ (!m_pDirectoryMergeSplitter->isVisible() && m_pMainWidget!=0 && m_pMainWidget->isVisible() ||
+ m_pDirectoryMergeSplitter->isVisible() && m_pMainWidget!=0 && !m_pMainWidget->isVisible() && bTextDataAvailable )
+ );
+
+ bool bDirWindowHasFocus = 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==0 ? 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!=0 );
+}
diff --git a/src/smalldialogs.cpp b/src/smalldialogs.cpp
new file mode 100644
index 0000000..d748611
--- /dev/null
+++ b/src/smalldialogs.cpp
@@ -0,0 +1,579 @@
+/***************************************************************************
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#include "smalldialogs.h"
+#include "optiondialog.h"
+
+#include <qcombobox.h>
+#include <qcheckbox.h>
+#include <qlineedit.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qdragobject.h>
+#include <qregexp.h>
+#include <qtooltip.h>
+#include <qpopupmenu.h>
+#include <qcursor.h>
+
+
+#include <kfiledialog.h>
+#include <klocale.h>
+
+// OpenDialog **************************************************************
+
+OpenDialog::OpenDialog(
+ QWidget* pParent, const QString& n1, const QString& n2, const QString& n3,
+ bool bMerge, const QString& outputName, const char* slotConfigure, OptionDialog* pOptions )
+: QDialog( pParent, "OpenDialog", true /*modal*/ )
+{
+ m_pOptions = pOptions;
+
+ QVBoxLayout* v = new QVBoxLayout( this, 5 );
+ QGridLayout* h = new QGridLayout( v, 5, 4, 5 );
+ h->setColStretch( 1, 10 );
+
+ QLabel* label = new QLabel( i18n("A (Base):"), this );
+
+ m_pLineA = new QComboBox( true, this );
+ m_pLineA->insertStringList( m_pOptions->m_recentAFiles );
+ m_pLineA->setEditText( KURL(n1).prettyURL() );
+ m_pLineA->setMinimumSize( 200, m_pLineA->size().height() );
+ QPushButton * button = new QPushButton( i18n("File..."), this );
+ connect( button, SIGNAL(clicked()), this, SLOT( selectFileA() ) );
+ QPushButton * button2 = new QPushButton( i18n("Dir..."), this );
+ connect( button2, SIGNAL(clicked()), this, SLOT( selectDirA() ) );
+ connect( m_pLineA, SIGNAL(textChanged(const QString&)), this, SLOT(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( true, this );
+ m_pLineB->insertStringList( m_pOptions->m_recentBFiles );
+ m_pLineB->setEditText( KURL(n2).prettyURL() );
+ m_pLineB->setMinimumSize( 200, m_pLineB->size().height() );
+ button = new QPushButton( i18n("File..."), this );
+ connect( button, SIGNAL(clicked()), this, SLOT( selectFileB() ) );
+ button2 = new QPushButton( i18n("Dir..."), this );
+ connect( button2, SIGNAL(clicked()), this, SLOT( selectDirB() ) );
+ connect( m_pLineB, SIGNAL(textChanged(const QString&)), this, SLOT(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( true, this );
+ m_pLineC->insertStringList( m_pOptions->m_recentCFiles );
+ m_pLineC->setEditText( KURL(n3).prettyURL() );
+ m_pLineC->setMinimumSize( 200, m_pLineC->size().height() );
+ button = new QPushButton( i18n("File..."), this );
+ connect( button, SIGNAL(clicked()), this, SLOT( selectFileC() ) );
+ button2 = new QPushButton( i18n("Dir..."), this );
+ connect( button2, SIGNAL(clicked()), this, SLOT( selectDirC() ) );
+ connect( m_pLineC, SIGNAL(textChanged(const QString&)), this, SLOT(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 );
+
+ QPopupMenu* m = new QPopupMenu(this);
+ int id=0;
+ m->insertItem( i18n("Swap %1<->%2").arg("A").arg("B"), id++ );
+ m->insertItem( i18n("Swap %1<->%2").arg("B").arg("C"), id++ );
+ m->insertItem( i18n("Swap %1<->%2").arg("C").arg("A"), id++ );
+ m->insertItem( i18n("Copy %1->Output").arg("A"), id++ );
+ m->insertItem( i18n("Copy %1->Output").arg("B"), id++ );
+ m->insertItem( i18n("Copy %1->Output").arg("C"), id++ );
+ m->insertItem( i18n("Swap %1<->Output").arg("A"), id++ );
+ m->insertItem( i18n("Swap %1<->Output").arg("B"), id++ );
+ m->insertItem( i18n("Swap %1<->Output").arg("C"), id++ );
+ connect( m, SIGNAL(activated(int)), this, SLOT(slotSwapCopyNames(int)));
+ button->setPopup(m);
+
+
+ hl->addStretch(2);
+
+ label = new QLabel( i18n("Output (optional):"), this );
+ m_pLineOut = new QComboBox( true, this );
+ m_pLineOut->insertStringList( m_pOptions->m_recentOutputFiles );
+ m_pLineOut->setEditText( KURL(outputName).prettyURL() );
+ m_pLineOut->setMinimumSize( 200, m_pLineOut->size().height() );
+ button = new QPushButton( i18n("File..."), this );
+ connect( button, SIGNAL(clicked()), this, SLOT( selectOutputName() ) );
+ button2 = new QPushButton( i18n("Dir..."), this );
+ connect( button2, SIGNAL(clicked()), this, SLOT( selectOutputDir() ) );
+ connect( m_pMerge, SIGNAL(stateChanged(int)), this, SLOT(internalSlot(int)) );
+ connect( this, SIGNAL(internalSignal(bool)), m_pLineOut, SLOT(setEnabled(bool)) );
+ connect( this, SIGNAL(internalSignal(bool)), button, SLOT(setEnabled(bool)) );
+ connect( this, SIGNAL(internalSignal(bool)), button2, SLOT(setEnabled(bool)) );
+
+ 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->addColSpacing( 1, 200 );
+
+ QHBoxLayout* l = new QHBoxLayout( v, 5 );
+
+ button = new QPushButton( i18n("Configure..."), this );
+ connect( button, SIGNAL(clicked()), pParent, slotConfigure );
+ l->addWidget( button, 1 );
+
+ l->addStretch(1);
+
+ button = new QPushButton( i18n("&OK"), this );
+ button->setDefault( true );
+ connect( button, SIGNAL(clicked()), this, SLOT( accept() ) );
+ l->addWidget( button, 1 );
+
+ button = new QPushButton( i18n("&Cancel"), this );
+ connect( button, SIGNAL(clicked()), this, SLOT( reject() ) );
+ l->addWidget( button,1 );
+
+ QSize sh = sizeHint();
+ setFixedHeight( sh.height() );
+ m_bInputFileNameChanged = false;
+
+#ifdef KREPLACEMENTS_H
+ 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::Drop)
+ {
+ QDropEvent* d = static_cast<QDropEvent*>(e);
+
+ if ( !QUriDrag::canDecode( d ) ) {
+ return false;
+ }
+
+ QStringList lst;
+ QUriDrag::decodeLocalFiles( d, lst );
+
+ if ( lst.count() > 0 )
+ {
+ static_cast<QLineEdit*>(o)->setText( lst[0] );
+ static_cast<QLineEdit*>(o)->setFocus();
+ }
+
+ return true;
+ }
+ return false;
+}
+
+
+void OpenDialog::selectURL( QComboBox* pLine, bool bDir, int i, bool bSave )
+{
+ QString current = pLine->currentText();
+ if (current.isEmpty() && i>3 ){ current = m_pLineC->currentText(); }
+ if (current.isEmpty() ){ current = m_pLineB->currentText(); }
+ if (current.isEmpty() ){ current = m_pLineA->currentText(); }
+ KURL newURL = bDir ? KFileDialog::getExistingURL( current, this)
+ : bSave ? KFileDialog::getSaveURL( current, 0, this)
+ : KFileDialog::getOpenURL( current, 0, this);
+ if ( !newURL.isEmpty() )
+ {
+ 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 accidently overwrote it with
+// wrong data during a merge.
+void OpenDialog::inputFilenameChanged()
+{
+ if(!m_bInputFileNameChanged)
+ {
+ m_bInputFileNameChanged=true;
+ m_pLineOut->clearEdit();
+ }
+}
+
+static void fixCurrentText( QComboBox* pCB )
+{
+ QString s = pCB->currentText();
+
+ int pos = s.find( '\n' );
+ if ( pos>=0 )
+ s=s.left(pos);
+ pos = s.find( '\r' );
+ if ( pos>=0 )
+ s=s.left(pos);
+
+ pCB->setCurrentText( s );
+}
+
+void OpenDialog::accept()
+{
+ unsigned int maxNofRecentFiles = 10;
+
+ fixCurrentText( m_pLineA );
+ QString s = m_pLineA->currentText();
+ s = KURL::fromPathOrURL(s).prettyURL();
+ QStringList* sl = &m_pOptions->m_recentAFiles;
+ // If an item exist, remove it from the list and reinsert it at the beginning.
+ sl->remove(s);
+ if ( !s.isEmpty() ) sl->prepend( s );
+ if (sl->count()>maxNofRecentFiles) sl->erase( sl->at(maxNofRecentFiles), sl->end() );
+
+ fixCurrentText( m_pLineB );
+ s = m_pLineB->currentText();
+ s = KURL::fromPathOrURL(s).prettyURL();
+ sl = &m_pOptions->m_recentBFiles;
+ sl->remove(s);
+ if ( !s.isEmpty() ) sl->prepend( s );
+ if (sl->count()>maxNofRecentFiles) sl->erase( sl->at(maxNofRecentFiles), sl->end() );
+
+ fixCurrentText( m_pLineC );
+ s = m_pLineC->currentText();
+ s = KURL::fromPathOrURL(s).prettyURL();
+ sl = &m_pOptions->m_recentCFiles;
+ sl->remove(s);
+ if ( !s.isEmpty() ) sl->prepend( s );
+ if (sl->count()>maxNofRecentFiles) sl->erase( sl->at(maxNofRecentFiles), sl->end() );
+
+ fixCurrentText( m_pLineOut );
+ s = m_pLineOut->currentText();
+ s = KURL::fromPathOrURL(s).prettyURL();
+ sl = &m_pOptions->m_recentOutputFiles;
+ sl->remove(s);
+ if ( !s.isEmpty() ) sl->prepend( s );
+ if (sl->count()>maxNofRecentFiles) sl->erase( sl->at(maxNofRecentFiles), sl->end() );
+
+ QDialog::accept();
+}
+
+void OpenDialog::slotSwapCopyNames( int id ) // id selected in the popup menu
+{
+ QComboBox* cb1=0;
+ QComboBox* cb2=0;
+ 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->setCurrentText(t1);
+ if ( id<=2 || id>=6 )
+ {
+ cb1->setCurrentText( t2 );
+ }
+ }
+}
+
+// FindDialog *********************************************
+
+FindDialog::FindDialog(QWidget* pParent)
+: QDialog( pParent )
+{
+ QGridLayout* layout = new QGridLayout( this );
+ layout->setMargin(5);
+ layout->setSpacing(5);
+
+ int line=0;
+ layout->addMultiCellWidget( new QLabel(i18n("Search text:"),this), line,line,0,1 );
+ ++line;
+
+ m_pSearchString = new QLineEdit( this );
+ layout->addMultiCellWidget( m_pSearchString, line,line,0,1 );
+ ++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;
+
+ QPushButton* pButton = new QPushButton( i18n("&Search"), this );
+ layout->addWidget( pButton, line, 0 );
+ connect( pButton, SIGNAL(clicked()), this, SLOT(accept()));
+
+ pButton = new QPushButton( i18n("&Cancel"), this );
+ layout->addWidget( pButton, line, 1 );
+ connect( pButton, SIGNAL(clicked()), this, SLOT(reject()));
+
+ hide();
+}
+
+
+RegExpTester::RegExpTester( QWidget* pParent, const QString& autoMergeRegExpToolTip,
+ const QString& historyStartRegExpToolTip, const QString& historyEntryStartRegExpToolTip, const QString& historySortKeyOrderToolTip )
+: QDialog( pParent)
+{
+ int line=0;
+ setCaption(i18n("Regular Expression Tester"));
+ QGridLayout* pGrid = new QGridLayout( this, 11, 2, 5, 5 );
+
+ QLabel* l = new QLabel(i18n("Auto merge regular expression:"), this);
+ pGrid->addWidget(l,line,0);
+ QToolTip::add( l, autoMergeRegExpToolTip );
+ m_pAutoMergeRegExpEdit = new QLineEdit(this);
+ pGrid->addWidget(m_pAutoMergeRegExpEdit,line,1);
+ connect( m_pAutoMergeRegExpEdit, SIGNAL(textChanged(const QString&)), this, SLOT(slotRecalc()));
+ ++line;
+
+ l = new QLabel(i18n("Example auto merge line:"), this);
+ pGrid->addMultiCellWidget(l,line,line,0,1);
+ QToolTip::add( l, i18n("For auto merge test copy a line as used in your files.") );
+ m_pAutoMergeExampleEdit = new QLineEdit(this);
+ pGrid->addWidget(m_pAutoMergeExampleEdit,line,1);
+ connect( m_pAutoMergeExampleEdit, SIGNAL(textChanged(const QString&)), this, SLOT(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);
+ QToolTip::add( l, historyStartRegExpToolTip );
+ m_pHistoryStartRegExpEdit = new QLineEdit(this);
+ pGrid->addWidget(m_pHistoryStartRegExpEdit,line,1);
+ connect( m_pHistoryStartRegExpEdit, SIGNAL(textChanged(const QString&)), this, SLOT(slotRecalc()));
+ ++line;
+
+ l = new QLabel(i18n("Example history start line (with leading comment):"), this);
+ pGrid->addMultiCellWidget(l,line,line,0,1);
+ ++line;
+ QToolTip::add( l, 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, SIGNAL(textChanged(const QString&)), this, SLOT(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);
+ QToolTip::add( l, historyEntryStartRegExpToolTip );
+ m_pHistoryEntryStartRegExpEdit = new QLineEdit(this);
+ pGrid->addWidget(m_pHistoryEntryStartRegExpEdit,line,1);
+ connect( m_pHistoryEntryStartRegExpEdit, SIGNAL(textChanged(const QString&)), this, SLOT(slotRecalc()));
+ ++line;
+
+ l = new QLabel(i18n("History sort key order:"), this);
+ pGrid->addWidget(l,line,0);
+ QToolTip::add( l, historySortKeyOrderToolTip );
+ m_pHistorySortKeyOrderEdit = new QLineEdit(this);
+ pGrid->addWidget(m_pHistorySortKeyOrderEdit,line,1);
+ connect( m_pHistorySortKeyOrderEdit, SIGNAL(textChanged(const QString&)), this, SLOT(slotRecalc()));
+ ++line;
+
+ l = new QLabel(i18n("Example history entry start line (without leading comment):"), this);
+ pGrid->addMultiCellWidget(l,line,line,0,1);
+ QToolTip::add( l, i18n("Copy a history entry start line as used in your files,\n"
+ "but omit the leading comment.") );
+ ++line;
+ m_pHistoryEntryStartExampleEdit = new QLineEdit(this);
+ pGrid->addWidget(m_pHistoryEntryStartExampleEdit,line,1);
+ connect( m_pHistoryEntryStartExampleEdit, SIGNAL(textChanged(const QString&)), this, SLOT(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;
+
+ QPushButton* pButton = new QPushButton(i18n("OK"), this);
+ pGrid->addWidget(pButton,line,0);
+ connect( pButton, SIGNAL(clicked()), this, SLOT(accept()));
+
+ pButton = new QPushButton(i18n("Cancel"), this);
+ pGrid->addWidget(pButton,line,1);
+ connect( pButton, SIGNAL(clicked()), this, SLOT(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 don't match in regular expression.") );
+ m_pHistorySortKeyResult->setText( i18n("") );
+ 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( i18n("") );
+ }
+}
+
+#include "smalldialogs.moc"
diff --git a/src/smalldialogs.h b/src/smalldialogs.h
new file mode 100644
index 0000000..5791b13
--- /dev/null
+++ b/src/smalldialogs.h
@@ -0,0 +1,120 @@
+/***************************************************************************
+ * 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#ifndef SMALLDIALOGS_H
+#define SMALLDIALOGS_H
+
+#include <qdialog.h>
+#include "diff.h"
+
+class OptionDialog;
+class QComboBox;
+class QCheckBox;
+class QLineEdit;
+class QLabel;
+
+class OpenDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ OpenDialog(
+ QWidget* pParent, const QString& n1, const QString& n2, const QString& n3,
+ bool bMerge, const QString& outputName, const char* slotConfigure, OptionDialog* pOptions );
+
+ QComboBox* m_pLineA;
+ QComboBox* m_pLineB;
+ QComboBox* m_pLineC;
+ QComboBox* m_pLineOut;
+
+ QCheckBox* m_pMerge;
+ virtual void accept();
+ virtual bool eventFilter(QObject* o, QEvent* e);
+private:
+ OptionDialog* m_pOptions;
+ void selectURL( QComboBox* pLine, bool bDir, int i, bool bSave );
+ bool m_bInputFileNameChanged;
+private slots:
+ void selectFileA();
+ void selectFileB();
+ void selectFileC();
+ void selectDirA();
+ void selectDirB();
+ void selectDirC();
+ void selectOutputName();
+ void selectOutputDir();
+ void internalSlot(int);
+ void inputFilenameChanged();
+ void slotSwapCopyNames(int);
+signals:
+ void internalSignal(bool);
+};
+
+class FindDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ FindDialog(QWidget* pParent);
+
+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;
+ OptionDialog* m_pOptionDialog;
+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 slots:
+ void slotRecalc();
+};
+
+#endif
diff --git a/src/version.h b/src/version.h
new file mode 100644
index 0000000..79c3154
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,2 @@
+#undef VERSION
+#define VERSION "0.9.92"
diff --git a/src/xpm/autoadvance.xpm b/src/xpm/autoadvance.xpm
new file mode 100644
index 0000000..d499999
--- /dev/null
+++ b/src/xpm/autoadvance.xpm
@@ -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
index 0000000..c027e6c
--- /dev/null
+++ b/src/xpm/currentpos.xpm
@@ -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
index 0000000..162b692
--- /dev/null
+++ b/src/xpm/down1arrow.xpm
@@ -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
index 0000000..6f34208
--- /dev/null
+++ b/src/xpm/down2arrow.xpm
@@ -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
index 0000000..214bc8b
--- /dev/null
+++ b/src/xpm/downend.xpm
@@ -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
index 0000000..faf1472
--- /dev/null
+++ b/src/xpm/file.xpm
@@ -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
index 0000000..2543c9b
--- /dev/null
+++ b/src/xpm/filenew.xpm
@@ -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
index 0000000..880417e
--- /dev/null
+++ b/src/xpm/fileopen.xpm
@@ -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
index 0000000..6ada912
--- /dev/null
+++ b/src/xpm/fileprint.xpm
@@ -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
index 0000000..ed3ea96
--- /dev/null
+++ b/src/xpm/filesave.xpm
@@ -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
index 0000000..7b2edcd
--- /dev/null
+++ b/src/xpm/folder.xpm
@@ -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
index 0000000..4e44f9f
--- /dev/null
+++ b/src/xpm/iconA.xpm
@@ -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
index 0000000..9405ee8
--- /dev/null
+++ b/src/xpm/iconB.xpm
@@ -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
index 0000000..56b7315
--- /dev/null
+++ b/src/xpm/iconC.xpm
@@ -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
index 0000000..2ab91e8
--- /dev/null
+++ b/src/xpm/link_arrow.xpm
@@ -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
index 0000000..0775687
--- /dev/null
+++ b/src/xpm/nextunsolved.xpm
@@ -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
index 0000000..d8d175c
--- /dev/null
+++ b/src/xpm/prevunsolved.xpm
@@ -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
index 0000000..d54fec3
--- /dev/null
+++ b/src/xpm/reload.xpm
@@ -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
index 0000000..9fa2e3b
--- /dev/null
+++ b/src/xpm/showequalfiles.xpm
@@ -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
index 0000000..041b54d
--- /dev/null
+++ b/src/xpm/showfilesonlyina.xpm
@@ -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
index 0000000..80caaca
--- /dev/null
+++ b/src/xpm/showfilesonlyinb.xpm
@@ -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
index 0000000..5f548a4
--- /dev/null
+++ b/src/xpm/showfilesonlyinc.xpm
@@ -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
index 0000000..fb697dc
--- /dev/null
+++ b/src/xpm/showlinenumbers.xpm
@@ -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
index 0000000..2112e91
--- /dev/null
+++ b/src/xpm/showwhitespace.xpm
@@ -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
index 0000000..0a637ae
--- /dev/null
+++ b/src/xpm/showwhitespacechars.xpm
@@ -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
index 0000000..7162719
--- /dev/null
+++ b/src/xpm/startmerge.xpm
@@ -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
index 0000000..3e144ba
--- /dev/null
+++ b/src/xpm/up1arrow.xpm
@@ -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
index 0000000..ebe933b
--- /dev/null
+++ b/src/xpm/up2arrow.xpm
@@ -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
index 0000000..167433d
--- /dev/null
+++ b/src/xpm/upend.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static const char *upend[]={
+"16 16 3 1",
+" c #0080ff",
+"# c #000000",
+". c None",
+"................",
+"................",
+"................",
+"................",
+"..############..",
+".......##.......",
+"......# #......",
+".....# #.....",
+"....# #....",
+"...# #...",
+"..############..",
+"................",
+"................",
+"................",
+"................",
+"................"};