From 84f131808d5caa62777c3626017697b5d832ff42 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Thu, 29 Oct 2009 00:53:13 -0700 Subject: [PATCH 1/4] Removed some remaining cell "influences" in nib2cib'ed cib. There are references to NSCells in the object graph which should be removed, their children promoted to their parent's children. Reviewed by me. --- Tools/nib2cib/NSCell.j | 1 + Tools/nib2cib/NSIBObjectData.j | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/Tools/nib2cib/NSCell.j b/Tools/nib2cib/NSCell.j index a25a3f25f..604afeadf 100644 --- a/Tools/nib2cib/NSCell.j +++ b/Tools/nib2cib/NSCell.j @@ -27,6 +27,7 @@ @import "NSFont.j" + @implementation NSCell : CPObject { int _state @accessors(readonly, getter=state); diff --git a/Tools/nib2cib/NSIBObjectData.j b/Tools/nib2cib/NSIBObjectData.j index 6c7214bbb..e19f4ce78 100644 --- a/Tools/nib2cib/NSIBObjectData.j +++ b/Tools/nib2cib/NSIBObjectData.j @@ -58,6 +58,8 @@ _objectsKeys = [aCoder decodeObjectForKey:@"NSObjectsKeys"]; _objectsValues = [aCoder decodeObjectForKey:@"NSObjectsValues"]; + [self removeCellsFromObjectGraph]; + //_oidKeys = [aCoder decodeObjectForKey:@"NSOidsKeys"]; //_oidValues = [aCoder decodeObjectForKey:@"NSOidsValues"]; @@ -68,6 +70,68 @@ return self; } +- (void)removeCellsFromObjectGraph +{ + // FIXME: Remove from top level objects and connections? + + // Most cell references should be naturally removed by the fact that we don't manually + // encode them anywhere, however, they remain in our object graph. For each cell found, + // take its children and promote them to our parent object's children. + var count = _objectsKeys.length, + parentForCellUIDs = { }, + promotedChildrenForCellUIDs = { }; + + while (count--) + { + var child = _objectsKeys[count]; + + if (!child) + continue; + + var parent = _objectsValues[count]; + + // If this object is a cell, remember it's parent. + if ([child isKindOfClass:[NSCell class]]) + { + parentForCellUIDs[[child UID]] = parent; + continue; + } + + // If parent also isn't a cell, we don't care about it. + if (![parent isKindOfClass:[NSCell class]]) + continue; + + // Remember this child for later promotion. + var parentUID = [parent UID], + children = promotedChildrenForCellUIDs[parentUID]; + + if (!children) + { + children = []; + promotedChildrenForCellUIDs[parentUID] = children; + } + + children.push(child); + + _objectsKeys.splice(count, 1); + _objectsValues.splice(count, 1); + } + + for (var cellUID in promotedChildrenForCellUIDs) + if (promotedChildrenForCellUIDs.hasOwnProperty(cellUID)) + { + var children = promotedChildrenForCellUIDs[cellUID], + parent = parentForCellUIDs[cellUID]; + + children.forEach(function(aChild) + { + CPLog.warn("Promoted " + aChild + " to child of " + parent); + _objectsKeys.push(aChild); + _objectsValues.push(parent); + }); + } +} + @end @implementation NSIBObjectData : _CPCibObjectData From 499d2d93d569fe05396ce6e09238a249d744f831 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Thu, 29 Oct 2009 16:25:05 -0700 Subject: [PATCH 2/4] Added import just in case. Reviewed by me. --- Tools/nib2cib/NSIBObjectData.j | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tools/nib2cib/NSIBObjectData.j b/Tools/nib2cib/NSIBObjectData.j index e19f4ce78..26d2e5481 100644 --- a/Tools/nib2cib/NSIBObjectData.j +++ b/Tools/nib2cib/NSIBObjectData.j @@ -22,6 +22,8 @@ @import +@import "NSCell.j" + @implementation _CPCibObjectData (NSCoding) From bcd8c72ae4c1407d6aacefca867670d77338575e Mon Sep 17 00:00:00 2001 From: Ross Boucher Date: Fri, 30 Oct 2009 10:53:47 -0700 Subject: [PATCH 3/4] Add an unsaved state affordance to the close button on standard windows. --- AppKit/CPWindow/CPWindow.j | 2 ++ AppKit/CPWindow/_CPStandardWindowView.j | 45 +++++++++++++++++++------ AppKit/CPWindow/_CPWindowView.j | 4 +++ AppKit/CPWindowController.j | 21 ++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/AppKit/CPWindow/CPWindow.j b/AppKit/CPWindow/CPWindow.j index 59c0c3501..32509b2d3 100644 --- a/AppKit/CPWindow/CPWindow.j +++ b/AppKit/CPWindow/CPWindow.j @@ -1510,6 +1510,8 @@ CPTexturedBackgroundWindowMask _isDocumentEdited = isDocumentEdited; [CPMenu _setMenuBarIconImageAlphaValue:_isDocumentEdited ? 0.5 : 1.0]; + + [_windowView setDocumentEdited:isDocumentEdited]; } /*! diff --git a/AppKit/CPWindow/_CPStandardWindowView.j b/AppKit/CPWindow/_CPStandardWindowView.j index 5b100a2cc..8ea33a72e 100644 --- a/AppKit/CPWindow/_CPStandardWindowView.j +++ b/AppKit/CPWindow/_CPStandardWindowView.j @@ -94,13 +94,15 @@ var _CPTexturedWindowHeadGradientColor = nil, @end -var _CPStandardWindowViewBodyBackgroundColor = nil, - _CPStandardWindowViewDividerBackgroundColor = nil, - _CPStandardWindowViewTitleBackgroundColor = nil, - _CPStandardWindowViewCloseButtonImage = nil, - _CPStandardWindowViewCloseButtonHighlightedImage = nil, - _CPStandardWindowViewMinimizeButtonImage = nil, - _CPStandardWindowViewMinimizeButtonHighlightedImage = nil; +var _CPStandardWindowViewBodyBackgroundColor = nil, + _CPStandardWindowViewDividerBackgroundColor = nil, + _CPStandardWindowViewTitleBackgroundColor = nil, + _CPStandardWindowViewCloseButtonImage = nil, + _CPStandardWindowViewCloseButtonHighlightedImage = nil, + _CPStandardWindowViewCloseButtonUnsavedImage = nil, + _CPStandardWindowViewCloseButtonUnsavedHighlightedImage = nil, + _CPStandardWindowViewMinimizeButtonImage = nil, + _CPStandardWindowViewMinimizeButtonHighlightedImage = nil; var STANDARD_GRADIENT_HEIGHT = 41.0; STANDARD_TITLEBAR_HEIGHT = 25.0; @@ -115,6 +117,8 @@ var STANDARD_GRADIENT_HEIGHT = 41.0; CPTextField _titleField; CPButton _closeButton; CPButton _minimizeButton; + + BOOL _isDocumentEdited; } + (CPColor)bodyBackgroundColor @@ -262,14 +266,14 @@ var STANDARD_GRADIENT_HEIGHT = 41.0; _CPStandardWindowViewCloseButtonImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPWindow/Standard/CPWindowStandardCloseButton.png"] size:CGSizeMake(16.0, 16.0)]; _CPStandardWindowViewCloseButtonHighlightedImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPWindow/Standard/CPWindowStandardCloseButtonHighlighted.png"] size:CGSizeMake(16.0, 16.0)]; + _CPStandardWindowViewCloseButtonUnsavedImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPWindow/Standard/CPWindowStandardCloseButtonUnsaved.png"] size:CGSizeMake(16.0, 16.0)]; + _CPStandardWindowViewCloseButtonUnsavedHighlightedImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPWindow/Standard/CPWindowStandardCloseButtonUnsavedHighlighted.png"] size:CGSizeMake(16.0, 16.0)]; } _closeButton = [[CPButton alloc] initWithFrame:CGRectMake(8.0, 7.0, 16.0, 16.0)]; [_closeButton setBordered:NO]; - - [_closeButton setImage:_CPStandardWindowViewCloseButtonImage]; - [_closeButton setAlternateImage:_CPStandardWindowViewCloseButtonHighlightedImage]; + [self _updateCloseButton]; [self addSubview:_closeButton]; } @@ -367,6 +371,27 @@ var STANDARD_GRADIENT_HEIGHT = 41.0; } } */ + +- (void)_updateCloseButton +{ + if (_isDocumentEdited) + { + [_closeButton setImage:_CPStandardWindowViewCloseButtonUnsavedImage]; + [_closeButton setAlternateImage:_CPStandardWindowViewCloseButtonUnsavedHighlightedImage]; + } + else + { + [_closeButton setImage:_CPStandardWindowViewCloseButtonImage]; + [_closeButton setAlternateImage:_CPStandardWindowViewCloseButtonHighlightedImage]; + } +} + +- (void)setDocumentEdited:(BOOL)isEdited +{ + _isDocumentEdited = isEdited; + [self _updateCloseButton]; +} + - (void)setTitle:(CPString)aTitle { [_titleField setStringValue:aTitle]; diff --git a/AppKit/CPWindow/_CPWindowView.j b/AppKit/CPWindow/_CPWindowView.j index 5231016f6..0f878fd9a 100644 --- a/AppKit/CPWindow/_CPWindowView.j +++ b/AppKit/CPWindow/_CPWindowView.j @@ -88,6 +88,10 @@ var _CPWindowViewResizeIndicatorImage = nil; return self; } +- (void)setDocumentEdited:(BOOL)isEdited +{ +} + - (void)setTitle:(CPString)aTitle { } diff --git a/AppKit/CPWindowController.j b/AppKit/CPWindowController.j index 5909e2d63..aaaf14291 100644 --- a/AppKit/CPWindowController.j +++ b/AppKit/CPWindowController.j @@ -399,6 +399,27 @@ */ - (void)setDocumentEdited:(BOOL)isEdited { + // we ignore this. in a multi-doc world, we base this flag on the logical OR + // of all the open documents edited state. so this is just a hint to re-check that state. + //[[self window] setDocumentEdited:isEdited]; + [self _synchronizeDocumentEditedState]; +} + +- (void)_synchronizeDocumentEditedState +{ + var docs = [self documents], + count = [docs count], + isEdited = NO; + + while (count--) + { + if ([docs[count] isDocumentEdited]) + { + isEdited = YES; + break; + } + } + [[self window] setDocumentEdited:isEdited]; } From 14ad8f6d6db356cc416e94706c32fcf89a810ff3 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Sat, 31 Oct 2009 18:32:58 -0700 Subject: [PATCH 4/4] Cleaned up key equivalent handling and added support for key equivalents to nib2cib. Reviewed by me. --- AppKit/CPApplication.j | 7 ++--- AppKit/CPCompatibility.j | 6 ++-- AppKit/CPEvent.j | 8 +++++ AppKit/CPMenuItem.j | 41 +++++++++++++++---------- AppKit/CPView.j | 13 ++++++++ AppKit/CPWindow/CPWindow.j | 15 +++++++-- Tools/nib2cib/NSEvent.j | 62 ++++++++++++++++++++++++++++++++++++++ Tools/nib2cib/NSMenuItem.j | 6 ++-- 8 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 Tools/nib2cib/NSEvent.j diff --git a/AppKit/CPApplication.j b/AppKit/CPApplication.j index c5033ed53..cc6e44e4d 100644 --- a/AppKit/CPApplication.j +++ b/AppKit/CPApplication.j @@ -446,7 +446,7 @@ CPRunContinuesResponse = -1002; if ([_mainMenu performKeyEquivalent:anEvent]) return YES; - return NO; + return [[self keyWindow] performKeyEquivalent:anEvent]; } /*! @@ -458,10 +458,7 @@ CPRunContinuesResponse = -1002; _currentEvent = anEvent; // Check if this is a candidate for key equivalent... - if ([anEvent type] == CPKeyDown && - [anEvent modifierFlags] & (CPCommandKeyMask | CPControlKeyMask) && - [[anEvent characters] length] > 0 && - [self _handleKeyEquivalent:anEvent]) + if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent]) return; if (_eventListeners.length) diff --git a/AppKit/CPCompatibility.j b/AppKit/CPCompatibility.j index 2539b2ab1..b2636811d 100644 --- a/AppKit/CPCompatibility.j +++ b/AppKit/CPCompatibility.j @@ -179,7 +179,7 @@ function CPBrowserIsEngine(anEngine) if (USER_AGENT.indexOf("Mac") != -1) { CPPlatformActionKeyMask = CPCommandKeyMask; - + CPUndoKeyEquivalent = @"Z"; CPRedoKeyEquivalent = @"Z"; @@ -189,10 +189,10 @@ if (USER_AGENT.indexOf("Mac") != -1) else { CPPlatformActionKeyMask = CPControlKeyMask; - + CPUndoKeyEquivalent = @"Z"; CPRedoKeyEquivalent = @"Y"; - + CPUndoKeyEquivalentModifierMask = CPControlKeyMask; CPRedoKeyEquivalentModifierMask = CPControlKeyMask; } diff --git a/AppKit/CPEvent.j b/AppKit/CPEvent.j index eb942a79f..95993fb8a 100644 --- a/AppKit/CPEvent.j +++ b/AppKit/CPEvent.j @@ -515,6 +515,14 @@ var _CPEventPeriodicEventPeriod = 0, return _deltaZ; } +- (BOOL)_couldBeKeyEquivalent +{ + // FIXME: More cases? Space? + return _type === CPKeyDown && + _modifierFlags & (CPCommandKeyMask | CPControlKeyMask) && + [_characters length] > 0; +} + /*! Generates periodic events every \c aPeriod seconds. @param aDelay the number of seconds before the first event diff --git a/AppKit/CPMenuItem.j b/AppKit/CPMenuItem.j index 2a35aceb2..7306b8f49 100644 --- a/AppKit/CPMenuItem.j +++ b/AppKit/CPMenuItem.j @@ -744,26 +744,29 @@ CPControlKeyMask @end -var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", +var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", - CPMenuItemTitleKey = @"CPMenuItemTitleKey", - CPMenuItemTargetKey = @"CPMenuItemTargetKey", - CPMenuItemActionKey = @"CPMenuItemActionKey", + CPMenuItemTitleKey = @"CPMenuItemTitleKey", + CPMenuItemTargetKey = @"CPMenuItemTargetKey", + CPMenuItemActionKey = @"CPMenuItemActionKey", - CPMenuItemIsEnabledKey = @"CPMenuItemIsEnabledKey", - CPMenuItemIsHiddenKey = @"CPMenuItemIsHiddenKey", + CPMenuItemIsEnabledKey = @"CPMenuItemIsEnabledKey", + CPMenuItemIsHiddenKey = @"CPMenuItemIsHiddenKey", - CPMenuItemTagKey = @"CPMenuItemTagKey", - CPMenuItemStateKey = @"CPMenuItemStateKey", + CPMenuItemTagKey = @"CPMenuItemTagKey", + CPMenuItemStateKey = @"CPMenuItemStateKey", - CPMenuItemImageKey = @"CPMenuItemImageKey", - CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey", + CPMenuItemImageKey = @"CPMenuItemImageKey", + CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey", - CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey", - CPMenuItemMenuKey = @"CPMenuItemMenuKey", + CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey", + CPMenuItemMenuKey = @"CPMenuItemMenuKey", - CPMenuItemRepresentedObjectKey = @"CPMenuItemRepresentedObjectKey", - CPMenuItemViewKey = @"CPMenuItemViewKey"; + CPMenuItemKeyEquivalentKey = @"CPMenuItemKeyEquivalentKey", + CPMenuItemKeyEquivalentModifierMaskKey = @"CPMenuItemKeyEquivalentModifierMaskKey", + + CPMenuItemRepresentedObjectKey = @"CPMenuItemRepresentedObjectKey", + CPMenuItemViewKey = @"CPMenuItemViewKey"; #define DEFAULT_VALUE(aKey, aDefaultValue) [aCoder containsValueForKey:(aKey)] ? [aCoder decodeObjectForKey:(aKey)] : (aDefaultValue) #define ENCODE_IFNOT(aKey, aValue, aDefaultValue) if ((aValue) !== (aDefaultValue)) [aCoder encodeObject:(aValue) forKey:(aKey)]; @@ -804,8 +807,8 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", _submenu = DEFAULT_VALUE(CPMenuItemSubmenuKey, nil); _menu = DEFAULT_VALUE(CPMenuItemMenuKey, nil); -// CPString _keyEquivalent; -// unsigned _keyEquivalentModifierMask; + _keyEquivalent = [aCoder decodeObjectForKey:CPMenuItemKeyEquivalentKey] || @""; + _keyEquivalentModifierMask = [aCoder decodeObjectForKey:CPMenuItemKeyEquivalentModifierMaskKey] || 0; // int _mnemonicLocation; @@ -847,6 +850,12 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil); ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil); + if (_keyEquivalent && _keyEquivalent.length) + [aCoder encodeObject:_keyEquivalent forKey:CPMenuItemKeyEquivalentKey]; + + if (_keyEquivalentModifierMask) + [aCoder encodeObject:_keyEquivalentModifierMask forKey:CPMenuItemKeyEquivalentModifierMaskKey]; + ENCODE_IFNOT(CPMenuItemRepresentedObjectKey, _representedObject, nil); ENCODE_IFNOT(CPMenuItemViewKey, _view, nil); } diff --git a/AppKit/CPView.j b/AppKit/CPView.j index c0e7a5157..c53892f76 100644 --- a/AppKit/CPView.j +++ b/AppKit/CPView.j @@ -1861,6 +1861,19 @@ setBoundsOrigin: @implementation CPView (KeyView) +- (BOOL)performKeyEquivalent:(CPEvent)anEvent +{ + var subviews = [self subviews], + count = [subviews count]; + + // Is reverse iteration correct here? It matches the other (correct) code like hit testing. + while (count--) + if ([subviews[count] performKeyEquivalent:anEvent]) + return YES; + + return NO; +} + - (BOOL)canBecomeKeyView { return [self acceptsFirstResponder] && ![self isHiddenOrHasHiddenAncestor]; diff --git a/AppKit/CPWindow/CPWindow.j b/AppKit/CPWindow/CPWindow.j index 32509b2d3..fc82ea3a8 100644 --- a/AppKit/CPWindow/CPWindow.j +++ b/AppKit/CPWindow/CPWindow.j @@ -1996,10 +1996,19 @@ CPTexturedBackgroundWindowMask return NO; } -- (void)keyDown:(CPEvent)event +- (void)performKeyEquivalent:(CPEvent)anEvent { - if (![self performKeyEquivalent:event]) - [self interpretKeyEvents:[event]]; + // FIXME: should we be starting at the root, in other words _windowView? + // The evidence seems to point to no... + return [[self contentView] performKeyEquivalent:anEvent]; +} + +- (void)keyDown:(CPEvent)anEvent +{ + // It's not clear why we do performKeyEquivalent again here... + // Perhaps to allow something to happen between sendEvent: and keyDown:? + if (![anEvent _couldBeKeyEquivalent] || ![self performKeyEquivalent:anEvent]) + [self interpretKeyEvents:[anEvent]]; } - (void)insertNewline:(id)sender diff --git a/Tools/nib2cib/NSEvent.j b/Tools/nib2cib/NSEvent.j new file mode 100644 index 000000000..d0d7e53d4 --- /dev/null +++ b/Tools/nib2cib/NSEvent.j @@ -0,0 +1,62 @@ +/* + * NSEvent.j + * nib2cib + * + * Created by Francisco Tolmasky. + * Copyright 2009, 280 North, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +NSAlphaShiftKeyMask = 1 << 16; +NSShiftKeyMask = 1 << 17; +NSControlKeyMask = 1 << 18; +NSAlternateKeyMask = 1 << 19; +NSCommandKeyMask = 1 << 20; +NSNumericPadKeyMask = 1 << 21; +NSHelpKeyMask = 1 << 22; +NSFunctionKeyMask = 1 << 23; +NSDeviceIndependentModifierFlagsMask = 0xffff0000; + +function CP_NSMapKeyMask(anNSKeyMask) +{ + var keyMask = 0; + + if (anNSKeyMask & NSAlphaShiftKeyMask) + keyMask |= CPAlphaShiftKeyMask; + + if (anNSKeyMask & NSShiftKeyMask) + keyMask |= CPShiftKeyMask; + + if (anNSKeyMask & NSControlKeyMask) + keyMask |= CPControlKeyMask; + + if (anNSKeyMask & NSAlternateKeyMask) + keyMask |= CPAlternateKeyMask; + + if (anNSKeyMask & NSCommandKeyMask) + keyMask |= CPCommandKeyMask; + + if (anNSKeyMask & NSNumericPadKeyMask) + keyMask |= CPNumericPadKeyMask; + + if (anNSKeyMask & NSHelpKeyMask) + keyMask |= CPHelpKeyMask; + + if (aKeyMask & NSFunctionKeyMask) + keyMask |= CPFunctionKeyMask; + + return keyMask; +} diff --git a/Tools/nib2cib/NSMenuItem.j b/Tools/nib2cib/NSMenuItem.j index 56ed373a2..245e867bf 100644 --- a/Tools/nib2cib/NSMenuItem.j +++ b/Tools/nib2cib/NSMenuItem.j @@ -57,9 +57,9 @@ _submenu = [aCoder decodeObjectForKey:"NSSubmenu"]; _menu = [aCoder decodeObjectForKey:"NSMenu"]; -// _keyEquivalent = [aCoder decodeObjectForKey:"NSKeyEquiv"]; -// _keyEquivalentModifierMask = [aCoder decodeObjectForKey:"NSKeyEquivModMask"]; - + _keyEquivalent = [aCoder decodeObjectForKey:"NSKeyEquiv"]; + _keyEquivalentModifierMask = CP_NSMapKeyMask([aCoder decodeObjectForKey:"NSKeyEquivModMask"]); + // _mnemonicLocation = [aCoder decodeObjectForKey:"NSMnemonicLoc"]; // _isAlternate = [aCoder decodeBoolForKey:"NSIsAlternate"];