OSDN Git Service

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