OSDN Git Service

Improve plugin system (#797)
[winmerge-jp/winmerge-jp.git] / Src / DirViewColItems.cpp
1 /** 
2  * @file  DirViewColItems.cpp
3  *
4  * @brief Code for individual columns in the DirView
5  *
6  * @date  Created: 2003-08-19
7  */
8
9 #include "pch.h"
10 #include "DirViewColItems.h"
11 #include <Poco/Timestamp.h>
12 #include <Shlwapi.h>
13 #include "UnicodeString.h"
14 #include "DiffItem.h"
15 #include "DiffContext.h"
16 #include "locality.h"
17 #include "paths.h"
18 #include "MergeApp.h"
19 #include "FileTransform.h"
20 #include "DebugNew.h"
21
22 using Poco::Timestamp;
23
24 using std::swap;
25
26 namespace
27 {
28 const char *COLHDR_FILENAME     = N_("Filename");
29 const char *COLHDR_DIR          = NC_("DirView|ColumnHeader", "Folder");
30 const char *COLHDR_RESULT       = N_("Comparison result");
31 const char *COLHDR_LTIMEM       = N_("Left Date");
32 const char *COLHDR_RTIMEM       = N_("Right Date");
33 const char *COLHDR_MTIMEM       = N_("Middle Date");
34 const char *COLHDR_EXTENSION    = N_("Extension");
35 const char *COLHDR_LSIZE        = N_("Left Size");
36 const char *COLHDR_RSIZE        = N_("Right Size");
37 const char *COLHDR_MSIZE        = N_("Middle Size");
38 const char *COLHDR_RSIZE_SHORT  = N_("Right Size (Short)");
39 const char *COLHDR_LSIZE_SHORT  = N_("Left Size (Short)");
40 const char *COLHDR_MSIZE_SHORT  = N_("Middle Size (Short)");
41 const char *COLHDR_LTIMEC       = N_("Left Creation Time");
42 const char *COLHDR_RTIMEC       = N_("Right Creation Time");
43 const char *COLHDR_MTIMEC       = N_("Middle Creation Time");
44 const char *COLHDR_NEWER        = N_("Newer File");
45 const char *COLHDR_LVERSION     = N_("Left File Version");
46 const char *COLHDR_RVERSION     = N_("Right File Version");
47 const char *COLHDR_MVERSION     = N_("Middle File Version");
48 const char *COLHDR_RESULT_ABBR  = N_("Short Result");
49 const char *COLHDR_LATTRIBUTES  = N_("Left Attributes");
50 const char *COLHDR_RATTRIBUTES  = N_("Right Attributes");
51 const char *COLHDR_MATTRIBUTES  = N_("Middle Attributes");
52 const char *COLHDR_LEOL_TYPE    = N_("Left EOL");
53 const char *COLHDR_MEOL_TYPE    = N_("Middle EOL");
54 const char *COLHDR_REOL_TYPE    = N_("Right EOL");
55 const char *COLHDR_LENCODING    = N_("Left Encoding");
56 const char *COLHDR_RENCODING    = N_("Right Encoding");
57 const char *COLHDR_MENCODING    = N_("Middle Encoding");
58 const char *COLHDR_NIDIFFS      = N_("Ignored Diff");
59 const char *COLHDR_NSDIFFS      = N_("Differences");
60 const char *COLHDR_BINARY       = NC_("DirView|ColumnHeader", "Binary");
61 const char *COLHDR_UNPACKER     = N_("Unpacker");
62 const char *COLHDR_PREDIFFER    = N_("Prediffer");
63
64 const char *COLDESC_FILENAME    = N_("Filename or folder name.");
65 const char *COLDESC_DIR         = N_("Subfolder name when subfolders are included.");
66 const char *COLDESC_RESULT      = N_("Comparison result, long form.");
67 const char *COLDESC_LTIMEM      = N_("Left side modification date.");
68 const char *COLDESC_RTIMEM      = N_("Right side modification date.");
69 const char *COLDESC_MTIMEM      = N_("Middle side modification date.");
70 const char *COLDESC_EXTENSION   = N_("File's extension.");
71 const char *COLDESC_LSIZE       = N_("Left file size in bytes.");
72 const char *COLDESC_RSIZE       = N_("Right file size in bytes.");
73 const char *COLDESC_MSIZE       = N_("Middle file size in bytes.");
74 const char *COLDESC_LSIZE_SHORT = N_("Left file size abbreviated.");
75 const char *COLDESC_RSIZE_SHORT = N_("Right file size abbreviated.");
76 const char *COLDESC_MSIZE_SHORT = N_("Middle file size abbreviated.");
77 const char *COLDESC_LTIMEC      = N_("Left side creation time.");
78 const char *COLDESC_RTIMEC      = N_("Right side creation time.");
79 const char *COLDESC_MTIMEC      = N_("Middle side creation time.");
80 const char *COLDESC_NEWER       = N_("Tells which side has newer modification date.");
81 const char *COLDESC_LVERSION    = N_("Left side file version, only for some filetypes.");
82 const char *COLDESC_RVERSION    = N_("Right side file version, only for some filetypes.");
83 const char *COLDESC_MVERSION    = N_("Middle side file version, only for some filetypes.");
84 const char *COLDESC_RESULT_ABBR = N_("Short comparison result.");
85 const char *COLDESC_LATTRIBUTES = N_("Left side attributes.");
86 const char *COLDESC_RATTRIBUTES = N_("Right side attributes.");
87 const char *COLDESC_MATTRIBUTES = N_("Middle side attributes.");
88 const char *COLDESC_LEOL_TYPE   = N_("Left side file EOL type.");
89 const char *COLDESC_REOL_TYPE   = N_("Right side file EOL type.");
90 const char *COLDESC_MEOL_TYPE   = N_("Middle side file EOL type.");
91 const char *COLDESC_LENCODING   = N_("Left side encoding.");
92 const char *COLDESC_RENCODING   = N_("Right side encoding.");
93 const char *COLDESC_MENCODING   = N_("Middle side encoding.");
94 const char *COLDESC_NIDIFFS     = N_("Number of ignored differences in file. These differences are ignored by WinMerge and cannot be merged.");
95 const char *COLDESC_NSDIFFS     = N_("Number of differences in file. This number does not include ignored differences.");
96 const char *COLDESC_BINARY      = N_("Shows an asterisk (*) if the file is binary.");
97 const char *COLDESC_UNPACKER    = N_("Unpacker plugin name or pipeline.");
98 const char *COLDESC_PREDIFFER   = N_("Prediffer plugin name or pipeline.");
99 }
100
101 /**
102  * @brief Function to compare two int64_t's for a sort
103  */
104 static int cmp64(int64_t i1, int64_t i2)
105 {
106         if (i1==i2) return 0;
107         return i1>i2 ? 1 : -1;
108 }
109
110 /**
111  * @brief Function to compare two uint64_t's for a sort
112  */
113 static int cmpu64(uint64_t i1, uint64_t i2)
114 {
115         if (i1==i2) return 0;
116         return i1>i2 ? 1 : -1;
117 }
118 /**
119  * @brief Convert int64_t to int sign
120  */
121 static int sign64(int64_t val)
122 {
123   if (val>0) return 1;
124   if (val<0) return -1;
125   return 0;
126 }
127 /**
128  * @brief Function to compare two diffcodes for a sort
129  * @todo How shall we order diff statuses?
130  */
131 static int cmpdiffcode(unsigned diffcode1, unsigned diffcode2)
132 {
133         return diffcode1-diffcode2;     
134 }
135 /**
136  * @brief Function to compare two doubles for a sort
137  */
138 static int cmpfloat(double v1, double v2)
139 {
140         if (v1>v2)
141                 return 1;
142         if (v1<v2)
143                 return -1;
144         return 0;
145 }
146 /**
147  * @brief Formats a size as a short string.
148  *
149  * MakeShortSize(500) = "500b"
150  * MakeShortSize(1024) = "1Kb"
151  * MakeShortSize(12000) = "1.7Kb"
152  * MakeShortSize(200000) = "195Kb"
153  * @param [in] size File's size to convert.
154  * @return Size string with localized suffix.
155  * @note Localized suffix strings are read from resource.
156  * @todo Can't handle > terabyte filesizes.
157  */
158 static String MakeShortSize(int64_t size)
159 {
160         TCHAR buffer[48];
161         if (size < 1024)
162                 return strutils::format(_T("%d B"), static_cast<int>(size));
163         else
164                 StrFormatByteSize64(size, buffer, static_cast<unsigned>(std::size(buffer)));
165         return buffer;
166 }
167
168 /**
169  * @name Functions to format content of each type of column.
170  * These functions all receive two parameters, a pointer to CDiffContext.
171  * which contains general compare information. And a void pointer whose type
172  * depends on column to format. Function to call for each column, and
173  * parameter for the function are defined in static DirColInfo f_cols table.
174  */
175 /* @{ */
176 /**
177  * @brief Format Filename column data.
178  * @param [in] p Pointer to DIFFITEM.
179  * @return String to show in the column.
180  */
181 template<class Type>
182 static Type ColFileNameGet(const CDiffContext *, const void *p) //sfilename
183 {
184         const boost::flyweight<String> &lfilename = static_cast<const DIFFITEM*>(p)->diffFileInfo[0].filename;
185         const boost::flyweight<String> &rfilename = static_cast<const DIFFITEM*>(p)->diffFileInfo[1].filename;
186         if (lfilename.get().empty())
187                 return rfilename;
188         else if (rfilename.get().empty() || lfilename == rfilename)
189                 return lfilename;
190         else
191                 return static_cast<Type>(lfilename.get() + _T("|") + rfilename.get());
192 }
193
194 /**
195  * @brief Format Extension column data.
196  * @param [in] p Pointer to DIFFITEM.
197  * @return String to show in the column.
198  */
199 static String ColExtGet(const CDiffContext *, const void *p) //sfilename
200 {
201         const DIFFITEM &di = *static_cast<const DIFFITEM*>(p);
202         // We don't show extension for folder names
203         if (di.diffcode.isDirectory())
204                 return _T("");
205         const String &r = di.diffFileInfo[0].filename;
206         String s = paths::FindExtension(r);
207         return s.c_str() + _tcsspn(s.c_str(), _T("."));
208 }
209
210 /**
211  * @brief Format Folder column data.
212  * @param [in] p Pointer to DIFFITEM.
213  * @return String to show in the column.
214  */
215 static String ColPathGet(const CDiffContext *, const void *p)
216 {
217         const DIFFITEM &di = *static_cast<const DIFFITEM*>(p);
218         String s = di.diffFileInfo[1].path;
219         const String &t = di.diffFileInfo[0].path;
220
221         // If we have unique path, just print the existing path name
222         if (s.length() == 0 || t.length() == 0)
223         {
224                 if (s.length() == 0)
225                         return t;
226                 else
227                         return s;
228         }
229
230         size_t i = 0, j = 0;
231         do
232         {
233                 const TCHAR *pi = _tcschr(s.c_str() + i, '\\');
234                 const TCHAR *pj = _tcschr(t.c_str() + j, '\\');
235                 size_t i_ahead = (pi != nullptr ? pi - s.c_str() : std::string::npos);
236                 size_t j_ahead = (pj != nullptr ? pj - t.c_str() : std::string::npos);
237                 size_t length_s = ((i_ahead != std::string::npos ? i_ahead : s.length()) - i);
238                 size_t length_t = ((j_ahead != std::string::npos ? j_ahead : t.length()) - j);
239                 if (length_s != length_t ||
240                         memcmp(s.c_str() + i, t.c_str() + j, length_s) != 0)
241                 {
242                         String u(t.c_str() + j, length_t + 1);
243                         u[length_t] = '|';
244                         s.insert(i, u);
245                         i_ahead += u.length();
246                 }
247                 i = i_ahead + 1;
248                 j = j_ahead + 1;
249         } while (i && j);
250         if (s.empty())
251                 s = _T(".");
252         return s;
253 }
254
255 /**
256  * @brief Format Result column data.
257  * @param [in] pCtxt Pointer to compare context.
258  * @param [in] p Pointer to DIFFITEM.
259  * @return String to show in the column.
260  */
261 static String ColStatusGet(const CDiffContext *pCtxt, const void *p)
262 {
263         const DIFFITEM &di = *static_cast<const DIFFITEM*>(p);
264         int nDirs = pCtxt->GetCompareDirs();
265         // Note that order of items does matter. We must check for
266         // skipped items before unique items, for example, so that
267         // skipped unique items are labeled as skipped, not unique.
268         String s;
269         if (di.diffcode.isResultError())
270         {
271                 s = _("Unable to compare files");
272         }
273         else if (di.diffcode.isResultAbort())
274         {
275                 s = _("Item aborted");
276         }
277         else if (di.diffcode.isResultFiltered())
278         {
279                 if (di.diffcode.isDirectory())
280                         s = _("Folder skipped");
281                 else
282                         s = _("File skipped");
283         }
284         else if (di.diffcode.isSideFirstOnly())
285         {
286                 s = strutils::format_string1(_("Left only: %1"),
287                                 di.getFilepath(0, pCtxt->GetNormalizedLeft()));
288         }
289         else if (di.diffcode.isSideSecondOnly())
290         {
291                 if (nDirs < 3)
292                 {
293                         s = strutils::format_string1(_("Right only: %1"),
294                                         di.getFilepath(1, pCtxt->GetNormalizedRight()));
295                 }
296                 else
297                 {
298                         s = strutils::format_string1(_("Middle only: %1"),
299                                         di.getFilepath(1, pCtxt->GetNormalizedMiddle()));
300                 }
301         }
302         else if (di.diffcode.isSideThirdOnly())
303         {
304                 s = strutils::format_string1(_("Right only: %1"),
305                                 di.getFilepath(2, pCtxt->GetNormalizedRight()));
306         }
307         else if (nDirs > 2 && !di.diffcode.existsFirst())
308         {
309                 s = strutils::format_string1(_("Does not exist in %1"),
310                                 pCtxt->GetNormalizedLeft());
311         }
312         else if (nDirs > 2 && !di.diffcode.existsSecond())
313         {
314                 s = strutils::format_string1(_("Does not exist in %1"),
315                                 pCtxt->GetNormalizedMiddle());
316         }
317         else if (nDirs > 2 && !di.diffcode.existsThird())
318         {
319                 s = strutils::format_string1(_("Does not exist in %1"),
320                                 pCtxt->GetNormalizedRight());
321         }
322         else if (di.diffcode.isResultSame())
323         {
324                 if (di.diffcode.isText())
325                         s = _("Text files are identical");
326                 else if (di.diffcode.isBin())
327                         s = _("Binary files are identical");
328                 else if (di.diffcode.isImage())
329                         s = _("Image files are identical");
330                 else
331                         s = _("Identical");
332         }
333         else if (di.diffcode.isResultDiff()) // diff
334         {
335                 if (di.diffcode.isText())
336                         s = _("Text files are different");
337                 else if (di.diffcode.isBin())
338                         s = _("Binary files are different");
339                 else if (di.diffcode.isImage())
340                         s = _("Image files are different");
341                 else if (di.diffcode.isDirectory())
342                         s = _("Folders are different");
343                 else
344                         s = _("Files are different");
345                 if (nDirs > 2)
346                 {
347                         switch (di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY)
348                         {
349                         case DIFFCODE::DIFF1STONLY: s += _("(Middle and right are identical)"); break;
350                         case DIFFCODE::DIFF2NDONLY: s += _("(Left and right are identical)"); break;
351                         case DIFFCODE::DIFF3RDONLY: s += _("(Left and middle are identical)"); break;
352                         }
353                 }
354         }
355         return s;
356 }
357
358 /**
359  * @brief Format Date column data.
360  * @param [in] p Pointer to integer (seconds since 1.1.1970).
361  * @return String to show in the column.
362  */
363 static String ColTimeGet(const CDiffContext *, const void *p)
364 {
365         const int64_t r = *static_cast<const int64_t*>(p) / Timestamp::resolution();
366         if (r)
367                 return locality::TimeString(&r);
368         else
369                 return _T("");
370 }
371
372 /**
373  * @brief Format Sizw column data.
374  * @param [in] p Pointer to integer containing size in bytes.
375  * @return String to show in the column.
376  */
377 static String ColSizeGet(const CDiffContext *, const void *p)
378 {
379         const int64_t &r = *static_cast<const int64_t*>(p);
380         String s;
381         if (r != -1)
382         {
383                 s = locality::NumToLocaleStr(r);
384         }
385         return s;
386 }
387
388 /**
389  * @brief Format Folder column data.
390  * @param [in] p Pointer to DIFFITEM.
391  * @return String to show in the column.
392  */
393 static String ColSizeShortGet(const CDiffContext *, const void *p)
394 {
395         const int64_t &r = *static_cast<const int64_t*>(p);
396         String s;
397         if (r != -1)
398         {
399                 s = MakeShortSize(r);
400         }
401         return s;
402 }
403
404 /**
405  * @brief Format Difference cout column data.
406  * @param [in] p Pointer to integer having count of differences.
407  * @return String to show in the column.
408  */
409 static String ColDiffsGet(const CDiffContext *, const void *p)
410 {
411         const int &r = *static_cast<const int*>(p);
412         String s;
413         if (r == CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE)
414         { // QuickCompare, unknown
415                 s = _T("*");
416         }
417         else if (r == CDiffContext::DIFFS_UNKNOWN)
418         { // Unique item
419                 s = _T("");
420         }
421         else
422         {
423                 s = locality::NumToLocaleStr(r);
424         }
425         return s;
426 }
427
428 /**
429  * @brief Format Newer/Older column data.
430  * @param [in] p Pointer to DIFFITEM.
431  * @return String to show in the column.
432  */
433 static String ColNewerGet(const CDiffContext *pCtxt, const void *p)
434 {
435         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
436         if (pCtxt->GetCompareDirs() < 3)
437         {
438                 if (di.diffcode.isSideFirstOnly())
439                 {
440                         return _T("<--");
441                 }
442                 if (di.diffcode.isSideSecondOnly())
443                 {
444                         return _T("-->");
445                 }
446                 if (di.diffFileInfo[0].mtime != 0 && di.diffFileInfo[1].mtime != 0)
447                 {
448                         if (di.diffFileInfo[0].mtime > di.diffFileInfo[1].mtime)
449                         {
450                                 return _T("<-");
451                         }
452                         if (di.diffFileInfo[0].mtime < di.diffFileInfo[1].mtime)
453                         {
454                                 return _T("->");
455                         }
456                         return _T("==");
457                 }
458                 return _T("***");
459         }
460         else
461         {
462                 String res;
463                 int sortno[3] = {0, 1, 2};
464                 Timestamp sorttime[3] = {di.diffFileInfo[0].mtime, di.diffFileInfo[1].mtime, di.diffFileInfo[2].mtime};
465                 for (int i = 0; i < 3; i++)
466                 {
467                         for (int j = i; j < 3; j++)
468                         {
469                                 if (sorttime[i] < sorttime[j])
470                                 {
471                                         swap(sorttime[i], sorttime[j]);
472                                         swap(sortno[i], sortno[j]);
473                                 }
474                         }
475                 }
476                 res = _T("LMR")[sortno[0]];
477                 res += sorttime[0] == sorttime[1] ? _T("==") : _T("<-");
478                 res += _T("LMR")[sortno[1]];
479                 res += sorttime[1] == sorttime[2] ? _T("==") : _T("<-");
480                 res += _T("LMR")[sortno[2]];
481                 return res;
482         }
483 }
484
485 /**
486  * @brief Format Version info to string.
487  * @param [in] pCtxt Pointer to compare context.
488  * @param [in] pdi Pointer to DIFFITEM.
489  * @param [in] bLeft Is the item left-size item?
490  * @return String proper to show in the GUI.
491  */
492 static String GetVersion(const CDiffContext * pCtxt, const DIFFITEM *pdi, int nIndex)
493 {
494         DIFFITEM &di = const_cast<DIFFITEM &>(*pdi);
495         DiffFileInfo & dfi = di.diffFileInfo[nIndex];
496         if (dfi.version.IsCleared())
497         {
498                 pCtxt->UpdateVersion(di, nIndex);
499         }
500         return dfi.version.GetFileVersionString();
501 }
502
503 static uint64_t GetVersionQWORD(const CDiffContext * pCtxt, const DIFFITEM *pdi, int nIndex)
504 {
505         DIFFITEM &di = const_cast<DIFFITEM &>(*pdi);
506         DiffFileInfo & dfi = di.diffFileInfo[nIndex];
507         if (dfi.version.IsCleared())
508         {
509                 pCtxt->UpdateVersion(di, nIndex);
510         }
511         return dfi.version.GetFileVersionQWORD();
512 }
513
514 /**
515  * @brief Format Version column data (for left-side).
516  * @param [in] pCtxt Pointer to compare context.
517  * @param [in] p Pointer to DIFFITEM.
518  * @return String to show in the column.
519  */
520 static String ColLversionGet(const CDiffContext * pCtxt, const void *p)
521 {
522         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
523         return GetVersion(pCtxt, &di, 0);
524 }
525
526 /**
527  * @brief Format Version column data (for middle-side).
528  * @param [in] pCtxt Pointer to compare context.
529  * @param [in] p Pointer to DIFFITEM.
530  * @return String to show in the column.
531  */
532 static String ColMversionGet(const CDiffContext * pCtxt, const void *p)
533 {
534         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
535         return GetVersion(pCtxt, &di, 1);
536 }
537
538 /**
539  * @brief Format Version column data (for right-side).
540  * @param [in] pCtxt Pointer to compare context.
541  * @param [in] p Pointer to DIFFITEM.
542  * @return String to show in the column.
543  */
544 static String ColRversionGet(const CDiffContext * pCtxt, const void *p)
545 {
546         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
547         return GetVersion(pCtxt, &di, pCtxt->GetCompareDirs() < 3 ? 1 : 2);
548 }
549
550 /**
551  * @brief Format Short Result column data.
552  * @param [in] p Pointer to DIFFITEM.
553  * @return String to show in the column.
554  */
555 static String ColStatusAbbrGet(const CDiffContext *pCtxt, const void *p)
556 {
557         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
558         const char *id = 0;
559         int nDirs = pCtxt->GetCompareDirs();
560
561         // Note that order of items does matter. We must check for
562         // skipped items before unique items, for example, so that
563         // skipped unique items are labeled as skipped, not unique.
564         if (di.diffcode.isResultError())
565         {
566                 id = N_("Error");
567         }
568         else if (di.diffcode.isResultAbort())
569         {
570                 id = N_("Item aborted");
571         }
572         else if (di.diffcode.isResultFiltered())
573         {
574                 if (di.diffcode.isDirectory())
575                         id = N_("Folder skipped");
576                 else
577                         id = N_("File skipped");
578         }
579         else if (di.diffcode.isSideFirstOnly())
580         {
581                 id = N_("Left Only");
582         }
583         else if (di.diffcode.isSideSecondOnly())
584         {
585                 if (nDirs < 3)
586                         id = N_("Right Only");
587                 else
588                         id = N_("Middle Only");
589         }
590         else if (di.diffcode.isSideThirdOnly())
591         {
592                 id = N_("Right Only");
593         }
594         else if (nDirs > 2 && !di.diffcode.existsFirst())
595         {
596                 id = N_("No item in left");
597         }
598         else if (nDirs > 2 && !di.diffcode.existsSecond())
599         {
600                 id = N_("No item in middle");
601         }
602         else if (nDirs > 2 && !di.diffcode.existsThird())
603         {
604                 id = N_("No item in right");
605         }
606         else if (di.diffcode.isResultSame())
607         {
608                 id = N_("Identical");
609         }
610         else if (di.diffcode.isResultDiff())
611         {
612                 id = N_("Different");
613         }
614
615         return id ? tr(id) : _T("");
616 }
617
618 /**
619  * @brief Format Binary column data.
620  * @param [in] p Pointer to DIFFITEM.
621  * @return String to show in the column.
622  */
623 static String ColBinGet(const CDiffContext *, const void *p)
624 {
625         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
626
627         if (di.diffcode.isBin())
628                 return _T("*");
629         else
630                 return _T("");
631 }
632
633 /**
634  * @brief Format File Attributes column data.
635  * @param [in] p Pointer to file flags class.
636  * @return String to show in the column.
637  */
638 static String ColAttrGet(const CDiffContext *, const void *p)
639 {
640         const FileFlags &r = *static_cast<const FileFlags *>(p);
641         return r.ToString();
642 }
643
644 /**
645  * @brief Format File Encoding column data.
646  * @param [in] p Pointer to file information.
647  * @return String to show in the column.
648  */
649 static String ColEncodingGet(const CDiffContext *, const void *p)
650 {
651         const DiffFileInfo &r = *static_cast<const DiffFileInfo *>(p);
652         return r.encoding.GetName();
653 }
654
655 /**
656  * @brief Format EOL type to string.
657  * @param [in] p Pointer to DIFFITEM.
658  * @param [in] bLeft Are we formatting left-side file's data?
659  * @return EOL type as as string.
660  */
661 static String GetEOLType(const CDiffContext *, const void *p, int index)
662 {
663         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
664         const DiffFileInfo & dfi = di.diffFileInfo[index];
665         const FileTextStats &stats = dfi.m_textStats;
666
667         if (stats.ncrlfs == 0 && stats.ncrs == 0 && stats.nlfs == 0)
668         {
669                 return String();
670         }
671         if (di.diffcode.isBin())
672         {
673                 return tr("EOL Type", "Binary");
674         }
675
676         char *id = 0;
677         if (stats.ncrlfs > 0 && stats.ncrs == 0 && stats.nlfs == 0)
678         {
679                 id = N_("Win");
680         }
681         else if (stats.ncrlfs == 0 && stats.ncrs > 0 && stats.nlfs == 0)
682         {
683                 id = N_("Mac");
684         }
685         else if (stats.ncrlfs == 0 && stats.ncrs == 0 && stats.nlfs > 0)
686         {
687                 id = N_("Unix");
688         }
689         else
690         {
691                 return strutils::format(_T("%s:%d/%d/%d"),
692                         _("Mixed"),
693                         stats.ncrlfs, stats.ncrs, stats.nlfs);
694         }
695         
696         return tr(id);
697 }
698
699 /**
700  * @brief Format EOL type column data (for left-side file).
701  * @param [in] pCtxt Pointer to compare context.
702  * @param [in] p Pointer to DIFFITEM.
703  * @return String to show in the column.
704  */
705 static String ColLEOLTypeGet(const CDiffContext * pCtxt, const void *p)
706 {
707         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
708         return GetEOLType(pCtxt, &di, 0);
709 }
710
711 /**
712  * @brief Format EOL type column data (for right-side file).
713  * @param [in] pCtxt Pointer to compare context.
714  * @param [in] p Pointer to DIFFITEM.
715  * @return String to show in the column.
716  */
717 static String ColMEOLTypeGet(const CDiffContext * pCtxt, const void *p)
718 {
719         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
720         return GetEOLType(pCtxt, &di, 1);
721 }
722
723 static String ColREOLTypeGet(const CDiffContext * pCtxt, const void *p)
724 {
725         const DIFFITEM &di = *static_cast<const DIFFITEM *>(p);
726         return GetEOLType(pCtxt, &di, pCtxt->GetCompareDirs() < 3 ? 1 : 2);
727 }
728
729 static String GetPluginPipeline(const CDiffContext* pCtxt, const DIFFITEM& di, bool unpacker)
730 {
731         if (di.diffcode.isDirectory())
732                 return _T("");
733         PackingInfo* pInfoUnpacker = nullptr;
734         PrediffingInfo * pInfoPrediffer = nullptr;
735         String filteredFilenames = pCtxt->GetFilteredFilenames(di);
736         const_cast<CDiffContext *>(pCtxt)->FetchPluginInfos(filteredFilenames, &pInfoUnpacker, &pInfoPrediffer);
737         if (unpacker)
738                 return pInfoUnpacker ? pInfoUnpacker->GetPluginPipeline() : _T("");
739         else
740                 return pInfoPrediffer ? pInfoPrediffer->GetPluginPipeline() : _T("");
741 }
742
743 static String ColUnpackerGet(const CDiffContext * pCtxt, const void *p)
744 {
745         return GetPluginPipeline(pCtxt, *static_cast<const DIFFITEM *>(p), true);
746 }
747
748 static String ColPredifferGet(const CDiffContext * pCtxt, const void *p)
749 {
750         return GetPluginPipeline(pCtxt, *static_cast<const DIFFITEM *>(p), false);
751 }
752
753 /**
754  * @}
755  */
756
757 /**
758  * @name Functions to sort each type of column info.
759  * These functions are used to sort information in folder compare GUI. Each
760  * column info (type) has its own function to compare the data. Each
761  * function receives three parameters:
762  * - pointer to compare context
763  * - two parameters for data to compare (type varies)
764  * Return value is -1, 0, or 1, where 0 means datas are identical.
765  */
766 /* @{ */
767 /**
768  * @brief Compare file names.
769  * @param [in] pCtxt Pointer to compare context.
770  * @param [in] p Pointer to DIFFITEM having first name to compare.
771  * @param [in] q Pointer to DIFFITEM having second name to compare.
772  * @return Compare result.
773  */
774 static int ColFileNameSort(const CDiffContext *pCtxt, const void *p, const void *q)
775 {
776         const DIFFITEM &ldi = *static_cast<const DIFFITEM *>(p);
777         const DIFFITEM &rdi = *static_cast<const DIFFITEM *>(q);
778         if (ldi.diffcode.isDirectory() && !rdi.diffcode.isDirectory())
779                 return -1;
780         if (!ldi.diffcode.isDirectory() && rdi.diffcode.isDirectory())
781                 return 1;
782         return strutils::compare_nocase(ColFileNameGet<boost::flyweight<String> >(pCtxt, p), ColFileNameGet<boost::flyweight<String> >(pCtxt, q));
783 }
784
785 /**
786  * @brief Compare file name extensions.
787  * @param [in] pCtxt Pointer to compare context.
788  * @param [in] p First file name extension to compare.
789  * @param [in] q Second file name extension to compare.
790  * @return Compare result.
791  */
792 static int ColExtSort(const CDiffContext *pCtxt, const void *p, const void *q)
793 {
794         const DIFFITEM &ldi = *static_cast<const DIFFITEM *>(p);
795         const DIFFITEM &rdi = *static_cast<const DIFFITEM *>(q);
796         if (ldi.diffcode.isDirectory() && !rdi.diffcode.isDirectory())
797                 return -1;
798         if (!ldi.diffcode.isDirectory() && rdi.diffcode.isDirectory())
799                 return 1;
800         return strutils::compare_nocase(ColExtGet(pCtxt, p), ColExtGet(pCtxt, q));
801 }
802
803 /**
804  * @brief Compare folder names.
805  * @param [in] pCtxt Pointer to compare context.
806  * @param [in] p Pointer to DIFFITEM having first folder name to compare.
807  * @param [in] q Pointer to DIFFITEM having second folder name to compare.
808  * @return Compare result.
809  */
810 static int ColPathSort(const CDiffContext *pCtxt, const void *p, const void *q)
811 {
812         return strutils::compare_nocase(ColPathGet(pCtxt, p), ColPathGet(pCtxt, q));
813 }
814
815 /**
816  * @brief Compare compare results.
817  * @param [in] p Pointer to DIFFITEM having first result to compare.
818  * @param [in] q Pointer to DIFFITEM having second result to compare.
819  * @return Compare result.
820  */
821 static int ColStatusSort(const CDiffContext *, const void *p, const void *q)
822 {
823         const DIFFITEM &ldi = *static_cast<const DIFFITEM *>(p);
824         const DIFFITEM &rdi = *static_cast<const DIFFITEM *>(q);
825         return cmpdiffcode(rdi.diffcode.diffcode, ldi.diffcode.diffcode);
826 }
827
828 /**
829  * @brief Compare file times.
830  * @param [in] p First time to compare.
831  * @param [in] q Second time to compare.
832  * @return Compare result.
833  */
834 static int ColTimeSort(const CDiffContext *, const void *p, const void *q)
835 {
836         const int64_t &r = *static_cast<const int64_t*>(p);
837         const int64_t &s = *static_cast<const int64_t*>(q);
838         return cmp64(r, s);
839 }
840
841 /**
842  * @brief Compare file sizes.
843  * @param [in] p First size to compare.
844  * @param [in] q Second size to compare.
845  * @return Compare result.
846  */
847 static int ColSizeSort(const CDiffContext *, const void *p, const void *q)
848 {
849         const int64_t &r = *static_cast<const int64_t*>(p);
850         const int64_t &s = *static_cast<const int64_t*>(q);
851         return cmp64(r, s);
852 }
853
854 /**
855  * @brief Compare difference counts.
856  * @param [in] p First count to compare.
857  * @param [in] q Second count to compare.
858  * @return Compare result.
859  */
860 static int ColDiffsSort(const CDiffContext *, const void *p, const void *q)
861 {
862         const int &r = *static_cast<const int*>(p);
863         const int &s = *static_cast<const int*>(q);
864         return r - s;
865 }
866
867 /**
868  * @brief Compare newer/older statuses.
869  * @param [in] pCtxt Pointer to compare context.
870  * @param [in] p Pointer to DIFFITEM having first status to compare.
871  * @param [in] q Pointer to DIFFITEM having second status to compare.
872  * @return Compare result.
873  */
874 static int ColNewerSort(const CDiffContext *pCtxt, const void *p, const void *q)
875 {
876         return ColNewerGet(pCtxt, p).compare(ColNewerGet(pCtxt, q));
877 }
878
879 /**
880  * @brief Compare left-side file versions.
881  * @param [in] pCtxt Pointer to compare context.
882  * @param [in] p Pointer to DIFFITEM having first version to compare.
883  * @param [in] q Pointer to DIFFITEM having second version to compare.
884  * @return Compare result.
885  */
886 static int ColLversionSort(const CDiffContext *pCtxt, const void *p, const void *q)
887 {
888         return cmpu64(GetVersionQWORD(pCtxt, reinterpret_cast<const DIFFITEM *>(p), 0), GetVersionQWORD(pCtxt, reinterpret_cast<const DIFFITEM *>(q), 0));
889 }
890
891 /**
892  * @brief Compare middle-side file versions.
893  * @param [in] pCtxt Pointer to compare context.
894  * @param [in] p Pointer to DIFFITEM having first version to compare.
895  * @param [in] q Pointer to DIFFITEM having second version to compare.
896  * @return Compare result.
897  */
898 static int ColMversionSort(const CDiffContext *pCtxt, const void *p, const void *q)
899 {
900         return cmpu64(GetVersionQWORD(pCtxt, reinterpret_cast<const DIFFITEM *>(p), 1), GetVersionQWORD(pCtxt, reinterpret_cast<const DIFFITEM *>(q), 1));
901 }
902
903 /**
904  * @brief Compare right-side file versions.
905  * @param [in] pCtxt Pointer to compare context.
906  * @param [in] p Pointer to DIFFITEM having first version to compare.
907  * @param [in] q Pointer to DIFFITEM having second version to compare.
908  * @return Compare result.
909  */
910 static int ColRversionSort(const CDiffContext *pCtxt, const void *p, const void *q)
911 {
912         const int i = pCtxt->GetCompareDirs() < 3 ? 1 : 2;
913         return cmpu64(GetVersionQWORD(pCtxt, reinterpret_cast<const DIFFITEM *>(p), i), GetVersionQWORD(pCtxt, reinterpret_cast<const DIFFITEM *>(q), i));
914 }
915
916 /**
917  * @brief Compare binary statuses.
918  * This function returns a comparison of binary results.
919  * @param [in] p Pointer to DIFFITEM having first status to compare.
920  * @param [in] q Pointer to DIFFITEM having second status to compare.
921  * @return Compare result:
922  * - if both items are text files or binary files: 0
923  * - if left is text and right is binary: -1
924  * - if left is binary and right is text: 1
925  */
926 static int ColBinSort(const CDiffContext *, const void *p, const void *q)
927 {
928         const DIFFITEM &ldi = *static_cast<const DIFFITEM *>(p);
929         const DIFFITEM &rdi = *static_cast<const DIFFITEM *>(q);
930         const bool i = ldi.diffcode.isBin();
931         const bool j = rdi.diffcode.isBin();
932
933         if (!i && !j)
934                 return 0;
935         else if (i && !j)
936                 return 1;
937         else if (!i && j)
938                 return -1;
939         else
940                 return 0;
941 }
942
943 /**
944  * @brief Compare file flags.
945  * @param [in] p Pointer to first flag structure to compare.
946  * @param [in] q Pointer to second flag structure to compare.
947  * @return Compare result.
948  */
949 static int ColAttrSort(const CDiffContext *, const void *p, const void *q)
950 {
951         const FileFlags &r = *static_cast<const FileFlags *>(p);
952         const FileFlags &s = *static_cast<const FileFlags *>(q);
953         if (r.attributes == s.attributes)
954                 return 0;
955         return r.attributes < s.attributes ? -1 : 1;
956 }
957
958 /**
959  * @brief Compare file encodings.
960  * @param [in] p Pointer to first structure to compare.
961  * @param [in] q Pointer to second structure to compare.
962  * @return Compare result.
963  */
964 static int ColEncodingSort(const CDiffContext *, const void *p, const void *q)
965 {
966         const DiffFileInfo &r = *static_cast<const DiffFileInfo *>(p);
967         const DiffFileInfo &s = *static_cast<const DiffFileInfo *>(q);
968         return FileTextEncoding::Collate(r.encoding, s.encoding);
969 }
970 /* @} */
971
972 #undef FIELD_OFFSET     // incorrect for Win32 as defined in WinNT.h
973 #define FIELD_OFFSET(type, field)    ((size_t)(LONG_PTR)&(((type *)nullptr)->field))
974
975 /**
976  * @brief All existing folder compare columns.
977  *
978  * This table has information for all folder compare columns. Fields are
979  * (in this order):
980  *  - internal name
981  *  - name resource ID: column's name shown in header
982  *  - description resource ID: columns description text
983  *  - custom function for getting column data
984  *  - custom function for sorting column data
985  *  - parameter for custom functions: DIFFITEM (if `nullptr`) or one of its fields
986  *  - default column order number, -1 if not shown by default
987  *  - ascending (`true`) or descending (`false`) default sort order
988  *  - alignment of column contents: numbers are usually right-aligned
989  */
990 static DirColInfo f_cols[] =
991 {
992         { _T("Name"), nullptr, COLHDR_FILENAME, COLDESC_FILENAME, &ColFileNameGet<String>, &ColFileNameSort, 0, 0, true, DirColInfo::ALIGN_LEFT },
993         { _T("Path"), "DirView|ColumnHeader", COLHDR_DIR, COLDESC_DIR, &ColPathGet, &ColPathSort, 0, 1, true, DirColInfo::ALIGN_LEFT },
994         { _T("Status"), nullptr, COLHDR_RESULT, COLDESC_RESULT, &ColStatusGet, &ColStatusSort, 0, 2, true, DirColInfo::ALIGN_LEFT },
995         { _T("Lmtime"), nullptr, COLHDR_LTIMEM, COLDESC_LTIMEM, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].mtime), 3, false, DirColInfo::ALIGN_LEFT },
996         { _T("Rmtime"), nullptr, COLHDR_RTIMEM, COLDESC_RTIMEM, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].mtime), 4, false, DirColInfo::ALIGN_LEFT },
997         { _T("Lctime"), nullptr, COLHDR_LTIMEC, COLDESC_LTIMEC, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].ctime), -1, false, DirColInfo::ALIGN_LEFT },
998         { _T("Rctime"), nullptr, COLHDR_RTIMEC, COLDESC_RTIMEC, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].ctime), -1, false, DirColInfo::ALIGN_LEFT },
999         { _T("Ext"), nullptr, COLHDR_EXTENSION, COLDESC_EXTENSION, &ColExtGet, &ColExtSort, 0, 5, true, DirColInfo::ALIGN_LEFT },
1000         { _T("Lsize"), nullptr, COLHDR_LSIZE, COLDESC_LSIZE, &ColSizeGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].size), -1, false, DirColInfo::ALIGN_RIGHT },
1001         { _T("Rsize"), nullptr, COLHDR_RSIZE, COLDESC_RSIZE, &ColSizeGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].size), -1, false, DirColInfo::ALIGN_RIGHT },
1002         { _T("LsizeShort"), nullptr, COLHDR_LSIZE_SHORT, COLDESC_LSIZE_SHORT, &ColSizeShortGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].size), -1, false, DirColInfo::ALIGN_RIGHT },
1003         { _T("RsizeShort"), nullptr, COLHDR_RSIZE_SHORT, COLDESC_RSIZE_SHORT, &ColSizeShortGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].size), -1, false, DirColInfo::ALIGN_RIGHT },
1004         { _T("Newer"), nullptr, COLHDR_NEWER, COLDESC_NEWER, &ColNewerGet, &ColNewerSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1005         { _T("Lversion"), nullptr, COLHDR_LVERSION, COLDESC_LVERSION, &ColLversionGet, &ColLversionSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1006         { _T("Rversion"), nullptr, COLHDR_RVERSION, COLDESC_RVERSION, &ColRversionGet, &ColRversionSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1007         { _T("StatusAbbr"), nullptr, COLHDR_RESULT_ABBR, COLDESC_RESULT_ABBR, &ColStatusAbbrGet, &ColStatusSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1008         { _T("Binary"), "DirView|ColumnHeader", COLHDR_BINARY, COLDESC_BINARY, &ColBinGet, &ColBinSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1009         { _T("Lattr"), nullptr, COLHDR_LATTRIBUTES, COLDESC_LATTRIBUTES, &ColAttrGet, &ColAttrSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].flags), -1, true, DirColInfo::ALIGN_LEFT },
1010         { _T("Rattr"), nullptr, COLHDR_RATTRIBUTES, COLDESC_RATTRIBUTES, &ColAttrGet, &ColAttrSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].flags), -1, true, DirColInfo::ALIGN_LEFT },
1011         { _T("Lencoding"), nullptr, COLHDR_LENCODING, COLDESC_LENCODING, &ColEncodingGet, &ColEncodingSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0]), -1, true, DirColInfo::ALIGN_LEFT },
1012         { _T("Rencoding"), nullptr, COLHDR_RENCODING, COLDESC_RENCODING, &ColEncodingGet, &ColEncodingSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1]), -1, true, DirColInfo::ALIGN_LEFT },
1013         { _T("Snsdiffs"), nullptr, COLHDR_NSDIFFS, COLDESC_NSDIFFS, ColDiffsGet, ColDiffsSort, FIELD_OFFSET(DIFFITEM, nsdiffs), -1, false, DirColInfo::ALIGN_RIGHT },
1014         { _T("Snidiffs"), nullptr, COLHDR_NIDIFFS, COLDESC_NIDIFFS, ColDiffsGet, ColDiffsSort, FIELD_OFFSET(DIFFITEM, nidiffs), -1, false, DirColInfo::ALIGN_RIGHT },
1015         { _T("Leoltype"), nullptr, COLHDR_LEOL_TYPE, COLDESC_LEOL_TYPE, &ColLEOLTypeGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1016         { _T("Reoltype"), nullptr, COLHDR_REOL_TYPE, COLDESC_REOL_TYPE, &ColREOLTypeGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1017         { _T("Unpacker"), nullptr, COLHDR_UNPACKER, COLDESC_UNPACKER, &ColUnpackerGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1018         { _T("Prediffer"), nullptr, COLHDR_PREDIFFER, COLDESC_PREDIFFER, &ColPredifferGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1019 };
1020 static DirColInfo f_cols3[] =
1021 {
1022         { _T("Name"), nullptr, COLHDR_FILENAME, COLDESC_FILENAME, &ColFileNameGet<String>, &ColFileNameSort, 0, 0, true, DirColInfo::ALIGN_LEFT },
1023         { _T("Path"), "DirView|ColumnHeader", COLHDR_DIR, COLDESC_DIR, &ColPathGet, &ColPathSort, 0, 1, true, DirColInfo::ALIGN_LEFT },
1024         { _T("Status"), nullptr, COLHDR_RESULT, COLDESC_RESULT, &ColStatusGet, &ColStatusSort, 0, 2, true, DirColInfo::ALIGN_LEFT },
1025         { _T("Lmtime"), nullptr, COLHDR_LTIMEM, COLDESC_LTIMEM, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].mtime), 3, false, DirColInfo::ALIGN_LEFT },
1026         { _T("Mmtime"), nullptr, COLHDR_MTIMEM, COLDESC_MTIMEM, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].mtime), 4, false, DirColInfo::ALIGN_LEFT },
1027         { _T("Rmtime"), nullptr, COLHDR_RTIMEM, COLDESC_RTIMEM, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[2].mtime), 5, false, DirColInfo::ALIGN_LEFT },
1028         { _T("Lctime"), nullptr, COLHDR_LTIMEC, COLDESC_LTIMEC, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].ctime), -1, false, DirColInfo::ALIGN_LEFT },
1029         { _T("Mctime"), nullptr, COLHDR_MTIMEC, COLDESC_MTIMEC, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].ctime), -1, false, DirColInfo::ALIGN_LEFT },
1030         { _T("Rctime"), nullptr, COLHDR_RTIMEC, COLDESC_RTIMEC, &ColTimeGet, &ColTimeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[2].ctime), -1, false, DirColInfo::ALIGN_LEFT },
1031         { _T("Ext"), nullptr, COLHDR_EXTENSION, COLDESC_EXTENSION, &ColExtGet, &ColExtSort, 0, 6, true, DirColInfo::ALIGN_LEFT },
1032         { _T("Lsize"), nullptr, COLHDR_LSIZE, COLDESC_LSIZE, &ColSizeGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].size), -1, false, DirColInfo::ALIGN_RIGHT },
1033         { _T("Msize"), nullptr, COLHDR_MSIZE, COLDESC_MSIZE, &ColSizeGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].size), -1, false, DirColInfo::ALIGN_RIGHT },
1034         { _T("Rsize"), nullptr, COLHDR_RSIZE, COLDESC_RSIZE, &ColSizeGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[2].size), -1, false, DirColInfo::ALIGN_RIGHT },
1035         { _T("LsizeShort"), nullptr, COLHDR_LSIZE_SHORT, COLDESC_LSIZE_SHORT, &ColSizeShortGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].size), -1, false, DirColInfo::ALIGN_RIGHT },
1036         { _T("MsizeShort"), nullptr, COLHDR_MSIZE_SHORT, COLDESC_MSIZE_SHORT, &ColSizeShortGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].size), -1, false, DirColInfo::ALIGN_RIGHT },
1037         { _T("RsizeShort"), nullptr, COLHDR_RSIZE_SHORT, COLDESC_RSIZE_SHORT, &ColSizeShortGet, &ColSizeSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[2].size), -1, false, DirColInfo::ALIGN_RIGHT },
1038         { _T("Newer"), nullptr, COLHDR_NEWER, COLDESC_NEWER, &ColNewerGet, &ColNewerSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1039         { _T("Lversion"), nullptr, COLHDR_LVERSION, COLDESC_LVERSION, &ColLversionGet, &ColLversionSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1040         { _T("Mversion"), nullptr, COLHDR_MVERSION, COLDESC_MVERSION, &ColMversionGet, &ColMversionSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1041         { _T("Rversion"), nullptr, COLHDR_RVERSION, COLDESC_RVERSION, &ColRversionGet, &ColRversionSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1042         { _T("StatusAbbr"), nullptr, COLHDR_RESULT_ABBR, COLDESC_RESULT_ABBR, &ColStatusAbbrGet, &ColStatusSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1043         { _T("Binary"), "DirView|ColumnHeader", COLHDR_BINARY, COLDESC_BINARY, &ColBinGet, &ColBinSort, 0, -1, true, DirColInfo::ALIGN_LEFT },
1044         { _T("Lattr"), nullptr, COLHDR_LATTRIBUTES, COLDESC_LATTRIBUTES, &ColAttrGet, &ColAttrSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0].flags), -1, true, DirColInfo::ALIGN_LEFT },
1045         { _T("Mattr"), nullptr, COLHDR_MATTRIBUTES, COLDESC_MATTRIBUTES, &ColAttrGet, &ColAttrSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1].flags), -1, true, DirColInfo::ALIGN_LEFT },
1046         { _T("Rattr"), nullptr, COLHDR_RATTRIBUTES, COLDESC_RATTRIBUTES, &ColAttrGet, &ColAttrSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[2].flags), -1, true, DirColInfo::ALIGN_LEFT },
1047         { _T("Lencoding"), nullptr, COLHDR_LENCODING, COLDESC_LENCODING, &ColEncodingGet, &ColEncodingSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[0]), -1, true, DirColInfo::ALIGN_LEFT },
1048         { _T("Mencoding"), nullptr, COLHDR_MENCODING, COLDESC_MENCODING, &ColEncodingGet, &ColEncodingSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[1]), -1, true, DirColInfo::ALIGN_LEFT },
1049         { _T("Rencoding"), nullptr, COLHDR_RENCODING, COLDESC_RENCODING, &ColEncodingGet, &ColEncodingSort, FIELD_OFFSET(DIFFITEM, diffFileInfo[2]), -1, true, DirColInfo::ALIGN_LEFT },
1050         { _T("Snsdiffs"), nullptr, COLHDR_NSDIFFS, COLDESC_NSDIFFS, ColDiffsGet, ColDiffsSort, FIELD_OFFSET(DIFFITEM, nsdiffs), -1, false, DirColInfo::ALIGN_RIGHT },
1051         { _T("Snidiffs"), nullptr, COLHDR_NIDIFFS, COLDESC_NIDIFFS, ColDiffsGet, ColDiffsSort, FIELD_OFFSET(DIFFITEM, nidiffs), -1, false, DirColInfo::ALIGN_RIGHT },
1052         { _T("Leoltype"), nullptr, COLHDR_LEOL_TYPE, COLDESC_LEOL_TYPE, &ColLEOLTypeGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1053         { _T("Meoltype"), nullptr, COLHDR_MEOL_TYPE, COLDESC_MEOL_TYPE, &ColMEOLTypeGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1054         { _T("Reoltype"), nullptr, COLHDR_REOL_TYPE, COLDESC_REOL_TYPE, &ColREOLTypeGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1055         { _T("Unpacker"), nullptr, COLHDR_UNPACKER, COLDESC_UNPACKER, &ColUnpackerGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1056         { _T("Prediffer"), nullptr, COLHDR_PREDIFFER, COLDESC_PREDIFFER, &ColPredifferGet, 0, 0, -1, true, DirColInfo::ALIGN_LEFT },
1057 };
1058
1059 /**
1060  * @brief Count of all known columns
1061  */
1062 const int g_ncols = static_cast<int>(std::size(f_cols));
1063 const int g_ncols3 = static_cast<int>(std::size(f_cols3));
1064
1065 /**
1066  * @brief Registry base value name for saving/loading info for this column
1067  */
1068 String
1069 DirViewColItems::GetColRegValueNameBase(int col) const
1070 {
1071         if (m_nDirs < 3)
1072         {
1073                 assert(col>=0 && col<static_cast<int>(std::size(f_cols)));
1074                 return strutils::format(_T("WDirHdr_%s"), f_cols[col].regName);
1075         }
1076         else
1077         {
1078                 assert(col>=0 && col<static_cast<int>(std::size(f_cols3)));
1079                 return strutils::format(_T("WDirHdr_%s"), f_cols3[col].regName);
1080         }
1081 }
1082
1083 /**
1084  * @brief Get default physical order for specified logical column
1085  */
1086 int
1087 DirViewColItems::GetColDefaultOrder(int col) const
1088 {
1089         if (m_nDirs < 3)
1090         {
1091                 assert(col>=0 && col<static_cast<int>(std::size(f_cols)));
1092                 return f_cols[col].physicalIndex;
1093         }
1094         else
1095         {
1096                 assert(col>=0 && col<static_cast<int>(std::size(f_cols3)));
1097                 return f_cols3[col].physicalIndex;
1098         }
1099 }
1100
1101 /**
1102  * @brief Return the info about the specified physical column
1103  */
1104 const DirColInfo *
1105 DirViewColItems::GetDirColInfo(int col) const
1106 {
1107         if (m_nDirs < 3)
1108         {
1109                 if (col < 0 || col >= static_cast<int>(std::size(f_cols)))
1110                 {
1111                         assert(false); // fix caller, should not ask for nonexistent columns
1112                         return nullptr;
1113                 }
1114                 return &f_cols[col];
1115         }
1116         else
1117         {
1118                 if (col < 0 || col >= static_cast<int>(std::size(f_cols3)))
1119                 {
1120                         assert(false); // fix caller, should not ask for nonexistent columns
1121                         return nullptr;
1122                 }
1123                 return &f_cols3[col];
1124         }
1125 }
1126
1127 /**
1128  * @brief Check if specified physical column has specified resource id name
1129  */
1130 bool
1131 DirViewColItems::IsColById(int col, const char *idname) const
1132 {
1133         int nDirs = m_nDirs;
1134         if (nDirs < 3)
1135         {
1136                 if (col < 0 || col >= static_cast<int>(std::size(f_cols)))
1137                 {
1138                         assert(false); // fix caller, should not ask for nonexistent columns
1139                         return false;
1140                 }
1141                 return f_cols[col].idName == idname;
1142         }
1143         else
1144         {
1145                 if (col < 0 || col >= sizeof(f_cols3)/sizeof(f_cols3[0]))
1146                 {
1147                         assert(false); // fix caller, should not ask for nonexistent columns
1148                         return false;
1149                 }
1150                 return f_cols3[col].idName == idname;
1151         }
1152 }
1153
1154 /**
1155  * @brief Is specified physical column the name column?
1156  */
1157 bool
1158 DirViewColItems::IsColName(int col) const
1159 {
1160         return IsColById(col, COLHDR_FILENAME);
1161 }
1162 /**
1163  * @brief Is specified physical column the left modification time column?
1164  */
1165 bool
1166 DirViewColItems::IsColLmTime(int col) const
1167 {
1168         return IsColById(col, COLHDR_LTIMEM);
1169 }
1170 /**
1171  * @brief Is specified physical column the middle modification time column?
1172  */
1173 bool
1174 DirViewColItems::IsColMmTime(int col) const
1175 {
1176         return IsColById(col, COLHDR_MTIMEM);
1177 }
1178 /**
1179  * @brief Is specified physical column the right modification time column?
1180  */
1181 bool
1182 DirViewColItems::IsColRmTime(int col) const
1183 {
1184         return IsColById(col, COLHDR_RTIMEM);
1185 }
1186 /**
1187  * @brief Is specified physical column the full status (result) column?
1188  */
1189 bool
1190 DirViewColItems::IsColStatus(int col) const
1191 {
1192         return IsColById(col, COLHDR_RESULT);
1193 }
1194 /**
1195  * @brief Is specified physical column the full status (result) column?
1196  */
1197 bool
1198 DirViewColItems::IsColStatusAbbr(int col) const
1199 {
1200         return IsColById(col, COLHDR_RESULT_ABBR);
1201 }
1202
1203 /**
1204  * @brief return whether column normally sorts ascending (dates do not)
1205  */
1206 bool
1207 DirViewColItems::IsDefaultSortAscending(int col) const
1208 {
1209         const DirColInfo * pColInfo = GetDirColInfo(col);
1210         if (pColInfo == nullptr)
1211         {
1212                 assert(false); // fix caller, should not ask for nonexistent columns
1213                 return false;
1214         }
1215         return pColInfo->defSortUp;
1216 }
1217
1218 /**
1219  * @brief Return display name of column
1220  */
1221 String
1222 DirViewColItems::GetColDisplayName(int col) const
1223 {
1224         const DirColInfo * colinfo = GetDirColInfo(col);
1225         return tr(colinfo->idNameContext, colinfo->idName);
1226 }
1227
1228 /**
1229  * @brief Return description of column
1230  */
1231 String
1232 DirViewColItems::GetColDescription(int col) const
1233 {
1234         const DirColInfo * colinfo = GetDirColInfo(col);
1235         return tr(colinfo->idDesc);
1236 }
1237
1238 /**
1239  * @brief Return total number of known columns
1240  */
1241 int
1242 DirViewColItems::GetColCount() const
1243 {
1244         if (m_nDirs < 3)
1245                 return g_ncols;
1246         else
1247                 return g_ncols3;
1248 }
1249
1250 /**
1251  * @brief Get text for specified column.
1252  * This function retrieves the text for the specified colum. Text is
1253  * retrieved by using column-specific handler functions.
1254  * @param [in] pCtxt Compare context.
1255  * @param [in] col Column number.
1256  * @param [in] di Difference data.
1257  * @return Text for the specified column.
1258  */
1259 String
1260 DirViewColItems::ColGetTextToDisplay(const CDiffContext *pCtxt, int col,
1261                 const DIFFITEM &di) const
1262 {
1263         // Custom properties have custom get functions
1264         const DirColInfo * pColInfo = GetDirColInfo(col);
1265         if (pColInfo == nullptr)
1266         {
1267                 assert(false); // fix caller, should not ask for nonexistent columns
1268                 return _T("???");
1269         }
1270         ColGetFncPtrType fnc = pColInfo->getfnc;
1271         size_t offset = pColInfo->offset;
1272         String s = (*fnc)(pCtxt, reinterpret_cast<const char *>(&di) + offset);
1273
1274         // Add '*' to newer time field
1275         if (IsColLmTime(col) || IsColMmTime(col) || IsColRmTime(col))
1276         {
1277                 if (m_nDirs < 3)
1278                 {
1279                         if (di.diffFileInfo[0].mtime != 0 || di.diffFileInfo[1].mtime != 0)
1280                         {
1281                                 if
1282                                         (
1283                                                 IsColLmTime(col) && di.diffFileInfo[0].mtime > di.diffFileInfo[1].mtime // Left modification time
1284                                                 || IsColRmTime(col) && di.diffFileInfo[0].mtime < di.diffFileInfo[1].mtime // Right modification time
1285                                                 )
1286                                 {
1287                                         s.insert(0, _T("* "));
1288                                 }
1289                                 else
1290                                 {
1291                                         s.insert(0, _T("  "));  // Looks best with a fixed-font, but not too bad otherwise
1292                                 }
1293                                 // GreyMerlin (14 Nov 2009) - the flagging of Date needs to be done with
1294                                 //              something not involving extra characters.  Perhaps <red> for oldest, 
1295                                 //              <green> for newest.  Note (20 March 2017): the introduction of 3-Way
1296                                 //              Merge and the yellow difference highlighting adds to the design
1297                                 //              difficulty of any changes.  So maybe this "* "/"  " scheme is good enough.
1298
1299                         }
1300                 }
1301                 else
1302                 {
1303                         if (di.diffFileInfo[0].mtime != 0 || di.diffFileInfo[1].mtime != 0 || di.diffFileInfo[2].mtime != 0)
1304                         {
1305                                 if
1306                                         (
1307                                                 IsColLmTime(col) && di.diffFileInfo[0].mtime > di.diffFileInfo[1].mtime && di.diffFileInfo[0].mtime > di.diffFileInfo[2].mtime // Left modification time
1308                                                 || IsColMmTime(col) && di.diffFileInfo[1].mtime > di.diffFileInfo[0].mtime && di.diffFileInfo[1].mtime > di.diffFileInfo[2].mtime // Middle modification time
1309                                                 || IsColRmTime(col) && di.diffFileInfo[2].mtime > di.diffFileInfo[0].mtime && di.diffFileInfo[2].mtime > di.diffFileInfo[1].mtime // Right modification time
1310                                                 )
1311                                 {
1312                                         s.insert(0, _T("* "));
1313                                 }
1314                                 else
1315                                 {
1316                                         s.insert(0, _T("  "));  // Looks best with a fixed-font, but not too bad otherwise
1317                                 }
1318                                 // GreyMerlin (14 Nov 2009) - See note above.
1319
1320                         }
1321                 }
1322         }
1323
1324         return s;
1325 }
1326
1327
1328 /**
1329  * @brief Sort two items on specified column.
1330  * This function determines order of two items in specified column. Order
1331  * is determined by column-specific functions.
1332  * @param [in] pCtxt Compare context.
1333  * @param [in] col Column number to sort.
1334  * @param [in] ldi Left difference item data.
1335  * @param [in] rdi Right difference item data.
1336  * @return Order of items.
1337  */
1338 int
1339 DirViewColItems::ColSort(const CDiffContext *pCtxt, int col, const DIFFITEM &ldi,
1340                 const DIFFITEM &rdi, bool bTreeMode) const
1341 {
1342         // Custom properties have custom sort functions
1343         const DirColInfo * pColInfo = GetDirColInfo(col);
1344         if (pColInfo == nullptr)
1345         {
1346                 assert(false); // fix caller, should not ask for nonexistent columns
1347                 return 0;
1348         }
1349         size_t offset = pColInfo->offset;
1350         const void * arg1;
1351         const void * arg2;
1352         if (bTreeMode)
1353         {
1354                 int lLevel = ldi.GetDepth();
1355                 int rLevel = rdi.GetDepth();
1356                 const DIFFITEM *lcur = &ldi, *rcur = &rdi;
1357                 if (lLevel < rLevel)
1358                 {
1359                         for (; lLevel != rLevel; rLevel--)
1360                                 rcur = rcur->GetParentLink();
1361                 }
1362                 else if (rLevel < lLevel)
1363                 {
1364                         for (; lLevel != rLevel; lLevel--)
1365                                 lcur = lcur->GetParentLink();
1366                 }
1367                 while (lcur->GetParentLink() != rcur->GetParentLink())
1368                 {
1369                         lcur = lcur->GetParentLink();
1370                         rcur = rcur->GetParentLink();
1371                 }
1372                 arg1 = reinterpret_cast<const char *>(lcur) + offset;
1373                 arg2 = reinterpret_cast<const char *>(rcur) + offset;
1374         }
1375         else
1376         {
1377                 arg1 = reinterpret_cast<const char *>(&ldi) + offset;
1378                 arg2 = reinterpret_cast<const char *>(&rdi) + offset;
1379         }
1380         if (ColSortFncPtrType fnc = pColInfo->sortfnc)
1381         {
1382                 return (*fnc)(pCtxt, arg1, arg2);
1383         }
1384         if (ColGetFncPtrType fnc = pColInfo->getfnc)
1385         {
1386                 String p = (*fnc)(pCtxt, arg1);
1387                 String q = (*fnc)(pCtxt, arg2);
1388                 return strutils::compare_nocase(p, q);
1389         }
1390         return 0;
1391 }
1392
1393 void DirViewColItems::SetColumnOrdering(const int colorder[])
1394 {
1395         m_dispcols = 0;
1396         for (int i = 0; i < m_numcols; ++i)
1397         {
1398                 m_colorder[i] = colorder[i];
1399                 int phy = m_colorder[i];
1400                 if (phy>=0)
1401                 {
1402                         ++m_dispcols;
1403                         m_invcolorder[phy] = i;
1404                 }
1405         }
1406 }
1407
1408 /**
1409  * @brief Set column ordering to default initial order
1410  */
1411 void DirViewColItems::ResetColumnOrdering()
1412 {
1413         ClearColumnOrders();
1414         m_dispcols = 0;
1415         for (int i=0; i<m_numcols; ++i)
1416         {
1417                 int phy = GetColDefaultOrder(i);
1418                 m_colorder[i] = phy;
1419                 if (phy>=0)
1420                 {
1421                         m_invcolorder[phy] = i;
1422                         ++m_dispcols;
1423                 }
1424         }
1425 }
1426
1427 /**
1428  * @brief Reset all current column ordering information
1429  */
1430 void DirViewColItems::ClearColumnOrders()
1431 {
1432         m_colorder.resize(m_numcols);
1433         m_invcolorder.resize(m_numcols);
1434         for (int i=0; i<m_numcols; ++i)
1435         {
1436                 m_colorder[i] = -1;
1437                 m_invcolorder[i] = -1;
1438         }
1439 }
1440
1441 /**
1442  * @brief Remove any windows reordering of columns (params are physical columns)
1443  */
1444 void DirViewColItems::MoveColumn(int psrc, int pdest)
1445 {
1446         // actually moved column
1447         m_colorder[m_invcolorder[psrc]] = pdest;
1448         // shift all other affected columns
1449         int dir = psrc > pdest ? +1 : -1;
1450         int i=0;
1451         for (i=pdest; i!=psrc; i += dir)
1452         {
1453                 m_colorder[m_invcolorder[i]] = i+dir;
1454         }
1455         // fix inverse mapping
1456         for (i=0; i<m_numcols; ++i)
1457         {
1458                 if (m_colorder[i] >= 0)
1459                         m_invcolorder[m_colorder[i]] = i;
1460         }
1461 }
1462
1463 /**
1464  * @brief Resets column widths to defaults.
1465  */
1466 String DirViewColItems::ResetColumnWidths(int defcolwidth)
1467 {
1468         String result;
1469         for (int i = 0; i < m_numcols; i++)
1470         {
1471                 if (!result.empty()) result += ' ';
1472                 result += strutils::to_str(defcolwidth);
1473         }
1474         return result;
1475 }
1476
1477 /**
1478  * @brief Load column orders from registry
1479  */
1480 void DirViewColItems::LoadColumnOrders(String colorders)
1481 {
1482         assert(m_numcols == -1);
1483         m_numcols = GetColCount();
1484         ClearColumnOrders();
1485         m_dispcols = 0;
1486         std::basic_istringstream<TCHAR> ss(colorders);
1487
1488         // Load column orders
1489         // Break out if one is missing
1490         // Break out & mark failure (m_dispcols == -1) if one is invalid
1491         int i=0;
1492         for (i=0; i<m_numcols; ++i)
1493         {
1494                 int ord = -1;
1495                 ss >> ord;
1496                 if (ord<-1 || ord >= m_numcols)
1497                         break;
1498                 m_colorder[i] = ord;
1499                 if (ord>=0)
1500                 {
1501                         ++m_dispcols;
1502                         if (m_invcolorder[ord] != -1)
1503                         {
1504                                 m_dispcols = -1;
1505                                 break;
1506                         }
1507                         m_invcolorder[ord] = i;
1508                 }
1509         }
1510         // Check that a contiguous range was set
1511         for (i=0; i<m_dispcols; ++i)
1512         {
1513                 if (m_invcolorder[i] < 0)
1514                 {
1515                         m_dispcols = -1;
1516                         break;
1517                 }
1518         }
1519         // Must have at least one column
1520         if (m_dispcols<=1)
1521         {
1522                 ResetColumnOrdering();
1523         }
1524 }
1525
1526 /// store current column orders into registry
1527 String DirViewColItems::SaveColumnOrders()
1528 {
1529         assert(static_cast<int>(m_colorder.size()) == m_numcols);
1530         assert(static_cast<int>(m_invcolorder.size()) == m_numcols);
1531         return strutils::join<String (*)(int)>(m_colorder.begin(), m_colorder.end(), _T(" "), strutils::to_str);
1532 }