OSDN Git Service

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