SmartFarm/SOURCE/SmartFarmHMI/SmartFarmHMI_FMX/gen_popup.pas
2026-09-04 10:53:44 +09:00

328 lines
12 KiB
ObjectPascal

procedure TfrmMain.OnIrriCondEdit(Sender: TObject);
var
ActIdx, CondType, CondIdx, FieldID: Integer;
ValPart: Integer;
Act: ^TIrriAction;
ValStr: string;
begin
if FRefreshingAutoUI then Exit;
if Sender is TComboBox then
begin
ActIdx := (Sender as TComboBox).Tag;
ValPart := Trunc((Sender as TComboBox).TagFloat);
end
else if Sender is TEdit then
begin
ActIdx := (Sender as TEdit).Tag;
ValPart := Trunc((Sender as TEdit).TagFloat);
end
else Exit;
if (ActIdx < 0) or (ActIdx > High(FIrriSchedules[FCurrentEditIrriSchedIdx].Actions)) then Exit;
Act := @FIrriSchedules[FCurrentEditIrriSchedIdx].Actions[ActIdx];
CondType := ValPart div 1000;
CondIdx := (ValPart div 10) mod 100;
FieldID := ValPart mod 10;
var PCondArray: ^TArray<TAutoCondItem>;
var PCondMode: ^TAutoCondMode;
case CondType of
0: begin PCondArray := @Act^.StartConds; PCondMode := @Act^.StartCondMode; end;
1: begin PCondArray := @Act^.StopConds; PCondMode := @Act^.StopCondMode; end;
2: begin PCondArray := @Act^.EndConds; PCondMode := @Act^.EndCondMode; end;
else Exit;
end;
if FieldID = 3 then // AND/OR Mode
begin
if (Sender as TComboBox).ItemIndex = 0 then PCondMode^ := acmAND
else PCondMode^ := acmOR;
end
else if (CondIdx >= 0) and (CondIdx < Length(PCondArray^)) then
begin
case FieldID of
0: // NodeID
begin
ValStr := (Sender as TComboBox).Items[(Sender as TComboBox).ItemIndex];
var ss := ValStr.Split([' ']); // ID만 추출
if Length(ss) > 0 then PCondArray^[CondIdx].NodeID := ss[0];
end;
1: // Op
PCondArray^[CondIdx].Op := TAutoCondOp((Sender as TComboBox).ItemIndex);
2: // Value
PCondArray^[CondIdx].Value := StrToFloatDef((Sender as TEdit).Text, 0.0);
end;
end;
SaveIrriSchedules;
end;
procedure TfrmMain.OnIrriCondAdd(Sender: TObject);
var
ActIdx, CondType: Integer;
Act: ^TIrriAction;
Item: TAutoCondItem;
begin
if FRefreshingAutoUI then Exit;
if not (Sender is TRectangle) then Exit;
ActIdx := (Sender as TRectangle).Tag;
CondType := Trunc((Sender as TRectangle).TagFloat) div 1000;
if (ActIdx < 0) or (ActIdx > High(FIrriSchedules[FCurrentEditIrriSchedIdx].Actions)) then Exit;
Act := @FIrriSchedules[FCurrentEditIrriSchedIdx].Actions[ActIdx];
Item.NodeID := '';
Item.Op := acoGT;
Item.Value := 0.0;
case CondType of
0: begin SetLength(Act^.StartConds, Length(Act^.StartConds)+1); Act^.StartConds[High(Act^.StartConds)] := Item; end;
1: begin SetLength(Act^.StopConds, Length(Act^.StopConds)+1); Act^.StopConds[High(Act^.StopConds)] := Item; end;
2: begin SetLength(Act^.EndConds, Length(Act^.EndConds)+1); Act^.EndConds[High(Act^.EndConds)] := Item; end;
end;
SaveIrriSchedules;
RefreshIrriActionPopupUI(FCurrentEditIrriSchedIdx);
end;
procedure TfrmMain.OnIrriCondDel(Sender: TObject);
var
ActIdx, CondType, CondIdx: Integer;
ValPart: Integer;
Act: ^TIrriAction;
begin
if FRefreshingAutoUI then Exit;
if not (Sender is TRectangle) then Exit;
ActIdx := (Sender as TRectangle).Tag;
ValPart := Trunc((Sender as TRectangle).TagFloat);
CondType := ValPart div 1000;
CondIdx := (ValPart div 10) mod 100;
if (ActIdx < 0) or (ActIdx > High(FIrriSchedules[FCurrentEditIrriSchedIdx].Actions)) then Exit;
Act := @FIrriSchedules[FCurrentEditIrriSchedIdx].Actions[ActIdx];
case CondType of
0: Delete(Act^.StartConds, CondIdx, 1);
1: Delete(Act^.StopConds, CondIdx, 1);
2: Delete(Act^.EndConds, CondIdx, 1);
end;
SaveIrriSchedules;
RefreshIrriActionPopupUI(FCurrentEditIrriSchedIdx);
end;
procedure TfrmMain.RefreshIrriActionPopupUI(ASchedIdx: Integer);
type
PIrriAction = ^TIrriAction;
var
i: Integer;
Card: TRectangle;
LblHeader: TLabel;
EdDelay: TEdit;
CbNode: TComboBox;
ChkDel: TCheckBox;
Act: PIrriAction;
TopLyt: TRectangle;
BaseY: Single;
begin
if (ASchedIdx < 0) or (ASchedIdx > High(FIrriSchedules)) then Exit;
lblIrriActionTitle.Text := '동작 순서 관리 : ' + FIrriSchedules[ASchedIdx].SchedName;
scrlIrriAction.BeginUpdate;
if Self.Focused <> nil then Self.Focused := nil;
try
for i := scrlIrriAction.Content.ControlsCount - 1 downto 0 do
begin
var C := scrlIrriAction.Content.Controls[i];
if C is TComboBox then (C as TComboBox).OnChange := nil;
if C is TEdit then (C as TEdit).OnExit := nil;
if C is TCheckBox then (C as TCheckBox).OnChange := nil;
C.Parent := nil; // macOS 안정
end;
for i := 0 to High(FIrriSchedules[ASchedIdx].Actions) do
begin
Act := @FIrriSchedules[ASchedIdx].Actions[i];
Card := TRectangle.Create(Self);
Card.Parent := scrlIrriAction;
Card.Align := TAlignLayout.Top;
Card.Margins.Rect := RectF(0, 0, 0, 12);
Card.Fill.Color := IfThen(Act^.IsSelected, $FF3A2020, $FF333333);
Card.Stroke.Color := $FF555555;
Card.XRadius := 5; Card.YRadius := 5;
{$IFDEF ANDROID}
var ChkBg := TRectangle.Create(Self);
ChkBg.Parent := Card;
ChkBg.Position.X := 10; ChkBg.Position.Y := 13;
ChkBg.Width := 29; ChkBg.Height := 29;
ChkBg.Fill.Color := claWhite; ChkBg.Stroke.Kind := TBrushKind.None;
ChkBg.XRadius := 4; ChkBg.YRadius := 4;
ChkBg.HitTest := False;
{$ENDIF}
ChkDel := TCheckBox.Create(Self);
ChkDel.Parent := Card;
ChkDel.Position.X := 8; ChkDel.Position.Y := 10;
ChkDel.Width := 24; ChkDel.Height := 24;
ChkDel.IsChecked := Act^.IsSelected;
ChkDel.Tag := i;
ChkDel.Scale.X := 1.5; ChkDel.Scale.Y := 1.5;
ChkDel.OnChange := OnIrriActionCheck;
LblHeader := BuildLabel(Card, Format('동작 순번 %d', [i+1]), 18, TAlignLayout.None, claWhite, True);
LblHeader.SetBounds(42, 10, 150, 30);
var LblNode := BuildLabel(Card, '작동 구역 (DO):', 16, TAlignLayout.None, claLightgray, False);
LblNode.SetBounds(10, 50, 120, 30);
CbNode := TComboBox.Create(Self);
CbNode.Parent := Card;
CbNode.SetBounds(130, 50, 200, 30);
var kDO: Integer;
for kDO := 0 to High(FNodeConfigs) do
begin
if FNodeConfigs[kDO].ID.StartsWith('DO_') then
begin
var LItm := TListBoxItem.Create(CbNode);
LItm.Parent := CbNode;
LItm.Height := 24 * 1.8;
LItm.Text := FNodeConfigs[kDO].ID + ' [' + (
if FNodeConfigs[kDO].CustomName <> '' then FNodeConfigs[kDO].CustomName
else if FIsKorean then FNodeConfigs[kDO].TitleKO
else FNodeConfigs[kDO].TitleEN) + ']';
CbNode.AddObject(LItm);
end;
end;
var kSelDO: Integer;
CbNode.ItemIndex := -1;
for kSelDO := 0 to CbNode.Items.Count - 1 do
if CbNode.Items[kSelDO].StartsWith(Act^.TargetNodeID + ' ') or
(CbNode.Items[kSelDO] = Act^.TargetNodeID) then
begin CbNode.ItemIndex := kSelDO; Break; end;
CbNode.Tag := i;
CbNode.TagFloat := 10;
CbNode.OnChange := OnIrriActionEdit;
var LblDelay := BuildLabel(Card, '쿨다운(초):', 16, TAlignLayout.None, claLightgray, False);
LblDelay.SetBounds(340, 50, 90, 30);
EdDelay := TEdit.Create(Self);
EdDelay.Parent := Card;
EdDelay.SetBounds(435, 50, 65, 30);
EdDelay.Text := IntToStr(Act^.DelaySeconds);
EdDelay.Tag := i; EdDelay.TagFloat := 11;
EdDelay.FilterChar := '0123456789';
EdDelay.OnExit := OnIrriActionEdit;
var LblTimeout := BuildLabel(Card, '제한(분):', 16, TAlignLayout.None, claLightgray, False);
LblTimeout.SetBounds(510, 50, 80, 30);
var EdTimeout := TEdit.Create(Self);
EdTimeout.Parent := Card;
EdTimeout.SetBounds(590, 50, 65, 30);
EdTimeout.Text := IntToStr(Act^.TimeoutMin);
EdTimeout.Tag := i; EdTimeout.TagFloat := 12;
EdTimeout.FilterChar := '0123456789';
EdTimeout.OnExit := OnIrriActionEdit;
BaseY := 100;
var DrawCondGroup := procedure(ATitle: string; AConds: TArray<TAutoCondItem>; AMode: TAutoCondMode; CondType: Integer)
var
k: Integer;
cSen, cOp, cMode: TComboBox; eVal: TEdit;
btnDel, btnAdd: TRectangle;
begin
var lTitle := BuildLabel(Card, ATitle, 14, TAlignLayout.None, claLightgray, True);
lTitle.SetBounds(10, BaseY, 80, 30);
cMode := TComboBox.Create(Self); cMode.Parent := Card;
cMode.SetBounds(95, BaseY, 70, 30);
cMode.Items.Add('AND'); cMode.Items.Add('OR');
cMode.ItemIndex := Integer(AMode);
cMode.Tag := i; cMode.TagFloat := CondType * 1000 + 3;
cMode.OnChange := OnIrriCondEdit;
btnAdd := CreateCustomButton(Card, '+ 추가', TAlignLayout.None, 60, OnIrriCondAdd);
btnAdd.SetBounds(175, BaseY, 60, 30);
btnAdd.Fill.Color := $FF226622;
btnAdd.Tag := i; btnAdd.TagFloat := CondType * 1000 + 4;
if Length(AConds) = 0 then
begin
BuildLabel(Card, ' [조건 없음 - 무조건 동작]', 14, TAlignLayout.None, claGray, False).SetBounds(250, BaseY, 200, 30);
end;
BaseY := BaseY + 40;
for k := 0 to High(AConds) do
begin
cSen := TComboBox.Create(Self); cSen.Parent := Card;
cSen.SetBounds(80, BaseY, 240, 30);
var kS: Integer;
for kS := 0 to High(FNodeConfigs) do
begin
var strId := FNodeConfigs[kS].ID;
// 유량계 힌트 표시 (노드 ID가 DI 이고 유량이름을 포함하거나 DI_F 등 규칙이 있다면)
var strName := FNodeConfigs[kS].CustomName;
if strName = '' then begin if FIsKorean then strName := FNodeConfigs[kS].TitleKO else strName := FNodeConfigs[kS].TitleEN; end;
if (strId.StartsWith('DI_')) and ((strName.Contains('유량')) or (strName.Contains('Flow'))) then
strName := strName + ' (리터/펄스)';
var LItmS := TListBoxItem.Create(cSen);
LItmS.Parent := cSen;
LItmS.Height := 24 * 1.8;
LItmS.Text := strId + ' [' + strName + ']';
cSen.AddObject(LItmS);
end;
var kSel: Integer;
cSen.ItemIndex := -1;
for kSel := 0 to cSen.Items.Count - 1 do
if cSen.Items[kSel].StartsWith(AConds[k].NodeID + ' ') or
(cSen.Items[kSel] = AConds[k].NodeID) then
begin cSen.ItemIndex := kSel; Break; end;
cSen.Tag := i; cSen.TagFloat := CondType * 1000 + k * 10 + 0;
cSen.OnChange := OnIrriCondEdit;
cOp := TComboBox.Create(Self); cOp.Parent := Card;
cOp.SetBounds(330, BaseY, 50, 30);
cOp.Items.Add('>'); cOp.Items.Add('<'); cOp.Items.Add('=');
cOp.ItemIndex := Integer(AConds[k].Op);
cOp.Tag := i; cOp.TagFloat := CondType * 1000 + k * 10 + 1;
cOp.OnChange := OnIrriCondEdit;
eVal := TEdit.Create(Self); eVal.Parent := Card;
eVal.SetBounds(390, BaseY, 70, 30);
eVal.Text := FloatToStr(AConds[k].Value);
eVal.Tag := i; eVal.TagFloat := CondType * 1000 + k * 10 + 2;
eVal.FilterChar := '0123456789.';
eVal.OnExit := OnIrriCondEdit;
btnDel := CreateCustomButton(Card, 'X', TAlignLayout.None, 30, OnIrriCondDel);
btnDel.SetBounds(470, BaseY, 40, 30);
btnDel.Fill.Color := $FFFF5555;
btnDel.Tag := i; btnDel.TagFloat := CondType * 1000 + k * 10 + 5;
BaseY := BaseY + 36;
end;
BaseY := BaseY + 8; // 여백 추가
end;
DrawCondGroup('시작 조건:', Act^.StartConds, Act^.StartCondMode, 0);
DrawCondGroup('정지 조건:', Act^.StopConds, Act^.StopCondMode, 1);
DrawCondGroup('종료 조건:', Act^.EndConds, Act^.EndCondMode, 2);
Card.Height := BaseY + 10;
end;
finally
scrlIrriAction.EndUpdate;
end;
end;