|
@@ -23,8 +23,10 @@ interface
|
|
{$I ..\config.inc}
|
|
{$I ..\config.inc}
|
|
|
|
|
|
uses
|
|
uses
|
|
- SysUtils, Classes, Forms, Controls, {$IFDEF WINDOWS}Windows,{$ENDIF} ExtCtrls, Dialogs, LCLType,
|
|
|
|
- UCommon.UI, UBlockChain, UAccounts, UNode, UWallet, UConst, UFolderHelper, UGridUtils, URPC, UPoolMining,
|
|
|
|
|
|
+ SysUtils, Classes, Forms, Controls, {$IFDEF WINDOWS}Windows,{$ENDIF} ExtCtrls,
|
|
|
|
+ Dialogs, LCLType,
|
|
|
|
+ UCommon, UCommon.UI,
|
|
|
|
+ UBlockChain, UAccounts, UNode, UWallet, UConst, UFolderHelper, UGridUtils, URPC, UPoolMining,
|
|
ULog, UThread, UNetProtocol, UCrypto,
|
|
ULog, UThread, UNetProtocol, UCrypto,
|
|
UFRMMainForm, UCTRLSyncronization, UFRMAccountExplorer, UFRMOperationExplorer, UFRMPendingOperations, UFRMOperation,
|
|
UFRMMainForm, UCTRLSyncronization, UFRMAccountExplorer, UFRMOperationExplorer, UFRMPendingOperations, UFRMOperation,
|
|
UFRMLogs, UFRMMessages, UFRMNodes, UFRMBlockExplorer, UFRMWalletKeys;
|
|
UFRMLogs, UFRMMessages, UFRMNodes, UFRMBlockExplorer, UFRMWalletKeys;
|
|
@@ -34,6 +36,9 @@ type
|
|
|
|
|
|
TLoadDatabaseThread = class;
|
|
TLoadDatabaseThread = class;
|
|
|
|
|
|
|
|
+ { TUserInterfaceState }
|
|
|
|
+ TUserInterfaceState = (uisLoading, uisLoaded, uisDiscoveringPeers, uisSyncronizingBlockchain, uisActive, uisIsolated, uisDisconnected, uisError);
|
|
|
|
+
|
|
{ TUserInterface }
|
|
{ TUserInterface }
|
|
|
|
|
|
TUserInterface = class
|
|
TUserInterface = class
|
|
@@ -69,51 +74,83 @@ type
|
|
FStatusBar2Text : AnsiString; static;
|
|
FStatusBar2Text : AnsiString; static;
|
|
FMessagesNotificationText : AnsiString; static;
|
|
FMessagesNotificationText : AnsiString; static;
|
|
FDisplayedStartupSyncDialog : boolean; static;
|
|
FDisplayedStartupSyncDialog : boolean; static;
|
|
-
|
|
|
|
- // Methods
|
|
|
|
- class procedure RefreshConnectionStatusDisplay;
|
|
|
|
|
|
+ FAppStarted : TNotifyManyEvent; static;
|
|
|
|
+ FLoading : TProgressNotifyMany; static;
|
|
|
|
+ FLoaded : TNotifyManyEvent; static;
|
|
|
|
+ FStateChanged : TNotifyManyEvent; static;
|
|
|
|
+ FSettingsChanged : TNotifyManyEvent; static;
|
|
|
|
+ FAccountsChanged : TNotifyManyEvent; static;
|
|
|
|
+ FBlocksChanged : TNotifyManyEvent; static;
|
|
|
|
+ FReceivedHelloMessage : TNotifyManyEvent; static;
|
|
|
|
+ FNodeMessageEvent : TNodeMessageManyEvent; static;
|
|
|
|
+ FNetStatisticsChanged : TNotifyManyEvent; static;
|
|
|
|
+ FNetConnectionsUpdated : TNotifyManyEvent; static;
|
|
|
|
+ FNetNodeServersUpdated : TNotifyManyEvent; static;
|
|
|
|
+ FNetBlackListUpdated : TNotifyManyEvent; static;
|
|
|
|
+ FMiningServerNewBlockFound : TNotifyManyEvent; static;
|
|
|
|
+ FUIRefreshTimer : TNotifyManyEvent; static;
|
|
|
|
+ FState : TUserInterfaceState; static;
|
|
|
|
+ FStateText : String; static;
|
|
|
|
|
|
// Getters/Setters
|
|
// Getters/Setters
|
|
class function GetEnabled : boolean; static;
|
|
class function GetEnabled : boolean; static;
|
|
class procedure SetEnabled(ABool: boolean); static;
|
|
class procedure SetEnabled(ABool: boolean); static;
|
|
- class procedure SetStatusBar0Text(const text : AnsiString); static;
|
|
|
|
- class procedure SetStatusBar1Text(const text : AnsiString); static;
|
|
|
|
- class procedure SetStatusBar2Text(const text : AnsiString); static;
|
|
|
|
- class procedure SetMessagesNotificationText(const text : AnsiString); static;
|
|
|
|
|
|
+ class procedure SetState(AState : TUserInterfaceState); static;
|
|
|
|
+ //TODO: remove
|
|
class procedure SetMainFormMode(AMode: TFRMMainFormMode); static;
|
|
class procedure SetMainFormMode(AMode: TFRMMainFormMode); static;
|
|
class function GetMainFormMode : TFRMMainFormMode; static;
|
|
class function GetMainFormMode : TFRMMainFormMode; static;
|
|
|
|
|
|
- // Aux methods
|
|
|
|
- class procedure FinishedLoadingDatabase;
|
|
|
|
-
|
|
|
|
// Handlers
|
|
// Handlers
|
|
- class procedure OnTimerUpdateStatusTimer(Sender: TObject);
|
|
|
|
- class procedure OnSubFormDestroyed(Sender: TObject);
|
|
|
|
-
|
|
|
|
- // Backend Handlers (TODO: refactor this out with TNotifyManyEvents)
|
|
|
|
class procedure OnSettingsChanged(Sender: TObject);
|
|
class procedure OnSettingsChanged(Sender: TObject);
|
|
- class procedure OnAccountsChanged(Sender: TObject);
|
|
|
|
- class procedure OnBlocksChanged(Sender: TObject);
|
|
|
|
|
|
+ class procedure OnLoaded(Sender: TObject);
|
|
|
|
+ class procedure OnUITimerRefresh(Sender: TObject);
|
|
class procedure OnReceivedHelloMessage(Sender: TObject);
|
|
class procedure OnReceivedHelloMessage(Sender: TObject);
|
|
- class procedure OnNodeMessageEvent(NetConnection: TNetConnection; MessageData: TRawBytes);
|
|
|
|
- class procedure OnNetStatisticsChanged(Sender: TObject);
|
|
|
|
- class procedure OnNetConnectionsUpdated(Sender: TObject);
|
|
|
|
- class procedure OnNetNodeServersUpdated(Sender: TObject);
|
|
|
|
- class procedure OnNetBlackListUpdated(Sender: TObject);
|
|
|
|
class procedure OnMiningServerNewBlockFound(Sender: TObject);
|
|
class procedure OnMiningServerNewBlockFound(Sender: TObject);
|
|
|
|
+ class procedure OnSubFormDestroyed(Sender: TObject);
|
|
class procedure OnTrayIconDblClick(Sender: TObject);
|
|
class procedure OnTrayIconDblClick(Sender: TObject);
|
|
|
|
+
|
|
|
|
+ // Aux
|
|
|
|
+ class procedure NotifyLoadedEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyLoadingEvent(Sender: TObject; const message: AnsiString; curPos, totalCount: Int64);
|
|
|
|
+ class procedure NotifyStateChanged(Sender: TObject);
|
|
|
|
+ class procedure NotifySettingsChangedEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyAccountsChangedEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyBlocksChangedEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyReceivedHelloMessageEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyNodeMessageEventEvent(NetConnection: TNetConnection; MessageData: TRawBytes);
|
|
|
|
+ class procedure NotifyNetStatisticsChangedEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyNetConnectionsUpdatedEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyNetNodeServersUpdatedEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyNetBlackListUpdatedEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyMiningServerNewBlockFoundEvent(Sender: TObject);
|
|
|
|
+ class procedure NotifyUIRefreshTimerEvent(Sender: TObject);
|
|
public
|
|
public
|
|
// Properties
|
|
// Properties
|
|
class property Enabled : boolean read GetEnabled write SetEnabled;
|
|
class property Enabled : boolean read GetEnabled write SetEnabled;
|
|
class property Started : boolean read FStarted;
|
|
class property Started : boolean read FStarted;
|
|
|
|
+ class property State : TUserInterfaceState read FState write SetState;
|
|
|
|
+ class property StateText : string read FStateText;
|
|
class property Node : TNode read FNode;
|
|
class property Node : TNode read FNode;
|
|
class property Log : TLog read FLog;
|
|
class property Log : TLog read FLog;
|
|
class property PoolMiningServer : TPoolMiningServer read FPoolMiningServer;
|
|
class property PoolMiningServer : TPoolMiningServer read FPoolMiningServer;
|
|
class property MainFormMode : TFRMMainFormMode read GetMainFormMode write SetMainFormMode;
|
|
class property MainFormMode : TFRMMainFormMode read GetMainFormMode write SetMainFormMode;
|
|
- class property StatusBar0Text : AnsiString read FStatusBar0Text write SetStatusBar0Text;
|
|
|
|
- class property StatusBar1Text : AnsiString read FStatusBar1Text write SetStatusBar1Text;
|
|
|
|
- class property StatusBar2Text : AnsiString read FStatusBar2Text write SetStatusBar2Text;
|
|
|
|
- class property MessagesNotificationText : AnsiString read FMessagesNotificationText write SetMessagesNotificationText;
|
|
|
|
|
|
+
|
|
|
|
+ // Events
|
|
|
|
+ class property AppStarted : TNotifyManyEvent read FAppStarted;
|
|
|
|
+ class property Loading : TProgressNotifyMany read FLoading;
|
|
|
|
+ class property Loaded : TNotifyManyEvent read FLoaded;
|
|
|
|
+ class property StateChanged : TNotifyManyEvent read FStateChanged;
|
|
|
|
+ class property SettingsChanged : TNotifyManyEvent read FSettingsChanged;
|
|
|
|
+ class property AccountsChanged : TNotifyManyEvent read FAccountsChanged;
|
|
|
|
+ class property BlocksChanged : TNotifyManyEvent read FBlocksChanged;
|
|
|
|
+ class property ReceivedHelloMessage : TNotifyManyEvent read FReceivedHelloMessage;
|
|
|
|
+ class property NodeMessageEvent : TNodeMessageManyEvent read FNodeMessageEvent;
|
|
|
|
+ class property NetStatisticsChanged : TNotifyManyEvent read FNetStatisticsChanged;
|
|
|
|
+ class property NetConnectionsUpdated : TNotifyManyEvent read FNetConnectionsUpdated;
|
|
|
|
+ class property NetNodeServersUpdated : TNotifyManyEvent read FNetNodeServersUpdated;
|
|
|
|
+ class property NetBlackListUpdated : TNotifyManyEvent read FNetBlackListUpdated;
|
|
|
|
+ class property MiningServerNewBlockFound : TNotifyManyEvent read FMiningServerNewBlockFound;
|
|
|
|
+ class property UIRefreshTimer : TNotifyManyEvent read FUIRefreshTimer;
|
|
|
|
|
|
// Methods
|
|
// Methods
|
|
class procedure StartApplication(mainForm : TForm);
|
|
class procedure StartApplication(mainForm : TForm);
|
|
@@ -165,6 +202,8 @@ type
|
|
|
|
|
|
TLoadDatabaseThread = Class(TPCThread)
|
|
TLoadDatabaseThread = Class(TPCThread)
|
|
protected
|
|
protected
|
|
|
|
+ procedure OnProgressNotify(sender : TObject; const message : AnsiString; curPos, totalCount : Int64);
|
|
|
|
+ procedure OnLoaded;
|
|
procedure BCExecute; override;
|
|
procedure BCExecute; override;
|
|
End;
|
|
End;
|
|
|
|
|
|
@@ -176,7 +215,7 @@ implementation
|
|
|
|
|
|
uses
|
|
uses
|
|
UFRMAbout, UFRMNodesIp, UFRMPascalCoinWalletConfig, UFRMPayloadDecoder, UFRMMemoText,
|
|
UFRMAbout, UFRMNodesIp, UFRMPascalCoinWalletConfig, UFRMPayloadDecoder, UFRMMemoText,
|
|
- UOpenSSL, UFileStorage, UTime, UCommon, USettings, UCoreUtils, UMemory,
|
|
|
|
|
|
+ UOpenSSL, UFileStorage, UTime, USettings, UCoreUtils, UMemory,
|
|
UWIZOperation, UWIZSendPASC, UWIZChangeKey, UWIZEnlistAccountForSale;
|
|
UWIZOperation, UWIZSendPASC, UWIZChangeKey, UWIZEnlistAccountForSale;
|
|
|
|
|
|
{%region UI Lifecyle}
|
|
{%region UI Lifecyle}
|
|
@@ -252,11 +291,12 @@ begin
|
|
FNode := TNode.Node;
|
|
FNode := TNode.Node;
|
|
FNode.NetServer.Port := TSettings.InternetServerPort;
|
|
FNode.NetServer.Port := TSettings.InternetServerPort;
|
|
FNode.PeerCache := TSettings.PeerCache+';'+CT_Discover_IPs;
|
|
FNode.PeerCache := TSettings.PeerCache+';'+CT_Discover_IPs;
|
|
|
|
+ FReceivedHelloMessage.Add(OnReceivedHelloMessage);
|
|
|
|
|
|
// Subscribe to Node events (TODO refactor with FNotifyEvents)
|
|
// Subscribe to Node events (TODO refactor with FNotifyEvents)
|
|
FNodeNotifyEvents := TNodeNotifyEvents.Create(FMainForm);
|
|
FNodeNotifyEvents := TNodeNotifyEvents.Create(FMainForm);
|
|
- FNodeNotifyEvents.OnBlocksChanged := OnBlocksChanged;
|
|
|
|
- FNodeNotifyEvents.OnNodeMessageEvent := OnNodeMessageEvent;
|
|
|
|
|
|
+ FNodeNotifyEvents.OnBlocksChanged := NotifyBlocksChangedEvent;
|
|
|
|
+ FNodeNotifyEvents.OnNodeMessageEvent := NotifyNodeMessageEventEvent;
|
|
|
|
|
|
// Start RPC server
|
|
// Start RPC server
|
|
FRPCServer := TRPCServer.Create;
|
|
FRPCServer := TRPCServer.Create;
|
|
@@ -271,24 +311,26 @@ begin
|
|
TFileStorage(FNode.Bank.Storage).Initialize;
|
|
TFileStorage(FNode.Bank.Storage).Initialize;
|
|
|
|
|
|
// Reading database
|
|
// Reading database
|
|
|
|
+ State := uisLoading;
|
|
|
|
+ FLoaded.Add(OnLoaded);
|
|
TLoadDatabaseThread.Create(false).FreeOnTerminate := true;
|
|
TLoadDatabaseThread.Create(false).FreeOnTerminate := true;
|
|
|
|
|
|
// Init
|
|
// Init
|
|
- TNetData.NetData.OnReceivedHelloMessage := OnReceivedHelloMessage;
|
|
|
|
- TNetData.NetData.OnStatisticsChanged := OnNetStatisticsChanged;
|
|
|
|
- TNetData.NetData.OnNetConnectionsUpdated := OnNetConnectionsUpdated;
|
|
|
|
- TNetData.NetData.OnNodeServersUpdated := OnNetNodeServersUpdated;
|
|
|
|
- TNetData.NetData.OnBlackListUpdated := OnNetBlackListUpdated;
|
|
|
|
|
|
+ TNetData.NetData.OnReceivedHelloMessage := NotifyReceivedHelloMessageEvent;
|
|
|
|
+ TNetData.NetData.OnStatisticsChanged := NotifyNetStatisticsChangedEvent;
|
|
|
|
+ TNetData.NetData.OnNetConnectionsUpdated := NotifyNetConnectionsUpdatedEvent;
|
|
|
|
+ TNetData.NetData.OnNodeServersUpdated := NotifyNetNodeServersUpdatedEvent;
|
|
|
|
+ TNetData.NetData.OnBlackListUpdated := NotifyNetBlackListUpdatedEvent;
|
|
|
|
|
|
// Start refresh timer
|
|
// Start refresh timer
|
|
- FTimerUpdateStatus.OnTimer := OnTimerUpdateStatusTimer;
|
|
|
|
|
|
+ FTimerUpdateStatus.OnTimer := NotifyUIRefreshTimerEvent;
|
|
FTimerUpdateStatus.Interval := 1000;
|
|
FTimerUpdateStatus.Interval := 1000;
|
|
FTimerUpdateStatus.Enabled := true;
|
|
FTimerUpdateStatus.Enabled := true;
|
|
|
|
+ FUIRefreshTimer.Add(OnUITimerRefresh); //TODO: move to initialisation?
|
|
|
|
|
|
// open the sync dialog
|
|
// open the sync dialog
|
|
- FMainForm.SyncControl.UpdateBlockChainState; //TODO fix this work-flow
|
|
|
|
- RefreshConnectionStatusDisplay;
|
|
|
|
FStarted := true;
|
|
FStarted := true;
|
|
|
|
+ FAppStarted.Invoke(nil);
|
|
Except
|
|
Except
|
|
On E:Exception do begin
|
|
On E:Exception do begin
|
|
E.Message := 'An error occurred during initialization. Application cannot continue:'+#10+#10+E.Message+#10+#10+'Application will close...';
|
|
E.Message := 'An error occurred during initialization. Application cannot continue:'+#10+#10+E.Message+#10+#10+'Application will close...';
|
|
@@ -297,15 +339,8 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
-
|
|
|
|
// Notify accounts changed
|
|
// Notify accounts changed
|
|
- OnAccountsChanged(FMainForm);
|
|
|
|
-
|
|
|
|
- // Refresh status bar since may not have been displayed
|
|
|
|
- SetStatusBar0Text(FStatusBar0Text);
|
|
|
|
- SetStatusBar0Text(FStatusBar1Text);
|
|
|
|
- SetStatusBar0Text(FStatusBar2Text);
|
|
|
|
- SetMessagesNotificationText(FMessagesNotificationText);
|
|
|
|
|
|
+ NotifyAccountsChangedEvent(FMainForm);
|
|
|
|
|
|
// Show sync dialog
|
|
// Show sync dialog
|
|
ShowSyncDialog;
|
|
ShowSyncDialog;
|
|
@@ -327,7 +362,7 @@ begin
|
|
TLog.NewLog(ltinfo,Classname,'Quit Application - START');
|
|
TLog.NewLog(ltinfo,Classname,'Quit Application - START');
|
|
Try
|
|
Try
|
|
step := 'Saving Settings';
|
|
step := 'Saving Settings';
|
|
- TSettings.OnChanged.Remove(OnSettingsChanged);
|
|
|
|
|
|
+ TSettings.OnChanged.Remove(NotifySettingsChangedEvent);
|
|
TSettings.Save;
|
|
TSettings.Save;
|
|
|
|
|
|
// Destroys root form, non-modal forms and all their attached components
|
|
// Destroys root form, non-modal forms and all their attached components
|
|
@@ -406,7 +441,41 @@ begin
|
|
Application.BringToFront();
|
|
Application.BringToFront();
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.FinishedLoadingDatabase;
|
|
|
|
|
|
+{%endregion}
|
|
|
|
+
|
|
|
|
+{%region UI Handlers }
|
|
|
|
+
|
|
|
|
+class procedure TUserInterface.OnTrayIconDblClick(Sender: TObject);
|
|
|
|
+begin
|
|
|
|
+ RunInForeground;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TUserInterface.OnSubFormDestroyed(Sender: TObject);
|
|
|
|
+begin
|
|
|
|
+ try
|
|
|
|
+ FUILock.Acquire;
|
|
|
|
+ if Sender = FAccountExplorer then
|
|
|
|
+ FAccountExplorer := nil // form free's on close
|
|
|
|
+ else if Sender = FPendingOperationForm then
|
|
|
|
+ FPendingOperationForm := nil // form free's on close
|
|
|
|
+ else if Sender = FOperationsExplorerForm then
|
|
|
|
+ FOperationsExplorerForm := nil // form free's on close
|
|
|
|
+ else if Sender = FBlockExplorerForm then
|
|
|
|
+ FBlockExplorerForm := nil // form free's on close
|
|
|
|
+ else if Sender = FLogsForm then
|
|
|
|
+ FLogsForm := nil // form free's on close
|
|
|
|
+ else if Sender = FNodesForm then
|
|
|
|
+ FNodesForm := nil // form free's on close
|
|
|
|
+ else if Sender = FMessagesForm then
|
|
|
|
+ FMessagesForm := nil
|
|
|
|
+ else
|
|
|
|
+ raise Exception.Create('Internal Error: [NotifySubFormDestroyed] encountered an unknown sub-form instance');
|
|
|
|
+ finally
|
|
|
|
+ FUILock.Release;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TUserInterface.OnLoaded(Sender: TObject);
|
|
begin
|
|
begin
|
|
FPoolMiningServer := TPoolMiningServer.Create;
|
|
FPoolMiningServer := TPoolMiningServer.Create;
|
|
FPoolMiningServer.Port := TSettings.MinerServerRpcPort;
|
|
FPoolMiningServer.Port := TSettings.MinerServerRpcPort;
|
|
@@ -414,11 +483,99 @@ begin
|
|
FPoolMiningServer.MinerPayload := TSettings.MinerName;
|
|
FPoolMiningServer.MinerPayload := TSettings.MinerName;
|
|
FNode.Operations.AccountKey := TWallet.MiningKey;
|
|
FNode.Operations.AccountKey := TWallet.MiningKey;
|
|
FPoolMiningServer.Active := TSettings.MinerServerRpcActive;
|
|
FPoolMiningServer.Active := TSettings.MinerServerRpcActive;
|
|
- FPoolMiningServer.OnMiningServerNewBlockFound := OnMiningServerNewBlockFound;
|
|
|
|
- FMainForm.SyncControl.OnFinishedLoadingDatabase;
|
|
|
|
- FMainForm.OnFinishedLoadingDatabase;
|
|
|
|
- // Refresh UI
|
|
|
|
- OnAccountsChanged(FMainForm);
|
|
|
|
|
|
+ FPoolMiningServer.OnMiningServerNewBlockFound := NotifyMiningServerNewBlockFoundEvent;
|
|
|
|
+ State := uisLoaded;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TUserInterface.OnUITimerRefresh(Sender: Tobject);
|
|
|
|
+var
|
|
|
|
+ LActive, LDiscoveringPeers, LGettingNewBlockchain, LRemoteHasBiggerBlock, LNoConnections : boolean;
|
|
|
|
+ LState : TUserInterfaceState;
|
|
|
|
+ LLocalTip, LRemoteTip : Cardinal;
|
|
|
|
+begin
|
|
|
|
+ LState := FState;
|
|
|
|
+ LActive := FNode.NetServer.Active;
|
|
|
|
+ LDiscoveringPeers := TNetData.NetData.IsDiscoveringServers;
|
|
|
|
+ LGettingNewBlockchain := TNetData.NetData.IsGettingNewBlockChainFromClient;
|
|
|
|
+ LLocalTip := Node.Bank.BlocksCount;
|
|
|
|
+ LRemoteTip := TNetData.NetData.MaxRemoteOperationBlock.block;
|
|
|
|
+ LRemoteHasBiggerBlock := LRemoteTip > LLocalTip;
|
|
|
|
+ LNoConnections := TNetData.NetData.NetStatistics.ActiveConnections = 0;
|
|
|
|
+
|
|
|
|
+ if LActive then begin
|
|
|
|
+ if LDiscoveringPeers then
|
|
|
|
+ LState := uisDiscoveringPeers;
|
|
|
|
+
|
|
|
|
+ if LGettingNewBlockchain OR LRemoteHasBiggerBlock then
|
|
|
|
+ LState := uisSyncronizingBlockchain;
|
|
|
|
+
|
|
|
|
+ if LNoConnections then
|
|
|
|
+ LState := uisIsolated;
|
|
|
|
+
|
|
|
|
+ if (NOT LDiscoveringPeers) AND (NOT LGettingNewBlockchain) AND (NOT LRemoteHasBiggerBlock) AND (NOT LNoConnections) then
|
|
|
|
+ LState := uisActive;
|
|
|
|
+
|
|
|
|
+ end else LState := uisDisconnected;
|
|
|
|
+ State := LState;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TUserInterface.OnSettingsChanged(Sender: TObject);
|
|
|
|
+Var wa : Boolean;
|
|
|
|
+ i : Integer;
|
|
|
|
+begin
|
|
|
|
+ if TSettings.SaveLogFiles then begin
|
|
|
|
+ if TSettings.SaveDebugLogs then
|
|
|
|
+ FLog.SaveTypes := CT_TLogTypes_ALL
|
|
|
|
+ else
|
|
|
|
+ FLog.SaveTypes := CT_TLogTypes_DEFAULT;
|
|
|
|
+ FLog.FileName := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'PascalCointWallet.log';
|
|
|
|
+ end else begin
|
|
|
|
+ FLog.SaveTypes := [];
|
|
|
|
+ FLog.FileName := '';
|
|
|
|
+ end;
|
|
|
|
+ if Assigned(FNode) then begin
|
|
|
|
+ wa := FNode.NetServer.Active;
|
|
|
|
+ FNode.NetServer.Port := TSettings.InternetServerPort;
|
|
|
|
+ FNode.NetServer.Active := wa;
|
|
|
|
+ FNode.Operations.BlockPayload := TSettings.MinerName;
|
|
|
|
+ FNode.NodeLogFilename := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'blocks.log';
|
|
|
|
+ end;
|
|
|
|
+ if Assigned(FPoolMiningServer) then begin
|
|
|
|
+ if FPoolMiningServer.Port <> TSettings.MinerServerRpcPort then begin
|
|
|
|
+ FPoolMiningServer.Active := false;
|
|
|
|
+ FPoolMiningServer.Port := TSettings.MinerServerRpcPort;
|
|
|
|
+ end;
|
|
|
|
+ FPoolMiningServer.Active :=TSettings.MinerServerRpcActive;
|
|
|
|
+ FPoolMiningServer.UpdateAccountAndPayload(TWallet.MiningKey, TSettings.MinerName);
|
|
|
|
+ end;
|
|
|
|
+ if Assigned(FRPCServer) then begin
|
|
|
|
+ FRPCServer.Active := TSettings.RpcPortEnabled;
|
|
|
|
+ FRPCServer.ValidIPs := TSettings.RpcAllowedIPs;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TUserInterface.OnReceivedHelloMessage(Sender: TObject);
|
|
|
|
+Var nsarr : TNodeServerAddressArray;
|
|
|
|
+ i : Integer;
|
|
|
|
+ s : AnsiString;
|
|
|
|
+begin
|
|
|
|
+ // Internal handler
|
|
|
|
+ // No lock required
|
|
|
|
+ //CheckMining;
|
|
|
|
+ // Update node servers Peer Cache
|
|
|
|
+ nsarr := TNetData.NetData.NodeServersAddresses.GetValidNodeServers(true,0);
|
|
|
|
+ s := '';
|
|
|
|
+ for i := low(nsarr) to High(nsarr) do begin
|
|
|
|
+ if (s<>'') then s := s+';';
|
|
|
|
+ s := s + nsarr[i].ip+':'+IntToStr( nsarr[i].port );
|
|
|
|
+ end;
|
|
|
|
+ TSettings.PeerCache := s;
|
|
|
|
+
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TUserInterface.OnMiningServerNewBlockFound(Sender: TObject);
|
|
|
|
+begin
|
|
|
|
+ FPoolMiningServer.MinerAccountKey := TWallet.MiningKey;
|
|
end;
|
|
end;
|
|
|
|
|
|
{%endregion}
|
|
{%endregion}
|
|
@@ -808,28 +965,6 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.RefreshConnectionStatusDisplay;
|
|
|
|
-var errors : AnsiString;
|
|
|
|
-begin
|
|
|
|
- FUILock.Acquire;
|
|
|
|
- Try
|
|
|
|
- FMainForm.SyncControl.UpdateNodeStatus;
|
|
|
|
- OnNetStatisticsChanged(FMainForm);
|
|
|
|
- if Assigned(FNode) then begin
|
|
|
|
- if FNode.IsBlockChainValid(errors) then begin
|
|
|
|
- StatusBar2Text := Format('Last account time:%s',
|
|
|
|
- [FormatDateTime('dd/mm/yyyy hh:nn:ss',UnivDateTime2LocalDateTime(UnixToUnivDateTime( FNode.Bank.LastOperationBlock.timestamp )))]);
|
|
|
|
- end else begin
|
|
|
|
- StatusBar2Text := 'NO BLOCKCHAIN: '+errors;
|
|
|
|
- end;
|
|
|
|
- end else begin
|
|
|
|
- StatusBar2Text := '';
|
|
|
|
- end;
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
-end;
|
|
|
|
-
|
|
|
|
{%endregion}
|
|
{%endregion}
|
|
|
|
|
|
{%region Auxillary methods}
|
|
{%region Auxillary methods}
|
|
@@ -841,7 +976,16 @@ end;
|
|
|
|
|
|
class procedure TUserInterface.SetEnabled(ABool: boolean);
|
|
class procedure TUserInterface.SetEnabled(ABool: boolean);
|
|
begin
|
|
begin
|
|
- FMainForm.Enabled:=ABool;
|
|
|
|
|
|
+ if Assigned(FMainForm) then
|
|
|
|
+ FMainForm.Enabled:=ABool;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TUserInterface.SetState(AState : TUserInterfaceState); static;
|
|
|
|
+begin
|
|
|
|
+ if AState = FState then
|
|
|
|
+ exit;
|
|
|
|
+ FState := AState;
|
|
|
|
+ NotifyStateChanged(nil);
|
|
end;
|
|
end;
|
|
|
|
|
|
class procedure TUserInterface.SetMainFormMode(AMode: TFRMMainFormMode);
|
|
class procedure TUserInterface.SetMainFormMode(AMode: TFRMMainFormMode);
|
|
@@ -859,251 +1003,97 @@ begin
|
|
Result := FMainForm.Mode;
|
|
Result := FMainForm.Mode;
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.SetStatusBar0Text(const text : AnsiString); static;
|
|
|
|
|
|
+class procedure TUserInterface.NotifyLoadedEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- FStatusBar0Text := text;
|
|
|
|
- if Assigned(FMainForm) then
|
|
|
|
- FMainForm.sbStatusBar.Panels[0].Text := FStatusBar0Text;
|
|
|
|
|
|
+ TUserInterface.FLoaded.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.SetStatusBar1Text(const text : AnsiString); static;
|
|
|
|
|
|
+class procedure TUserInterface.NotifyLoadingEvent(Sender: TObject; const message: AnsiString; curPos, totalCount: Int64);
|
|
begin
|
|
begin
|
|
- FStatusBar1Text := text;
|
|
|
|
- if Assigned(FMainForm) then
|
|
|
|
- FMainForm.sbStatusBar.Panels[1].Text := text;
|
|
|
|
|
|
+ TUserInterface.FLoading.Invoke(Sender, message, curPos, totalCount);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.SetStatusBar2Text(const text : AnsiString); static;
|
|
|
|
|
|
+class procedure TUserInterface.NotifyStateChanged(Sender: TObject);
|
|
begin
|
|
begin
|
|
- FStatusBar2Text := text;
|
|
|
|
- if Assigned(FMainForm) then
|
|
|
|
- FMainForm.sbStatusBar.Panels[2].Text := text;
|
|
|
|
|
|
+ TUserInterface.FStateChanged.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.SetMessagesNotificationText(const text : AnsiString); static;
|
|
|
|
|
|
+class procedure TUserInterface.NotifySettingsChangedEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- FMessagesNotificationText := text;
|
|
|
|
- if Assigned(FMainForm.SyncControl) then begin
|
|
|
|
- if (text = '') then
|
|
|
|
- FMainForm.SyncControl.lblReceivedMessages.Visible := false;
|
|
|
|
- FMainForm.SyncControl.lblReceivedMessages.Caption := text;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.FSettingsChanged.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-{%endregion}
|
|
|
|
-
|
|
|
|
-{%region Handlers -- TODO: many need to be refactored out with TNotifyManyEvent}
|
|
|
|
-
|
|
|
|
-class procedure TUserInterface.OnSettingsChanged(Sender: TObject);
|
|
|
|
-Var wa : Boolean;
|
|
|
|
- i : Integer;
|
|
|
|
|
|
+class procedure TUserInterface.NotifyAccountsChangedEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- if TSettings.SaveLogFiles then begin
|
|
|
|
- if TSettings.SaveDebugLogs then
|
|
|
|
- FLog.SaveTypes := CT_TLogTypes_ALL
|
|
|
|
- else
|
|
|
|
- FLog.SaveTypes := CT_TLogTypes_DEFAULT;
|
|
|
|
- FLog.FileName := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'PascalCointWallet.log';
|
|
|
|
- end else begin
|
|
|
|
- FLog.SaveTypes := [];
|
|
|
|
- FLog.FileName := '';
|
|
|
|
- end;
|
|
|
|
- if Assigned(FNode) then begin
|
|
|
|
- wa := FNode.NetServer.Active;
|
|
|
|
- FNode.NetServer.Port := TSettings.InternetServerPort;
|
|
|
|
- FNode.NetServer.Active := wa;
|
|
|
|
- FNode.Operations.BlockPayload := TSettings.MinerName;
|
|
|
|
- FNode.NodeLogFilename := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'blocks.log';
|
|
|
|
- end;
|
|
|
|
- if Assigned(FPoolMiningServer) then begin
|
|
|
|
- if FPoolMiningServer.Port <> TSettings.MinerServerRpcPort then begin
|
|
|
|
- FPoolMiningServer.Active := false;
|
|
|
|
- FPoolMiningServer.Port := TSettings.MinerServerRpcPort;
|
|
|
|
- end;
|
|
|
|
- FPoolMiningServer.Active :=TSettings.MinerServerRpcActive;
|
|
|
|
- FPoolMiningServer.UpdateAccountAndPayload(TWallet.MiningKey, TSettings.MinerName);
|
|
|
|
- end;
|
|
|
|
- if Assigned(FRPCServer) then begin
|
|
|
|
- FRPCServer.Active := TSettings.RpcPortEnabled;
|
|
|
|
- FRPCServer.ValidIPs := TSettings.RpcAllowedIPs;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.FAccountsChanged.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnAccountsChanged(Sender: TObject);
|
|
|
|
|
|
+class procedure TUserInterface.NotifyBlocksChangedEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- FUILock.Acquire;
|
|
|
|
- Try
|
|
|
|
- if Assigned(FAccountExplorer) then
|
|
|
|
- FAccountExplorer.RefreshAccountsGrid(true);
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.FBlocksChanged.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnBlocksChanged(Sender: TObject);
|
|
|
|
|
|
+class procedure TUserInterface.NotifyNodeMessageEventEvent(NetConnection: TNetConnection; MessageData: TRawBytes);
|
|
begin
|
|
begin
|
|
- FUILock.Acquire;
|
|
|
|
- try
|
|
|
|
- try
|
|
|
|
- if Assigned(FAccountExplorer) then
|
|
|
|
- FAccountExplorer.RefreshAccountsGrid(false);
|
|
|
|
- FMainForm.SyncControl.UpdateBlockChainState;
|
|
|
|
- except
|
|
|
|
- On E:Exception do begin
|
|
|
|
- E.Message := 'Error at OnNewAccount '+E.Message;
|
|
|
|
- Raise;
|
|
|
|
- end;
|
|
|
|
- end;
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.FNodeMessageEvent.Invoke(NetConnection, MessageData);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnNodeMessageEvent(NetConnection: TNetConnection; MessageData: TRawBytes);
|
|
|
|
|
|
+class procedure TUserInterface.NotifyReceivedHelloMessageEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- FUILock.Acquire;
|
|
|
|
- Try
|
|
|
|
- if Assigned(FMessagesForm) then
|
|
|
|
- FMessagesForm.OnNodeMessageEvent(NetConnection, MessageData);
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.FReceivedHelloMessage.Invoke(Sender);;
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnReceivedHelloMessage(Sender: TObject);
|
|
|
|
-Var nsarr : TNodeServerAddressArray;
|
|
|
|
- i : Integer;
|
|
|
|
- s : AnsiString;
|
|
|
|
|
|
+class procedure TUserInterface.NotifyNetStatisticsChangedEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- // No lock required
|
|
|
|
- //CheckMining;
|
|
|
|
- // Update node servers Peer Cache
|
|
|
|
- nsarr := TNetData.NetData.NodeServersAddresses.GetValidNodeServers(true,0);
|
|
|
|
- s := '';
|
|
|
|
- for i := low(nsarr) to High(nsarr) do begin
|
|
|
|
- if (s<>'') then s := s+';';
|
|
|
|
- s := s + nsarr[i].ip+':'+IntToStr( nsarr[i].port );
|
|
|
|
- end;
|
|
|
|
- TSettings.PeerCache := s;
|
|
|
|
- TNode.Node.PeerCache := s;
|
|
|
|
|
|
+ TUserInterface.FNetStatisticsChanged.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnNetStatisticsChanged(Sender: TObject);
|
|
|
|
-Var NS : TNetStatistics;
|
|
|
|
|
|
+class procedure TUserInterface.NotifyNetConnectionsUpdatedEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- FUILock.Acquire; // TODO - lock may not be required
|
|
|
|
- Try
|
|
|
|
- //HS CheckMining;
|
|
|
|
- if Assigned(FNode) then begin
|
|
|
|
- If FNode.NetServer.Active then begin
|
|
|
|
- StatusBar0Text := 'Active (Port '+Inttostr(FNode.NetServer.Port)+')';
|
|
|
|
- end else StatusBar0Text := 'Server stopped';
|
|
|
|
- NS := TNetData.NetData.NetStatistics;
|
|
|
|
- StatusBar1Text := Format('Connections:%d Clients:%d Servers:%d - Rcvd:%d Kb Send:%d Kb',
|
|
|
|
- [NS.ActiveConnections,NS.ClientsConnections,NS.ServersConnections,NS.BytesReceived DIV 1024,NS.BytesSend DIV 1024]);
|
|
|
|
- end else begin
|
|
|
|
- StatusBar0Text := '';
|
|
|
|
- StatusBar1Text := '';
|
|
|
|
- end;
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.FNetConnectionsUpdated.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnNetConnectionsUpdated(Sender: TObject);
|
|
|
|
|
|
+class procedure TUserInterface.NotifyNetNodeServersUpdatedEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- try
|
|
|
|
- FUILock.Acquire;
|
|
|
|
- if Assigned(FNodesForm) then
|
|
|
|
- FNodesForm.OnNetConnectionsUpdated;
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.NetNodeServersUpdated.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnNetNodeServersUpdated(Sender: TObject);
|
|
|
|
|
|
+class procedure TUserInterface.NotifyNetBlackListUpdatedEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- try
|
|
|
|
- FUILock.Acquire;
|
|
|
|
- if Assigned(FNodesForm) then
|
|
|
|
- FNodesForm.OnNetNodeServersUpdated;
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.FNetBlackListUpdated.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnNetBlackListUpdated(Sender: TObject);
|
|
|
|
|
|
+class procedure TUserInterface.NotifyMiningServerNewBlockFoundEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- try
|
|
|
|
- FUILock.Acquire;
|
|
|
|
- if Assigned(FNodesForm) then
|
|
|
|
- FNodesForm.OnNetBlackListUpdated;
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.FMiningServerNewBlockFound.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnMiningServerNewBlockFound(Sender: TObject);
|
|
|
|
|
|
+class procedure TUserInterface.NotifyUIRefreshTimerEvent(Sender: TObject);
|
|
begin
|
|
begin
|
|
- // No lock required
|
|
|
|
- FPoolMiningServer.MinerAccountKey := TWallet.MiningKey;
|
|
|
|
|
|
+ TUserInterface.FUIRefreshTimer.Invoke(Sender);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnTimerUpdateStatusTimer(Sender: TObject);
|
|
|
|
-begin
|
|
|
|
- Try
|
|
|
|
- RefreshConnectionStatusDisplay;
|
|
|
|
- FMainForm.SyncControl.UpdateBlockChainState;
|
|
|
|
- FMainForm.SyncControl.UpdateNodeStatus;
|
|
|
|
- Except
|
|
|
|
- On E:Exception do begin
|
|
|
|
- E.Message := 'Exception at TimerUpdate '+E.ClassName+': '+E.Message;
|
|
|
|
- TLog.NewLog(lterror,ClassName,E.Message);
|
|
|
|
- end;
|
|
|
|
- End;
|
|
|
|
-end;
|
|
|
|
|
|
+{%endregion}
|
|
|
|
|
|
-class procedure TUserInterface.OnTrayIconDblClick(Sender: TObject);
|
|
|
|
|
|
+{ TLoadDatabaseThread }
|
|
|
|
+
|
|
|
|
+procedure TLoadDatabaseThread.OnProgressNotify(sender: TObject; const message: AnsiString; curPos, totalCount: Int64);
|
|
begin
|
|
begin
|
|
- RunInForeground;
|
|
|
|
|
|
+ TUserInterface.NotifyLoadingEvent(sender, message, curPos, totalCount);
|
|
end;
|
|
end;
|
|
|
|
|
|
-class procedure TUserInterface.OnSubFormDestroyed(Sender: TObject);
|
|
|
|
|
|
+procedure TLoadDatabaseThread.OnLoaded;
|
|
begin
|
|
begin
|
|
- try
|
|
|
|
- FUILock.Acquire;
|
|
|
|
- if Sender = FAccountExplorer then
|
|
|
|
- FAccountExplorer := nil // form free's on close
|
|
|
|
- else if Sender = FPendingOperationForm then
|
|
|
|
- FPendingOperationForm := nil // form free's on close
|
|
|
|
- else if Sender = FOperationsExplorerForm then
|
|
|
|
- FOperationsExplorerForm := nil // form free's on close
|
|
|
|
- else if Sender = FBlockExplorerForm then
|
|
|
|
- FBlockExplorerForm := nil // form free's on close
|
|
|
|
- else if Sender = FLogsForm then
|
|
|
|
- FLogsForm := nil // form free's on close
|
|
|
|
- else if Sender = FNodesForm then
|
|
|
|
- FNodesForm := nil // form free's on close
|
|
|
|
- else if Sender = FMessagesForm then
|
|
|
|
- FMessagesForm := nil
|
|
|
|
- else
|
|
|
|
- raise Exception.Create('Internal Error: [NotifySubFormDestroyed] encountered an unknown sub-form instance');
|
|
|
|
- finally
|
|
|
|
- FUILock.Release;
|
|
|
|
- end;
|
|
|
|
|
|
+ TUserInterface.NotifyLoadedEvent(Self);
|
|
end;
|
|
end;
|
|
|
|
|
|
-{%endregion}
|
|
|
|
-
|
|
|
|
-{ TLoadDatabaseThread }
|
|
|
|
-
|
|
|
|
procedure TLoadDatabaseThread.BCExecute;
|
|
procedure TLoadDatabaseThread.BCExecute;
|
|
begin
|
|
begin
|
|
// Read Operations saved from disk
|
|
// Read Operations saved from disk
|
|
- TNode.Node.InitSafeboxAndOperations; // New Build 2.1.4 to load pending operations buffer
|
|
|
|
|
|
+ TNode.Node.InitSafeboxAndOperations($FFFFFFFF, OnProgressNotify);
|
|
TNode.Node.AutoDiscoverNodes(CT_Discover_IPs);
|
|
TNode.Node.AutoDiscoverNodes(CT_Discover_IPs);
|
|
TNode.Node.NetServer.Active := true;
|
|
TNode.Node.NetServer.Active := true;
|
|
- Synchronize( TUserInterface.FinishedLoadingDatabase );
|
|
|
|
|
|
+ Synchronize( OnLoaded );
|
|
end;
|
|
end;
|
|
|
|
|
|
initialization
|
|
initialization
|
|
@@ -1111,4 +1101,3 @@ initialization
|
|
finalization
|
|
finalization
|
|
// TODO - final cleanup here, show a modal dialog?
|
|
// TODO - final cleanup here, show a modal dialog?
|
|
end.
|
|
end.
|
|
-
|
|
|