unit uPasteThread;

//По списку вставляет в выделенный узел
//Если была пометка Cut, то есть вырезать, то удаляет ранее выделенные узлы

interface

uses
   System.SysUtils, System.Classes, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param,
  FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Stan.Async,
  FireDAC.DApt, FireDAC.UI.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Phys,
  FireDAC.VCLUI.Wait, FireDAC.Phys.MySQLDef, FireDAC.Phys.MySQL, Data.DB, FireDAC.Comp.Client,
  FireDAC.Comp.DataSet,
  Vcl.ComCtrls, // Для TTreeView
  Contnrs,vcl.dialogs,
  syncobjs,vcl.Forms,vcl.Controls, uSelectionOptions,uSelectedOrChecked,uDBConnectionBPL,
  uTreeView,uSQLQueries,uEtcetra;

type
  TPasteThread = class(TThread)
  private

  FIDofCopiedNode:integer;

  FIDList:TStringList; // Список ID для копирования
  FIDListInserted:TStringList; // Список ID, который получится после копирования

  FDBTreeView:TDBTreeView; // << Cюда извне будем передавать ссылку на объект дерева

  FCut:boolean;

  FSQLQueries:TSQLQueries;

  FEvent:TEvent; // << Для синхронизации с потоком переиндексации снаружи


  //Основные методы
  function  CopyRecordInDB(SourceID: integer): integer;
  procedure CopyIDListToDB;
  procedure Structuring;
  Procedure UpdateNodeInTree;
  Procedure UncheckAllCheckBoxes; // снятие галочек после вставки

  //Удаление в случае вырезки
  procedure DeleteNodesFromDB(IDListChecked: TStringlist);
//  procedure DeleteNodesFromTree;


  procedure DisposeNodeData(Node:TTreeNode);
  procedure DeleteNodesFromTree3;

  //Вспомогательные
  Function IsFolderByID(NodeID: integer): boolean;
  Function FindParentInDB(ID:integer):integer;
  Function FindNodeInTree(ID:Integer): TTreeNode;
  Function IsParent(IDParent:integer; IDChildren:integer ):boolean; // ребенок ли, по ID?

  public

  property IDList:TStringList read FIDList write FIDList;
  property DBTreeView:TDBTreeView read FDBTreeView write FDBTreeView;
  property IDofCopiedNode:integer read FIDofCopiedNode write FIDofCopiedNode;
  property Cut:boolean read FCut write FCut;

  property Event:TEvent read FEvent write FEvent;


  procedure MyCreate;

  protected
    procedure Execute; override;
  end;

implementation


{ TPasteThread }

//------------------------------EXECUTE-------------------------------------------------------------
procedure TPasteThread.Execute;
var ID:integer; Level1,Level2:integer;
begin


 try
  Screen.Cursor:=crSQLWait;

    FEvent.ReSetEvent; //<< Вошли в событие

     ID:=Integer(FDBTreeView.Selected.data^); // <<Узел, в который вставляем


    if FIDList.IndexOf(ID.ToString)=-1 then // //НЕЛЬЗЯ РОДИТЕЛЕЙ В ДЕТЕЙ ВСТАВЛЯТЬ
    // begin
    //if not IsParent(FIDofCopiedNode,ID)  then  // << Так неправильно..., некорректно работало

     if IsFolderByID(ID) then  // //ТАКЖЕ НЕЛЬЗЯ ВСТАВЛЯТЬ НЕ В ПАПКИ...


        begin
        Screen.Cursor:=crSQLWait;

         CopyIDListToDB; // Копирует записи из FIDList в базу
         Structuring;   // Структурирует базу по новому

         if FCut then
          begin
            DeleteNodesFromDB(FIDList);
            Synchronize(DeleteNodesFromTree3);
          end;

        Synchronize(UpdateNodeInTree); // Обновляет информацию в дереве
        Synchronize(UncheckAllCheckBoxes);

        Screen.Cursor:=crDefault;
        end; // else

    //    raise Exception.Create('Вставка элементов возможна только в папки');

    //end else raise Exception.Create('Вставка невозможна. Конечная папка является дочерней к вставляемой');






 finally
 Screen.Cursor:=crDefault;

   //Освобождение
  FreeAndNil(FIDList);
  FreeAndNil(FIDListInserted);
  FreeAndNil(FSQLQueries);

  FEvent.SetEvent; // Вышли из события

 end;






end;


//------------------------------CREATE--------------------------------------------------------------
procedure TPasteThread.MyCreate;
begin

//Создание
FIDList:=TStringList.Create;
FIDListInserted:=TStringList.Create;

FSQLQueries:=TSQLQueries.Create(nil);
FSQLQueries.Name:='PasteThread';
end;






//---------------------Копирование 1 записи в БД-CopyRecordToDB-------------------------------------
function TPasteThread.CopyRecordInDB(SourceID: integer): integer;

var qSelectInsert:TFDQuery; qGetKey:TFDQuery;  DBConnectionLocal:TDBConnectionBPL;
//    SQLQueries:TSQLQueries;
begin

 //Создание
 DBConnectionLocal:=TDBConnectionBPL.Create(nil);
// SQLQueries:=TSQLQueries.Create(nil);

 qSelectInsert:=TFDQuery.Create(nil);
 qSelectInsert.Connection:=DBConnectionLocal.FDConnection;

 //Собственно копирование
 qSelectInsert.SQL.Text:=FSQLQueries.CopyRecordInDB.Text;
{ 'INSERT INTO `treeview_db`.`tree`'+

 '(`IdParent`,'+
  '`Name`,'+
  '`Index`,'+
  '`isChecked`,'+
  '`IsExpanded`,'+
  '`Level`,'+
  '`IsFolder`)'+

 'SELECT'+

 '`IdParent`,'+
 '`Name`,'+
 '`Index`,'+
 '`isChecked`,'+
 '`IsExpanded`,'+
 '`Level`,'+
 '`IsFolder`'+

 ' from'+
 '`treeview_db`.`tree` where id=:id' ;
 }
 qSelectInsert.Params.ParamValues['id']:=SourceId;
 qSelectInsert.ExecSQL;



 //----------Получение ID последней вставленной записи

           qGetKey:=TFDQuery.Create(nil);
           qGetKey.Connection:=DBConnectionLocal.FDConnection;
           qGetKey.SQL.Text:=FSQLQueries.MaxID.Text;
//           'SELECT Max(id) as newkey FROM treeview_db.tree;';

            //Получение id последней записи
           with qGetKey do
           begin
             Open;
             Result:=qGetKey.FieldByName('NewKey').AsInteger;
             Close;
           end;


// Проверка, если совпадают имена у элементов вставляемого списка и



// Освобождение

FreeAndNil(DBConnectionLocal);
FreeAndNil(qGetKey);
FreeAndNil( qSelectInsert);
//FreeAndNil(SQLQueries);

end;


//---------------------------Копирование всех записей по FIDLIST в БД-------------------------------

procedure TPasteThread.CopyIDListToDB;
var i:integer;
    LastInsertedID:integer;
    ID:integer;


begin

    //Защита, выходим если ничего не выбрано или выбранный узел не папка
    if Assigned(FDBTreeView.Selected) then

    begin
    ID:=Integer(FDBTreeView.Selected.Data^);
    If not IsFolderByID(ID) then exit;
    end else exit;


     //Копируем по списку, собираем ID скопированных в новый список
     for i := 0 to FIDList.Count-1 do
     begin

     LastInsertedID:=CopyRecordInDB(IDList[i].ToInteger());
     FIDListInserted.Add(LastInsertedID.ToString);

     end;



end;


//-------------------------Структурирование, восстановление иерархии--------------------------------

procedure TPasteThread.Structuring;
var
  i: Integer; IDParent:Integer; ParentIndexInIDListChecked:integer;
  qUpdateIdParent:TFDQuery;
  DBConnectionLocal:TDBConnectionBPL;
 // SQLQueries:TSQLQueries;

begin

  //Создание
  DBConnectionLocal:=TDBConnectionBPL.Create(nil);

  qUpdateIdParent:=TFDQuery.Create(nil);

//Готовим qUpdateIDParent
qUpdateIdParent.Connection:=DBConnectionLocal.FDConnection;

qUpdateIdParent.SQL.Text:=FSQLQueries.UpdateIdParent.Text;
//'UPDATE `treeview_db`.`tree` SET `IdParent`=:idParent WHERE `id`=:id;';
//----------------------------------------

// Цикл
for i := 0 to FIDList.Count-1 do
 begin
  IDParent:=FindParentInDB(FIDList[i].ToInteger()); // <<< Ищем ID родителя по базе


  // Если родитель не в списке IDListChecked
  if FIDList.IndexOf(IDParent.ToString())=-1 then
//   showmessage('Родитель в списке IDListChecked ');

        begin

        with qUpdateIdParent.Params

            do begin


            ParamValues['idParent']:=(Integer(FDBTreeView.Selected.Data^));
            ParamValues['id']:=FIDListInserted[i];   //<<< Так работает


            end;

             qUpdateIdParent.ExecSQL;

        end

        else

        begin
        ParentIndexInIDListChecked:=FIDList.IndexOf(IDParent.ToString());

        // IDListInserted[i] - индекс конкретного ребенка для которого найден родитель;

        with qUpdateIdParent.Params

            do begin

          //  showmessage(IDListInserted[ParentIndexInIDListChecked]);

            ParamValues['idParent']:=(FIDListInserted[ParentIndexInIDListChecked]);
            ParamValues['id']:=FIDListInserted[i];   //<<< Так работает

            end;

             qUpdateIdParent.ExecSQL;

        end;


 // for...
 end;


 FreeAndNil(qUpdateIdParent);
 FreeAndNil(DBConnectionLocal);
// FreeAndNil(SQLQueries);


end;

//---------------------------------Обновление дерева------------------------------------------------

procedure TPasteThread.UpdateNodeInTree;
var IDParent:integer;

    ParentNode:TTreeNode;
    Node:TTreeNode;

    i:integer;
    DBTreeView:TTreeView;
    qSelectAll:TFdQuery;
    name:string;
    id:^integer;
    DBConnectionLocal:TDBConnectionBPL;

    NameField:string;

    IsCheckedField:string;

//    SQLQueries:TSQLQueries;

begin

  //Создание

  DBConnectionLocal:=TDBConnectionBPL.Create(nil);
//  SQLQueries:=TSQLQueries.Create(nil);

  DBTreeView:=FDBTreeView;

  NameField:=FDBTreeView.DBTreeViewTable.NodeName;
  IsCheckedField:=FDBTreeView.DBTreeViewTable.IsChecked;


  qSelectAll:=TFdQuery.Create(nil);
  qSelectAll.Connection:=DBConnectionLocal.FDConnection;
  qSelectAll.SQL.Text:=FSQLQueries.SelectByID.Text;
//  'SELECT * FROM treeview_db.tree where id=:id;';


  //Раскрываем узел, в который вставляем

  if DBTreeView.Selected<>nil then
  DBTreeView.Selected.Expand(false);


  for i := 0 to FIDListInserted.Count-1 do

    begin
    IDParent:=FindParentInDB(FIDListInserted[i].ToInteger());


      // Если родитель во вставленном списке
      if   FIDListInserted.IndexOf(IDParent.ToString())<>-1 then

      begin

        ParentNode:=FindNodeInTree(IDParent);

        qSelectAll.Params.ParamValues['id']:=FIDListInserted[i];

        qSelectAll.Open();
        //Name:=qSelectAll.FieldByName('name').AsString;
        Name:=qSelectAll.FieldByName(NameField).AsString;
        qSelectAll.Close;

        new(id); id^:=FIDListInserted[i].ToInteger;


      Node:=DBTreeView.Items.AddChildObject(ParentNode,Name,id);


        //Обновление StateIndex
         with qSelectAll do
          begin

          Open();

//          if FieldByName('IsChecked').AsBoolean=true
          if FieldByName(IsCheckedField).AsBoolean=true then
          Node.StateIndex:=2 else
          Node.StateIndex:=1;

          Close();

          end;

      end else

      begin

      //Если родителя нет в списке
         ParentNode:=DBTreeView.Selected;

        qSelectAll.Params.ParamValues['id']:=FIDListInserted[i];
        qSelectAll.Open();
        //Name:=qSelectAll.FieldByName('name').AsString;
        Name:=qSelectAll.FieldByName(NameField).AsString;
        qSelectAll.Close;

        new(id); id^:=FIDListInserted[i].ToInteger;


        Node:=DBTreeView.Items.AddChildObject(ParentNode,Name,id);



         //Обновление StateIndex
         with qSelectAll do
          begin

          Open();
          //if FieldByName('IsChecked').AsBoolean=true then
          if FieldByName(IsCheckedField).AsBoolean=true then
          Node.StateIndex:=2 else
          Node.StateIndex:=1;

          Close();

          end;



      end;



    //for
    end;


 DBTreeView.Selected.Expand(false);

 //Освобождение
 FreeAndNil(DBConnectionLocal);
 FreeAndNil(qSelectAll);
// FreeAdNil(SQLQueries);


end;


//-------------------------------Вспомогательная, проверка на папку по базе

function TPasteThread.IsFolderByID(NodeID: integer): boolean;
var qCheckIsFolder:TFDQuery;
    DBConnectionLocal:TDBConnectionBPL;
//    SQLQueres:TSQLQueries;

    IsFolderField:string;

begin


IsFolderField:=FDBTreeView.DBTreeViewTable.IsFolder;



//Создание
DBConnectionLocal:=TDBConnectionBPL.Create(nil);
//SQLQueries:=TSQLQueries.Create(nil);
qCheckIsFolder:=TFDQuery.Create(nil);
qCheckIsFolder.Connection:=DBConnectionLocal.FDConnection;


qCheckIsFolder.SQL.Text:=FSQLQueries.CheckIsFolder.Text+NodeID.ToString;
//'Select * from `treeview_db`.`tree` where id='+NodeID.ToString;

//Проверка на папку
qCheckIsFolder.Open();

//IsFolderField
//if qCheckIsFolder.FieldByName('IsFolder').AsBoolean=true then result:=true else
if qCheckIsFolder.FieldByName(IsFolderField).AsBoolean=true then result:=true
else
Result:=false;
qCheckIsFolder.Close;

//Освобождение
FreeAndNil(DBConnectionLocal);
FreeAndNil(qCheckIsFolder);
//FreeAndNil(SQLQueries);
//
end;

//-------------------------Поиск родителя в БД------------------------------------------------------


function TPasteThread.FindParentInDB(ID: integer): integer;
var qSelectParent:TFDQuery;
    DBConnectionLocal:TDBConnectionBPL;
//    SQLQueries:TSQLQueries;

    idParentField:string;

begin

 idParentField:=DBTreeView.DBTreeViewTable.idParent;

 DBConnectionLocal:=TDBConnectionBPL.Create(nil);
// SQLQueries:=TSQLQueries.Create(nil);
 qSelectParent:=TFDQuery.Create(nil);
 qSelectParent.Connection:=DBConnectionLocal.FDConnection;



 with qSelectParent do
 begin

   SQL.Text:=FSQLQueries.SelectParent.Text+ID.ToString;;
//   'Select * from `treeview_db`.`tree` where id='+ID.ToString;
   Open;
   //Result:=FieldByName('idParent').AsInteger;
   Result:=FieldByName(idParentField).AsInteger;
   Close;

 end;

//Освобождение
FreeAndNil(DBConnectionLocal);
FreeAndNil(qSelectParent);
//FreeAndNil(SQLQueries);

end;
//--------------------------------------------------------------------------------------------------


//--------------------------Поиск узла в дереве-----------------------------------------------------

function TPasteThread.FindNodeInTree(ID: Integer): TTreeNode;
var i:integer; DBTreeView:TDBTreeView;

begin

DBTreeView:=FDBTreeView;

 Result:=nil; //Если родитель не будет найден, то это нулевой уровень
//Иначе, это будет какой-то конкретный узел
 for i := 0 to DBTreeView.Items.Count-1 do


 if DBTreeView.Items.Item[i].Data<>nil then

 begin
     if Integer(DBTreeView.Items.Item[i].Data^)=ID then

   begin
     Result:=DBTreeView.Items.Item[i];
     Break;
   end;
 end;




end;


//------------------------Проверяет ребенок ли по ID------------------------------------------------

function TPasteThread.IsParent(IDParent, IDChildren: integer): boolean;

var Node:TTreeNode;

begin

 Result:=false;

 Node:=FindNodeInTree(IDChildren);
 if Node=nil then exit;


repeat
  if Integer(Node.Data^)=IDParent then

     begin
       Result:=true; exit;
     end;

  Node:=Node.Parent;
until Node=nil;


end;

//--------------------------------------------------------------------------------------------------



//------------------------Удаление из базы данных по списку------------------------------

procedure TPasteThread.DeleteNodesFromDB(IDListChecked: TStringlist);

var qDelete:TFDQuery;  i:integer;
    DBConnection:TDBConnectionBPL;
//    SQLQueries:TSQLQueries;

begin

//Создание
DBConnection:=TDBConnectionBPL.Create(nil);
//SQLQueries:=TSQLQueries.Create(nil);
qDelete:=TFDQuery.Create(nil);
qDelete.Connection:=DBConnection.FDConnection;

qDelete.SQL.Text:=FSQLQueries.DeleteNodesFromDB.Text;
//'DELETE FROM `treeview_db`.`tree` WHERE `id`=:id;';
qDelete.Connection:=DBConnection.FDConnection;



 for i := 0 to IDListChecked.Count-1 do
   begin
    with qDelete.Params do
     ParamValues['ID']:=IDListChecked[i];
     qDelete.ExecSQL;
   end;


//Освобождение
FreeAndNil(DBConnection);
FreeAndNil(qDelete);
//FreeAndNil(SQLQueries);


end;


//----------------------------Удаление из дерева по списку----------------------

{
procedure TPasteThread.DeleteNodesFromTree;
var i,j:integer;
    Node:TTreeNode;
    DBTreeViewLocal:TDBTreeView;
//Этот код дает течь в памяти
begin


  //Присвоение
  DBTreeViewLocal:=Self.FDBtreeView;


//Удаление самих узлов
for i := FIDList.Count-1 downto 0 do
  begin

  Node:=FindNodeInTree(FIDList[i].ToInteger());

  if Node<>nil then
      begin
      Dispose(Node.Data);

      end;

      DBTreeViewLocal.Items.Delete(Node);

  end;


end;
}

//-------------------------Рекурсивное удаление узла из дерева



//-------------------Очистили память под указателями всех потомков 1 узла
//--------------------и самого узла

procedure TPasteThread.DisposeNodeData(Node:TTreeNode);
var i:integer;
begin

Dispose(Node.Data);
if Node.HasChildren then
 for i := 0 to Node.Count-1 do DisposeNodeData(Node[i]);

end;

procedure TPasteThread.DeleteNodesFromTree3;
var i:integer; Node:TTreeNode; DBTreeViewLocal:TDBTreeView;
begin

  DBTreeViewLocal:=Self.FDBTreeView;

// Чистим память под указателями
for i := FIDList.Count-1 downto 0  do
   begin

   Node:=FindNodeInTree(FIDList[i].ToInteger());
   if Node<>nil then

       begin
       DisposeNodeData(Node);

       DBTreeViewLocal.Items.Delete(Node);
       end;


   end;


end;


// Снятие галочек после вставки

procedure TPasteThread.UncheckAllCheckBoxes;
var DMEtcetra:TDMEtcetra;
begin
DMEtcetra:=TDMEtcetra.Create(nil);

DMEtcetra.ClearAllCheckBoxes2(FDBTreeView);

FreeAndNil(DMEtcetra);


end;



end.
