Unit MarketViewBase; Interface Uses CustomTables, GenericDataNodes, DataNodes, SaveAndRestore, MarketData, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Menus, Dialogs, Grids, ExtCtrls; Type { These are the instructions for telling the market view which stock windows it needs to send stock symbols to. See MarketViewLinks.pas for the human readable description of each field. } TMarketViewLink = Record DoubleClickGroup, CycleGroup : String; SortGroup : Array [1..5] Of String; CycleTimeMS : Integer; RowLimit : Integer; End; TTableColumnDescriptor = Class; TSymbolTableRowDescriptor = Class; TTableColumnDescriptorFactory = Class; { This allows me to access TStringGrid.MoveRow. That is used by the sorting algorithm. } TMoveGrid = Class(TStringGrid) End; { When the user requests a sort, this can mean one of three things: Turn on autosort for this column, manually sort up, or manually sort down. A property of the grid tells us which one the user wants. If the user is in autosort mode, doubleclicking a second time switches the order. The same is not true about manual sorting. If the user is manually sorting, then doubleclicking again means to redo the previous sort. } TSortMode = (smAuto, smManualAscending, smManualDecending); TStringArray = Array Of String; { This is the base class for tables which display data in rows and columns. Each row represents some data, typically a stock symbol. Each column is a formula with formatting instructions. Superclasses are responsible for filling in the rows of the table. The user can select columns from a menu. The data in this table is stored the objects property of a TStringGrid. I chose this because it automatically rearranges the data whenever a user drags and drops a column. The data is layed out in four sections. The top left corner is always empty. The remainder of the top row is filled with TTableColumnDescriptor objects. These can be Nil if the column is empty. (The TStringTable wants to always have at least one row and one column which are not fixed.) The remainder of the left column is filled with TSymbolTableRowDescriptor objects. These can be disabled, but cannot be Nil. The remainder of the table is filled with TTableCellData objects. } TMarketViewBase = Class(TForm, ISavable) Private { This is the visible part of the grid. } Grid: TMoveGrid; { This stores the default colors used by the formatting routines. } StandardPalette : TStandardPalette; { These handle the standard callbacks from the Grid and the form. } Procedure GridDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); Procedure GridGetEditText(Sender: TObject; ACol, ARow: Integer; Var Value: String); Procedure GridSelectCell(Sender: TObject; ACol, ARow: Integer; Var CanSelect: Boolean); Procedure GridSetEditText(Sender: TObject; ACol, ARow: Integer; Const Value: String); Procedure GridDoubleClick(Sender: TObject); Procedure FormClose(Sender: TObject; Var Action: TCloseAction); Private { These allow us to refresh data at the right time. They should be replaced with a TDeferredUpdate } Dirty : Boolean; Timer : TDataNodeLink; Procedure RedrawNow; { Assumes that a row contains no meaningful data. Creates a row descriptor for the first column, and cell data objects for the remaining columns. It is an error to call this on the top row (row = 0) or on a row that does not exist in the table. } Procedure InitializeRow(Row : Integer); { This releases any old data nodes that we were listing to. Then it creates new data nodes. It uses the column and row data to know what data to request, then it stores in data nodes in the cell's cell data object. } Procedure UpdateCell(ACol, ARow : Integer); { This is called when the user double clicks inside of a cell. } Procedure DoCellClick(ACol, ARow : Integer); { This resizes all rows and columns. The new sizes are based on the hints given by the column headers. If the user resized a column, that information is thrown away. } Procedure SetDefaultCellSizes; { This applies some colors from the pallette directly properties of the form. Other colors are used in formulas; data nodes pass that information along. } Procedure FromPaletteToForm; { This is a debugging and profiling operation. It redraws the window immediately 100 times, the tells the user how long it took. } Procedure TimeTest(Sender : TObject); { These operations support the sorting user interface. } Private AutoSortDirection : Boolean; AutoSortColumn : TTableColumnDescriptor; AutoSortTimer : TTimer; LastAutoSortTime : TDateTime; ColumnHeaders : TCellRenderer; AutoSortPeriodMS : Integer; SortMode : TSortMode; Function CurrentAutoSortColumnNumber : Integer; Procedure OnAutoSortTimer(Sender : TObject); Procedure SetAutoSortColumn(Column : Integer); Procedure SetAutoSortColumnAndDirection(Column : Integer); Procedure ClearAutoSort; Procedure UpdateAutoSortHeader; Procedure SortOnce(Col : Integer; Direction : Boolean); { These operations support the implementation of sorting. } Private RowPositions : Array Of Integer; Procedure InitSort; Procedure CommitSort; Procedure SortMoveRow(OldPos, NewPos : Integer); Function SortGetCell(Col, Row : Integer) : TObject; { These operations send stock symbols from this table to stock windows. See MarketViewLinks.Pas for the human readable description of the options. } Private MarketViewLink : TMarketViewLink; PreviousCycleRow : Integer; CycleTimer : TTimer; Procedure EditMarketViewLinks(Sender : TObject); Function SendRowToGroup(RowNumber : Integer; Group : String) : Boolean; Procedure OnCycleTimer(Sender : TObject); Procedure LoadMarketViewLinks; Private { This is the item on the standard market view menu labeled "Add". It displays a submenu listing each standard column which is available. } FColumnsMenuItem : TMenuItem; { This lists the standard columns, exacly as they appear on the menu. Each one is attached to a ColumnDescriptor factory, which is used if the user selects that column from the menu. } FColumnFactories : TStringList; { This is the callback from the menu when the user uses the menu to add a column. } Procedure DoAddColumn(Sender : TObject); { This initializes FColumnFactories. Override GetStandardColumnNames to customize this process. } Procedure CreateStandardColumns; { This updates the menu to reflect changes in FColumnFactories. } Procedure RefreshColumnMenu; Private { Each window is associated with one account. Any columns which refer to portfolio data will refer to this account. } FAccount : String; Protected { See FAccount, above. } Procedure SetAccount(Account : String); Virtual; Property Account : String Read FAccount Write SetAccount; { This fills in blanks in the factory which are specific to the window. (A seperate routine fills in blanks which are specific to the row.) This base class only fills in one value, the account. Override this if a new window provides more data. } Procedure DoReplacements(Factory : IGenericDataNodeFactory); Virtual; { This callback called whenever we have new data. For simplicity we always repaint the entire screen any time we have new data. There are also performance concerns, because windows slows down if you invalidate a lot of small rectangles. It appears to use an N-squared data structure to hold these. } Procedure NewData; { This is called every time the timer goes off. If the screen is dirty, we repaint the entire screen. If we are autosorting, we repaint one column header every time the timer goes off. } Procedure CheckForRedraw; Virtual; { This adds or removes rows. } Procedure SetSymbolCount(Count : Integer); { This sets the number of rows, then fills in each row with a given symbol. } Procedure SetSymbolList(Symbols : TStrings); Virtual; { This addes the given column descriptor to the right side of the table. If the width is 0, we automatically resize all columns to their default values. Otherwise we use the given width in pixels. In the future we need to only set the size of the given column, not all columns. } Procedure AddColumn(ColumnDescriptor : TTableColumnDescriptor; Width : Integer = 0); Overload; { This reads the approprate values from the factory, then calls the function above. The factory is not modified or freed. } Procedure AddColumn(Factory : TTableColumnDescriptorFactory); Overload; { This removes the numbered column. It is an error to remove the first column (Column = 0), or a colum which does not exist. } Procedure RemoveColumn(Column : Integer); { This maps mouse coordinates to a cell number. Note that mouse coordinates are relative to the screen, not the window. If these coordinates are not in any cell, ACol and ARow are both set to -1. } Procedure GetCell(Point : TPoint; Out ACol, ARow : Integer); { This creates a new row descriptor. The base implementation stores a stock symbol for each row. Superclasses can create more complicated row descriptors which store more data. } Function NewRowDescriptor : TSymbolTableRowDescriptor; Virtual; { Row 0 is the first data row. Non existant rows return nil. Legal rows will never return Nil. Note that superclasses do not have access to the underlying data structures, so they must use this function. } Function GetRowDescriptor(Row : Integer) : TSymbolTableRowDescriptor; { This is the number of rows, not counting the header. Note: This might not be the same as the last value you put into SetRowCount. In particular, SetRowCount(0) will create 1 row, which is gaurenteed to be empty. } Function GetRowCount : Integer; { This refreshes all data cells to match the formulas in the columns and the values in the rows. } Procedure UpdateAllCells; { This create a column descriptor based on the given instructions. See TTableColumnDescriptorFactory for the current format of this string. In the event of an error, this returns Nil. These descriptors are not intended for human consumption. This is a closed loop, where the columns write instructions to a stream when you save them, and read those same instructions back when you restore them. } Function CreateColumnDescriptor(Instructions : String) : TTableColumnDescriptor; Virtual; { This creates a column descriptor based on the name of a standard column. (Currently this is the only way to create a column.) The column name is the same name which appears on the menu. This returns Nil if there is no standard column with that name. } Function CreateStandardColumn(ColumnName : String) : TTableColumnDescriptor; Virtual; { This returns a list of the column names which appear on the menu. Everything returned from here should be handled by CreateStandardColumn. The two both use the same format. } Function GetStandardColumnNames : TStringArray; Virtual; Public { Returns the list of symbols we are displaying. Blank lines are represented as the empty string. The caller is reponsible for creating and destroying the TStrings object. } Procedure GetSymbolList(Symbols : TStrings); { Displays the font dialog. } Procedure ShowFontDialog(Sender: TObject); { Standard. } Constructor Create(AOwner : TComponent); Override; Destructor Destroy; Override; { Part of the ISavable interface. } Function IsTopLevelWindow : Boolean; Function GetWindowFactory(AOwner : TComponent) : TWindowFactory; Virtual; Abstract; End; { This is a simple base class which know about no (almost) data but has all interfaces. In practice, everyone uses TSymbolTableRowDescriptor, but I keep this around because it shows the interface so clearly. } TTableRowDescriptor = Class(TObject) Private { This is a reference to the window. It is used to allow all formulas in a the same window to refer to the same pallette. Currently this is overkill; we could get the same results from TMarketViewBase.DoReplacements. But eventually we want the ability to set the background color for a row. } FConfigurationOwner : String; Public { This is fills in the blanks in the factory. This version only updates the configuration owner property. Overriden versions should do more. I considered a hash table matching placeholders to their values. But it was more convenient to name each one seperately. Their aren't that many placeholders, and it's useful to have a name and type for each replacement at compile time. } Procedure DoReplacements(Factory : IGenericDataNodeFactory); Virtual; { If this is true we do not request or display any data in this row. } Function Disabled : Boolean; Virtual; Abstract; { This forces the row discriptor to return say it is diabled. Usually this is achieved by changing some property. } Procedure Disable; Virtual; Abstract; { See the MarketData unit for standard values for this. Any value will do. You just have use the same value consistantly. } Property ConfigurationOwner : String {Read FConfigurationOwner} Write FConfigurationOwner; Protected End; { This adds the ability to store a symbol on each row. It is convenient to assume that all rows have a symbol. That allows many of the common tools, like market view links, to work on all market views. } TSymbolTableRowDescriptor = Class(TTableRowDescriptor) Private FSymbol : String; Public { This replaces the symbol in adition the the inherited functionality. } Procedure DoReplacements(Factory : IGenericDataNodeFactory); Override; { A row is disabled if and only if the symbol is ''. } Function Disabled : Boolean; Override; Procedure Disable; Override; Property Symbol : String Read FSymbol Write FSymbol; Protected End; { Trees with replacements form a powerful basis for factories. } TDoReplacements = Procedure(Factory : IGenericDataNodeFactory) Of Object; { This is the basis for the body of the table. Each cell contains a list of data nodes. The column header is required to interpret this data. This cell is smart enough, however, to clean up after itself. } TTableCellData = Class(TObject) Private FEnabled : Boolean; FNodes : Array Of TGenericDataNode; FLinks : Array Of TDataNodeLink; OwnerReplacements : TDoReplacements; Procedure ReleaseData; Public { OwnerReplacements typically points back to the market view object. } Constructor Create(OwnerReplacements : TDoReplacements); { This is read from the row descriptor, which controls the enabled / disabled state. This is used by the column descriptor, which actually does the displaying. The column descriptors and the row descriptors never talk directly. } Property Enabled : Boolean Read FEnabled; { This reploads the data. Any time the column or the row changes, we throw out all the old data and recreate it from scratch. } Procedure Update(Formulas : TTableColumnDescriptor; Values : TTableRowDescriptor; DirtyCallback : TThreadMethod); { This is the only way to access the data. } Function GetData(Index : Integer) : TGenericDataNode; Destructor Destroy; Override; End; { Currently we only save two properties for each column. We have a name, which refers to a list of standard columns, and we have the width of the column. This will grow. } TTableColumnDescriptorFactory = Class(TWindowFactory) Private FWidth : Integer; FStringCreationInstructions : String; Procedure SetCreationInstructions(Index : Integer); Public { AOwner is the marketview. Gaurenteed to by of type TMarketViewBase. } Function CreateWindow(AOwner : TObject) : TObject; Override; Published Property Width : Integer Read FWidth Write FWidth; { This is only for backward compatibility. Use StringCreationInstruction instead. } Property CreationInstructions : Integer Write SetCreationInstructions Stored False; { Start with a "#" to say select a standard column by name. (Currently this is the only method supported. We use the "#" so we can have backward compatibility when we add support for custom made columns.) The rest is the same name that is visible in the menu of columns. } Property StringCreationInstructions : String Read FStringCreationInstructions Write FStringCreationInstructions; End; { This lists the formulas presented by the column, formats the data when we display the column, and provides rules for sorting the column. } TTableColumnDescriptor = Class(TCellRenderer) Private Protected { We store the original instructions which created this column, so we can save this for later. } FCreationInstructions : String; { This is the data we request. } FFactories : Array Of IGenericDataNodeFactory; FName : String; { This is not used in the market view. It allows for other configurations. We can put the data and the display instructions into the same cell. } Data : TTableCellData; Procedure Setup(Var Continue : Boolean); Override; { This is how we sort. AB requires a positive result. The default implementation returns 0 regardless of the data, so the sort has no effect. } Class Function Compare(A, B : TTableCellData) : Integer; Virtual; { This is stored here as a convenience. It is used by multiple classes. } Class Function CompareDoubles(A, B : TTableCellData) : Integer; Public Function CreateFactory(AOwner : TComponent) : TTableColumnDescriptorFactory; { This is the only way to access the factories. } Function FactoryCount : Integer; Function Factory(Index : Integer) : IGenericDataNodeFactory; Function Title : String; Override; { This is the column heading. Currently there is no way for a user to change it. That must change. } Property Name : String Write FName; { Sort the table. Grid should be the owner of this column. ACol should be the location of this column. If Direction is true the sort is decending. The sort is stable; sorting by column B, then column A is equivalent to sorting with column A as the primary key and column B as the secondary key. } Procedure Sort(Grid : TMarketViewBase; ACol : Integer; Direction : Boolean); End; { This displays a single floating point number. 0 is all the way to the left and red, 1 is all the way to the right and green. The bar shrinks and fades to the background color as we approach 0.5. Below 0 fills the entire cell with red. Above 1 fills the entire cell with green. } TPercentBarTableColumnDescriptor = Class(TTableColumnDescriptor) Private Protected Procedure DrawCellImpl; Override; Class Function Compare(A, B : TTableCellData) : Integer; Override; Public Constructor Create(Value : IGenericDataNodeFactory); End; { This displays an arbitrary string, left justified. Foreground and background colors are configurable=. Nil uses the default. Raised gives us a 3d effect. True makes the the cell appear to be raised. False or unknown is flat. Nil is also flat, but in that case we do not reserve space for the border. } TStringTableColumnDescriptor = Class(TTableColumnDescriptor) Private { These read from the appropriate data. This could be specific to the cell. If no data is available, it returns the default for the window. } Function GetFgColor : TColor; Function GetBgColor : TColor; Protected Procedure DrawCellImpl; Override; { By default this is read directly from the data. Superclasses can format other types of data to display as a string. } Function StringValue : String; Virtual; { Setting this to false is the same as returning an empty string for string value. } Function StringValueValid : Boolean; Virtual; { This uses the standard Pascal <, =, and > operations. Invalid data is treated like the empty string. } Class Function Compare(A, B : TTableCellData) : Integer; Override; Public Constructor Create(Value : IGenericDataNodeFactory; FgColor : IGenericDataNodeFactory = Nil; BgColor : IGenericDataNodeFactory = Nil; Raised : IGenericDataNodeFactory = Nil); End; { This displays integers in the standard way. They are right justified with no commas. } TIntegerTableColumnDescriptor = Class(TStringTableColumnDescriptor) Protected Function StringValue : String; Override; Function StringValueValid : Boolean; Override; Class Function Compare(A, B : TTableCellData) : Integer; Override; Public Procedure AfterConstruction; Override; End; { This displays a floating point number. It is formatted to show two places after the decimal. } TPriceTableColumnDescriptor = Class(TStringTableColumnDescriptor) Protected Function StringValue : String; Override; Function StringValueValid : Boolean; Override; Class Function Compare(A, B : TTableCellData) : Integer; Override; Public Function SizeHint : String; Override; Procedure AfterConstruction; Override; End; { This displays a date as a string. Dates are stored in the standard Delphi way, TDateTime. A date of 0 is translated to the empty string. } TDateTableColumnDescriptor = Class(TPriceTableColumnDescriptor) Protected Function StringValue : String; Override; Public Function SizeHint : String; Override; Procedure AfterConstruction; Override; End; { This displays a time as a string. Times are stored in the standard Delphi way, TDateTime. A date of 0 is translated to the empty string. This sorts by the entire date, but only displays the current time. } TTimeTableColumnDescriptor = Class(TPriceTableColumnDescriptor) Protected Function StringValue : String; Override; Public Function SizeHint : String; Override; Procedure AfterConstruction; Override; End; { This is the common base class for cell renders which take in a From value and a To value. This is an abstract class because it does not implement any drawing. } TDualNumberTableColumnDescriptor = Class(TTableColumnDescriptor) Private Class Function DataValid(Data : TTableCellData) : Boolean; Protected Function GetFgColor : TColor; Function GetBgColor : TColor; Function GetFrom : Double; Function GetTo : Double; Function Vertical : Boolean; Function GetFromPosition : Integer; Function GetToPosition : Integer; Function FgValid : Boolean; Protected { ToValue is the primary key. FromValuye is the secondary key. } Class Function Compare(A, B : TTableCellData) : Integer; Override; Public Constructor Create(FromValue : IGenericDataNodeFactory; ToValue : IGenericDataNodeFactory; FgColor : IGenericDataNodeFactory = Nil; BgColor : IGenericDataNodeFactory = Nil); End; TCylinderTableColumnDescriptor = Class(TDualNumberTableColumnDescriptor) Protected Procedure DrawCellImpl; Override; End; TConeTableColumnDescriptor = Class(TDualNumberTableColumnDescriptor) Protected Procedure DrawCellImpl; Override; End; TBooleanTableColumnDescriptor = Class(TTableColumnDescriptor) Private Protected Procedure DrawCellImpl; Override; Class Function Compare(A, B : TTableCellData) : Integer; Override; Public Constructor Create(Value : IGenericDataNodeFactory); End; { This stores all details of the market view. } TMarketViewBaseFactory = Class(TFormFactory) Private FFont : TFont; FBgColor : TColor; FRedBgColor, FRedFgColor, FGreenBgColor, FGreenFgColor : TColor; //FGridColor : TColor; FHGridLines, FVGridLines : Boolean; FAccount : String; FDoubleClickGroup, FCycleGroup : String; FSortGroup : TStrings; FCycleTimeMS : Integer; FRowLimit : Integer; FAutoSortDirection : Boolean; FAutoSortColumn : Integer; FColumns : TComponentList; Protected { Copy data to and from the market view. } Procedure LoadBase(Source : TMarketViewBase); Procedure FactoryToForm(Form : TForm); Override; Public Constructor Create(AOwner : TComponent); Override; Destructor Destroy; Override; Published Property Font : TFont Read FFont Write FFont; Property BgColor : TColor Read FBgColor Write FBgColor; Property RedBgColor : TColor Read FRedBgColor Write FRedBgColor; Property GreenBgColor : TColor Read FGreenBgColor Write FGreenBgColor; Property RedFgColor : TColor Read FRedFgColor Write FRedFgColor; Property GreenFgColor : TColor Read FGreenFgColor Write FGreenFgColor; //Property GridColor : TColor Read FGridColor Write FGridColor; Property HGridLines : Boolean Read FHGridLines Write FHGridLines; Property VGridLines : Boolean Read FVGridLines Write FVGridLines; Property Account : String Read FAccount Write FAccount; Property DoubleClickGroup : String Read FDoubleClickGroup Write FDoubleClickGroup; Property CycleGroup : String Read FCycleGroup Write FCycleGroup; Property SortGroup : TStrings Read FSortGroup Write FSortGroup; Property CycleTimeMS : Integer Read FCycleTimeMS Write FCycleTimeMS; Property RowLimit : Integer Read FRowLimit Write FRowLimit; Property AutoSortDirection : Boolean Read FAutoSortDirection Write FAutoSortDirection; Property AutoSortColumn : Integer Read FAutoSortColumn Write FAutoSortColumn; Property Columns : TComponentList Read FColumns Write FColumns; End; Implementation Uses ColorTools, FontsAndColorsUnit, MarketViewLinks, Groups, ThreeD, StandardPlaceHolders, RSI, CCI, IntradaySma, Math, Types, ActnList; //////////////////////////////////////////////////////////////////////// // Cache Cell Data // // When we set the entire list of symbols, we release and recreate // everything. This can be bad, as we might or might not release and // recreate the same data nodes over and over. This forces us to hold // onto the data just until a large, atomic operation is complete. // // This code is not thread safe. It is assumes that this is only called // from the GUI thread. // // Note, because some of the work is done in other threads, there is // no way to be sure that all of the data node are gone. This only // prevents them from going too quickly. If you have a limited amount // of memory, or other resources, that's a different problem all togather. //////////////////////////////////////////////////////////////////////// Var DataNodeLockCount : Integer; DataNodeCache : Array Of TDataNodeLink; DataNodeCacheSize : Integer; Procedure LockDataNodes; Begin Inc(DataNodeLockCount) End; Procedure UnlockDataNodes; Var I : Integer; Begin Dec(DataNodeLockCount); If DataNodeLockCount = 0 Then Begin For I := 0 To Pred(DataNodeCacheSize) Do DataNodeCache[I].Release; SetLength(DataNodeCache, 0); DataNodeCacheSize := 0 End End; Procedure ReleaseDataNode(Link : TDataNodeLink); Begin If DataNodeLockCount = 0 Then Link.Release Else Begin If DataNodeCacheSize = Length(DataNodeCache) Then SetLength(DataNodeCache, Max(32, 2 * Length(DataNodeCache))); DataNodeCache[DataNodeCacheSize] := Link; Inc(DataNodeCacheSize) End End; //////////////////////////////////////////////////////////////////////// // TMarketViewMenuAction //////////////////////////////////////////////////////////////////////// Type { Common functionality used below. } TMarketViewMenuAction = Class(TAction) Protected O : TMarketViewBase; Function ColumnNumber : Integer; Public Procedure DoUpdate(Sender : TObject); Virtual; Abstract; Procedure DoExecute(Sender : TObject); Virtual; Abstract; Constructor Create(AOwner : TComponent); Override; End; Function TMarketViewMenuAction.ColumnNumber : Integer; Var Col, Row : Integer; Begin Result := -1; O.GetCell(O.PopupMenu.PopupPoint, Col, Row); If Col > -1 Then If O.Grid.Objects[Col, 0] <> Nil Then Result := Col End; Constructor TMarketViewMenuAction.Create(AOwner : TComponent); Begin Inherited; O := AOwner As TMarketViewBase; OnUpdate := DoUpdate; OnExecute := DoExecute End; //////////////////////////////////////////////////////////////////////// // TSortMenuAction //////////////////////////////////////////////////////////////////////// Type { Sort once now, or turn on the auto sort now. } TSortMenuAction = Class(TMarketViewMenuAction) Public Auto, Direction : Boolean; Procedure DoUpdate(Sender : TObject); Override; Procedure DoExecute(Sender : TObject); Override; End; Procedure TSortMenuAction.DoUpdate(Sender : TObject); Var Col : Integer; Begin Col := ColumnNumber; Enabled := ColumnNumber <> -1; If Auto Then If Direction Then Caption := 'Auto Sort Decending' Else Caption := 'Auto Sort Ascending' Else If Direction Then Caption := 'Sort Decending' Else Caption := 'Sort Ascending'; If (Not Enabled) Or (Not Auto) Then Checked := False Else Checked := (O.AutoSortDirection = Direction) And (Col = O.CurrentAutoSortColumnNumber) End; Procedure TSortMenuAction.DoExecute(Sender : TObject); Begin If Auto Then If Checked Then O.ClearAutoSort Else Begin O.AutoSortDirection := Direction; O.SetAutoSortColumn(ColumnNumber) End Else Begin O.ClearAutoSort; O.SortOnce(ColumnNumber, Direction); End; End; //////////////////////////////////////////////////////////////////////// // TSortModeMenuAction //////////////////////////////////////////////////////////////////////// Type { This changes the meaning of doubleclick. } TSortModeMenuAction = Class(TMarketViewMenuAction) Public SortMode : TSortMode; Procedure DoUpdate(Sender : TObject); Override; Procedure DoExecute(Sender : TObject); Override; End; Procedure TSortModeMenuAction.DoUpdate(Sender : TObject); Begin Case SortMode Of smAuto : Caption := 'Auto Sort'; smManualAscending : Caption := 'Sort Ascending'; smManualDecending : Caption := 'Sort Decending'; Else Assert(False) End; Checked := SortMode = O.SortMode End; Procedure TSortModeMenuAction.DoExecute(Sender : TObject); Begin O.SortMode := SortMode End; //////////////////////////////////////////////////////////////////////// // TRemoveColumnMenuAction //////////////////////////////////////////////////////////////////////// Type { This deletes the column under the mouse. } TRemoveColumnMenuAction = Class(TMarketViewMenuAction) Public Procedure DoUpdate(Sender : TObject); Override; Procedure DoExecute(Sender : TObject); Override; End; Procedure TRemoveColumnMenuAction.DoUpdate(Sender : TObject); Var Col : Integer; Begin Col := ColumnNumber; Enabled := ColumnNumber <> -1; If Enabled Then Caption := 'Remove "' + (O.Grid.Objects[Col, 0] As TTableColumnDescriptor).FName + '"' Else Caption := 'Remove' End; Procedure TRemoveColumnMenuAction.DoExecute(Sender : TObject); Begin O.RemoveColumn(ColumnNumber); End; //////////////////////////////////////////////////////////////////////// // TSortableHeaderCellRenderer //////////////////////////////////////////////////////////////////////// Type { This displays the header cells on the top row. It can draw an arrow to show the direction of the sort, and it can change color to show the direction and how long it will be before the next autosort. } TSortableHeaderCellRenderer = Class(TCellRenderer) Private FOwner : TMarketViewBase; Procedure DrawTriangle(Direction : Boolean; Color : TColor); Protected Procedure DrawCellImpl; Override; Public Constructor Create(Owner : TMarketViewBase); End; Constructor TSortableHeaderCellRenderer.Create(Owner : TMarketViewBase); Begin Inherited Create; FOwner := Owner End; Procedure TSortableHeaderCellRenderer.DrawTriangle(Direction : Boolean; Color : TColor); Var TriangleWidth, TriangleLeft, TriangleRight, TriangleCenter, TriangleWideY, TriangleNarrowY : Integer; Begin TriangleWidth := ClientHeight - 2; If Odd(TriangleWidth) Then Dec(TriangleWidth); If (ClientWidth < TriangleWidth) Or (TriangleWidth < 6) Then Exit; TriangleRight := Clip.Left + ClientWidth; TriangleLeft := TriangleRight - TriangleWidth + 1; ClientWidth := ClientWidth - TriangleWidth - 2; If Direction Then Begin TriangleWideY := ClientTop + 2; TriangleNarrowY := ClientTop + ClientHeight - 2 End Else Begin TriangleWideY := ClientTop + ClientHeight - 2; TriangleNarrowY := ClientTop + 1 End; TriangleCenter := TriangleLeft + TriangleWidth Div 2; Canvas.Pen.Width := 2; Canvas.Pen.Color := ReflectColor(Color); Canvas.Polyline([Point(TriangleRight, TriangleWideY), Point(TriangleCenter, TriangleNarrowY)]); Canvas.Pen.Color := ShadowColor(Color); Canvas.Polyline([Point(TriangleCenter, TriangleNarrowY), Point(TriangleLeft, TriangleWideY)]); If Not Direction Then Canvas.Pen.Color := ReflectColor(Color); Canvas.Polyline([Point(TriangleLeft, TriangleWideY), Point(TriangleRight, TriangleWideY)]); Canvas.Polyline([Point(TriangleRight, TriangleWideY), Point(TriangleLeft, TriangleWideY)]); Canvas.Pen.Width := 1; End; Procedure TSortableHeaderCellRenderer.DrawCellImpl; Var Text : String; Viewer : TObject; BgColor, SortedColor : TColor; AutoSort : Boolean; Begin Viewer := Grid.Objects[ACol, ARow]; AutoSort := Assigned(Viewer) And (Viewer = FOwner.AutoSortColumn); BgColor := ColorToRGB(Grid.FixedColor); If FOwner.AutoSortDirection Then SortedColor := $008fdf8f Else SortedColor := $008f8fdf; If AutoSort Then BgColor := Between((Now - FOwner.LastAutoSortTime) / (FOwner.AutoSortPeriodMS / 24 / 60 / 60 / 1000), SortedColor, BgColor); PaintBackgroundRaised(BgColor); If AutoSort Then DrawTriangle(FOwner.AutoSortDirection, BgColor); Grid.Canvas.Font := Grid.Font; Grid.Canvas.Font.Color := clBlack; Grid.Canvas.Brush.Color := BgColor; If Assigned(Viewer) Then Text := (Viewer As TCellRenderer).Title Else Text := ''; TextCenter(MakeStringFit(Text)) End; //////////////////////////////////////////////////////////////////////// // TTableRowDescriptor //////////////////////////////////////////////////////////////////////// Procedure TTableRowDescriptor.DoReplacements(Factory : IGenericDataNodeFactory); Begin If FConfigurationOwner <> '' Then Factory.SetValue(ConfigurationOwnerKey, FConfigurationOwner) End; //////////////////////////////////////////////////////////////////////// // TSymbolTableRowDescriptor //////////////////////////////////////////////////////////////////////// Procedure TSymbolTableRowDescriptor.DoReplacements(Factory : IGenericDataNodeFactory); Begin Inherited; Factory.SetValue(SymbolNameKey, FSymbol) End; Function TSymbolTableRowDescriptor.Disabled : Boolean; Begin Result := FSymbol = '' End; Procedure TSymbolTableRowDescriptor.Disable; Begin FSymbol := '' End; //////////////////////////////////////////////////////////////////////// // TTableCellData //////////////////////////////////////////////////////////////////////// Constructor TTableCellData.Create(OwnerReplacements : TDoReplacements); Begin Self.OwnerReplacements := OwnerReplacements End; Procedure TTableCellData.ReleaseData; Var I : Integer; Begin SetLength(FNodes, 0); For I := 0 to Pred(Length(FLinks)) Do If Assigned(FLinks[I]) Then ReleaseDataNode(FLinks[I]); SetLength(FLinks, 0); FEnabled := False End; Procedure TTableCellData.Update(Formulas : TTableColumnDescriptor; Values : TTableRowDescriptor; DirtyCallback : TThreadMethod); Var I : Integer; Factory : IGenericDataNodeFactory; //NTemp : TGenericDataNode; //LTemp : TDataNodeLink; Begin ReleaseData; If Assigned(Formulas) And Assigned(Values) Then If Not Values.Disabled Then Begin FEnabled := True; SetLength(FNodes, Formulas.FactoryCount); SetLength(FLinks, Formulas.FactoryCount); For I := 0 To Pred(Formulas.FactoryCount) Do Begin Factory := Formulas.Factory(I); If Assigned(Factory) Then Begin Factory := Factory.Duplicate.MakeThreadSafe; Values.DoReplacements(Factory); OwnerReplacements(Factory); Factory.Find(DirtyCallback, FNodes[I], FLinks[I]); //Factory.Find(DirtyCallback, NTemp, LTemp); FLinks[I].SetReceiveInput(True) End End End End; Function TTableCellData.GetData(Index : Integer) : TGenericDataNode; Begin If (Index < 0) Or (Index >= Length(FNodes)) Then Result := Nil Else Result := FNodes[Index] End; Destructor TTableCellData.Destroy; Begin ReleaseData End; //////////////////////////////////////////////////////////////////////// // TTableColumnDescriptorFactory //////////////////////////////////////////////////////////////////////// Procedure TTableColumnDescriptorFactory.SetCreationInstructions(Index : Integer); Const { This exists for historical reasons only. This allows us to read in old style config files when we saved the number rather than the name. Do not update this. } Names : Array [1..41] Of String = ( 'Symbol', 'Company Name', 'Bid Size', 'Best Bid', 'Best Ask', // 1-5 'Ask Size', 'Volume', 'Ticks', 'Open', 'Low', // 6-10 'Last', 'High', 'Day Range', 'Close', '52 Week Low', // 11-15 '52 Week High', 'Year Range', 'Change From Close', 'Change From Open', '#', // 16-20 '2 Points', 'Alt 2 Points', 'Last Print Size', '? Earnings Per Share', '? Dividend', // 21-25 '? DivYield', '? OASupport', '? OAGrade', '? HISTRELPE', '? EPSTrendGR', // 26-30 '? SOES', '? XDivDate', '? 52HighDate', '? 52LowDate', '? PERatio', // 31-35 '? DivInterval', '? Shares', '? Cusip', 'Open Position Size', 'Symbol (Color by Direction)', // 36-40 'Symbol (Color by Direction)'); // 41 Begin If (Index < Low(Names)) Or (Index > High(Names)) Then StringCreationInstructions := '' Else StringCreationInstructions := '#' + Names[Index] End; Function TTableColumnDescriptorFactory.CreateWindow(AOwner : TObject) : TObject; Var MarketView : TMarketViewBase; Begin MarketView := AOwner As TMarketViewBase; Result := MarketView.CreateColumnDescriptor(FStringCreationInstructions) End; //////////////////////////////////////////////////////////////////////// // TTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Function TTableColumnDescriptor.CreateFactory(AOwner : TComponent) : TTableColumnDescriptorFactory; Begin Result := TTableColumnDescriptorFactory.Create(AOwner); Result.FStringCreationInstructions := FCreationInstructions End; Class Function TTableColumnDescriptor.Compare(A, B : TTableCellData) : Integer; Begin Result := 0 End; Procedure TTableColumnDescriptor.Sort(Grid : TMarketViewBase; ACol : Integer; Direction : Boolean); Var ItemToMove, PotentialLocation, NewLocation : Integer; CompareResult : Integer; Begin Grid.InitSort; For ItemToMove := 2 To Grid.GetRowCount Do Begin NewLocation := 1; For PotentialLocation := Pred(ItemToMove) DownTo 1 Do Begin CompareResult := Compare(Grid.SortGetCell(ACol, ItemToMove) As TTableCellData, Grid.SortGetCell(ACol, PotentialLocation) As TTableCellData); If Direction Then CompareResult := - CompareResult; If CompareResult >= 0 Then Begin NewLocation := Succ(PotentialLocation); Break End End; Grid.SortMoveRow(ItemToMove, NewLocation) End; Grid.CommitSort End; Class Function TTableColumnDescriptor.CompareDoubles(A, B : TTableCellData) : Integer; Procedure GetValue(Data : TTableCellData; Var Valid : Boolean; Var Value : Double); Var Node : TGenericDataNode; Begin Node := Data.GetData(0); If Assigned(Node) Then Begin Valid := Node.IsValid; If Valid Then Value := Node.GetDouble End Else Valid := False End; Var AValid, BValid : Boolean; AValue, BValue : Double; Begin GetValue(A, AValid, AValue); GetValue(B, BValid, BValue); If AValid Then If BValid Then If AValue < BValue Then Result := -1 Else If AValue > BValue Then Result := 1 Else Result := 0 Else Result := 1 Else If BValid Then Result := -1 Else Result := 1 End; Function TTableColumnDescriptor.Title : String; Begin Result := FName End; Procedure TTableColumnDescriptor.Setup(Var Continue : Boolean); Begin Inherited; If Continue Then Begin Data := Grid.Objects[ACol, ARow] As TTableCellData; Continue := Data <> Nil End; If Continue Then Continue := Data.Enabled; If Not Continue Then Begin Data := Nil; Grid.Canvas.Brush.Color := Grid.Color; Grid.Canvas.FillRect(Clip) End End; Function TTableColumnDescriptor.FactoryCount : Integer; Begin Result := Length(FFactories) End; Function TTableColumnDescriptor.Factory(Index : Integer) : IGenericDataNodeFactory; Begin Result := FFactories[Index] End; //////////////////////////////////////////////////////////////////////// // TPercentBarTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Class Function TPercentBarTableColumnDescriptor.Compare(A, B : TTableCellData) : Integer; Begin Result := CompareDoubles(A, B) End; Constructor TPercentBarTableColumnDescriptor.Create(Value : IGenericDataNodeFactory); Begin SetLength(FFactories, 1); FFactories[0] := Value End; Procedure TPercentBarTableColumnDescriptor.DrawCellImpl; Const NegativeColor = $000000ff; // Bright red PositiveColor = $0000ff00; // Bright green Var DataAsNode : TGenericDataNode; DataAsDouble : Double; Rect : TRect; Begin PaintBackground(Grid.Color); DataAsNode := Data.GetData(0); If DataAsNode <> Nil Then Begin If DataAsNode.IsValid Then Begin DataAsDouble := DataAsNode.GetDouble; If DataAsDouble < 0.0 Then PaintBackground(NegativeColor) Else If DataAsDouble > 1.0 Then PaintBackground(PositiveColor) Else Begin If DataAsDouble < 0.5 Then Begin Rect.Top := Clip.Top; Rect.Bottom := Clip.Bottom; Rect.Left := Clip.Left + Round(Width * DataAsDouble); Rect.Right := Clip.Left + Width Div 2; Grid.Canvas.Brush.Color := Between(DataAsDouble * 2, NegativeColor, Grid.Color); Grid.Canvas.FillRect(Rect) End Else If DataAsDouble > 0.5 Then Begin Rect.Top := Clip.Top; Rect.Bottom := Clip.Bottom; Rect.Left := Clip.Left + Width Div 2; Rect.Right := Clip.Left + Round(Width * DataAsDouble); Grid.Canvas.Brush.Color := Between((1 - DataAsDouble) * 2, PositiveColor, Grid.Color); Grid.Canvas.FillRect(Rect) End End End End End; //////////////////////////////////////////////////////////////////////// // TStringTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Function TStringTableColumnDescriptor.GetFgColor : TColor; Var Formula : TGenericDataNode; Begin Result := Grid.Font.Color; Formula := Data.GetData(1); If Assigned(Formula) Then If Formula.IsValid Then Result := Formula.GetColor; Result := ColorToRGB(Result) End; Function TStringTableColumnDescriptor.GetBgColor : TColor; Var Formula : TGenericDataNode; Begin Result := Grid.Color; Formula := Data.GetData(2); If Assigned(Formula) Then If Formula.IsValid Then Result := Formula.GetColor; Result := ColorToRGB(Result) End; Class Function TStringTableColumnDescriptor.Compare(A, B : TTableCellData) : Integer; Procedure GetValue(Data : TTableCellData; Var Value : String); Var Node : TGenericDataNode; Begin Value := ''; Node := Data.GetData(0); If Assigned(Node) Then If Node.IsValid Then Value := Node.GetString End; Var AValue, BValue : String; Begin GetValue(A, AValue); GetValue(B, BValue); If AValue < BValue Then Result := -1 Else If AValue > BValue Then Result := 1 Else Result := 0 End; Function TStringTableColumnDescriptor.StringValue : String; Begin Result := MakeStringFit(Data.GetData(0).GetString) End; Function TStringTableColumnDescriptor.StringValueValid : Boolean; Begin If Data.GetData(0) <> Nil Then Result := Data.GetData(0).IsValid Else Result := False End; Constructor TStringTableColumnDescriptor.Create(Value : IGenericDataNodeFactory; FgColor : IGenericDataNodeFactory; BgColor : IGenericDataNodeFactory; Raised : IGenericDataNodeFactory); Begin SetLength(FFactories, 4); FFactories[0] := Value; FFactories[1] := FgColor; FFactories[2] := BgColor; FFactories[3] := Raised End; Procedure TStringTableColumnDescriptor.DrawCellImpl; Var RaisedData : TGenericDataNode; Begin RaisedData := Data.GetData(3); If Assigned(RaisedData) Then If RaisedData.IsValid And RaisedData.GetBoolean Then PaintBackgroundRaised(GetBgColor) Else PaintBackgroundReserveBorder(GetBgColor) Else PaintBackground(GetBgColor); If StringValueValid Then Begin Canvas.Font.Color := GetFgColor; TextOut(StringValue) End End; //////////////////////////////////////////////////////////////////////// // TIntegerTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Class Function TIntegerTableColumnDescriptor.Compare(A, B : TTableCellData) : Integer; Procedure GetValue(Data : TTableCellData; Var Valid : Boolean; Var Value : Integer); Var Node : TGenericDataNode; Begin Node := Data.GetData(0); If Assigned(Node) Then Begin Valid := Node.IsValid; If Valid Then Value := Node.GetInteger End Else Valid := False End; Var AValid, BValid : Boolean; AValue, BValue : Integer; Begin GetValue(A, AValid, AValue); GetValue(B, BValid, BValue); If AValid Then If BValid Then If AValue < BValue Then Result := -1 Else If AValue > BValue Then Result := 1 Else Result := 0 Else Result := 1 Else If BValid Then Result := -1 Else Result := 1 End; Function TIntegerTableColumnDescriptor.StringValue : String; Begin Result := MakeIntegerFit(Data.GetData(0).GetInteger); End; Function TIntegerTableColumnDescriptor.StringValueValid : Boolean; Begin If Data.GetData(0) <> Nil Then Result := Data.GetData(0).IsValid Else Result := False End; Procedure TIntegerTableColumnDescriptor.AfterConstruction; Begin Inherited; Alignment := taRightJustify End; //////////////////////////////////////////////////////////////////////// // TPriceTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Function TPriceTableColumnDescriptor.SizeHint : String; Begin Result := '999.99' End; Class Function TPriceTableColumnDescriptor.Compare(A, B : TTableCellData) : Integer; Begin Result := CompareDoubles(A, B) End; Function TPriceTableColumnDescriptor.StringValue : String; Begin Result := MakePriceFit(Data.GetData(0).GetDouble) End; Function TPriceTableColumnDescriptor.StringValueValid : Boolean; Begin If Data.GetData(0) <> Nil Then Result := Data.GetData(0).IsValid Else Result := False End; Procedure TPriceTableColumnDescriptor.AfterConstruction; Begin Inherited; Alignment := taRightJustify End; //////////////////////////////////////////////////////////////////////// // TDateTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Function TDateTableColumnDescriptor.SizeHint : String; Begin Result := '00/00/0000' End; Function TDateTableColumnDescriptor.StringValue : String; Var D : TDateTime; Begin D := Data.GetData(0).GetDouble; If D = 0 Then Result := '' Else Result := MakeStringFit(DateToStr(D)) End; Procedure TDateTableColumnDescriptor.AfterConstruction; Begin Inherited; Alignment := taLeftJustify End; //////////////////////////////////////////////////////////////////////// // TTimeTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Function TTimeTableColumnDescriptor.SizeHint : String; Begin Result := '00:00:00 AM' End; Function TTimeTableColumnDescriptor.StringValue : String; Var D : TDateTime; Begin D := Data.GetData(0).GetDouble; If D = 0 Then Result := '' Else Result := MakeStringFit(TimeToStr(D)) End; Procedure TTimeTableColumnDescriptor.AfterConstruction; Begin Inherited; Alignment := taLeftJustify End; //////////////////////////////////////////////////////////////////////// // TDualNumberTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Function TDualNumberTableColumnDescriptor.GetFgColor : TColor; Var Formula : TGenericDataNode; Begin Result := ColorToRGB(Grid.Font.Color); Formula := Data.GetData(2); If Assigned(Formula) Then If Formula.IsValid Then Result := Formula.GetColor End; Function TDualNumberTableColumnDescriptor.GetBgColor : TColor; Var Formula : TGenericDataNode; Begin Result := ColorToRGB(Grid.Color); Formula := Data.GetData(3); If Assigned(Formula) Then If Formula.IsValid Then Result := Formula.GetColor End; Function TDualNumberTableColumnDescriptor.GetFrom : Double; Var Formula : TGenericDataNode; Begin Result := 0; Formula := Data.GetData(0); If Assigned(Formula) Then If Formula.IsValid Then Result := Formula.GetDouble End; Function TDualNumberTableColumnDescriptor.GetTo : Double; Var Formula : TGenericDataNode; Begin Result := 0; Formula := Data.GetData(1); If Assigned(Formula) Then If Formula.IsValid Then Result := Formula.GetDouble End; Function TDualNumberTableColumnDescriptor.Vertical : Boolean; Begin Result := Height > Width End; Function TDualNumberTableColumnDescriptor.GetFromPosition : Integer; Begin If Vertical Then Result := Round(Clip.Top + Height * (1 - GetFrom)) Else Result := Round(Clip.Left + Width * GetFrom) End; Function TDualNumberTableColumnDescriptor.GetToPosition : Integer; Begin If Vertical Then Result := Round(Clip.Top + Height * (1 - GetTo)) Else Result := Round(Clip.Left + Width * GetTo) End; Class Function TDualNumberTableColumnDescriptor.DataValid(Data : TTableCellData) : Boolean; Var Data0 : TGenericDataNode; Begin Assert(Assigned(Data)); If Data.Enabled Then Begin Data0 := Data.GetData(0); Assert(Assigned(Data0)); Result := Data0.IsValid; // Dan's computer failed here on a nil pointer, before I added the two assert statements. // I think the If Enabled statement fixes this bug. If Result Then Result := Data.GetData(1).IsValid End Else Result := False End; Function TDualNumberTableColumnDescriptor.FgValid : Boolean; Begin If Data.GetData(2) <> Nil Then Result := Data.GetData(2).IsValid Else Result := True; If Result Then Result := DataValid(Data) End; Class Function TDualNumberTableColumnDescriptor.Compare(A, B : TTableCellData) : Integer; Procedure GetValues(Data : TTableCellData; Var Valid : Boolean; Var FromValue, ToValue : Double); Begin Valid := DataValid(Data); If Valid Then Begin FromValue := Data.GetData(0).GetDouble; ToValue := Data.GetData(1).GetDouble End End; Var AValid, BValid : Boolean; AFrom, ATo, BFrom, BTo : Double; Begin GetValues(A, AValid, AFrom, ATo); GetValues(B, BValid, BFrom, BTo); If AValid Then If BValid Then If ATo < BTo Then Result := -1 Else If ATo > BTo Then Result := 1 Else If AFrom < BFrom Then Result := -1 Else If AFrom > BFrom Then Result := 1 Else Result := 0 Else Result := 1 Else If BValid Then Result := -1 Else Result := 1 End; Constructor TDualNumberTableColumnDescriptor.Create( FromValue : IGenericDataNodeFactory; ToValue : IGenericDataNodeFactory; FgColor : IGenericDataNodeFactory = Nil; BgColor : IGenericDataNodeFactory = Nil); Begin SetLength(FFactories, 4); FFactories[0] := FromValue; FFactories[1] := ToValue; FFactories[2] := FgColor; FFactories[3] := BgColor End; //////////////////////////////////////////////////////////////////////// // TCylinderTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Var BitmapBuffer : TBitmap; Procedure TCylinderTableColumnDescriptor.DrawCellImpl; Var Rect : TRect; ClipWidth, ClipHeight : Integer; Begin ClipWidth := Clip.Right - Clip.Left; If ClipWidth > BitmapBuffer.Width Then BitmapBuffer.Width := ClipWidth; ClipHeight := Clip.Bottom - Clip.Top; If ClipHeight > BitmapBuffer.Height Then BitmapBuffer.Height := ClipHeight; Rect.Left := 0; Rect.Top := 0; Rect.Right := BitmapBuffer.Width; Rect.Bottom := BitmapBuffer.Height; BitmapBuffer.Canvas.Brush.Color := GetBgColor; BitmapBuffer.Canvas.FillRect(Rect); If FgValid Then Begin If Vertical Then Begin Rect.Left := 0; Rect.Right := Width; Rect.Top := Round(GetTo * Height); Rect.Bottom := Round(GetFrom * Height) End Else Begin Rect.Top := 0; Rect.Bottom := Height; Rect.Left := Round(GetFrom * Width); Rect.Right := Round(GetTo * Width) End; Cylinder(BitmapBuffer.Canvas, Rect, GetFgColor, Vertical) End; Rect.Left := 0; Rect.Top := 0; Rect.Bottom := ClipHeight; Rect.Right := ClipWidth; Canvas.CopyRect(Clip, BitmapBuffer.Canvas, Rect); End; //////////////////////////////////////////////////////////////////////// // TConeTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Procedure TConeTableColumnDescriptor.DrawCellImpl; Var Rect : TRect; Begin BitmapBuffer.Width := Clip.Right - Clip.Left; BitmapBuffer.Height := Clip.Bottom - Clip.Top; Rect.Left := 0; Rect.Top := 0; Rect.Right := BitmapBuffer.Width; Rect.Bottom := BitmapBuffer.Height; BitmapBuffer.Canvas.Brush.Color := GetBgColor; BitmapBuffer.Canvas.FillRect(Rect); If FgValid Then Begin If Vertical Then Begin Rect.Left := 0; Rect.Right := Width; Rect.Top := Round(GetTo * Height); Rect.Bottom := Round(GetFrom * Height) End Else Begin Rect.Top := 0; Rect.Bottom := Height; Rect.Left := Round(GetFrom * Width); Rect.Right := Round(GetTo * Width) End; Cone(BitmapBuffer.Canvas, Rect, GetFgColor, Vertical) End; Canvas.Draw(Clip.Left, Clip.Top, BitmapBuffer) End; //////////////////////////////////////////////////////////////////////// // TBooleanTableColumnDescriptor //////////////////////////////////////////////////////////////////////// Class Function TBooleanTableColumnDescriptor.Compare(A, B : TTableCellData) : Integer; Type TFU = (tfuTrue, tfuFalse, tfuUnknown); Function GetValue(D : TTableCellData) : TFU; Begin If D.GetData(0).IsValid Then If D.GetData(0).GetBoolean Then Result := tfuTrue Else Result := tfuFalse Else Result := tfuUnknown End; Var AValue, BValue : TFU; Begin AValue := GetValue(A); BValue := GetValue(B); If AValue < BValue Then Result := 1 Else If AValue > BValue Then Result := -1 Else Result := 0 End; Constructor TBooleanTableColumnDescriptor.Create(Value : IGenericDataNodeFactory); Begin SetLength(FFactories, 1); FFactories[0] := Value End; Procedure TBooleanTableColumnDescriptor.DrawCellImpl; Const NegativeColor = $000000ff; // Bright red PositiveColor = $0000ff00; // Bright green Var DataAsNode : TGenericDataNode; Begin PaintBackground(Grid.Color); DataAsNode := Data.GetData(0); If DataAsNode <> Nil Then If DataAsNode.IsValid Then If DataAsNode.GetBoolean Then DrawPyramid(PositiveColor, True) Else DrawPyramid(NegativeColor, False) End; //////////////////////////////////////////////////////////////////////// // TMarketViewBaseFactory //////////////////////////////////////////////////////////////////////// Constructor TMarketViewBaseFactory.Create(AOwner : TComponent); Begin Inherited; FFont := TFont.Create; FBgColor := clWhite; FSortGroup := TStringList.Create; //FGridColor := clDkGray End; Destructor TMarketViewBaseFactory.Destroy; Begin FFont.Free; FSortGroup.Free; Inherited End; Procedure TMarketViewBaseFactory.LoadBase(Source : TMarketViewBase); Var I : Integer; ColumnFactory : TTableColumnDescriptorFactory; Begin FormToFactory(Source); Font.Assign(Source.Grid.Font); BgColor := Source.Grid.Color; GreenBgColor := Source.StandardPalette.GetValue(scGreenBg); RedBgColor := Source.StandardPalette.GetValue(scRedBg); GreenFgColor := Source.StandardPalette.GetValue(scGreenFg); RedFgColor := Source.StandardPalette.GetValue(scRedFg); HGridLines := goHorzLine In Source.Grid.Options; VGridLines := goVertLine In Source.Grid.Options; Account := Source.Account; FDoubleClickGroup := Source.MarketViewLink.DoubleClickGroup; FCycleGroup := Source.MarketViewLink.CycleGroup; FSortGroup.Clear; For I := Low(Source.MarketViewLink.SortGroup) To High(Source.MarketViewLink.SortGroup) Do FSortGroup.Add(Source.MarketViewLink.SortGroup[I]); FCycleTimeMS := Source.MarketViewLink.CycleTimeMS; FRowLimit := Source.MarketViewLink.RowLimit; FAutoSortDirection := Source.AutoSortDirection; FAutoSortColumn := Source.CurrentAutoSortColumnNumber; For I := Pred(Source.Grid.ColCount) DownTo 1 Do If Assigned(Source.Grid.Objects[I, 0]) Then Begin ColumnFactory := (Source.Grid.Objects[I, 0] As TTableColumnDescriptor).CreateFactory(AppropriateOwner); ColumnFactory.Width := Source.Grid.ColWidths[I]; TComponentList.Push(ColumnFactory, FColumns) End End; Procedure TMarketViewBaseFactory.FactoryToForm(Form : TForm); Var Window : TMarketViewBase; I : Integer; RemainingColumns : TComponentList; Begin Inherited; Window := Form As TMarketViewBase; Window.Grid.Font.Assign(Font); Window.Grid.Color := BgColor; Window.StandardPalette.SetValue(scGreenBg, GreenBgColor); Window.StandardPalette.SetValue(scRedBg, RedBgColor); Window.StandardPalette.SetValue(scGreenFg, GreenFgColor); Window.StandardPalette.SetValue(scRedFg, RedFgColor); Window.StandardPalette.SetValue(scForeground, Font.Color); Window.StandardPalette.SetValue(scBackground, BgColor); If HGridLines Then Window.Grid.Options := Window.Grid.Options + [goHorzLine] Else Window.Grid.Options := Window.Grid.Options - [goHorzLine]; If VGridLines Then Window.Grid.Options := Window.Grid.Options + [goVertLine] Else Window.Grid.Options := Window.Grid.Options - [goVertLine]; Window.Account := Account; Window.SetDefaultCellSizes; Window.MarketViewLink.DoubleClickGroup := DoubleClickGroup; Window.MarketViewLink.CycleGroup := CycleGroup; For I := Low(Window.MarketViewLink.SortGroup) To High(Window.MarketViewLink.SortGroup) Do If I <= SortGroup.Count Then Window.MarketViewLink.SortGroup[I] := SortGroup[Pred(I)] Else Window.MarketViewLink.SortGroup[I] := ''; Window.MarketViewLink.CycleTimeMS := CycleTimeMS; Window.MarketViewLink.RowLimit := RowLimit; Window.LoadMarketViewLinks; RemainingColumns := FColumns; While Assigned(RemainingColumns) Do Begin Window.AddColumn(RemainingColumns.First As TTableColumnDescriptorFactory); RemainingColumns := RemainingColumns.Rest End; Window.AutoSortDirection := FAutoSortDirection; Window.SetAutoSortColumn(FAutoSortColumn) End; //////////////////////////////////////////////////////////////////////// // TMarketViewBase //////////////////////////////////////////////////////////////////////// // RowPositions : Array Of Integer; Procedure TMarketViewBase.InitSort; Var I : Integer; Begin SetLength(RowPositions, Grid.RowCount); For I := 0 To Pred(Grid.RowCount) Do RowPositions[I] := I End; Procedure TMarketViewBase.CommitSort; Var Row, Col, I : Integer; OrigGrid : Array Of TObject; Begin Grid.InvalidateGrid; SetLength(OrigGrid, Grid.RowCount * Grid.ColCount); I := 0; For Row := 0 To Pred(Grid.RowCount) Do For Col := 0 To Pred(Grid.ColCount) Do Begin OrigGrid[I] := Grid.Objects[Col, Row]; Inc(I) End; For Row := 0 To Pred(Grid.RowCount) Do Begin I := RowPositions[Row] * Grid.ColCount; For Col := 0 To Pred(Grid.ColCount) Do Begin Grid.Objects[Col, Row] := OrigGrid[I]; Inc(I) End End End; Procedure TMarketViewBase.SortMoveRow(OldPos, NewPos : Integer); Var I : Integer; MovedValue : Integer; Begin If OldPos > NewPos Then Begin MovedValue := RowPositions[OldPos]; For I := OldPos DownTo Succ(NewPos) Do RowPositions[I] := RowPositions[Pred(I)]; RowPositions[NewPos] := MovedValue End Else If OldPos < NewPos Then Begin MovedValue := RowPositions[OldPos]; For I := OldPos To Pred(NewPos) Do RowPositions[I] := RowPositions[Succ(I)]; RowPositions[NewPos] := MovedValue End End; Function TMarketViewBase.SortGetCell(Col, Row : Integer) : TObject; Begin Result := Grid.Objects[Col, RowPositions[Row]] End; Function TMarketViewBase.CreateColumnDescriptor(Instructions : String) : TTableColumnDescriptor; Begin If Instructions = '' Then Result := Nil Else If Instructions[1] = '#' Then Begin Result := CreateStandardColumn(Copy(Instructions, 2, MaxInt)); If Assigned(Result) Then Result.FCreationInstructions := Instructions End Else Result := Nil End; Function TMarketViewBase.CreateStandardColumn(ColumnName : String) : TTableColumnDescriptor; Begin Result := Nil; If ColumnName = 'Symbol' Then Result := TStringTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('Symbol')) Else If ColumnName = 'Company Name' Then Result := TStringTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('PrintableName'), TTriColor.FgFactory( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('LastPrintPrice'), TGenericDataNodeFactory.CreateWithArgs(TPreviousValue, TGenericDataNodeFactory.FindFactory('LastPrintPrice')) As IGenericDataNodeFactory ) As IGenericDataNodeFactory), Nil, TGenericDataNodeFactory.CreateWithArgs(TVisiblePulses, TGenericDataNodeFactory.FindFactory('LastPrintTime'))) Else If ColumnName = 'Bid Size' Then Result := TIntegerTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('BidSize'), Nil, Nil, TGenericDataNodeFactory.CreateWithArgs(TLessThan, TGenericDataNodeFactory.CreateWithArgs(TIntegerToDouble, TGenericDataNodeFactory.FindFactory('AskSize')) As IGenericDataNodeFactory, TGenericDataNodeFactory.CreateWithArgs(TIntegerToDouble, TGenericDataNodeFactory.FindFactory('BidSize')) As IGenericDataNodeFactory )) Else If ColumnName = 'Best Bid' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('BidPrice'), Nil, TTriColor.BgFactory( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('BidPrice'), TGenericDataNodeFactory.CreateWithArgs(TPreviousValue, TGenericDataNodeFactory.CreateWithArgs(TIgnoreNullChanges, TGenericDataNodeFactory.FindFactory('BidPrice')) As IGenericDataNodeFactory) As IGenericDataNodeFactory) As IGenericDataNodeFactory)) Else If ColumnName = 'Best Ask' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('AskPrice'), Nil, TTriColor.BgFactory( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('AskPrice'), TGenericDataNodeFactory.CreateWithArgs(TPreviousValue, TGenericDataNodeFactory.CreateWithArgs(TIgnoreNullChanges, TGenericDataNodeFactory.FindFactory('AskPrice')) As IGenericDataNodeFactory) As IGenericDataNodeFactory) As IGenericDataNodeFactory)) Else If ColumnName = 'Ask Size' Then Result := TIntegerTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('AskSize'), Nil, Nil, TGenericDataNodeFactory.CreateWithArgs(TLessThan, TGenericDataNodeFactory.CreateWithArgs(TIntegerToDouble, TGenericDataNodeFactory.FindFactory('BidSize')) As IGenericDataNodeFactory, TGenericDataNodeFactory.CreateWithArgs(TIntegerToDouble, TGenericDataNodeFactory.FindFactory('AskSize')) As IGenericDataNodeFactory)) Else If ColumnName = 'Volume' Then Result := TIntegerTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('Volume')) Else If ColumnName = 'Open' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('OpenPrice')) Else If ColumnName = 'Low' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('LowPrice')) Else If ColumnName = 'Last' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('LastPrintPrice')) Else If ColumnName = 'High' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('HighPrice')) Else If ColumnName = 'Day Range' Then Result := TPercentBarTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('DayRange')) Else If ColumnName = 'Close' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('ClosePrice')) Else If ColumnName = '52 Week Low' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('52WeekLow')) Else If ColumnName = '52 Week High' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('52WeekHigh')) Else If ColumnName = 'Year Range' Then Result := TPercentBarTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('YearRange')) Else If ColumnName = 'Change From Close' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('ChangeFromClose'), TTriColor.FgFactory(TGenericDataNodeFactory.FindFactory('ChangeFromClose'))) Else If ColumnName = 'Change From Open' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('ChangeFromOpen'), Nil, TTriColor.BgFactory(TGenericDataNodeFactory.FindFactory('ChangeFromOpen'))) Else If ColumnName = '2 Points' Then Result := TConeTableColumnDescriptor.Create( TGenericDataNodeFactory.CreateWithArgs(TConstantDoubleDataNode, 0.5), TGenericDataNodeFactory.CreateWithArgs(TAdd, TGenericDataNodeFactory.CreateWithArgs(TDivide, TGenericDataNodeFactory.FindFactory('ChangeFromClose') As IUnknown, TGenericDataNodeFactory.CreateWithArgs(TConstantDoubleDataNode, 4.0) As IUnknown) As IUnknown, TGenericDataNodeFactory.CreateWithArgs(TConstantDoubleDataNode, 0.5) As IUnknown), TGenericDataNodeFactory.CreateWithArgs(TConstantColorDataNode, clGreen)) Else If ColumnName = 'Alt 2 Points' Then Result := TCylinderTableColumnDescriptor.Create( TGenericDataNodeFactory.CreateWithArgs(TConstantDoubleDataNode, 0.5), TGenericDataNodeFactory.CreateWithArgs(TAdd, TGenericDataNodeFactory.CreateWithArgs(TDivide, TGenericDataNodeFactory.FindFactory('ChangeFromClose') As IUnknown, TGenericDataNodeFactory.CreateWithArgs(TConstantDoubleDataNode, 4.0) As IUnknown) As IUnknown, TGenericDataNodeFactory.CreateWithArgs(TConstantDoubleDataNode, 0.5) As IUnknown), TGenericDataNodeFactory.CreateWithArgs(TConstantColorDataNode, clGreen)) Else If ColumnName = 'Last Print Size' Then Result := TIntegerTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('LastPrintSize')) Else If ColumnName = '? 52HighDate' Then Result := TDateTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('52HighDate')) Else If ColumnName = '? 52LowDate' Then Result := TDateTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('52LowDate')) Else If ColumnName = 'Symbol (Color by Direction)' Then Result := TStringTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('Symbol'), Nil, TTriColor.BgFactory( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('LastPrintPrice'), TGenericDataNodeFactory.CreateWithArgs(TPreviousValue, TGenericDataNodeFactory.FindFactory('LastPrintPrice')) As IGenericDataNodeFactory ) As IGenericDataNodeFactory)) Else If ColumnName = 'Increase from Open' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('LastPrintPrice'), TGenericDataNodeFactory.FindFactory('OpenPrice'))) Else If ColumnName = 'Decrease from Open' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('AskPrice'), TGenericDataNodeFactory.FindFactory('OpenPrice'))) Else If ColumnName = 'Volume Break' Then Result := TIntegerTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('VolumeBreak')) Else If ColumnName = 'Tick Volatility' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('TickVolatility')) Else If ColumnName = 'Todays Close' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('TodaysClosePrice')) Else If ColumnName = 'Bid Tick' Then Result := TBooleanTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('BidTick')) Else If ColumnName = 'Last Time' Then Result := TTimeTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('LastPrintTime')) Else If ColumnName = 'Most Recent Close' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('MostRecentClose')) Else If ColumnName = 'Last Exchange' Then Result := TStringTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('LastPrintExchange')) Else If ColumnName = 'Last FormT' Then Result := TBooleanTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('LastPrintFormT')) Else If ColumnName = 'Bid Exchange' Then Result := TStringTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('BidExchange')) Else If ColumnName = 'Ask Exchange' Then Result := TStringTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('AskExchange')) Else If ColumnName = 'Listed Exchange' Then Result := TStringTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('ListedExchange')) Else If ColumnName = 'Gap' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('Gap')) Else If ColumnName = 'Up Days' Then Result := TIntegerTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('UpDays')) Else If ColumnName = 'Up Trend' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('UpTrendStrength')) Else If ColumnName = 'Down Trend' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('DownTrendStrength')) Else If ColumnName = 'Expected Open' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('ExpectedOpen')) Else If ColumnName = 'RSI 5-min' Then Result := TPriceTableColumnDescriptor.Create(CreateRsiFactory(5)) Else If ColumnName = 'RSI 15-min' Then Result := TPriceTableColumnDescriptor.Create(CreateRsiFactory(15)) Else If ColumnName = 'CCI 3-min' Then Result := TPriceTableColumnDescriptor.Create(CreateCciFactory(3)) Else If ColumnName = 'NYSE Bid' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('NyseBidPrice'), Nil, TTriColor.BgFactory( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('NyseBidPrice'), TGenericDataNodeFactory.CreateWithArgs(TPreviousValue, TGenericDataNodeFactory.CreateWithArgs(TIgnoreNullChanges, TGenericDataNodeFactory.FindFactory('NyseBidPrice')) As IGenericDataNodeFactory) As IGenericDataNodeFactory) As IGenericDataNodeFactory)) Else If ColumnName = 'NYSE Ask' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('NyseAskPrice'), Nil, TTriColor.BgFactory( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('NyseAskPrice'), TGenericDataNodeFactory.CreateWithArgs(TPreviousValue, TGenericDataNodeFactory.CreateWithArgs(TIgnoreNullChanges, TGenericDataNodeFactory.FindFactory('NyseAskPrice')) As IGenericDataNodeFactory) As IGenericDataNodeFactory) As IGenericDataNodeFactory)) Else If ColumnName = 'NYSE Spread' Then Result := TPriceTableColumnDescriptor.Create( TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('NyseAskPrice'), TGenericDataNodeFactory.FindFactory('NyseBidPrice'))) Else If ColumnName = 'Up Candles 5' Then Result := TIntegerTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('UpCandles5')) Else If ColumnName = 'Up Candles 10' Then Result := TIntegerTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('UpCandles10')) Else If ColumnName = 'Up Candles 15' Then Result := TIntegerTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('UpCandles15')) Else If ColumnName = 'Up Candles 30' Then Result := TIntegerTableColumnDescriptor.Create( TGenericDataNodeFactory.FindFactory('UpCandles30')) Else If ColumnName = 'Put/Call Ratio' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('PutCallRatio')) Else If ColumnName = 'Distance From NBBO' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('DistanceFromNbbo')) Else If ColumnName = 'Today''s Range' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('TodaysRange')) Else If ColumnName = '5 Min Price Change' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('TimedMovement5')) Else If ColumnName = '10 Min Price Change' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('TimedMovement10')) Else If ColumnName = '15 Min Price Change' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('TimedMovement15')) Else If ColumnName = '30 Min Price Change' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('TimedMovement30')) Else If ColumnName = '5 Min Volume Change' Then Result := TIntegerTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('VolumeChange5')) Else If ColumnName = '10 Min Volume Change' Then Result := TIntegerTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('VolumeChange10')) Else If ColumnName = '15 Min Volume Change' Then Result := TIntegerTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('VolumeChange15')) Else If ColumnName = '30 Min Volume Change' Then Result := TIntegerTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('VolumeChange30')) Else If ColumnName = '5 Min SPY % Change' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('SPY5')) Else If ColumnName = '10 Min SPY % Change' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('SPY10')) Else If ColumnName = '15 Min SPY % Change' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('SPY15')) Else If ColumnName = '30 Min SPY % Change' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('SPY30')) Else If ColumnName = '30 Min Range' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('IntraDayRange30')) Else If ColumnName = '60 Min Range' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('IntraDayRange60')) Else If ColumnName = '120 Min Range' Then Result := TPriceTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('IntraDayRange120')) Else If ColumnName = 'STC Debug' Then Result := TStringTableColumnDescriptor.Create(TGenericDataNodeFactory.FindFactory('ShortTermCandlesDebug')) Else If ColumnName = '8 Period SMA 15 min' Then Result := TPriceTableColumnDescriptor.Create(CreateIntradaySmaFactory(15, 8)) Else If ColumnName = '20 Period SMA 15 min' Then Result := TPriceTableColumnDescriptor.Create(CreateIntradaySmaFactory(15, 20)) Else If ColumnName = '200 Period SMA 15 min' Then Result := TPriceTableColumnDescriptor.Create(CreateIntradaySmaFactory(15, 200)) ; If Result <> Nil Then Result.Name := ColumnName End; Function TMarketViewBase.GetStandardColumnNames : TStringArray; Const { This is used to populate the list of choices on the menu. } Names : Array [0..73] Of String = ( 'Increase from Open', 'Decrease from Open', 'Symbol', 'Company Name', 'Bid Size', 'Best Bid', 'Best Ask', 'Ask Size', 'Volume', 'Open', 'Low', 'Last', 'High', 'Day Range', 'Close', '52 Week Low', '52 Week High', 'Year Range', 'Change From Close', 'Change From Open', '2 Points', 'Alt 2 Points', 'Last Print Size', '? 52HighDate', '? 52LowDate', 'Symbol (Color by Direction)', 'Volume Break', 'Tick Volatility', 'Todays Close', 'Bid Tick', 'Last Time', 'Most Recent Close', 'Last Exchange', 'Bid Exchange', 'Ask Exchange', 'Listed Exchange', 'Gap', 'Last FormT', 'Up Days', 'Up Trend', 'Down Trend', 'Expected Open', 'RSI 5-min', 'RSI 15-min', 'CCI 3-min', 'NYSE Bid', 'NYSE Ask', 'NYSE Spread', 'Up Candles 5', 'Up Candles 10', 'Up Candles 15', 'Up Candles 30', 'Put/Call Ratio', 'Distance From NBBO', 'Today''s Range', '5 Min Price Change', '10 Min Price Change', '15 Min Price Change', '30 Min Price Change', '5 Min Volume Change', '10 Min Volume Change', '15 Min Volume Change', '30 Min Volume Change', '5 Min SPY % Change', '10 Min SPY % Change', '15 Min SPY % Change', '30 Min SPY % Change', '30 Min Range', '60 Min Range', '120 Min Range', 'STC Debug', '8 Period SMA 15 min', '20 Period SMA 15 min', '200 Period SMA 15 min'); Var I : Integer; Begin SetLength(Result, Length(Names)); For I := 0 To Pred(Length(Names)) Do Result[I] := Names[I] End; Procedure TMarketViewBase.CreateStandardColumns; Var I : Integer; F : TTableColumnDescriptorFactory; D : TTableColumnDescriptor; Names : TStringArray; Begin Names := GetStandardColumnNames; For I := Low(Names) To High(Names) Do Begin F := TTableColumnDescriptorFactory.Create(Self); F.StringCreationInstructions := '#' + Names[I]; D := F.CreateWindow(Self) As TTableColumnDescriptor; If Not Assigned(D) Then Break; FColumnFactories.AddObject(D.FName, F); D.Free End End; Procedure TMarketViewBase.DoReplacements(Factory : IGenericDataNodeFactory); Begin // Replacements global to the window here. End; Procedure TMarketViewBase.ShowFontDialog(Sender: TObject); Var I : TStandardColors; Begin For I := Low(TStandardColors) To High(TStandardColors) Do FontsAndColorsDlg.MarketViewColors[I] := StandardPalette.GetValue(I); FontsAndColorsDlg.MarketViewFont.Assign(Grid.Font); FontsAndColorsDlg.MarketViewHorizontal := goHorzLine In Grid.Options; FontsAndColorsDlg.MarketViewVertical := goVertLine In Grid.Options; //FontsAndColorsDlg.MarketViewGridColor := Grid. FontsAndColorsDlg.ShowModal; If FontsAndColorsDlg.ModalResult = mrOk Then Begin For I := Low(TStandardColors) To High(TStandardColors) Do StandardPalette.SetValue(I, FontsAndColorsDlg.MarketViewColors[I]); Grid.Font.Assign(FontsAndColorsDlg.MarketViewFont); Grid.Color := ColorToRGB(FontsAndColorsDlg.MarketViewColors[scBackground]); If FontsAndColorsDlg.MarketViewHorizontal Then Grid.Options := Grid.Options + [goHorzLine] Else Grid.Options := Grid.Options - [goHorzLine]; If FontsAndColorsDlg.MarketViewVertical Then Grid.Options := Grid.Options + [goVertLine] Else Grid.Options := Grid.Options - [goVertLine]; SetDefaultCellSizes End End; Procedure TMarketViewBase.GetSymbolList(Symbols : TStrings); Var I : Integer; Begin Symbols.Clear; For I := 1 To Pred(Grid.RowCount) Do Symbols.Add((Grid.Objects[0, I] As TSymbolTableRowDescriptor).Symbol) End; Function TMarketViewBase.IsTopLevelWindow : Boolean; Begin Result := True End; Procedure TMarketViewBase.NewData; Begin Dirty := True // DAN BUG. Dan blew up here right after switching layouts. End; Procedure TMarketViewBase.CheckForRedraw; Begin UpdateAutoSortHeader; If Dirty Then RedrawNow; Dirty := False End; Procedure TMarketViewBase.RedrawNow; Begin Grid.Invalidate End; Procedure TMarketViewBase.UpdateAllCells; Var Col, Row : Integer; Begin LockDataNodes; Try For Col := 1 To Pred(Grid.ColCount) Do For Row := 1 To Pred(Grid.RowCount) Do UpdateCell(Col, Row) Finally UnlockDataNodes End End; Procedure TMarketViewBase.UpdateCell(ACol, ARow : Integer); Var Formulas : TTableColumnDescriptor; Parameters : TTableRowDescriptor; Data : TTableCellData; Begin Formulas := Grid.Objects[ACol, 0] As TTableColumnDescriptor; Parameters := Grid.Objects[0, ARow] As TTableRowDescriptor; Data := Grid.Objects[ACol, ARow] As TTableCellData; If Assigned(Formulas) And Assigned(Parameters) And Assigned(Data) Then Begin Data.Update(Formulas, Parameters, NewData); Grid.Invalidate End End; Function TMarketViewBase.NewRowDescriptor : TSymbolTableRowDescriptor; Begin Result := TSymbolTableRowDescriptor.Create End; Procedure TMarketViewBase.InitializeRow(Row : Integer); Var Col : Integer; RowDescriptor : TSymbolTableRowDescriptor; Begin Assert((Row > 0) And (Row <= Grid.RowCount)); RowDescriptor := NewRowDescriptor; RowDescriptor.ConfigurationOwner := GetConfigurationOwnerKey(Self); Grid.Objects[0, Row] := RowDescriptor; For Col := 1 To Pred(Grid.ColCount) Do Begin Grid.Objects[Col, Row] := TTableCellData.Create(DoReplacements); UpdateCell(Col, Row) End End; Procedure TMarketViewBase.RemoveColumn(Column : Integer); Var Row : Integer; Begin If (Column > 0) And (Column < Grid.ColCount) Then Begin For Row := 0 To Pred(Grid.RowCount) Do Grid.Objects[Column, Row].Free; For Row := 0 To Pred(Grid.RowCount) Do Grid.Objects[Column, Row] := Nil; If Not ((Column = 1) And (Grid.ColCount = 2)) Then Grid.DeleteColumn(Column) End; End; Procedure TMarketViewBase.AddColumn(Factory : TTableColumnDescriptorFactory); Var ColumnDescriptor : TTableColumnDescriptor; Begin ColumnDescriptor := Factory.CreateWindow(Self) As TTableColumnDescriptor; If Assigned(ColumnDescriptor) Then AddColumn(ColumnDescriptor, Factory.Width) End; Procedure TMarketViewBase.AddColumn(ColumnDescriptor : TTableColumnDescriptor; Width : Integer); Var Col, Row : Integer; Begin Col := Pred(Grid.ColCount); If Grid.Objects[Col, 0] <> Nil Then Begin Grid.ColCount := Succ(Grid.ColCount); Inc(Col) End; Grid.Objects[Col, 0] := ColumnDescriptor; For Row := 1 To Pred(Grid.RowCount) Do Begin Grid.Objects[Col, Row] := TTableCellData.Create(DoReplacements); UpdateCell(Col, Row) End; If Width = 0 Then SetDefaultCellSizes Else Grid.ColWidths[Col] := Width End; Procedure TMarketViewBase.TimeTest(Sender : TObject); Const Count = 100; Var Start, Finish : TDateTime; Seconds : Double; I : Integer; Begin Start := Now; For I := 1 To Count Do Grid.Repaint; Finish := Now; Seconds := (Finish - Start) * 24 * 60 * 60; Application.MessageBox(PChar(Format('%.3f seconds / %d trials = %.3f', [Seconds, Count, Seconds / Count])), 'Result') End; Procedure TMarketViewBase.SetSymbolCount(Count : Integer); Var NewSymbolCount, PreviousSymbolCount, Row, Col : Integer; Begin If Count < 1 Then NewSymbolCount := 1 Else NewSymbolCount := Count; PreviousSymbolCount := Pred(Grid.RowCount); If PreviousSymbolCount < NewSymbolCount Then Begin Grid.RowCount := Succ(NewSymbolCount); For Row := Succ(PreviousSymbolCount) To NewSymbolCount Do InitializeRow(Row) End Else If PreviousSymbolCount > NewSymbolCount Then Begin For Row := Succ(NewSymbolCount) To PreviousSymbolCount Do For Col := 0 To Pred(Grid.ColCount) Do Grid.Objects[Col, Row].Free; Grid.RowCount := Succ(NewSymbolCount) End; If Count < 1 Then Begin // If we Ask for 0 rows, we get 1 row, but it is gaurenteed to be empty. (Grid.Objects[0, 1] As TSymbolTableRowDescriptor).Disable; For Col := 1 To Pred(Grid.ColCount) Do UpdateCell(Col, 1) End End; Procedure TMarketViewBase.SetSymbolList(Symbols : TStrings); Var SymbolNumber : Integer; Col : Integer; NewSymbol : String; Begin LockDataNodes; Try SetSymbolCount(Symbols.Count); For SymbolNumber := 0 To Pred(Symbols.Count) Do Begin NewSymbol := Trim(UpperCase(Symbols[SymbolNumber])); (Grid.Objects[0, Succ(SymbolNumber)] As TSymbolTableRowDescriptor).Symbol := NewSymbol; For Col := 1 To Pred(Grid.ColCount) Do UpdateCell(Col, Succ(SymbolNumber)) End Finally UnlockDataNodes End End; Procedure TMarketViewBase.GridDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); Begin If (ACol = 0) Then THeaderCellRenderer.Instance.DrawCell(Self, Sender, ACol, ARow, Rect, State) Else If (ARow = 0) Then ColumnHeaders.DrawCell(Self, Sender, ACol, ARow, Rect, State) Else If Grid.Objects[ACol, 0] <> Nil Then (Grid.Objects[ACol, 0] As TTableColumnDescriptor).DrawCell(Self, Sender, ACol, ARow, Rect, State) Else With Grid Do Begin Canvas.Brush.Color := Color; Canvas.FillRect(Rect); End End; procedure TMarketViewBase.GridGetEditText(Sender: TObject; ACol, ARow: Integer; var Value: String); Begin //If Assigned(Grid.Objects[ACol, ARow]) Then // (Grid.Objects[ACol, ARow] As TCellOrganizer).BeforeEdit(Value) End; procedure TMarketViewBase.GridSelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); Begin //If Assigned(Grid.Objects[ACol, ARow]) Then // CanSelect := (Grid.Objects[ACol, ARow] As TCellOrganizer).Editable //Else CanSelect := False End; Procedure TMarketViewBase.GridSetEditText(Sender: TObject; ACol, ARow: Integer; const Value: String); //Var // Grid : TStringGrid; Begin //Grid := (Sender As TStringGrid); ////If (Not Grid.EditorMode) Or (Grid.Selection.Left <> ACol) Or (Grid.Selection.Top <> ARow) Then // If Assigned(Grid.Objects[ACol, ARow]) Then // (Grid.Objects[ACol, ARow] As TCellOrganizer).AfterEdit(ACol, ARow, Value) End; Destructor TMarketViewBase.Destroy; Var Col, Row : Integer; Begin FColumnFactories.Free; ColumnHeaders.Free; If Assigned(Timer) Then Timer.Release; For Row := Pred(Grid.RowCount) DownTo 0 Do For Col := Pred(Grid.ColCount) DownTo 0 Do Grid.Objects[Col, Row].Free; Inherited End; Procedure TMarketViewBase.FormClose(Sender: TObject; Var Action: TCloseAction); Begin Action := caFree End; Constructor TMarketViewBase.Create(AOwner : TComponent); Var I : Integer; SortAction : TSortMenuAction; SortModeAction : TSortModeMenuAction; DuplicateAction : TDuplicateAction; SaveAction : TSaveAction; Begin FColumnFactories := TStringList.Create; ColumnHeaders := TSortableHeaderCellRenderer.Create(Self); CreateNew(AOwner); Caption := 'Market View'; OnClose := FormClose; Grid := TMoveGrid.Create(Self); //Grid.DoubleBuffered := True; //This can cause the CPU to go really high in some cases, especially with overlapping windows. Grid.Parent := Self; Grid.Align := alClient; Grid.DefaultDrawing := False; Grid.Options := [goColSizing, goRowMoving, goColMoving, goAlwaysShowEditor]; Grid.OnDrawCell := GridDrawCell; Grid.OnGetEditText := GridGetEditText; Grid.OnSelectCell := GridSelectCell; Grid.OnSetEditText := GridSetEditText; Grid.OnDblClick := GridDoubleClick; StandardPalette := TStandardPalette.Create(Self); StandardPalette.Sane; FromPaletteToForm; PopupMenu := TPopupMenu.Create(Self); PopupMenu.Items.Add(TMenuItem.Create(Self)); PopupMenu.Items[0].Caption := 'Sort'; PopupMenu.Items[0].Add(TMenuItem.Create(Self)); SortAction := TSortMenuAction.Create(Self); SortAction.Auto := True; SortAction.Direction := False; PopupMenu.Items[0].Items[0].Action := SortAction; PopupMenu.Items[0].Add(TMenuItem.Create(Self)); SortAction := TSortMenuAction.Create(Self); SortAction.Auto := True; SortAction.Direction := True; PopupMenu.Items[0].Items[1].Action := SortAction; PopupMenu.Items[0].Add(TMenuItem.Create(Self)); SortAction := TSortMenuAction.Create(Self); SortAction.Auto := False; SortAction.Direction := False; PopupMenu.Items[0].Items[2].Action := SortAction; PopupMenu.Items[0].Add(TMenuItem.Create(Self)); SortAction := TSortMenuAction.Create(Self); SortAction.Auto := False; SortAction.Direction := True; PopupMenu.Items[0].Items[3].Action := SortAction; PopupMenu.Items[0].Add(TMenuItem.Create(Self)); PopupMenu.Items[0].Items[4].Caption := '-'; PopupMenu.Items[0].Add(TMenuItem.Create(Self)); PopupMenu.Items[0].Items[5].Caption := 'Doubleclick means'; SortModeAction := TSortModeMenuAction.Create(Self); SortModeAction.SortMode := smAuto; PopupMenu.Items[0].Items[5].Add(TMenuItem.Create(Self)); PopupMenu.Items[0].Items[5].Items[0].Action := SortModeAction; SortModeAction := TSortModeMenuAction.Create(Self); SortModeAction.SortMode := smManualAscending; PopupMenu.Items[0].Items[5].Add(TMenuItem.Create(Self)); PopupMenu.Items[0].Items[5].Items[1].Action := SortModeAction; SortModeAction := TSortModeMenuAction.Create(Self); SortModeAction.SortMode := smManualDecending; PopupMenu.Items[0].Items[5].Add(TMenuItem.Create(Self)); PopupMenu.Items[0].Items[5].Items[2].Action := SortModeAction; PopupMenu.Items.Add(TMenuItem.Create(Self)); PopupMenu.Items[1].Caption := 'Fonts and Colors...'; PopupMenu.Items[1].OnClick := ShowFontDialog; PopupMenu.Items.Add(TMenuItem.Create(Self)); PopupMenu.Items[2].Caption := 'Link to Stock Windows...'; PopupMenu.Items[2].OnClick := EditMarketViewLinks; FColumnsMenuItem := TMenuItem.Create(Self); FColumnsMenuItem.Caption := 'Add'; PopupMenu.Items.Add(FColumnsMenuItem); PopupMenu.Items.Add(TMenuItem.Create(Self)); PopupMenu.Items[4].Action := TRemoveColumnMenuAction.Create(Self); PopupMenu.Items.Add(TMenuItem.Create(Self)); PopupMenu.Items[5].Caption := 'Time Test...'; PopupMenu.Items[5].OnClick := TimeTest; DuplicateAction := TDuplicateAction.Create(Self); DuplicateAction.PanelOnly := True; PopupMenu.Items.Add(TMenuItem.Create(Self)); PopupMenu.Items[6].Action := DuplicateAction; SaveAction := TSaveAction.Create(Self); PopupMenu.Items.Add(TMenuItem.Create(Self)); PopupMenu.Items[7].Action := SaveAction; TScreenUpdater.Find(CheckForRedraw, Timer); Timer.SetReceiveInput(True); Grid.ColWidths[0] := 7; Grid.RowCount := 2; Grid.ColCount := 2; For I := 1 To Pred(Grid.RowCount) Do InitializeRow(I); SetAccount(''); AutoSortPeriodMS := 5000; CreateStandardColumns; RefreshColumnMenu; End; Procedure TMarketViewBase.FromPaletteToForm; Begin Grid.Font.Color := StandardPalette.GetValue(scForeground); Grid.Color := StandardPalette.GetValue(scBackground); End; Procedure TMarketViewBase.GetCell(Point : TPoint; Out ACol, ARow : Integer); Begin Point := Grid.ScreenToClient(Point); Grid.MouseToCell(Point.X, Point.Y, ACol, ARow) End; Procedure TMarketViewBase.GridDoubleClick(Sender: TObject); Var ACol, ARow : Integer; Begin; GetCell(Mouse.CursorPos, ACol, ARow); If (ACol <> -1) And (ARow <> -1) Then DoCellClick(ACol, ARow) End; Procedure TMarketViewBase.DoCellClick(ACol, ARow : Integer); Begin If ARow = 0 Then If Grid.Objects[ACol, ARow] <> Nil Then Case SortMode Of smAuto : SetAutosortColumnAndDirection(ACol); smManualAscending : Begin ClearAutoSort; SortOnce(ACol, False); End; smManualDecending : Begin ClearAutoSort; SortOnce(ACol, True); End; Else Assert(False); End Else Else SendRowToGroup(ARow, MarketViewLink.DoubleClickGroup) End; Procedure TMarketViewBase.SetDefaultCellSizes; Var Col, Row : Integer; Body, Header : Integer; Renderer : TCellRenderer; Hint : String; Begin Grid.Canvas.Font := Grid.Font; Grid.ColWidths[0] := 7; Body := 0; Header := 0; Begin For Col := 1 To Pred(Grid.ColCount) Do Begin Renderer := Grid.Objects[Col, 0] As TCellRenderer; If Assigned(Renderer) Then Hint := Renderer.SizeHint Else Hint := 'XXXXXXXXXXXX'; Grid.ColWidths[Col] := 4 + Grid.Canvas.TextWidth(Hint); Body := Max(Body, Grid.Canvas.TextHeight(Hint)); If Assigned(Renderer) Then Header := Max(Header, Grid.Canvas.TextHeight(Renderer.Title)) End; Grid.RowHeights[0] := 4 + Header; Grid.DefaultRowHeight := 4 + Body; For Row := 1 To Pred(Grid.RowCount) Do Grid.RowHeights[Row] := 4 + Body End; Grid.Invalidate End; Function TMarketViewBase.GetRowDescriptor(Row : Integer) : TSymbolTableRowDescriptor; Begin Inc(Row); If (Row < 1) Or (Row >= Grid.RowCount) Then Result := Nil Else Result := Grid.Objects[0, Row] As TSymbolTableRowDescriptor End; Function TMarketViewBase.GetRowCount : Integer; Begin Result := Pred(Grid.RowCount) End; Function TMarketViewBase.CurrentAutoSortColumnNumber : Integer; Var I : Integer; Begin Result := -1; If AutoSortColumn <> Nil Then For I := 1 To Pred(Grid.ColCount) Do If Grid.Objects[I, 0] = AutoSortColumn Then Begin Result := I; Break; End End; Procedure TMarketViewBase.OnAutoSortTimer(Sender : TObject); Var Col : Integer; Begin Col := CurrentAutoSortColumnNumber; If Col <> -1 Then Begin LastAutoSortTime := Now; SortOnce(Col, AutoSortDirection) End; End; Procedure TMarketViewBase.SortOnce(Col : Integer; Direction : Boolean); Var Descriptor : TTableColumnDescriptor; I : Integer; Begin Descriptor := Grid.Objects[Col, 0] As TTableColumnDescriptor; Descriptor.Sort(Self, Col, Direction); For I := Low(MarketViewLink.SortGroup) To High(MarketViewLink.SortGroup) Do SendRowToGroup(I, MarketViewLink.SortGroup[I]) End; Procedure TMarketViewBase.SetAutosortColumnAndDirection(Column : Integer); Var PreviousColumn : Integer; Begin PreviousColumn := CurrentAutoSortColumnNumber; If PreviousColumn = Column Then If AutoSortDirection = False Then AutoSortDirection := True Else Column := -1 Else AutoSortDirection := False; SetAutoSortColumn(Column); End; Procedure TMarketViewBase.SetAutoSortColumn(Column : Integer); Var PreviousColumn : Integer; Begin If Not Assigned(AutoSortTimer) Then Begin AutoSortTimer := TTimer.Create(Self); AutoSortTimer.OnTimer := OnAutoSortTimer; AutoSortTimer.Interval := AutoSortPeriodMS; End; PreviousColumn := CurrentAutoSortColumnNumber; If PreviousColumn <> -1 Then Grid.Cells[PreviousColumn, 0] := ''; If Column = -1 Then Begin AutoSortTimer.Enabled := False; AutoSortColumn := Nil; End Else Begin AutoSortColumn := Grid.Objects[Column, 0] As TTableColumnDescriptor; OnAutoSortTimer(Nil); AutoSortTimer.Enabled := True End End; Procedure TMarketViewBase.ClearAutoSort; Begin SetAutoSortColumn(-1) End; Procedure TMarketViewBase.UpdateAutoSortHeader; Var Col : Integer; Begin Col := CurrentAutoSortColumnNumber; If Col <> -1 Then Grid.Cells[Col, 0] := '' End; Procedure TMarketViewBase.SetAccount(Account : String); Begin FAccount := Account; UpdateAllCells End; Procedure TMarketViewBase.EditMarketViewLinks(Sender : TObject); Begin MarketViewLinksDialogBox.MarketViewLink := MarketViewLink; MarketViewLinksDialogBox.ListOwner := Self; MarketViewLinksDialogBox.ShowModal; If MarketViewLinksDialogBox.ModalResult = mrOk Then Begin MarketViewLink := MarketViewLinksDialogBox.MarketViewLink; LoadMarketViewLinks End; End; Function TMarketViewBase.SendRowToGroup(RowNumber : Integer; Group : String) : Boolean; Var NewSymbol : String; Begin NewSymbol := ''; If (RowNumber > 0) And (RowNumber < Grid.RowCount) Then If Grid.Objects[0, RowNumber] <> Nil Then NewSymbol := (Grid.Objects[0, RowNumber] As TSymbolTableRowDescriptor).Symbol; Result := NewSymbol <> ''; If Result Then TGroupList.Instance.SetSymbol(Group, NewSymbol) End; Procedure TMarketViewBase.OnCycleTimer(Sender : TObject); Var NextCycleRow, Attempts, LastRow : Integer; Begin LastRow := Min(Pred(Grid.RowCount), MarketViewLink.RowLimit); NextCycleRow := PreviousCycleRow; Attempts := 0; Repeat Inc(Attempts); If Attempts > LastRow Then Exit; NextCycleRow := Succ(NextCycleRow); If NextCycleRow > LastRow Then NextCycleRow := 1; Until SendRowToGroup(NextCycleRow, MarketViewLink.CycleGroup); PreviousCycleRow := NextCycleRow End; Procedure TMarketViewBase.LoadMarketViewLinks; Begin If Not Assigned(CycleTimer) Then Begin CycleTimer := TTimer.Create(Self); CycleTimer.OnTimer := OnCycleTimer End; CycleTimer.Enabled := (MarketViewLink.CycleGroup <> '') And (MarketViewLink.CycleTimeMS > 0); If CycleTimer.Enabled Then CycleTimer.Interval := MarketViewLink.CycleTimeMS End; Procedure TMarketViewBase.DoAddColumn(Sender : TObject); Begin AddColumn(FColumnFactories.Objects[(Sender As TMenuItem).Tag] As TTableColumnDescriptorFactory) End; Procedure TMarketViewBase.RefreshColumnMenu; Var MenuItem : TMenuItem; I : Integer; Begin While FColumnsMenuItem.Count > 0 Do Begin MenuItem := FColumnsMenuItem[0]; FColumnsMenuItem.Remove(MenuItem); MenuItem.Free End; If FColumnFactories.Count = 0 Then Begin MenuItem := TMenuItem.Create(PopupMenu); MenuItem.Caption := 'None'; MenuItem.Enabled := False; FColumnsMenuItem.Add(MenuItem) End Else For I := 0 To Pred(FColumnFactories.Count) Do Begin MenuItem := TMenuItem.Create(PopupMenu); MenuItem.Caption := FColumnFactories.Strings[I]; MenuItem.Tag := I; MenuItem.OnClick := DoAddColumn; FColumnsMenuItem.Add(MenuItem) End End; Initialization RegisterClass(TTableColumnDescriptorFactory); BitmapBuffer := TBitmap.Create; //BitmapBuffer.IgnorePalette := True; BitmapBuffer.PixelFormat := pf32Bit; // This saves 33% of the time compared to pfDevice. It would be nice to know the real mode of the screen. //BitmapBuffer.HandleType := bmDIB; // This has approximately the same effect as the line above, but the documentation seems to prefer the line above. End.