1// ProgressDialog2.cpp
2
3#include "StdAfx.h"
4
5#ifdef Z7_OLD_WIN_SDK
6#include <ShlGuid.h>
7#endif
8
9#include "../../../Common/IntToString.h"
10#include "../../../Common/StringConvert.h"
11
12#include "../../../Windows/Clipboard.h"
13#include "../../../Windows/ErrorMsg.h"
14
15#include "../GUI/ExtractRes.h"
16
17#include "LangUtils.h"
18
19#include "DialogSize.h"
20#include "ProgressDialog2.h"
21#include "ProgressDialog2Res.h"
22
23using namespace NWindows;
24
25extern HINSTANCE g_hInstance;
26
27static const UINT_PTR kTimerID = 3;
28
29static const UINT kCloseMessage = WM_APP + 1;
30// we can't use WM_USER, since WM_USER can be used by standard Windows procedure for Dialog
31
32static const UINT kTimerElapse =
33  #ifdef UNDER_CE
34  500
35  #else
36  200
37  #endif
38  ;
39
40static const UINT kCreateDelay =
41  #ifdef UNDER_CE
42  2500
43  #else
44  500
45  #endif
46  ;
47
48static const DWORD kPauseSleepTime = 100;
49
50#ifdef Z7_LANG
51
52static const UInt32 kLangIDs[] =
53{
54  IDT_PROGRESS_ELAPSED,
55  IDT_PROGRESS_REMAINING,
56  IDT_PROGRESS_TOTAL,
57  IDT_PROGRESS_SPEED,
58  IDT_PROGRESS_PROCESSED,
59  IDT_PROGRESS_RATIO,
60  IDT_PROGRESS_ERRORS,
61  IDB_PROGRESS_BACKGROUND,
62  IDB_PAUSE
63};
64
65static const UInt32 kLangIDs_Colon[] =
66{
67  IDT_PROGRESS_PACKED,
68  IDT_PROGRESS_FILES
69};
70
71#endif
72
73
74#define UNDEFINED_VAL         ((UInt64)(Int64)-1)
75#define INIT_AS_UNDEFINED(v)  v = UNDEFINED_VAL;
76#define IS_UNDEFINED_VAL(v)   ((v) == UNDEFINED_VAL)
77#define IS_DEFINED_VAL(v)     ((v) != UNDEFINED_VAL)
78
79CProgressSync::CProgressSync():
80    _stopped(false), _paused(false),
81    _bytesProgressMode(true),
82    _isDir(false),
83    _totalBytes(UNDEFINED_VAL), _completedBytes(0),
84    _totalFiles(UNDEFINED_VAL), _curFiles(0),
85    _inSize(UNDEFINED_VAL),
86    _outSize(UNDEFINED_VAL)
87    {}
88
89#define CHECK_STOP  if (_stopped) return E_ABORT; if (!_paused) return S_OK;
90#define CRITICAL_LOCK NSynchronization::CCriticalSectionLock lock(_cs);
91
92bool CProgressSync::Get_Paused()
93{
94  CRITICAL_LOCK
95  return _paused;
96}
97
98HRESULT CProgressSync::CheckStop()
99{
100  for (;;)
101  {
102    {
103      CRITICAL_LOCK
104      CHECK_STOP
105    }
106    ::Sleep(kPauseSleepTime);
107  }
108}
109
110HRESULT CProgressSync::ScanProgress(UInt64 numFiles, UInt64 totalSize, const FString &fileName, bool isDir)
111{
112  {
113    CRITICAL_LOCK
114    _totalFiles = numFiles;
115    _totalBytes = totalSize;
116    _filePath = fs2us(fileName);
117    _isDir = isDir;
118    // _completedBytes = 0;
119    CHECK_STOP
120  }
121  return CheckStop();
122}
123
124HRESULT CProgressSync::Set_NumFilesTotal(UInt64 val)
125{
126  {
127    CRITICAL_LOCK
128    _totalFiles = val;
129    CHECK_STOP
130  }
131  return CheckStop();
132}
133
134void CProgressSync::Set_NumBytesTotal(UInt64 val)
135{
136  CRITICAL_LOCK
137  _totalBytes = val;
138}
139
140void CProgressSync::Set_NumFilesCur(UInt64 val)
141{
142  CRITICAL_LOCK
143  _curFiles = val;
144}
145
146HRESULT CProgressSync::Set_NumBytesCur(const UInt64 *val)
147{
148  {
149    CRITICAL_LOCK
150    if (val)
151      _completedBytes = *val;
152    CHECK_STOP
153  }
154  return CheckStop();
155}
156
157HRESULT CProgressSync::Set_NumBytesCur(UInt64 val)
158{
159  {
160    CRITICAL_LOCK
161    _completedBytes = val;
162    CHECK_STOP
163  }
164  return CheckStop();
165}
166
167void CProgressSync::Set_Ratio(const UInt64 *inSize, const UInt64 *outSize)
168{
169  CRITICAL_LOCK
170  if (inSize)
171    _inSize = *inSize;
172  if (outSize)
173    _outSize = *outSize;
174}
175
176void CProgressSync::Set_TitleFileName(const UString &fileName)
177{
178  CRITICAL_LOCK
179  _titleFileName = fileName;
180}
181
182void CProgressSync::Set_Status(const UString &s)
183{
184  CRITICAL_LOCK
185  _status = s;
186}
187
188HRESULT CProgressSync::Set_Status2(const UString &s, const wchar_t *path, bool isDir)
189{
190  {
191    CRITICAL_LOCK
192    _status = s;
193    if (path)
194      _filePath = path;
195    else
196      _filePath.Empty();
197    _isDir = isDir;
198  }
199  return CheckStop();
200}
201
202void CProgressSync::Set_FilePath(const wchar_t *path, bool isDir)
203{
204  CRITICAL_LOCK
205  if (path)
206    _filePath = path;
207  else
208    _filePath.Empty();
209  _isDir = isDir;
210}
211
212
213void CProgressSync::AddError_Message(const wchar_t *message)
214{
215  CRITICAL_LOCK
216  Messages.Add(message);
217}
218
219void CProgressSync::AddError_Message_Name(const wchar_t *message, const wchar_t *name)
220{
221  UString s;
222  if (name && *name != 0)
223    s += name;
224  if (message && *message != 0)
225  {
226    if (!s.IsEmpty())
227      s.Add_LF();
228    s += message;
229    if (!s.IsEmpty() && s.Back() == L'\n')
230      s.DeleteBack();
231  }
232  AddError_Message(s);
233}
234
235void CProgressSync::AddError_Code_Name(HRESULT systemError, const wchar_t *name)
236{
237  UString s = NError::MyFormatMessage(systemError);
238  if (systemError == 0)
239    s = "Error";
240  AddError_Message_Name(s, name);
241}
242
243CProgressDialog::CProgressDialog():
244   _timer(0),
245   CompressingMode(true),
246   MainWindow(NULL)
247{
248  _isDir = false;
249
250  _numMessages = 0;
251  IconID = -1;
252  MessagesDisplayed = false;
253  _wasCreated = false;
254  _needClose = false;
255  _inCancelMessageBox = false;
256  _externalCloseMessageWasReceived = false;
257
258  _numPostedMessages = 0;
259  _numAutoSizeMessages = 0;
260  _errorsWereDisplayed = false;
261  _waitCloseByCancelButton = false;
262  _cancelWasPressed = false;
263  ShowCompressionInfo = true;
264  WaitMode = false;
265  if (_dialogCreatedEvent.Create() != S_OK)
266    throw 1334987;
267  if (_createDialogEvent.Create() != S_OK)
268    throw 1334987;
269  // #ifdef __ITaskbarList3_INTERFACE_DEFINED__
270  CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (void**)&_taskbarList);
271  if (_taskbarList)
272    _taskbarList->HrInit();
273  // #endif
274}
275
276#ifndef Z7_SFX
277
278CProgressDialog::~CProgressDialog()
279{
280  // #ifdef __ITaskbarList3_INTERFACE_DEFINED__
281  SetTaskbarProgressState(TBPF_NOPROGRESS);
282  // #endif
283  AddToTitle(L"");
284}
285void CProgressDialog::AddToTitle(LPCWSTR s)
286{
287  if (MainWindow)
288  {
289    CWindow window(MainWindow);
290    window.SetText((UString)s + MainTitle);
291  }
292}
293
294#endif
295
296
297void CProgressDialog::SetTaskbarProgressState()
298{
299  // #ifdef __ITaskbarList3_INTERFACE_DEFINED__
300  if (_taskbarList && _hwndForTaskbar)
301  {
302    TBPFLAG tbpFlags;
303    if (Sync.Get_Paused())
304      tbpFlags = TBPF_PAUSED;
305    else
306      tbpFlags = _errorsWereDisplayed ? TBPF_ERROR: TBPF_NORMAL;
307    SetTaskbarProgressState(tbpFlags);
308  }
309  // #endif
310}
311
312static const unsigned kTitleFileNameSizeLimit = 36;
313static const unsigned kCurrentFileNameSizeLimit = 82;
314
315static void ReduceString(UString &s, unsigned size)
316{
317  if (s.Len() <= size)
318    return;
319  s.Delete(size / 2, s.Len() - size);
320  s.Insert(size / 2, L" ... ");
321}
322
323void CProgressDialog::EnableErrorsControls(bool enable)
324{
325  ShowItem_Bool(IDT_PROGRESS_ERRORS, enable);
326  ShowItem_Bool(IDT_PROGRESS_ERRORS_VAL, enable);
327  ShowItem_Bool(IDL_PROGRESS_MESSAGES, enable);
328}
329
330bool CProgressDialog::OnInit()
331{
332  _hwndForTaskbar = MainWindow;
333  if (!_hwndForTaskbar)
334    _hwndForTaskbar = GetParent();
335  if (!_hwndForTaskbar)
336    _hwndForTaskbar = *this;
337
338  INIT_AS_UNDEFINED(_progressBar_Range)
339  INIT_AS_UNDEFINED(_progressBar_Pos)
340
341  INIT_AS_UNDEFINED(_prevPercentValue)
342  INIT_AS_UNDEFINED(_prevElapsedSec)
343  INIT_AS_UNDEFINED(_prevRemainingSec)
344
345  INIT_AS_UNDEFINED(_prevSpeed)
346  _prevSpeed_MoveBits = 0;
347
348  _prevTime = ::GetTickCount();
349  _elapsedTime = 0;
350
351  INIT_AS_UNDEFINED(_totalBytes_Prev)
352  INIT_AS_UNDEFINED(_processed_Prev)
353  INIT_AS_UNDEFINED(_packed_Prev)
354  INIT_AS_UNDEFINED(_ratio_Prev)
355
356  _filesStr_Prev.Empty();
357  _filesTotStr_Prev.Empty();
358
359  _foreground = true;
360
361  m_ProgressBar.Attach(GetItem(IDC_PROGRESS1));
362  _messageList.Attach(GetItem(IDL_PROGRESS_MESSAGES));
363  _messageList.SetUnicodeFormat();
364  _messageList.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT);
365
366  _wasCreated = true;
367  _dialogCreatedEvent.Set();
368
369  #ifdef Z7_LANG
370  LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs));
371  LangSetDlgItems_Colon(*this, kLangIDs_Colon, Z7_ARRAY_SIZE(kLangIDs_Colon));
372  #endif
373
374  CWindow window(GetItem(IDB_PROGRESS_BACKGROUND));
375  window.GetText(_background_String);
376  _backgrounded_String = _background_String;
377  _backgrounded_String.RemoveChar(L'&');
378
379  window = GetItem(IDB_PAUSE);
380  window.GetText(_pause_String);
381
382  LangString(IDS_PROGRESS_FOREGROUND, _foreground_String);
383  LangString(IDS_CONTINUE, _continue_String);
384  LangString(IDS_PROGRESS_PAUSED, _paused_String);
385
386  SetText(_title);
387  SetPauseText();
388  SetPriorityText();
389
390  _messageList.InsertColumn(0, L"", 30);
391  _messageList.InsertColumn(1, L"", 600);
392
393  _messageList.SetColumnWidthAuto(0);
394  _messageList.SetColumnWidthAuto(1);
395
396  EnableErrorsControls(false);
397
398  GetItemSizes(IDCANCEL, _buttonSizeX, _buttonSizeY);
399  _numReduceSymbols = kCurrentFileNameSizeLimit;
400  NormalizeSize(true);
401
402  if (!ShowCompressionInfo)
403  {
404    HideItem(IDT_PROGRESS_PACKED);
405    HideItem(IDT_PROGRESS_PACKED_VAL);
406    HideItem(IDT_PROGRESS_RATIO);
407    HideItem(IDT_PROGRESS_RATIO_VAL);
408  }
409
410  if (IconID >= 0)
411  {
412    HICON icon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IconID));
413    // SetIcon(ICON_SMALL, icon);
414    SetIcon(ICON_BIG, icon);
415  }
416  _timer = SetTimer(kTimerID, kTimerElapse);
417  #ifdef UNDER_CE
418  Foreground();
419  #endif
420
421  CheckNeedClose();
422
423  SetTaskbarProgressState();
424
425  return CModalDialog::OnInit();
426}
427
428static const UINT kIDs[] =
429{
430  IDT_PROGRESS_ELAPSED,   IDT_PROGRESS_ELAPSED_VAL,
431  IDT_PROGRESS_REMAINING, IDT_PROGRESS_REMAINING_VAL,
432  IDT_PROGRESS_FILES,     IDT_PROGRESS_FILES_VAL,
433  0,                      IDT_PROGRESS_FILES_TOTAL,
434  IDT_PROGRESS_ERRORS,    IDT_PROGRESS_ERRORS_VAL,
435
436  IDT_PROGRESS_TOTAL,     IDT_PROGRESS_TOTAL_VAL,
437  IDT_PROGRESS_SPEED,     IDT_PROGRESS_SPEED_VAL,
438  IDT_PROGRESS_PROCESSED, IDT_PROGRESS_PROCESSED_VAL,
439  IDT_PROGRESS_PACKED,    IDT_PROGRESS_PACKED_VAL,
440  IDT_PROGRESS_RATIO,     IDT_PROGRESS_RATIO_VAL
441};
442
443bool CProgressDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize)
444{
445  int sY;
446  int sStep;
447  int mx, my;
448  {
449    RECT r;
450    GetClientRectOfItem(IDT_PROGRESS_ELAPSED, r);
451    mx = r.left;
452    my = r.top;
453    sY = RECT_SIZE_Y(r);
454    GetClientRectOfItem(IDT_PROGRESS_REMAINING, r);
455    sStep = r.top - my;
456  }
457
458  InvalidateRect(NULL);
459
460  const int xSizeClient = xSize - mx * 2;
461
462  {
463    unsigned i;
464    for (i = 800; i > 40; i = i * 9 / 10)
465      if (Units_To_Pixels_X((int)i) <= xSizeClient)
466        break;
467    _numReduceSymbols = i / 4;
468  }
469
470  int yPos = ySize - my - _buttonSizeY;
471
472  ChangeSubWindowSizeX(GetItem(IDT_PROGRESS_STATUS), xSize - mx * 2);
473  ChangeSubWindowSizeX(GetItem(IDT_PROGRESS_FILE_NAME), xSize - mx * 2);
474  ChangeSubWindowSizeX(GetItem(IDC_PROGRESS1), xSize - mx * 2);
475
476  int bSizeX = _buttonSizeX;
477  int mx2 = mx;
478  for (;; mx2--)
479  {
480    const int bSize2 = bSizeX * 3 + mx2 * 2;
481    if (bSize2 <= xSizeClient)
482      break;
483    if (mx2 < 5)
484    {
485      bSizeX = (xSizeClient - mx2 * 2) / 3;
486      break;
487    }
488  }
489  if (bSizeX < 2)
490    bSizeX = 2;
491
492  {
493    RECT r;
494    GetClientRectOfItem(IDL_PROGRESS_MESSAGES, r);
495    const int y = r.top;
496    int ySize2 = yPos - my - y;
497    const int kMinYSize = _buttonSizeY + _buttonSizeY * 3 / 4;
498    int xx = xSize - mx * 2;
499    if (ySize2 < kMinYSize)
500    {
501      ySize2 = kMinYSize;
502      if (xx > bSizeX * 2)
503        xx -= bSizeX;
504    }
505
506    _messageList.Move(mx, y, xx, ySize2);
507  }
508
509  {
510    int xPos = xSize - mx;
511    xPos -= bSizeX;
512    MoveItem(IDCANCEL, xPos, yPos, bSizeX, _buttonSizeY);
513    xPos -= (mx2 + bSizeX);
514    MoveItem(IDB_PAUSE, xPos, yPos, bSizeX, _buttonSizeY);
515    xPos -= (mx2 + bSizeX);
516    MoveItem(IDB_PROGRESS_BACKGROUND, xPos, yPos, bSizeX, _buttonSizeY);
517  }
518
519  int valueSize;
520  int labelSize;
521  int padSize;
522
523  labelSize = Units_To_Pixels_X(MY_PROGRESS_LABEL_UNITS_MIN);
524  valueSize = Units_To_Pixels_X(MY_PROGRESS_VAL_UNITS);
525  padSize = Units_To_Pixels_X(MY_PROGRESS_PAD_UNITS);
526  const int requiredSize = (labelSize + valueSize) * 2 + padSize;
527
528  int gSize;
529  {
530    if (requiredSize < xSizeClient)
531    {
532      const int incr = (xSizeClient - requiredSize) / 3;
533      labelSize += incr;
534    }
535    else
536      labelSize = (xSizeClient - valueSize * 2 - padSize) / 2;
537    if (labelSize < 0)
538      labelSize = 0;
539
540    gSize = labelSize + valueSize;
541    padSize = xSizeClient - gSize * 2;
542  }
543
544  labelSize = gSize - valueSize;
545
546  yPos = my;
547  for (unsigned i = 0; i < Z7_ARRAY_SIZE(kIDs); i += 2)
548  {
549    int x = mx;
550    const unsigned kNumColumn1Items = 5 * 2;
551    if (i >= kNumColumn1Items)
552    {
553      if (i == kNumColumn1Items)
554        yPos = my;
555      x = mx + gSize + padSize;
556    }
557    if (kIDs[i] != 0)
558    MoveItem(kIDs[i], x, yPos, labelSize, sY);
559    MoveItem(kIDs[i + 1], x + labelSize, yPos, valueSize, sY);
560    yPos += sStep;
561  }
562  return false;
563}
564
565void CProgressDialog::OnCancel() { Sync.Set_Stopped(true); }
566void CProgressDialog::OnOK() { }
567
568void CProgressDialog::SetProgressRange(UInt64 range)
569{
570  if (range == _progressBar_Range)
571    return;
572  _progressBar_Range = range;
573  INIT_AS_UNDEFINED(_progressBar_Pos)
574  _progressConv.Init(range);
575  m_ProgressBar.SetRange32(0, _progressConv.Count(range));
576}
577
578void CProgressDialog::SetProgressPos(UInt64 pos)
579{
580  if (pos >= _progressBar_Range ||
581      pos <= _progressBar_Pos ||
582      pos - _progressBar_Pos >= (_progressBar_Range >> 10))
583  {
584    m_ProgressBar.SetPos(_progressConv.Count(pos));
585    // #ifdef __ITaskbarList3_INTERFACE_DEFINED__
586    if (_taskbarList && _hwndForTaskbar)
587      _taskbarList->SetProgressValue(_hwndForTaskbar, pos, _progressBar_Range);
588    // #endif
589    _progressBar_Pos = pos;
590  }
591}
592
593#define UINT_TO_STR_2(val) { s[0] = (wchar_t)('0' + (val) / 10); s[1] = (wchar_t)('0' + (val) % 10); s += 2; }
594
595void GetTimeString(UInt64 timeValue, wchar_t *s);
596void GetTimeString(UInt64 timeValue, wchar_t *s)
597{
598  UInt64 hours = timeValue / 3600;
599  UInt32 seconds = (UInt32)(timeValue - hours * 3600);
600  UInt32 minutes = seconds / 60;
601  seconds %= 60;
602  if (hours > 99)
603  {
604    ConvertUInt64ToString(hours, s);
605    for (; *s != 0; s++);
606  }
607  else
608  {
609    UInt32 hours32 = (UInt32)hours;
610    UINT_TO_STR_2(hours32)
611  }
612  *s++ = ':'; UINT_TO_STR_2(minutes)
613  *s++ = ':'; UINT_TO_STR_2(seconds)
614  *s = 0;
615}
616
617static void ConvertSizeToString(UInt64 v, wchar_t *s)
618{
619  Byte c = 0;
620       if (v >= ((UInt64)100000 << 20)) { v >>= 30; c = 'G'; }
621  else if (v >= ((UInt64)100000 << 10)) { v >>= 20; c = 'M'; }
622  else if (v >= ((UInt64)100000 <<  0)) { v >>= 10; c = 'K'; }
623  ConvertUInt64ToString(v, s);
624  if (c != 0)
625  {
626    s += MyStringLen(s);
627    *s++ = ' ';
628    *s++ = c;
629    *s++ = 'B';
630    *s++ = 0;
631  }
632}
633
634void CProgressDialog::ShowSize(unsigned id, UInt64 val, UInt64 &prev)
635{
636  if (val == prev)
637    return;
638  prev = val;
639  wchar_t s[40];
640  s[0] = 0;
641  if (IS_DEFINED_VAL(val))
642    ConvertSizeToString(val, s);
643  SetItemText(id, s);
644}
645
646static void GetChangedString(const UString &newStr, UString &prevStr, bool &hasChanged)
647{
648  hasChanged = !(prevStr == newStr);
649  if (hasChanged)
650    prevStr = newStr;
651}
652
653static unsigned GetPower32(UInt32 val)
654{
655  const unsigned kStart = 32;
656  UInt32 mask = ((UInt32)1 << (kStart - 1));
657  for (unsigned i = kStart;; i--)
658  {
659    if (i == 0 || (val & mask) != 0)
660      return i;
661    mask >>= 1;
662  }
663}
664
665static unsigned GetPower64(UInt64 val)
666{
667  UInt32 high = (UInt32)(val >> 32);
668  if (high == 0)
669    return GetPower32((UInt32)val);
670  return GetPower32(high) + 32;
671}
672
673static UInt64 MyMultAndDiv(UInt64 mult1, UInt64 mult2, UInt64 divider)
674{
675  unsigned pow1 = GetPower64(mult1);
676  unsigned pow2 = GetPower64(mult2);
677  while (pow1 + pow2 > 64)
678  {
679    if (pow1 > pow2) { pow1--; mult1 >>= 1; }
680    else             { pow2--; mult2 >>= 1; }
681    divider >>= 1;
682  }
683  UInt64 res = mult1 * mult2;
684  if (divider != 0)
685    res /= divider;
686  return res;
687}
688
689void CProgressDialog::UpdateStatInfo(bool showAll)
690{
691  UInt64 total, completed, totalFiles, completedFiles, inSize, outSize;
692  bool bytesProgressMode;
693
694  bool titleFileName_Changed;
695  bool curFilePath_Changed;
696  bool status_Changed;
697  unsigned numErrors;
698  {
699    NSynchronization::CCriticalSectionLock lock(Sync._cs);
700    total = Sync._totalBytes;
701    completed = Sync._completedBytes;
702    totalFiles = Sync._totalFiles;
703    completedFiles = Sync._curFiles;
704    inSize = Sync._inSize;
705    outSize = Sync._outSize;
706    bytesProgressMode = Sync._bytesProgressMode;
707
708    GetChangedString(Sync._titleFileName, _titleFileName, titleFileName_Changed);
709    GetChangedString(Sync._filePath, _filePath, curFilePath_Changed);
710    GetChangedString(Sync._status, _status, status_Changed);
711    if (_isDir != Sync._isDir)
712    {
713      curFilePath_Changed = true;
714      _isDir = Sync._isDir;
715    }
716    numErrors = Sync.Messages.Size();
717  }
718
719  UInt32 curTime = ::GetTickCount();
720
721  const UInt64 progressTotal = bytesProgressMode ? total : totalFiles;
722  const UInt64 progressCompleted = bytesProgressMode ? completed : completedFiles;
723  {
724    if (IS_UNDEFINED_VAL(progressTotal))
725    {
726      // SetPos(0);
727      // SetRange(progressCompleted);
728    }
729    else
730    {
731      if (_progressBar_Pos != 0 || progressCompleted != 0 ||
732          (_progressBar_Range == 0 && progressTotal != 0))
733      {
734        SetProgressRange(progressTotal);
735        SetProgressPos(progressCompleted);
736      }
737    }
738  }
739
740  ShowSize(IDT_PROGRESS_TOTAL_VAL, total, _totalBytes_Prev);
741
742  _elapsedTime += (curTime - _prevTime);
743  _prevTime = curTime;
744  UInt64 elapsedSec = _elapsedTime / 1000;
745  bool elapsedChanged = false;
746  if (elapsedSec != _prevElapsedSec)
747  {
748    _prevElapsedSec = elapsedSec;
749    elapsedChanged = true;
750    wchar_t s[40];
751    GetTimeString(elapsedSec, s);
752    SetItemText(IDT_PROGRESS_ELAPSED_VAL, s);
753  }
754
755  bool needSetTitle = false;
756  if (elapsedChanged || showAll)
757  {
758    if (numErrors > _numPostedMessages)
759    {
760      UpdateMessagesDialog();
761      wchar_t s[32];
762      ConvertUInt64ToString(numErrors, s);
763      SetItemText(IDT_PROGRESS_ERRORS_VAL, s);
764      if (!_errorsWereDisplayed)
765      {
766        _errorsWereDisplayed = true;
767        EnableErrorsControls(true);
768        SetTaskbarProgressState();
769      }
770    }
771
772    if (progressCompleted != 0)
773    {
774      if (IS_UNDEFINED_VAL(progressTotal))
775      {
776        if (IS_DEFINED_VAL(_prevRemainingSec))
777        {
778          INIT_AS_UNDEFINED(_prevRemainingSec)
779          SetItemText(IDT_PROGRESS_REMAINING_VAL, L"");
780        }
781      }
782      else
783      {
784        UInt64 remainingTime = 0;
785        if (progressCompleted < progressTotal)
786          remainingTime = MyMultAndDiv(_elapsedTime, progressTotal - progressCompleted, progressCompleted);
787        UInt64 remainingSec = remainingTime / 1000;
788        if (remainingSec != _prevRemainingSec)
789        {
790          _prevRemainingSec = remainingSec;
791          wchar_t s[40];
792          GetTimeString(remainingSec, s);
793          SetItemText(IDT_PROGRESS_REMAINING_VAL, s);
794        }
795      }
796      {
797        const UInt64 elapsedTime = (_elapsedTime == 0) ? 1 : _elapsedTime;
798        // 22.02: progressCompleted can be for number of files
799        UInt64 v = (completed * 1000) / elapsedTime;
800        Byte c = 0;
801        unsigned moveBits = 0;
802             if (v >= ((UInt64)10000 << 10)) { moveBits = 20; c = 'M'; }
803        else if (v >= ((UInt64)10000 <<  0)) { moveBits = 10; c = 'K'; }
804        v >>= moveBits;
805        if (moveBits != _prevSpeed_MoveBits || v != _prevSpeed)
806        {
807          _prevSpeed_MoveBits = moveBits;
808          _prevSpeed = v;
809          wchar_t s[40];
810          ConvertUInt64ToString(v, s);
811          unsigned pos = MyStringLen(s);
812          s[pos++] = ' ';
813          if (moveBits != 0)
814            s[pos++] = c;
815          s[pos++] = 'B';
816          s[pos++] = '/';
817          s[pos++] = 's';
818          s[pos++] = 0;
819          SetItemText(IDT_PROGRESS_SPEED_VAL, s);
820        }
821      }
822    }
823
824    {
825      UInt64 percent = 0;
826      {
827        if (IS_DEFINED_VAL(progressTotal))
828        {
829          percent = progressCompleted * 100;
830          if (progressTotal != 0)
831            percent /= progressTotal;
832        }
833      }
834      if (percent != _prevPercentValue)
835      {
836        _prevPercentValue = percent;
837        needSetTitle = true;
838      }
839    }
840
841    {
842      wchar_t s[64];
843
844      ConvertUInt64ToString(completedFiles, s);
845      if (_filesStr_Prev != s)
846      {
847        _filesStr_Prev = s;
848        SetItemText(IDT_PROGRESS_FILES_VAL, s);
849      }
850
851      s[0] = 0;
852      if (IS_DEFINED_VAL(totalFiles))
853      {
854        MyStringCopy(s, L" / ");
855        ConvertUInt64ToString(totalFiles, s + MyStringLen(s));
856      }
857      if (_filesTotStr_Prev != s)
858      {
859        _filesTotStr_Prev = s;
860        SetItemText(IDT_PROGRESS_FILES_TOTAL, s);
861      }
862    }
863
864    const UInt64 packSize   = CompressingMode ? outSize : inSize;
865    const UInt64 unpackSize = CompressingMode ? inSize : outSize;
866
867    if (IS_UNDEFINED_VAL(unpackSize) &&
868        IS_UNDEFINED_VAL(packSize))
869    {
870      ShowSize(IDT_PROGRESS_PROCESSED_VAL, completed, _processed_Prev);
871      ShowSize(IDT_PROGRESS_PACKED_VAL, UNDEFINED_VAL, _packed_Prev);
872    }
873    else
874    {
875      ShowSize(IDT_PROGRESS_PROCESSED_VAL, unpackSize, _processed_Prev);
876      ShowSize(IDT_PROGRESS_PACKED_VAL, packSize, _packed_Prev);
877
878      if (IS_DEFINED_VAL(packSize) &&
879          IS_DEFINED_VAL(unpackSize) &&
880          unpackSize != 0)
881      {
882        wchar_t s[32];
883        UInt64 ratio = packSize * 100 / unpackSize;
884        if (_ratio_Prev != ratio)
885        {
886          _ratio_Prev = ratio;
887          ConvertUInt64ToString(ratio, s);
888          MyStringCat(s, L"%");
889          SetItemText(IDT_PROGRESS_RATIO_VAL, s);
890        }
891      }
892    }
893  }
894
895  if (needSetTitle || titleFileName_Changed)
896    SetTitleText();
897
898  if (status_Changed)
899  {
900    UString s = _status;
901    ReduceString(s, _numReduceSymbols);
902    SetItemText(IDT_PROGRESS_STATUS, _status);
903  }
904
905  if (curFilePath_Changed)
906  {
907    UString s1, s2;
908    if (_isDir)
909      s1 = _filePath;
910    else
911    {
912      int slashPos = _filePath.ReverseFind_PathSepar();
913      if (slashPos >= 0)
914      {
915        s1.SetFrom(_filePath, (unsigned)(slashPos + 1));
916        s2 = _filePath.Ptr((unsigned)(slashPos + 1));
917      }
918      else
919        s2 = _filePath;
920    }
921    ReduceString(s1, _numReduceSymbols);
922    ReduceString(s2, _numReduceSymbols);
923    s1.Add_LF();
924    s1 += s2;
925    SetItemText(IDT_PROGRESS_FILE_NAME, s1);
926  }
927}
928
929bool CProgressDialog::OnTimer(WPARAM /* timerID */, LPARAM /* callback */)
930{
931  if (Sync.Get_Paused())
932    return true;
933  CheckNeedClose();
934  UpdateStatInfo(false);
935  return true;
936}
937
938struct CWaitCursor
939{
940  HCURSOR _waitCursor;
941  HCURSOR _oldCursor;
942  CWaitCursor()
943  {
944    _waitCursor = LoadCursor(NULL, IDC_WAIT);
945    if (_waitCursor != NULL)
946      _oldCursor = SetCursor(_waitCursor);
947  }
948  ~CWaitCursor()
949  {
950    if (_waitCursor != NULL)
951      SetCursor(_oldCursor);
952  }
953};
954
955INT_PTR CProgressDialog::Create(const UString &title, NWindows::CThread &thread, HWND wndParent)
956{
957  INT_PTR res = 0;
958  try
959  {
960    if (WaitMode)
961    {
962      CWaitCursor waitCursor;
963      HANDLE h[] = { thread, _createDialogEvent };
964
965      const DWORD res2 = WaitForMultipleObjects(Z7_ARRAY_SIZE(h), h, FALSE, kCreateDelay);
966      if (res2 == WAIT_OBJECT_0 && !Sync.ThereIsMessage())
967        return 0;
968    }
969    _title = title;
970    BIG_DIALOG_SIZE(360, 192);
971    res = CModalDialog::Create(SIZED_DIALOG(IDD_PROGRESS), wndParent);
972  }
973  catch(...)
974  {
975    _wasCreated = true;
976    _dialogCreatedEvent.Set();
977  }
978  thread.Wait_Close();
979  if (!MessagesDisplayed)
980    MessageBoxW(wndParent, L"Progress Error", L"7-Zip", MB_ICONERROR);
981  return res;
982}
983
984bool CProgressDialog::OnExternalCloseMessage()
985{
986  // it doesn't work if there is MessageBox.
987  // #ifdef __ITaskbarList3_INTERFACE_DEFINED__
988  SetTaskbarProgressState(TBPF_NOPROGRESS);
989  // #endif
990  // AddToTitle(L"Finished ");
991  // SetText(L"Finished2 ");
992
993  UpdateStatInfo(true);
994
995  SetItemText(IDCANCEL, LangString(IDS_CLOSE));
996  ::SendMessage(GetItem(IDCANCEL), BM_SETSTYLE, BS_DEFPUSHBUTTON, MAKELPARAM(TRUE, 0));
997  HideItem(IDB_PROGRESS_BACKGROUND);
998  HideItem(IDB_PAUSE);
999
1000  ProcessWasFinished_GuiVirt();
1001
1002  bool thereAreMessages;
1003  CProgressFinalMessage fm;
1004  {
1005    NSynchronization::CCriticalSectionLock lock(Sync._cs);
1006    thereAreMessages = !Sync.Messages.IsEmpty();
1007    fm = Sync.FinalMessage;
1008  }
1009
1010  if (!fm.ErrorMessage.Message.IsEmpty())
1011  {
1012    MessagesDisplayed = true;
1013    if (fm.ErrorMessage.Title.IsEmpty())
1014      fm.ErrorMessage.Title = "7-Zip";
1015    MessageBoxW(*this, fm.ErrorMessage.Message, fm.ErrorMessage.Title, MB_ICONERROR);
1016  }
1017  else if (!thereAreMessages)
1018  {
1019    MessagesDisplayed = true;
1020
1021    if (!fm.OkMessage.Message.IsEmpty())
1022    {
1023      if (fm.OkMessage.Title.IsEmpty())
1024        fm.OkMessage.Title = "7-Zip";
1025      MessageBoxW(*this, fm.OkMessage.Message, fm.OkMessage.Title, MB_OK);
1026    }
1027  }
1028
1029  if (thereAreMessages && !_cancelWasPressed)
1030  {
1031    _waitCloseByCancelButton = true;
1032    UpdateMessagesDialog();
1033    return true;
1034  }
1035
1036  End(0);
1037  return true;
1038}
1039
1040bool CProgressDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam)
1041{
1042  switch (message)
1043  {
1044    case kCloseMessage:
1045    {
1046      if (_timer)
1047      {
1048        /* 21.03 : KillTimer(kTimerID) instead of KillTimer(_timer).
1049           But (_timer == kTimerID) in Win10. So it worked too */
1050        KillTimer(kTimerID);
1051        _timer = 0;
1052      }
1053      if (_inCancelMessageBox)
1054      {
1055        /* if user is in MessageBox(), we will call OnExternalCloseMessage()
1056           later, when MessageBox() will be closed */
1057        _externalCloseMessageWasReceived = true;
1058        break;
1059      }
1060      return OnExternalCloseMessage();
1061    }
1062    /*
1063    case WM_SETTEXT:
1064    {
1065      if (_timer == 0)
1066        return true;
1067      break;
1068    }
1069    */
1070  }
1071  return CModalDialog::OnMessage(message, wParam, lParam);
1072}
1073
1074void CProgressDialog::SetTitleText()
1075{
1076  UString s;
1077  if (Sync.Get_Paused())
1078  {
1079    s += _paused_String;
1080    s.Add_Space();
1081  }
1082  if (IS_DEFINED_VAL(_prevPercentValue))
1083  {
1084    char temp[32];
1085    ConvertUInt64ToString(_prevPercentValue, temp);
1086    s += temp;
1087    s += '%';
1088  }
1089  if (!_foreground)
1090  {
1091    s.Add_Space();
1092    s += _backgrounded_String;
1093  }
1094
1095  s.Add_Space();
1096  #ifndef Z7_SFX
1097  {
1098    unsigned len = s.Len();
1099    s += MainAddTitle;
1100    AddToTitle(s);
1101    s.DeleteFrom(len);
1102  }
1103  #endif
1104
1105  s += _title;
1106  if (!_titleFileName.IsEmpty())
1107  {
1108    UString fileName = _titleFileName;
1109    ReduceString(fileName, kTitleFileNameSizeLimit);
1110    s.Add_Space();
1111    s += fileName;
1112  }
1113  SetText(s);
1114}
1115
1116void CProgressDialog::SetPauseText()
1117{
1118  SetItemText(IDB_PAUSE, Sync.Get_Paused() ? _continue_String : _pause_String);
1119  SetTitleText();
1120}
1121
1122void CProgressDialog::OnPauseButton()
1123{
1124  bool paused = !Sync.Get_Paused();
1125  Sync.Set_Paused(paused);
1126  UInt32 curTime = ::GetTickCount();
1127  if (paused)
1128    _elapsedTime += (curTime - _prevTime);
1129  SetTaskbarProgressState();
1130  _prevTime = curTime;
1131  SetPauseText();
1132}
1133
1134void CProgressDialog::SetPriorityText()
1135{
1136  SetItemText(IDB_PROGRESS_BACKGROUND, _foreground ?
1137      _background_String :
1138      _foreground_String);
1139  SetTitleText();
1140}
1141
1142void CProgressDialog::OnPriorityButton()
1143{
1144  _foreground = !_foreground;
1145  #ifndef UNDER_CE
1146  SetPriorityClass(GetCurrentProcess(), _foreground ? NORMAL_PRIORITY_CLASS: IDLE_PRIORITY_CLASS);
1147  #endif
1148  SetPriorityText();
1149}
1150
1151void CProgressDialog::AddMessageDirect(LPCWSTR message, bool needNumber)
1152{
1153  wchar_t sz[16];
1154  sz[0] = 0;
1155  if (needNumber)
1156    ConvertUInt32ToString(_numMessages + 1, sz);
1157  const unsigned itemIndex = _messageStrings.Size(); // _messageList.GetItemCount();
1158  if (_messageList.InsertItem(itemIndex, sz) == (int)itemIndex)
1159  {
1160    _messageList.SetSubItem(itemIndex, 1, message);
1161    _messageStrings.Add(message);
1162  }
1163}
1164
1165void CProgressDialog::AddMessage(LPCWSTR message)
1166{
1167  UString s = message;
1168  bool needNumber = true;
1169  while (!s.IsEmpty())
1170  {
1171    const int pos = s.Find(L'\n');
1172    if (pos < 0)
1173      break;
1174    AddMessageDirect(s.Left((unsigned)pos), needNumber);
1175    needNumber = false;
1176    s.DeleteFrontal((unsigned)pos + 1);
1177  }
1178  AddMessageDirect(s, needNumber);
1179  _numMessages++;
1180}
1181
1182static unsigned GetNumDigits(UInt32 val)
1183{
1184  unsigned i;
1185  for (i = 0; val >= 10; i++)
1186    val /= 10;
1187  return i;
1188}
1189
1190void CProgressDialog::UpdateMessagesDialog()
1191{
1192  UStringVector messages;
1193  {
1194    NSynchronization::CCriticalSectionLock lock(Sync._cs);
1195    unsigned num = Sync.Messages.Size();
1196    if (num > _numPostedMessages)
1197    {
1198      messages.ClearAndReserve(num - _numPostedMessages);
1199      for (unsigned i = _numPostedMessages; i < num; i++)
1200        messages.AddInReserved(Sync.Messages[i]);
1201      _numPostedMessages = num;
1202    }
1203  }
1204  if (!messages.IsEmpty())
1205  {
1206    FOR_VECTOR (i, messages)
1207      AddMessage(messages[i]);
1208    if (_numAutoSizeMessages < 256 || GetNumDigits(_numPostedMessages) > GetNumDigits(_numAutoSizeMessages))
1209    {
1210      _messageList.SetColumnWidthAuto(0);
1211      _messageList.SetColumnWidthAuto(1);
1212      _numAutoSizeMessages = _numPostedMessages;
1213    }
1214  }
1215}
1216
1217
1218bool CProgressDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND)
1219{
1220  switch (buttonID)
1221  {
1222    // case IDOK: // if IDCANCEL is not DEFPUSHBUTTON
1223    case IDCANCEL:
1224    {
1225      if (_waitCloseByCancelButton)
1226      {
1227        MessagesDisplayed = true;
1228        End(IDCLOSE);
1229        break;
1230      }
1231
1232      if (_cancelWasPressed)
1233        return true;
1234
1235      const bool paused = Sync.Get_Paused();
1236
1237      if (!paused)
1238      {
1239        OnPauseButton();
1240      }
1241
1242      _inCancelMessageBox = true;
1243      const int res = ::MessageBoxW(*this, LangString(IDS_PROGRESS_ASK_CANCEL), _title, MB_YESNOCANCEL);
1244      _inCancelMessageBox = false;
1245      if (res == IDYES)
1246        _cancelWasPressed = true;
1247
1248      if (!paused)
1249      {
1250        OnPauseButton();
1251      }
1252
1253      if (_externalCloseMessageWasReceived)
1254      {
1255        /* we have received kCloseMessage while we were in MessageBoxW().
1256           so we call OnExternalCloseMessage() here.
1257           it can show MessageBox and it can close dialog */
1258        OnExternalCloseMessage();
1259        return true;
1260      }
1261
1262      if (!_cancelWasPressed)
1263        return true;
1264
1265      MessagesDisplayed = true;
1266      // we will call Sync.Set_Stopped(true) in OnButtonClicked() : OnCancel()
1267      break;
1268    }
1269
1270    case IDB_PAUSE:
1271      OnPauseButton();
1272      return true;
1273    case IDB_PROGRESS_BACKGROUND:
1274      OnPriorityButton();
1275      return true;
1276  }
1277  return CModalDialog::OnButtonClicked(buttonID, buttonHWND);
1278}
1279
1280void CProgressDialog::CheckNeedClose()
1281{
1282  if (_needClose)
1283  {
1284    PostMsg(kCloseMessage);
1285    _needClose = false;
1286  }
1287}
1288
1289void CProgressDialog::ProcessWasFinished()
1290{
1291  // Set Window title here.
1292  if (!WaitMode)
1293    WaitCreating();
1294
1295  if (_wasCreated)
1296    PostMsg(kCloseMessage);
1297  else
1298    _needClose = true;
1299}
1300
1301
1302bool CProgressDialog::OnNotify(UINT /* controlID */, LPNMHDR header)
1303{
1304  if (header->hwndFrom != _messageList)
1305    return false;
1306  switch (header->code)
1307  {
1308    case LVN_KEYDOWN:
1309    {
1310      LPNMLVKEYDOWN keyDownInfo = LPNMLVKEYDOWN(header);
1311      switch (keyDownInfo->wVKey)
1312      {
1313        case 'A':
1314        {
1315          if (IsKeyDown(VK_CONTROL))
1316          {
1317            _messageList.SelectAll();
1318            return true;
1319          }
1320          break;
1321        }
1322        case VK_INSERT:
1323        case 'C':
1324        {
1325          if (IsKeyDown(VK_CONTROL))
1326          {
1327            CopyToClipboard();
1328            return true;
1329          }
1330          break;
1331        }
1332      }
1333    }
1334  }
1335  return false;
1336}
1337
1338
1339static void ListView_GetSelected(NControl::CListView &listView, CUIntVector &vector)
1340{
1341  vector.Clear();
1342  int index = -1;
1343  for (;;)
1344  {
1345    index = listView.GetNextSelectedItem(index);
1346    if (index < 0)
1347      break;
1348    vector.Add((unsigned)index);
1349  }
1350}
1351
1352
1353void CProgressDialog::CopyToClipboard()
1354{
1355  CUIntVector indexes;
1356  ListView_GetSelected(_messageList, indexes);
1357  UString s;
1358  unsigned numIndexes = indexes.Size();
1359  if (numIndexes == 0)
1360    numIndexes = (unsigned)_messageList.GetItemCount();
1361
1362  for (unsigned i = 0; i < numIndexes; i++)
1363  {
1364    const unsigned index = (i < indexes.Size() ? indexes[i] : i);
1365    // s.Add_UInt32(index);
1366    // s += ": ";
1367    s += _messageStrings[index];
1368    {
1369      s +=
1370        #ifdef _WIN32
1371          "\r\n"
1372        #else
1373          "\n"
1374        #endif
1375        ;
1376    }
1377  }
1378
1379  ClipboardSetText(*this, s);
1380}
1381
1382
1383static THREAD_FUNC_DECL MyThreadFunction(void *param)
1384{
1385  CProgressThreadVirt *p = (CProgressThreadVirt *)param;
1386  try
1387  {
1388    p->Process();
1389    p->ThreadFinishedOK = true;
1390  }
1391  catch (...) { p->Result = E_FAIL; }
1392  return 0;
1393}
1394
1395
1396HRESULT CProgressThreadVirt::Create(const UString &title, HWND parentWindow)
1397{
1398  NWindows::CThread thread;
1399  const WRes wres = thread.Create(MyThreadFunction, this);
1400  if (wres != 0)
1401    return HRESULT_FROM_WIN32(wres);
1402  CProgressDialog::Create(title, thread, parentWindow);
1403  return S_OK;
1404}
1405
1406static void AddMessageToString(UString &dest, const UString &src)
1407{
1408  if (!src.IsEmpty())
1409  {
1410    if (!dest.IsEmpty())
1411      dest.Add_LF();
1412    dest += src;
1413  }
1414}
1415
1416void CProgressThreadVirt::Process()
1417{
1418  CProgressCloser closer(*this);
1419  UString m;
1420  try { Result = ProcessVirt(); }
1421  catch(const wchar_t *s) { m = s; }
1422  catch(const UString &s) { m = s; }
1423  catch(const char *s) { m = GetUnicodeString(s); }
1424  catch(int v)
1425  {
1426    m = "Error #";
1427    m.Add_UInt32((unsigned)v);
1428  }
1429  catch(...) { m = "Error"; }
1430  if (Result != E_ABORT)
1431  {
1432    if (m.IsEmpty() && Result != S_OK)
1433      m = HResultToMessage(Result);
1434  }
1435  AddMessageToString(m, FinalMessage.ErrorMessage.Message);
1436
1437  {
1438    FOR_VECTOR(i, ErrorPaths)
1439    {
1440      if (i >= 32)
1441        break;
1442      AddMessageToString(m, fs2us(ErrorPaths[i]));
1443    }
1444  }
1445
1446  CProgressSync &sync = Sync;
1447  NSynchronization::CCriticalSectionLock lock(sync._cs);
1448  if (m.IsEmpty())
1449  {
1450    if (!FinalMessage.OkMessage.Message.IsEmpty())
1451      sync.FinalMessage.OkMessage = FinalMessage.OkMessage;
1452  }
1453  else
1454  {
1455    sync.FinalMessage.ErrorMessage.Message = m;
1456    if (Result == S_OK)
1457      Result = E_FAIL;
1458  }
1459}
1460
1461UString HResultToMessage(HRESULT errorCode)
1462{
1463  if (errorCode == E_OUTOFMEMORY)
1464    return LangString(IDS_MEM_ERROR);
1465  else
1466    return NError::MyFormatMessage(errorCode);
1467}
1468