This is DataTable.m in view mode; [Download] [Up]
// ------------------------------------------------------------------------------------- // 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.