

const
  // резделитель сообщений в журналах на форме
  // разделяет текст предыдущих сообщений от новых.
  cnMessagesFormDelimiter =
        #13#10'-------------------------------------------------'#13#10;

  // опции для функции ShowMessageValid.
  // детальное описание см. в подрограмме ShowMessageValid.
  // блокировать кнопку "прервать операцию"
  mvBreakDisabled     = '(BREAKHIDE)';
  // открывать модальное окно. если опции нет, окно открывается как не модальное.
  // ВНИМАНИЕ
  // строковое описание должно быть синхронизировано с параметром aOptions
  // подпрограммы ShowMessageValid.
  mvModal             = '(MODAL)';
  // отображать панель с ошибкой
  mvError             = '(ERRORSHOW)';
  // открывать окно как новое. если опции нет, то будет выполнен поиск окна
  // MessageNotValidateForm в компонентах Application.
  mvNew               = '(FORMNEW)';
  // локальные окна с сообщениями.
  mvLocal             = '(LOCAL)';
  // открывает сообщение в блокноте.
  mvNotepad           = '(NOTEPAD)';
  // указывается, что окно отображается для принятия решения: продолжить или
  // отказать
  mvNextOrBreak       = '(NEXTORBREAK)';
  // блокировать панель с кнопками продолжить/прервать
  mvControlDisabled   = '(CONTROLDIS)';
  // отображать окно поверх всех
  mvStayOnTop         = '(STAYONTOP)';
  // диалог выбора Да/нет
  mvDialogYN          = '(DIALOG:Y/N)';


var
  { ВНИМАНИЕ
    перечисленные здесь переменные нельзя использовать явно.
    это все для внутреннего использования этим модулем.
    все обращения выполнять только через подпрограммы!}
  vFormMessageValidDir: string;   // каталог, где лежать DFM образы форм
  vForm_MessageValid: TForm;    // форма сообщений об ошибках проверки данных
  vMessagePanelHeader: TPanel;
  vMessageLastText: TMemo;       // блокнот текущего сообщения
  vMessageHistoryText: TMemo;    // блокнот истории
  vTabHistoryText: TTabSheet;    // закладка с историей
  vTabLastText: TTabSheet;       // закладка с последним текстом
  vMessageHeader: TEdit;        // заголовок
  vMessageValidPgControl: TPageControl;   // панель закладок
  vMessageBBCancel: TButton;      // кнопка "прервать операцию"
  vFormMessageValidErrorPanel: TPanel;
  vFormMessageValidErrorLabel: TLabel;
  vFormMessageValidErrorBevel: Tbevel;
  vMessageValidPnControl: TPanel;
  // флаг фиксации открытия диалога с выбором: продолжить/отменить
  // фиксируется хотя бы одно открытие такого диалога.
  IsOpenedMessageDialogNextOrBreak: boolean;
  { список названий компонентов окон, которые требую разружения компонентов
    после закрытия форм. здесь хранятся строки Form.Name. в результате,
    когда окно закрывается, то, если имя находится в этом списке, то такой
    компонент будет разрушен.
    при завершении скрипта все окна, которые тут описаны, должны быть разрушены.}
  vFormMessageValidLocals: TStringList;
  vFormMessageValidPrefix: string;
  vFormMessageValidCounter: integer;
  // флаг инициализации модуля
  // это необходимо в счучае, если скрипт завершается при подготовке примера.
  // в этом случае скрипт не выполняется и модуль не инициализирован.
  // но при этом в секции finally вызывается финализация модуля и возникает
  // сбой разрушения несозданных объектов (Imp_FK_BH).
  // поэтому добавлен флаг, который =true, если выполнена полноценная инициализация,
  //  и =false, если выполнен вызов подпрограммы InitializeMessageValid
  // но сама инициализация не выполнена.
  vMessageValidInitialized: boolean;



function ShowMessageValid( aFileName: string;
                           aTitle: string = '';
                           aOptions: string = '(MODAL)';
                           aMessageEx: string = 'Продолжить?'): boolean;
var
  SL: TStringList;
  xModalResult: integer;
  xModal: boolean;
  xExecuteNotePad: boolean;
begin

    try
      // режим открытия в блокноте
      xExecuteNotePad := (POS( mvNotepad, aOptions) > 0);

      { если не трубется блокнот - открываем/создаем окно.
        если окно не можем открыть - вызываем блокнот.}
      if NOT xExecuteNotePad then
        if NOT DoGetMessageValid( (POS( mvNew, aOptions) > 0),
                                  (POS( mvLocal, aOptions) > 0))
          then xExecuteNotePad := TRUE;
      if xExecuteNotePad then begin
        if FileExists( aFileName)
          then ShellExecute( 'NotePad', aFileName, False);
        // режим модального диалога?
        // если диалог не требуется, возвращается всегда TRUE.
        if (POS( mvDialogYN, aOptions) <= 0)
          then Result := TRUE
          else
        if (MessageDlg(aMessageEx, mtConfirmation, mbYes + mbNo, 0) = mrNo)
          then Result := FALSE
          else Result := TRUE;
        EXIT;
      end;

      // режим модального диалога?
      xModal := (POS( mvModal, aOptions) > 0);

      // заголовок формы
      if (LENGTH( aTitle) >= 0)
        then vForm_MessageValid.Caption := aTitle;

      // заголовок - имя файла
      vMessageHeader.Text := aFileName;

      // если требуется отображать заголовок об ошибке - отображаем панель
      // с ошибкой
      if (POS( mvError, aOptions) > 0) then begin
        vFormMessageValidErrorPanel.Show;
        vFormMessageValidErrorLabel.Show;
        vFormMessageValidErrorBevel.Show;
        vFormMessageValidErrorBevel.Top := vMessageHeader.Top - 1;
        vFormMessageValidErrorPanel.Top := vFormMessageValidErrorBevel.Top - 1;
      end;

      // блокирование кнопки "прервать операцию"
      if (POS( mvBreakDisabled, aOptions) > 0) then begin
        if (vMessageBBCancel <> NIL)
          then vMessageBBCancel.Visible := FALSE;
      end else begin
        // кнопка "прервать операцию" разрешена
        if (vMessageBBCancel <> NIL)
          then vMessageBBCancel.Visible := TRUE;
      end;

      // управление панелью с кнопками
      if (POS( mvControlDisabled, aOptions) > 0) then begin
        if (vMessageValidPnControl <> NIL)
          then vMessageValidPnControl.Visible := FALSE;
      end else begin
        if (vMessageValidPnControl <> NIL)
          then vMessageValidPnControl.Visible := TRUE;
      end;

      // проверяем, если открывается как диалог принятия решения продолжить или
      // прервать операцию, то фиксируем открытие такого окна.
      if (POS( mvNextOrBreak, aOptions) > 0)
        then IsOpenedMessageDialogNextOrBreak := TRUE;

  

      // отображаем панель заголовка с названием файла журнала
      if NOT vMessagePanelHeader.Visible
        then vMessagePanelHeader.Show;

      // текущее сообщение
      if FileExists( aFileName)
        then vMessageLastText.Lines.LoadFromFile( aFileName);

      // суммарное сообщение - все старые + текущее
      // это сообение будет сохранено в примечании "история"
      SL:= TStringList.Create;
      try
        SL.Text := vMessageHistoryText.Lines.Text
                    + #13#10 + vMessageLastText.Lines.Text
                    + cnMessagesFormDelimiter;
        // итоговый лог
        SL.SaveToFile( aFileName);
        // всегда последнее сообщение отображаем активным
        vMessageValidPgControl.activePage := vTabLastText;
        if xModal
          then xModalResult := vForm_MessageValid.ShowModal
          else begin
            // отображение окна поверх всех - поддержка в АЦК-exchange 5.0.4.27
            // и выше. эти строки не будут компилироваться в младших версиях,
            // поэтому если это потребуется, эти строки нужно будет закоментировать
            if (POS( mvStayOnTop, aOptions) > 0) then begin
              if NOT CompareVersionAZK_Exchange(
                            'Открытие окна поверх всех',
                            '5.0.4.27',
                            FALSE)
                then vForm_MessageValid.BringOnTop;
            end;
            vForm_MessageValid.Show;
            //vForm_MessageValid.BringToFront;
            xModalResult := mrOk;
          end;

        if ( xModalResult = mrCancel)
          then begin
  
  //          if aHaltOnCancel then HaltEx( 'Отмена операции.');
            Result := False;
          end else begin
  
    	      // добавляем новый текст сообщения к истории
        	  vMessageHistoryText.Lines.Text := SL.Text;
    	      // отображаем панель истории
      	    vTabHistoryText.TabVisible := True;
      	    Result := True;
      	  end;
        finally
          SL.Free;
      end;
      except
        ExceptionProc( 'ShowMessageValid');
    end;

end;


{~WarningMessage
  отображение сообщения aMessage.
  все сообщения отображаются в одном глобальном окне.
  сообщение aMessage добавляется к текущим сообщениям и отображается в сумме
  с предыдущими сообщениями.
  результат:
    =true - продолжение операции;
    =false - требуется останов операции (выгрузки/загрузки)}
function WarningMessage( aMessage: string;
                         aShowImmediate: boolean): boolean;
begin

    try
      // здесь всегда ищем глобальное окно и новые окна не создаем.
      DoGetMessageValid( FALSE, FALSE);
      // накапливаем сообщение
      // если текст уже содержит сообщения - в начале добавляем разделитель.
      if (vMessageLastText.Lines.Count > 0)
        then vMessageLastText.Lines.Add( cnMessagesFormDelimiter);
      // добавляем сообщение
      vMessageLastText.Lines.Add( aMessage);
      if NOT aShowImmediate
        then Result := TRUE
        else begin
          // скрываем панель заголовка с названием файла журнала
          if vMessagePanelHeader.Visible
            then vMessagePanelHeader.Hide;
          // открываем панель с кнопками
          if (vMessageValidPnControl <> NIL)
            then vMessageValidPnControl.Visible := TRUE;
          // активируем флаг,что вызван диалог с выбором
          IsOpenedMessageDialogNextOrBreak := TRUE;
          // открываем диалог. разрешаем выбор: продолжить или отменить операцию.
          // возвращается выбор.
          if (vMessageBBCancel <> NIL)
            then vMessageBBCancel.Visible := TRUE;
          if (vForm_MessageValid.ShowModal <> mrCancel)
            then Result := TRUE
            else Result := FALSE;
  // если в результате выбрана отмена операции выполняем остановку скрипта.
  //        if NOT Result then HaltEx( 'Отмена операции.');
        end;
      except
        ExceptionProc( 'WarningMessage');
    end;

end;

  

function DoGetMessageValid( aNewForm, aLocalForm: boolean): boolean;

  procedure DoOnShow;
  var xbbCancel: TButton;
  begin
    // при открытии окна кнопку подстраиваем под правый край
    xbbCancel:= TButton( vForm_MessageValid.FindComponent('bbCancel'));
    xbbCancel.Left := (vForm_MessageValid.ClientWidth - 10) - xbbCancel.Width;
    // на всякий случай выравниваем контрол заголовка
    vMessageHeader.Left := 2;
    vMessageHeader.Width := vForm_MessageValid.ClientWidth - 6;
  end;

var
  xPControl: TPageControl;
  i: integer;
  s: string;
begin

    try
      vForm_MessageValid := NIL;
      if NOT(aNewForm) then //AND NOT(aLocalForm)) then
        // ищем окно среди компонентов, связанных с application
        for i := 0 to Application.ComponentCount - 1 do
          if (Application.GetComponent(i).ClassName = 'TForm') then
            if (Tform(Application.GetComponent(i)).Name='MessageNotValidateForm') then begin
            vForm_MessageValid := TForm( Application.GetComponent(i));
            BREAK;
          end;

      if (vForm_MessageValid = NIL) then begin
        // если требуется создать новое глобальное окно (которое должно быть
        // открыто после завершения скрипта), то такая возможность есть только
        // в AzkExchange версии 5.0.4.26 и выше. все что ниже - требует
        // использования подручных средств - блокнот!
        if (aNewForm AND NOT(aLocalForm)) then begin
          if NOT CompareVersionAZK_Exchange(
                        'Создание новых глобальных окон',
                        '5.0.4.26',
                        FALSE) then begin
            Result := FALSE;
            EXIT;
          end;
        end;

        vForm_MessageValid := TForm.Create(application);
        LoadForm( vFormMessageValidDir + 'Form_MessageNotValidate.dfm', vForm_MessageValid);

        // если требуется создать новое локальное окно, то определяем название
        // такого окна (названия класса формы) и сохраняем его, чтобы при
        // завершении скрипта закрывать такие окна автоматически.
        if (aNewForm AND aLocalForm) then begin
          INC( vFormMessageValidCounter);
          s := vFormMessageValidPrefix
                + INTTOSTR( vFormMessageValidCounter)
                + '_form';

          vForm_MessageValid.Name := s;
          vFormMessageValidLocals.Add( s);
        end;
      end;


      vForm_MessageValid.Caption := 'Журнал обработки ЭД';//'Ошибка проверки данных при выгрузке';

      // обязательно для вновь созданной или глобальной формы переопределить
      // событие отображения формы. это дает то, что для глобальных форм,
      // на этом событие может висеть обработчик давно уже завершившегося
      // скрипта, поэтому в момент отображения такой формы будет сбой.
      vForm_MessageValid.OnShow := @DoOnShow;


      if aNewForm
        then vForm_MessageValid.SetFreeOnHide;

      vMessagePanelHeader := TPanel( vForm_MessageValid.FindComponent('pnHeader'));
      vMessageLastText := TMemo( vForm_MessageValid.FindComponent('mmText'));
    	vMessageHistoryText := TMemo( vForm_MessageValid.FindComponent('mmHistory'));
    	vMessageHeader := TEdit( vForm_MessageValid.FindComponent('edHeader'));
      vMessageValidPgControl:= TPageControl( vForm_MessageValid.FindComponent('pControl'));
      vMessageValidPnControl:= TPanel( vForm_MessageValid.FindComponent('pnControl'));
      vTabHistoryText:= TTabSheet( vForm_MessageValid.FindComponent('tabMsgHistory'));
      vTabLastText := TTabSheet( vForm_MessageValid.FindComponent('tabMsgLast'));
      vMessageBBCancel := TButton( vForm_MessageValid.FindComponent('bbCancel'));
      vFormMessageValidErrorPanel:= TPanel( vForm_MessageValid.FindComponent('pnError'));
      vFormMessageValidErrorLabel:= TLabel( vForm_MessageValid.FindComponent('lbError'));
      vFormMessageValidErrorBevel:= Tbevel( vForm_MessageValid.FindComponent('bvPanelError'));
      vForm_MessageValid.Hide;

      Result := TRUE;
      except
        ExceptionProc( 'DoGetMessageValid');
    end;

end;

{~DoShowMessageValidFree
  разрешение всех локальных форм.
  если параметр aFreeAll=false, будут разрушены закрытые формы.
  если параметр aFreeAll=true, будут разрушены все локальные формы.
  подпрограмма оперирует только зарегистрированными формами в списке
  vFormMessageValidLocals. это формы, созданные с опцией mvLocal.}
procedure DoShowMessageValidFree( aFreeAll: boolean);
var
  xForm: TForm;
  i,xIndex: integer;
  xFree: boolean;
begin

    try
      for i := Application.ComponentCount - 1 downto 0 do
        if (UPPERCASE( Application.GetComponent(i).ClassName) = 'TFORM') then begin
          xForm:= TForm( Application.GetComponent(i));

          // проверяем, есть ли эта форма в списке открытых форм текущего скрипта
          xIndex := vFormMessageValidLocals.IndexOf( xForm.Name);

          if (xIndex <> -1) then begin
            if aFreeAll
              then xFree := TRUE
              else xFree := (NOT xForm.Visible);
            if xFree then begin

              vFormMessageValidLocals.Delete( xIndex);

              xForm.Free;

            end;
          end;
        end;
      except
        ExceptionLog( 'Сбой Un_MessageValidForm.DoShowMessageValidFree');
    end;

end;


procedure InitializeMessageValid( aPrefix, aDirIni: string);
begin
  try

    // инициализация пока не выполнена.
    // здесь выполняется только сброс флага. в этом случае при вызове
    // FinalizeMessageValid никаких ресурсов не сосвобождается!
    vMessageValidInitialized := FALSE;
    if ( LENGTH( aPrefix) <= 0) then EXIT;

    
    IsOpenedMessageDialogNextOrBreak  := FALSE;
    vFormMessageValidDir              := aDirIni + 'Forms\';
    vFormMessageValidPrefix           := aPrefix;
    vFormMessageValidCounter          := 0;
    vFormMessageValidLocals           := TStringList.Create;
    // ищем глобальное окно сообщений и сбрасываем все сообщения в нем.
    DoGetMessageValid( FALSE, FALSE);
    vMessageLastText.Lines.Clear;
    vMessageHistoryText.Lines.Clear;
    // скрываем панель с ошибкой
    vFormMessageValidErrorPanel.Hide;
    // инициализация выполнена
    vMessageValidInitialized := TRUE;
    
    except
      ExceptionLog( 'Сбой скрипта: "Un_MessageValidForm.InitializeMessageValid".');
  end;
end;

{~FinalizeMessageValid
   завершение работы с окнами сообщений.
   закрываем и разрушаем все локальные окна. глобальные окна не трогаем.}
procedure FinalizeMessageValid;
begin
  try
    
    if vMessageValidInitialized then begin
      vMessageValidInitialized := FALSE;
      DoShowMessageValidFree( TRUE);
      vFormMessageValidLocals.Free;
    end;
    
    except
      ExceptionLog( 'Сбой скрипта: "Un_MessageValidForm.FinalizeMessageValid".');
  end;
end;


begin

end.
