diff options
Diffstat (limited to 'src')
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( ¶m1[0] ); + if ( retVal == 0 ) + { + std::string param2( g_stringsize, ' ' ); + retVal = popstring( ¶m2[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 Binary files differnew file mode 100644 index 0000000..50e3397 --- /dev/null +++ b/src/hi16-app-kdiff3.png diff --git a/src/hi32-app-kdiff3.png b/src/hi32-app-kdiff3.png Binary files differnew file mode 100644 index 0000000..cd269b2 --- /dev/null +++ b/src/hi32-app-kdiff3.png 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 Binary files differnew file mode 100644 index 0000000..a10847b --- /dev/null +++ b/src/kdiff3.ico 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>&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>&File</text> + <Action name="file_reload"/> + </Menu> + <Menu name="directory"><text>&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>&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&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>&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>&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 ≪ +} + +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 Binary files differnew file mode 100644 index 0000000..50e3397 --- /dev/null +++ b/src/lo16-app-kdiff3.png diff --git a/src/lo32-app-kdiff3.png b/src/lo32-app-kdiff3.png Binary files differnew file mode 100644 index 0000000..cd269b2 --- /dev/null +++ b/src/lo32-app-kdiff3.png 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", +"................", +"................", +"................", +"................", +"..############..", +".......##.......", +"......# #......", +".....# #.....", +"....# #....", +"...# #...", +"..############..", +"................", +"................", +"................", +"................", +"................"}; |