| 1 | unit SunriseChatCoreUnit;
|
|---|
| 2 |
|
|---|
| 3 | interface
|
|---|
| 4 |
|
|---|
| 5 | uses
|
|---|
| 6 | SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
|
|---|
| 7 | Forms, StdCtrls, ExtCtrls, SunriseChatCoreUtils, DateUtils, Dialogs;
|
|---|
| 8 |
|
|---|
| 9 | const
|
|---|
| 10 | ProtocolVersion = 3;
|
|---|
| 11 | CoreVersion = '3.0';
|
|---|
| 12 | RemoveTimeout = 33/24/3600; // 33 seconds
|
|---|
| 13 | AliveCommandSendingPeriod = 5; // in seconds
|
|---|
| 14 |
|
|---|
| 15 | UserStatusModeText: array[0..4] of string = ('Online', 'Away', 'Writing',
|
|---|
| 16 | 'Invisible', 'Offline');
|
|---|
| 17 |
|
|---|
| 18 | DefaultEventsFormat: array[0..22] of string = ('<%0:s> %1:s', '<%0:s> %3:s: %1:s',
|
|---|
| 19 | 'User %0:s connected from %1:s', 'User %0:s disconnected', 'User %0:s restored',
|
|---|
| 20 | 'User %0:s timeout', 'User %0:s went off', 'User %0:s got beck',
|
|---|
| 21 | 'Callup sended to user %0:s', 'Callup received from user %0:s',
|
|---|
| 22 | 'Ping sended to user %0:s', 'Ping response received from user %0:s',
|
|---|
| 23 | 'User %0:s changed nick to %1:s', 'Nick %0:s conflict',
|
|---|
| 24 | 'Room %1:s created by user %0:s', 'User %0:s left room %1:s',
|
|---|
| 25 | 'User %0:s gone to auto away mode', 'User %0:s returned from auto away mode',
|
|---|
| 26 | '%0:s hours', 'Welcome %0:s!', 'Goodbye %0:s!', 'User %0:s invited to private room',
|
|---|
| 27 | 'Custom event');
|
|---|
| 28 |
|
|---|
| 29 | type
|
|---|
| 30 | TSunriseChatCore = class;
|
|---|
| 31 |
|
|---|
| 32 | TSystemCommand = (scUnknown, scMessage, scConnect, scDisconnect, scCallUp,
|
|---|
| 33 | scCallUpResponse, scPing, scPingResponse, scAlive, scWhoIs, scCreateRoom,
|
|---|
| 34 | scLeaveRoom, scUserInfo, scCustomCommand);
|
|---|
| 35 |
|
|---|
| 36 | TAppEventType = (aeCommonMessage, aeOneUserMessage, aeUserConnect,
|
|---|
| 37 | aeUserDisconnect, aeUserRestore, aeUserTimeout, aeUserGoAway, aeUserGoBack,
|
|---|
| 38 | aeSendCallUp, aeReceiveCallUp, aeSendPing, aePingResponse, aeUserChangeNick,
|
|---|
| 39 | aeUserChangeNickConflict, aeCreateRoom, aeLeaveRoom, aeUserAutoGoAway,
|
|---|
| 40 | aeUserAutoGoBack, aeShowHours, aeStart, aeEnd, aeUserInvited, aeCustomEvent);
|
|---|
| 41 |
|
|---|
| 42 | TMessageEvent = procedure (Text: string; Color: Integer) of object;
|
|---|
| 43 | TClassMethod = procedure of object;
|
|---|
| 44 |
|
|---|
| 45 | TRoomType = (rtPublic, rtPrivate);
|
|---|
| 46 | TRoomLine = class(TPersistent)
|
|---|
| 47 | public
|
|---|
| 48 | Nick: string;
|
|---|
| 49 | Text: string;
|
|---|
| 50 | Font: TFont;
|
|---|
| 51 | Time: TDateTime;
|
|---|
| 52 | EventType: TAppEventType;
|
|---|
| 53 | constructor Create;
|
|---|
| 54 | procedure Assign(Source: TPersistent); override;
|
|---|
| 55 | destructor Destroy; override;
|
|---|
| 56 | end;
|
|---|
| 57 |
|
|---|
| 58 | TRoom = class
|
|---|
| 59 | private
|
|---|
| 60 | FParent: TSunriseChatCore;
|
|---|
| 61 | public
|
|---|
| 62 | Id: Cardinal;
|
|---|
| 63 | Name: string;
|
|---|
| 64 | Typ: TRoomType;
|
|---|
| 65 | StartLine: Integer;
|
|---|
| 66 | Count: Integer;
|
|---|
| 67 | Lines: TList; // of TRoomLine;
|
|---|
| 68 | constructor Create(Parent: TSunriseChatCore);
|
|---|
| 69 | procedure Select;
|
|---|
| 70 | procedure Remove;
|
|---|
| 71 | destructor Destroy; override;
|
|---|
| 72 | end;
|
|---|
| 73 |
|
|---|
| 74 | TUserStatusMode = (usOnline, usAway, usWriting, usInvisible, usOffline);
|
|---|
| 75 |
|
|---|
| 76 | TClientIdentification = record
|
|---|
| 77 | Machine: Cardinal;
|
|---|
| 78 | User: Cardinal;
|
|---|
| 79 | end;
|
|---|
| 80 |
|
|---|
| 81 | TUser = class(TPersistent)
|
|---|
| 82 | private
|
|---|
| 83 | FParent: TSunriseChatCore;
|
|---|
| 84 | FNick: string;
|
|---|
| 85 | procedure SetNick(const Value: string);
|
|---|
| 86 | public
|
|---|
| 87 | HostName: string;
|
|---|
| 88 | Id: TClientIdentification;
|
|---|
| 89 | Female: Boolean;
|
|---|
| 90 | Color: Integer;
|
|---|
| 91 | Client: string;
|
|---|
| 92 | ClientVersion: string;
|
|---|
| 93 | CoreVersion: string;
|
|---|
| 94 | OSVersion: string;
|
|---|
| 95 | OSUser: string;
|
|---|
| 96 | Status: TUserStatusMode;
|
|---|
| 97 | LastTime: TDateTime; // Time of last Alive command (local)
|
|---|
| 98 | Delay: TDateTime; // Time between LastTime
|
|---|
| 99 | IdleTime: Integer; // User inactivity delay in seconds
|
|---|
| 100 | UpTime: TDateTime; // Time of start of user client application
|
|---|
| 101 | LocalSystemTime: TDateTime; // Current system time on user computer
|
|---|
| 102 | UserInfoTime: TDateTime; // Time of last UserInfo command
|
|---|
| 103 | NickTime: TDateTime; // Time of start of using nick
|
|---|
| 104 | Reason: string; // Reason in away mode
|
|---|
| 105 | Sequence: Integer; // Command sequence number
|
|---|
| 106 | ErrorCount: Integer; // Number of sequence errors
|
|---|
| 107 | BlockMessages: Boolean; // Blocking user activity
|
|---|
| 108 | DetailInfo: string; // User description
|
|---|
| 109 | property Nick: string read FNick write SetNick;
|
|---|
| 110 | constructor Create(Parent: TSunriseChatCore);
|
|---|
| 111 | procedure Assign(Source: TPersistent); override;
|
|---|
| 112 | end;
|
|---|
| 113 |
|
|---|
| 114 | TAddMessageMethod = procedure(EventType: TAppEventType; Room: TRoom;
|
|---|
| 115 | const Args: array of const; RoomLine: TRoomLine) of object;
|
|---|
| 116 |
|
|---|
| 117 | TSunriseChatCore = class(TComponent)
|
|---|
| 118 | private
|
|---|
| 119 | FCounter: Integer;
|
|---|
| 120 | FTimer1: TTimer;
|
|---|
| 121 | FOnAddMessage: TAddMessageMethod;
|
|---|
| 122 | FOnChangeNetworkState: TClassMethod;
|
|---|
| 123 | FOnUserListUpdate: TClassMethod;
|
|---|
| 124 | // FOnPingResponse: TClassMethod;
|
|---|
| 125 | // FOnCallUp: TClassMethod;
|
|---|
| 126 | // FOnNickChange: TClassMethod;
|
|---|
| 127 | FOnRoomListChanged: TClassMethod;
|
|---|
| 128 | FMaxRoomLines: Integer;
|
|---|
| 129 | LastHour: Word;
|
|---|
| 130 | FOnSendCommand: TGetStrProc;
|
|---|
| 131 | FUseDefaultEventsText: Boolean;
|
|---|
| 132 | procedure FTimer1Timer(Sender: TObject);
|
|---|
| 133 | procedure SetMaxRoomLines(Number: Integer);
|
|---|
| 134 | function SameClientId(Id1, Id2: TClientIdentification): Boolean;
|
|---|
| 135 | protected
|
|---|
| 136 | FActive: Boolean;
|
|---|
| 137 | procedure SetActive(const Value: Boolean);
|
|---|
| 138 | public
|
|---|
| 139 | LocalUser: TUser;
|
|---|
| 140 | UserList: TList; // of TUser;
|
|---|
| 141 | ActiveRoom: TRoom;
|
|---|
| 142 | RoomList: TList; // of TRoom;
|
|---|
| 143 | FAutoAwayDelay: Integer;
|
|---|
| 144 | constructor Create(AOwner: TComponent); override;
|
|---|
| 145 | procedure DeleteRoom(RoomIndex: Integer);
|
|---|
| 146 | procedure AddMessage(MessageEventType: TAppEventType; Room: TRoom; const Args: array of const);
|
|---|
| 147 | procedure DeleteOfflineUsers;
|
|---|
| 148 | procedure UpdateUser(User: TUser);
|
|---|
| 149 | procedure SendCommand(Command: TSystemCommand; Text: string = '';
|
|---|
| 150 | DestinationMachineId: Cardinal = 0; DestinationUserId: Cardinal = 0);
|
|---|
| 151 | procedure ProcessCommand(Text: string);
|
|---|
| 152 | destructor Destroy; override;
|
|---|
| 153 | published
|
|---|
| 154 | property AutoAwayDelay: Integer read FAutoAwayDelay write FAutoAwayDelay;
|
|---|
| 155 | property UseDefaultEventsText: Boolean read FUseDefaultEventsText write FUseDefaultEventsText;
|
|---|
| 156 | property Active: Boolean read FActive write SetActive;
|
|---|
| 157 | property MaxRoomLines: Integer read FMaxRoomLines write SetMaxRoomLines;
|
|---|
| 158 | // property OnNickChange: TClassMethod read FOnNickChange write FOnNickChange;
|
|---|
| 159 | property OnUserListUpdate: TClassMethod read FOnUserListUpdate write FOnUserListUpdate;
|
|---|
| 160 | property OnAddMessage: TAddMessageMethod read FOnAddMessage write FOnAddMessage;
|
|---|
| 161 | property OnChangeNetworkState: TClassMethod read FOnChangeNetworkState write FOnChangeNetworkState;
|
|---|
| 162 | // property OnPingResponse: TClassMethod read FOnPingResponse write FOnPingResponse;
|
|---|
| 163 | // property OnCallUp: TClassMethod read FOnCallUp write FOnCallUp;
|
|---|
| 164 | property OnRoomListChanged: TClassMethod read FOnRoomListChanged write FOnRoomListChanged;
|
|---|
| 165 | property OnSendCommand: TGetStrProc read FOnSendCommand write FOnSendCommand;
|
|---|
| 166 | end;
|
|---|
| 167 |
|
|---|
| 168 | procedure Register;
|
|---|
| 169 |
|
|---|
| 170 | implementation
|
|---|
| 171 |
|
|---|
| 172 | uses
|
|---|
| 173 | UProtocolMessageLog;
|
|---|
| 174 |
|
|---|
| 175 | procedure Register;
|
|---|
| 176 | begin
|
|---|
| 177 | RegisterComponents('Chronosoft', [TSunriseChatCore]);
|
|---|
| 178 | end;
|
|---|
| 179 |
|
|---|
| 180 | { TSunriseChatCore }
|
|---|
| 181 |
|
|---|
| 182 | constructor TSunriseChatCore.Create(AOwner: TComponent);
|
|---|
| 183 | var
|
|---|
| 184 | NewRoom: TRoom;
|
|---|
| 185 | begin
|
|---|
| 186 | inherited;
|
|---|
| 187 | RoomList := TList.Create;
|
|---|
| 188 | UserList := TList.Create;
|
|---|
| 189 | LastHour := HourOf(Now);
|
|---|
| 190 | LocalUser := TUser.Create(Self);
|
|---|
| 191 | with LocalUser do begin
|
|---|
| 192 | OSVersion := GetWindowsVersionStr;
|
|---|
| 193 | OSUser := GetUserName;
|
|---|
| 194 | Status := usOnline;
|
|---|
| 195 | HostName := LocalHostName;
|
|---|
| 196 | //HostName := LocalHostName;
|
|---|
| 197 | Uptime := Now;
|
|---|
| 198 | Id.Machine := Random(High(Integer));
|
|---|
| 199 | Id.User := Random(High(Integer));
|
|---|
| 200 | end;
|
|---|
| 201 | LocalUser.CoreVersion := CoreVersion;
|
|---|
| 202 | FMaxRoomLines := 100;
|
|---|
| 203 | NewRoom := TRoom.Create(Self);
|
|---|
| 204 | with NewRoom do begin
|
|---|
| 205 | Id := 0;
|
|---|
| 206 | Name := 'Public';
|
|---|
| 207 | Typ := rtPublic;
|
|---|
| 208 | end;
|
|---|
| 209 | ActiveRoom := NewRoom;
|
|---|
| 210 | RoomList.Add(NewRoom);
|
|---|
| 211 | FActive := False;
|
|---|
| 212 |
|
|---|
| 213 | // Initialise timer
|
|---|
| 214 | FTimer1 := TTimer.Create(Self);
|
|---|
| 215 | FCounter := 0;
|
|---|
| 216 | with FTimer1 do begin
|
|---|
| 217 | Interval := 1000; // 1 second
|
|---|
| 218 | OnTimer := FTimer1Timer;
|
|---|
| 219 | Enabled := False;
|
|---|
| 220 | end;
|
|---|
| 221 | FUseDefaultEventsText := True;
|
|---|
| 222 | FAutoAwayDelay := 5;
|
|---|
| 223 | end;
|
|---|
| 224 |
|
|---|
| 225 | destructor TSunriseChatCore.Destroy;
|
|---|
| 226 | var
|
|---|
| 227 | I: Integer;
|
|---|
| 228 | begin
|
|---|
| 229 | Active := False;
|
|---|
| 230 | for I := 0 to UserList.Count - 1 do TList(UserList[I]).Free;
|
|---|
| 231 | UserList.Free;
|
|---|
| 232 | LocalUser.Free;
|
|---|
| 233 | for I := 0 to RoomList.Count - 1 do TRoom(RoomList[I]).Free;
|
|---|
| 234 | RoomList.Free;
|
|---|
| 235 | inherited;
|
|---|
| 236 | end;
|
|---|
| 237 |
|
|---|
| 238 | function TSunriseChatCore.SameClientId(Id1, Id2: TClientIdentification): Boolean;
|
|---|
| 239 | begin
|
|---|
| 240 | Result := (Id1.Machine = Id2.Machine) and (Id1.User = Id2.User);
|
|---|
| 241 | end;
|
|---|
| 242 |
|
|---|
| 243 | procedure TSunriseChatCore.ProcessCommand(Text: string);
|
|---|
| 244 | var
|
|---|
| 245 | SourceUser: TUser;
|
|---|
| 246 | TargetUSer: TUser;
|
|---|
| 247 | ProtocolVersion: Integer;
|
|---|
| 248 | Seq: Integer;
|
|---|
| 249 | Command: TSystemCommand;
|
|---|
| 250 | // Data: string;
|
|---|
| 251 | Part: string;
|
|---|
| 252 | I: Integer;
|
|---|
| 253 | IdleTime2: Integer;
|
|---|
| 254 | NewRoom: TRoom;
|
|---|
| 255 | UserListCount: Integer;
|
|---|
| 256 | Args: array of TVarRec;
|
|---|
| 257 | RoomId: Cardinal;
|
|---|
| 258 | RoomName: string;
|
|---|
| 259 | RoomType: TRoomType;
|
|---|
| 260 | TextMessage: string;
|
|---|
| 261 |
|
|---|
| 262 | function Parse: string;
|
|---|
| 263 | begin
|
|---|
| 264 | Result := Copy(Text, 1, Pos('|', Text)-1);
|
|---|
| 265 | Delete(Text, 1, Length(Result)+1);
|
|---|
| 266 | end;
|
|---|
| 267 |
|
|---|
| 268 | begin
|
|---|
| 269 | UserListCount := UserList.Count;
|
|---|
| 270 | SourceUser := TUser.Create(Self);
|
|---|
| 271 | TargetUSer := TUser.Create(Self);
|
|---|
| 272 | // OnAddMessage(Text,clSysMessage);
|
|---|
| 273 | //AddMessage(Command);
|
|---|
| 274 | with SourceUser do try
|
|---|
| 275 | ProtocolVersion := StrToInt(Parse);
|
|---|
| 276 | case ProtocolVersion of
|
|---|
| 277 | 3: begin
|
|---|
| 278 | SourceUser.Id.Machine := StrToInt64(Parse); // Source IP
|
|---|
| 279 | SourceUser.Id.User := StrToInt64(Parse); // Source ID
|
|---|
| 280 | TargetUser.Id.Machine := StrToInt64(Parse); // Destination IP
|
|---|
| 281 | TargetUser.Id.User := StrToInt64(Parse); // Destination ID
|
|---|
| 282 | Seq := StrToInt(Parse); // sequence command number
|
|---|
| 283 |
|
|---|
| 284 | ProtocolMessageLogForm.Memo1.Lines.Add('ProcessCommand: ' + Text);
|
|---|
| 285 | // Load source user data
|
|---|
| 286 | if SameClientId(TargetUser.Id, LocalUser.Id) then begin
|
|---|
| 287 | I := 0;
|
|---|
| 288 | while (I < UserList.Count) and not SameClientId(TUser(UserList[I]).Id, TargetUser.Id) do
|
|---|
| 289 | I := I + 1;
|
|---|
| 290 | if I < UserList.Count then
|
|---|
| 291 | TargetUser.Assign(UserList[I]);
|
|---|
| 292 | end;
|
|---|
| 293 |
|
|---|
| 294 | // Load source user data
|
|---|
| 295 | I := 0;
|
|---|
| 296 | while (I < UserList.Count) and not SameClientId(TUser(UserList[I]).Id, Id) do
|
|---|
| 297 | I := I + 1;
|
|---|
| 298 | if I < UserList.Count then begin
|
|---|
| 299 | SourceUser.Assign(UserList[I]);
|
|---|
| 300 | end else Sequence := Seq - 1;
|
|---|
| 301 | if Seq <> (SourceUser.Sequence + 1) then ErrorCount := ErrorCount + 1;
|
|---|
| 302 | Sequence := Seq;
|
|---|
| 303 | ProtocolMessageLogForm.Memo1.Lines.Add('ProcessCommand: UpdateUser ');
|
|---|
| 304 | UpdateUser(SourceUser);
|
|---|
| 305 |
|
|---|
| 306 | begin
|
|---|
| 307 | Command := TSystemCommand(StrToInt(Parse)); // Command
|
|---|
| 308 | case Command of
|
|---|
| 309 |
|
|---|
| 310 | scMessage: begin
|
|---|
| 311 | TextMessage := Parse;
|
|---|
| 312 | RoomId := StrToInt64(Parse);
|
|---|
| 313 | RoomName := Parse;
|
|---|
| 314 | RoomType := TRoomType(StrToInt(Parse));
|
|---|
| 315 |
|
|---|
| 316 | // Search room
|
|---|
| 317 | I := 0;
|
|---|
| 318 | while (I < RoomList.Count) and (TRoom(RoomList[I]).Id <> RoomId) do
|
|---|
| 319 | I := I + 1;
|
|---|
| 320 | if (RoomType = rtPublic) and (I = RoomList.Count) then begin
|
|---|
| 321 | NewRoom := TRoom.Create(Self);
|
|---|
| 322 | with NewRoom do begin
|
|---|
| 323 | Id := RoomId;
|
|---|
| 324 | Name := RoomName;
|
|---|
| 325 | Typ := RoomType;
|
|---|
| 326 | end;
|
|---|
| 327 | RoomList.Add(NewRoom);
|
|---|
| 328 | if Assigned(FOnRoomListChanged) then FOnRoomListChanged;
|
|---|
| 329 | end;
|
|---|
| 330 |
|
|---|
| 331 | UpdateUser(SourceUser);
|
|---|
| 332 | if (I < RoomList.Count) and not BlockMessages then begin
|
|---|
| 333 | if not SameClientId(TargetUser.Id, LocalUser.Id) then
|
|---|
| 334 | AddMessage(aeCommonMessage, TRoom(RoomList[I]), [Nick, TextMessage, Color])
|
|---|
| 335 | else AddMessage(aeOneUserMessage, TRoom(RoomList[I]), [Nick, TextMessage, Color, TargetUser.Nick]);
|
|---|
| 336 | end;
|
|---|
| 337 | end;
|
|---|
| 338 |
|
|---|
| 339 | scCreateRoom: begin
|
|---|
| 340 | RoomId := StrToInt64(Parse);
|
|---|
| 341 | RoomName := Parse;
|
|---|
| 342 | RoomType := TRoomType(StrToInt(Parse));
|
|---|
| 343 | //ShowMessage(LocalUser.IP+','+IP+' '+IntToStr(LocalUser.ID)+','+IntToStr(ID));
|
|---|
| 344 | if (RoomType = rtPublic) or ((RoomType = rtPrivate) and
|
|---|
| 345 | SameClientId(LocalUser.Id, TargetUser.Id)) then begin
|
|---|
| 346 | // Search room
|
|---|
| 347 | I := 0;
|
|---|
| 348 | while (I < RoomList.Count) and (TRoom(RoomList[I]).Id <> RoomId) do
|
|---|
| 349 | I := I + 1;
|
|---|
| 350 | if I < RoomList.Count then
|
|---|
| 351 | else begin
|
|---|
| 352 | NewRoom := TRoom.Create(Self);
|
|---|
| 353 | with NewRoom do begin
|
|---|
| 354 | Id := RoomId;
|
|---|
| 355 | Name := RoomName;
|
|---|
| 356 | Typ := RoomType;
|
|---|
| 357 | end;
|
|---|
| 358 | RoomList.Add(NewRoom);
|
|---|
| 359 | end;
|
|---|
| 360 | if Assigned(FOnRoomListChanged) then FOnRoomListChanged;
|
|---|
| 361 | AddMessage(aeCreateRoom, TRoom(RoomList[I]), [SourceUser.Nick, RoomName]);
|
|---|
| 362 | end;
|
|---|
| 363 | end;
|
|---|
| 364 |
|
|---|
| 365 | scLeaveRoom: begin
|
|---|
| 366 | RoomId := StrToInt64(Parse);
|
|---|
| 367 | RoomName := Parse;
|
|---|
| 368 |
|
|---|
| 369 | // Search room
|
|---|
| 370 | I := 0;
|
|---|
| 371 | while (I < RoomList.Count) and (TRoom(RoomList[I]).Id <> RoomId) do
|
|---|
| 372 | I := I + 1;
|
|---|
| 373 | if I < RoomList.Count then begin
|
|---|
| 374 | if Assigned(FOnRoomListChanged) then FOnRoomListChanged;
|
|---|
| 375 | AddMessage(aeLeaveRoom, TRoom(RoomList[I]), [Nick, RoomName]);
|
|---|
| 376 | end;
|
|---|
| 377 | end;
|
|---|
| 378 |
|
|---|
| 379 | scConnect: begin
|
|---|
| 380 | Nick := Parse;
|
|---|
| 381 | NickTime := StrToDateTime(Parse);
|
|---|
| 382 | if not SameClientId(LocalUser.Id, Id) then
|
|---|
| 383 | AddMessage(aeUserConnect, nil, [Nick, HostName])
|
|---|
| 384 | else AddMessage(aeStart, nil , [Nick]);
|
|---|
| 385 | SendCommand(scUserInfo);
|
|---|
| 386 | UpdateUser(SourceUser);
|
|---|
| 387 | end;
|
|---|
| 388 |
|
|---|
| 389 | scDisconnect: begin
|
|---|
| 390 | if not SameClientId(LocalUser.Id, Id) then
|
|---|
| 391 | AddMessage(aeUserDisconnect, nil, [Nick])
|
|---|
| 392 | else AddMessage(aeEnd, nil , [Nick]);
|
|---|
| 393 |
|
|---|
| 394 | // Delete disconnected user
|
|---|
| 395 | I := 0;
|
|---|
| 396 | while (I < UserList.Count) and not SameClientId(TUser(UserList[I]).Id, Id) do
|
|---|
| 397 | I := I + 1;
|
|---|
| 398 | if I < UserList.Count then begin
|
|---|
| 399 | TUser(UserList[I]).Free;
|
|---|
| 400 | UserList.Delete(I);
|
|---|
| 401 | end;
|
|---|
| 402 | if Assigned(OnUserListUpdate) then OnUserListUpdate;
|
|---|
| 403 | end;
|
|---|
| 404 |
|
|---|
| 405 | scCallUp: begin
|
|---|
| 406 | if (not BlockMessages) and SameClientId(LocalUser.Id, TargetUser.Id) then AddMessage(aeReceiveCallUp, nil, [Nick]);
|
|---|
| 407 | end;
|
|---|
| 408 |
|
|---|
| 409 | scPing: begin
|
|---|
| 410 | if SameClientId(LocalUser.Id, TargetUser.Id) then SendCommand(scPingResponse, Parse, TargetUser.Id.Machine, TargetUSer.Id.User);
|
|---|
| 411 | end;
|
|---|
| 412 |
|
|---|
| 413 | scPingResponse: begin
|
|---|
| 414 | if SameClientId(LocalUser.Id, TargetUser.Id) then AddMessage(aePingResponse, nil, [Nick,TimeToStr(Now - StrToTime(Parse))]);
|
|---|
| 415 | end;
|
|---|
| 416 |
|
|---|
| 417 | scAlive: begin
|
|---|
| 418 | Delay := Now - LastTime;
|
|---|
| 419 | LastTime := Now;
|
|---|
| 420 | if not TryStrToInt(Parse, IdleTime2) then IdleTime2 := 0;
|
|---|
| 421 | if (IdleTime2 > AutoAwayDelay * 60) and (IdleTime < AutoAwayDelay * 60) and (Status = usOnline) then begin
|
|---|
| 422 | AddMessage(aeUserGoAway, nil, [Nick, IntToStr(AutoAwayDelay)]);
|
|---|
| 423 | end;
|
|---|
| 424 | if (IdleTime > AutoAwayDelay * 60) and (IdleTime2 < AutoAwayDelay * 60) and (Status = usOnline) then begin
|
|---|
| 425 | AddMessage(aeUserAutoGoBack, nil, [Nick, IntToStr(IdleTime div 60)]);
|
|---|
| 426 | end;
|
|---|
| 427 | IdleTime := IdleTime2;
|
|---|
| 428 | UpdateUser(SourceUser);
|
|---|
| 429 | end;
|
|---|
| 430 |
|
|---|
| 431 | scWhoIs: begin
|
|---|
| 432 | //if SameClientId(LocalUser.Id, TargetUser.Id) then
|
|---|
| 433 | SendCommand(scUserInfo);
|
|---|
| 434 | end;
|
|---|
| 435 |
|
|---|
| 436 | scCustomCommand: begin
|
|---|
| 437 | while Length(Text) > 0 do begin
|
|---|
| 438 | SetLength(Args, Length(Args)+1);
|
|---|
| 439 | Args[High(Args)].VPChar := PChar(Parse);
|
|---|
| 440 | end;
|
|---|
| 441 | AddMessage(aeCustomEvent, nil, Args);
|
|---|
| 442 | end;
|
|---|
| 443 |
|
|---|
| 444 | scUserInfo: begin
|
|---|
| 445 | Part := Parse;
|
|---|
| 446 | if (Part <> Nick) and (NickTime <> 0) then AddMessage(aeUserChangeNick, nil, [Nick, Part]);
|
|---|
| 447 | FNick := Part;
|
|---|
| 448 | ProtocolMessageLogForm.Memo1.Lines.Add('ProcessCommand: UserInfo ' + FNick);
|
|---|
| 449 | NickTime := StrToDateTime(Parse);
|
|---|
| 450 | Color := StrToInt(Parse);
|
|---|
| 451 | Reason := Parse;
|
|---|
| 452 | Part := Parse;
|
|---|
| 453 | if TUserStatusMode(StrToInt(Part)) <> Status then
|
|---|
| 454 | case TUserStatusMode(StrToInt(Part)) of
|
|---|
| 455 | usAway: begin
|
|---|
| 456 | AddMessage(aeUserGoAway, nil, [Nick, Reason]);
|
|---|
| 457 | UpdateUser(SourceUser);
|
|---|
| 458 | end;
|
|---|
| 459 | usOnline: begin
|
|---|
| 460 | AddMessage(aeUserGoBack, nil, [Nick]);
|
|---|
| 461 | UpdateUser(SourceUser);
|
|---|
| 462 | end;
|
|---|
| 463 | end;
|
|---|
| 464 | Status := TUserStatusMode(StrToInt(Part));
|
|---|
| 465 | HostName := Parse;
|
|---|
| 466 | OSVersion := Parse;
|
|---|
| 467 | OSUser := Parse;
|
|---|
| 468 | Uptime := StrToDateTime(Parse);
|
|---|
| 469 | Client := Parse;
|
|---|
| 470 | ClientVersion := Parse;
|
|---|
| 471 | CoreVersion := Parse;
|
|---|
| 472 | LocalSystemTime := StrToDateTime(Parse);
|
|---|
| 473 | DetailInfo := Parse;
|
|---|
| 474 | UserInfoTime := Now;
|
|---|
| 475 |
|
|---|
| 476 | // Nick conflict test
|
|---|
| 477 | if (LocalUser.Nick = Nick) and not SameClientId(LocalUser.Id, Id) then begin
|
|---|
| 478 | if LocalUser.NickTime > NickTime then begin
|
|---|
| 479 | // Set nick to Host(Guest) n
|
|---|
| 480 | if Copy(LocalUser.Nick, 1, 4) = 'Host' then begin
|
|---|
| 481 | if TryStrToInt(Copy(LocalUser.Nick, 5, 255), I) then I := I + 1 else I := 1;
|
|---|
| 482 | end else I := 1;
|
|---|
| 483 | LocalUser.Nick := 'Host' + IntToStr(I);
|
|---|
| 484 | AddMessage(aeUserChangeNickConflict, nil, [LocalUser.Nick]);
|
|---|
| 485 | end else SendCommand(scUserInfo);
|
|---|
| 486 | end;
|
|---|
| 487 | UpdateUser(SourceUser);
|
|---|
| 488 | end;
|
|---|
| 489 | end;
|
|---|
| 490 | if (UserListCount <> UserList.Count) and (Command <> scConnect) and
|
|---|
| 491 | (Command <> scUserInfo) then
|
|---|
| 492 | AddMessage(aeUserRestore, nil, [SourceUser.Nick]);
|
|---|
| 493 | if Nick = '' then SendCommand(scWhoIs, '', Id.Machine, Id.User);
|
|---|
| 494 | end;
|
|---|
| 495 | end;
|
|---|
| 496 | end;
|
|---|
| 497 | except
|
|---|
| 498 | end;
|
|---|
| 499 | SourceUser.Free;
|
|---|
| 500 | TargetUSer.Free;
|
|---|
| 501 | end;
|
|---|
| 502 |
|
|---|
| 503 | procedure TSunriseChatCore.SendCommand(Command: TSystemCommand;
|
|---|
| 504 | Text: string = '';
|
|---|
| 505 | DestinationMachineId: Cardinal = 0; DestinationUserId: Cardinal = 0);
|
|---|
| 506 | var
|
|---|
| 507 | Data: string;
|
|---|
| 508 |
|
|---|
| 509 | procedure AddPart(Part: string);
|
|---|
| 510 | begin
|
|---|
| 511 | Data := Data + Part + '|';
|
|---|
| 512 | end;
|
|---|
| 513 |
|
|---|
| 514 | begin
|
|---|
| 515 | if LocalUser.Status <> usInvisible then begin
|
|---|
| 516 | //if not NoResloveHostName then
|
|---|
| 517 | Data := '';
|
|---|
| 518 | with LocalUser do begin
|
|---|
| 519 | AddPart(IntToStr(ProtocolVersion)); // Version of protocol
|
|---|
| 520 | AddPart(IntToStr(Int64(Id.Machine))); // Source machine id
|
|---|
| 521 | AddPart(IntToStr(Int64(Id.User))); // Source user id
|
|---|
| 522 | AddPart(IntToStr(Int64(DestinationMachineId))); // Destination machine id
|
|---|
| 523 | AddPart(IntToStr(Int64(DestinationUserId))); // Destination user id
|
|---|
| 524 | Sequence := Sequence + 1;
|
|---|
| 525 | AddPart(IntToStr(Sequence)); // Command sequence number
|
|---|
| 526 | AddPart(IntToStr(Integer(Command))); // Command
|
|---|
| 527 | case Command of
|
|---|
| 528 |
|
|---|
| 529 | scMessage: begin
|
|---|
| 530 | AddPart(Text); // Text
|
|---|
| 531 | AddPart(IntToStr(ActiveRoom.Id)); // Room name
|
|---|
| 532 | AddPart(ActiveRoom.Name); // Room name
|
|---|
| 533 | AddPart(IntToStr(Integer(ActiveRoom.Typ))); // Public or private room
|
|---|
| 534 | end;
|
|---|
| 535 |
|
|---|
| 536 | scConnect: begin
|
|---|
| 537 | AddPart(Nick);
|
|---|
| 538 | AddPart(DateTimeToStr(NickTime));
|
|---|
| 539 | end;
|
|---|
| 540 |
|
|---|
| 541 | scDisconnect: begin
|
|---|
| 542 | end;
|
|---|
| 543 |
|
|---|
| 544 | scCallUp: begin
|
|---|
| 545 | end;
|
|---|
| 546 |
|
|---|
| 547 | scCallUpResponse: begin
|
|---|
| 548 | end;
|
|---|
| 549 |
|
|---|
| 550 | scPing: begin
|
|---|
| 551 | AddPart(TimeToStr(Now)); // Send time
|
|---|
| 552 | end;
|
|---|
| 553 |
|
|---|
| 554 | scPingResponse: begin
|
|---|
| 555 | AddPart(Text); // Send time
|
|---|
| 556 | end;
|
|---|
| 557 |
|
|---|
| 558 | scAlive: begin
|
|---|
| 559 | AddPart(IntToStr(SecondsIdle));
|
|---|
| 560 | end;
|
|---|
| 561 |
|
|---|
| 562 | scWhoIs: begin
|
|---|
| 563 | end;
|
|---|
| 564 |
|
|---|
| 565 | scCreateRoom: begin
|
|---|
| 566 | AddPart(IntToStr(Int64(ActiveRoom.Id))); // Room Id
|
|---|
| 567 | AddPart(ActiveRoom.Name); // Room name
|
|---|
| 568 | AddPart(IntToStr(Integer(ActiveRoom.Typ))); // Public or private room
|
|---|
| 569 | end;
|
|---|
| 570 |
|
|---|
| 571 | scLeaveRoom: begin
|
|---|
| 572 | AddPart(IntToStr(Int64(Id))); // Room Id
|
|---|
| 573 | AddPart(ActiveRoom.Name); // Room name
|
|---|
| 574 | end;
|
|---|
| 575 |
|
|---|
| 576 | scUserInfo: begin
|
|---|
| 577 | AddPart(Nick); // User name
|
|---|
| 578 | AddPart(DateTimeToStr(NickTime)); // User name time
|
|---|
| 579 | AddPart(IntToStr(Color)); // Text color
|
|---|
| 580 | AddPart(Reason); // Away mode reason
|
|---|
| 581 | AddPart(IntToStr(Integer(Status))); // User status
|
|---|
| 582 | AddPart(HostName); // Local host name
|
|---|
| 583 | AddPart(OSVersion); // OS version
|
|---|
| 584 | AddPart(OSUser); // Logged user
|
|---|
| 585 | AddPart(DateTimeToStr(Uptime)); // Application uptime
|
|---|
| 586 | AddPart(Client); // Name of client application
|
|---|
| 587 | AddPart(ClientVersion); // Version of application
|
|---|
| 588 | AddPart(CoreVersion); // SunriseChatCoreVersion
|
|---|
| 589 | AddPart(DateTimeToStr(Now)); // Local system time
|
|---|
| 590 | AddPart(DetailInfo); // Detailed info about user
|
|---|
| 591 | end;
|
|---|
| 592 | end;
|
|---|
| 593 | end;
|
|---|
| 594 | //if Assigned(ProtocolMessageLogForm) then
|
|---|
| 595 | // ProtocolMessageLogForm.Memo1.Lines.Add('SendCommand: ' + Data);
|
|---|
| 596 | if Assigned(FOnSendCommand) then FOnSendCommand(Data);
|
|---|
| 597 | end;
|
|---|
| 598 | end;
|
|---|
| 599 |
|
|---|
| 600 | procedure TSunriseChatCore.FTimer1Timer(Sender: TObject);
|
|---|
| 601 | begin
|
|---|
| 602 | if (FCounter mod AliveCommandSendingPeriod) = 0 then SendCommand(scAlive);
|
|---|
| 603 | DeleteOfflineUsers;
|
|---|
| 604 | if HourOf(Now) <> LastHour then begin
|
|---|
| 605 | LastHour := HourOf(Now);
|
|---|
| 606 | AddMessage(aeShowHours, nil, [HourOf(Now), MinuteOf(Now)]);
|
|---|
| 607 | end;
|
|---|
| 608 | FCounter := FCounter + 1;
|
|---|
| 609 | end;
|
|---|
| 610 |
|
|---|
| 611 | procedure TSunriseChatCore.DeleteOfflineUsers;
|
|---|
| 612 | var
|
|---|
| 613 | I: Integer;
|
|---|
| 614 | Changed: Boolean;
|
|---|
| 615 | begin
|
|---|
| 616 | I := 0;
|
|---|
| 617 | Changed := False;
|
|---|
| 618 | while (I < UserList.Count) do begin
|
|---|
| 619 | if (Now - TUser(UserList[I]).LastTime) > RemoveTimeout then begin
|
|---|
| 620 | AddMessage(aeUserTimeout, nil, [TUser(UserList[I]).Nick]);
|
|---|
| 621 | TUser(UserList[I]).Free;
|
|---|
| 622 | UserList.Delete(I);
|
|---|
| 623 | Changed := True;
|
|---|
| 624 | end else I := I + 1;
|
|---|
| 625 | end;
|
|---|
| 626 | if Changed and Assigned(FOnUserListUpdate) then FOnUserListUpdate;
|
|---|
| 627 | end;
|
|---|
| 628 |
|
|---|
| 629 | procedure TSunriseChatCore.UpdateUser(User: TUser);
|
|---|
| 630 | var
|
|---|
| 631 | I: Integer;
|
|---|
| 632 | NewUser: TUser;
|
|---|
| 633 | begin
|
|---|
| 634 | I := 0;
|
|---|
| 635 | while (I < UserList.Count) and not SameClientId(TUser(UserList[I]).Id, User.Id) do
|
|---|
| 636 | I := I + 1;
|
|---|
| 637 | if I = UserList.Count then begin
|
|---|
| 638 | NewUser := TUser.Create(Self);
|
|---|
| 639 | UserList.Add(NewUser);
|
|---|
| 640 | end;
|
|---|
| 641 | User.LastTime := Now;
|
|---|
| 642 | TUser(UserList[I]).Assign(User);
|
|---|
| 643 | if Assigned(FOnUserListUpdate) then FOnUserListUpdate;
|
|---|
| 644 | end;
|
|---|
| 645 |
|
|---|
| 646 | procedure TSunriseChatCore.AddMessage(MessageEventType: TAppEventType;
|
|---|
| 647 | Room: TRoom; const Args: array of const);
|
|---|
| 648 | var
|
|---|
| 649 | NewRoomLine: TRoomLine;
|
|---|
| 650 | RoomIndex: Integer;
|
|---|
| 651 | I: Integer;
|
|---|
| 652 | RoomListStartIndex: Integer;
|
|---|
| 653 | RoomListEndIndex: Integer;
|
|---|
| 654 | begin
|
|---|
| 655 | //if (StrPas(PChar(Args[0].VString)) <> '') then
|
|---|
| 656 | begin
|
|---|
| 657 | // Create new room line
|
|---|
| 658 | NewRoomLine := TRoomLine.Create;
|
|---|
| 659 | with NewRoomLine do begin
|
|---|
| 660 | if UseDefaultEventsText then try
|
|---|
| 661 | Text := Format(DefaultEventsFormat[Integer(MessageEventType)], Args);
|
|---|
| 662 | except
|
|---|
| 663 | raise Exception.Create('Default event text format error');
|
|---|
| 664 | end else Text := '';
|
|---|
| 665 | Time := Now;
|
|---|
| 666 | if MessageEventType in [aeOneUserMessage, aeCommonMessage] then begin
|
|---|
| 667 | Font.Color := Args[2].VInteger;
|
|---|
| 668 | end;
|
|---|
| 669 | end;
|
|---|
| 670 | NewRoomLine.EventType := MessageEventType;
|
|---|
| 671 |
|
|---|
| 672 | if Assigned(OnAddMessage) then OnAddMessage(MessageEventType, Room, Args, NewRoomLine);
|
|---|
| 673 |
|
|---|
| 674 | // Select target rooms
|
|---|
| 675 | if not Assigned(Room) then begin // nil = add to all rooms
|
|---|
| 676 | RoomListStartIndex := 0;
|
|---|
| 677 | RoomListEndIndex := RoomList.Count - 1;
|
|---|
| 678 | end else begin
|
|---|
| 679 | RoomListStartIndex := RoomList.IndexOf(Room);
|
|---|
| 680 | RoomListEndIndex := RoomList.IndexOf(Room);
|
|---|
| 681 | end;
|
|---|
| 682 |
|
|---|
| 683 | // Add new line
|
|---|
| 684 | for RoomIndex := RoomListStartIndex to RoomListEndIndex do
|
|---|
| 685 | if (RoomIndex < RoomList.Count) and (RoomIndex >= 0) then
|
|---|
| 686 | with TRoom(RoomList[RoomIndex]) do begin
|
|---|
| 687 | Count := Count + 1;
|
|---|
| 688 | if Count > FMaxRoomLines then begin
|
|---|
| 689 | StartLine := (StartLine + 1) mod FMaxRoomLines;
|
|---|
| 690 | Count := FMaxRoomLines;
|
|---|
| 691 | end;
|
|---|
| 692 | I := (StartLine + Count - 1) mod FMaxRoomLines;
|
|---|
| 693 | TRoomLine(Lines[I]).Assign(NewRoomLine);
|
|---|
| 694 | end;
|
|---|
| 695 | NewRoomLine.Free;
|
|---|
| 696 | end;
|
|---|
| 697 | end;
|
|---|
| 698 |
|
|---|
| 699 | procedure TSunriseChatCore.DeleteRoom(RoomIndex: Integer);
|
|---|
| 700 | begin
|
|---|
| 701 | TRoom(RoomList[RoomIndex]).Free;
|
|---|
| 702 | RoomList.Delete(RoomIndex);
|
|---|
| 703 | if Assigned(FOnRoomListChanged) then FOnRoomListChanged;
|
|---|
| 704 | end;
|
|---|
| 705 |
|
|---|
| 706 | procedure TSunriseChatCore.SetMaxRoomLines(Number: Integer);
|
|---|
| 707 | var
|
|---|
| 708 | I, II: Integer;
|
|---|
| 709 | begin
|
|---|
| 710 | FMaxRoomLines := Number;
|
|---|
| 711 | for I := 0 to RoomList.Count-1 do with TRoom(RoomList[I]) do begin
|
|---|
| 712 | for II := 0 to Lines.Count-1 do TRoomLine(Lines[II]).Free;
|
|---|
| 713 | Lines.Count := Number;
|
|---|
| 714 | for II := 0 to Lines.Count-1 do Lines[II] := TRoomLine.Create;
|
|---|
| 715 | end;
|
|---|
| 716 | end;
|
|---|
| 717 |
|
|---|
| 718 | procedure TSunriseChatCore.SetActive(const Value: Boolean);
|
|---|
| 719 | begin
|
|---|
| 720 | FTimer1.Enabled := Value;
|
|---|
| 721 | FActive := Value;
|
|---|
| 722 | end;
|
|---|
| 723 |
|
|---|
| 724 | { TRoom }
|
|---|
| 725 |
|
|---|
| 726 | constructor TRoom.Create(Parent: TSunriseChatCore);
|
|---|
| 727 | var
|
|---|
| 728 | I: Integer;
|
|---|
| 729 | begin
|
|---|
| 730 | FParent := Parent;
|
|---|
| 731 | ID := Random(High(Integer));
|
|---|
| 732 | Lines := TList.Create;
|
|---|
| 733 | Lines.Count := FParent.FMaxRoomLines;
|
|---|
| 734 | for I := 0 to Lines.Count-1 do begin
|
|---|
| 735 | Lines[I] := TRoomLine.Create;
|
|---|
| 736 | end;
|
|---|
| 737 | StartLine := 0;
|
|---|
| 738 | Count := 0;
|
|---|
| 739 | end;
|
|---|
| 740 |
|
|---|
| 741 | destructor TRoom.Destroy;
|
|---|
| 742 | var
|
|---|
| 743 | I: Integer;
|
|---|
| 744 | begin
|
|---|
| 745 | for I := 0 to Lines.Count-1 do TRoomLine(Lines[I]).Free;
|
|---|
| 746 | Lines.Free;
|
|---|
| 747 | inherited;
|
|---|
| 748 | end;
|
|---|
| 749 |
|
|---|
| 750 | procedure TRoom.Remove;
|
|---|
| 751 | begin
|
|---|
| 752 | with FParent.RoomList do
|
|---|
| 753 | if Id <> 0 then Delete(IndexOf(Self));
|
|---|
| 754 | end;
|
|---|
| 755 |
|
|---|
| 756 | procedure TRoom.Select;
|
|---|
| 757 | begin
|
|---|
| 758 | FParent.ActiveRoom := Self;
|
|---|
| 759 | end;
|
|---|
| 760 |
|
|---|
| 761 | { TUser }
|
|---|
| 762 |
|
|---|
| 763 | procedure TUser.Assign(Source: TPersistent);
|
|---|
| 764 | begin
|
|---|
| 765 | if Source is TUser then
|
|---|
| 766 | with TUser(Source) do begin
|
|---|
| 767 | Self.FNick := FNick;
|
|---|
| 768 | Self.HostName := HostName;
|
|---|
| 769 | Self.Id := Id;
|
|---|
| 770 | Self.Female := Female;
|
|---|
| 771 | Self.Color := Color;
|
|---|
| 772 | Self.Client := Client;
|
|---|
| 773 | Self.ClientVersion := ClientVersion;
|
|---|
| 774 | Self.CoreVersion := CoreVersion;
|
|---|
| 775 | Self.OSVersion := OSVersion;
|
|---|
| 776 | Self.OSUser := OSUser;
|
|---|
| 777 | Self.Status := Status;
|
|---|
| 778 | Self.LastTime := LastTime;
|
|---|
| 779 | Self.UpTime := UpTime;
|
|---|
| 780 | Self.LocalSystemTime := LocalSystemTime;
|
|---|
| 781 | Self.UserInfoTime := UserInfoTime;
|
|---|
| 782 | Self.NickTime := NickTime;
|
|---|
| 783 | Self.Reason := Reason;
|
|---|
| 784 | Self.Sequence := Sequence;
|
|---|
| 785 | Self.ErrorCount := ErrorCount;
|
|---|
| 786 | Self.BlockMessages := BlockMessages;
|
|---|
| 787 | Self.Delay := Delay;
|
|---|
| 788 | Self.DetailInfo := DetailInfo;
|
|---|
| 789 | Self.IdleTime := IdleTime;
|
|---|
| 790 | end else inherited;
|
|---|
| 791 | end;
|
|---|
| 792 |
|
|---|
| 793 | constructor TUser.Create(Parent: TSunriseChatCore);
|
|---|
| 794 | begin
|
|---|
| 795 | FParent := Parent;
|
|---|
| 796 | end;
|
|---|
| 797 |
|
|---|
| 798 | procedure TUser.SetNick(const Value: string);
|
|---|
| 799 | begin
|
|---|
| 800 | NickTime := Now;
|
|---|
| 801 | FNick := Value;
|
|---|
| 802 | FParent.SendCommand(scUserInfo);
|
|---|
| 803 | end;
|
|---|
| 804 |
|
|---|
| 805 | { TRoomLine }
|
|---|
| 806 |
|
|---|
| 807 | procedure TRoomLine.Assign(Source: TPersistent);
|
|---|
| 808 | begin
|
|---|
| 809 | if Source is TRoomLine then
|
|---|
| 810 | with TRoomLine(Source) do begin
|
|---|
| 811 | Self.Text := Text;
|
|---|
| 812 | Self.Font.Assign(Font);
|
|---|
| 813 | Self.Time := Time;
|
|---|
| 814 | Self.EventType := EventType;
|
|---|
| 815 | end else inherited;
|
|---|
| 816 | end;
|
|---|
| 817 |
|
|---|
| 818 | constructor TRoomLine.Create;
|
|---|
| 819 | begin
|
|---|
| 820 | Font := TFont.Create;
|
|---|
| 821 | end;
|
|---|
| 822 |
|
|---|
| 823 | destructor TRoomLine.Destroy;
|
|---|
| 824 | begin
|
|---|
| 825 | Font.Free;
|
|---|
| 826 | inherited;
|
|---|
| 827 | end;
|
|---|
| 828 |
|
|---|
| 829 | end.
|
|---|