├── .gitignore ├── English.lproj ├── InfoPlist.strings └── MainMenu.nib │ ├── designable.nib │ └── keyedobjects.nib ├── Info.plist ├── MBTableGrid Screenshot.png ├── MBTableGrid.h ├── MBTableGrid.m ├── MBTableGrid.xcodeproj ├── TemplateIcon.icns ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── mikecsh.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── mikecsh.xcuserdatad │ └── xcschemes │ ├── MBTableGrid.xcscheme │ ├── MBTableGridDemo.xcscheme │ └── xcschememanagement.plist ├── MBTableGrid ├── MBTableGrid-Info.plist ├── MBTableGrid-Prefix.pch └── en.lproj │ └── InfoPlist.strings ├── MBTableGridCell.h ├── MBTableGridCell.m ├── MBTableGridContentView.h ├── MBTableGridContentView.m ├── MBTableGridController.h ├── MBTableGridController.m ├── MBTableGridHeaderCell.h ├── MBTableGridHeaderCell.m ├── MBTableGridHeaderView.h ├── MBTableGridHeaderView.m ├── MBTableGrid_Prefix.pch ├── README.md └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | *.mode1v3 3 | *.pbxuser 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikecsh/mbtablegrid/de2a3d37fc119a7fb14aa42dc715db85ae333924/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /English.lproj/MainMenu.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikecsh/mbtablegrid/de2a3d37fc119a7fb14aa42dc715db85ae333924/English.lproj/MainMenu.nib/keyedobjects.nib -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.yourcompany.MBTableGrid 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /MBTableGrid Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikecsh/mbtablegrid/de2a3d37fc119a7fb14aa42dc715db85ae333924/MBTableGrid Screenshot.png -------------------------------------------------------------------------------- /MBTableGrid.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import 27 | 28 | @class MBTableGridHeaderView, MBTableGridHeaderCell, MBTableGridContentView; 29 | @protocol MBTableGridDelegate, MBTableGridDataSource; 30 | 31 | /* Notifications */ 32 | 33 | /** 34 | * @brief Posted after an MBTableGrid object's selection changes. 35 | * The notification object is the table grid whose selection 36 | * changed. This notification does not contain a userInfo 37 | * dictionary. 38 | * 39 | * @details This notification will often be posted twice for a single 40 | * selection change (once for the column selection and once 41 | * for the row selection). As such, any methods called in 42 | * response to this notification should be especially efficient. 43 | */ 44 | APPKIT_EXTERN NSString *MBTableGridDidChangeSelectionNotification; 45 | 46 | /** 47 | * @brief Posted after one or more columns are moved by user action 48 | * in an MBTableGrid object. The notification object is 49 | * the table grid in which the column(s) moved. The \c userInfo 50 | * dictionary contains the following information: 51 | * - \c @"OldColumns": An NSIndexSet containing the columns' 52 | * original indices. 53 | * - \c @"NewColumns": An NSIndexSet containing the columns' 54 | * new indices. 55 | */ 56 | APPKIT_EXTERN NSString *MBTableGridDidMoveColumnsNotification; 57 | 58 | /** 59 | * @brief Posted after one or more rows are moved by user action 60 | * in an MBTableGrid object. The notification object is 61 | * the table grid in which the row(s) moved. The userInfo 62 | * dictionary contains the following information: 63 | * - \c @"OldRows": An NSIndexSet containing the rows' 64 | * original indices. 65 | * - \c @"NewRows": An NSIndexSet containing the rows' 66 | * new indices. 67 | */ 68 | APPKIT_EXTERN NSString *MBTableGridDidMoveRowsNotification; 69 | 70 | APPKIT_EXTERN NSString *MBTableGridColumnDataType; 71 | APPKIT_EXTERN NSString *MBTableGridRowDataType; 72 | 73 | typedef enum { 74 | MBTableGridLeftEdge = 0, 75 | MBTableGridRightEdge = 1, 76 | MBTableGridTopEdge = 3, 77 | MBTableGridBottomEdge = 4 78 | } MBTableGridEdge; 79 | 80 | /** 81 | * @brief MBTableGrid (sometimes referred to as a table grid) 82 | * is a means of displaying tabular data in a spreadsheet 83 | * format. 84 | * 85 | * @details An MBTableGrid object must have an object that acts 86 | * as a data source and may optionally have an object which 87 | * acts as a delegate. The data source must adopt the 88 | * MBTableGridDataSource protocol, and the delegate must 89 | * adopt the MBTableGridDelegate protocol. The data source 90 | * provides information that MBTableGrid needs to construct 91 | * the table grid and facillitates the insertion, deletion, and 92 | * reordering of data within it. The delegate optionally provides 93 | * formatting and validation information. For more information 94 | * on these, see the MBTableGridDataSource and MBTableGridDelegate 95 | * protocols. 96 | * 97 | * MBTableGrid and its methods actually encompass 98 | * several subviews, including MBTableGridContentView 99 | * (which handles the display, selection, and editing of 100 | * cells) and MBTableGridHeaderView (which handles 101 | * the display, selection, and dragging of column and 102 | * row headers). In general, however, it is not necessary 103 | * to interact with these views directly. 104 | * 105 | * @author Matthew Ball 106 | */ 107 | @interface MBTableGrid : NSControl { 108 | /* Headers */ 109 | MBTableGridHeaderCell *headerCell; 110 | 111 | /* Data Source */ 112 | IBOutlet id __unsafe_unretained dataSource; 113 | 114 | /* Delegate */ 115 | IBOutlet id __unsafe_unretained delegate; 116 | 117 | /* Headers */ 118 | NSScrollView *columnHeaderScrollView; 119 | MBTableGridHeaderView *columnHeaderView; 120 | NSScrollView *rowHeaderScrollView; 121 | MBTableGridHeaderView *rowHeaderView; 122 | 123 | /* Content */ 124 | NSScrollView *contentScrollView; 125 | MBTableGridContentView *contentView; 126 | 127 | /* Selections */ 128 | NSIndexSet *selectedColumnIndexes; 129 | NSIndexSet *selectedRowIndexes; 130 | 131 | /* Behavior */ 132 | BOOL allowsMultipleSelection; 133 | BOOL shouldOverrideModifiers; 134 | 135 | /* Sticky Edges (for Shift+Arrow expansions) */ 136 | MBTableGridEdge stickyColumnEdge; 137 | MBTableGridEdge stickyRowEdge; 138 | 139 | NSMutableDictionary *columnWidths; 140 | 141 | } 142 | 143 | #pragma mark - 144 | #pragma mark Reloading the Grid 145 | 146 | /** 147 | * @name Reloading the Grid 148 | */ 149 | /** 150 | * @{ 151 | */ 152 | 153 | /** 154 | * @brief Marks the receiver as needing redisplay, so 155 | * it will reload the data for visible cells and 156 | * draw the new values. 157 | * 158 | * @details This method forces redraw of all the visible 159 | * cells in the receiver. If you want to update 160 | * the value in a single cell, column, or row, 161 | * it is more efficient to use \c frameOfCellAtColumn:row:, 162 | * \c rectOfColumn:, or \c rectOfRow: in conjunction with 163 | * \c setNeedsDisplayInRect:. 164 | * 165 | * @see frameOfCellAtColumn:row: 166 | * @see rectOfColumn: 167 | * @see rectOfRow: 168 | */ 169 | - (void)reloadData; 170 | 171 | /** 172 | * @} 173 | */ 174 | 175 | #pragma mark - 176 | #pragma mark Resize column 177 | 178 | /** 179 | * @name Resize column 180 | */ 181 | /** 182 | * @{ 183 | */ 184 | 185 | /** 186 | * @brief Live resizes column 187 | * 188 | * @details This method resizes the column and updates the views 189 | * 190 | */ 191 | - (void) resizeColumnWithIndex:(NSUInteger)columnIndex withDistance:(float)distance; 192 | 193 | /** 194 | * @} 195 | */ 196 | 197 | #pragma mark - 198 | #pragma mark Selecting Columns and Rows 199 | 200 | /** 201 | * @name Selecting Columns and Rows 202 | */ 203 | /** 204 | * @{ 205 | */ 206 | 207 | /** 208 | * @brief Returns an index set containing the indexes of 209 | * the selected columns. 210 | * 211 | * @return An index set containing the indexes of the 212 | * selected columns. 213 | * 214 | * @see selectedRowIndexes 215 | * @see selectCellsInColumns:rows: 216 | */ 217 | @property(nonatomic, strong) NSIndexSet *selectedColumnIndexes; 218 | 219 | /** 220 | * @brief Returns an index set containing the indexes of 221 | * the selected rows. 222 | * 223 | * @return An index set containing the indexes of the 224 | * selected rows. 225 | * 226 | * @see selectedColumnIndexes 227 | * @see selectCellsInColumns:rows: 228 | */ 229 | @property(nonatomic, strong) NSIndexSet *selectedRowIndexes; 230 | 231 | /** 232 | * @} 233 | */ 234 | 235 | #pragma mark - 236 | #pragma mark Dimensions 237 | 238 | /** 239 | * @name Dimensions 240 | */ 241 | /** 242 | * @{ 243 | */ 244 | 245 | /** 246 | * @brief Returns the number of rows in the receiver. 247 | * 248 | * @return The number of rows in the receiver. 249 | * 250 | * @see numberOfColumns 251 | */ 252 | 253 | @property (nonatomic, assign) NSUInteger numberOfRows; 254 | 255 | /** 256 | * @brief Returns the number of columns in the receiver. 257 | * 258 | * @return The number of rows in the receiver. 259 | * 260 | * @see numberOfRows 261 | */ 262 | 263 | @property (nonatomic, assign) NSUInteger numberOfColumns; 264 | 265 | /** 266 | * @} 267 | */ 268 | 269 | #pragma mark - 270 | #pragma mark Configuring Behavior 271 | 272 | /** 273 | * @name Configuring Behavior 274 | */ 275 | /** 276 | * @{ 277 | */ 278 | 279 | /** 280 | * @brief Indicates whether the receiver allows 281 | * the user to select more than one cell at 282 | * a time. 283 | * 284 | * @details The default is \c YES. You can select multiple 285 | * cells programmatically regardless of this setting. 286 | * 287 | * @return \c YES if the receiver allows the user 288 | * to select more than one cell at a time. 289 | * Otherwise, \c NO. 290 | */ 291 | @property(assign) BOOL allowsMultipleSelection; 292 | 293 | /** 294 | * @} 295 | */ 296 | 297 | #pragma mark - 298 | #pragma mark Managing the Delegate and the Data Source 299 | /** 300 | * @name Managing the Delegate and the Data Source 301 | */ 302 | /** 303 | * @{ 304 | */ 305 | 306 | /** 307 | * @brief The object that provides the data displayed by 308 | * the grid. 309 | * 310 | * @details The data source must adopt the \c MBTableGridDataSource 311 | * protocol. The data source is not retained. 312 | * 313 | * @see delegate 314 | */ 315 | @property(unsafe_unretained) id dataSource; 316 | 317 | /** 318 | * @brief The object that acts as the delegate of the 319 | * receiving table grid. 320 | * 321 | * @details The delegate must adopt the \c MBTableGridDelegate 322 | * protocol. The delegate is not retained. 323 | * 324 | * @see dataSource 325 | */ 326 | @property(nonatomic, unsafe_unretained) id delegate; 327 | 328 | /** 329 | * @} 330 | */ 331 | 332 | #pragma mark - 333 | #pragma mark Layout Support 334 | /** 335 | * @name Layout Support 336 | */ 337 | /** 338 | * @{ 339 | */ 340 | 341 | /** 342 | * @brief Returns the rectangle containing the column at 343 | * a given index. 344 | * 345 | * @param columnIndex The index of a column in the receiver. 346 | * 347 | * @return The rectangle containing the column at \c columnIndex. 348 | * Returns \c NSZeroRect if \c columnIndex lies outside 349 | * the range of valid column indices for the receiver. 350 | * 351 | * @see frameOfCellAtColumn:row: 352 | * @see rectOfRow: 353 | * @see headerRectOfColumn: 354 | */ 355 | - (NSRect)rectOfColumn:(NSUInteger)columnIndex; 356 | 357 | /** 358 | * @brief Returns the rectangle containing the row at a 359 | * given index. 360 | * 361 | * @param rowIndex The index of a row in the receiver. 362 | * 363 | * @return The rectangle containing the row at \c rowIndex. 364 | * Returns \c NSZeroRect if \c rowIndex lies outside 365 | * the range of valid column indices for the receiver. 366 | * 367 | * @see frameOfCellAtColumn:row: 368 | * @see rectOfColumn: 369 | * @see headerRectOfRow: 370 | */ 371 | - (NSRect)rectOfRow:(NSUInteger)rowIndex; 372 | 373 | /** 374 | * @brief Returns a rectangle locating the cell that lies at 375 | * the intersection of \c columnIndex and \c rowIndex. 376 | * 377 | * @param columnIndex The index of the column containing the cell 378 | * whose rectangle you want. 379 | * @param rowIndex The index of the row containing the cell 380 | * whose rectangle you want. 381 | * 382 | * @return A rectangle locating the cell that lies at the intersection 383 | * of \c columnIndex and \c rowIndex. Returns \c NSZeroRect if 384 | * \c columnIndex or \c rowIndex is greater than the number of 385 | * columns or rows in the receiver. 386 | * 387 | * @see rectOfColumn: 388 | * @see rectOfRow: 389 | * @see headerRectOfColumn: 390 | * @see headerRectOfRow: 391 | */ 392 | - (NSRect)frameOfCellAtColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 393 | 394 | /** 395 | * @brief Returns the rectangle containing the header tile for 396 | * the column at \c columnIndex. 397 | * 398 | * @param columnIndex The index of the column containing the 399 | * header whose rectangle you want. 400 | * 401 | * @return A rectangle locating the header for the column at 402 | * \c columnIndex. Returns \c NSZeroRect if \c columnIndex 403 | * lies outside the range of valid column indices for the 404 | * receiver. 405 | * 406 | * @see headerRectOfRow: 407 | * @see headerRectOfCorner 408 | */ 409 | - (NSRect)headerRectOfColumn:(NSUInteger)columnIndex; 410 | 411 | /** 412 | * @brief Returns the rectangle containing the header tile for 413 | * the row at \c rowIndex. 414 | * 415 | * @param rowIndex The index of the row containing the 416 | * header whose rectangle you want. 417 | * 418 | * @return A rectangle locating the header for the row at 419 | * \c rowIndex. Returns \c NSZeroRect if \c rowIndex 420 | * lies outside the range of valid column indices for the 421 | * receiver. 422 | * 423 | * @see headerRectOfColumn: 424 | * @see headerRectOfCorner 425 | */ 426 | - (NSRect)headerRectOfRow:(NSUInteger)rowIndex; 427 | 428 | /** 429 | * @brief Returns the rectangle containing the corner which 430 | * divides the row headers from the column headers. 431 | * 432 | * @return A rectangle locating the corner separating rows 433 | * from columns. 434 | * 435 | * @see headerRectOfColumn: 436 | * @see headerRectOfRow: 437 | */ 438 | - (NSRect)headerRectOfCorner; 439 | 440 | /** 441 | * @brief Returns the index of the column a given point lies in. 442 | * 443 | * @param aPoint A point in the coordinate system of the receiver. 444 | * 445 | * @return The index of the column \c aPoint lies in, or \c NSNotFound if \c aPoint 446 | * lies outside the receiver's bounds. 447 | * 448 | * @see rowAtPoint: 449 | */ 450 | - (NSInteger)columnAtPoint:(NSPoint)aPoint; 451 | 452 | /** 453 | * @brief Returns the index of the row a given point lies in. 454 | * 455 | * @param aPoint A point in the coordinate system of the receiver. 456 | * 457 | * @return The index of the row \c aPoint lies in, or \c NSNotFound if \c aPoint 458 | * lies outside the receiver's bounds. 459 | * 460 | * @see columnAtPoint: 461 | */ 462 | - (NSInteger)rowAtPoint:(NSPoint)aPoint; 463 | 464 | /** 465 | * @} 466 | */ 467 | 468 | #pragma mark - 469 | #pragma mark Auxiliary Views 470 | /** 471 | * @name Auxiliary Views 472 | */ 473 | /** 474 | * @{ 475 | */ 476 | 477 | /** 478 | * @brief Returns the \c MBTableGridHeaderView object used 479 | * to draw headers over columns. 480 | * 481 | * @return The \c MBTableGridHeaderView object used to draw 482 | * column headers. 483 | * 484 | * @see rowHeaderView 485 | */ 486 | - (MBTableGridHeaderView *)columnHeaderView; 487 | 488 | /** 489 | * @brief Returns the \c MBTableGridHeaderView object used 490 | * to draw headers beside rows. 491 | * 492 | * @return The \c MBTableGridHeaderView object used to draw 493 | * column headers. 494 | * 495 | * @see columnHeaderView 496 | */ 497 | - (MBTableGridHeaderView *)rowHeaderView; 498 | 499 | /** 500 | * @brief Returns the receiver's content view. 501 | * 502 | * @details An \c MBTableGrid object uses its content view to 503 | * draw the individual cells. It is enclosed in a 504 | * scroll view to allow for scrolling. 505 | * 506 | * @return The receiver's content view. 507 | */ 508 | - (MBTableGridContentView *)contentView; 509 | 510 | /** 511 | * @} 512 | */ 513 | 514 | @end 515 | 516 | #pragma mark - 517 | 518 | /** 519 | * @brief The \c MBTableGridDataSource protocol is adopted 520 | * by an object that mediates the data model for an 521 | * \c MBTableGrid object. 522 | * 523 | * @details As a representative of the data model, the data 524 | * source supplies no information about the grid's 525 | * appearance or behavior. Rather, the grid's 526 | * delegate (adopting the \c MBTableGridDelegate 527 | * protocol) can provide that information. 528 | */ 529 | @protocol MBTableGridDataSource 530 | 531 | @required 532 | 533 | #pragma mark - 534 | #pragma mark Dimensions 535 | 536 | /** 537 | * @name Dimensions 538 | */ 539 | /** 540 | * @{ 541 | */ 542 | 543 | @required 544 | 545 | /** 546 | * @brief Returns the number of rows managed for \c aTableGrid 547 | * by the data source object. 548 | * 549 | * @param aTableGrid The table grid that sent the message. 550 | * 551 | * @return The number of rows in \c aTableGrid. 552 | * 553 | * @see numberOfColumnsInTableGrid: 554 | */ 555 | - (NSUInteger)numberOfRowsInTableGrid:(MBTableGrid *)aTableGrid; 556 | 557 | /** 558 | * @brief Returns the number of rows managed for \c aTableGrid 559 | * by the data source object. 560 | * 561 | * @param aTableGrid The table grid that sent the message. 562 | * 563 | * @return The number of rows in \c aTableGrid. 564 | * 565 | * @see numberOfRowsInTableGrid: 566 | */ 567 | - (NSUInteger)numberOfColumnsInTableGrid:(MBTableGrid *)aTableGrid; 568 | 569 | /** 570 | * @} 571 | */ 572 | 573 | /** 574 | * @name Accessing Cell Values 575 | */ 576 | /** 577 | * @{ 578 | */ 579 | 580 | @required 581 | 582 | /** 583 | * @brief Returns the data object associated with the specified column and row. 584 | * 585 | * @param aTableGrid The table grid that sent the message. 586 | * @param columnIndex A column in \c aTableGrid. 587 | * @param rowIndex A row in \c aTableGrid. 588 | * 589 | * @return The object for the specified cell of the view. 590 | * 591 | * @see tableGrid:setObjectValue:forColumn:row: 592 | */ 593 | - (id)tableGrid:(MBTableGrid *)aTableGrid objectValueForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 594 | 595 | @optional 596 | 597 | /** 598 | * @brief Returns the background color for the specified column and row. 599 | * 600 | * @param aTableGrid The table grid that sent the message. 601 | * @param columnIndex A column in \c aTableGrid. 602 | * @param rowIndex A row in \c aTableGrid. 603 | * 604 | * @return The background color for the specified cell of the view. 605 | */ 606 | - (NSColor *)tableGrid:(MBTableGrid *)aTableGrid backgroundColorForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 607 | 608 | @optional 609 | 610 | /** 611 | * @brief Sets the sata object for an item in a given row in a given column. 612 | * 613 | * @details Although this method is optional, it must be implemented in order 614 | * to enable grid editing. 615 | * 616 | * @param aTableGrid The table grid that sent the message. 617 | * @param anObject The new value for the item. 618 | * @param columnIndex A column in \c aTableGrid. 619 | * @param rowIndex A row in \c aTableGrid. 620 | * 621 | * @see tableGrid:objectValueForColumn:row: 622 | */ 623 | - (void)tableGrid:(MBTableGrid *)aTableGrid setObjectValue:(id)anObject forColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 624 | 625 | @optional 626 | 627 | /** 628 | * @brief Returnd the width of given column. 629 | * 630 | * @param aTableGrid The table grid that sent the message. 631 | * @param columnIndex A column in \c aTableGrid. 632 | * 633 | * @see tableGrid:setWidthForColumn: 634 | */ 635 | - (float)tableGrid:(MBTableGrid *)aTableGrid withForColumn:(NSUInteger)columnIndex; 636 | 637 | @optional 638 | 639 | /** 640 | * @brief Sets the column width for the given column. 641 | * 642 | * @param aTableGrid The table grid that sent the message. 643 | * @param columnIndex A column in \c aTableGrid. 644 | * 645 | * @see tableGrid:widthForColumn: 646 | */ 647 | - (float)tableGrid:(MBTableGrid *)aTableGrid setWidthForColumn:(NSUInteger)columnIndex; 648 | 649 | /** 650 | * @} 651 | */ 652 | 653 | /** 654 | * @name Header Values 655 | */ 656 | /** 657 | * @{ 658 | */ 659 | 660 | @optional 661 | 662 | /** 663 | * @brief Returns the value which should be displayed in the header 664 | * for the specified column. 665 | * 666 | * @param aTableGrid The table grid that sent the message. 667 | * @param columnIndex The index of the column. 668 | * 669 | * @return The header value for the column. 670 | * 671 | * @see tableGrid:headerStringForRow: 672 | */ 673 | - (NSString *)tableGrid:(MBTableGrid *)aTableGrid headerStringForColumn:(NSUInteger)columnIndex; 674 | 675 | /** 676 | * @brief Returns the value which should be displayed in the header 677 | * for the specified row. 678 | * 679 | * @param aTableGrid The table grid that sent the message. 680 | * @param rowIndex The index of the row. 681 | * 682 | * @return The header value for the row. 683 | * 684 | * @see tableGrid:headerStringForColumn: 685 | */ 686 | - (NSString *)tableGrid:(MBTableGrid *)aTableGrid headerStringForRow:(NSUInteger)rowIndex; 687 | 688 | /** 689 | * @} 690 | */ 691 | 692 | #pragma mark - 693 | #pragma mark Dragging 694 | 695 | /** 696 | * @name Dragging 697 | */ 698 | /** 699 | * @{ 700 | */ 701 | 702 | @optional 703 | 704 | #pragma mark Columns 705 | 706 | /** 707 | * @brief Returns a Boolean value that indicates whether a column drag operation is allowed. 708 | * 709 | * @details Invoked by \c aTableGrid after it has been determined that a drag should begin, 710 | * but before the drag has been started. 711 | * 712 | * To refuse the drag, return \c NO. To start a drag, return \c YES and place the 713 | * drag data onto pboard. 714 | * 715 | * If this method returns \c YES, the table grid will automatically place the 716 | * relavent information onto the pasteboard for simply reordering columns. Therefore, 717 | * you only need to place data onto the pasteboard if you want to enable some other 718 | * kind of dragging behavior (such as dragging into the finder as a CSV file). 719 | * 720 | * @param aTableGrid The table grid that sent the message. 721 | * @param columnIndexes An index set of column numbers that will be participating 722 | * in the drag. 723 | * @param pboard The pasteboard to which to write the drag data. 724 | * 725 | * @return \c YES if the drag operation is allowed, \c NO otherwise. 726 | * 727 | * @see tableGrid:writeColumnsWithIndexes:toPasteboard: 728 | */ 729 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid writeColumnsWithIndexes:(NSIndexSet *)columnIndexes toPasteboard:(NSPasteboard *)pboard; 730 | 731 | /** 732 | * @brief Returns a Boolean value indicating whether the proposed columns can be 733 | * moved to the specified index. 734 | * 735 | * @details This method is invoked by \c MBTableGrid during drag operations. It allows 736 | * the data source to define valid drop targets for columns. 737 | * 738 | * @param aTableGrid The table grid that sent the message. 739 | * @param columnIndexes An index set describing the columns which 740 | * are currently being dragged. 741 | * @param index The proposed index where the columns should 742 | * be moved. 743 | * 744 | * @return \c YES if \c columnIndex is a valid drop target, \c NO otherwise. 745 | * 746 | * @see tableGrid:moveColumns:toIndex: 747 | * @see tableGrid:canMoveRows:toIndex: 748 | */ 749 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid canMoveColumns:(NSIndexSet *)columnIndexes toIndex:(NSUInteger)index; 750 | 751 | /** 752 | * @brief Returns a Boolean value indicating whether the proposed columns 753 | * were moved to the specified index. 754 | * 755 | * @details The data source should take care of modifiying the data model to 756 | * reflect the changed column order. 757 | * 758 | * @param aTableGrid The table grid that sent the message. 759 | * @param columnIndexes An index set describing the columns which were dragged. 760 | * @param index The index where the columns should be moved. 761 | * 762 | * @return \c YES if the move was successful, otherwise \c NO. 763 | * 764 | * @see tableGrid:canMoveColumns:toIndex: 765 | * @see tableGrid:moveRows:toIndex: 766 | */ 767 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid moveColumns:(NSIndexSet *)columnIndexes toIndex:(NSUInteger)index; 768 | 769 | #pragma mark Rows 770 | 771 | /** 772 | * @brief Returns a Boolean value that indicates whether a row drag operation is allowed. 773 | * 774 | * @details Invoked by \c aTableGrid after it has been determined that a drag should begin, 775 | * but before the drag has been started. 776 | * 777 | * To refuse the drag, return \c NO. To start a drag, return \c YES and place the 778 | * drag data onto \c pboard. 779 | * 780 | * If this method returns \c YES, the table grid will automatically place the 781 | * relavent information onto the pasteboard for simply reordering rows. Therefore, 782 | * you only need to place data onto the pasteboard if you want to enable some other 783 | * kind of dragging behavior (such as dragging into the finder as a CSV). 784 | * 785 | * @param aTableGrid The table grid that sent the message. 786 | * @param rowIndexes An index set of row numbers that will be participating 787 | * in the drag. 788 | * @param pboard The pasteboard to which to write the drag data. 789 | * 790 | * @return \c YES if the drag operation is allowed, \c NO otherwise. 791 | * 792 | * @see tableGrid:writeColumnsWithIndexes:toPasteboard: 793 | */ 794 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard; 795 | 796 | /** 797 | * @brief Returns a Boolean value indicating whether the proposed rows can be 798 | * moved to the specified index. 799 | * 800 | * @details This method is invoked by \c MBTableGrid during drag operations. It allows 801 | * the data source to define valid drop targets for rows. 802 | * 803 | * @param aTableGrid The table grid that sent the message. 804 | * @param rowIndexes An index set describing the rows which 805 | * are currently being dragged. 806 | * @param index The proposed index where the rows should 807 | * be moved. 808 | * 809 | * @return \c YES if \c rowIndex is a valid drop target, \c NO otherwise. 810 | * 811 | * @see tableGrid:moveRows:toIndex: 812 | * @see tableGrid:canMoveColumns:toIndex: 813 | */ 814 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid canMoveRows:(NSIndexSet *)rowIndexes toIndex:(NSUInteger)index; 815 | 816 | /** 817 | * @brief Returns a Boolean value indicating whether the proposed rows 818 | * were moved to the specified index. 819 | * 820 | * @details The data source should take care of modifiying the data model to 821 | * reflect the changed row order. 822 | * 823 | * @param aTableGrid The table grid that sent the message. 824 | * @param rowIndexes An index set describing the rows which were dragged. 825 | * @param index The index where the rows should be moved. 826 | * 827 | * @return \c YES if the move was successful, otherwise \c NO. 828 | * 829 | * @see tableGrid:canMoveRows:toIndex: 830 | * @see tableGrid:moveColumns:toIndex: 831 | */ 832 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid moveRows:(NSIndexSet *)rowIndexes toIndex:(NSUInteger)index; 833 | 834 | #pragma mark Other Values 835 | 836 | /** 837 | * @brief Used by \c aTableGrid to determine a valid drop target. 838 | * 839 | * @param aTableGrid The table grid that sent the message. 840 | * @param info An object that contains more information about 841 | * this dragging operation. 842 | * @param columnIndex The index of the proposed target column. 843 | * @param rowIndex The index of the proposed target row. 844 | * 845 | * @return The dragging operation the data source will perform. 846 | * 847 | * @see tableGrid:acceptDrop:column:row: 848 | */ 849 | - (NSDragOperation)tableGrid:(MBTableGrid *)aTableGrid validateDrop:(id )info proposedColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 850 | 851 | /** 852 | * @brief Invoked by \c aTableGrid when the mouse button is released over 853 | * a table grid that previously decided to allow a drop. 854 | * 855 | * @details The data source should incorporate the data from the dragging 856 | * pasteboard in the implementation of this method. You can get the 857 | * data for the drop operation from \c info using the \c draggingPasteboard 858 | * method. 859 | * 860 | * @param aTableGrid The table grid that sent the message. 861 | * @param info An object that contains more information about 862 | * this dragging operation. 863 | * @param columnIndex The index of the proposed target column. 864 | * @param rowIndex The index of the proposed target row. 865 | * 866 | * @return \c YES if the drop was successful, otherwise \c NO. 867 | * 868 | * @see tableGrid:validateDrop:proposedColumn:row: 869 | */ 870 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid acceptDrop:(id )info column:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 871 | 872 | /** 873 | * @} 874 | */ 875 | 876 | @end 877 | 878 | #pragma mark - 879 | 880 | /** 881 | * @brief The delegate of an \c MBTableGrid object must adopt the 882 | * \c MBTableGridDelegate protocol. Optional methods of the 883 | * protocol allow the delegate to validate selections and data 884 | * modifications and provide formatting information. 885 | */ 886 | @protocol MBTableGridDelegate 887 | 888 | // Being a delegate, the entire protocol is optional 889 | @optional 890 | 891 | #pragma mark - 892 | #pragma mark Managing Selections 893 | /** 894 | * @name Managing Selections 895 | */ 896 | /** 897 | * @{ 898 | */ 899 | 900 | /** 901 | * @brief Tells the delegate that the specified columns are about to be 902 | * selected. 903 | * 904 | * @param aTableGrid The table grid object informing the delegate 905 | * about the impending selection. 906 | * @param indexPath An index path locating the columns in \c aTableGrid. 907 | * 908 | * @return An index path which confirms or alters the impending selection. 909 | * Return an \c NSIndexPath object other than \c indexPath if you want 910 | * different columns to be selected. 911 | * 912 | * @see tableGrid:willSelectRowsAtIndexPath: 913 | */ 914 | - (NSIndexSet *)tableGrid:(MBTableGrid *)aTableGrid willSelectColumnsAtIndexPath:(NSIndexSet *)indexPath; 915 | 916 | /** 917 | * @brief Tells the delegate that the specified rows are about to be 918 | * selected. 919 | * 920 | * @param aTableGrid The table grid object informing the delegate 921 | * about the impending selection. 922 | * @param indexPath An index path locating the rows in \c aTableGrid. 923 | * 924 | * @return An index path which confirms or alters the impending selection. 925 | * Return an \c NSIndexPath object other than \c indexPath if you want 926 | * different rows to be selected. 927 | * 928 | * @see tableGrid:willSelectColumnsAtIndexPath: 929 | */ 930 | - (NSIndexSet *)tableGrid:(MBTableGrid *)aTableGrid willSelectRowsAtIndexPath:(NSIndexSet *)indexPath; 931 | 932 | /** 933 | * @brief Informs the delegate that the table grid's selection has changed. 934 | * 935 | * @details \c aNotification is an \c MBTableGridDidChangeSelectionNotification. 936 | */ 937 | - (void)tableGridDidChangeSelection:(NSNotification *)aNotification; 938 | 939 | /** 940 | * @} 941 | */ 942 | 943 | #pragma mark - 944 | #pragma mark Moving Columns and Rows 945 | 946 | /** 947 | * @name Moving Columns and Rows 948 | */ 949 | /** 950 | * @{ 951 | */ 952 | 953 | /** 954 | * @brief Informs the delegate that columns were moved by user action in 955 | * the table grid. 956 | * 957 | * @details \c aNotification is an \c MBTableGridDidMoveColumnsNotification. 958 | * 959 | * @see tableGridDidMoveRows: 960 | */ 961 | - (void)tableGridDidMoveColumns:(NSNotification *)aNotification; 962 | 963 | /** 964 | * @brief Informs the delegate that rows were moved by user action in 965 | * the table grid. 966 | * 967 | * @details \c aNotification is an \c MBTableGridDidMoveRowsNotification. 968 | * 969 | * @see tableGridDidMoveColumns: 970 | */ 971 | - (void)tableGridDidMoveRows:(NSNotification *)aNotification; 972 | 973 | /** 974 | * @} 975 | */ 976 | 977 | #pragma mark - 978 | #pragma mark Editing Cells 979 | 980 | /** 981 | * @name Editing Cells 982 | */ 983 | /** 984 | * @{ 985 | */ 986 | 987 | /** 988 | * @brief Asks the delegate if the specified cell can be edited. 989 | * 990 | * @details The delegate can implement this method to disallow editing of 991 | * specific cells. 992 | * 993 | * @param aTableGrid The table grid which will edit the cell. 994 | * @param columnIndex The column of the cell. 995 | * @param rowIndex The row of the cell. 996 | * 997 | * @return \c YES to permit \c aTableGrid to edit the specified cell, \c NO to deny permission. 998 | */ 999 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid shouldEditColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 1000 | 1001 | /** 1002 | * @} 1003 | */ 1004 | 1005 | @end 1006 | -------------------------------------------------------------------------------- /MBTableGrid.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import "MBTableGrid.h" 27 | #import "MBTableGridHeaderView.h" 28 | #import "MBTableGridHeaderCell.h" 29 | #import "MBTableGridContentView.h" 30 | #import "MBTableGridCell.h" 31 | 32 | #pragma mark - 33 | #pragma mark Constant Definitions 34 | NSString *MBTableGridDidChangeSelectionNotification = @"MBTableGridDidChangeSelectionNotification"; 35 | NSString *MBTableGridDidMoveColumnsNotification = @"MBTableGridDidMoveColumnsNotification"; 36 | NSString *MBTableGridDidMoveRowsNotification = @"MBTableGridDidMoveRowsNotification"; 37 | CGFloat MBTableHeaderMinimumColumnWidth = 20.0f; 38 | 39 | #pragma mark - 40 | #pragma mark Drag Types 41 | NSString *MBTableGridColumnDataType = @"MBTableGridColumnDataType"; 42 | NSString *MBTableGridRowDataType = @"MBTableGridRowDataType"; 43 | 44 | @interface MBTableGrid (Drawing) 45 | - (void)_drawColumnHeaderBackgroundInRect:(NSRect)aRect; 46 | - (void)_drawRowHeaderBackgroundInRect:(NSRect)aRect; 47 | - (void)_drawCornerHeaderBackgroundInRect:(NSRect)aRect; 48 | @end 49 | 50 | @interface MBTableGrid (DataAccessors) 51 | - (NSString *)_headerStringForColumn:(NSUInteger)columnIndex; 52 | - (NSString *)_headerStringForRow:(NSUInteger)rowIndex; 53 | - (id)_objectValueForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 54 | - (void)_setObjectValue:(id)value forColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 55 | - (float)_widthForColumn:(NSUInteger)columnIndex; 56 | - (float)_setWidthForColumn:(NSUInteger)columnIndex; 57 | - (id)_backgroundColorForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 58 | - (BOOL)_canEditCellAtColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 59 | @end 60 | 61 | @interface MBTableGrid (DragAndDrop) 62 | - (void)_dragColumnsWithEvent:(NSEvent *)theEvent; 63 | - (void)_dragRowsWithEvent:(NSEvent *)theEvent; 64 | - (NSImage *)_imageForSelectedColumns; 65 | - (NSImage *)_imageForSelectedRows; 66 | - (NSUInteger)_dropColumnForPoint:(NSPoint)aPoint; 67 | - (NSUInteger)_dropRowForPoint:(NSPoint)aPoint; 68 | @end 69 | 70 | @interface MBTableGrid (PrivateAccessors) 71 | - (MBTableGridContentView *)_contentView; 72 | - (void)_setStickyColumn:(MBTableGridEdge)stickyColumn row:(MBTableGridEdge)stickyRow; 73 | - (MBTableGridEdge)_stickyColumn; 74 | - (MBTableGridEdge)_stickyRow; 75 | @end 76 | 77 | @interface MBTableGridContentView (Private) 78 | - (void)_setDraggingColumnOrRow:(BOOL)flag; 79 | - (void)_setDropColumn:(NSInteger)columnIndex; 80 | - (void)_setDropRow:(NSInteger)rowIndex; 81 | @end 82 | 83 | 84 | @implementation MBTableGrid 85 | 86 | @synthesize allowsMultipleSelection; 87 | @synthesize dataSource; 88 | @synthesize delegate; 89 | @synthesize selectedColumnIndexes; 90 | @synthesize selectedRowIndexes; 91 | 92 | 93 | #pragma mark - 94 | #pragma mark Initialization & Superclass Overrides 95 | 96 | - (id)initWithFrame:(NSRect)frameRect 97 | { 98 | if(self = [super initWithFrame:frameRect]) { 99 | 100 | columnWidths = [NSMutableDictionary dictionary]; 101 | 102 | // Post frame changed notifications 103 | [self setPostsFrameChangedNotifications:YES]; 104 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewFrameDidChange:) name:NSViewFrameDidChangeNotification object:self]; 105 | 106 | // Set the default cell 107 | MBTableGridCell *defaultCell = [[MBTableGridCell alloc] initTextCell:@""]; 108 | [defaultCell setBordered:YES]; 109 | [defaultCell setScrollable:YES]; 110 | [defaultCell setLineBreakMode:NSLineBreakByTruncatingTail]; 111 | [self setCell:defaultCell]; 112 | 113 | // Setup the column headers 114 | NSRect columnHeaderFrame = NSMakeRect(MBTableGridRowHeaderWidth, 0, frameRect.size.width-MBTableGridRowHeaderWidth, MBTableGridColumnHeaderHeight); 115 | 116 | columnHeaderScrollView = [[NSScrollView alloc] initWithFrame:columnHeaderFrame]; 117 | columnHeaderView = [[MBTableGridHeaderView alloc] initWithFrame:NSMakeRect(0,0,columnHeaderFrame.size.width,columnHeaderFrame.size.height)]; 118 | // [columnHeaderView setAutoresizingMask:NSViewWidthSizable]; 119 | [columnHeaderView setOrientation:MBTableHeaderHorizontalOrientation]; 120 | [columnHeaderScrollView setDocumentView:columnHeaderView]; 121 | [columnHeaderScrollView setAutoresizingMask:NSViewWidthSizable]; 122 | [columnHeaderScrollView setDrawsBackground:NO]; 123 | [self addSubview:columnHeaderScrollView]; 124 | 125 | // Setup the row headers 126 | NSRect rowHeaderFrame = NSMakeRect(0, MBTableGridColumnHeaderHeight, MBTableGridRowHeaderWidth, [self frame].size.height-MBTableGridColumnHeaderHeight); 127 | rowHeaderScrollView = [[NSScrollView alloc] initWithFrame:rowHeaderFrame]; 128 | rowHeaderView = [[MBTableGridHeaderView alloc] initWithFrame:NSMakeRect(0,0,rowHeaderFrame.size.width,rowHeaderFrame.size.height)]; 129 | //[rowHeaderView setAutoresizingMask:NSViewHeightSizable]; 130 | [rowHeaderView setOrientation:MBTableHeaderVerticalOrientation]; 131 | [rowHeaderScrollView setDocumentView:rowHeaderView]; 132 | [rowHeaderScrollView setAutoresizingMask:NSViewHeightSizable]; 133 | [rowHeaderScrollView setDrawsBackground:NO]; 134 | [self addSubview:rowHeaderScrollView]; 135 | 136 | // Setup the content view 137 | NSRect contentFrame = NSMakeRect(MBTableGridRowHeaderWidth, MBTableGridColumnHeaderHeight, [self frame].size.width-MBTableGridRowHeaderWidth, [self frame].size.height-MBTableGridColumnHeaderHeight); 138 | contentScrollView = [[NSScrollView alloc] initWithFrame:contentFrame]; 139 | contentView = [[MBTableGridContentView alloc] initWithFrame:NSMakeRect(0,0,contentFrame.size.width,contentFrame.size.height)]; 140 | [contentScrollView setDocumentView:contentView]; 141 | [contentScrollView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)]; 142 | [contentScrollView setHasHorizontalScroller:YES]; 143 | [contentScrollView setHasVerticalScroller:YES]; 144 | [contentScrollView setAutohidesScrollers:YES]; 145 | [self addSubview:contentScrollView]; 146 | 147 | // We want to synchronize the scroll views 148 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contentViewDidScroll:) name:NSViewBoundsDidChangeNotification object:[contentScrollView contentView]]; 149 | 150 | // Set the default selection 151 | self.selectedColumnIndexes = [NSIndexSet indexSetWithIndex:0]; 152 | self.selectedRowIndexes = [NSIndexSet indexSetWithIndex:0]; 153 | self.allowsMultipleSelection = YES; 154 | 155 | // Set the default sticky edges 156 | stickyColumnEdge = MBTableGridLeftEdge; 157 | stickyRowEdge = MBTableGridTopEdge; 158 | 159 | shouldOverrideModifiers = NO; 160 | } 161 | return self; 162 | } 163 | 164 | - (void)awakeFromNib 165 | { 166 | // [self reloadData]; 167 | [self registerForDraggedTypes:nil]; 168 | } 169 | 170 | - (void)dealloc 171 | { 172 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 173 | } 174 | 175 | - (BOOL)isFlipped 176 | { 177 | return YES; 178 | } 179 | 180 | - (BOOL)canBecomeKeyView 181 | { 182 | return YES; 183 | } 184 | 185 | - (BOOL)acceptsFirstResponder 186 | { 187 | return YES; 188 | } 189 | 190 | - (void)drawRect:(NSRect)aRect 191 | { 192 | // If the view is the first responder, draw the focus ring 193 | NSResponder *firstResponder = [[self window] firstResponder]; 194 | if (([[firstResponder class] isSubclassOfClass:[NSView class]] && [(NSView *)firstResponder isDescendantOf:self]) && [[self window] isKeyWindow]) { 195 | [[NSGraphicsContext currentContext] saveGraphicsState]; 196 | NSSetFocusRingStyle(NSFocusRingOnly); 197 | 198 | [[NSBezierPath bezierPathWithRect:NSMakeRect(0,0,[self frame].size.width,[self frame].size.height)] fill]; 199 | 200 | [[NSGraphicsContext currentContext] restoreGraphicsState]; 201 | [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]]; 202 | } 203 | 204 | // Draw the corner header 205 | NSRect cornerRect = [self headerRectOfCorner]; 206 | [self _drawCornerHeaderBackgroundInRect:cornerRect]; 207 | 208 | // Draw the column header background 209 | NSRect columnHeaderRect = NSMakeRect(NSWidth(cornerRect), 0, [self frame].size.width-NSWidth(cornerRect), MBTableGridColumnHeaderHeight); 210 | [self _drawColumnHeaderBackgroundInRect:columnHeaderRect]; 211 | 212 | // Draw the row header background 213 | NSRect rowHeaderRect = NSMakeRect(0, NSMaxY(cornerRect), MBTableGridRowHeaderWidth, [self frame].size.height - MBTableGridColumnHeaderHeight); 214 | [self _drawRowHeaderBackgroundInRect:rowHeaderRect]; 215 | } 216 | 217 | #pragma mark Resize scrollview content size 218 | 219 | - (void) resizeColumnWithIndex:(NSUInteger)columnIndex withDistance:(float)distance 220 | { 221 | 222 | // Get column key 223 | NSString *columnKey = [NSString stringWithFormat:@"column%lu", columnIndex]; 224 | 225 | // Set new width of column 226 | float currentWidth = [columnWidths[columnKey] floatValue]; 227 | currentWidth += distance; 228 | 229 | if (currentWidth < MBTableHeaderMinimumColumnWidth) { 230 | currentWidth = MBTableHeaderMinimumColumnWidth; 231 | } 232 | 233 | columnWidths[columnKey] = @(currentWidth); 234 | 235 | // Update views with new sizes 236 | [contentView setFrameSize:NSMakeSize(NSWidth(contentView.frame) + distance, NSHeight(contentView.frame))]; 237 | [columnHeaderView setFrameSize:NSMakeSize(NSWidth(columnHeaderView.frame) + distance, NSHeight(columnHeaderView.frame))]; 238 | [contentView setNeedsDisplay:YES]; 239 | [columnHeaderView setNeedsDisplayInRect:columnHeaderView.frame]; 240 | 241 | } 242 | 243 | 244 | - (void)registerForDraggedTypes:(NSArray *)pboardTypes 245 | { 246 | // Add the column and row types to the array 247 | NSMutableArray *types = [NSMutableArray arrayWithArray:pboardTypes]; 248 | 249 | if (!pboardTypes) { 250 | types = [NSMutableArray array]; 251 | } 252 | [types addObjectsFromArray:@[MBTableGridColumnDataType, MBTableGridRowDataType]]; 253 | 254 | [super registerForDraggedTypes:types]; 255 | 256 | // Register the content view for everything 257 | [contentView registerForDraggedTypes:types]; 258 | } 259 | 260 | #pragma mark Mouse Events 261 | 262 | - (void)mouseDown:(NSEvent *)theEvent 263 | { 264 | // End editing (if necessary) 265 | [[self cell] endEditing:[[self window] fieldEditor:NO forObject:contentView]]; 266 | 267 | // If we're not the first responder, we need to be 268 | if([[self window] firstResponder] != self) { 269 | [[self window] makeFirstResponder:self]; 270 | } 271 | } 272 | 273 | #pragma mark Keyboard Events 274 | 275 | - (void)keyDown:(NSEvent *)theEvent 276 | { 277 | [self interpretKeyEvents:@[theEvent]]; 278 | } 279 | 280 | /*- (void)interpretKeyEvents:(NSArray *)eventArray 281 | { 282 | 283 | }*/ 284 | 285 | #pragma mark NSResponder Event Handlers 286 | 287 | - (void)insertTab:(id)sender 288 | { 289 | // Pressing "Tab" moves to the next column 290 | [self moveRight:sender]; 291 | } 292 | 293 | - (void)insertBacktab:(id)sender 294 | { 295 | // We want to change the selection, not expand it 296 | shouldOverrideModifiers = YES; 297 | 298 | // Pressing Shift+Tab moves to the previous column 299 | [self moveLeft:sender]; 300 | } 301 | 302 | - (void)insertNewline:(id)sender 303 | { 304 | if([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) { 305 | // Pressing Shift+Return moves to the previous row 306 | shouldOverrideModifiers = YES; 307 | [self moveUp:sender]; 308 | } else { 309 | // Pressing Return moves to the next row 310 | [self moveDown:sender]; 311 | } 312 | } 313 | 314 | - (void)moveUp:(id)sender 315 | { 316 | NSUInteger column = [self.selectedColumnIndexes firstIndex]; 317 | NSUInteger row = [self.selectedRowIndexes firstIndex]; 318 | 319 | // Accomodate for the sticky edges 320 | if(stickyColumnEdge == MBTableGridRightEdge) { 321 | column = [self.selectedColumnIndexes lastIndex]; 322 | } 323 | if(stickyRowEdge == MBTableGridBottomEdge) { 324 | row = [self.selectedRowIndexes lastIndex]; 325 | } 326 | 327 | // If we're already at the first row, do nothing 328 | if(row <= 0) 329 | return; 330 | 331 | // If the Shift key was not held, move the selection 332 | self.selectedColumnIndexes = [NSIndexSet indexSetWithIndex:column]; 333 | self.selectedRowIndexes = [NSIndexSet indexSetWithIndex:(row-1)]; 334 | 335 | if (row > 0) { 336 | NSRect cellRect = [self frameOfCellAtColumn:column row:row - 1]; 337 | cellRect = [self convertRect:cellRect toView:self.contentView]; 338 | [self scrollToArea:cellRect animate:NO]; 339 | } 340 | 341 | } 342 | 343 | - (void)moveUpAndModifySelection:(id)sender 344 | { 345 | if(shouldOverrideModifiers) { 346 | [self moveLeft:sender]; 347 | shouldOverrideModifiers = NO; 348 | return; 349 | } 350 | 351 | NSUInteger firstRow = [self.selectedRowIndexes firstIndex]; 352 | NSUInteger lastRow = [self.selectedRowIndexes lastIndex]; 353 | 354 | // If there is only one row selected, change the sticky edge to the bottom 355 | if([self.selectedRowIndexes count] == 1) { 356 | stickyRowEdge = MBTableGridBottomEdge; 357 | } 358 | 359 | // We can't expand past the last row 360 | if(stickyRowEdge == MBTableGridBottomEdge && firstRow <= 0) 361 | return; 362 | 363 | if(stickyRowEdge == MBTableGridTopEdge) { 364 | // If the top edge is sticky, contract the selection 365 | lastRow--; 366 | } else if(stickyRowEdge == MBTableGridBottomEdge) { 367 | // If the bottom edge is sticky, expand the contraction 368 | firstRow--; 369 | } 370 | self.selectedRowIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(firstRow, lastRow-firstRow+1)]; 371 | 372 | NSUInteger column = [self.selectedColumnIndexes firstIndex]; 373 | 374 | if (firstRow - 1 > 0) { 375 | NSRect cellRect = [self frameOfCellAtColumn:column row:firstRow - 1]; 376 | cellRect = [self convertRect:cellRect toView:contentScrollView.contentView]; 377 | [self scrollToArea:cellRect animate:NO]; 378 | } 379 | 380 | } 381 | 382 | - (void)moveDown:(id)sender 383 | { 384 | NSUInteger column = [self.selectedColumnIndexes firstIndex]; 385 | NSUInteger row = [self.selectedRowIndexes firstIndex]; 386 | 387 | // Accomodate for the sticky edges 388 | if(stickyColumnEdge == MBTableGridRightEdge) { 389 | column = [self.selectedColumnIndexes lastIndex]; 390 | } 391 | if(stickyRowEdge == MBTableGridBottomEdge) { 392 | row = [self.selectedRowIndexes lastIndex]; 393 | } 394 | 395 | // If we're already at the last row, do nothing 396 | if(row >= (_numberOfRows-1)) 397 | return; 398 | 399 | // If the Shift key was not held, move the selection 400 | self.selectedColumnIndexes = [NSIndexSet indexSetWithIndex:column]; 401 | self.selectedRowIndexes = [NSIndexSet indexSetWithIndex:(row+1)]; 402 | 403 | if (row + 1 < [self numberOfRows]) { 404 | NSRect cellRect = [self frameOfCellAtColumn:column row:row + 1]; 405 | cellRect = [self convertRect:cellRect toView:contentScrollView.contentView]; 406 | [self scrollToArea:cellRect animate:NO]; 407 | } 408 | 409 | } 410 | 411 | - (void)moveDownAndModifySelection:(id)sender 412 | { 413 | if(shouldOverrideModifiers) { 414 | [self moveDown:sender]; 415 | shouldOverrideModifiers = NO; 416 | return; 417 | } 418 | 419 | NSUInteger firstRow = [self.selectedRowIndexes firstIndex]; 420 | NSUInteger lastRow = [self.selectedRowIndexes lastIndex]; 421 | 422 | // If there is only one row selected, change the sticky edge to the top 423 | if([self.selectedRowIndexes count] == 1) { 424 | stickyRowEdge = MBTableGridTopEdge; 425 | } 426 | 427 | // We can't expand past the last row 428 | if(stickyRowEdge == MBTableGridTopEdge && lastRow >= (_numberOfRows-1)) 429 | return; 430 | 431 | if(stickyRowEdge == MBTableGridTopEdge) { 432 | // If the top edge is sticky, contract the selection 433 | lastRow++; 434 | } else if(stickyRowEdge == MBTableGridBottomEdge) { 435 | // If the bottom edge is sticky, expand the contraction 436 | firstRow++; 437 | } 438 | self.selectedRowIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(firstRow, lastRow-firstRow+1)]; 439 | 440 | NSUInteger column = [self.selectedColumnIndexes lastIndex]; 441 | 442 | if (lastRow + 1 < [self numberOfRows]) { 443 | NSRect cellRect = [self frameOfCellAtColumn:column row:lastRow + 1]; 444 | cellRect = [self convertRect:cellRect toView:contentScrollView.contentView]; 445 | [self scrollToArea:cellRect animate:NO]; 446 | } 447 | 448 | } 449 | 450 | - (void)moveLeft:(id)sender 451 | { 452 | NSUInteger column = [self.selectedColumnIndexes firstIndex]; 453 | NSUInteger row = [self.selectedRowIndexes firstIndex]; 454 | 455 | // Accomodate for the sticky edges 456 | if(stickyColumnEdge == MBTableGridRightEdge) { 457 | column = [self.selectedColumnIndexes lastIndex]; 458 | } 459 | if(stickyRowEdge == MBTableGridBottomEdge) { 460 | row = [self.selectedRowIndexes lastIndex]; 461 | } 462 | 463 | if (column > 0) { 464 | NSRect cellRect = [self frameOfCellAtColumn:column - 1 row:row]; 465 | cellRect = [self convertRect:cellRect toView:contentScrollView.contentView]; 466 | [self scrollToArea:cellRect animate:NO]; 467 | } 468 | 469 | // If we're already at the first column, do nothing 470 | if(column <= 0) 471 | return; 472 | 473 | // If the Shift key was not held, move the selection 474 | self.selectedColumnIndexes = [NSIndexSet indexSetWithIndex:(column-1)]; 475 | self.selectedRowIndexes = [NSIndexSet indexSetWithIndex:row]; 476 | 477 | } 478 | 479 | - (void)moveLeftAndModifySelection:(id)sender 480 | { 481 | if(shouldOverrideModifiers) { 482 | [self moveLeft:sender]; 483 | shouldOverrideModifiers = NO; 484 | return; 485 | } 486 | 487 | NSUInteger firstColumn = [self.selectedColumnIndexes firstIndex]; 488 | NSUInteger lastColumn = [self.selectedColumnIndexes lastIndex]; 489 | 490 | // If there is only one column selected, change the sticky edge to the right 491 | if([self.selectedColumnIndexes count] == 1) { 492 | stickyColumnEdge = MBTableGridRightEdge; 493 | } 494 | 495 | NSUInteger row = [self.selectedRowIndexes firstIndex]; 496 | 497 | if (firstColumn > 0) { 498 | NSRect cellRect = [self frameOfCellAtColumn:firstColumn - 1 row:row]; 499 | cellRect = [self convertRect:cellRect toView:contentScrollView.contentView]; 500 | [self scrollToArea:cellRect animate:NO]; 501 | } 502 | 503 | 504 | // We can't expand past the first column 505 | if(stickyColumnEdge == MBTableGridRightEdge && firstColumn <= 0) 506 | return; 507 | 508 | if(stickyColumnEdge == MBTableGridLeftEdge) { 509 | // If the top edge is sticky, contract the selection 510 | lastColumn--; 511 | } else if(stickyColumnEdge == MBTableGridRightEdge) { 512 | // If the bottom edge is sticky, expand the contraction 513 | firstColumn--; 514 | } 515 | self.selectedColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(firstColumn, lastColumn-firstColumn+1)]; 516 | } 517 | 518 | - (void)moveRight:(id)sender 519 | { 520 | NSUInteger column = [self.selectedColumnIndexes firstIndex]; 521 | NSUInteger row = [self.selectedRowIndexes firstIndex]; 522 | 523 | // Accomodate for the sticky edges 524 | if(stickyColumnEdge == MBTableGridRightEdge) { 525 | column = [self.selectedColumnIndexes lastIndex]; 526 | } 527 | if(stickyRowEdge == MBTableGridBottomEdge) { 528 | row = [self.selectedRowIndexes lastIndex]; 529 | } 530 | 531 | // If we're already at the last column, do nothing 532 | if(column >= (_numberOfColumns-1)) 533 | return; 534 | 535 | // If the Shift key was not held, move the selection 536 | self.selectedColumnIndexes = [NSIndexSet indexSetWithIndex:(column+1)]; 537 | self.selectedRowIndexes = [NSIndexSet indexSetWithIndex:row]; 538 | 539 | if (column + 1 < [self numberOfColumns]) { 540 | NSRect cellRect = [self frameOfCellAtColumn:column + 1 row:row]; 541 | cellRect = [self convertRect:cellRect toView:contentScrollView.contentView]; 542 | [self scrollToArea:cellRect animate:NO]; 543 | } 544 | } 545 | 546 | - (void)moveRightAndModifySelection:(id)sender 547 | { 548 | if(shouldOverrideModifiers) { 549 | [self moveRight:sender]; 550 | shouldOverrideModifiers = NO; 551 | return; 552 | } 553 | 554 | NSUInteger firstColumn = [self.selectedColumnIndexes firstIndex]; 555 | NSUInteger lastColumn = [self.selectedColumnIndexes lastIndex]; 556 | 557 | // If there is only one column selected, change the sticky edge to the right 558 | if([self.selectedColumnIndexes count] == 1) { 559 | stickyColumnEdge = MBTableGridLeftEdge; 560 | } 561 | 562 | // We can't expand past the last column 563 | if(stickyColumnEdge == MBTableGridLeftEdge && lastColumn >= (_numberOfColumns-1)) 564 | return; 565 | 566 | if(stickyColumnEdge == MBTableGridLeftEdge) { 567 | // If the top edge is sticky, contract the selection 568 | lastColumn++; 569 | } else if(stickyColumnEdge == MBTableGridRightEdge) { 570 | // If the bottom edge is sticky, expand the contraction 571 | firstColumn++; 572 | } 573 | self.selectedColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(firstColumn, lastColumn-firstColumn+1)]; 574 | 575 | NSUInteger row = [self.selectedRowIndexes lastIndex]; 576 | 577 | if (lastColumn + 1 < [self numberOfColumns]) { 578 | NSRect cellRect = [self frameOfCellAtColumn:lastColumn + 1 row:row]; 579 | cellRect = [self convertRect:cellRect toView:contentScrollView.contentView]; 580 | [self scrollToArea:cellRect animate:NO]; 581 | } 582 | 583 | } 584 | 585 | - (void)scrollToArea:(NSRect)area animate:(BOOL)shouldAnimate { 586 | if (shouldAnimate) { 587 | [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) { 588 | [context setAllowsImplicitAnimation:YES]; 589 | [self.contentView scrollRectToVisible:area]; 590 | } completionHandler:^{ 591 | }]; 592 | } else { 593 | [contentScrollView.contentView scrollRectToVisible:area]; 594 | } 595 | } 596 | 597 | - (void)selectAll:(id)sender 598 | { 599 | stickyColumnEdge = MBTableGridLeftEdge; 600 | stickyRowEdge = MBTableGridTopEdge; 601 | 602 | self.selectedColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, _numberOfColumns)]; 603 | self.selectedRowIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, _numberOfRows)]; 604 | } 605 | 606 | - (void)deleteBackward:(id)sender 607 | { 608 | // Clear the contents of every selected cell 609 | NSUInteger column = [self.selectedColumnIndexes firstIndex]; 610 | while(column <= [self.selectedColumnIndexes lastIndex]) { 611 | NSUInteger row = [self.selectedRowIndexes firstIndex]; 612 | while(row <= [self.selectedRowIndexes lastIndex]) { 613 | [self _setObjectValue:nil forColumn:column row:row]; 614 | row++; 615 | } 616 | column++; 617 | } 618 | [self reloadData]; 619 | } 620 | 621 | - (void)insertText:(id)aString 622 | { 623 | [contentView editSelectedCell:self]; 624 | 625 | // Insert the typed string into the field editor 626 | NSText *fieldEditor = [[self window] fieldEditor:YES forObject:self]; 627 | [fieldEditor setString:aString]; 628 | } 629 | 630 | #pragma mark - 631 | #pragma mark Notifications 632 | 633 | - (void)viewFrameDidChange:(NSNotification *)aNotification 634 | { 635 | //[self reloadData]; 636 | } 637 | 638 | - (void)contentViewDidScroll:(NSNotification *)aNotification 639 | { 640 | NSView *changedView = [aNotification object]; 641 | 642 | // Get the origin of the NSClipView 643 | NSPoint changedBoundsOrigin = [changedView bounds].origin; 644 | 645 | /* 646 | * Column Header Synchronization 647 | */ 648 | 649 | // Get the column headers' current origin 650 | NSPoint curColumnOffset = [[columnHeaderScrollView contentView] bounds].origin; 651 | NSPoint newColumnOffset = curColumnOffset; 652 | 653 | // Column headers are synchronized in the horizontal plane 654 | newColumnOffset.x = changedBoundsOrigin.x; 655 | 656 | // If the synced position is different from our current position, reposition the headers view 657 | if (!NSEqualPoints(curColumnOffset, changedBoundsOrigin)) 658 | { 659 | [[columnHeaderScrollView contentView] scrollToPoint:newColumnOffset]; 660 | // we have to tell the NSScrollView to update its 661 | // scrollers 662 | [columnHeaderScrollView reflectScrolledClipView:[columnHeaderScrollView contentView]]; 663 | } 664 | 665 | /* 666 | * Row Header Synchronization 667 | */ 668 | 669 | // Get the row headers' current origin 670 | NSPoint curRowOffset = [[rowHeaderScrollView contentView] bounds].origin; 671 | NSPoint newRowOffset = curRowOffset; 672 | 673 | // Row headers are synchronized in the vertical plane 674 | newRowOffset.y = changedBoundsOrigin.y; 675 | 676 | // If the synced position is different from our current position, reposition the headers view 677 | if (!NSEqualPoints(curRowOffset, changedBoundsOrigin)) 678 | { 679 | [[rowHeaderScrollView contentView] scrollToPoint:newRowOffset]; 680 | // we have to tell the NSScrollView to update its 681 | // scrollers 682 | [rowHeaderScrollView reflectScrolledClipView:[rowHeaderScrollView contentView]]; 683 | } 684 | } 685 | 686 | #pragma mark - 687 | #pragma mark Protocol Methods 688 | 689 | #pragma mark NSDraggingDestination 690 | 691 | - (NSDragOperation)draggingEntered:(id )sender 692 | { 693 | NSPasteboard *pboard = [sender draggingPasteboard]; 694 | 695 | NSData *columnData = [pboard dataForType:MBTableGridColumnDataType]; 696 | NSData *rowData = [pboard dataForType:MBTableGridRowDataType]; 697 | 698 | if (columnData) { 699 | return NSDragOperationMove; 700 | } else if (rowData) { 701 | return NSDragOperationMove; 702 | } else { 703 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:validateDrop:proposedColumn:row:)]) { 704 | NSPoint mouseLocation = [self convertPoint:[sender draggingLocation] fromView:nil]; 705 | NSUInteger dropColumn = [self columnAtPoint:mouseLocation]; 706 | NSUInteger dropRow = [self rowAtPoint:mouseLocation]; 707 | 708 | NSDragOperation dragOperation = [[self dataSource] tableGrid:self validateDrop:sender proposedColumn:dropColumn row:dropRow]; 709 | 710 | // If the drag is okay, highlight the appropriate cell 711 | if (dragOperation != NSDragOperationNone) { 712 | [contentView _setDropColumn:dropColumn]; 713 | [contentView _setDropRow:dropRow]; 714 | } 715 | 716 | return dragOperation; 717 | } 718 | } 719 | 720 | return NSDragOperationNone; 721 | } 722 | 723 | - (NSDragOperation)draggingUpdated:(id )sender 724 | { 725 | NSPasteboard *pboard = [sender draggingPasteboard]; 726 | NSData *columnData = [pboard dataForType:MBTableGridColumnDataType]; 727 | NSData *rowData = [pboard dataForType:MBTableGridRowDataType]; 728 | NSPoint mouseLocation = [self convertPoint:[sender draggingLocation] fromView:nil]; 729 | 730 | if (columnData) { 731 | // If we're dragging a column 732 | 733 | NSUInteger dropColumn = [self _dropColumnForPoint:mouseLocation]; 734 | 735 | if (dropColumn == NSNotFound) { 736 | return NSDragOperationNone; 737 | } 738 | 739 | NSIndexSet *draggedColumns = (NSIndexSet *)[NSKeyedUnarchiver unarchiveObjectWithData:columnData]; 740 | 741 | BOOL canDrop = NO; 742 | if([[self dataSource] respondsToSelector:@selector(tableGrid:canMoveColumns:toIndex:)]) { 743 | canDrop = [[self dataSource] tableGrid:self canMoveColumns:draggedColumns toIndex:dropColumn]; 744 | } 745 | 746 | [contentView _setDraggingColumnOrRow:YES]; 747 | 748 | if(canDrop) { 749 | [contentView _setDropColumn:dropColumn]; 750 | return NSDragOperationMove; 751 | } else { 752 | [contentView _setDropColumn:NSNotFound]; 753 | } 754 | 755 | } else if (rowData) { 756 | // If we're dragging a row 757 | 758 | NSUInteger dropRow = [self _dropRowForPoint:mouseLocation]; 759 | 760 | if(dropRow == NSNotFound) { 761 | return NSDragOperationNone; 762 | } 763 | 764 | NSIndexSet *draggedRows = (NSIndexSet *)[NSKeyedUnarchiver unarchiveObjectWithData:rowData]; 765 | 766 | BOOL canDrop = NO; 767 | if([[self dataSource] respondsToSelector:@selector(tableGrid:canMoveRows:toIndex:)]) { 768 | canDrop = [[self dataSource] tableGrid:self canMoveRows:draggedRows toIndex:dropRow]; 769 | } 770 | 771 | [contentView _setDraggingColumnOrRow:YES]; 772 | 773 | if(canDrop) { 774 | [contentView _setDropRow:dropRow]; 775 | return NSDragOperationMove; 776 | } else { 777 | [contentView _setDropRow:NSNotFound]; 778 | } 779 | 780 | } else { 781 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:validateDrop:proposedColumn:row:)]) { 782 | NSUInteger dropColumn = [self columnAtPoint:mouseLocation]; 783 | NSUInteger dropRow = [self rowAtPoint:mouseLocation]; 784 | 785 | [contentView _setDraggingColumnOrRow:NO]; 786 | 787 | NSDragOperation dragOperation = [[self dataSource] tableGrid:self validateDrop:sender proposedColumn:dropColumn row:dropRow]; 788 | 789 | // If the drag is okay, highlight the appropriate cell 790 | if (dragOperation != NSDragOperationNone) { 791 | [contentView _setDropColumn:dropColumn]; 792 | [contentView _setDropRow:dropRow]; 793 | } 794 | 795 | return dragOperation; 796 | } 797 | } 798 | return NSDragOperationNone; 799 | } 800 | 801 | - (void)draggingExited:(id )sender 802 | { 803 | [contentView _setDropColumn:NSNotFound]; 804 | [contentView _setDropRow:NSNotFound]; 805 | } 806 | 807 | - (void)draggingEnded:(id )sender 808 | { 809 | [contentView _setDropColumn:NSNotFound]; 810 | [contentView _setDropRow:NSNotFound]; 811 | } 812 | 813 | - (BOOL)prepareForDragOperation:(id )sender 814 | { 815 | return YES; 816 | } 817 | 818 | - (BOOL)performDragOperation:(id )sender 819 | { 820 | NSPasteboard *pboard = [sender draggingPasteboard]; 821 | NSData *columnData = [pboard dataForType:MBTableGridColumnDataType]; 822 | NSData *rowData = [pboard dataForType:MBTableGridRowDataType]; 823 | NSPoint mouseLocation = [self convertPoint:[sender draggingLocation] fromView:nil]; 824 | 825 | if (columnData) { 826 | // If we're dragging a column 827 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:moveColumns:toIndex:)]) { 828 | // Get which columns are being dragged 829 | NSIndexSet *draggedColumns = (NSIndexSet *)[NSKeyedUnarchiver unarchiveObjectWithData:columnData]; 830 | 831 | // Get the index to move the columns to 832 | NSUInteger dropColumn = [self _dropColumnForPoint:mouseLocation]; 833 | 834 | // Tell the data source to move the columns 835 | BOOL didDrag = [[self dataSource] tableGrid:self moveColumns:draggedColumns toIndex:dropColumn]; 836 | 837 | if (didDrag) { 838 | NSUInteger startIndex = dropColumn; 839 | NSUInteger length = [draggedColumns count]; 840 | 841 | if (dropColumn > [draggedColumns firstIndex]) { 842 | startIndex -= [draggedColumns count]; 843 | } 844 | 845 | NSIndexSet *newColumns = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(startIndex, length)]; 846 | 847 | // Post the notification 848 | [[NSNotificationCenter defaultCenter] postNotificationName:MBTableGridDidMoveColumnsNotification object:self userInfo:@{@"OldColumns": draggedColumns, @"NewColumns": newColumns}]; 849 | 850 | // Change the selection to reflect the newly-dragged columns 851 | self.selectedColumnIndexes = newColumns; 852 | } 853 | 854 | return didDrag; 855 | } 856 | } else if (rowData) { 857 | // If we're dragging a row 858 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:moveRows:toIndex:)]) { 859 | // Get which rows are being dragged 860 | NSIndexSet *draggedRows = (NSIndexSet *)[NSKeyedUnarchiver unarchiveObjectWithData:rowData]; 861 | 862 | // Get the index to move the rows to 863 | NSUInteger dropRow = [self _dropRowForPoint:mouseLocation]; 864 | 865 | // Tell the data source to move the rows 866 | BOOL didDrag = [[self dataSource] tableGrid:self moveRows:draggedRows toIndex:dropRow]; 867 | 868 | if (didDrag) { 869 | NSUInteger startIndex = dropRow; 870 | NSUInteger length = [draggedRows count]; 871 | 872 | if (dropRow > [draggedRows firstIndex]) { 873 | startIndex -= [draggedRows count]; 874 | } 875 | 876 | NSIndexSet *newRows = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(startIndex, length)]; 877 | 878 | // Post the notification 879 | [[NSNotificationCenter defaultCenter] postNotificationName:MBTableGridDidMoveRowsNotification object:self userInfo:@{@"OldRows": draggedRows, @"NewRows": newRows}]; 880 | 881 | // Change the selection to reflect the newly-dragged rows 882 | self.selectedRowIndexes = newRows; 883 | } 884 | 885 | return didDrag; 886 | } 887 | } else { 888 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:acceptDrop:column:row:)]) { 889 | NSUInteger dropColumn = [self columnAtPoint:mouseLocation]; 890 | NSUInteger dropRow = [self rowAtPoint:mouseLocation]; 891 | 892 | // Pass the drag to the data source 893 | BOOL didPerformDrag = [[self dataSource] tableGrid:self acceptDrop:sender column:dropColumn row:dropRow]; 894 | 895 | return didPerformDrag; 896 | } 897 | } 898 | 899 | return NO; 900 | } 901 | 902 | - (void)concludeDragOperation:(id )sender 903 | { 904 | [contentView _setDropColumn:NSNotFound]; 905 | [contentView _setDropRow:NSNotFound]; 906 | } 907 | 908 | #pragma mark - 909 | #pragma mark Subclass Methods 910 | 911 | #pragma mark Dimensions 912 | 913 | 914 | #pragma mark Reloading the Grid 915 | 916 | - (void)reloadData 917 | { 918 | 919 | // Set number of columns 920 | if([[self dataSource] respondsToSelector:@selector(numberOfColumnsInTableGrid:)]) { 921 | 922 | _numberOfColumns = [[self dataSource] numberOfColumnsInTableGrid:self]; 923 | 924 | } else { 925 | 926 | _numberOfColumns = 0; 927 | 928 | } 929 | 930 | // Set number of rows 931 | if([[self dataSource] respondsToSelector:@selector(numberOfRowsInTableGrid:)]) { 932 | 933 | _numberOfRows = [[self dataSource] numberOfRowsInTableGrid:self]; 934 | 935 | } else { 936 | 937 | _numberOfRows = 0; 938 | 939 | } 940 | 941 | // Update the content view's size 942 | NSUInteger lastColumn = _numberOfColumns-1; 943 | NSUInteger lastRow = _numberOfRows-1; 944 | NSRect bottomRightCellFrame = [contentView frameOfCellAtColumn:lastColumn row:lastRow]; 945 | 946 | NSRect contentRect = NSMakeRect([contentView frame].origin.x, [contentView frame].origin.y, NSMaxX(bottomRightCellFrame), NSMaxY(bottomRightCellFrame)); 947 | [contentView setFrameSize:contentRect.size]; 948 | 949 | // Update the column header view's size 950 | NSRect columnHeaderFrame = [columnHeaderView frame]; 951 | columnHeaderFrame.size.width = contentRect.size.width; 952 | if(![[contentScrollView verticalScroller] isHidden]) { 953 | columnHeaderFrame.size.width += [NSScroller scrollerWidth]; 954 | } 955 | [columnHeaderView setFrameSize:columnHeaderFrame.size]; 956 | 957 | // Update the row header view's size 958 | NSRect rowHeaderFrame = [rowHeaderView frame]; 959 | rowHeaderFrame.size.height = contentRect.size.height; 960 | if(![[contentScrollView horizontalScroller] isHidden]) { 961 | columnHeaderFrame.size.height += [NSScroller scrollerWidth]; 962 | } 963 | [rowHeaderView setFrameSize:rowHeaderFrame.size]; 964 | 965 | [self setNeedsDisplay:YES]; 966 | } 967 | 968 | #pragma mark Layout Support 969 | 970 | - (NSRect)rectOfColumn:(NSUInteger)columnIndex 971 | { 972 | NSRect rect = [self convertRect:[contentView rectOfColumn:columnIndex] fromView:contentView]; 973 | rect.origin.y = 0; 974 | rect.size.height += MBTableGridColumnHeaderHeight; 975 | if(rect.size.height > [self frame].size.height) { 976 | rect.size.height = [self frame].size.height; 977 | 978 | // If the scrollbar is visible, don't include it in the rect 979 | if(![[contentScrollView horizontalScroller] isHidden]) { 980 | rect.size.height -= [NSScroller scrollerWidth]; 981 | } 982 | } 983 | 984 | return rect; 985 | } 986 | 987 | - (NSRect)rectOfRow:(NSUInteger)rowIndex 988 | { 989 | NSRect rect = [self convertRect:[contentView rectOfRow:rowIndex] fromView:contentView]; 990 | rect.origin.x = 0; 991 | rect.size.width += MBTableGridRowHeaderWidth; 992 | 993 | return rect; 994 | } 995 | 996 | - (NSRect)frameOfCellAtColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 997 | { 998 | return [self convertRect:[contentView frameOfCellAtColumn:columnIndex row:rowIndex] fromView:contentView]; 999 | } 1000 | 1001 | - (NSRect)headerRectOfColumn:(NSUInteger)columnIndex 1002 | { 1003 | return [self convertRect:[columnHeaderView headerRectOfColumn:columnIndex] fromView:columnHeaderView]; 1004 | } 1005 | 1006 | - (NSRect)headerRectOfRow:(NSUInteger)rowIndex 1007 | { 1008 | return [self convertRect:[rowHeaderView headerRectOfColumn:rowIndex] fromView:rowHeaderView]; 1009 | } 1010 | 1011 | - (NSRect)headerRectOfCorner 1012 | { 1013 | NSRect rect = NSMakeRect(0, 0, MBTableGridRowHeaderWidth, MBTableGridColumnHeaderHeight); 1014 | return rect; 1015 | } 1016 | 1017 | - (NSInteger)columnAtPoint:(NSPoint)aPoint 1018 | { 1019 | NSInteger column = 0; 1020 | while(column < _numberOfColumns) { 1021 | NSRect columnFrame = [self rectOfColumn:column]; 1022 | if(NSPointInRect(aPoint, columnFrame)) { 1023 | return column; 1024 | } 1025 | column++; 1026 | } 1027 | return NSNotFound; 1028 | } 1029 | 1030 | - (NSInteger)rowAtPoint:(NSPoint)aPoint 1031 | { 1032 | NSInteger row = 0; 1033 | while(row < _numberOfRows) { 1034 | NSRect rowFrame = [self rectOfRow:row]; 1035 | if(NSPointInRect(aPoint, rowFrame)) { 1036 | return row; 1037 | } 1038 | row++; 1039 | } 1040 | return NSNotFound; 1041 | } 1042 | 1043 | #pragma mark Auxiliary Views 1044 | 1045 | - (MBTableGridHeaderView *)columnHeaderView 1046 | { 1047 | return columnHeaderView; 1048 | } 1049 | 1050 | - (MBTableGridHeaderView *)rowHeaderView 1051 | { 1052 | return rowHeaderView; 1053 | } 1054 | 1055 | - (MBTableGridContentView *)contentView 1056 | { 1057 | return contentView; 1058 | } 1059 | 1060 | #pragma mark - Overridden Property Accessors 1061 | 1062 | - (void)setSelectedColumnIndexes:(NSIndexSet *)anIndexSet 1063 | { 1064 | if(anIndexSet == selectedColumnIndexes) 1065 | return; 1066 | 1067 | 1068 | // Allow the delegate to validate the selection 1069 | if ([[self delegate] respondsToSelector:@selector(tableGrid:willSelectColumnsAtIndexPath:)]) { 1070 | anIndexSet = [[self delegate] tableGrid:self willSelectColumnsAtIndexPath:anIndexSet]; 1071 | } 1072 | 1073 | selectedColumnIndexes = anIndexSet; 1074 | 1075 | [self setNeedsDisplay:YES]; 1076 | 1077 | // Post the notification 1078 | [[NSNotificationCenter defaultCenter] postNotificationName:MBTableGridDidChangeSelectionNotification object:self]; 1079 | } 1080 | 1081 | - (void)setSelectedRowIndexes:(NSIndexSet *)anIndexSet 1082 | { 1083 | if(anIndexSet == selectedColumnIndexes) 1084 | return; 1085 | 1086 | 1087 | // Allow the delegate to validate the selection 1088 | if ([[self delegate] respondsToSelector:@selector(tableGrid:willSelectRowsAtIndexPath:)]) { 1089 | anIndexSet = [[self delegate] tableGrid:self willSelectRowsAtIndexPath:anIndexSet]; 1090 | } 1091 | 1092 | selectedRowIndexes = anIndexSet; 1093 | 1094 | [self setNeedsDisplay:YES]; 1095 | 1096 | // Post the notification 1097 | [[NSNotificationCenter defaultCenter] postNotificationName:MBTableGridDidChangeSelectionNotification object:self]; 1098 | } 1099 | 1100 | - (void)setDelegate:(id )anObject 1101 | { 1102 | if (anObject == delegate) 1103 | return; 1104 | 1105 | if (delegate) { 1106 | // Unregister the delegate for relavent notifications 1107 | [[NSNotificationCenter defaultCenter] removeObserver:delegate name:MBTableGridDidChangeSelectionNotification object:self]; 1108 | [[NSNotificationCenter defaultCenter] removeObserver:delegate name:MBTableGridDidMoveColumnsNotification object:self]; 1109 | [[NSNotificationCenter defaultCenter] removeObserver:delegate name:MBTableGridDidMoveRowsNotification object:self]; 1110 | } 1111 | 1112 | delegate = anObject; 1113 | 1114 | // Register the new delegate for relavent notifications 1115 | if ([delegate respondsToSelector:@selector(tableGridDidChangeSelection:)]) { 1116 | [[NSNotificationCenter defaultCenter] addObserver:delegate selector:@selector(tableGridDidChangeSelection:) name:MBTableGridDidChangeSelectionNotification object:self]; 1117 | } 1118 | if ([delegate respondsToSelector:@selector(tableGridDidMoveColumns:)]) { 1119 | [[NSNotificationCenter defaultCenter] addObserver:delegate selector:@selector(tableGridDidMoveColumns:) name:MBTableGridDidMoveColumnsNotification object:self]; 1120 | } 1121 | if ([delegate respondsToSelector:@selector(tableGridDidMoveRows:)]) { 1122 | [[NSNotificationCenter defaultCenter] addObserver:delegate selector:@selector(tableGridDidMoveRows:) name:MBTableGridDidMoveRowsNotification object:self]; 1123 | } 1124 | } 1125 | 1126 | @end 1127 | 1128 | @implementation MBTableGrid (Drawing) 1129 | 1130 | - (void)_drawColumnHeaderBackgroundInRect:(NSRect)aRect 1131 | { 1132 | if ([self needsToDrawRect:aRect]) { 1133 | NSColor *topGradientTop = [NSColor colorWithDeviceWhite:0.91 alpha:1.0]; 1134 | NSColor *topGradientBottom = [NSColor colorWithDeviceWhite:0.89 alpha:1.0]; 1135 | NSColor *bottomGradientTop = [NSColor colorWithDeviceWhite:0.85 alpha:1.0]; 1136 | NSColor *bottomGradientBottom = [NSColor colorWithDeviceWhite:0.83 alpha:1.0]; 1137 | NSColor *topColor = [NSColor colorWithDeviceWhite:0.95 alpha:1.0]; 1138 | NSColor *borderColor = [NSColor colorWithDeviceWhite:0.65 alpha:1.0]; 1139 | 1140 | NSGradient *topGradient = [[NSGradient alloc] initWithColors:@[topGradientTop, topGradientBottom]]; 1141 | NSGradient *bottomGradient = [[NSGradient alloc] initWithColors:@[bottomGradientTop, bottomGradientBottom]]; 1142 | 1143 | NSRect topRect = NSMakeRect(NSMinX(aRect), 0, NSWidth(aRect), NSHeight(aRect)/2); 1144 | NSRect bottomRect = NSMakeRect(NSMinX(aRect), NSMidY(aRect)-0.5, NSWidth(aRect), NSHeight(aRect)/2+0.5); 1145 | 1146 | // Draw the gradients 1147 | [topGradient drawInRect:topRect angle:90.0]; 1148 | [bottomGradient drawInRect:bottomRect angle:90.0]; 1149 | 1150 | // Draw the top bevel line 1151 | NSRect topLine = NSMakeRect(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), 1.0); 1152 | [topColor set]; 1153 | NSRectFill(topLine); 1154 | 1155 | // Draw the bottom border 1156 | [borderColor set]; 1157 | NSRect bottomLine = NSMakeRect(NSMinX(aRect), NSMaxY(aRect)-1.0, NSWidth(aRect), 1.0); 1158 | NSRectFill(bottomLine); 1159 | 1160 | } 1161 | } 1162 | 1163 | - (void)_drawRowHeaderBackgroundInRect:(NSRect)aRect 1164 | { 1165 | if ([self needsToDrawRect:aRect]) { 1166 | NSColor *topGradientTop = [NSColor colorWithDeviceWhite:0.91 alpha:1.0]; 1167 | NSColor *topGradientBottom = [NSColor colorWithDeviceWhite:0.89 alpha:1.0]; 1168 | NSColor *bottomGradientTop = [NSColor colorWithDeviceWhite:0.85 alpha:1.0]; 1169 | NSColor *bottomGradientBottom = [NSColor colorWithDeviceWhite:0.83 alpha:1.0]; 1170 | NSColor *sideColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.4]; 1171 | NSColor *borderColor = [NSColor colorWithDeviceWhite:0.65 alpha:1.0]; 1172 | 1173 | NSGradient *topGradient = [[NSGradient alloc] initWithColors:@[topGradientTop, topGradientBottom]]; 1174 | NSGradient *bottomGradient = [[NSGradient alloc] initWithColors:@[bottomGradientTop, bottomGradientBottom]]; 1175 | 1176 | NSRect leftRect = NSMakeRect(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect)/2, NSHeight(aRect)); 1177 | NSRect rightRect = NSMakeRect(NSMidX(aRect)-0.5, NSMinY(aRect), NSWidth(aRect)/2+0.5, NSHeight(aRect)); 1178 | 1179 | // Draw the gradients 1180 | [topGradient drawInRect:leftRect angle:0.0]; 1181 | [bottomGradient drawInRect:rightRect angle:0.0]; 1182 | 1183 | // Draw the left bevel line 1184 | NSRect leftLine = NSMakeRect(NSMinX(aRect), NSMinY(aRect), 1.0, NSHeight(aRect)); 1185 | [sideColor set]; 1186 | [[NSBezierPath bezierPathWithRect:leftLine] fill]; 1187 | 1188 | // Draw the right border 1189 | [borderColor set]; 1190 | NSRect rightLine = NSMakeRect(NSMaxX(aRect)-1, NSMinY(aRect), 1.0, NSHeight(aRect)); 1191 | NSRectFill(rightLine); 1192 | 1193 | } 1194 | } 1195 | 1196 | - (void)_drawCornerHeaderBackgroundInRect:(NSRect)aRect 1197 | { 1198 | if ([self needsToDrawRect:aRect]) { 1199 | NSColor *topGradientTop = [NSColor colorWithDeviceWhite:0.91 alpha:1.0]; 1200 | NSColor *topGradientBottom = [NSColor colorWithDeviceWhite:0.89 alpha:1.0]; 1201 | NSColor *bottomGradientTop = [NSColor colorWithDeviceWhite:0.85 alpha:1.0]; 1202 | NSColor *bottomGradientBottom = [NSColor colorWithDeviceWhite:0.83 alpha:1.0]; 1203 | NSColor *topColor = [NSColor colorWithDeviceWhite:0.95 alpha:1.0]; 1204 | NSColor *sideColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.4]; 1205 | NSColor *borderColor = [NSColor colorWithDeviceWhite:0.65 alpha:1.0]; 1206 | 1207 | NSGradient *topGradient = [[NSGradient alloc] initWithColors:@[topGradientTop, topGradientBottom]]; 1208 | NSGradient *bottomGradient = [[NSGradient alloc] initWithColors:@[bottomGradientTop, bottomGradientBottom]]; 1209 | 1210 | // Divide the frame in two 1211 | NSRect mainRect = aRect; 1212 | NSRect bottomRightRect = NSMakeRect(NSMidX(aRect)-0.5, NSMidY(aRect)-0.5, NSWidth(aRect)/2, NSHeight(aRect)/2+0.5); 1213 | 1214 | // Draw the gradients 1215 | [topGradient drawInRect:mainRect angle:90.0]; 1216 | [bottomGradient drawInRect:bottomRightRect angle:90.0]; 1217 | 1218 | // Draw the top bevel line 1219 | NSRect topLine = NSMakeRect(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), 1.0); 1220 | [topColor set]; 1221 | NSRectFill(topLine); 1222 | 1223 | // Draw the left bevel line 1224 | NSRect leftLine = NSMakeRect(NSMinX(aRect), NSMinY(aRect), 1.0, NSHeight(aRect)); 1225 | [sideColor set]; 1226 | [[NSBezierPath bezierPathWithRect:leftLine] fill]; 1227 | 1228 | // Draw the right border 1229 | [borderColor set]; 1230 | NSRect borderLine = NSMakeRect(NSMaxX(aRect)-1, NSMinY(aRect), 1.0, NSHeight(aRect)); 1231 | NSRectFill(borderLine); 1232 | 1233 | // Draw the bottom border 1234 | NSRect bottomLine = NSMakeRect(NSMinX(aRect), NSMaxY(aRect)-1.0, NSWidth(aRect), 1.0); 1235 | NSRectFill(bottomLine); 1236 | 1237 | } 1238 | } 1239 | 1240 | @end 1241 | 1242 | @implementation MBTableGrid (DataAccessors) 1243 | 1244 | - (NSString *)_headerStringForColumn:(NSUInteger)columnIndex 1245 | { 1246 | // Ask the data source 1247 | if([[self dataSource] respondsToSelector:@selector(tableGrid:headerStringForColumn:)]) { 1248 | return [[self dataSource] tableGrid:self headerStringForColumn:columnIndex]; 1249 | } 1250 | 1251 | char alphabetChar = columnIndex + 'A'; 1252 | return [NSString stringWithFormat:@"%c", alphabetChar]; 1253 | } 1254 | 1255 | - (NSString *)_headerStringForRow:(NSUInteger)rowIndex 1256 | { 1257 | // Ask the data source 1258 | if([[self dataSource] respondsToSelector:@selector(tableGrid:headerStringForRow:)]) { 1259 | return [[self dataSource] tableGrid:self headerStringForRow:rowIndex]; 1260 | } 1261 | 1262 | return [NSString stringWithFormat:@"%lu", (rowIndex+1)]; 1263 | } 1264 | 1265 | - (id)_objectValueForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 1266 | { 1267 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:objectValueForColumn:row:)]) { 1268 | id value = [[self dataSource] tableGrid:self objectValueForColumn:columnIndex row:rowIndex]; 1269 | return value; 1270 | } else { 1271 | NSLog(@"WARNING: MBTableGrid data source does not implement tableGrid:objectValueForColumn:row:"); 1272 | } 1273 | return nil; 1274 | } 1275 | 1276 | - (id)_backgroundColorForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 1277 | { 1278 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:backgroundColorForColumn:row:)]) { 1279 | return [[self dataSource] tableGrid:self backgroundColorForColumn:columnIndex row:rowIndex]; 1280 | } else { 1281 | NSLog(@"WARNING: MBTableGrid data source does not implement tableGrid:backgroundColorForColumn:row:"); 1282 | } 1283 | return nil; 1284 | } 1285 | 1286 | - (void)_setObjectValue:(id)value forColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 1287 | { 1288 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:setObjectValue:forColumn:row:)]) { 1289 | [[self dataSource] tableGrid:self setObjectValue:value forColumn:columnIndex row:rowIndex]; 1290 | } 1291 | } 1292 | 1293 | - (float)_widthForColumn:(NSUInteger)columnIndex 1294 | { 1295 | 1296 | NSString *column = [NSString stringWithFormat:@"column%lu", columnIndex]; 1297 | if (columnIndex < columnWidths.count) { 1298 | return [columnWidths[column] floatValue]; 1299 | } else { 1300 | return [self _setWidthForColumn:columnIndex]; 1301 | } 1302 | 1303 | } 1304 | 1305 | - (float)_setWidthForColumn:(NSUInteger)columnIndex 1306 | { 1307 | 1308 | if ([[self dataSource] respondsToSelector:@selector(tableGrid:setWidthForColumn:)]) { 1309 | 1310 | NSString *column = [NSString stringWithFormat:@"column%lu", columnIndex]; 1311 | 1312 | float width = [[self dataSource] tableGrid:self setWidthForColumn:columnIndex]; 1313 | columnWidths[column] = COLUMNFLOATSIZE(width); 1314 | 1315 | return width; 1316 | 1317 | } else { 1318 | return MBTableGridColumnHeaderWidth; 1319 | } 1320 | 1321 | } 1322 | - (BOOL)_canEditCellAtColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 1323 | { 1324 | // Can't edit if the data source doesn't implement the method 1325 | if (![[self dataSource] respondsToSelector:@selector(tableGrid:setObjectValue:forColumn:row:)]) { 1326 | return NO; 1327 | } 1328 | 1329 | // Ask the delegate if the cell is editable 1330 | if ([[self delegate] respondsToSelector:@selector(tableGrid:shouldEditColumn:row:)]) { 1331 | return [[self delegate] tableGrid:self shouldEditColumn:columnIndex row:rowIndex]; 1332 | } 1333 | 1334 | return YES; 1335 | } 1336 | 1337 | @end 1338 | 1339 | @implementation MBTableGrid (PrivateAccessors) 1340 | 1341 | - (MBTableGridContentView *)_contentView 1342 | { 1343 | return contentView; 1344 | } 1345 | 1346 | - (void)_setStickyColumn:(MBTableGridEdge)stickyColumn row:(MBTableGridEdge)stickyRow 1347 | { 1348 | stickyColumnEdge = stickyColumn; 1349 | stickyRowEdge = stickyRow; 1350 | } 1351 | 1352 | - (MBTableGridEdge)_stickyColumn 1353 | { 1354 | return stickyColumnEdge; 1355 | } 1356 | 1357 | - (MBTableGridEdge)_stickyRow 1358 | { 1359 | return stickyRowEdge; 1360 | } 1361 | 1362 | @end 1363 | 1364 | @implementation MBTableGrid (DragAndDrop) 1365 | 1366 | - (void)_dragColumnsWithEvent:(NSEvent *)theEvent 1367 | { 1368 | NSImage *dragImage = [self _imageForSelectedColumns]; 1369 | 1370 | NSRect firstSelectedColumn = [self rectOfColumn:[self.selectedColumnIndexes firstIndex]]; 1371 | NSPoint location = firstSelectedColumn.origin; 1372 | location.y += [dragImage size].height; 1373 | 1374 | NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; 1375 | [pboard declareTypes:@[MBTableGridColumnDataType] owner:self]; 1376 | 1377 | BOOL shouldDrag = NO; 1378 | if([[self dataSource] respondsToSelector:@selector(tableGrid:writeColumnsWithIndexes:toPasteboard:)]) { 1379 | shouldDrag = [[self dataSource] tableGrid:self writeColumnsWithIndexes:self.selectedColumnIndexes toPasteboard:pboard]; 1380 | } 1381 | 1382 | if(shouldDrag) { 1383 | // Set the column drag type 1384 | [pboard setData:[NSKeyedArchiver archivedDataWithRootObject:self.selectedColumnIndexes] forType:MBTableGridColumnDataType]; 1385 | 1386 | [self dragImage:dragImage at:location offset:NSZeroSize event:theEvent pasteboard:pboard source:self slideBack:YES]; 1387 | } 1388 | } 1389 | 1390 | - (void)_dragRowsWithEvent:(NSEvent *)theEvent 1391 | { 1392 | NSImage *dragImage = [self _imageForSelectedRows]; 1393 | 1394 | NSRect firstSelectedRow = [self rectOfRow:[self.selectedRowIndexes firstIndex]]; 1395 | NSPoint location = firstSelectedRow.origin; 1396 | location.y += [dragImage size].height; 1397 | 1398 | NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; 1399 | [pboard declareTypes:@[MBTableGridRowDataType] owner:self]; 1400 | 1401 | BOOL shouldDrag = NO; 1402 | if([[self dataSource] respondsToSelector:@selector(tableGrid:writeRowsWithIndexes:toPasteboard:)]) { 1403 | shouldDrag = [[self dataSource] tableGrid:self writeRowsWithIndexes:self.selectedRowIndexes toPasteboard:pboard]; 1404 | } 1405 | 1406 | if(shouldDrag) { 1407 | // Set the column drag type 1408 | [pboard setData:[NSKeyedArchiver archivedDataWithRootObject:self.selectedRowIndexes] forType:MBTableGridRowDataType]; 1409 | 1410 | [self dragImage:dragImage at:location offset:NSZeroSize event:theEvent pasteboard:pboard source:self slideBack:YES]; 1411 | } 1412 | } 1413 | 1414 | - (NSImage *)_imageForSelectedColumns 1415 | { 1416 | NSRect firstColumnFrame = [self rectOfColumn:[self.selectedColumnIndexes firstIndex]]; 1417 | NSRect lastColumnFrame = [self rectOfColumn:[self.selectedColumnIndexes lastIndex]]; 1418 | NSRect columnsFrame = NSMakeRect(NSMinX(firstColumnFrame), NSMinY(firstColumnFrame), NSMaxX(lastColumnFrame) - NSMinX(firstColumnFrame), NSHeight(firstColumnFrame)); 1419 | // Extend the frame to show the left border 1420 | columnsFrame.origin.x -= 1.0; 1421 | columnsFrame.size.width += 1.0; 1422 | 1423 | // Take a snapshot of the view 1424 | NSImage *opaqueImage = [[NSImage alloc] initWithData:[self dataWithPDFInsideRect:columnsFrame]]; 1425 | 1426 | // Create the translucent drag image 1427 | NSImage *finalImage = [[NSImage alloc] initWithSize:[opaqueImage size]]; 1428 | [finalImage lockFocus]; 1429 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 1430 | [opaqueImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy fraction:0.7]; 1431 | #else 1432 | [opaqueImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:0.7]; 1433 | #endif 1434 | [finalImage unlockFocus]; 1435 | 1436 | return finalImage; 1437 | } 1438 | 1439 | - (NSImage *)_imageForSelectedRows 1440 | { 1441 | NSRect firstRowFrame = [self rectOfRow:[self.selectedRowIndexes firstIndex]]; 1442 | NSRect lastRowFrame = [self rectOfRow:[self.selectedRowIndexes lastIndex]]; 1443 | NSRect rowsFrame = NSMakeRect(NSMinX(firstRowFrame), NSMinY(firstRowFrame), NSWidth(firstRowFrame), NSMaxY(lastRowFrame) - NSMinY(firstRowFrame)); 1444 | // Extend the frame to show the top border 1445 | rowsFrame.origin.y -= 1.0; 1446 | rowsFrame.size.height += 1.0; 1447 | 1448 | // Take a snapshot of the view 1449 | NSImage *opaqueImage = [[NSImage alloc] initWithData:[self dataWithPDFInsideRect:rowsFrame]]; 1450 | 1451 | // Create the translucent drag image 1452 | NSImage *finalImage = [[NSImage alloc] initWithSize:[opaqueImage size]]; 1453 | [finalImage lockFocus]; 1454 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 1455 | [opaqueImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy fraction:0.7]; 1456 | #else 1457 | [opaqueImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:0.7]; 1458 | #endif 1459 | [finalImage unlockFocus]; 1460 | 1461 | return finalImage; 1462 | } 1463 | 1464 | - (NSUInteger)_dropColumnForPoint:(NSPoint)aPoint 1465 | { 1466 | NSUInteger column = [self columnAtPoint:aPoint]; 1467 | 1468 | if(column == NSNotFound) { 1469 | return NSNotFound; 1470 | } 1471 | 1472 | // If we're in the right half of the column, we intent to drop on the right side 1473 | NSRect columnFrame = [self rectOfColumn:column]; 1474 | columnFrame.size.width /= 2; 1475 | if (!NSPointInRect(aPoint, columnFrame)) { 1476 | column++; 1477 | } 1478 | 1479 | return column; 1480 | } 1481 | 1482 | - (NSUInteger)_dropRowForPoint:(NSPoint)aPoint 1483 | { 1484 | NSUInteger row = [self rowAtPoint:aPoint]; 1485 | 1486 | if(row == NSNotFound) { 1487 | return NSNotFound; 1488 | } 1489 | 1490 | // If we're in the bottom half of the row, we intent to drop on the bottom side 1491 | NSRect rowFrame = [self rectOfRow:row]; 1492 | rowFrame.size.height /= 2; 1493 | 1494 | if (!NSPointInRect(aPoint, rowFrame)) { 1495 | row++; 1496 | } 1497 | 1498 | return row; 1499 | } 1500 | 1501 | @end -------------------------------------------------------------------------------- /MBTableGrid.xcodeproj/TemplateIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikecsh/mbtablegrid/de2a3d37fc119a7fb14aa42dc715db85ae333924/MBTableGrid.xcodeproj/TemplateIcon.icns -------------------------------------------------------------------------------- /MBTableGrid.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; }; 11 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 12 | 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 13 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 14 | C9412A5F0D8AE98C00E9E614 /* MBTableGridController.m in Sources */ = {isa = PBXBuildFile; fileRef = C9412A5E0D8AE98C00E9E614 /* MBTableGridController.m */; }; 15 | E2E62BAC1781C33500F36275 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2E62BAB1781C33400F36275 /* Cocoa.framework */; }; 16 | E2E62BB21781C33500F36275 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E2E62BB01781C33500F36275 /* InfoPlist.strings */; }; 17 | E2E62BBA1781C37300F36275 /* MBTableGrid.m in Sources */ = {isa = PBXBuildFile; fileRef = C9412A3E0D8A061C00E9E614 /* MBTableGrid.m */; }; 18 | E2E62BBB1781C37600F36275 /* MBTableGridContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = C9412B210D8B2C5E00E9E614 /* MBTableGridContentView.m */; }; 19 | E2E62BBC1781C37800F36275 /* MBTableGridHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C9412B390D8B2F5400E9E614 /* MBTableGridHeaderView.m */; }; 20 | E2E62BBD1781C37B00F36275 /* MBTableGridHeaderCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C9412A4A0D8A294F00E9E614 /* MBTableGridHeaderCell.m */; }; 21 | E2E62BBE1781C38000F36275 /* MBTableGridCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C9412D7C0D8B5AB900E9E614 /* MBTableGridCell.m */; }; 22 | E2E62BBF1781C3FE00F36275 /* MBTableGrid.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2E62BAA1781C33400F36275 /* MBTableGrid.framework */; }; 23 | E2E62BF71781C53800F36275 /* MBTableGrid.h in Headers */ = {isa = PBXBuildFile; fileRef = C9412A3D0D8A061C00E9E614 /* MBTableGrid.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | E2E62BF81781C53800F36275 /* MBTableGridContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = C9412B200D8B2C5E00E9E614 /* MBTableGridContentView.h */; }; 25 | E2E62BF91781C53800F36275 /* MBTableGridHeaderView.h in Headers */ = {isa = PBXBuildFile; fileRef = C9412B380D8B2F5400E9E614 /* MBTableGridHeaderView.h */; }; 26 | E2E62BFA1781C53800F36275 /* MBTableGridHeaderCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C9412A490D8A294F00E9E614 /* MBTableGridHeaderCell.h */; }; 27 | E2E62BFB1781C53800F36275 /* MBTableGridCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C9412D7B0D8B5AB900E9E614 /* MBTableGridCell.h */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 32 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 33 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 34 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 35 | 29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = ""; }; 36 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 37 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 38 | 32CA4F630368D1EE00C91783 /* MBTableGrid_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBTableGrid_Prefix.pch; sourceTree = ""; }; 39 | 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 8D1107320486CEB800E47090 /* MBTableGrid.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MBTableGrid.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | C9412A3D0D8A061C00E9E614 /* MBTableGrid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBTableGrid.h; sourceTree = SOURCE_ROOT; }; 42 | C9412A3E0D8A061C00E9E614 /* MBTableGrid.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBTableGrid.m; sourceTree = SOURCE_ROOT; }; 43 | C9412A490D8A294F00E9E614 /* MBTableGridHeaderCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBTableGridHeaderCell.h; sourceTree = SOURCE_ROOT; }; 44 | C9412A4A0D8A294F00E9E614 /* MBTableGridHeaderCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBTableGridHeaderCell.m; sourceTree = SOURCE_ROOT; }; 45 | C9412A5D0D8AE98C00E9E614 /* MBTableGridController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBTableGridController.h; sourceTree = ""; }; 46 | C9412A5E0D8AE98C00E9E614 /* MBTableGridController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBTableGridController.m; sourceTree = ""; }; 47 | C9412B200D8B2C5E00E9E614 /* MBTableGridContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBTableGridContentView.h; sourceTree = SOURCE_ROOT; }; 48 | C9412B210D8B2C5E00E9E614 /* MBTableGridContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBTableGridContentView.m; sourceTree = SOURCE_ROOT; }; 49 | C9412B380D8B2F5400E9E614 /* MBTableGridHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBTableGridHeaderView.h; sourceTree = SOURCE_ROOT; }; 50 | C9412B390D8B2F5400E9E614 /* MBTableGridHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBTableGridHeaderView.m; sourceTree = SOURCE_ROOT; }; 51 | C9412D7B0D8B5AB900E9E614 /* MBTableGridCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBTableGridCell.h; sourceTree = SOURCE_ROOT; }; 52 | C9412D7C0D8B5AB900E9E614 /* MBTableGridCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBTableGridCell.m; sourceTree = SOURCE_ROOT; }; 53 | E2E62BAA1781C33400F36275 /* MBTableGrid.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MBTableGrid.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | E2E62BAB1781C33400F36275 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 55 | E2E62BAF1781C33500F36275 /* MBTableGrid-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MBTableGrid-Info.plist"; sourceTree = ""; }; 56 | E2E62BB11781C33500F36275 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 57 | E2E62BB31781C33500F36275 /* MBTableGrid-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MBTableGrid-Prefix.pch"; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 66 | E2E62BBF1781C3FE00F36275 /* MBTableGrid.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | E2E62BA61781C33400F36275 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | E2E62BAC1781C33500F36275 /* Cocoa.framework in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | 080E96DDFE201D6D7F000001 /* Classes */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | C9412A5D0D8AE98C00E9E614 /* MBTableGridController.h */, 85 | C9412A5E0D8AE98C00E9E614 /* MBTableGridController.m */, 86 | ); 87 | name = Classes; 88 | sourceTree = ""; 89 | }; 90 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 94 | ); 95 | name = "Linked Frameworks"; 96 | sourceTree = ""; 97 | }; 98 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 102 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 103 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 104 | ); 105 | name = "Other Frameworks"; 106 | sourceTree = ""; 107 | }; 108 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 8D1107320486CEB800E47090 /* MBTableGrid.app */, 112 | E2E62BAA1781C33400F36275 /* MBTableGrid.framework */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | 29B97314FDCFA39411CA2CEA /* MBTableGrid */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 080E96DDFE201D6D7F000001 /* Classes */, 121 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 122 | 29B97317FDCFA39411CA2CEA /* Resources */, 123 | E2E62BAD1781C33500F36275 /* MBTableGrid */, 124 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 125 | 19C28FACFE9D520D11CA2CBB /* Products */, 126 | ); 127 | name = MBTableGrid; 128 | sourceTree = ""; 129 | }; 130 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 32CA4F630368D1EE00C91783 /* MBTableGrid_Prefix.pch */, 134 | 29B97316FDCFA39411CA2CEA /* main.m */, 135 | ); 136 | name = "Other Sources"; 137 | sourceTree = ""; 138 | }; 139 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 8D1107310486CEB800E47090 /* Info.plist */, 143 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 144 | 29B97318FDCFA39411CA2CEA /* MainMenu.nib */, 145 | ); 146 | name = Resources; 147 | sourceTree = ""; 148 | }; 149 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 153 | E2E62BAB1781C33400F36275 /* Cocoa.framework */, 154 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 155 | ); 156 | name = Frameworks; 157 | sourceTree = ""; 158 | }; 159 | E2E62BAD1781C33500F36275 /* MBTableGrid */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | C9412A3D0D8A061C00E9E614 /* MBTableGrid.h */, 163 | C9412A3E0D8A061C00E9E614 /* MBTableGrid.m */, 164 | C9412B200D8B2C5E00E9E614 /* MBTableGridContentView.h */, 165 | C9412B210D8B2C5E00E9E614 /* MBTableGridContentView.m */, 166 | C9412B380D8B2F5400E9E614 /* MBTableGridHeaderView.h */, 167 | C9412B390D8B2F5400E9E614 /* MBTableGridHeaderView.m */, 168 | C9412A490D8A294F00E9E614 /* MBTableGridHeaderCell.h */, 169 | C9412A4A0D8A294F00E9E614 /* MBTableGridHeaderCell.m */, 170 | C9412D7B0D8B5AB900E9E614 /* MBTableGridCell.h */, 171 | C9412D7C0D8B5AB900E9E614 /* MBTableGridCell.m */, 172 | E2E62BAE1781C33500F36275 /* Supporting Files */, 173 | ); 174 | path = MBTableGrid; 175 | sourceTree = ""; 176 | }; 177 | E2E62BAE1781C33500F36275 /* Supporting Files */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | E2E62BAF1781C33500F36275 /* MBTableGrid-Info.plist */, 181 | E2E62BB01781C33500F36275 /* InfoPlist.strings */, 182 | E2E62BB31781C33500F36275 /* MBTableGrid-Prefix.pch */, 183 | ); 184 | name = "Supporting Files"; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXGroup section */ 188 | 189 | /* Begin PBXHeadersBuildPhase section */ 190 | E2E62BA71781C33400F36275 /* Headers */ = { 191 | isa = PBXHeadersBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | E2E62BF71781C53800F36275 /* MBTableGrid.h in Headers */, 195 | E2E62BF81781C53800F36275 /* MBTableGridContentView.h in Headers */, 196 | E2E62BF91781C53800F36275 /* MBTableGridHeaderView.h in Headers */, 197 | E2E62BFB1781C53800F36275 /* MBTableGridCell.h in Headers */, 198 | E2E62BFA1781C53800F36275 /* MBTableGridHeaderCell.h in Headers */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXHeadersBuildPhase section */ 203 | 204 | /* Begin PBXNativeTarget section */ 205 | 8D1107260486CEB800E47090 /* MBTableGridDemo */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "MBTableGridDemo" */; 208 | buildPhases = ( 209 | 8D1107290486CEB800E47090 /* Resources */, 210 | 8D11072C0486CEB800E47090 /* Sources */, 211 | 8D11072E0486CEB800E47090 /* Frameworks */, 212 | ); 213 | buildRules = ( 214 | ); 215 | dependencies = ( 216 | ); 217 | name = MBTableGridDemo; 218 | productInstallPath = "$(HOME)/Applications"; 219 | productName = MBTableGrid; 220 | productReference = 8D1107320486CEB800E47090 /* MBTableGrid.app */; 221 | productType = "com.apple.product-type.application"; 222 | }; 223 | E2E62BA91781C33400F36275 /* MBTableGrid */ = { 224 | isa = PBXNativeTarget; 225 | buildConfigurationList = E2E62BB71781C33500F36275 /* Build configuration list for PBXNativeTarget "MBTableGrid" */; 226 | buildPhases = ( 227 | E2E62BA51781C33400F36275 /* Sources */, 228 | E2E62BA61781C33400F36275 /* Frameworks */, 229 | E2E62BA71781C33400F36275 /* Headers */, 230 | E2E62BA81781C33400F36275 /* Resources */, 231 | ); 232 | buildRules = ( 233 | ); 234 | dependencies = ( 235 | ); 236 | name = MBTableGrid; 237 | productName = MBTableGrid; 238 | productReference = E2E62BAA1781C33400F36275 /* MBTableGrid.framework */; 239 | productType = "com.apple.product-type.framework"; 240 | }; 241 | /* End PBXNativeTarget section */ 242 | 243 | /* Begin PBXProject section */ 244 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 245 | isa = PBXProject; 246 | attributes = { 247 | LastUpgradeCheck = 0460; 248 | }; 249 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MBTableGrid" */; 250 | compatibilityVersion = "Xcode 3.2"; 251 | developmentRegion = English; 252 | hasScannedForEncodings = 1; 253 | knownRegions = ( 254 | en, 255 | ); 256 | mainGroup = 29B97314FDCFA39411CA2CEA /* MBTableGrid */; 257 | projectDirPath = ""; 258 | projectRoot = ""; 259 | targets = ( 260 | 8D1107260486CEB800E47090 /* MBTableGridDemo */, 261 | E2E62BA91781C33400F36275 /* MBTableGrid */, 262 | ); 263 | }; 264 | /* End PBXProject section */ 265 | 266 | /* Begin PBXResourcesBuildPhase section */ 267 | 8D1107290486CEB800E47090 /* Resources */ = { 268 | isa = PBXResourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */, 272 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | E2E62BA81781C33400F36275 /* Resources */ = { 277 | isa = PBXResourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | E2E62BB21781C33500F36275 /* InfoPlist.strings in Resources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | /* End PBXResourcesBuildPhase section */ 285 | 286 | /* Begin PBXSourcesBuildPhase section */ 287 | 8D11072C0486CEB800E47090 /* Sources */ = { 288 | isa = PBXSourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | 8D11072D0486CEB800E47090 /* main.m in Sources */, 292 | C9412A5F0D8AE98C00E9E614 /* MBTableGridController.m in Sources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | E2E62BA51781C33400F36275 /* Sources */ = { 297 | isa = PBXSourcesBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | E2E62BBA1781C37300F36275 /* MBTableGrid.m in Sources */, 301 | E2E62BBB1781C37600F36275 /* MBTableGridContentView.m in Sources */, 302 | E2E62BBC1781C37800F36275 /* MBTableGridHeaderView.m in Sources */, 303 | E2E62BBD1781C37B00F36275 /* MBTableGridHeaderCell.m in Sources */, 304 | E2E62BBE1781C38000F36275 /* MBTableGridCell.m in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | /* End PBXSourcesBuildPhase section */ 309 | 310 | /* Begin PBXVariantGroup section */ 311 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { 312 | isa = PBXVariantGroup; 313 | children = ( 314 | 089C165DFE840E0CC02AAC07 /* English */, 315 | ); 316 | name = InfoPlist.strings; 317 | sourceTree = ""; 318 | }; 319 | 29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = { 320 | isa = PBXVariantGroup; 321 | children = ( 322 | 29B97319FDCFA39411CA2CEA /* English */, 323 | ); 324 | name = MainMenu.nib; 325 | sourceTree = ""; 326 | }; 327 | E2E62BB01781C33500F36275 /* InfoPlist.strings */ = { 328 | isa = PBXVariantGroup; 329 | children = ( 330 | E2E62BB11781C33500F36275 /* en */, 331 | ); 332 | name = InfoPlist.strings; 333 | sourceTree = ""; 334 | }; 335 | /* End PBXVariantGroup section */ 336 | 337 | /* Begin XCBuildConfiguration section */ 338 | C01FCF4B08A954540054247B /* Debug */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ALWAYS_SEARCH_USER_PATHS = NO; 342 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 343 | CLANG_ENABLE_OBJC_ARC = YES; 344 | COMBINE_HIDPI_IMAGES = YES; 345 | COPY_PHASE_STRIP = NO; 346 | GCC_DYNAMIC_NO_PIC = NO; 347 | GCC_MODEL_TUNING = G5; 348 | GCC_OPTIMIZATION_LEVEL = 0; 349 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 350 | GCC_PREFIX_HEADER = MBTableGrid_Prefix.pch; 351 | INFOPLIST_FILE = Info.plist; 352 | INSTALL_PATH = "$(HOME)/Applications"; 353 | MACOSX_DEPLOYMENT_TARGET = 10.8; 354 | PRODUCT_NAME = MBTableGrid; 355 | SDKROOT = macosx; 356 | }; 357 | name = Debug; 358 | }; 359 | C01FCF4C08A954540054247B /* Release */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ALWAYS_SEARCH_USER_PATHS = NO; 363 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 364 | CLANG_ENABLE_OBJC_ARC = YES; 365 | COMBINE_HIDPI_IMAGES = YES; 366 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 367 | GCC_MODEL_TUNING = G5; 368 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 369 | GCC_PREFIX_HEADER = MBTableGrid_Prefix.pch; 370 | INFOPLIST_FILE = Info.plist; 371 | INSTALL_PATH = "$(HOME)/Applications"; 372 | MACOSX_DEPLOYMENT_TARGET = 10.8; 373 | PRODUCT_NAME = MBTableGrid; 374 | SDKROOT = macosx; 375 | }; 376 | name = Release; 377 | }; 378 | C01FCF4F08A954540054247B /* Debug */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ALWAYS_SEARCH_USER_PATHS = NO; 382 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 383 | GCC_OPTIMIZATION_LEVEL = 0; 384 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 385 | GCC_WARN_UNUSED_VARIABLE = YES; 386 | ONLY_ACTIVE_ARCH = YES; 387 | SDKROOT = macosx; 388 | }; 389 | name = Debug; 390 | }; 391 | C01FCF5008A954540054247B /* Release */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ALWAYS_SEARCH_USER_PATHS = NO; 395 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | SDKROOT = macosx; 399 | }; 400 | name = Release; 401 | }; 402 | E2E62BB81781C33500F36275 /* Debug */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 406 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 407 | CLANG_CXX_LIBRARY = "libc++"; 408 | CLANG_ENABLE_OBJC_ARC = YES; 409 | CLANG_WARN_CONSTANT_CONVERSION = YES; 410 | CLANG_WARN_EMPTY_BODY = YES; 411 | CLANG_WARN_ENUM_CONVERSION = YES; 412 | CLANG_WARN_INT_CONVERSION = YES; 413 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 414 | COMBINE_HIDPI_IMAGES = YES; 415 | COPY_PHASE_STRIP = NO; 416 | DYLIB_COMPATIBILITY_VERSION = 1; 417 | DYLIB_CURRENT_VERSION = 1; 418 | FRAMEWORK_VERSION = A; 419 | GCC_C_LANGUAGE_STANDARD = gnu99; 420 | GCC_DYNAMIC_NO_PIC = NO; 421 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 422 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 423 | GCC_PREFIX_HEADER = "MBTableGrid/MBTableGrid-Prefix.pch"; 424 | GCC_PREPROCESSOR_DEFINITIONS = ( 425 | "DEBUG=1", 426 | "$(inherited)", 427 | ); 428 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 429 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 430 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 431 | INFOPLIST_FILE = "MBTableGrid/MBTableGrid-Info.plist"; 432 | INSTALL_PATH = "@loader_path/../Frameworks"; 433 | MACOSX_DEPLOYMENT_TARGET = 10.8; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | WRAPPER_EXTENSION = framework; 436 | }; 437 | name = Debug; 438 | }; 439 | E2E62BB91781C33500F36275 /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 443 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 444 | CLANG_CXX_LIBRARY = "libc++"; 445 | CLANG_ENABLE_OBJC_ARC = YES; 446 | CLANG_WARN_CONSTANT_CONVERSION = YES; 447 | CLANG_WARN_EMPTY_BODY = YES; 448 | CLANG_WARN_ENUM_CONVERSION = YES; 449 | CLANG_WARN_INT_CONVERSION = YES; 450 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 451 | COMBINE_HIDPI_IMAGES = YES; 452 | COPY_PHASE_STRIP = YES; 453 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 454 | DYLIB_COMPATIBILITY_VERSION = 1; 455 | DYLIB_CURRENT_VERSION = 1; 456 | FRAMEWORK_VERSION = A; 457 | GCC_C_LANGUAGE_STANDARD = gnu99; 458 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 459 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 460 | GCC_PREFIX_HEADER = "MBTableGrid/MBTableGrid-Prefix.pch"; 461 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 462 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 463 | INFOPLIST_FILE = "MBTableGrid/MBTableGrid-Info.plist"; 464 | INSTALL_PATH = "@loader_path/../Frameworks"; 465 | MACOSX_DEPLOYMENT_TARGET = 10.8; 466 | PRODUCT_NAME = "$(TARGET_NAME)"; 467 | WRAPPER_EXTENSION = framework; 468 | }; 469 | name = Release; 470 | }; 471 | /* End XCBuildConfiguration section */ 472 | 473 | /* Begin XCConfigurationList section */ 474 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "MBTableGridDemo" */ = { 475 | isa = XCConfigurationList; 476 | buildConfigurations = ( 477 | C01FCF4B08A954540054247B /* Debug */, 478 | C01FCF4C08A954540054247B /* Release */, 479 | ); 480 | defaultConfigurationIsVisible = 0; 481 | defaultConfigurationName = Release; 482 | }; 483 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MBTableGrid" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | C01FCF4F08A954540054247B /* Debug */, 487 | C01FCF5008A954540054247B /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | E2E62BB71781C33500F36275 /* Build configuration list for PBXNativeTarget "MBTableGrid" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | E2E62BB81781C33500F36275 /* Debug */, 496 | E2E62BB91781C33500F36275 /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | }; 500 | /* End XCConfigurationList section */ 501 | }; 502 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 503 | } 504 | -------------------------------------------------------------------------------- /MBTableGrid.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MBTableGrid.xcodeproj/project.xcworkspace/xcuserdata/mikecsh.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikecsh/mbtablegrid/de2a3d37fc119a7fb14aa42dc715db85ae333924/MBTableGrid.xcodeproj/project.xcworkspace/xcuserdata/mikecsh.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MBTableGrid.xcodeproj/project.xcworkspace/xcuserdata/mikecsh.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MBTableGrid.xcodeproj/xcuserdata/mikecsh.xcuserdatad/xcschemes/MBTableGrid.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | 45 | 51 | 52 | 54 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /MBTableGrid.xcodeproj/xcuserdata/mikecsh.xcuserdatad/xcschemes/MBTableGridDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /MBTableGrid.xcodeproj/xcuserdata/mikecsh.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MBTableGrid.xcscheme 8 | 9 | orderHint 10 | 2 11 | 12 | MBTableGridDemo.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 8D1107260486CEB800E47090 21 | 22 | primary 23 | 24 | 25 | E2E62BA91781C33400F36275 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /MBTableGrid/MBTableGrid-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.github.mikecsh.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /MBTableGrid/MBTableGrid-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'MBTableGrid' target in the 'MBTableGrid' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /MBTableGrid/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /MBTableGridCell.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import 27 | 28 | /** 29 | * @brief \c MBTableGridCell is responsible for 30 | * the drawing and editing of \c MBTableGrid's 31 | * cells. 32 | */ 33 | @interface MBTableGridCell : NSTextFieldCell { 34 | 35 | } 36 | 37 | - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView withBackgroundColor:(NSColor *)backgroundColor; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /MBTableGridCell.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import "MBTableGridCell.h" 27 | 28 | 29 | @implementation MBTableGridCell 30 | 31 | -(id)initTextCell:(NSString *)aString 32 | { 33 | self = [super initTextCell:aString]; 34 | 35 | if (self) 36 | { 37 | [self setBackgroundColor:[NSColor clearColor]]; 38 | return self; 39 | } 40 | 41 | return nil; 42 | } 43 | 44 | - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView withBackgroundColor:(NSColor *)backgroundColor 45 | { 46 | 47 | [backgroundColor set]; 48 | NSRectFill(cellFrame); 49 | 50 | [self drawWithFrame:cellFrame inView:controlView]; 51 | } 52 | 53 | - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView 54 | { 55 | 56 | NSColor *borderColor = [NSColor colorWithDeviceWhite:0.83 alpha:1.0]; 57 | [borderColor set]; 58 | 59 | // Draw the right border 60 | NSRect rightLine = NSMakeRect(NSMaxX(cellFrame)-1.0, NSMinY(cellFrame), 1.0, NSHeight(cellFrame)); 61 | NSRectFill(rightLine); 62 | 63 | // Draw the bottom border 64 | NSRect bottomLine = NSMakeRect(NSMinX(cellFrame), NSMaxY(cellFrame)-1.0, NSWidth(cellFrame), 1.0); 65 | NSRectFill(bottomLine); 66 | 67 | [self drawInteriorWithFrame:cellFrame inView:controlView]; 68 | } 69 | 70 | - (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView 71 | { 72 | // Do not draw any highlight. 73 | return nil; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /MBTableGridContentView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import 27 | 28 | #define MBTableGridColumnHeaderHeight 19.0 29 | #define MBTableGridColumnHeaderWidth 60 30 | #define MBTableGridRowHeaderWidth 40.0 31 | #define COLUMNFLOATSIZE(x) [NSNumber numberWithFloat:x] 32 | #define COLUMNKEY(idx) [NSString stringWithFormat:@"column%lu",idx] 33 | 34 | @class MBTableGrid, MBTableGridCell; 35 | 36 | /** 37 | * @brief \c MBTableGridContentView provides the actual display 38 | * and editing capabilities of MBTableGrid. It is designed 39 | * to be placed inside a scroll view. 40 | */ 41 | @interface MBTableGridContentView : NSView { 42 | NSInteger mouseDownColumn; 43 | NSInteger mouseDownRow; 44 | 45 | NSInteger editedColumn; 46 | NSInteger editedRow; 47 | 48 | NSImage *cursorImage; 49 | NSImage *cursorExtendSelectionImage; 50 | NSRect grabHandleRect; 51 | 52 | NSInteger dropColumn; 53 | NSInteger dropRow; 54 | 55 | NSTimer *autoscrollTimer; 56 | 57 | BOOL isDraggingColumnOrRow; 58 | 59 | MBTableGridCell *_cell; 60 | 61 | NSMutableArray *columnWidths; 62 | 63 | } 64 | 65 | /** 66 | * @name The Grid View 67 | */ 68 | /** 69 | * @{ 70 | */ 71 | 72 | /** 73 | * @brief Returns the \c MBTableGrid the receiver 74 | * belongs to. 75 | */ 76 | - (MBTableGrid *)tableGrid; 77 | 78 | /** 79 | * @} 80 | */ 81 | 82 | /** 83 | * @name Editing Values 84 | */ 85 | /** 86 | * @{ 87 | */ 88 | 89 | /** 90 | * @brief Begin editing the currently-selected 91 | * cell. If multiple cells are selected, 92 | * selects the top-left one and begins 93 | * editing its value. 94 | */ 95 | - (void)editSelectedCell:(id)sender; 96 | 97 | /** 98 | * @} 99 | */ 100 | 101 | /** 102 | * @name Layout Support 103 | */ 104 | /** 105 | * @{ 106 | */ 107 | 108 | /** 109 | * @brief Returns the rectangle containing the column at 110 | * a given index. 111 | * @param columnIndex The index of a column in the receiver. 112 | * @return The rectangle containing the column at \c columnIndex. 113 | * Returns \c NSZeroRect if \c columnIndex lies outside 114 | * the range of valid column indices for the receiver. 115 | * @see frameOfCellAtColumn:row: 116 | * @see rectOfRow: 117 | */ 118 | - (NSRect)rectOfColumn:(NSUInteger)columnIndex; 119 | 120 | /** 121 | * @brief Returns the rectangle containing the row at a 122 | * given index. 123 | * @param rowIndex The index of a row in the receiver. 124 | * @return The rectangle containing the row at \c rowIndex. 125 | * Returns \c NSZeroRect if \c rowIndex lies outside 126 | * the range of valid column indices for the receiver. 127 | * @see frameOfCellAtColumn:row: 128 | * @see rectOfColumn: 129 | */ 130 | - (NSRect)rectOfRow:(NSUInteger)rowIndex; 131 | 132 | /** 133 | * @brief Returns a rectangle locating the cell that lies at 134 | * the intersection of \c columnIndex and \c rowIndex. 135 | * @param columnIndex The index of the column containing the cell 136 | * whose rectangle you want. 137 | * @param rowIndex The index of the row containing the cell 138 | * whose rectangle you want. 139 | * @return A rectangle locating the cell that lies at the intersection 140 | * of \c columnIndex and \c rowIndex. Returns \c NSZeroRect if 141 | * \c columnIndex or \c rowIndex is greater than the number of 142 | * columns or rows in the receiver. 143 | * @see rectOfColumn: 144 | * @see rectOfRow: 145 | */ 146 | - (NSRect)frameOfCellAtColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 147 | 148 | /** 149 | * @brief Returns the index of the column a given point lies in. 150 | * @param aPoint A point in the coordinate system of the receiver. 151 | * @return The index of the column \c aPoint lies in, or \c NSNotFound if \c aPoint 152 | * lies outside the receiver's bounds. 153 | * @see rowAtPoint: 154 | */ 155 | - (NSInteger)columnAtPoint:(NSPoint)aPoint; 156 | 157 | /** 158 | * @brief Returns the index of the row a given point lies in. 159 | * @param aPoint A point in the coordinate system of the receiver. 160 | * @return The index of the row \c aPoint lies in, or \c NSNotFound if \c aPoint 161 | * lies outside the receiver's bounds. 162 | * @see columnAtPoint: 163 | */ 164 | - (NSInteger)rowAtPoint:(NSPoint)aPoint; 165 | 166 | /** 167 | * @} 168 | */ 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /MBTableGridContentView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import "MBTableGridContentView.h" 27 | 28 | #import "MBTableGrid.h" 29 | #import "MBTableGridCell.h" 30 | 31 | 32 | #define kGRAB_HANDLE_HALF_SIDE_LENGTH 2.0f 33 | #define kGRAB_HANDLE_SIDE_LENGTH 4.0f 34 | 35 | @interface MBTableGrid (Private) 36 | - (id)_objectValueForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 37 | - (void)_setObjectValue:(id)value forColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 38 | - (BOOL)_canEditCellAtColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 39 | - (void)_setStickyColumn:(MBTableGridEdge)stickyColumn row:(MBTableGridEdge)stickyRow; 40 | - (float)_widthForColumn:(NSUInteger)columnIndex; 41 | - (id)_backgroundColorForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex; 42 | - (MBTableGridEdge)_stickyColumn; 43 | - (MBTableGridEdge)_stickyRow; 44 | @end 45 | 46 | @interface MBTableGridContentView (Cursors) 47 | - (NSCursor *)_cellSelectionCursor; 48 | - (NSImage *)_cellSelectionCursorImage; 49 | - (NSCursor *)_cellExtendSelectionCursor; 50 | - (NSImage *)_cellExtendSelectionCursorImage; 51 | @end 52 | 53 | @interface MBTableGridContentView (DragAndDrop) 54 | - (void)_setDraggingColumnOrRow:(BOOL)flag; 55 | - (void)_setDropColumn:(NSInteger)columnIndex; 56 | - (void)_setDropRow:(NSInteger)rowIndex; 57 | - (void)_timerAutoscrollCallback:(NSTimer *)aTimer; 58 | @end 59 | 60 | @implementation MBTableGridContentView 61 | 62 | #pragma mark - 63 | #pragma mark Initialization & Superclass Overrides 64 | 65 | - (id)initWithFrame:(NSRect)frameRect 66 | { 67 | if(self = [super initWithFrame:frameRect]) { 68 | mouseDownColumn = NSNotFound; 69 | mouseDownRow = NSNotFound; 70 | 71 | editedColumn = NSNotFound; 72 | editedRow = NSNotFound; 73 | 74 | dropColumn = NSNotFound; 75 | dropRow = NSNotFound; 76 | 77 | 78 | grabHandleRect = NSRectFromCGRect(CGRectZero); 79 | 80 | // Cache the cursor image 81 | cursorImage = [self _cellSelectionCursorImage]; 82 | cursorExtendSelectionImage = [self _cellExtendSelectionCursorImage]; 83 | 84 | isDraggingColumnOrRow = NO; 85 | 86 | _cell = [[MBTableGridCell alloc] initTextCell:@""]; 87 | [_cell setBordered:YES]; 88 | [_cell setScrollable:YES]; 89 | [_cell setLineBreakMode:NSLineBreakByTruncatingTail]; 90 | } 91 | return self; 92 | } 93 | 94 | 95 | - (void)drawRect:(NSRect)rect 96 | { 97 | 98 | NSUInteger numberOfColumns = [self tableGrid].numberOfColumns; 99 | NSUInteger numberOfRows = [self tableGrid].numberOfRows; 100 | 101 | NSUInteger firstColumn = NSNotFound; 102 | NSUInteger lastColumn = numberOfColumns - 1; 103 | NSUInteger firstRow = NSNotFound; 104 | NSUInteger lastRow = numberOfRows - 1; 105 | 106 | // Find the columns to draw 107 | NSUInteger column = 0; 108 | while (column < numberOfColumns) { 109 | NSRect columnRect = [self rectOfColumn:column]; 110 | if (firstColumn == NSNotFound && NSMinX([self visibleRect]) >= NSMinX(columnRect) && NSMinX([self visibleRect]) <= NSMaxX(columnRect)) { 111 | firstColumn = column; 112 | } else if (firstColumn != NSNotFound && NSMaxX([self visibleRect]) >= NSMinX(columnRect) && NSMaxX([self visibleRect]) <= NSMaxX(columnRect)) { 113 | lastColumn = column; 114 | break; 115 | } 116 | column++; 117 | } 118 | 119 | // Find the rows to draw 120 | NSUInteger row = 0; 121 | while (row < numberOfRows) { 122 | NSRect rowRect = [self rectOfRow:row]; 123 | if (firstRow == NSNotFound && NSMinY([self visibleRect]) >= rowRect.origin.x && NSMinY([self visibleRect]) <= NSMaxY(rowRect)) { 124 | firstRow = row; 125 | } else if (firstRow != NSNotFound && NSMaxY([self visibleRect]) >= NSMinY(rowRect) && NSMaxY([self visibleRect]) <= NSMaxY(rowRect)) { 126 | lastRow = row; 127 | break; 128 | } 129 | row++; 130 | } 131 | 132 | column = firstColumn; 133 | while (column <= lastColumn) { 134 | row = firstRow; 135 | while (row <= lastRow) { 136 | NSRect cellFrame = [self frameOfCellAtColumn:column row:row]; 137 | // Only draw the cell if we need to 138 | if ([self needsToDrawRect:cellFrame] && !(row == editedRow && column == editedColumn)) { 139 | 140 | NSColor *backgroundColor = [[self tableGrid] _backgroundColorForColumn:column row:row] ?: [NSColor whiteColor]; 141 | 142 | 143 | [_cell setObjectValue:[[self tableGrid] _objectValueForColumn:column row:row]]; 144 | [_cell drawWithFrame:cellFrame inView:self withBackgroundColor:backgroundColor];// Draw background color 145 | 146 | } 147 | row++; 148 | } 149 | column++; 150 | } 151 | 152 | // Draw the selection rectangle 153 | NSIndexSet *selectedColumns = [[self tableGrid] selectedColumnIndexes]; 154 | NSIndexSet *selectedRows = [[self tableGrid] selectedRowIndexes]; 155 | 156 | if([selectedColumns count] && [selectedRows count] && [self tableGrid].numberOfColumns > 0 && [self tableGrid].numberOfRows > 0) { 157 | NSRect selectionTopLeft = [self frameOfCellAtColumn:[selectedColumns firstIndex] row:[selectedRows firstIndex]]; 158 | NSRect selectionBottomRight = [self frameOfCellAtColumn:[selectedColumns lastIndex] row:[selectedRows lastIndex]]; 159 | 160 | NSRect selectionRect; 161 | selectionRect.origin = selectionTopLeft.origin; 162 | selectionRect.size.width = NSMaxX(selectionBottomRight)-selectionTopLeft.origin.x; 163 | selectionRect.size.height = NSMaxY(selectionBottomRight)-selectionTopLeft.origin.y; 164 | 165 | NSRect selectionInsetRect = NSInsetRect(selectionRect, 1, 1); 166 | NSBezierPath *selectionPath = [NSBezierPath bezierPathWithRect:selectionInsetRect]; 167 | NSAffineTransform *translate = [NSAffineTransform transform]; 168 | [translate translateXBy:-0.5 yBy:-0.5]; 169 | [selectionPath transformUsingAffineTransform:translate]; 170 | 171 | NSColor *selectionColor = [NSColor alternateSelectedControlColor]; 172 | 173 | // If the view is not the first responder, then use a gray selection color 174 | NSResponder *firstResponder = [[self window] firstResponder]; 175 | if (![[firstResponder class] isSubclassOfClass:[NSView class]] || ![(NSView *)firstResponder isDescendantOf:[self tableGrid]] || ![[self window] isKeyWindow]) { 176 | selectionColor = [[selectionColor colorUsingColorSpaceName:NSDeviceWhiteColorSpace] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; 177 | } 178 | else 179 | { 180 | // Draw grab handle 181 | [selectionColor set]; 182 | grabHandleRect = NSMakeRect(NSMaxX(selectionInsetRect) - kGRAB_HANDLE_HALF_SIDE_LENGTH, NSMaxY(selectionInsetRect) - kGRAB_HANDLE_HALF_SIDE_LENGTH, kGRAB_HANDLE_SIDE_LENGTH, kGRAB_HANDLE_SIDE_LENGTH); 183 | NSRectFill(grabHandleRect); 184 | } 185 | 186 | [selectionColor set]; 187 | [selectionPath setLineWidth: 1.0]; 188 | [selectionPath stroke]; 189 | 190 | [[selectionColor colorWithAlphaComponent:0.2f] set]; 191 | [selectionPath fill]; 192 | 193 | // Inavlidate cursors so we use the correct cursor for the selection in the right place 194 | [[self window] invalidateCursorRectsForView:self]; 195 | } 196 | 197 | // Draw the column drop indicator 198 | if (isDraggingColumnOrRow && dropColumn != NSNotFound && dropColumn <= [self tableGrid].numberOfColumns && dropRow == NSNotFound) { 199 | NSRect columnBorder; 200 | if(dropColumn < [self tableGrid].numberOfColumns) { 201 | columnBorder = [self rectOfColumn:dropColumn]; 202 | } else { 203 | columnBorder = [self rectOfColumn:dropColumn-1]; 204 | columnBorder.origin.x += columnBorder.size.width; 205 | } 206 | columnBorder.origin.x = NSMinX(columnBorder)-2.0; 207 | columnBorder.size.width = 4.0; 208 | 209 | NSColor *selectionColor = [NSColor alternateSelectedControlColor]; 210 | 211 | NSBezierPath *borderPath = [NSBezierPath bezierPathWithRect:columnBorder]; 212 | [borderPath setLineWidth:2.0]; 213 | 214 | [selectionColor set]; 215 | [borderPath stroke]; 216 | } 217 | 218 | // Draw the row drop indicator 219 | if (isDraggingColumnOrRow && dropRow != NSNotFound && dropRow <= [self tableGrid].numberOfRows && dropColumn == NSNotFound) { 220 | NSRect rowBorder; 221 | if(dropRow < [self tableGrid].numberOfRows) { 222 | rowBorder = [self rectOfRow:dropRow]; 223 | } else { 224 | rowBorder = [self rectOfRow:dropRow-1]; 225 | rowBorder.origin.y += rowBorder.size.height; 226 | } 227 | rowBorder.origin.y = NSMinY(rowBorder)-2.0; 228 | rowBorder.size.height = 4.0; 229 | 230 | NSColor *selectionColor = [NSColor alternateSelectedControlColor]; 231 | 232 | NSBezierPath *borderPath = [NSBezierPath bezierPathWithRect:rowBorder]; 233 | [borderPath setLineWidth:2.0]; 234 | 235 | [selectionColor set]; 236 | [borderPath stroke]; 237 | } 238 | 239 | // Draw the cell drop indicator 240 | if (!isDraggingColumnOrRow && dropRow != NSNotFound && dropRow <= [self tableGrid].numberOfRows && dropColumn != NSNotFound && dropColumn <= [self tableGrid].numberOfColumns) { 241 | NSRect cellFrame = [self frameOfCellAtColumn:dropColumn row:dropRow]; 242 | cellFrame.origin.x -= 2.0; 243 | cellFrame.origin.y -= 2.0; 244 | cellFrame.size.width += 3.0; 245 | cellFrame.size.height += 3.0; 246 | 247 | NSBezierPath *borderPath = [NSBezierPath bezierPathWithRect:NSInsetRect(cellFrame, 2, 2)]; 248 | 249 | NSColor *dropColor = [NSColor alternateSelectedControlColor]; 250 | [dropColor set]; 251 | 252 | [borderPath setLineWidth:2.0]; 253 | [borderPath stroke]; 254 | } 255 | } 256 | 257 | - (BOOL)isFlipped 258 | { 259 | return YES; 260 | } 261 | 262 | - (void)mouseDown:(NSEvent *)theEvent 263 | { 264 | 265 | // Setup the timer for autoscrolling 266 | // (the simply calling autoscroll: from mouseDragged: only works as long as the mouse is moving) 267 | autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(_timerAutoscrollCallback:) userInfo:nil repeats:YES]; 268 | 269 | NSPoint loc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 270 | mouseDownColumn = [self columnAtPoint:loc]; 271 | mouseDownRow = [self rowAtPoint:loc]; 272 | 273 | if([theEvent clickCount] == 1) { 274 | // Pass the event back to the MBTableGrid (Used to give First Responder status) 275 | [[self tableGrid] mouseDown:theEvent]; 276 | 277 | // Single click 278 | if(([theEvent modifierFlags] & NSShiftKeyMask) && [self tableGrid].allowsMultipleSelection) { 279 | // If the shift key was held down, extend the selection 280 | NSUInteger stickyColumn = [[self tableGrid].selectedColumnIndexes firstIndex]; 281 | NSUInteger stickyRow = [[self tableGrid].selectedRowIndexes firstIndex]; 282 | 283 | MBTableGridEdge stickyColumnEdge = [[self tableGrid] _stickyColumn]; 284 | MBTableGridEdge stickyRowEdge = [[self tableGrid] _stickyRow]; 285 | 286 | // Compensate for sticky edges 287 | if (stickyColumnEdge == MBTableGridRightEdge) { 288 | stickyColumn = [[self tableGrid].selectedColumnIndexes lastIndex]; 289 | } 290 | if (stickyRowEdge == MBTableGridBottomEdge) { 291 | stickyRow = [[self tableGrid].selectedRowIndexes lastIndex]; 292 | } 293 | 294 | NSRange selectionColumnRange = NSMakeRange(stickyColumn, mouseDownColumn-stickyColumn+1); 295 | NSRange selectionRowRange = NSMakeRange(stickyRow, mouseDownRow-stickyRow+1); 296 | 297 | if (mouseDownColumn < stickyColumn) { 298 | selectionColumnRange = NSMakeRange(mouseDownColumn, stickyColumn-mouseDownColumn+1); 299 | stickyColumnEdge = MBTableGridRightEdge; 300 | } else { 301 | stickyColumnEdge = MBTableGridLeftEdge; 302 | } 303 | 304 | if (mouseDownRow < stickyRow) { 305 | selectionRowRange = NSMakeRange(mouseDownRow, stickyRow-mouseDownRow+1); 306 | stickyRowEdge = MBTableGridBottomEdge; 307 | } else { 308 | stickyRowEdge = MBTableGridTopEdge; 309 | } 310 | 311 | // Select the proper cells 312 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:selectionColumnRange]; 313 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndexesInRange:selectionRowRange]; 314 | 315 | // Set the sticky edges 316 | [[self tableGrid] _setStickyColumn:stickyColumnEdge row:stickyRowEdge]; 317 | } else { 318 | // No modifier keys, so change the selection 319 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndex:mouseDownColumn]; 320 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndex:mouseDownRow]; 321 | [[self tableGrid] _setStickyColumn:MBTableGridLeftEdge row:MBTableGridTopEdge]; 322 | } 323 | 324 | [self setNeedsDisplay:YES]; 325 | 326 | } else if([theEvent clickCount] == 2) { 327 | // Double click 328 | [self editSelectedCell:self]; 329 | [self setNeedsDisplay:YES]; 330 | } 331 | } 332 | 333 | - (void)mouseDragged:(NSEvent *)theEvent 334 | { 335 | if (mouseDownColumn != NSNotFound && mouseDownRow != NSNotFound && [self tableGrid].allowsMultipleSelection) { 336 | NSPoint loc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 337 | NSInteger column = [self columnAtPoint:loc]; 338 | NSInteger row = [self rowAtPoint:loc]; 339 | 340 | MBTableGridEdge columnEdge = MBTableGridLeftEdge; 341 | MBTableGridEdge rowEdge = MBTableGridTopEdge; 342 | 343 | // Select the appropriate number of columns 344 | if(column != NSNotFound) { 345 | NSInteger firstColumnToSelect = mouseDownColumn; 346 | NSInteger numberOfColumnsToSelect = column-mouseDownColumn+1; 347 | if(column < mouseDownColumn) { 348 | firstColumnToSelect = column; 349 | numberOfColumnsToSelect = mouseDownColumn-column+1; 350 | 351 | // Set the sticky edge to the right 352 | columnEdge = MBTableGridRightEdge; 353 | } 354 | 355 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(firstColumnToSelect,numberOfColumnsToSelect)]; 356 | 357 | } 358 | 359 | // Select the appropriate number of rows 360 | if(row != NSNotFound) { 361 | NSInteger firstRowToSelect = mouseDownRow; 362 | NSInteger numberOfRowsToSelect = row-mouseDownRow+1; 363 | if(row < mouseDownRow) { 364 | firstRowToSelect = row; 365 | numberOfRowsToSelect = mouseDownRow-row+1; 366 | 367 | // Set the sticky row to the bottom 368 | rowEdge = MBTableGridBottomEdge; 369 | } 370 | 371 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(firstRowToSelect,numberOfRowsToSelect)]; 372 | 373 | } 374 | 375 | // Set the sticky edges 376 | [[self tableGrid] _setStickyColumn:columnEdge row:rowEdge]; 377 | 378 | [self setNeedsDisplay:YES]; 379 | } 380 | 381 | // [self autoscroll:theEvent]; 382 | } 383 | 384 | - (void)mouseUp:(NSEvent *)theEvent 385 | { 386 | [autoscrollTimer invalidate]; 387 | autoscrollTimer = nil; 388 | 389 | mouseDownColumn = NSNotFound; 390 | mouseDownRow = NSNotFound; 391 | } 392 | 393 | #pragma mark Cursor Rects 394 | 395 | - (void)resetCursorRects 396 | { 397 | //NSLog(@"%s - %f %f %f %f", __func__, grabHandleRect.origin.x, grabHandleRect.origin.y, grabHandleRect.size.width, grabHandleRect.size.height); 398 | // The main cursor should be the cell selection cursor 399 | [self addCursorRect:[self visibleRect] cursor:[self _cellSelectionCursor]]; 400 | [self addCursorRect:grabHandleRect cursor:[self _cellExtendSelectionCursor]]; 401 | } 402 | 403 | #pragma mark - 404 | #pragma mark Notifications 405 | 406 | #pragma mark Field Editor 407 | 408 | - (void)textDidEndEditing:(NSNotification *)aNotification 409 | { 410 | // Give focus back to the table grid (the field editor took it) 411 | [[self window] makeFirstResponder:[self tableGrid]]; 412 | 413 | NSString *value = [[[aNotification object] string] copy]; 414 | [[self tableGrid] _setObjectValue:value forColumn:editedColumn row:editedRow]; 415 | 416 | editedColumn = NSNotFound; 417 | editedRow = NSNotFound; 418 | 419 | // End the editing session 420 | [[[self tableGrid] cell] endEditing:[[self window] fieldEditor:NO forObject:self]]; 421 | } 422 | 423 | #pragma mark - 424 | #pragma mark Protocol Methods 425 | 426 | #pragma mark NSDraggingDestination 427 | 428 | /* 429 | * These methods simply pass the drag event back to the table grid. 430 | * They are only required for autoscrolling. 431 | */ 432 | 433 | - (NSDragOperation)draggingEntered:(id )sender 434 | { 435 | // Setup the timer for autoscrolling 436 | // (the simply calling autoscroll: from mouseDragged: only works as long as the mouse is moving) 437 | autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(_timerAutoscrollCallback:) userInfo:nil repeats:YES]; 438 | 439 | return [[self tableGrid] draggingEntered:sender]; 440 | } 441 | 442 | - (NSDragOperation)draggingUpdated:(id )sender 443 | { 444 | return [[self tableGrid] draggingUpdated:sender]; 445 | } 446 | 447 | - (void)draggingExited:(id )sender 448 | { 449 | [autoscrollTimer invalidate]; 450 | autoscrollTimer = nil; 451 | 452 | [[self tableGrid] draggingExited:sender]; 453 | } 454 | 455 | - (void)draggingEnded:(id )sender 456 | { 457 | [[self tableGrid] draggingEnded:sender]; 458 | } 459 | 460 | - (BOOL)prepareForDragOperation:(id )sender 461 | { 462 | return [[self tableGrid] prepareForDragOperation:sender]; 463 | } 464 | 465 | - (BOOL)performDragOperation:(id )sender 466 | { 467 | return [[self tableGrid] performDragOperation:sender]; 468 | } 469 | 470 | - (void)concludeDragOperation:(id )sender 471 | { 472 | [[self tableGrid] concludeDragOperation:sender]; 473 | } 474 | 475 | #pragma mark - 476 | #pragma mark Subclass Methods 477 | 478 | - (MBTableGrid *)tableGrid 479 | { 480 | return (MBTableGrid *)[[self enclosingScrollView] superview]; 481 | } 482 | 483 | - (void)editSelectedCell:(id)sender 484 | { 485 | // Get the top-left selection 486 | editedColumn = [[self tableGrid].selectedColumnIndexes firstIndex]; 487 | editedRow = [[self tableGrid].selectedRowIndexes firstIndex]; 488 | 489 | // Check if the cell can be edited 490 | if(![[self tableGrid] _canEditCellAtColumn:editedColumn row:editedRow]) { 491 | editedColumn = NSNotFound; 492 | editedRow = NSNotFound; 493 | return; 494 | } 495 | 496 | // Select it and only it 497 | if([[self tableGrid].selectedColumnIndexes count] > 1) { 498 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndex:editedColumn]; 499 | } 500 | if([[self tableGrid].selectedRowIndexes count] > 1) { 501 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndex:editedRow]; 502 | } 503 | 504 | NSTextFieldCell *cell = [[self tableGrid] cell]; 505 | [cell setEditable:YES]; 506 | [cell setSelectable:YES]; 507 | [cell setObjectValue:[[self tableGrid] _objectValueForColumn:editedColumn row:editedRow]]; 508 | 509 | NSRect cellFrame = [self frameOfCellAtColumn:editedColumn row:editedRow]; 510 | NSText *editor = [[self window] fieldEditor:YES forObject:self]; 511 | [cell editWithFrame:cellFrame inView:self editor:editor delegate:self event:nil]; 512 | } 513 | 514 | #pragma mark Layout Support 515 | 516 | - (NSRect)rectOfColumn:(NSUInteger)columnIndex 517 | { 518 | 519 | float width = [[self tableGrid] _widthForColumn:columnIndex]; 520 | 521 | NSRect rect = NSMakeRect(0, 0, width, [self frame].size.height); 522 | //rect.origin.x += 60.0 * columnIndex; 523 | 524 | NSUInteger i = 0; 525 | while(i < columnIndex) { 526 | float headerWidth = [[self tableGrid] _widthForColumn:i]; 527 | rect.origin.x += headerWidth; 528 | i++; 529 | } 530 | 531 | return rect; 532 | } 533 | 534 | - (NSRect)rectOfRow:(NSUInteger)rowIndex 535 | { 536 | 537 | float heightForRow = 20.0; 538 | NSRect rect = NSMakeRect(0, 0, [self frame].size.width, heightForRow); 539 | 540 | rect.origin.y += 20.0 * rowIndex; 541 | 542 | /*NSUInteger i = 0; 543 | while(i < rowIndex) { 544 | float rowHeight = rect.size.height; 545 | rect.origin.y += rowHeight; 546 | i++; 547 | }*/ 548 | 549 | return rect; 550 | } 551 | 552 | - (NSRect)frameOfCellAtColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 553 | { 554 | NSRect columnRect = [self rectOfColumn:columnIndex]; 555 | NSRect rowRect = [self rectOfRow:rowIndex]; 556 | return NSMakeRect(columnRect.origin.x, rowRect.origin.y, columnRect.size.width, rowRect.size.height); 557 | } 558 | 559 | - (NSInteger)columnAtPoint:(NSPoint)aPoint 560 | { 561 | NSInteger column = 0; 562 | while(column < [self tableGrid].numberOfColumns) { 563 | NSRect columnFrame = [self rectOfColumn:column]; 564 | if(NSPointInRect(aPoint, columnFrame)) { 565 | return column; 566 | } 567 | column++; 568 | } 569 | return NSNotFound; 570 | } 571 | 572 | - (NSInteger)rowAtPoint:(NSPoint)aPoint 573 | { 574 | NSInteger row = 0; 575 | while(row < [self tableGrid].numberOfRows) { 576 | NSRect rowFrame = [self rectOfRow:row]; 577 | if(NSPointInRect(aPoint, rowFrame)) { 578 | return row; 579 | } 580 | row++; 581 | } 582 | return NSNotFound; 583 | } 584 | 585 | @end 586 | 587 | @implementation MBTableGridContentView (Cursors) 588 | 589 | - (NSCursor *)_cellSelectionCursor 590 | { 591 | NSCursor *cursor = [[NSCursor alloc] initWithImage:cursorImage hotSpot:NSMakePoint(8, 8)]; 592 | return cursor; 593 | } 594 | 595 | /** 596 | * @warning This method is not as efficient as it could be, but 597 | * it should only be called once, at initialization. 598 | * TODO: Make it faster 599 | */ 600 | - (NSImage *)_cellSelectionCursorImage 601 | { 602 | NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(20, 20)]; 603 | [image setFlipped:YES]; 604 | [image lockFocus]; 605 | 606 | NSRect horizontalInner = NSMakeRect(7.0, 2.0, 2.0, 12.0); 607 | NSRect verticalInner = NSMakeRect(2.0, 7.0, 12.0, 2.0); 608 | 609 | NSRect horizontalOuter = NSInsetRect(horizontalInner, -1.0, -1.0); 610 | NSRect verticalOuter = NSInsetRect(verticalInner, -1.0, -1.0); 611 | 612 | // Set the shadow 613 | NSShadow *shadow = [[NSShadow alloc] init]; 614 | [shadow setShadowColor:[NSColor colorWithDeviceWhite:0.0 alpha:0.8]]; 615 | [shadow setShadowBlurRadius:2.0]; 616 | [shadow setShadowOffset:NSMakeSize(0, -1.0)]; 617 | 618 | [[NSGraphicsContext currentContext] saveGraphicsState]; 619 | 620 | [shadow set]; 621 | 622 | [[NSColor blackColor] set]; 623 | NSRectFill(horizontalOuter); 624 | NSRectFill(verticalOuter); 625 | 626 | [[NSGraphicsContext currentContext] restoreGraphicsState]; 627 | 628 | // Fill them again to compensate for the shadows 629 | NSRectFill(horizontalOuter); 630 | NSRectFill(verticalOuter); 631 | 632 | [[NSColor whiteColor] set]; 633 | NSRectFill(horizontalInner); 634 | NSRectFill(verticalInner); 635 | 636 | [image unlockFocus]; 637 | 638 | return image; 639 | } 640 | 641 | - (NSCursor *)_cellExtendSelectionCursor 642 | { 643 | NSCursor *cursor = [[NSCursor alloc] initWithImage:cursorExtendSelectionImage hotSpot:NSMakePoint(8, 8)]; 644 | return cursor; 645 | } 646 | 647 | /** 648 | * @warning This method is not as efficient as it could be, but 649 | * it should only be called once, at initialization. 650 | * TODO: Make it faster 651 | */ 652 | - (NSImage *)_cellExtendSelectionCursorImage 653 | { 654 | NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(20, 20)]; 655 | [image setFlipped:YES]; 656 | [image lockFocus]; 657 | 658 | NSRect horizontalInner = NSMakeRect(7.0, 2.0, 2.0, 12.0); 659 | NSRect verticalInner = NSMakeRect(2.0, 7.0, 12.0, 2.0); 660 | 661 | NSRect horizontalOuter = NSInsetRect(horizontalInner, -1.0, -1.0); 662 | NSRect verticalOuter = NSInsetRect(verticalInner, -1.0, -1.0); 663 | 664 | // Set the shadow 665 | NSShadow *shadow = [[NSShadow alloc] init]; 666 | [shadow setShadowColor:[NSColor colorWithDeviceWhite:0.0 alpha:0.8]]; 667 | [shadow setShadowBlurRadius:2.0]; 668 | [shadow setShadowOffset:NSMakeSize(0, -1.0)]; 669 | 670 | [[NSGraphicsContext currentContext] saveGraphicsState]; 671 | 672 | [shadow set]; 673 | 674 | [[NSColor blackColor] set]; 675 | NSRectFill(horizontalOuter); 676 | NSRectFill(verticalOuter); 677 | 678 | [[NSGraphicsContext currentContext] restoreGraphicsState]; 679 | 680 | // Fill them again to compensate for the shadows 681 | NSRectFill(horizontalOuter); 682 | NSRectFill(verticalOuter); 683 | 684 | [[NSColor blackColor] set]; 685 | NSRectFill(horizontalInner); 686 | NSRectFill(verticalInner); 687 | 688 | [image unlockFocus]; 689 | 690 | return image; 691 | } 692 | 693 | @end 694 | 695 | @implementation MBTableGridContentView (DragAndDrop) 696 | 697 | - (void)_setDraggingColumnOrRow:(BOOL)flag 698 | { 699 | isDraggingColumnOrRow = flag; 700 | } 701 | 702 | - (void)_setDropColumn:(NSInteger)columnIndex 703 | { 704 | dropColumn = columnIndex; 705 | [self setNeedsDisplay:YES]; 706 | } 707 | 708 | - (void)_setDropRow:(NSInteger)rowIndex 709 | { 710 | dropRow = rowIndex; 711 | [self setNeedsDisplay:YES]; 712 | } 713 | 714 | - (void)_timerAutoscrollCallback:(NSTimer *)aTimer 715 | { 716 | NSEvent* event = [NSApp currentEvent]; 717 | if ([event type] == NSLeftMouseDragged ) 718 | [self autoscroll:event]; 719 | } 720 | 721 | @end 722 | -------------------------------------------------------------------------------- /MBTableGridController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import 27 | #import 28 | 29 | @interface MBTableGridController : NSObject { 30 | IBOutlet MBTableGrid *tableGrid; 31 | 32 | NSMutableArray *columns; 33 | NSArray *columnSampleWidths; 34 | } 35 | 36 | - (IBAction)addColumn:(id)sender; 37 | - (IBAction)addRow:(id)sender; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /MBTableGridController.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import "MBTableGridController.h" 27 | 28 | @interface NSMutableArray (SwappingAdditions) 29 | - (void)moveObjectsAtIndexes:(NSIndexSet *)indexes toIndex:(NSUInteger)index; 30 | @end 31 | 32 | @implementation MBTableGridController 33 | 34 | - (void)awakeFromNib 35 | { 36 | 37 | 38 | columnSampleWidths = @[@40, @50, @60, @70, @80, @90, @100, @110, @120, @130]; 39 | 40 | columns = [[NSMutableArray alloc] initWithCapacity:500]; 41 | 42 | // Add 10 columns 43 | int i = 0; 44 | while (i < 10) { 45 | [self addColumn:self]; 46 | i++; 47 | } 48 | 49 | // Add 100 rows 50 | int j = 0; 51 | while (j < 100) { 52 | [self addRow:self]; 53 | j++; 54 | } 55 | 56 | [tableGrid reloadData]; 57 | 58 | // Register to receive text strings 59 | [tableGrid registerForDraggedTypes:@[NSStringPboardType]]; 60 | } 61 | 62 | 63 | -(NSString *) genRandStringLength: (int) len 64 | { 65 | 66 | // Create alphanumeric table 67 | NSString *letters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 68 | 69 | // Create mutable string 70 | NSMutableString *randomString = [NSMutableString stringWithCapacity: len]; 71 | 72 | // Add random character to string 73 | for (int i=0; i 0) { 92 | return [columns[0] count]; 93 | } 94 | return 0; 95 | } 96 | 97 | 98 | - (NSUInteger)numberOfColumnsInTableGrid:(MBTableGrid *)aTableGrid 99 | { 100 | return [columns count]; 101 | } 102 | 103 | - (id)tableGrid:(MBTableGrid *)aTableGrid objectValueForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 104 | { 105 | if (columnIndex >= [columns count]) { 106 | return nil; 107 | } 108 | 109 | NSMutableArray *column = columns[columnIndex]; 110 | 111 | if (rowIndex >= [column count]) { 112 | return nil; 113 | } 114 | 115 | id value = column[rowIndex]; 116 | 117 | return value; 118 | } 119 | 120 | - (void)tableGrid:(MBTableGrid *)aTableGrid setObjectValue:(id)anObject forColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 121 | { 122 | if (columnIndex >= [columns count]) { 123 | return; 124 | } 125 | 126 | NSMutableArray *column = columns[columnIndex]; 127 | 128 | if (rowIndex >= [column count]) { 129 | return; 130 | } 131 | 132 | if (anObject == nil) { 133 | anObject = @""; 134 | } 135 | 136 | column[rowIndex] = anObject; 137 | } 138 | 139 | - (float)tableGrid:(MBTableGrid *)aTableGrid setWidthForColumn:(NSUInteger)columnIndex 140 | { 141 | 142 | return (columnIndex < columnSampleWidths.count) ? [columnSampleWidths[columnIndex] floatValue] : 60; 143 | 144 | } 145 | 146 | -(NSColor *)tableGrid:(MBTableGrid *)aTableGrid backgroundColorForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 147 | { 148 | if (rowIndex % 2 && columnIndex % 2) 149 | return [NSColor colorWithDeviceRed:1.0f green:0.5f blue:0.5f alpha:1.0f]; 150 | else 151 | return nil; 152 | } 153 | 154 | #pragma mark Dragging 155 | 156 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid writeColumnsWithIndexes:(NSIndexSet *)columnIndexes toPasteboard:(NSPasteboard *)pboard 157 | { 158 | return YES; 159 | } 160 | 161 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid canMoveColumns:(NSIndexSet *)columnIndexes toIndex:(NSUInteger)index 162 | { 163 | // Allow any column movement 164 | return YES; 165 | } 166 | 167 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid moveColumns:(NSIndexSet *)columnIndexes toIndex:(NSUInteger)index 168 | { 169 | [columns moveObjectsAtIndexes:columnIndexes toIndex:index]; 170 | return YES; 171 | } 172 | 173 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard 174 | { 175 | return YES; 176 | } 177 | 178 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid canMoveRows:(NSIndexSet *)rowIndexes toIndex:(NSUInteger)index 179 | { 180 | // Allow any row movement 181 | return YES; 182 | } 183 | 184 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid moveRows:(NSIndexSet *)rowIndexes toIndex:(NSUInteger)index 185 | { 186 | for (NSMutableArray *column in columns) { 187 | [column moveObjectsAtIndexes:rowIndexes toIndex:index]; 188 | } 189 | return YES; 190 | } 191 | 192 | - (NSDragOperation)tableGrid:(MBTableGrid *)aTableGrid validateDrop:(id )info proposedColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 193 | { 194 | return NSDragOperationCopy; 195 | } 196 | 197 | - (BOOL)tableGrid:(MBTableGrid *)aTableGrid acceptDrop:(id )info column:(NSUInteger)columnIndex row:(NSUInteger)rowIndex 198 | { 199 | NSPasteboard *pboard = [info draggingPasteboard]; 200 | 201 | NSString *value = [pboard stringForType:NSStringPboardType]; 202 | [self tableGrid:aTableGrid setObjectValue:value forColumn:columnIndex row:rowIndex]; 203 | 204 | return YES; 205 | } 206 | 207 | #pragma mark MBTableGridDelegate 208 | 209 | - (void)tableGridDidMoveRows:(NSNotification *)aNotification 210 | { 211 | NSLog(@"moved"); 212 | } 213 | 214 | #pragma mark - 215 | #pragma mark Subclass Methods 216 | 217 | - (IBAction)addColumn:(id)sender 218 | { 219 | NSMutableArray *column = [[NSMutableArray alloc] init]; 220 | 221 | // Default number of rows 222 | NSUInteger numberOfRows = 0; 223 | 224 | // If there are already other columns, get the number of rows from one of them 225 | if ([columns count] > 0) { 226 | numberOfRows = [(NSMutableArray *)columns[0] count]; 227 | } 228 | 229 | NSUInteger row = 0; 230 | while (row < numberOfRows) { 231 | // Insert blank items for each row 232 | [column addObject:@""]; 233 | 234 | row++; 235 | } 236 | 237 | [columns addObject:column]; 238 | 239 | [tableGrid reloadData]; 240 | } 241 | 242 | - (IBAction)addRow:(id)sender 243 | { 244 | for (NSMutableArray *column in columns) { 245 | // Add a blank item to each row 246 | [column addObject:@""]; 247 | } 248 | 249 | [tableGrid reloadData]; 250 | } 251 | 252 | @end 253 | 254 | @implementation NSMutableArray (SwappingAdditions) 255 | 256 | - (void)moveObjectsAtIndexes:(NSIndexSet *)indexes toIndex:(NSUInteger)index 257 | { 258 | NSArray *objects = [self objectsAtIndexes:indexes]; 259 | 260 | // Determine the new indexes for the objects 261 | NSRange newRange = NSMakeRange(index, [indexes count]); 262 | if (index > [indexes firstIndex]) { 263 | newRange.location -= [indexes count]; 264 | } 265 | NSIndexSet *newIndexes = [NSIndexSet indexSetWithIndexesInRange:newRange]; 266 | 267 | // Determine where the original objects are 268 | NSIndexSet *originalIndexes = indexes; 269 | 270 | // Remove the objects from their original locations 271 | [self removeObjectsAtIndexes:originalIndexes]; 272 | 273 | // Insert the objects at their new location 274 | [self insertObjects:objects atIndexes:newIndexes]; 275 | 276 | } 277 | 278 | @end 279 | -------------------------------------------------------------------------------- /MBTableGridHeaderCell.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import 27 | 28 | typedef enum _MBTableGridHeaderOrientation { 29 | MBTableHeaderHorizontalOrientation = 0, 30 | MBTableHeaderVerticalOrientation = 1, 31 | MBTableHeaderCornerOrientation = 2 32 | } MBTableGridHeaderOrientation; 33 | 34 | /** 35 | * @brief \c MBTableGridHeaderCell is solely 36 | * responsible for drawing column and 37 | * row headers. 38 | */ 39 | @interface MBTableGridHeaderCell : NSCell { 40 | MBTableGridHeaderOrientation orientation; 41 | } 42 | 43 | /** 44 | * @brief The orientation of the header. 45 | * @details Use this property to change the 46 | * cell's appearance to match its 47 | * orientation. 48 | */ 49 | @property(assign) MBTableGridHeaderOrientation orientation; 50 | 51 | @property (nonatomic, strong) NSTrackingArea *resizeTrackingArea; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /MBTableGridHeaderCell.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import "MBTableGridHeaderCell.h" 27 | 28 | @implementation MBTableGridHeaderCell 29 | 30 | @synthesize orientation; 31 | 32 | - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView 33 | { 34 | 35 | 36 | NSColor *topColor = [NSColor colorWithDeviceWhite:0.95 alpha:1.0]; 37 | NSColor *sideColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.4]; 38 | NSColor *borderColor = [NSColor colorWithDeviceWhite:0.65 alpha:1.0]; 39 | 40 | if(self.orientation == MBTableHeaderHorizontalOrientation) { 41 | // Draw the side bevels 42 | NSRect sideLine = NSMakeRect(NSMinX(cellFrame), NSMinY(cellFrame), 1.0, NSHeight(cellFrame)); 43 | [sideColor set]; 44 | [[NSBezierPath bezierPathWithRect:sideLine] fill]; 45 | sideLine.origin.x = NSMaxX(cellFrame)-2.0; 46 | [[NSBezierPath bezierPathWithRect:sideLine] fill]; 47 | 48 | // Draw the right border 49 | NSRect borderLine = NSMakeRect(NSMaxX(cellFrame)-1, NSMinY(cellFrame), 1.0, NSHeight(cellFrame)); 50 | [borderColor set]; 51 | NSRectFill(borderLine); 52 | 53 | // Draw the bottom border 54 | NSRect bottomLine = NSMakeRect(NSMinX(cellFrame), NSMaxY(cellFrame)-1.0, NSWidth(cellFrame), 1.0); 55 | NSRectFill(bottomLine); 56 | } else if(self.orientation == MBTableHeaderVerticalOrientation) { 57 | // Draw the top bevel line 58 | NSRect topLine = NSMakeRect(NSMinX(cellFrame), NSMinY(cellFrame), NSWidth(cellFrame), 1.0); 59 | [topColor set]; 60 | NSRectFill(topLine); 61 | 62 | // Draw the right border 63 | [borderColor set]; 64 | NSRect borderLine = NSMakeRect(NSMaxX(cellFrame)-1, NSMinY(cellFrame), 1.0, NSHeight(cellFrame)); 65 | NSRectFill(borderLine); 66 | 67 | // Draw the bottom border 68 | NSRect bottomLine = NSMakeRect(NSMinX(cellFrame), NSMaxY(cellFrame)-1.0, NSWidth(cellFrame), 1.0); 69 | NSRectFill(bottomLine); 70 | } 71 | 72 | if([self state] == NSOnState) { 73 | NSBezierPath *path = [NSBezierPath bezierPathWithRect:cellFrame]; 74 | NSColor *overlayColor = [[NSColor alternateSelectedControlColor] colorWithAlphaComponent:0.2]; 75 | [overlayColor set]; 76 | [path fill]; 77 | } 78 | 79 | // Draw the text 80 | [self drawInteriorWithFrame:cellFrame inView:controlView]; 81 | } 82 | 83 | - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView 84 | { 85 | 86 | NSFont *font = [NSFont labelFontOfSize:[NSFont labelFontSize]]; 87 | NSColor *color = [NSColor controlTextColor]; 88 | NSDictionary *attributes = @{NSFontAttributeName: font, NSForegroundColorAttributeName: color}; 89 | NSAttributedString *string = [[NSAttributedString alloc] initWithString:[self stringValue] attributes:attributes]; 90 | 91 | static CGFloat TEXT_PADDING = 6; 92 | 93 | NSRect textFrame; 94 | 95 | if (self.orientation == MBTableHeaderHorizontalOrientation) { 96 | textFrame = NSMakeRect(cellFrame.origin.x + TEXT_PADDING, cellFrame.origin.y + (cellFrame.size.height - [string size].height)/2, cellFrame.size.width - TEXT_PADDING, [string size].height); 97 | } else { 98 | textFrame = NSMakeRect(cellFrame.origin.x + (cellFrame.size.width-[string size].width)/2, cellFrame.origin.y + (cellFrame.size.height - [string size].height)/2, [string size].width, [string size].height); 99 | } 100 | 101 | [[NSGraphicsContext currentContext] saveGraphicsState]; 102 | NSShadow *textShadow = [[NSShadow alloc] init]; 103 | [textShadow setShadowOffset:NSMakeSize(0,-1)]; 104 | [textShadow setShadowBlurRadius:0.0]; 105 | [textShadow setShadowColor:[NSColor colorWithDeviceWhite:1.0 alpha:0.8]]; 106 | [textShadow set]; 107 | 108 | // [string drawInRect:textFrame]; 109 | [string drawWithRect:textFrame options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin]; 110 | 111 | [[NSGraphicsContext currentContext] restoreGraphicsState]; 112 | 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /MBTableGridHeaderView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import 27 | #import "MBTableGridHeaderCell.h" 28 | 29 | @class MBTableGrid; 30 | 31 | /** 32 | * @brief \c MBTableGridHeaderView deals with the 33 | * display and interaction with grid headers. 34 | */ 35 | @interface MBTableGridHeaderView : NSView { 36 | MBTableGridHeaderCell *headerCell; 37 | MBTableGridHeaderOrientation orientation; 38 | 39 | /* Dragging */ 40 | NSInteger mouseDownItem; 41 | NSPoint mouseDownLocation; 42 | NSPoint lastMouseDraggingLocation; 43 | BOOL shouldDragItems; 44 | BOOL isInDrag; 45 | 46 | /* Resizing */ 47 | NSMutableArray *trackingAreas; 48 | BOOL canResize; 49 | BOOL isResizing; 50 | NSUInteger draggingColumnIndex; 51 | 52 | } 53 | 54 | /** 55 | * @name The Grid View 56 | */ 57 | /** 58 | * @{ 59 | */ 60 | 61 | /** 62 | * @brief Returns the \c MBTableGrid the receiver 63 | * belongs to. 64 | */ 65 | - (MBTableGrid *)tableGrid; 66 | 67 | /** 68 | * @} 69 | */ 70 | 71 | /** 72 | * @name Display Properties 73 | */ 74 | /** 75 | * @{ 76 | */ 77 | 78 | /** 79 | * @brief The orientation of the receiver. 80 | */ 81 | @property MBTableGridHeaderOrientation orientation; 82 | 83 | /** 84 | * @} 85 | */ 86 | 87 | /** 88 | * @name Accessing Cells 89 | */ 90 | /** 91 | * @{ 92 | */ 93 | 94 | /** 95 | * @brief The receiver's cell. 96 | * 97 | * @details \c MBTableGridHeaderView uses its header cell to 98 | * draw row or column headers. 99 | * 100 | * To change the appearance of the headers, you can 101 | * use your own \c MBTableGridHeaderCell subclass. 102 | */ 103 | @property(strong) MBTableGridHeaderCell *headerCell; 104 | 105 | /** 106 | * @} 107 | */ 108 | 109 | /** 110 | * @name Layout Support 111 | */ 112 | /** 113 | * @{ 114 | */ 115 | 116 | /** 117 | * @brief Returns the rectangle containing the header tile for 118 | * the column at \c columnIndex. 119 | * @param columnIndex The index of the column containing the 120 | * header whose rectangle you want. 121 | * @return A rectangle locating the header for the column at 122 | * \c columnIndex. Returns \c NSZeroRect if \c columnIndex 123 | * lies outside the range of valid column indices for the 124 | * receiver. 125 | * @see headerRectOfRow: 126 | */ 127 | - (NSRect)headerRectOfColumn:(NSUInteger)columnIndex; 128 | 129 | /** 130 | * @brief Returns the rectangle containing the header tile for 131 | * the row at \c rowIndex. 132 | * @param rowIndex The index of the row containing the 133 | * header whose rectangle you want. 134 | * @return A rectangle locating the header for the row at 135 | * \c rowIndex. Returns \c NSZeroRect if \c rowIndex 136 | * lies outside the range of valid column indices for the 137 | * receiver. 138 | * @see headerRectOfColumn: 139 | */ 140 | - (NSRect)headerRectOfRow:(NSUInteger)rowIndex; 141 | 142 | /** 143 | * @} 144 | */ 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /MBTableGridHeaderView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008 Matthew Ball - http://www.mattballdesign.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #import "MBTableGridHeaderView.h" 27 | #import "MBTableGrid.h" 28 | #import "MBTableGridContentView.h" 29 | 30 | @interface MBTableGrid (Private) 31 | - (NSString *)_headerStringForColumn:(NSUInteger)columnIndex; 32 | - (NSString *)_headerStringForRow:(NSUInteger)rowIndex; 33 | - (MBTableGridContentView *)_contentView; 34 | - (void)_dragColumnsWithEvent:(NSEvent *)theEvent; 35 | - (void)_dragRowsWithEvent:(NSEvent *)theEvent; 36 | @end 37 | 38 | @implementation MBTableGridHeaderView 39 | 40 | @synthesize orientation; 41 | @synthesize headerCell; 42 | 43 | - (id)initWithFrame:(NSRect)frameRect 44 | { 45 | if(self = [super initWithFrame:frameRect]) { 46 | // Setup the header cell 47 | headerCell = [[MBTableGridHeaderCell alloc] init]; 48 | 49 | // We haven't clicked any item 50 | mouseDownItem = -1; 51 | 52 | // Initially, we're not dragging anything 53 | shouldDragItems = NO; 54 | isInDrag = NO; 55 | 56 | // No resize at start 57 | canResize = NO; 58 | isResizing = NO; 59 | 60 | } 61 | return self; 62 | } 63 | 64 | - (void)drawRect:(NSRect)rect 65 | { 66 | 67 | if (self.orientation == MBTableHeaderHorizontalOrientation) { 68 | 69 | // Remove all tracking areas 70 | if (trackingAreas) { 71 | 72 | for (NSTrackingArea *trackingArea in trackingAreas) { 73 | 74 | [self removeTrackingArea:trackingArea]; 75 | 76 | } 77 | 78 | } 79 | 80 | // reset tracking array 81 | trackingAreas = [NSMutableArray array]; 82 | 83 | // Draw the column headers 84 | NSUInteger numberOfColumns = [self tableGrid].numberOfColumns; 85 | [headerCell setOrientation:self.orientation]; 86 | NSUInteger column = 0; 87 | while (column < numberOfColumns) { 88 | NSRect headerRect = [self headerRectOfColumn:column]; 89 | 90 | // Only draw the header if we need to 91 | if ([self needsToDrawRect:headerRect]) { 92 | // Check if any part of the selection is in this column 93 | NSIndexSet *selectedColumns = [[self tableGrid] selectedColumnIndexes]; 94 | if ([selectedColumns containsIndex:column]) { 95 | [headerCell setState:NSOnState]; 96 | } else { 97 | [headerCell setState:NSOffState]; 98 | } 99 | 100 | [headerCell setStringValue:[[self tableGrid] _headerStringForColumn:column]]; 101 | [headerCell drawWithFrame:headerRect inView:self]; 102 | 103 | // Create new tracking area for resizing columns 104 | NSRect resizeRect = NSMakeRect(NSMinX(headerRect) + NSWidth(headerRect) - 2, NSMinY(headerRect), 5, NSHeight(headerRect)); 105 | NSTrackingArea *resizeTrackingArea = [[NSTrackingArea alloc] initWithRect:resizeRect options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:nil]; 106 | 107 | // keep track of tracking areas and add tracking to view 108 | [trackingAreas addObject:resizeTrackingArea]; 109 | [self addTrackingArea:resizeTrackingArea]; 110 | 111 | } 112 | column++; 113 | } 114 | 115 | } else if (self.orientation == MBTableHeaderVerticalOrientation) { 116 | // Draw the row headers 117 | NSUInteger numberOfRows = [self tableGrid].numberOfRows; 118 | [headerCell setOrientation:self.orientation]; 119 | NSUInteger row = 0; 120 | while(row < numberOfRows) { 121 | NSRect headerRect = [self headerRectOfRow:row]; 122 | 123 | // Only draw the header if we need to 124 | if ([self needsToDrawRect:headerRect]) { 125 | 126 | // Check if any part of the selection is in this column 127 | NSIndexSet *selectedRows = [[self tableGrid] selectedRowIndexes]; 128 | if ([selectedRows containsIndex:row]) { 129 | [headerCell setState:NSOnState]; 130 | } else { 131 | [headerCell setState:NSOffState]; 132 | } 133 | 134 | [headerCell setStringValue:[[self tableGrid] _headerStringForRow:row]]; 135 | [headerCell drawWithFrame:headerRect inView:self]; 136 | } 137 | row++; 138 | } 139 | } 140 | 141 | } 142 | 143 | - (BOOL)isFlipped 144 | { 145 | return YES; 146 | } 147 | 148 | - (void)mouseDown:(NSEvent *)theEvent 149 | { 150 | 151 | // Get the location of the click 152 | NSPoint loc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 153 | mouseDownLocation = loc; 154 | NSInteger column = [[self tableGrid] columnAtPoint:[self convertPoint:loc toView:[self tableGrid]]]; 155 | NSInteger row = [[self tableGrid] rowAtPoint:[self convertPoint:loc toView:[self tableGrid]]]; 156 | 157 | if (canResize) { 158 | 159 | // Set resize column index 160 | draggingColumnIndex = [[self tableGrid] columnAtPoint:[self convertPoint:NSMakePoint(loc.x - 3, loc.y) toView:[self tableGrid]]]; 161 | lastMouseDraggingLocation = loc; 162 | isResizing = YES; 163 | 164 | } else { 165 | 166 | // For single clicks, 167 | if([theEvent clickCount] == 1) { 168 | if(([theEvent modifierFlags] & NSShiftKeyMask) && [self tableGrid].allowsMultipleSelection) { 169 | // If the shift key was held down, extend the selection 170 | } else { 171 | // No modifier keys, so change the selection 172 | if(self.orientation == MBTableHeaderHorizontalOrientation) { 173 | mouseDownItem = column; 174 | 175 | if([[self tableGrid].selectedColumnIndexes containsIndex:column] && [[self tableGrid].selectedRowIndexes count] == [self tableGrid].numberOfRows) { 176 | // Allow the user to drag the column 177 | shouldDragItems = YES; 178 | } else { 179 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndex:column]; 180 | // Select every row 181 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,[self tableGrid].numberOfRows)]; 182 | } 183 | } else if(self.orientation == MBTableHeaderVerticalOrientation) { 184 | mouseDownItem = row; 185 | 186 | if([[self tableGrid].selectedRowIndexes containsIndex:row] && [[self tableGrid].selectedColumnIndexes count] == [self tableGrid].numberOfColumns) { 187 | // Allow the user to drag the row 188 | shouldDragItems = YES; 189 | } else { 190 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndex:row]; 191 | // Select every column 192 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,[self tableGrid].numberOfColumns)]; 193 | } 194 | } 195 | } 196 | } 197 | 198 | // Pass the event back to the MBTableGrid (Used to give First Responder status) 199 | [[self tableGrid] mouseDown:theEvent]; 200 | 201 | } 202 | } 203 | 204 | - (void)mouseDragged:(NSEvent *)theEvent 205 | { 206 | // Get the location of the mouse 207 | NSPoint loc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 208 | float deltaX = abs(loc.x - mouseDownLocation.x); 209 | float deltaY = abs(loc.y - mouseDownLocation.y); 210 | 211 | if (canResize) { 212 | 213 | // Set drag distance 214 | float dragDistance = loc.x - lastMouseDraggingLocation.x; 215 | lastMouseDraggingLocation = loc; 216 | 217 | // Resize column and resize views 218 | [self.tableGrid resizeColumnWithIndex:draggingColumnIndex withDistance:dragDistance]; 219 | 220 | } else { 221 | 222 | // Drag operation doesn't start until the mouse has moved more than 5 points 223 | float dragThreshold = 5.0; 224 | 225 | // If we've met the conditions for a drag operation, 226 | if (shouldDragItems && mouseDownItem >= 0 && (deltaX >= dragThreshold || deltaY >= dragThreshold)) { 227 | if (self.orientation == MBTableHeaderHorizontalOrientation) { 228 | [[self tableGrid] _dragColumnsWithEvent:theEvent]; 229 | } else if (self.orientation == MBTableHeaderVerticalOrientation) { 230 | [[self tableGrid] _dragRowsWithEvent:theEvent]; 231 | } 232 | 233 | // We've responded to the drag, so don't respond again during this drag session 234 | shouldDragItems = NO; 235 | 236 | // Flag that we are currently dragging items 237 | isInDrag = YES; 238 | } 239 | // Otherwise, extend the selection (if possible) 240 | else if (mouseDownItem >= 0 && !isInDrag && !shouldDragItems) { 241 | // Determine which item is under the mouse 242 | NSInteger itemUnderMouse = -1; 243 | if (self.orientation == MBTableHeaderHorizontalOrientation) { 244 | itemUnderMouse = [[self tableGrid] columnAtPoint:[self convertPoint:loc toView:[self tableGrid]]]; 245 | } else if(self.orientation == MBTableHeaderVerticalOrientation) { 246 | itemUnderMouse = [[self tableGrid] rowAtPoint:[self convertPoint:loc toView:[self tableGrid]]]; 247 | } 248 | 249 | // If there's nothing under the mouse, bail out (something went wrong) 250 | if (itemUnderMouse < 0) 251 | return; 252 | 253 | // Calculate the range of items to select 254 | NSInteger firstItemToSelect = mouseDownItem; 255 | NSInteger numberOfItemsToSelect = itemUnderMouse - mouseDownItem + 1; 256 | if(itemUnderMouse < mouseDownItem) { 257 | firstItemToSelect = itemUnderMouse; 258 | numberOfItemsToSelect = mouseDownItem - itemUnderMouse + 1; 259 | } 260 | 261 | // Set the selected items 262 | if (self.orientation == MBTableHeaderHorizontalOrientation) { 263 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(firstItemToSelect, numberOfItemsToSelect)]; 264 | } else if (self.orientation == MBTableHeaderVerticalOrientation) { 265 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(firstItemToSelect, numberOfItemsToSelect)]; 266 | } 267 | } 268 | 269 | } 270 | } 271 | 272 | - (void)mouseUp:(NSEvent *)theEvent 273 | { 274 | 275 | if (canResize) { 276 | 277 | isResizing = NO; 278 | 279 | } else { 280 | 281 | // If we only clicked on a header that was part of a bigger selection, select it 282 | if(shouldDragItems && !isInDrag) { 283 | if (self.orientation == MBTableHeaderHorizontalOrientation) { 284 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndex:mouseDownItem]; 285 | // Select every row 286 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,[self tableGrid].numberOfRows)]; 287 | } else if (self.orientation == MBTableHeaderVerticalOrientation) { 288 | [self tableGrid].selectedRowIndexes = [NSIndexSet indexSetWithIndex:mouseDownItem]; 289 | // Select every column 290 | [self tableGrid].selectedColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,[self tableGrid].numberOfColumns)]; 291 | } 292 | } 293 | // Reset the pressed item 294 | mouseDownItem = -1; 295 | 296 | // In case it didn't already happen, reset the drag flags 297 | shouldDragItems = NO; 298 | isInDrag = NO; 299 | 300 | // Reset the location 301 | mouseDownLocation = NSZeroPoint; 302 | 303 | } 304 | } 305 | 306 | - (void)mouseEntered:(NSEvent *)theEvent 307 | { 308 | 309 | // Change to resize cursor 310 | [[NSCursor resizeLeftRightCursor] set]; 311 | canResize = YES; 312 | 313 | } 314 | 315 | - (void)mouseExited:(NSEvent *)theEvent 316 | { 317 | 318 | 319 | if (!isResizing) { 320 | 321 | // Revert to normal cursor 322 | [[NSCursor arrowCursor] set]; 323 | canResize = NO; 324 | 325 | } 326 | 327 | } 328 | 329 | 330 | #pragma mark - 331 | #pragma mark Subclass Methods 332 | 333 | - (MBTableGrid *)tableGrid 334 | { 335 | return (MBTableGrid *)[[self enclosingScrollView] superview]; 336 | } 337 | 338 | #pragma mark Layout Support 339 | 340 | - (NSRect)headerRectOfColumn:(NSUInteger)columnIndex 341 | { 342 | NSRect rect = [[[self tableGrid] _contentView] rectOfColumn:columnIndex]; 343 | rect.size.height = MBTableGridColumnHeaderHeight; 344 | 345 | return rect; 346 | } 347 | 348 | - (NSRect)headerRectOfRow:(NSUInteger)rowIndex 349 | { 350 | NSRect rect = [[[self tableGrid] _contentView] rectOfRow:rowIndex]; 351 | rect.size.width = MBTableGridRowHeaderWidth; 352 | 353 | return rect; 354 | } 355 | 356 | @end 357 | -------------------------------------------------------------------------------- /MBTableGrid_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'MBTableGrid' target in the 'MBTableGrid' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MBTableGrid 2 | =========== 3 | 4 | An NSControl subclass which provides a spreadsheet-style table grid, forked from mattball/mbtablegrid. 5 | 6 | ##Current abilities: 7 | * Display very large numbers of rows/columns with good performance 8 | * Custom background colour per cell 9 | * Display grab handle in corner of selection 10 | * Extend selection with shift key 11 | * Autoscroll 12 | * Drag to re-arrange rows or columns 13 | 14 | ##Working on: 15 | * Resizable columns and rows 16 | * Delegate events when drag handle is used 17 | * Locking columns/rows/cells 18 | * and more... 19 | 20 | ##Screenshots: 21 | ![alt tag](https://raw.github.com/mikecsh/mbtablegrid/master/MBTableGrid%20Screenshot.png) 22 | 23 | 24 | Contributions very welcome. 25 | 26 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MBTableGrid 4 | // 5 | // Created by Matt Ball on 3/13/08. 6 | // Copyright __MyCompanyName__ 2008. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | --------------------------------------------------------------------------------