OSDN Git Service

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