TestController.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. /*
  2. * $Id$
  3. *
  4. * The iODBC driver manager.
  5. *
  6. * Copyright (C) 1996-2021 OpenLink Software <[email protected]>
  7. * All Rights Reserved.
  8. *
  9. * This software is released under the terms of either of the following
  10. * licenses:
  11. *
  12. * - GNU Library General Public License (see LICENSE.LGPL)
  13. * - The BSD License (see LICENSE.BSD).
  14. *
  15. * Note that the only valid version of the LGPL license as far as this
  16. * project is concerned is the original GNU Library General Public License
  17. * Version 2, dated June 1991.
  18. *
  19. * While not mandated by the BSD license, any patches you make to the
  20. * iODBC source code may be contributed back into the iODBC project
  21. * at your discretion. Contributions will benefit the Open Source and
  22. * Data Access community as a whole. Submissions may be made at:
  23. *
  24. * http://www.iodbc.org
  25. *
  26. *
  27. * GNU Library Generic Public License Version 2
  28. * ============================================
  29. * This library is free software; you can redistribute it and/or
  30. * modify it under the terms of the GNU Library General Public
  31. * License as published by the Free Software Foundation; only
  32. * Version 2 of the License dated June 1991.
  33. *
  34. * This library is distributed in the hope that it will be useful,
  35. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  36. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  37. * Library General Public License for more details.
  38. *
  39. * You should have received a copy of the GNU Library General Public
  40. * License along with this library; if not, write to the Free
  41. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  42. *
  43. *
  44. * The BSD License
  45. * ===============
  46. * Redistribution and use in source and binary forms, with or without
  47. * modification, are permitted provided that the following conditions
  48. * are met:
  49. *
  50. * 1. Redistributions of source code must retain the above copyright
  51. * notice, this list of conditions and the following disclaimer.
  52. * 2. Redistributions in binary form must reproduce the above copyright
  53. * notice, this list of conditions and the following disclaimer in
  54. * the documentation and/or other materials provided with the
  55. * distribution.
  56. * 3. Neither the name of OpenLink Software Inc. nor the names of its
  57. * contributors may be used to endorse or promote products derived
  58. * from this software without specific prior written permission.
  59. *
  60. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  61. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  62. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  63. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OPENLINK OR
  64. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  65. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  66. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  67. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  68. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  69. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  70. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  71. */
  72. #import "TestController.h"
  73. #import "Helpers.h"
  74. #import "ExecController.h"
  75. #import "NSAttributedStringAdditions.h"
  76. #import "LinkTextFieldCell.h"
  77. #define MIN_WIDTH 5
  78. #define MAX_WIDTH 80
  79. #define OpenTag 56000
  80. #define CloseTag 56001
  81. #define ExecSQLTag 56002
  82. #define NextRowsetTag 56003
  83. @implementation TestController
  84. @synthesize fQuery = _fQuery;
  85. - (id)init
  86. {
  87. [super init];
  88. hstmt = nil;
  89. hdbc = nil;
  90. henv = nil;
  91. mConnected = NO;
  92. mExistsResultset = NO;
  93. mNextResultset = NO;
  94. mBuffer = [NSMutableArray new];
  95. mRows = 0;
  96. self.fQuery = @"select * from orders";
  97. //self.fQuery = @"sparql select * {?s ?p ?o} limit 10";
  98. mMaxRows = 1000;
  99. return self;
  100. }
  101. - (void)dealloc
  102. {
  103. [self disconnect];
  104. [mBuffer release];
  105. [super dealloc];
  106. }
  107. - (void)awakeFromNib
  108. {
  109. [RSTable setDataSource:self];
  110. [RSTable setDelegate:self];
  111. [self clearGrid];
  112. #ifdef UNICODE
  113. [mWindow setTitle:[NSString stringWithFormat:@"iODBC Demo (Unicode) - Disconnected"]];
  114. #else
  115. [mWindow setTitle:[NSString stringWithFormat:@"iODBC Demo (Ansi) - Disconnected"]];
  116. #endif
  117. }
  118. - (void)disconnect
  119. {
  120. if (hstmt != nil)
  121. {
  122. #if (ODBCVER < 0x0300)
  123. SQLFreeStmt (hstmt, SQL_DROP);
  124. #else
  125. SQLCloseCursor (hstmt);
  126. SQLFreeHandle (SQL_HANDLE_STMT, hstmt);
  127. #endif
  128. hstmt = nil;
  129. }
  130. if (hdbc != nil)
  131. {
  132. if (mConnected)
  133. SQLDisconnect (hdbc);
  134. #ifdef UNICODE
  135. [mWindow setTitle:[NSString stringWithFormat:@"iODBC Demo (Unicode) - Disconnected"]];
  136. #else
  137. [mWindow setTitle:[NSString stringWithFormat:@"iODBC Demo (Ansi) - Disconnected"]];
  138. #endif
  139. [self clearGrid];
  140. mConnected = NO;
  141. #if (ODBCVER < 0x300)
  142. SQLFreeConnect (hdbc);
  143. #else
  144. SQLFreeHandle (SQL_HANDLE_DBC, hdbc);
  145. #endif
  146. hdbc = nil;
  147. }
  148. if (henv != nil)
  149. {
  150. #if (ODBCVER < 0x300)
  151. SQLFreeEnv (henv);
  152. #else
  153. SQLFreeHandle (SQL_HANDLE_ENV, henv);
  154. #endif
  155. henv = nil;
  156. }
  157. }
  158. - (void)clearGrid
  159. {
  160. NSArray *arr;
  161. unsigned i, count;
  162. NSTableColumn *aColumn;
  163. arr = [RSTable tableColumns];
  164. count = [arr count];
  165. for(i = 0; i < count; i++)
  166. {
  167. aColumn = [arr objectAtIndex:0];
  168. [RSTable removeTableColumn:aColumn];
  169. }
  170. }
  171. - (void)execSQL:(SQLTCHAR *)szSQL
  172. {
  173. SQLRETURN sts;
  174. [self clearGrid];
  175. mSPARQL_executed = false;
  176. if (mExistsResultset == YES)
  177. {
  178. #if (ODBCVER < 0x0300)
  179. SQLFreeStmt (hstmt, SQL_CLOSE);
  180. #else
  181. SQLCloseCursor (hstmt);
  182. #endif
  183. }
  184. mExistsResultset = NO;
  185. mNextResultset = NO;
  186. NSString *query = TEXTtoNS(szSQL);
  187. mSPARQL_executed = [[query.uppercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] hasPrefix:@"SPARQL"];
  188. if (!TEXTCMP(szSQL, TEXT("tables")))
  189. {
  190. if (SQLTables (hstmt, NULL, 0, NULL, 0, NULL, 0, NULL, 0) != SQL_SUCCESS)
  191. goto error;
  192. }
  193. else if (!TEXTCMP(szSQL, TEXT("qualifiers")))
  194. {
  195. if (SQLTables (hstmt, TEXT("%"), SQL_NTS, TEXT(""), 0, TEXT(""), 0, TEXT(""), 0) != SQL_SUCCESS)
  196. goto error;
  197. }
  198. else if (!TEXTCMP(szSQL, TEXT("owners")))
  199. {
  200. if (SQLTables (hstmt, TEXT(""), SQL_NTS, TEXT("%"), 0, TEXT(""), 0, TEXT(""), 0) != SQL_SUCCESS)
  201. goto error;
  202. }
  203. else if (!TEXTCMP(szSQL, TEXT("types")))
  204. {
  205. if (SQLTables (hstmt, TEXT(""), SQL_NTS, TEXT(""), 0, TEXT(""), 0, TEXT("%"), 0) != SQL_SUCCESS)
  206. goto error;
  207. }
  208. else if (!TEXTCMP(szSQL, TEXT("datatypes")))
  209. {
  210. if (SQLGetTypeInfo (hstmt, 0) != SQL_SUCCESS)
  211. goto error;
  212. }
  213. else
  214. {
  215. if (SQLPrepare (hstmt, szSQL, SQL_NTS) != SQL_SUCCESS)
  216. goto error;
  217. if ((sts = SQLExecute (hstmt)) != SQL_SUCCESS)
  218. {
  219. _nativeerrorbox (henv, hdbc, hstmt);
  220. if (sts != SQL_SUCCESS_WITH_INFO)
  221. return;
  222. }
  223. }
  224. [self loadResult];
  225. return;
  226. error:
  227. _nativeerrorbox (henv, hdbc, hstmt);
  228. return;
  229. }
  230. - (void)loadResult
  231. {
  232. SQLTCHAR fetchBuffer[1024];
  233. size_t displayWidth;
  234. short numCols;
  235. unsigned colNum;
  236. SQLTCHAR colName[256];
  237. SQLSMALLINT colType;
  238. SQLULEN colPrecision;
  239. SQLLEN colIndicator;
  240. SQLSMALLINT colScale;
  241. SQLSMALLINT colNullable;
  242. unsigned long totalRows;
  243. NSTableColumn *aColumn;
  244. NSMutableArray *row;
  245. NSFont *fnt = nil; //RSTable.font;
  246. if (!fnt)
  247. fnt = [NSFont systemFontOfSize:11.0];
  248. [self clearGrid];
  249. if (SQLNumResultCols (hstmt, &numCols) != SQL_SUCCESS)
  250. {
  251. _nativeerrorbox (henv, hdbc, hstmt);
  252. goto endCursor;
  253. }
  254. if (numCols == 0)
  255. {
  256. SQLLEN nrows = 0;
  257. SQLRowCount (hstmt, &nrows);
  258. NSRunAlertPanel(@"This operation completed successfully without returning data:",
  259. [NSString stringWithFormat:@"%ld rows affected", (long)nrows],
  260. NULL, NULL, NULL);
  261. goto endCursor;
  262. }
  263. mExistsResultset = YES;
  264. /*
  265. * Get the names for the columns
  266. */
  267. for (colNum = 1; colNum <= numCols; colNum++)
  268. {
  269. /*
  270. * Get the name and other type information
  271. */
  272. if (SQLDescribeCol (hstmt, colNum, (SQLTCHAR *) colName,
  273. sizeof(colName), NULL, &colType, &colPrecision, &colScale,
  274. &colNullable) != SQL_SUCCESS)
  275. {
  276. _nativeerrorbox (henv, hdbc, hstmt);
  277. goto endCursor;
  278. }
  279. /*
  280. * Calculate the display width for the column
  281. */
  282. switch (colType)
  283. {
  284. case SQL_VARCHAR:
  285. case SQL_CHAR:
  286. case SQL_WVARCHAR:
  287. case SQL_WCHAR:
  288. case SQL_GUID:
  289. displayWidth = colPrecision;
  290. break;
  291. case SQL_BINARY:
  292. displayWidth = colPrecision * 2;
  293. break;
  294. case SQL_LONGVARCHAR:
  295. case SQL_WLONGVARCHAR:
  296. case SQL_LONGVARBINARY:
  297. displayWidth = 256; /* show only first 256 */
  298. break;
  299. case SQL_BIT:
  300. displayWidth = 1;
  301. break;
  302. case SQL_TINYINT:
  303. case SQL_SMALLINT:
  304. case SQL_INTEGER:
  305. case SQL_BIGINT:
  306. displayWidth = colPrecision + 1; /* sign */
  307. break;
  308. case SQL_DOUBLE:
  309. case SQL_DECIMAL:
  310. case SQL_NUMERIC:
  311. case SQL_FLOAT:
  312. case SQL_REAL:
  313. displayWidth = colPrecision + 2; /* sign, comma */
  314. break;
  315. #ifdef SQL_TYPE_DATE
  316. case SQL_TYPE_DATE:
  317. #endif
  318. case SQL_DATE:
  319. displayWidth = 10;
  320. break;
  321. #ifdef SQL_TYPE_TIME
  322. case SQL_TYPE_TIME:
  323. #endif
  324. case SQL_TIME:
  325. displayWidth = 8;
  326. break;
  327. #ifdef SQL_TYPE_TIMESTAMP
  328. case SQL_TYPE_TIMESTAMP:
  329. #endif
  330. case SQL_TIMESTAMP:
  331. displayWidth = 19;
  332. if (colScale > 0)
  333. displayWidth = displayWidth + colScale + 1;
  334. break;
  335. default:
  336. displayWidth = 0; /* skip other data types */
  337. continue;
  338. }
  339. if (displayWidth < TEXTLEN(colName))
  340. displayWidth = TEXTLEN(colName);
  341. if (displayWidth > sizeof(fetchBuffer) - 1)
  342. displayWidth = sizeof(fetchBuffer) - 1;
  343. if (displayWidth > MAX_WIDTH)
  344. displayWidth = MAX_WIDTH;
  345. if (mSPARQL_executed && displayWidth <= 10)
  346. displayWidth = MAX_WIDTH;
  347. /* Add new column */
  348. aColumn = [NSTableColumn new];
  349. NSCell *cell = [[[LinkTextFieldCell alloc] init] autorelease];
  350. [aColumn setDataCell:cell];
  351. /* Calculate size of font */
  352. /**
  353. float fontWidth = [fnt boundingRectForFont].size.width;
  354. if (fontWidth == 0.0)
  355. fontWidth = [fnt maximumAdvancement].width;
  356. if (fontWidth == 0.0)
  357. fontWidth = 10.0; // Default
  358. **/
  359. float fontWidth = 10.0;
  360. /* Make sure we always have room for column label */
  361. int colNameLen = TEXTLEN(colName);
  362. colNameLen = colNameLen<MIN_WIDTH?MIN_WIDTH:colNameLen;
  363. displayWidth = displayWidth<MIN_WIDTH?MIN_WIDTH:displayWidth;
  364. float minWidth = fontWidth * (colNameLen<displayWidth?colNameLen:displayWidth);
  365. [aColumn setMinWidth:minWidth];
  366. /* Calculate maximum size of column */
  367. float strWidth = fontWidth * (displayWidth>colNameLen?displayWidth:colNameLen);
  368. [aColumn setMaxWidth:strWidth];
  369. /* Fill in column information */
  370. [aColumn setIdentifier:[NSString stringWithFormat:@"%d", colNum-1]];
  371. [aColumn setEditable:NO];
  372. [aColumn setResizingMask:NSTableColumnAutoresizingMask|NSTableColumnUserResizingMask];
  373. [[aColumn headerCell] setStringValue:TEXTtoNS(colName)];
  374. /* Add to table */
  375. [RSTable addTableColumn:aColumn];
  376. }
  377. [mBuffer removeAllObjects];
  378. /*
  379. * Print all the fields
  380. */
  381. totalRows = 0;
  382. while (totalRows < mMaxRows)
  383. {
  384. int sts = SQLFetch (hstmt);
  385. if (sts == SQL_NO_DATA_FOUND)
  386. break;
  387. if (sts != SQL_SUCCESS)
  388. {
  389. _nativeerrorbox (henv, hdbc, hstmt);
  390. break;
  391. }
  392. row = [NSMutableArray arrayWithCapacity:numCols];
  393. for (colNum = 1; colNum <= numCols; colNum++)
  394. {
  395. /*
  396. * Fetch this column as character
  397. */
  398. sts = SQLGetData (hstmt, colNum, SQL_C_TCHAR, fetchBuffer,
  399. sizeof(fetchBuffer), &colIndicator);
  400. if (sts != SQL_SUCCESS_WITH_INFO && sts != SQL_SUCCESS)
  401. {
  402. _nativeerrorbox (henv, hdbc, hstmt);
  403. goto endCursor;
  404. }
  405. /*
  406. * Show NULL fields as ****
  407. */
  408. if (colIndicator == SQL_NULL_DATA)
  409. TEXTCPY(fetchBuffer, TEXT("<NULL>"));
  410. NSString *val = TEXTtoNS (fetchBuffer);
  411. if (val!=nil && ([val hasPrefix:@"http://"] || [val hasPrefix:@"https://"]))
  412. [row addObject:[NSAttributedString attributedStringWithBlueLink:val]];
  413. else
  414. [row addObject:val?val:@"??"];
  415. }
  416. [mBuffer addObject:row];
  417. totalRows++;
  418. }
  419. mRows = totalRows;
  420. [RSTable reloadData];
  421. if (mSPARQL_executed)
  422. [RSTable sizeToFit];
  423. return;
  424. endCursor:
  425. #if (ODBCVER < 0x0300)
  426. SQLFreeStmt (hstmt, SQL_CLOSE);
  427. #else
  428. SQLCloseCursor (hstmt);
  429. #endif
  430. return;
  431. error:
  432. _nativeerrorbox (henv, hdbc, hstmt);
  433. return;
  434. }
  435. - (IBAction)aCloseConnection:(id)sender
  436. {
  437. [self disconnect];
  438. }
  439. - (IBAction)aExecSQL:(id)sender
  440. {
  441. ExecController *dlg = [[ExecController alloc] init];
  442. dlg.fSQL = _fQuery;
  443. dlg.MaxRows = mMaxRows;
  444. NSInteger rc = [NSApp runModalForWindow:dlg.window];
  445. self.fQuery = dlg.fSQL;
  446. mMaxRows = dlg.MaxRows;
  447. [dlg.window orderOut:mWindow];
  448. [dlg release];
  449. if (rc)
  450. [self execSQL:NStoTEXT(self.fQuery)];
  451. }
  452. - (IBAction)aFetchNextRowset:(id)sender
  453. {
  454. if (mExistsResultset)
  455. {
  456. if (SQLMoreResults (hstmt) == SQL_SUCCESS)
  457. {
  458. [self loadResult];
  459. }
  460. }
  461. }
  462. - (IBAction)aOpenConnection:(id)sender
  463. {
  464. SQLTCHAR szDSN[1024];
  465. SQLTCHAR dataSource[1024];
  466. SQLSMALLINT dsLen;
  467. SQLRETURN status;
  468. #if (ODBCVER < 0x300)
  469. if (SQLAllocEnv (&henv) != SQL_SUCCESS)
  470. #else
  471. if (SQLAllocHandle (SQL_HANDLE_ENV, NULL, &henv) != SQL_SUCCESS)
  472. #endif
  473. {
  474. _nativeerrorbox (henv, SQL_NULL_HDBC, SQL_NULL_HSTMT);
  475. return;
  476. }
  477. # ifdef UNICODE
  478. SQLSetEnvAttr (henv, SQL_ATTR_APP_UNICODE_TYPE,
  479. (SQLPOINTER) SQL_DM_CP_DEF, SQL_IS_UINTEGER);
  480. #endif
  481. #if (ODBCVER < 0x300)
  482. if (SQLAllocConnect (henv, &hdbc) != SQL_SUCCESS)
  483. #else
  484. SQLSetEnvAttr (henv, SQL_ATTR_ODBC_VERSION,
  485. (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
  486. if (SQLAllocHandle (SQL_HANDLE_DBC, henv, &hdbc) != SQL_SUCCESS)
  487. #endif
  488. {
  489. _nativeerrorbox (henv, hdbc, SQL_NULL_HSTMT);
  490. [self disconnect];
  491. return;
  492. }
  493. status = SQLDriverConnect (hdbc, (-1L), TEXT(""), SQL_NTS,
  494. dataSource, sizeof (dataSource), &dsLen, SQL_DRIVER_COMPLETE);
  495. if (status != SQL_SUCCESS)
  496. {
  497. _nativeerrorbox (henv, hdbc, SQL_NULL_HSTMT);
  498. if (status != SQL_SUCCESS_WITH_INFO)
  499. {
  500. [self disconnect];
  501. return;
  502. }
  503. }
  504. mConnected = YES;
  505. SQLGetInfo (hdbc, SQL_DATA_SOURCE_NAME, szDSN, sizeof (szDSN), NULL);
  506. #ifdef UNICODE
  507. [mWindow setTitle:[NSString stringWithFormat:@"iODBC Demo (Unicode) - Connected to [%@]", TEXTtoNS(szDSN)]];
  508. #else
  509. [mWindow setTitle:[NSString stringWithFormat:@"iODBC Demo (Ansi) - Connected to [%@]", TEXTtoNS(szDSN)]];
  510. #endif
  511. #if (ODBCVER < 0x0300)
  512. if (SQLAllocStmt (hdbc, &hstmt) != SQL_SUCCESS)
  513. #else
  514. if (SQLAllocHandle (SQL_HANDLE_STMT, hdbc, &hstmt) != SQL_SUCCESS)
  515. #endif
  516. {
  517. _nativeerrorbox (henv, hdbc, hstmt);
  518. [self disconnect];
  519. return;
  520. }
  521. [RSTable reloadData];
  522. }
  523. // Show/hide menu items
  524. - (BOOL)validateMenuItem:(NSMenuItem *)item
  525. {
  526. int tag = [item tag];
  527. if (tag == OpenTag)
  528. {
  529. return (hdbc == nil ? YES : NO);
  530. }
  531. else if (tag == CloseTag)
  532. {
  533. return (hdbc == nil ? NO : YES);
  534. }
  535. else if (tag == ExecSQLTag)
  536. {
  537. return (hstmt != nil ? YES : NO);
  538. }
  539. else if (tag == NextRowsetTag)
  540. {
  541. return mNextResultset;
  542. }
  543. else
  544. return YES;
  545. }
  546. - (id)tableView:(NSTableView *)aTable objectValueForTableColumn:(NSTableColumn *)aColumn
  547. row:(int)rowIndex
  548. {
  549. NSMutableArray *row;
  550. unsigned id;
  551. if (rowIndex < 0)
  552. return nil;
  553. id = [[aColumn identifier] intValue];
  554. row = [mBuffer objectAtIndex:rowIndex];
  555. return [row objectAtIndex:id];
  556. }
  557. - (int)numberOfRowsInTableView:(NSTableView *)aTable
  558. {
  559. return mRows;
  560. }
  561. /**
  562. - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn
  563. row:(NSInteger)row
  564. {
  565. if ([cell isKindOfClass:[LinkTextFieldCell class]])
  566. {
  567. LinkTextFieldCell *linkCell = (LinkTextFieldCell *)cell;
  568. // Setup the work to be done when a link is clicked
  569. linkCell.clickHandler = ^(NSString *url, id sender) {
  570. };
  571. }
  572. }
  573. **/
  574. @end