home *** CD-ROM | disk | FTP | other *** search
- // -------------------------------------------------------------------------------------
- // DataTable
- // This software is without warranty of any kind. Use at your own risk.
- // -------------------------------------------------------------------------------------
-
- #import <objc/objc.h>
- #import <appkit/appkit.h>
- #import <mach/mach.h>
- #import <dbkit/dbkit.h>
- #import <libc.h>
- #import <stdio.h>
- #import <string.h>
- #import <ctype.h>
- #import "DataTable.h"
-
- // -------------------------------------------------------------------------------------
- // misc
- #define KEY_COLUMN 0
-
- // -------------------------------------------------------------------------------------
- // string macros
- #define freeString(S) { if (S) { free(S); S = (char*)nil; } }
-
- // -------------------------------------------------------------------------------------
- // table macros
- #define dataROWS tableHandle->dataId
- #define columnINFO tableHandle->columnId
- #define orderedINFO tableHandle->reorderId
-
- // -------------------------------------------------------------------------------------
- @interface DataTable(private)
- - _resetDisplayedColumnOrdering;
- @end
-
- // -------------------------------------------------------------------------------------
- @implementation DataTable
- // -------------------------------------------------------------------------------------
-
- // -------------------------------------------------------------------------------------
- // initialize connection with data table
-
- /* open table */
- - initFromFile:(const char*)fileName
- {
- char *p = fileName? rindex((char*)fileName, '/') : (char*)nil;
-
- /* init super */
- [self init];
- nullIndex = (id)-1;
- delegate = (id)nil;
- isModified = NO;
-
- /* initialize table handle */
- tableHandle = (dataTable_t*)malloc(sizeof(dataTable_t));
- memset(tableHandle, 0, sizeof(dataTable_t));
- tableHandle->name = NXCopyStringBuffer(p? (p+1) : "Unknown");
- tableHandle->access = fileName? NXCopyStringBuffer(fileName) : (char*)nil;
- tableHandle->viewSize.width = tableHandle->viewSize.height = 0.0;
- tableHandle->columnId = [[[List alloc] initCount:1] empty];
- tableHandle->reorderId = (id)nil;
- tableHandle->dataId = [[List allocFromZone:[self zone]] init];
- tableHandle->date = [self timestamp];
-
- /* read column-info and data */
- if (![self readTableColumns] || ![self readTableData])
- NXLogError("Unable to load column/data for file %s", (fileName?fileName:"?"));
-
- /* return */
- return self;
-
- }
-
- // -------------------------------------------------------------------------------------
- // set attributes
-
- /* set delegate */
- - setDelegate:aDelegate
- {
- delegate = aDelegate;
- return self;
- }
-
- /* return current delegate */
- - delegate
- {
- return delegate;
- }
-
- /* null index */
- - setNullIndex:index
- {
- nullIndex = index;
- return self;
- }
-
- /* set number of rows */
- - setRows:(u_int)rowCount
- {
- return [self notImplemented:_cmd];
- }
-
- /* set number of columns */
- - setColumns:(u_int)colCount
- {
- return [self notImplemented:_cmd];
- }
-
- /* set display order for column */
- - setDisplayOrder:(int)order andWidth:(float)width forColumnIndex:(int)column
- {
- BOOL didEdit = NO;
- dataColumn_t *ci = [self columnInfoAt:column];
- if (!ci) return (id)nil;
- if (ci->displayOrder != order) {
- ci->displayOrder = order;
- ci->isHidden = (order >= 0)? NO : YES;
- didEdit = YES;
- if (tableHandle->reorderId) {
- [tableHandle->reorderId free];
- tableHandle->reorderId = (id)nil;
- }
- }
- if ((width >= 0.0) && (ci->size != width)) {
- ci->size = width;
- didEdit = YES;
- }
- if (didEdit) [self setDocEdited:YES];
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // freeing resources
-
- /* free columns */
- - _freeColumnInfo
- {
- int c;
- for (c = 0; c < [columnINFO count]; c++) {
- dataColumn_t *ci = [self columnInfoAt:c];
- freeString(ci->keyTag);
- freeString(ci->title);
- freeString(ci->nilValue);
- free(ci);
- }
- [columnINFO free];
- columnINFO = (id)nil;
- if (orderedINFO) {
- [orderedINFO free];
- orderedINFO = (id)nil;
- }
- return self;
- }
-
- /* free a single row */
- - _freeRow:rowId
- {
- int c;
- for (c = 0; c < [rowId count]; c++) {
- dataEntry_t *de = entryPTR(rowId, c);
- freeString(de->value);
- free(de);
- }
- return self;
- }
-
- /* free data */
- - _freeData
- {
- int r;
- for (r = 0; r < [dataROWS count]; r++) [self _freeRow:[dataROWS objectAt:r]];
- [dataROWS freeObjects];
- [dataROWS free];
- dataROWS = (id)nil;
- return self;
- }
-
- /* free */
- - free
- {
-
- /* free table */
- if (tableHandle) {
- freeString(tableHandle->name);
- freeString(tableHandle->access);
- [self _freeData];
- [self _freeColumnInfo];
- free(tableHandle);
- }
-
- /* free object */
- return [super free];
-
- }
-
- // -------------------------------------------------------------------------------------
- // View size (handle by superclass, delegate, or other object)
-
- /* set size */
- - setViewSize:(NXSize*)size
- {
- tableHandle->viewSize = *size;
- return self;
- }
-
- /* get size */
- - (const NXSize*)viewSize
- {
- if ((tableHandle->viewSize.width > 0.0) && (tableHandle->viewSize.height > 0.0))
- return &(tableHandle->viewSize);
- return (NXSize*)nil;
- }
-
- // -------------------------------------------------------------------------------------
- // saving the table
-
- /* check last modified date */
- - (BOOL)tableHasChanged
- {
- struct stat st;
-
- /* stat file */
- if (!tableHandle->access) return NO;
- if (stat((char*)tableHandle->access, &st) < 0) {
- NXLogError("Unable to stat file %s", tableHandle->access);
- return YES;
- }
-
- /* return file changed status */
- return tableHandle->date == st.st_mtime? NO : YES;
-
- }
-
- /* indicate table has been edited */
- - setDocEdited:(BOOL)flag
- {
- isModified = flag;
- if (delegate && (delegate != self) && [delegate respondsTo:@selector(setDocEdited:)])
- [delegate setDocEdited:flag];
- return self;
- }
-
- /* commit/save table */
- - commitTable
- {
-
- /* make sure we have a file name */
- if (!tableHandle->access) {
- // load table name */
- }
-
- /* write entire table */
- if ([self writeTable]) [self setDocEdited:NO];
-
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // column info
-
- /* return column info pointer */
- + (dataColumn_t*)_column:infoId infoAt:(int)n
- {
- if (!infoId || (n < 0) || (n >= [infoId count])) return (dataColumn_t*)nil;
- return (dataColumn_t*)[infoId objectAt:n];
- }
-
- /* get column info by index */
- - (dataColumn_t*)columnInfoAt:(u_int)col
- {
- return [[self class] _column:columnINFO infoAt:col];
- }
-
- /* return column number for matching keyTag */
- - (int)indexForColumnName:(const char*)name
- {
- int c;
- if (!name || !columnINFO) return -1;
- for (c = 0; c < [columnINFO count]; c++) {
- dataColumn_t *ci = [self columnInfoAt:c];
- if (!strcmp(name, ci->keyTag)) return c;
- }
- return -1;
- }
-
- /* reset displayed column ordering */
- - _resetDisplayedColumnOrdering
- {
- int c, cnt;
-
- /* empty reorder table */
- if (!orderedINFO) orderedINFO = [[List alloc] initCount:1];
- [orderedINFO empty];
-
- /* add visible column headers to new list */
- for (c = 0, cnt = [columnINFO count]; c < cnt; c++) {
- dataColumn_t *ci = [self columnInfoAt:c];
- if (!ci || (ci->displayOrder < 0)) continue;
- [orderedINFO addObject:(id)ci];
- }
-
- /* sort reorder table by display order (since table is small, use simple-sort) */
- for (c = 0, cnt = [orderedINFO count]; c < cnt; c++) {
- int n;
- dataColumn_t *ci = (dataColumn_t*)[orderedINFO objectAt:c];
- for (n = c + 1; n < cnt; n++) {
- dataColumn_t *ni = (dataColumn_t*)[orderedINFO objectAt:n];
- if (ci->displayOrder > ni->displayOrder) {
- [orderedINFO replaceObjectAt:c with:(id)ni];
- [orderedINFO replaceObjectAt:n with:(id)ci];
- ci = ni;
- }
- }
- }
-
- return self;
- }
-
- /* get column info by order */
- - (dataColumn_t*)orderedColumnInfoAt:(u_int)ord
- {
- if (!orderedINFO) [self _resetDisplayedColumnOrdering];
- return [[self class] _column:orderedINFO infoAt:ord];
- }
-
- /* add column info */
- - addColumnInfo:(dataColumn_t*)dc
- {
- if (dc->type == CDT_UNKNOWN) dc->type = CDT_STRING;
- if (!dc->title) dc->title = NXCopyStringBuffer(dc->keyTag);
- if (dc->minSize <= 0.0) dc->minSize = dc->size;
- [columnINFO addObject:(id)dc];
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // load text file table data
-
- /* duplicate row */
- - newRowName:(const char*)rowN copyFromRow:(int)rowX
- {
- int c, rcnt = [dataROWS count], ccnt = [columnINFO count];
- id rowId, newRow;
- if ((rowX < 0) || (rowX > rcnt)) rowX = rcnt;
- rowId = rowX < rcnt? [dataROWS objectAt:rowX] : (id)nil;
- newRow = [[[List alloc] initCount:0] empty];
- if (![dataROWS insertObject:newRow at:MIN(rowX + 1, rcnt)]) {
- NXLogError("Unable to add new row %s into table", rowN);
- [newRow free];
- return (id)nil;
- }
- for (c = 0; c < ccnt; c++) {
- dataEntry_t *ne = entryNEW, *oe = rowId? entryPTR(rowId, c) : (dataEntry_t*)nil;
- ne->value = (char*)[self copyStringValue:(c?(oe?oe->value:""):rowN) forColumn:c];
- ne->isValid = -1;
- [newRow insertObject:(id)ne at:c];
- }
- [self setDocEdited:YES];
- return newRow;
- }
-
- /* delete row */
- - deleteRowAt:(int)rowX
- {
- id rowId;
- if ((rowX < 0) || (rowX >= [dataROWS count])) return (id)nil;
- if (!(rowId = [dataROWS removeObjectAt:rowX])) return (id)nil;
- [self _freeRow:rowId];
- [self setDocEdited:YES];
- return self;
- }
-
- /* remove all table entries */
- - empty
- {
- return self;
- }
-
- /* fill/refill table */
- - reset
- {
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // sorting
-
- /* sort comparison */
- - (int)sortCompare:(dataColumn_t*)dc values:(const char*)val1:(const char*)val2
- {
- if (dc->type == CDT_INTEGER) return atoi(val1) - atoi(val2);
- return strcasecmp(val1, val2);
- }
-
- /* sort data table by column */
- #define STRCOMP(C,T,R1,R2) [self sortCompare:T values:entryVALUE(R1,C):entryVALUE(R2,C)]
- //#define STRCOMP(C,T,R1,R2) strcasecmp(entryVALUE(R1,C),entryVALUE(R2,C))
- - sortTableByColumn:(int)pri :(int)sec
- {
- int r, cnt;
- dataColumn_t *dcp, *dcs;
-
- /* check sort keys */
- if (pri < 0) {
- NXLogError("Invalid primary sort column %d", pri);
- return (id)nil;
- }
- if (pri == KEY_COLUMN) sec = -1;
-
- /* get column type */
- dcp = [self columnInfoAt:pri];
- dcs = (sec >= 0)? [self columnInfoAt:sec] : (dataColumn_t*)nil;
-
- /* sort */
- for (r = 0, cnt = [dataROWS count]; r < cnt; r++) {
- int n;
- id rowR = [dataROWS objectAt:r];
- for (n = r + 1; n < cnt; n++) {
- id rowN = [dataROWS objectAt:n];
- int cmp = STRCOMP(pri,dcp,rowR,rowN);
- if ((cmp > 0) || ((cmp == 0) && dcs && (STRCOMP(sec,dcs,rowR,rowN) > 0))) {
- [dataROWS replaceObjectAt:r with:rowN];
- [dataROWS replaceObjectAt:n with:rowR];
- rowR = rowN;
- }
- }
- }
-
- return self;
- }
- #undef STRCOMP
-
- /* sort data table by column */
- - sortTableByColumnName:(const char*)priName :(const char*)secName
- {
- int pri = [self indexForColumnName:priName];
- int sec = secName? [self indexForColumnName:secName] : -1;
-
- /* check sort keys */
- if (pri < 0) {
- NXLogError("Invalid primary sort key %s", (priName?priName:"?"));
- return (id)nil;
- }
- if (secName && (sec < 0)) {
- NXLogError("Invalid secondary sort key %s", secName);
- return (id)nil;
- }
-
- /* sort */
- return [self sortTableByColumn:pri:sec];
-
- }
-
- // -------------------------------------------------------------------------------------
- // validate entries
-
- /* validate specific entry */
- - (BOOL)_validateEntry:(dataEntry_t*)de forColumn:(u_int)column
- {
- if (!de) return NO;
- if (de->isValid < 0) {
- dataColumn_t *dc = [self columnInfoAt:column];
- if (dc->nilValue&&(!*de->value||!strcmp(de->value,dc->nilValue))) de->isValid = YES;
- else de->isValid = [self verifyValue:de->value dataType:dc->type]? 1 : 0;
- }
- return de->isValid;
- }
-
- /* validate entries until an error is found */
- - (BOOL)hasVerificationErrors
- {
- int r, rcnt = [dataROWS count];
- for (r = 0; r < rcnt; r++) {
- id rowId = [dataROWS objectAt:r];
- int c, ccnt = [rowId count];
- for (c = 0; c < ccnt; c ++) {
- dataEntry_t *de = entryPTR(rowId,c);
- if (![self _validateEntry:de forColumn:c]) return YES;
- }
- }
- return NO;
- }
-
- /* validate entry at specific location */
- - (BOOL)verifyValueAt:(u_int)row :(u_int)column
- {
- return [self _validateEntry:[self entryAtIndex:row:column] forColumn:column];
- }
-
- // -------------------------------------------------------------------------------------
- // table access
-
- /* find row for specified name */
- - (int)indexForRowName:(const char*)rowN exactMatch:(BOOL)exact
- {
- int r;
- if (!rowN) return -1; // not found
- for (r = 0; r < [dataROWS count]; r++) {
- id rowId = [dataROWS objectAt:r];
- char *rn = (char*)entryVALUE(rowId,0);
- if (exact) { if (!strcmp(rn, rowN)) return r; }
- else { if (!strstr(rn, rowN)) return r; }
- }
- return -1;
- }
-
- /* return specified table entry (by index) */
- - (dataEntry_t*)entryAtIndex:(u_int)rowX :(u_int)colX
- {
- id rowId = [dataROWS objectAt:rowX];
- if (!rowId || (colX > [rowId count])) return (dataEntry_t*)nil;
- return entryPTR(rowId,colX);
- }
-
- /* return specified table entry (by name) */
- - (const char*)valueForRowName:(const char*)rowN columnName:(const char*)colN
- {
- int wc = [self indexForColumnName:colN];
- if (wc >= 0) {
- int r = [self indexForRowName:rowN exactMatch:YES];
- if (r >= 0) return entryVALUE([dataROWS objectAt:r], wc);
- }
- return (char*)nil;
- }
-
- /* return specified table entry (by index) */
- - (const char*)valueAtIndex:(u_int)rowX :(u_int)colX
- {
- dataEntry_t *de = [self entryAtIndex:rowX:colX];
- return de? de->value : (char*)nil;
- }
-
- /* return string constant at location */
- - (const char*)valueFor:(u_int)rowIndex :(u_int)columnIndex
- {
- return [self valueAtIndex:rowIndex:columnIndex];
- }
-
- /* set indexed table entry */
- - setValue:(const char*)value atIndex:(u_int)rowX :(u_int)colX
- {
- dataEntry_t *de = [self entryAtIndex:rowX:colX];
- if (!de) return (id)nil;
- freeString(de->value);
- de->value = (char*)[self copyStringValue:value forColumn:colX];
- de->isValid = -1;
- [self setDocEdited:YES];
- return self;
- }
-
- /* set value for specified row/column name */
- - setValue:(const char*)value forRowName:(const char*)rowN columnName:(const char*)colN
- {
- int wc = [self indexForColumnName:colN];
- if (wc >= 0) {
- int wr = [self indexForRowName:rowN exactMatch:YES];
- if (wr >= 0) return [self setValue:value atIndex:wr:wc];
- }
- return (id)nil;
- }
-
- /* change table value (note: index is independent of actual column position) */
- - setValueFor:rowIndex :colIndex from:aValue
- {
- return [self setValue:[aValue stringValue] atIndex:(u_int)rowIndex:(u_int)colIndex];
- }
-
- /* change table value (note: index is independent of actual column position) */
- - setValueFor:colIndex at:(u_int)rowPosition from:aValue
- {
- return [self setValue:[aValue stringValue] atIndex:rowPosition:(u_int)colIndex];
- }
-
- /* get table value */
- - getValueFor:rowIndex :columnIndex into:aValue
- {
- return [self getValueFor:columnIndex at:(u_int)rowIndex into:aValue];
- }
-
- /* get table value */
- - getValueFor:index at:(u_int)aPosition into:aValue
- {
- if (index == nullIndex) { [aValue setNull]; return self; }
- [aValue setStringValue:(char*)[self valueFor:aPosition :(u_int)index]];
- return self;
- }
-
- /* return specified table entry (by name) */
- - (int)scanForValue:(const char*)value inColumnName:(const char*)colN
- startingAtRow:(int)rowX backwards:(BOOL)back
- {
- if (value && colN) {
- int wc = [self indexForColumnName:colN];
- if (wc >= 0) {
- int r = rowX < 0? [dataROWS count] : rowX % [dataROWS count];
- if (rowX < 0) r = [dataROWS count];
- else r = rowX % [dataROWS count];
- for (;;) {
- id rowId = [dataROWS objectAt:r];
- if (strstr(entryVALUE(rowId, wc), value)) return r;
- r = (r + 1) % [dataROWS count];
- if (r == rowX) break;
- }
- }
- }
- return -1;
- }
-
- // -------------------------------------------------------------------------------------
- // return table attributes
-
- /* return name of table */
- - (const char*)tableName
- {
- return tableHandle->name;
- }
-
- /* return title of table */
- - (const char*)tableTitle
- {
- return tableHandle->name;
- }
-
- /* return access(path) of table */
- - (const char*)tableAccess
- {
- return tableHandle->access;
- }
-
- /* return number of visible columns in table */
- - (u_int)visibleColumnCount
- {
- if (!orderedINFO) [self _resetDisplayedColumnOrdering];
- return [orderedINFO count];
- }
-
- /* return number of visible columns */
- - (u_int)columnCount
- {
- return [self visibleColumnCount];
- }
-
- /* return number of visible columns in table */
- - (u_int)actualColumnCount
- {
- return [columnINFO count];
- }
-
- /* return number of rows in table */
- - (u_int)rowCount
- {
- return [dataROWS count];
- }
-
- // -------------------------------------------------------------------------------------
- // DBTableView notification methods
-
- /* user selection changed */
- - tableViewDidChangeSelection:aTableView
- {
- return self;
- }
-
- /* user move column */
- - tableView:aTableView movedColumnFrom:(u_int)oldpos to:(u_int)newpos
- {
- [self setDocEdited:YES];
- return self;
- }
-
- /* selection will change */
- - (BOOL)tableViewWillChangeSelection:aTableView
- {
- return YES; // allow selection change
- }
-
- // -------------------------------------------------------------------------------------
- // subclass methods
-
- - (time_t)timestamp
- {
- return (time_t)0;
- }
-
- - readTableColumns
- {
- return [self subclassResponsibility:_cmd];
- }
-
- - readTableData
- {
- return [self subclassResponsibility:_cmd];
- }
-
- - writeTable
- {
- return [self subclassResponsibility:_cmd];
- }
-
- - (const char*)copyStringValue:(const char*)value forColumn:(int)index
- {
- return NXCopyStringBuffer(value? value : "");
- }
-
- - (BOOL)verifyValue:(const char*)value dataType:(int)dataType
- {
- return value? YES : NO;
- }
-
- // -------------------------------------------------------------------------------------
- @end
-