├── .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 | 
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 |
--------------------------------------------------------------------------------