OSDN Git Service

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