Link to: header | source 2 | source 3 | source
4 | tables
directory
Copyright Turtle Creek Software 1996-2006. All Rights Reserved.
Comments
CReportTable
This class manages accounting report tables for the Goldenseal accounting software,
small business management software, construction
project management software and
construction estimating software.
It's a table that appears in a report or print form.
SUPERCLASS = CDataTable
*********************
CLASS DETAILS
The rows of the table are usually data from one object, though there are
specialized tables that show other info in each row, and even simple tables
can have specialty rows-- titles, subtotals, etc.
The columns of the table are usually field members-- usually for an object.
However there are some specialty tables that fetch other types of data.
Data can be shown as a simple list, or items can be grouped into categories
or other breakdowns, with or without subtotals.
This class is a CReportInfoOwner, so it can easily restrict data to a date range,
item range, or items matching a particular field value.
Note that this class used to inherit from CObjectDataTable. They have diverged 4
enormously since the split.
Right now CReportTable fill in all data cells, just like a regular
spreadsheet table. That means that large reports can run out of memory,
but it allows us to easily track subtotals and totals, and to do sorts.
With our current memory setup as of 1/11/01, reports with
about 2,000 items are ok, but we can't go much higher than that.
*********************
CREATING A REPORT
Report generation requires several basic steps, to handle all the quirks of
turning general object data into many different types of reports.
1. Basic prep- happens here. Title row, etc.
2. Set up an intermediate TReportRowArray to store row contents. How this is
done depends on the report type:
A. SIMPLE LIST- we can just loop thru objects to fill in a TReportRowArray
B. TIME BREAKDOWN- we first create a TBreakdownInfoArray with a subarray for
each time period. Then we fill in object id's for each period. This requires
looping through all objects once. Then we use FillRowsFromBreakdownArray
to fill in a TReportRowArray from the breakdown array.
C. MATCH BREAKDOWN- we first create a TBreakdownInfoArray with a subarray for
each match value. Then we fill in object id's for each period. This requires
looping through all objects once. Then we use FillRowsFromBreakdownArray
to fill in a TReportRowArray from the breakdown array.
D. TWO LEVEL BREAKDOWNS-- we first fill in a TTwoLevelArray with basic data,
then call FillRowsFromTwoLevelArray to fill in a TReportRowArray.
E. SPECIAL REPORTS- we fetch a TReportRowArray from an object, which has a special
way of filling it in.
3. Fill in the table from the TReportRowArray. For most reports we loop through each
object in the array, and fetch data to fill into table columns.
A. For a regular report, each line in the array corresponds to a line in the display table.
B. For a condensed report (with subtotals only), we only show some lines from the array.
C. For special reports, data may be filled in via some custom method.
Constructor
/*********************************************************************************
stream constructor- called when the report table is created from a layout
*********************************************************************************/
CReportTable::CReportTable(const SPaneInfo &inPaneInfo,
const SViewInfo &inViewInfo,
CInputStream &stream)
: CDataTable(inPaneInfo, inViewInfo)
{
FormatType format = stream.GetFormat();
InitReportArrayTable();
// read in the general table info from the stream (stuff written
// in CTablePane::WriteToStream). We may not use it all,
// but we must read it in.
UInt8 rowHeight, numCols, expansion, textColor, titleColor,
gridlineColor, gridlineStyle, colorBlockSize, expansion2;
CTCS_RGBColor backgroundColor, altBackgroundColor;
stream.ReadEnum(&numCols); // rev TCS 3/14/00
stream.ReadEnum(&mTitleBarHeight);
stream.ReadEnum(&rowHeight);
if (format < cFormat10) // TCS 9/25/02
expansion = 0;
else
stream.ReadEnum(&expansion);
stream.ReadDBid(&mReportClassID);
if (format < cFormat10) // TCS 9/25/02
{
textColor = 0;
titleColor = 0;
gridlineColor = 0;
gridlineStyle = 0;
colorBlockSize = 0;
expansion2 = 0;
backgroundColor = CTCS_RGBColor::GetWhiteColor();
altBackgroundColor = CTCS_RGBColor::GetWhiteColor();
}
else
{
stream.ReadEnum(&textColor);
stream.ReadEnum(&titleColor);
stream.ReadEnum(&gridlineColor);
stream.ReadEnum(&gridlineStyle);
stream.ReadEnum(&colorBlockSize);
stream.ReadEnum(&expansion2);
stream.ReadData(&backgroundColor, sizeof(CTCS_RGBColor));
stream.ReadData(&altBackgroundColor, sizeof(CTCS_RGBColor));
#if TCS_FOR_WINDOWS // TCS 10/10/02
TCS_BlockReverse(&backgroundColor, sizeof(CTCS_RGBColor)/3, 3);
TCS_BlockReverse(&altBackgroundColor, sizeof(CTCS_RGBColor)/3, 3);
#endif
}
// set up the number of columns. Note that we include three extra
// columns, where we will store row type, object class ID and object ID
mNumDisplayCols = numCols;
SetNumCols(mNumDisplayCols + 3, cDontRedraw);
// set up other table data
mRowHeight = rowHeight;
// now loop through the columns and read in the basic column info
SLETableColInfo colInfo;
for (SInt32 col = 1; col <= numCols; col++)
{
if (format < cFormat6)
{ // it's the older format, so read from stream individually
stream.ReadLong(colInfo.tag); // rev TCS 3/14/00
stream.ReadShort(colInfo.width);
stream.ReadTextInfo(colInfo.textInfo);
stream.ReadTextInfo(colInfo.titleTextInfo);
// now initialize other info
colInfo.colTitle[0] = nil;
colInfo.borderInfo.SetHasBorders(false);
}
else
{ // get the column information
stream.ReadData(&colInfo, sizeof(SLETableColInfo));
#if TCS_FOR_WINDOWS
// mfs_kk 29jul2k2
// In case of windows
// Reverse the bytes after reading from stream
TCS_BlockReverse(&colInfo.tag, sizeof(colInfo.tag));
TCS_BlockReverse(&colInfo.width, sizeof(colInfo.width));
// mFont is private member of class CTCS_TextInfo
// so getting the font, reversing it and then setting it
SInt16 tempFont = colInfo.textInfo.GetTextFont();
TCS_BlockReverse(&tempFont, sizeof(SInt16));
colInfo.textInfo.SetTextFont(tempFont);
tempFont = colInfo.titleTextInfo.GetTextFont();
TCS_BlockReverse(&tempFont, sizeof(SInt16));
colInfo.titleTextInfo.SetTextFont(tempFont);
#endif
}
// add to our column array
mColumns.Append(colInfo);
// update the column width
SetColWidth(col, colInfo.width, cDontRedraw);
}
// we need to set our descriptor from the table info
// it's ok if it's nil
mDescriptor = DB_ClassDescriptor::GetExtendedClassDescriptor(mReportClassID);
// read data that was written via CReportTablePane::WriteToStream
if (format > cFormat7) // rev TCS 9/27/00
{
SReportTableFormatInfo formatInfo;
stream.ReadData(&formatInfo, sizeof(SReportTableFormatInfo));
#if TCS_FOR_WINDOWS
// mfs_kk 29jul2k2
// In case of windows
// Reverse the bytes after reading from stream
TCS_BlockReverse(&formatInfo.breakdownTag, sizeof(formatInfo.breakdownTag));
TCS_BlockReverse(&formatInfo.matchTag, sizeof(formatInfo.matchTag));
TCS_BlockReverse(&formatInfo.matchValue, sizeof(formatInfo.matchValue));
#endif
mDefaultBreakdownTag = formatInfo.breakdownTag;
mDefaultMatchTag = formatInfo.matchTag;
mDefaultMatchValue = formatInfo.matchValue;
mItemRange = formatInfo.itemRange;
mGroup = formatInfo.group;
mDateFieldType = formatInfo.dateChoice;
mAccountClassID = formatInfo.accountClass;
mBoldSubtotals = formatInfo.boldSubtotals; // TCS 12/29/00
mBoldTotals = formatInfo.boldTotals;
mStartsCondensed = formatInfo.startsCondensed; // TCS 5/30/01
mDateRange = formatInfo.dateRange; // TCS 1/4/02
mHideOverhead = formatInfo.hideOverhead; // TCS 11/8/03
}
else
{
stream.ReadTag(&mDefaultBreakdownTag); // rewrite TCS 3/14/00
stream.ReadTag(&mDefaultMatchTag);
stream.ReadEnum(&mItemRange);
stream.ReadEnum(&mGroup);
stream.ReadEnum(&mDateFieldType);
mAccountClassID = id_CustomerAccount;
mDefaultMatchValue = 0;
mBoldSubtotals = false;
mBoldTotals = false;
mStartsCondensed = false;
mDateRange = date_all; // TCS 1/4/02
mHideOverhead = false;
}
mCondensed = mStartsCondensed; // TCS 7/9/02
mBreakdownTag = mDefaultBreakdownTag;
mMatchTag = mDefaultMatchTag;
// we also read in match value and table info if format supports it TCS 8/3/99
if (format > cFormat6)
{
if (format < cFormat8) // rev TCS 9/27/00
stream.ReadLong(mDefaultMatchValue);
stream.ReadData(&mReportTableInfo, sizeof(SReportTableInfo)); // rev TCS 9/24/99
}
else
{
mDefaultMatchValue = 0;
mReportTableInfo.showTitles = true;
mReportTableInfo.showZeroItems = false;
mReportTableInfo.tableType = 0;
}
mMatchValue = mDefaultMatchValue;
SReportTableColInfo reportColInfo;
reportColInfo.useSubTotal = false;
reportColInfo.useGrandTotal = false;
reportColInfo.dataRounding = calc_roundpennies;
reportColInfo.totalRounding = calc_roundpennies;
reportColInfo.titleAlign = just_right;
reportColInfo.bodyAlign = just_right;
reportColInfo.titleText = style_plain;
reportColInfo.bodyText = style_plain;
SColDataInfo colData;
colData.subTotal = 0;
colData.groupTotal = 0; // TCS 1/24/02
colData.grandTotal = 0;
// read in report col info if format supports it 8/6/99
for (SInt32 col = 1; col <= mNumDisplayCols; col++)
{
if (format > cFormat6)
{ // read in stored data
stream.ReadData(&reportColInfo, sizeof(reportColInfo));
// check if we have any subtotals or grandtotals
if (reportColInfo.useSubTotal)
mShowSubTotal = true;
if (reportColInfo.useGrandTotal)
mShowGrandTotal = true;
}
colData.showSubtotal = reportColInfo.useSubTotal;
colData.showTotal = reportColInfo.useGrandTotal;
colData.dataRounding = reportColInfo.dataRounding; // 8/8/99
colData.totalRounding = reportColInfo.totalRounding;
colData.titleAlign = reportColInfo.titleAlign; // TCS 3/17/00
colData.bodyAlign = reportColInfo.bodyAlign;
colData.titleText = reportColInfo.titleText;
colData.bodyText = reportColInfo.bodyText;
colData.fieldType = DB_ClassDescriptor::GetFieldTypeForTag(mReportClassID, GetColTag(col));
// fill in data
mColumnData.Append(colData);
}
// initialize our memory tracking bit
mMemoryIsShort = false;
// make sure we listen to the grow zone, so we hear when
// memory is getting short TCS 8/21/01
LGrowZone* growZone = LGrowZone::GetGrowZone();
TCS_FailNILMsg(growZone, "Oops, no grow zone in CReportTable constructor!");
growZone->AddListener(this);
}
/*********************************************************************************
destructor
*********************************************************************************/
CReportTable::~CReportTable()
{
ClearJobCostCompareArray(); // TCS 1/29/02
}
Source Code
/*********************************************************************************
InitReportArrayTable
private initializer
*********************************************************************************/
void CReportTable::InitReportArrayTable()
{
mDescriptor = nil;
mItemRange = records_activeitems;
mStartDate.SetToToday();
mEndDate.SetToToday();
mBreakdownTag = 0;
mDefaultBreakdownTag = 0;
mTitleBarHeight = 0;
mShowSubTotal = false;
mShowGrandTotal = false;
mCalcDirty = true; // TCS 12/19/00
mHideOverhead = false; // TCS 11/8/03
mSoftCostAmount = 0; // TCS 11/8/03
mHardCostAmount = 0;
mHardCostMultiplier = 0;
mSubTotalCount = 0; // TCS 2/24/04
mGrandTotalCount = 0;
mGroupTotalCount = 0;
mFirstDataRow = 1;
mLastDataRow = 1;
}
/*********************************************************************************
ClearJobCostCompareArray TCS 1/29/02
clear any arrays allocated for job cost comparisons
*********************************************************************************/
void CReportTable::ClearJobCostCompareArray()
{
if (mJobCostCompareArray.GetCount() > 0)
{
TJobCostCompareArrayIterator iterator (mJobCostCompareArray);
SJobCostCompareInfo info;
while (iterator.Next(info))
{
TCS_Forget(info.itemArray);
}
}
mJobCostCompareArray.RemoveAllItems();
}
/*********************************************************************************
Func_PrepBreakdownArray
prep a breakdown info array with an object to list under
*********************************************************************************/
Boolean CReportTable::Func_PrepBreakdownArray(DB_PersistentObject *obj,
TBreakdownInfoArray *breakdownArray)
{
if (obj)
{
TCS_FailNILMsg(breakdownArray, TCS_GetErrString(errID_BadArray));
// fill the struct
SBreakdownInfo breakdownInfo;
breakdownInfo.itemID = obj->GetDBID();
breakdownInfo.classID = obj->GetDBClassID();
breakdownInfo.itemArray = NEW TObjectInfoArray;
TCS_FailNILMsg(breakdownInfo.itemArray, TCS_GetErrString(errID_BadArray));
// stuff the struct into the array rev TCS 1/3/02
breakdownArray->Append(breakdownInfo);
}
// we want to keep going, so always return false here,
// otherwise DoUntil will terminate
return false;
}
/*********************************************************************************
Func_FillSimpleReportArray rev TCS 1/5/00, 3/28/00, 1/16/02
fill in an object info array with the ids of the objects in this iterator
*********************************************************************************/
Boolean CReportTable::Func_FillSimpleReportArray(DB_PersistentObject *object,
TReportRowArray *reportRowArray, SReportInfo *matchInfo)
{
if (object)
{
TCS_FailNILMsg(reportRowArray, TCS_GetErrString(errID_BadArray));
TCS_FailNILMsg(matchInfo, TCS_GetErrString(errID_BadArray));
// check for field match
if (!object->MatchesData(*matchInfo))
return false;
// if we got this far, the object can be included, so
// fill the struct
SReportRowInfo info;
InitializeRowInfo(info, rowtype_data);
info.itemID = object->GetDBID();
info.classID = object->GetDBClassID();
info.isSoftCost = object->IsSoftCost();
// stuff the struct into the array
reportRowArray->Append(info);
}
// we want to keep going, so always return false here,
// otherwise DoUntil will terminate
return false;
}
/*********************************************************************************
Func_FillBreakdownArray
fill in a breakdown info array with the ids of the objects in this iterator
*********************************************************************************/
Boolean CReportTable::Func_FillBreakdownArray(DB_PersistentObject *object,
TBreakdownInfoArray *breakdownArray, SReportInfo *matchInfo,
TagType *breakdownTag, DBLongClass *accountClass)
{
if (object)
{
TCS_FailNILMsg(breakdownArray, TCS_GetErrString(errID_BadArray));
TCS_FailNILMsg(matchInfo, TCS_GetErrString(errID_BadFunction));
TCS_FailNILMsg(breakdownTag, TCS_GetErrString(errID_BadFunction));
TCS_FailNILMsg(accountClass, TCS_GetErrString(errID_BadFunction));
// for certain breakdowns, we restrict classes TCS 3/28/00
if (*breakdownTag == tag_mainaccount) // main account
{
if (object->GetMainAccountClass() != *accountClass)
return false;
}
else if (*breakdownTag == tag_secondaccount) // second account
{
if (object->GetSecondAccountClass() != *accountClass)
return false;
}
else if (*breakdownTag == tag_job) // job account
{
if (object->GetJobClass() != *accountClass)
return false;
}
// check for field match
if (!object->MatchesData(*matchInfo))
return false;
// if we got this far, the object can be included, so
// fill the struct
SObjectInfo info; // rev TCS 4/18/02
info.itemID = object->GetDBID();
info.classID = object->GetDBClassID();
info.transactionType = 0;
SInt32 matchValue = 0;
// fetch the data value that determines breakdown location
if (*breakdownTag) // rev TCS 11/18/02
object->GetMemberValue(*breakdownTag, type_long, &matchValue);
else
object->GetMemberValue(matchInfo->breakdownTag, type_long, &matchValue);
// find the item in the list
SBreakdownInfo breakdownInfo;
TBreakdownInfoArrayIterator iterator(*breakdownArray);
while (iterator.Next(breakdownInfo))
{
if (matchValue == breakdownInfo.itemID)
{
breakdownInfo.itemArray->Append(info);
return false;
}
}
// if we got this far it's unallocated
// so add it to the last item TCS 8/6/99
breakdownInfo.itemArray->Append(info);
}
// we want to keep going, so always return false here,
// otherwise DoUntil will terminate
return false;
}
/*********************************************************************************
Func_FillDateBreakdownArray TCS 8/26/99
fill in a date breakdown info array with the ids of the objects in this iterator
*********************************************************************************/
Boolean CReportTable::Func_FillDateBreakdownArray(DB_PersistentObject *object,
TBreakdownInfoArray *breakdownArray, SReportInfo *matchInfo)
{
TCS_FailNILMsg(breakdownArray, TCS_GetErrString(errID_BadArray));
TCS_FailNILMsg(matchInfo, TCS_GetErrString(errID_BadTable));
TagType dateTag = DB_ClassDescriptor::GetDateTagFromType(matchInfo->dateFieldType);
if (object)
{
// check for field match
if (!object->MatchesData(*matchInfo))
return false;
// fill the struct
SObjectInfo info; // rev TCS 4/18/02
info.itemID = object->GetDBID();
info.classID = object->GetDBClassID();
info.transactionType = 0;
CDate matchDate;
// fetch data value
if (dateTag)
{
object->GetMemberValue(dateTag, type_date, &matchDate);
}
else
matchDate = object->GetDate();
// find the item in the list
SBreakdownInfo breakdownInfo;
TBreakdownInfoArrayIterator iterator(*breakdownArray);
while (iterator.Next(breakdownInfo))
{
if (matchDate >= breakdownInfo.startDate &&
matchDate < breakdownInfo.endDate)
{
breakdownInfo.itemArray->Append(info);
return false;
}
}
}
// if we got this far, we don't add it to any array. Return false
// so DoUntil will continue looping thru objects
return false;
}
/*********************************************************************************
Func_FillTimeBreakdownArray TCS 5/30/01
fill in a time breakdown info array with the ids of the objects in this iterator
*********************************************************************************/
Boolean CReportTable::Func_FillTimeBreakdownArray(DB_PersistentObject *object,
TBreakdownInfoArray *breakdownArray, SReportInfo *matchInfo)
{
TCS_FailNILMsg(matchInfo, TCS_GetErrString(errID_BadTable));
TCS_FailNILMsg(breakdownArray, TCS_GetErrString(errID_BadArray));
TagType dateTag = DB_ClassDescriptor::GetDateTagFromType(matchInfo->dateFieldType);
if (object)
{
// check for field match
if (!object->MatchesData(*matchInfo))
return false;
// fill the struct
SObjectInfo info; // rev TCS 4/18/02
info.itemID = object->GetDBID();
info.classID = object->GetDBClassID();
info.transactionType = 0;
CDate matchTime;
// fetch data value
if (dateTag)
{
object->GetMemberValue(dateTag, type_time, &matchTime);
}
else
matchTime = object->GetDate();
SInt32 matchHour = matchTime.GetHour(),
startTime, endTime;
// find the item in the list
SBreakdownInfo breakdownInfo;
TBreakdownInfoArrayIterator iterator(*breakdownArray);
while (iterator.Next(breakdownInfo))
{
startTime = breakdownInfo.startDate.GetHour();
endTime = breakdownInfo.endDate.GetHour();
if (matchHour >= startTime && matchHour < endTime)
{
breakdownInfo.itemArray->Append(info);
return false;
}
}
}
return false;
}
/*********************************************************************************
Func_FillEnumBreakdownArray
fill in a breakdown info array with the ids of the objects in this iterator
*********************************************************************************/
Boolean CReportTable::Func_FillEnumBreakdownArray(DB_PersistentObject *object,
TBreakdownInfoArray *breakdownArray, SReportInfo *matchInfo)
{
TCS_FailNILMsg(matchInfo, TCS_GetErrString(errID_BadTable));
TCS_FailNILMsg(breakdownArray, TCS_GetErrString(errID_BadArray));
if (object)
{
// check for field match
if (!object->MatchesData(*matchInfo))
return false;
// if we got this far, the object can be included, so
// fill the struct
SObjectInfo info; // rev TCS 4/18/02
info.itemID = object->GetDBID();
info.classID = object->GetDBClassID();
info.transactionType = 0;
SInt32 matchValue = 0;
// fetch the data value that determines breakdown location
TagType breakdownTag = matchInfo->breakdownTag;
if (breakdownTag)
{
object->GetMemberValue(breakdownTag, type_long, &matchValue);
}
// find the item in the list
SBreakdownInfo breakdownInfo;
TBreakdownInfoArrayIterator iterator(*breakdownArray);
while (iterator.Next(breakdownInfo))
{
if (matchValue == breakdownInfo.classID)
{
breakdownInfo.itemArray->Append(info);
return false;
}
}
// if we got this far it's unallocated,
// so add it to the last item TCS 8/6/99
breakdownInfo.itemArray->Append(info);
}
// we want to keep going, so always return false here,
// otherwise DoUntil will terminate
return false;
}
/*********************************************************************************
Func_FillMixedBreakdownArray TCS 10/21/02
fill in a breakdown info array with the ids of the objects in this iterator
*********************************************************************************/
Boolean CReportTable::Func_FillMixedBreakdownArray(DB_PersistentObject *object,
TBreakdownInfoArray *breakdownArray, SReportInfo *matchInfo)
{
TCS_FailNILMsg(matchInfo, TCS_GetErrString(errID_BadTable));
TCS_FailNILMsg(breakdownArray, TCS_GetErrString(errID_BadArray));
if (object)
{
// check for field match
if (!object->MatchesData(*matchInfo))
return false;
// if we got this far, the object can be included, so
// fill the struct
SObjectInfo info;
info.itemID = object->GetDBID();
info.classID = object->GetDBClassID();
info.transactionType = 0;
SInt32 matchValue = 0;
// fetch the data value that determines breakdown location
TagType breakdownTag = matchInfo->breakdownTag;
if (breakdownTag)
{
object->GetMemberValue(breakdownTag, type_long, &matchValue);
}
// find the item in the list
SBreakdownInfo breakdownInfo;
TBreakdownInfoArrayIterator iterator(*breakdownArray);
if (matchValue < 0)
{
// we treat negative values as an enum search
while (iterator.Next(breakdownInfo))
{
if (matchValue == breakdownInfo.classID)
{
breakdownInfo.itemArray->Append(info);
return false;
}
}
}
else
{
// we treat positive values as an object id search
while (iterator.Next(breakdownInfo))
{
if (matchValue == breakdownInfo.itemID)
{
breakdownInfo.itemArray->Append(info);
return false;
}
}
}
// if we got this far it's unallocated,
// so add it to the last item TCS 8/6/99
breakdownInfo.itemArray->Append(info);
}
// we want to keep going, so always return false here,
// otherwise DoUntil will terminate
return false;
}
/*********************************************************************************
ListenToMessage
listen to messages. We may need to recalculate a row
*********************************************************************************/
void CReportTable::ListenToMessage(MessageT inMessage, void *ioParam)
{
switch (inMessage)
{
case msg_GrowZone:
// memory is short, so stop adding stuff TCS 8/21/01
SetMemoryIsShort();
*(SInt32 *)ioParam = 0; // we didn't free any memory
break;
default:
break;
}
// there's nothing to pass it along to, since the superclass is not a listener
}
/*********************************************************************************
UpdateReportDisplay TCS 6/29/99 rev 8/27/99
update our values and redraw the display
*********************************************************************************/
void CReportTable::UpdateReportDisplay()
{
// create the report table
CreateReport();
// resize table, since number of rows has probably changed
ResizeFrameToImage();
// update display
Refresh();
}
/*********************************************************************************
CreateStarterReportArray TCS 6/29/99 rev 1/2/00
prepare a starter report that shows just the titles and an optional notice row
*********************************************************************************/
void CReportTable::CreateStarterReportArray(const Boolean showNotice)
{
TReportRowArray *starterArray = NEW TReportRowArray;
TCS_FailNILMsg(starterArray, TCS_GetErrString(errID_BadArray));
TCS_TRY
{
// fill in the title
FillTitleRow(starterArray);
// include the optional notice row
if (showNotice)
FillNoticeRow(starterArray);
// fill in the table (which will show just titles and the 'no items' notice
FillReportTableFromArray(starterArray, breakdown_simplelist);
}
TCS_CATCH {}
TCS_Forget(starterArray);
ClearJobCostCompareArray(); // TCS 1/29/02
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************
InitializeRowInfo (static) TCS 1/4/00
initialize info for a row
*********************************************************************************/
void CReportTable::InitializeRowInfo(SReportRowInfo &info, const UInt8 rowType)
{
info.itemID = 0;
info.matchValue = 0;
info.classID = 0;
info.rowType = rowType;
info.conditions = 0; // TCS 4/5/02
info.isSoftCost = false; // TCS 11/9/03
info.spareByte = 0;
}
/*********************************************************************************
FillTitleRow TCS 6/29/99
fill in a struct for the title row of a report table
*********************************************************************************/
void CReportTable::FillTitleRow(TReportRowArray *rowArray)
{
TCS_FailNILMsg(rowArray, TCS_GetErrString(errID_BadArray));
if (ShowTitles())
{
// fill in the title row
SReportRowInfo reportInfo;
InitializeRowInfo(reportInfo, rowtype_title);
rowArray->Append(reportInfo);
}
}
/*********************************************************************************
FillNoticeRow TCS 7/10/99
fill in a struct for the second (notice) row of a report table. This is
shown when the report window is first opened, before the Update button has been
hit to fill in data.
*********************************************************************************/
void CReportTable::FillNoticeRow(TReportRowArray *rowArray)
{
TCS_FailNILMsg(rowArray, TCS_GetErrString(errID_BadArray));
// fill in the title row
SReportRowInfo reportInfo;
InitializeRowInfo(reportInfo, rowtype_notice);
rowArray->Append(reportInfo);
}
/*********************************************************************************
AppendGrandTotal moved TCS 1/4/00
set up an array of simple list items
*********************************************************************************/
void CReportTable::AppendGrandTotal(TReportRowArray *rowArray)
{
// if we're showing a grand total, append that to the array
SReportRowInfo rowInfo;
InitializeRowInfo(rowInfo, rowtype_grandtotal);
if (mShowGrandTotal)
rowArray->Append(rowInfo);
// end with a blank row temporarily, since we seem to lose last row sometimes
rowInfo.rowType = rowtype_blank;
rowArray->Append(rowInfo);
}/*********************************************************************************
CreateTitleCaptions // TCS removed all code 6/27/99
create the captions for the column titles. We don't need to do anything
for this, because titles are set up as a row within the table itself
*********************************************************************************/
void CReportTable::CreateTitleCaptions()
{
}
/*********************************************************************************
ClearBreakdownArray TCS 1/4/00 renamed 7/9/02
clean up a temporary breakdown array
*********************************************************************************/
void CReportTable::ClearBreakdownArray(TBreakdownInfoArray *breakdownArray)
{
if (breakdownArray)
{
// erase each of the allocated arrays
SBreakdownInfo breakdownInfo;
TBreakdownInfoArrayIterator iterator(*breakdownArray);
while (iterator.Next(breakdownInfo))
{
TCS_Forget(breakdownInfo.itemArray); // TCS rev 11/5/00
}
}
TCS_Forget(breakdownArray);
}
/*********************************************************************************
ClearTwoLevelArray TCS 4/18/02
clean up a temporary two-level breakdown array. We have two levels of
allocated arrays to dispose of.
*********************************************************************************/
void CReportTable::ClearTwoLevelArray(TTwoLevelArray *twoLevelArray)
{
if (twoLevelArray)
{
// erase each of the allocated arrays
STwoLevelInfo mainLevelInfo;
TTwoLevelArrayIterator iterator(*twoLevelArray);
while (iterator.Next(mainLevelInfo))
{
ClearItemCategoryArray(mainLevelInfo.groupArray);
}
}
// delete the main array itself
TCS_Forget(twoLevelArray);
}
/*********************************************************************************
ClearItemCategoryArray TCS 1/25/03
clean up a temporary two-level breakdown array. We have two levels of
allocated arrays to dispose of.
*********************************************************************************/
void CReportTable::ClearItemCategoryArray(TReportGroupArray *catArray)
{
if (catArray)
{
// remove the item arrays for each group
TReportGroupArrayIterator subIterator (*catArray);
SReportGroupInfo subcatInfo;
while (subIterator.Next(subcatInfo))
{
TCS_Forget(subcatInfo.itemArray);
}
// now remove the main group array
TCS_Forget(catArray);
}
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************
CreateReport rev TCS 6/29/99, 8/6/99
prepare the report- which may be a simple list, or a list broken down by
category etc.
See comments at the top
*********************************************************************************/
void CReportTable::CreateReport()
{
// sanity check
TCS_FailNILMsg(mDescriptor, TCS_GetErrString(errID_BadDescriptor));
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
// we might as well erase any existing rows TCS 1/15/04
MakeEmptyTable();
// we used to skip update if not dirty, but we now recalc every time
// just in case some of the data changed TCS rev 2/20/02
// fetch the type of report to create. This is usually an object class ID,
// but it may also be a portmanteau or parent class, or a special report ID
// that invokes special handling via CreateSpecialReport
DBid reportID = GetReportClassID();
UInt8 breakdownType = 0;
// start with a report row array struct, which contains info for
// each row of the table (object id, row type, etc). We create a new
// array on the heap, since it may get big rev TCS 1/1/01
TReportRowArray *rowArray = NEW TReportRowArray;
TCS_FailNILMsg(rowArray, TCS_GetErrString(errID_BadArray));
TCS_TRY
{
// fill in the optional title row
FillTitleRow(rowArray);
// are we making a special report type?
breakdownType = CreateSpecialReport(reportID, rowArray);
// if not, make a generic object report
if (!breakdownType)
{ // otherwise, figure what type of breakdown will be used
TagType breakdownTag = GetTableBreakdownTag();
// if we don't have a breakdown by now, there are problems
if (!breakdownTag)
{
TCS_DebugAlert("Oops, missing breakdown tag in CReportTable::CreateReport!");
return;
}
// fill in data, based on the breakdown type. This is step 2 in the brief
// process description in the method title (see method title)
switch (breakdownTag)
{
case breakdown_simplelist: // it's a simple list. Loop thru all selected items
CreateSimpleReportArray(rowArray);
break;
case breakdown_hour: // for a time breakdown we create a list of hours
// and then fill in items that belong to each time slot
CreateTimeReportArray(rowArray, breakdownTag);
breakdownType = breakdownTag;
break;
case breakdown_day: // for date breakdowns we create a list of date ranges
case breakdown_week: // and then fill in items that belong to each possible date
case breakdown_month:
CreateDateReportArray(rowArray, breakdownTag);
breakdownType = breakdownTag;
break;
default: // for other breakdowns we create a list of objects or enums
{ // and then fill in objects that match each category etc
SInt32 dataType = DB_ClassDescriptor::GetDataType(mReportClassID, breakdownTag);
if (dataType == type_enumobj)
{
CreateMixedReportArray(rowArray, breakdownTag); // TCS 10/21/02
breakdownType = breakdown_mixed;
}
else if (dataType == type_objectid || dataType == type_objectidx)
{
CreateBreakdownReportArray(rowArray, breakdownTag);
breakdownType = breakdown_object;
}
else if (dataType == type_enum || dataType == type_objclass)
{
CreateEnumReportArray(rowArray, breakdownTag);
breakdownType = breakdown_enum;
}
else
{
CreateSimpleReportArray(rowArray); // rev TCS 4/4/02
//TCS_DebugAlert("Oops, bad breakdown type in CReportTable::CreateReport!");
//return;
}
}
break;
}
}
// if no matching items were found, add a 'no items' message
SInt32 rowCount = rowArray->GetCount();
if ((ShowTitles() && rowCount < 3) || !ShowTitles() && rowCount < 2)
{
SReportRowInfo rowInfo;
InitializeRowInfo(rowInfo, rowtype_noitems); // TCS rev 7/11/02
rowArray->Append(rowInfo);
rowInfo.rowType = rowtype_blank;
rowArray->Append(rowInfo);
}
// fill in the optional grand total row
AppendGrandTotal(rowArray);
// now that our basic structure has been established,
// fill in the table with actual data from the objects.
// This is step 3 in the brief process description (see method title)
FillReportTableFromArray(rowArray, breakdownType);
// turn off the dirty flag TCS 12/19/00
mCalcDirty = false;
}
TCS_CATCH {}
// the approximate memory footprint for this table TCS 1/27/03
SInt32 tableSize = GetTableSize();
// delete the array
TCS_Forget(rowArray);
ClearJobCostCompareArray(); // TCS 1/29/02
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************
CreateSimpleReportArray 7/3/99 rev 1/5/00
set up an array of simple list items
*********************************************************************************/
void CReportTable::CreateSimpleReportArray(TReportRowArray *rowArray, const DBClass objectClass,
TObjectIDArray *objectArray)
{
// sanity check
TCS_FailNILMsg(mDescriptor, TCS_GetErrString(errID_BadDescriptor));
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
TCS_FailNILMsg(rowArray, TCS_GetErrString(errID_BadArray));
// fetch search parameters
SReportInfo reportInfo = GetReportInfo();
DBid reportID = GetReportClassID(); // bugfix TCS 11/15/00
TagType breakdownTag = GetTableBreakdownTag();
if (objectClass) // TCS 1/18/02 rev TCS 11/18/02
{
// if the caller passes in an object class, then there must
// also be an array of items to include
TCS_FailNILMsg(objectArray, TCS_GetErrString(errID_BadArray));
gDBFile->DoForEachInArray((BooleanActionFunc)Func_FillSimpleReportArray, objectClass,
objectArray, rowArray, &reportInfo);
}
else if (DB_ClassDescriptor::IsList(reportID)) // for lists, we always include all items TCS 6/29/01
{
gDBFile->DoForEach((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
rowArray, &reportInfo);
}
else switch (mItemRange) // loop thru objects and fill them into the array
{
case records_allitems:
gDBFile->DoForEach((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
rowArray, &reportInfo); // rev TCS 1/16/02
break;
case records_activeitems:
gDBFile->DoForEachActive((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
rowArray, &reportInfo);
break;
case records_founditems:
gDBFile->DoForEachFound((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
rowArray, &reportInfo);
break;
case records_flaggeditems:
gDBFile->DoForEachFlagged((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
rowArray, &reportInfo);
break;
case records_flaggedandfound:
gDBFile->DoForEachFoundandFlagged((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
rowArray, &reportInfo);
break;
case records_openitems:
gDBFile->DoForEachWithStatus((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
flag_open, rowArray, &reportInfo);
break;
case records_overdueitems:
gDBFile->DoForEachWithStatus((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
flag_overdue, rowArray, &reportInfo);
break;
case records_unpaid:
gDBFile->DoForEachWithStatus((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
flag_unpaid, rowArray, &reportInfo);
break;
case records_completeditems:
gDBFile->DoForEachWithStatus((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
flag_completed, rowArray, &reportInfo);
break;
case records_paid:
gDBFile->DoForEachWithStatus((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
flag_paid, rowArray, &reportInfo);
break;
case records_planneditems:
gDBFile->DoForEachWithStatus((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
flag_planned, rowArray, &reportInfo);
break;
case records_special:
gDBFile->DoForEachWithStatus((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
flag_special, rowArray, &reportInfo);
break;
case records_voiditems: // bugfix 1/5/00
gDBFile->DoForEachWithStatus((BooleanActionFunc)Func_FillSimpleReportArray, reportID,
flag_void, rowArray, &reportInfo);
break;
default:
TCS_DebugAlert("Oops, bad case in CReportTable::CreateSimpleReportArray!");
break;
}
// force a purge to tidy up memory TCS 1/24/03
gDBFile->SaveAllAndPurge();
// if we have data, end with a blank row
if (rowArray->GetCount() > 0)
{
SReportRowInfo blankRowInfo;
InitializeRowInfo(blankRowInfo, rowtype_blank);
rowArray->Append(blankRowInfo);
}
}
|