328 lines
12 KiB
ObjectPascal
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;
|