You are not logged in.
Pages: 1
I propose moving the setting of fInHeaderOrFooter in mORMotReport from DoHeaderFooterInternal() to DoHeader and DoFooter;
The reason for this is so that we can be in that mode while the event handlers StartPageHeader, EndPageHeader, StartPageFooter, EndPageFooter can be called when in Header/Footer mode.
This enables unlimited flexibility with customer headers and footers with DrawTextAt etc. Examples of this use would be in the generation of customer account statements or invoices.
Once again thanks for a great product.
Suggested code below of DoHeaderFooterInternal and DoHeader and DoFooter.
procedure TGDIPages.DoHeader;
begin
fHeaderDone := true;
if (fHeaderLines.Count = 0) then exit;
SaveLayout;
fInHeaderOrFooter := true;
try
if Assigned(fStartPageHeader) then
fStartPageHeader(Self);
Font.Color := clBlack;
DoHeaderFooterInternal(fHeaderLines);
if Assigned(fEndPageHeader) then
fEndPageHeader(Self);
GetLineHeight;
inc(fCurrentYPos,fLineHeight shr 2); // add a small header gap
fHeaderHeight := fCurrentYPos-fPageMarginsPx.Top;
finally
fInHeaderOrFooter := false;
RestoreSavedLayout;
end;
end;
procedure TGDIPages.DoFooter;
begin
if (fFooterLines.Count = 0) then exit;
SaveLayout;
fInHeaderOrFooter := true;
try
fCurrentYPos :=
fPhysicalSizePx.y - fPageMarginsPx.bottom - fFooterHeight + fFooterGap;
if Assigned(fStartPageFooter) then
fStartPageFooter(Self);
DoHeaderFooterInternal(fFooterLines);
if Assigned(fEndPageFooter) then
fEndPageFooter(Self);
finally
fInHeaderOrFooter := false;
RestoreSavedLayout;
end;
end;
procedure TGDIPages.DoHeaderFooterInternal(Lines: TObjectList);
var i: integer;
begin
SaveLayout;
try
for i := 0 to Lines.Count -1 do
with THeaderFooter(Lines[i]) do
begin
SavedState := State;
PrintFormattedLine(Text, State.Flags);
end;
finally
RestoreSavedLayout;
end;
end;
Regards,
Kevin.
Done.
Hi,
I would like to add the ability for the ribbon to be notified when a mark sql action popup menu item has been clicked by the user. This is so that I can update a "mark status" that I can display on my reports to show on the report what the report contains (if an item is marked since a mark action or query has been performed I clear the mark status to "Marked Items").
What I propose is in mORMotToolbar:
TMarkActionEvent = procedure (Sender : TObject; const aRecordClass : TSqLRecordClass; aSQLAction : TSQLAction) or object;
A TSQLLister.OnMarkAction property
private
FOnMarkAction: TMarkActionEvent;
public
property OnMarkAction: TMarkActionEvent read FOnMarkAction write FOnMarkAction;
and a call to OnMarkAction event handler in TSQLLister.ActionButtonClick()
begin
if fReportDetailedIndex<0 then
exit; // avoid recursive call after GB.Down := false below
aAction := isActionButton(Sender);
case A of
actNoAction: exit;
actUnMarkAll:
// (un)marking are standard actions
TableToGrid.SetMark(A);
actMark:
if Sender.InheritsFrom(TSynToolButton) then
Btn.DoDropDown else
if Sender.InheritsFrom(TMenuItem) then begin
// actMarkAllEntries..actMarkBeforeOneYear are regrouped in
// the only one aAction=actMark button
TableToGrid.SetMark(TSQLAction(TMenuItem(Sender).Tag));
if Assigned(FOnMarkAction) then
FOnMarkAction(Sender, fClass, TSQLAction(TMenuItem(Sender).Tag));
end
else begin
etc.
Thanks,
Kevin.
Hi AB,
I don't understand the need for that, why couldn't one assume that clients locale should already be set correctly to what they need and want?
Regarding to creating a ticket, my user id only works for this forum and there is no ability to register in the synopse.info section, I cannot create a ticket unless I login as guest.
Regards,
Kevin.
Hi AB,
With the English language, they can be different format settings. For example the Australian date format is d/m/y rather than m/d/y. However in mORMoti18n.SetCurrentLanguage() the data formats are always reset to US formats. I suggest that the call to GetFormatSettings be removed or then not changing the thread locale, so that the users FormatSettings will remain intact.
Regards,
Kevin Day.
Hi Arnaud,
One more thing should Mark in the TSQLAction declarations be lower case to be consistent with the rest (I had missed that when I previously suggested the change)? I notice that "actmark" is taken off when creating the menu items but not "actMark" via use of TrimLeftLowerCase() in TSQLRecord.CaptionName().
Cheers,
Kevin Day.
Hi Arnaud,
Thank you for a great library. Thank you for allowing me to contribute.
I found a bug in the TSQLRibbon.ChangeColorScheme() method in mORMotToolbar when ChangeColorScheme() is called with just the ColorScheme and no other parameters. An access violation is thrown on the line
if (Body<>nil) and Body.AdvOfficePagerStyler.InheritsFrom(TAdvOfficePagerOfficeStyler) then
TAdvOfficePagerOfficeStyler(Body.AdvOfficePagerStyler).Style := TOfficePagerStyle(StatusBarStyler.Style);
The issue is that in this case the StatusBarStyler is nil
I propose a fix to the above line to:
if (Body <> nil) and Body.AdvOfficePagerStyler.InheritsFrom(TAdvOfficePagerOfficeStyler) then
TAdvOfficePagerOfficeStyler(Body.AdvOfficePagerStyler).Style := TOfficePagerStyle(StatusStyle);
Full code for this method thus would be:
procedure TSQLRibbon.ChangeColorScheme(const ColorScheme: TToolBarStyle;
PanelStyler: TAdvPanelStyler; StatusBarStyler: TAdvOfficeStatusBarOfficeStyler;
CustomStyle: TMemoryStream);
var
PreviewStyle: TPreviewMenuStyle;
StatusStyle: TOfficeStatusBarStyle;
TBStyler: TAdvToolBarOfficeStyler;
i, curr: integer;
C: TComponent;
begin
if not fToolBar.ToolBarStyler.InheritsFrom(TAdvToolBarOfficeStyler) then
exit;
TBStyler := TAdvToolBarOfficeStyler(ToolBar.ToolBarStyler);
if TBStyler = nil then
exit;
TBStyler.Style := ColorScheme;
case ColorScheme of // bulky TMS styles don't match :(
bsCustom: begin
if CustomStyle <> nil then begin
CustomStyle.Seek(0, soFromBeginning);
CustomStyle.ReadComponent(TBStyler);
end;
PreviewStyle := AdvPreviewMenuStylers.psOffice2003Olive;
StatusStyle := AdvOfficeStatusBarStylers.psOffice2003Olive;
end;
bsOffice2007Silver: begin
PreviewStyle := AdvPreviewMenuStylers.psOffice2007Silver;
StatusStyle := AdvOfficeStatusBarStylers.psOffice2007Silver;
end;
bsOfficeXP: begin
PreviewStyle := AdvPreviewMenuStylers.psOfficeXP;
StatusStyle := AdvOfficeStatusBarStylers.psWindowsXP;
end;
bsWindowsVista .. high(TToolBarStyle): begin
PreviewStyle := TPreviewMenuStyle(ColorScheme);
StatusStyle := TOfficeStatusBarStyle(pred(ColorScheme));
end;
else begin
PreviewStyle := TPreviewMenuStyle(ColorScheme);
StatusStyle := TOfficeStatusBarStyle(ColorScheme);
end;
end;
if (PreviewMenu <> nil) and PreviewMenu.Styler.InheritsFrom(TAdvPreviewMenuOfficeStyler) then
TAdvPreviewMenuOfficeStyler(PreviewMenu.Styler).Style := PreviewStyle;
if StatusBarStyler <> nil then
StatusBarStyler.Style := StatusStyle;
if PanelStyler <> nil then
PanelStyler.Style := TToolBarStyleToPanel[ColorScheme];
TBStyler.CaptionAppearance.Assign(TBStyler.GroupAppearance.CaptionAppearance);
with TBStyler.GroupAppearance.TabAppearance do begin
TBStyler.PagerCaption.TextColor := TextColor;
TBStyler.PagerCaption.TextColorExtended := TextColorSelected;
end;
//** Change here
if (Body <> nil) and Body.AdvOfficePagerStyler.InheritsFrom(TAdvOfficePagerOfficeStyler) then
TAdvOfficePagerOfficeStyler(Body.AdvOfficePagerStyler).Style := TOfficePagerStyle(StatusStyle);
// update colors for windows
for i := 0 to Application.ComponentCount - 1 do begin
C := Application.Components[i];
if C.InheritsFrom(TCustomForm) then
SetStyle(C, TBStyler); // will set style for all embedded components
end;
// update report colors on every ribbon page
curr := fToolBar.ActivePageIndex;
for i := 0 to high(Page) do
with Page[i] do begin
if Report <> nil then
Report.Color := TBStyler.QATAppearance.ColorTo;
if i = curr then
List.Invalidate; // repaint list first row with new colors
end;
if Form <> nil then
Form.Invalidate; // whole form redraw
end;
Thank you Arnaud.
Additional to this we also need to patch TSQLLister.SetToolBar() in mORMotToolBar
to change
if (A2<actmarkOlderThanOneDay) or (A2>actmarkOlderThanOneYear) or
to
if (A2<actmarkForToday) or (A2>actmarkOlderThanOneYear) or
Full Code:
function TSQLLister.SetToolBar(const aToolBarName: string; const aActions;
ActionIsNotButton: pointer): TSynToolBar;
var TypeName: PShortString;
A,iTB,iGB,iM,img: integer;
GB: TSynToolButton;
iAction: cardinal;
M: TMenuItem;
EN: boolean;
A2: TSQLAction;
ActionNames: TStringDynArray;
begin
result := nil;
if fPage=nil then
exit;
// on existing Toolbar: update its buttons from aActions, and exit
for iTB := 0 to fPage.ToolBarCount-1 do
// test exact match, not with SameText(), since Caption can be translated
if fPage.ToolBars[iTB].Caption=aToolBarName then begin
result := fPage.ToolBars[iTB];
for iGB := 0 to result.ComponentCount-1 do begin
GB := TSynToolButton(result.Components[iGB]);
if isActionButton(GB)<>0 then begin
img := GB.ImageIndex;
EN := GetBit(aActions,img+1);
GB.Enabled := EN; // enable or disable buttons
for iM := 0 to fMenu.Items.Count-1 do
with fMenu.Items[iM] do
if ImageIndex=img then
Enabled := EN; // enable or disable popup menu item
end;
end;
break;
end;
if result<>nil then
exit; // we have found the toolbar
// no Toolbar: create one with its buttons; also create associated popup menu
EN := false;
for A := 0 to fActionMax do
if GetBit(aActions,A) then begin
EN := true;
break;
end;
if not EN then
exit; // aActions=[] -> no toolbar to add
if fMenu=nil then begin
fMenu := TSynPopupMenu.Create(fGrid);
fMenu.Images := ImageList16;
end;
result := fPage.CreateToolBar;
try
{$ifdef USETMSPACK}
result.BeginUpdate;
result.AutoPositionControls := true;
result.ShowOptionIndicator := false;
result.AutoSize := true;
{$else}
result.Images := ImageList32;
{$endif}
result.Caption := aToolBarName;
SetLength(ActionNames,fActionMax+1);
TypeName := @fClient.Model.Actions^.NameList;
for iAction := 0 to fActionMax do begin
ActionNames[iAction] := fClass.CaptionNameFromRTTI(TypeName); // expanded caption
inc(PByte(TypeName),ord(TypeName^[0])+1); // next enumerate value name
{$ifndef USETMSPACK}
end;
for iAction := fActionMax downto 0 do begin // TToolBar adds at 1st position
{$endif}
if GetBit(aActions,iAction) then // is this enumerate value inside aActions?
with result.CreateToolButton(ActionButtonClick,iAction,1,ActionNames[iAction],
ActionHints,fShortCutUsed,60,ImageList32) do begin
if GetBit(ActionIsNotButton,iAction) then
Style := bsCheck;
// create associated sub menu entry
if Style<>bsCheck then begin
NewMenuItem(fMenu,Caption,iAction-1);
if TSQLAction(iAction)=actMark then
// actMarkAllEntries..actMarkBeforeOneYear are regrouped in
// an only one aAction=actMark
with PTypeInfo(TypeInfo(TSQLAction))^.EnumBaseType^ do
for A2 := actMarkAllEntries to actmarkInverse do
// if (A2<actmarkOlderThanOneDay) or (A2>actmarkOlderThanOneYear) or
if (A2<actmarkForToday) or (A2>actmarkOlderThanOneYear) or
(TableToGrid.FieldIndexTimeLogForMark>=0) then begin
if A2=actmarkInverse then
CreateSubMenuItem('-',iAction,nil);
CreateSubMenuItem(fClass.CaptionNameFromRTTI(GetEnumName(A2)),
iAction,nil,iAction-1,integer(A2));
end;
end;
end;
end;
M := TMenuItem.Create(fMenu);
M.Caption := '-';
fMenu.Items.Add(M);
finally
{$ifdef USETMSPACK}
result.EndUpdate;
{$endif}
end;
end;
Hi There,
I am not sure how the best way to contribute code. I would like to add marked actions so that TSQLAction in unit mORMot.pas would now look like:
/// standard actions for User Interface generation
TSQLAction = (
/// action not defined
actNoAction,
/// Mark rows (standard action)
// - display sub-menu with actmarkAllEntries..actmarkBeforeOneYear items
actMark,
/// UnMark all rows (standard action)
actUnmarkAll,
/// Mark all rows
actmarkAllEntries,
/// Mark rows for today
actMarkForToday,
/// Mark rows for This Week
actMarkForThisWeek,
/// Mark rows for this month
actMarkForThisMonth,
/// Mark rows for today
actMarkForYestday,
/// Mark rows for Last Week
actMarkForLastWeek,
/// Mark rows for Last month
actMarkForLastMonth,
/// Mark rows After one day
actmarkOlderThanOneDay,
/// Mark rows older than one week
actmarkOlderThanOneWeek,
/// Mark rows older than one month
actmarkOlderThanOneMonth,
/// Mark rows older than one half year
actmarkOlderThanSixMonths,
/// Mark rows older than one year
actmarkOlderThanOneYear,
/// Inverse Mark values (ON->OFF, OFF->ON)
actmarkInverse);
Then also in mORMotUI.pas - TSQLTableToGrid.SetMark() would look like:
procedure TSQLTableToGrid.SetMark(aAction: TSQLAction);
var
i: integer;
V, Time: Int64;
Time2: Int64;
const
DIFFTIME: array [actMarkOlderThanOneDay .. actMarkOlderThanOneYear] of double = (1, 7, 31, 183,
365); // 183 = more or less half a year
begin
if NotDefined then
Exit;
with TDrawGrid(Owner) do
case aAction of
actmarkAllEntries:
for i := 1 to RowCount do
Marked[i] := true;
actUnmarkAll:
if fMarked <> nil then
Finalize(fMarked);
actmarkInverse:
for i := 1 to RowCount do
Marked[i] := not Marked[i];
actMarkForToday, actMarkForThisWeek, actMarkForThisMonth, actMarkForYestday,
actMarkForLastWeek, actMarkForLastMonth: begin
case aAction of
actMarkForToday: begin
PTimeLogBits(@Time)^.From(Date, true);
PTimeLogBits(@Time2)^.From(Date + 1, true);
end;
actMarkForThisWeek: begin
PTimeLogBits(@Time)^.From(StartOfTheWeek(Date), true);
PTimeLogBits(@Time2)^.From(EndOfTheWeek(Date) + 1, true);
end;
actMarkForThisMonth: begin
PTimeLogBits(@Time)^.From(StartOfTheMonth(Date), true);
PTimeLogBits(@Time2)^.From(EndOfTheMonth(Date) + 1, true);
end;
actMarkForYestday: begin
PTimeLogBits(@Time)^.From(Date - 1, true);
PTimeLogBits(@Time2)^.From(Date, true);
end;
actMarkForLastWeek: begin
PTimeLogBits(@Time)^.From(IncWeek(StartOfTheWeek(Date), -1), true);
PTimeLogBits(@Time2)^.From(StartOfTheWeek(Date), true);
end;
actMarkForLastMonth: begin
PTimeLogBits(@Time)^.From(IncMonth(StartOfTheMonth(Date), -1), true);
PTimeLogBits(@Time2)^.From(StartOfTheMonth(Date), true);
end;
end;
if FieldIndexTimeLogForMark >= 0 then begin
for i := 1 to RowCount do begin
SetInt64(Table.Get(i, fFieldIndexTimeLogForMark), V);
if (V >= Time) and (V <= Time2) then
Marked[i] := true;
end;
end;
end;
actMarkOlderThanOneDay .. actMarkOlderThanOneYear:
if FieldIndexTimeLogForMark >= 0 then begin
// use TDateTime calculation because TTimeLog is not duration compatible
PTimeLogBits(@Time)^.From(Now - DIFFTIME[aAction], true);
for i := 1 to RowCount do begin
SetInt64(Table.Get(i, fFieldIndexTimeLogForMark), V);
if (V > 0) and (V <= Time) then
Marked[i] := true;
end;
end;
else
Exit;
end;
TDrawGrid(Owner).Invalidate; // refresh screen
end;
Thanks. mORMot is a great framework!
Kevin Day
Hi Arnaud,
I suggest the following changes to Tgdipages to allow one to have lines around totals on columns such that there is a line close above the total and one close below the total. I propose the following additions / changes for this:
Firstly in private section - overload LineInternal() as below
procedure LineInternal(start, finish : integer; doubleline : boolean); overload;
procedure LineInternal(aty, start, finish : integer; doubleline : boolean); overload;
New LineInternal() bodies
procedure TGDIPages.LineInternal(start, finish : integer; doubleline : boolean);
begin
LineInternal(fCurrentYPos + (GetLineHeight shr 1), start, finish, doubleline);
end;
procedure TGDIPages.LineInternal(aty, start, finish : integer; doubleline : boolean);
var Y: integer;
begin
if (Self <> nil) and (fCanvas <> nil) then
with fCanvas do begin
Pen.Width := MulDiv(fDefaultLineWidth, Self.Font.size, 8);
if fsBold in Self.Font.style then Pen.Width := Pen.Width + 1;
if doubleline then begin
Y := aty - (Pen.Width);
MoveTo(start, Y);
LineTo(finish, Y);
MoveTo(start, Y + (Pen.Width * 2));
LineTo(finish, Y + (Pen.Width * 2));
end else begin
Y := aty - (Pen.Width shr 1);
MoveTo(start, Y);
LineTo(finish, Y);
end;
end;
end;
Then finally the new public method DrawColumnLine()
procedure TGDIPages.DrawColumnLine(ColIndex: integer; aAtTop: boolean;
aDoDoubleLine: boolean);
var Y: integer;
begin
if aAtTop then Y := fCurrentYPos - 1
else Y := fCurrentYPos + fLineHeight + 1;
with fColumns[ColIndex] do LineInternal(Y, ColLeft, ColRight, aDoDoubleLine);
end;
Hi,
I found a bug in SynCommons.IdemPCharU().
The corrected code below:
function IdemPCharU(p, up: PUTF8Char): boolean;
begin
result := false;
if (p=nil) or (up=nil) then
exit;
while up^<>#0 do begin
if GetNextUTF8Upper(p)<>ord(up^) then
exit;
inc(up);
// inc(p); already incremented by GetNextUTF8Upper()
end;
result := true;
end;
Cheers,
Kevin Day
Pages: 1