Cocoa: support OnDrawItem/OnCustomDraw/OnCustomDrawItem/OnCustomDrawSubItem for ListView/ListBox/CheckListBox, Merge branch 'cocoa/listview'

This commit is contained in:
rich2014 2024-08-14 20:32:04 +08:00
commit 76e937a5cb
6 changed files with 235 additions and 64 deletions

View File

@ -88,20 +88,29 @@ type
property CocoaOnlyState: Boolean read IsCocoaOnlyState write SetCocoaOnlyState;
end;
{
currently the following callbacks implement IListViewCallBack,
need to be considered before modification:
1. TLCLListViewCallback
2. TLCLListBoxCallback
3. TLCLCheckboxListCallback
}
{ IListViewCallBack }
IListViewCallBack = interface(ICommonCallback)
function ItemsCount: Integer;
function GetImageListType( out lvil: TListViewImageList ): Boolean;
function GetItemTextAt(ARow, ACol: Integer; var Text: String): Boolean;
function GetItemCheckedAt(ARow, ACol: Integer; var CheckState: Integer): Boolean;
function GetItemCheckedAt( row: Integer; var CheckState: Integer): Boolean;
function GetItemImageAt(ARow, ACol: Integer; var imgIdx: Integer): Boolean;
function GetImageFromIndex(imgIdx: Integer): NSImage;
procedure SetItemTextAt(ARow, ACol: Integer; const Text: String);
procedure SetItemCheckedAt(ARow, ACol: Integer; CheckState: Integer);
procedure SetItemCheckedAt( row: Integer; CheckState: Integer);
function shouldSelectionChange(NewSel: Integer): Boolean;
procedure ColumnClicked(ACol: Integer);
function DrawRow(rowidx: Integer; ctx: TCocoaContext; const r: TRect; state: TOwnerDrawState): Boolean;
function drawItem( row: Integer; ctx: TCocoaContext; const r: TRect; state: TOwnerDrawState ): Boolean;
function customDraw( row: Integer; col: Integer; ctx: TCocoaContext; state: TCustomDrawState ): Boolean;
function isCustomDrawSupported: Boolean;
procedure GetRowHeight(rowidx: Integer; var height: Integer);
function GetBorderStyle: TBorderStyle;
function onAddSubview( aView:NSView ): Boolean;

View File

@ -605,7 +605,7 @@ begin
cv:= TCocoaCollectionView( self.collectionView );
indexPath:= cv.indexPathForItem( self );
row:= indexPath.item;
cv.callback.SetItemCheckedAt( row, 0, sender.state );
cv.callback.SetItemCheckedAt( row, sender.state );
if sender.state = NSOnState then begin
cv.selectOneItemByIndex( row, True );
self.view.window.makeFirstResponder( self.collectionView );
@ -811,7 +811,7 @@ begin
checkBox:= cocoaItem.checkBox;
if Assigned(checkBox) then begin
self.callback.GetItemCheckedAt( row, 0, checkedValue );
self.callback.GetItemCheckedAt( row, checkedValue );
checkBox.setState( checkedValue );
checkBox.setHidden( NOT ((checkedValue=NSOnState) or isSelected) );
end;

View File

@ -13,7 +13,7 @@ uses
Controls, ComCtrls, Types, StdCtrls, LCLProc, Graphics, ImgList, Forms,
// Cocoa WS
CocoaPrivate, CocoaCallback, CocoaScrollers, CocoaWSScrollers, CocoaTextEdits,
CocoaWSCommon, cocoa_extra, CocoaGDIObjects;
CocoaWSCommon, cocoa_extra, CocoaGDIObjects, CocoaUtils;
type
{ TLCLListViewCallback }
@ -33,17 +33,20 @@ type
function ItemsCount: Integer;
function GetImageListType( out lvil: TListViewImageList ): Boolean;
function GetItemTextAt(ARow, ACol: Integer; var Text: String): Boolean;
function GetItemCheckedAt(ARow, ACol: Integer; var IsChecked: Integer): Boolean;
function GetItemCheckedAt( row: Integer; var IsChecked: Integer): Boolean;
function GetItemImageAt(ARow, ACol: Integer; var imgIdx: Integer): Boolean;
function GetImageFromIndex(imgIdx: Integer): NSImage;
procedure SetItemTextAt(ARow, ACol: Integer; const Text: String);
procedure SetItemCheckedAt(ARow, ACol: Integer; IsChecked: Integer);
procedure SetItemCheckedAt( row: Integer; IsChecked: Integer);
function getItemStableSelection(ARow: Integer): Boolean;
procedure selectOne(ARow: Integer; isSelected:Boolean );
function shouldSelectionChange(NewSel: Integer): Boolean;
procedure ColumnClicked(ACol: Integer);
function DrawRow(rowidx: Integer; ctx: TCocoaContext; const r: TRect;
function drawItem( row: Integer; ctx: TCocoaContext; const r: TRect;
state: TOwnerDrawState): Boolean;
function customDraw( row: Integer; col: Integer;
ctx: TCocoaContext; state: TCustomDrawState ): Boolean;
function isCustomDrawSupported: Boolean;
procedure GetRowHeight(rowidx: Integer; var h: Integer);
function GetBorderStyle: TBorderStyle;
function onAddSubview(aView: NSView): Boolean;
@ -301,15 +304,17 @@ begin
end;
end;
function TLCLListViewCallback.GetItemCheckedAt(ARow, ACol: Integer;
function TLCLListViewCallback.GetItemCheckedAt( row: Integer;
var IsChecked: Integer): Boolean;
var
BoolState : array [Boolean] of Integer = (NSOffState, NSOnState);
indexSet: NSIndexSet;
begin
if ownerData and Assigned(listView) and (ARow>=0) and (ARow < listView.Items.Count) then
IsChecked := BoolState[listView.Items[ARow].Checked]
indexSet:= self.checkedIndexSet;
if ownerData and Assigned(listView) and (row>=0) and (row < listView.Items.Count) then
IsChecked := BoolState[listView.Items[row].Checked]
else
IsChecked := BoolState[checkedIndexSet.containsIndex(ARow)];
IsChecked := BoolState[checkedIndexSet.containsIndex(row)];
Result := true;
end;
@ -408,15 +413,15 @@ begin
end;
procedure TLCLListViewCallback.SetItemCheckedAt(ARow, ACol: Integer;
procedure TLCLListViewCallback.SetItemCheckedAt( row: Integer;
IsChecked: Integer);
var
Msg: TLMNotify;
NMLV: TNMListView;
begin
if IsChecked = NSOnState
then checkedIndexSet.addIndex(ARow)
else checkedIndexSet.removeIndex(ARow);
then checkedIndexSet.addIndex(row)
else checkedIndexSet.removeIndex(row);
FillChar(Msg{%H-}, SizeOf(Msg), #0);
FillChar(NMLV{%H-}, SizeOf(NMLV), #0);
@ -425,7 +430,7 @@ begin
NMLV.hdr.hwndfrom := ListView.Handle;
NMLV.hdr.code := LVN_ITEMCHANGED;
NMLV.iItem := ARow;
NMLV.iItem := row;
NMLV.iSubItem := 0;
NMLV.uChanged := LVIF_STATE;
Msg.NMHdr := @NMLV.hdr;
@ -504,19 +509,6 @@ begin
LCLMessageGlue.DeliverMessage(ListView, Msg);
end;
function TLCLListViewCallback.DrawRow(rowidx: Integer; ctx: TCocoaContext;
const r: TRect; state: TOwnerDrawState): Boolean;
var
ALV: TCustomListViewAccess;
drawResult: TCustomDrawResult;
begin
ALV:= TCustomListViewAccess(self.listView);
ALV.Canvas.Handle:= HDC(ctx);
drawResult:= ALV.IntfCustomDraw( dtItem, cdPrePaint, rowidx, 0, [], nil );
Result:= cdrSkipDefault in drawResult;
ALV.Canvas.Handle:= 0;
end;
procedure TLCLListViewCallback.GetRowHeight(rowidx: Integer; var h: Integer);
begin
@ -576,5 +568,50 @@ begin
TCustomListViewAccess(Target).InitializeWnd;
end;
function TLCLListViewCallback.drawItem( row: Integer; ctx: TCocoaContext;
const r: TRect; state: TOwnerDrawState ): Boolean;
var
Mess: TLMDrawListItem;
DrawStruct: TDrawListItemStruct;
begin
DrawStruct.ItemState := state;
DrawStruct.Area := r;
DrawStruct.DC := HDC(ctx);
DrawStruct.ItemID := row;
FillChar(Mess, SizeOf(Mess), 0);
Mess.Msg := CN_DRAWITEM;
Mess.DrawListItemStruct := @DrawStruct;
self.DeliverMessage( Mess );
Result:= False;
end;
function TLCLListViewCallback.customDraw(row: Integer; col: Integer;
ctx: TCocoaContext; state: TCustomDrawState ): Boolean;
var
ALV: TCustomListViewAccess;
drawTarget: TCustomDrawTarget;
drawResult: TCustomDrawResult;
rect: TRect;
begin
ALV:= TCustomListViewAccess(self.listView);
rect:= NSRectToRect( self.Owner.lclContentView.bounds );
if col=0 then
drawTarget:= dtItem
else if col>0 then
drawTarget:= dtSubItem
else
drawTarget:= dtControl;
ALV.Canvas.Handle:= HDC(ctx);
drawResult:= ALV.IntfCustomDraw( drawTarget, cdPrePaint, row, col, state, @rect );
ALV.Canvas.Handle:= 0;
Result:= cdrSkipDefault in drawResult;
end;
function TLCLListViewCallback.isCustomDrawSupported: Boolean;
begin
Result:= True;
end;
end.

View File

@ -62,6 +62,8 @@ type
procedure setColumn( column: NSTableColumn ); message 'setColumn:';
function checkBox: NSButton; message 'checkBox';
procedure drawRect(dirtyRect: NSRect); override;
procedure loadView( row: Integer; col: Integer );
message 'loadView:col:';
procedure updateItemValue( row: NSInteger; col: NSInteger );
@ -73,6 +75,17 @@ type
procedure dealloc; override;
end;
{
1. TCocoaTableListView related need to support
TListView/TListBox/TCheckListBox, etc.
2. the differences between these controls can be considered to be
implemented in the callback.
3. however, after careful consideration, we tried to keep the original
intention of the callback, and added TCocoaTableViewProcessor to
isolate these differences.
}
{ TCocoaTableViewProcessor }
TCocoaTableViewProcessor = class
function isInitializing( tv: NSTableView ): Boolean; virtual; abstract;
procedure onReloadData( tv: NSTableView ); virtual; abstract;
@ -135,8 +148,10 @@ type
function fittingSize: NSSize; override;
procedure drawRect(dirtyRect: NSRect); override;
function lclCustomDrawRow(row: NSInteger; clipRect: NSRect): Boolean;
message 'lclCustomDrawRow:clipRect:';
function lclCallDrawItem( row: NSInteger; ctxSize: NSSize; clipRect: NSRect): Boolean;
message 'lclCallDrawItem:ctxSize:clipRect:';
function lclCallCustomDraw( row: Integer; col: Integer; ctxSize: NSSize; clipRect: NSRect): Boolean;
message 'lclCallCustomDraw:col:ctxSize:clipRect:';
// mouse
procedure mouseDown(event: NSEvent); override;
@ -336,21 +351,28 @@ begin
Result := TCocoaTableListView.alloc;
end;
procedure TCocoaTableRowView.drawRect(dirtyRect: NSRect);
procedure hideAllSubviews( parent: NSView );
var
view: NSView;
begin
for view in parent.subviews do
view.setHidden( True );
end;
procedure TCocoaTableRowView.drawRect(dirtyRect: NSRect);
var
done: Boolean;
begin
inherited drawRect(dirtyRect);
done:= self.tableView.lclCallDrawItem( row , self.bounds.size, dirtyRect );
done:= self.tableView.lclCustomDrawRow( row , dirtyRect );
if done then begin
// the Cocoa default drawing cannot be skipped in NSTableView,
// we can only hide the CellViews to get the same effect.
// in the Lazarus IDE, there is a ListBox with OwnerDraw in Project-Forms,
// it's a case where the default drawing must be skipped.
for view in self.subviews do
view.setHidden( True );
hideAllSubviews( self );
end else begin
inherited drawRect( dirtyRect );
end;
end;
@ -452,34 +474,102 @@ begin
Result:= NSZeroSize;
end;
function TCocoaTableListView.lclCustomDrawRow(row: NSInteger; clipRect: NSRect
): Boolean;
function isFocused( tv: TCocoaTableListView ; row: NSInteger ): Boolean;
begin
Result:= False;
if Assigned(tv.window) and (tv.window.firstResponder = tv) then begin
if row < 0 then
Result:= True
else if tv.isRowSelected(row) then
Result:= True;
end;
end;
function isChecked( tv: TCocoaTableListView ; row: NSInteger ): Boolean;
var
checked: Integer;
begin
Result:= False;
if row < 0 then
Exit;
tv.callback.GetItemCheckedAt( row, checked );
Result:= checked=NSOnState;
end;
function TCocoaTableListView.lclCallDrawItem(row: NSInteger;
ctxSize: NSSize; clipRect: NSRect ): Boolean;
var
ctx: TCocoaContext;
ItemState: TOwnerDrawState;
begin
Result:= False;
if NOT self.isOwnerDraw then
Exit;
if not Assigned(callback) then Exit;
ctx := TCocoaContext.Create(NSGraphicsContext.currentContext);
ctx.InitDraw(Round(bounds.size.width), Round(bounds.size.height));
ctx.InitDraw(Round(ctxSize.width), Round(ctxSize.height));
try
ItemState := [];
if isRowSelected(row) then Include(ItemState, odSelected);
if lclIsEnabled then Include(ItemState, odDisabled);
if Assigned(window) and (window.firstResponder = self) and (odSelected in ItemState) then
if NOT lclIsEnabled then Include(ItemState, odDisabled);
if isFocused(self,row) then
Include(ItemState, odFocused);
if isChecked(self,row) then
Include(ItemState, odChecked);
Result:= callback.DrawRow(row, ctx, NSRectToRect(clipRect), ItemState);
Result:= callback.drawItem(row, ctx, NSRectToRect(clipRect), ItemState);
finally
ctx.Free;
end;
end;
function TCocoaTableListView.lclCallCustomDraw(row: Integer; col: Integer;
ctxSize: NSSize; clipRect: NSRect): Boolean;
var
ctx: TCocoaContext;
state: TCustomDrawState;
begin
Result:= False;
if NOT Assigned(callback) then
Exit;
if NOT callback.isCustomDrawSupported then
Exit;
ctx := TCocoaContext.Create(NSGraphicsContext.currentContext);
ctx.InitDraw(Round(ctxSize.width), Round(ctxSize.height));
try
state := [];
if isRowSelected(row) then Include(state, cdsSelected);
if NOT lclIsEnabled then Include(state, cdsDisabled);
if isFocused(self,row) then
Include(state, cdsFocused);
if isChecked(self,row) then
Include(state, cdsChecked);
Result:= callback.customDraw(row, col, ctx, state);
finally
ctx.Free;
end;
end;
procedure TCocoaTableListView.drawRect(dirtyRect: NSRect);
var
done: Boolean;
begin
inherited drawRect(dirtyRect);
if CheckMainThread and Assigned(callback) then
callback.Draw(NSGraphicsContext.currentContext, bounds, dirtyRect);
done:= self.lclCallCustomDraw( -1, -1, self.bounds.size, dirtyRect );
if done then begin
// the Cocoa default drawing cannot be skipped in NSTableView,
// we can only hide the SubviewViews to get the same effect.
hideAllSubviews( self );
end else begin
inherited drawRect( dirtyRect );
end;
end;
function TCocoaTableListView.getIndexOfColumn(ACol: NSTableColumn): Integer;
@ -951,6 +1041,28 @@ begin
Result:= _checkBox;
end;
procedure TCocoaTableListItem.drawRect(dirtyRect: NSRect);
var
row: Integer;
col: Integer;
done: Boolean;
begin
row:= _tableView.rowForView( self );
col:= _tableView.columnForView( self );
done:= TCocoaTableListView(_tableView).lclCallCustomDraw(
row, col, self.bounds.size, dirtyRect );
if done then begin
// the Cocoa default drawing cannot be skipped in NSTableView,
// we can only hide the CellViews to get the same effect.
// in the Lazarus IDE, there is a ListView with OnCustomDrawItem
// in Perferences-Component Palette.
hideAllSubviews( self );
end else begin
inherited drawRect(dirtyRect);
end;
end;
procedure TCocoaTableListItem.createTextField;
var
fieldControl: NSTextField;
@ -1037,7 +1149,7 @@ begin
lclcb:= tv.callback;
if Assigned(_checkBox) then begin
lclcb.GetItemCheckedAt( row, 0, checkedValue );
lclcb.GetItemCheckedAt( row, checkedValue );
_checkBox.setState( checkedValue );
end;
@ -1181,7 +1293,7 @@ begin
if not Assigned(callback) then Exit;
row := rowForView(sender.superview);
callback.SetItemCheckedAt(row, 0, sender.state);
callback.SetItemCheckedAt(row, sender.state);
if sender.state = NSOnState then begin
self.selectOneItemByIndex(row, True);
self.window.makeFirstResponder( self );
@ -1437,8 +1549,6 @@ begin
begin
if Assigned(item.imageView) then begin
frame:= item.imageView.frame;
frame.origin.x:= frame.origin.x + item.frame.origin.x;
frame.origin.y:= frame.origin.y + item.frame.origin.y;
end;
end;
end;
@ -1636,7 +1746,7 @@ begin
{lvpHideSelection,
lvpHotTrack,}
lvpMultiSelect: _tableView.setAllowsMultipleSelection(AIsSet);
{lvpOwnerDraw,}
lvpOwnerDraw: _tableView.isOwnerDraw:= AIsSet;
lvpReadOnly: _tableView.readOnly := AIsSet;
{ lvpRowSelect,}
lvpShowColumnHeaders:

View File

@ -54,8 +54,8 @@ type
public
checklist: TCustomCheckListBox;
constructor Create(AOwner: NSObject; ATarget: TWinControl; AHandleView: NSView); override;
function GetItemCheckedAt(ARow, ACol: Integer; var CheckState: Integer): Boolean; override;
procedure SetItemCheckedAt(ARow, ACol: Integer; CheckState: Integer); override;
function GetItemCheckedAt( row: Integer; var CheckState: Integer): Boolean; override;
procedure SetItemCheckedAt( row: Integer; CheckState: Integer); override;
function GetCheckState(Index: Integer; var AState: Integer): Boolean;
function SetCheckState(Index: Integer; AState: Integer; InvalidateCocoa: Boolean = true): Boolean;
@ -151,19 +151,19 @@ begin
checklist := TCustomCheckListBox(ATarget);
end;
function TLCLCheckboxListCallback.GetItemCheckedAt(ARow, ACol: Integer;
function TLCLCheckboxListCallback.GetItemCheckedAt( row: Integer;
var CheckState: Integer): Boolean;
begin
Result := GetCheckState(Arow, CheckState);
Result := GetCheckState(row, CheckState);
end;
procedure TLCLCheckboxListCallback.SetItemCheckedAt(ARow, ACol: Integer;
procedure TLCLCheckboxListCallback.SetItemCheckedAt( row: Integer;
CheckState: Integer);
var
changed : Boolean;
begin
changed := SetCheckState(ARow, CheckState, false); // returns true, if changed!s
if changed then LCLSendChangedMsg(Target, ARow);
changed := SetCheckState(row, CheckState, false); // returns true, if changed!s
if changed then LCLSendChangedMsg(Target, row);
end;
function TLCLCheckboxListCallback.GetCheckState(Index: Integer; var AState: Integer): Boolean;

View File

@ -280,14 +280,18 @@ type
function ItemsCount: Integer; virtual;
function GetImageListType(out lvil: TListViewImageList): Boolean; virtual;
function GetItemTextAt(ARow, ACol: Integer; var Text: String): Boolean; virtual;
function GetItemCheckedAt(ARow, ACol: Integer; var isChecked: Integer): Boolean; virtual;
function GetItemCheckedAt( row: Integer; var isChecked: Integer): Boolean; virtual;
function GetItemImageAt(ARow, ACol: Integer; var imgIdx: Integer): Boolean; virtual;
function GetImageFromIndex(imgIdx: Integer): NSImage; virtual;
procedure SetItemTextAt(ARow, ACol: Integer; const Text: String); virtual;
procedure SetItemCheckedAt(ARow, ACol: Integer; isChecked: Integer); virtual;
procedure SetItemCheckedAt( row: Integer; isChecked: Integer); virtual;
function shouldSelectionChange(NewSel: Integer): Boolean; virtual;
procedure ColumnClicked(ACol: Integer); virtual;
function DrawRow(rowidx: Integer; ctx: TCocoaContext; const r: TRect; state: TOwnerDrawState): Boolean; virtual;
function drawItem( row: Integer; ctx: TCocoaContext; const r: TRect;
state: TOwnerDrawState ): Boolean; virtual;
function customDraw( row: Integer; col: Integer;
ctx: TCocoaContext; state: TCustomDrawState ): Boolean; virtual;
function isCustomDrawSupported: Boolean; virtual;
procedure GetRowHeight(rowidx: integer; var h: Integer); virtual;
function GetBorderStyle: TBorderStyle;
function onAddSubview(aView: NSView): Boolean;
@ -625,7 +629,7 @@ begin
if Result then Text := strings[ARow];
end;
function TLCLListBoxCallback.GetItemCheckedAt(ARow, ACol: Integer;
function TLCLListBoxCallback.GetItemCheckedAt( row: Integer;
var isChecked: Integer): Boolean;
begin
Result := false;
@ -648,7 +652,7 @@ begin
// todo:
end;
procedure TLCLListBoxCallback.SetItemCheckedAt(ARow, ACol: Integer;
procedure TLCLListBoxCallback.SetItemCheckedAt( row: Integer;
isChecked: Integer);
begin
// do nothing
@ -665,8 +669,8 @@ begin
// not needed
end;
function TLCLListBoxCallback.DrawRow(rowidx: Integer; ctx: TCocoaContext;
const r: TRect; state: TOwnerDrawState): Boolean;
function TLCLListBoxCallback.drawItem( row: Integer; ctx: TCocoaContext;
const r: TRect; state: TOwnerDrawState ): Boolean;
var
DrawStruct: TDrawListItemStruct;
begin
@ -677,11 +681,22 @@ begin
DrawStruct.ItemState := state;
DrawStruct.Area := r;
DrawStruct.DC := HDC(ctx);
DrawStruct.ItemID := rowIdx;
DrawStruct.ItemID := row;
LCLSendDrawListItemMsg(Target, @DrawStruct);
Result:= True;
end;
function TLCLListBoxCallback.customDraw(row: Integer; col: Integer;
ctx: TCocoaContext; state: TCustomDrawState ): Boolean;
begin
Result:= False;
end;
function TLCLListBoxCallback.isCustomDrawSupported: Boolean;
begin
Result:= False;
end;
procedure TLCLListBoxCallback.GetRowHeight(rowidx: integer; var h: Integer);
begin
if TCustomListBox(Target).Style = lbOwnerDrawVariable then