OSDN Git Service

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