Hide a child window from parent window hide all app - cocoa

I have 2 windows one is the header [mainWindow] (-setMovableByWindowBackground:YES) and the other is the content [secWindow], child of the header, the header have a button to hide the content.
[mainWindow addChildWindow:secWindow ordered:NSWindowBelow];
[mainWindow setMovableByWindowBackground:YES];
code to hide secWindow:
(IBAction) toggleSecondary: (id) sender;
{
if ([secWindow isVisible]) {
[secWindow orderOut:self];
} else {
[secWindow orderFront:self];
}
}
The problem is when a push the button, all app hide, Main and Sec windows and only need to hide the [secWindow].

Weel, I found a solution, I don't know if a correct way, but works for me. ^_^
//get the mainWindow cordinates
NSRect theFrame = [mainWindow frame];
NSPoint theOrigin = theFrame.origin;
NSPoint pSecWin = theFrame.origin;
//put secWin below mainWindow
pSecWin.y = theOrigin.y - secHeight;
(IBAction) toggleSecondary: (id) sender;
{
if ([secWindow isVisible]) {
[mainWindow removeChildWindow:secWindow];
[secWindow orderOut:self];
} else {
[secWindow setFrameOrigin:pSecWin];
[mainWindow addChildWindow:secWindow ordered:NSWindowBelow];
[secWindow orderFront:self];
}
}
so thats it, thanks anyway

Related

traffic buttons not responding in MacOS application

I am creating an application on MacOS and I am having this problem. I am using custom traffic buttons. When the window is not active if I click they do not respond, what I get is the window becoming active.
How could I fix that so the buttons will respond even if the window is not the active one.
I have them added to a custom view, here is what I have:
My View:
#implementation TrafficView
- (id)init {
if ((self = [super init])) {
trackingArea_.reset([[NSTrackingArea alloc]
initWithRect:[self bounds]
options:(NSTrackingMouseEnteredAndExited |
NSTrackingActiveAlways |
NSTrackingInVisibleRect)
owner:self
userInfo:nil]);
[self addTrackingArea:trackingArea_.get()];
}
return self;
}
- (void)dealloc {
[super dealloc];
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
return YES;
}
- (void)mouseEntered:(NSEvent*)event {
mouseInside_ = YES;
for (NSView* view in self.subviews)
[view setNeedsDisplay:YES];
}
- (void)mouseExited:(NSEvent*)event {
mouseInside_ = NO;
for (NSView* view in self.subviews)
[view setNeedsDisplay:YES];
}
- (BOOL)_mouseInGroup:(NSButton *)button {
return mouseInside_;
}
#end
Now to add the buttons I do this in the window implementation (I will post the code for only one button)
trafficView_.reset([[TrafficView alloc] init]);
[self.frameView addSubview:trafficView_];
// close button is declared as NSButton
closeButton_ =
[NSWindow standardWindowButton:NSWindowCloseButton
forStyleMask:NSTitledWindowMask];
[trafficView_ addSubview:closeButton_];
Note that my custom view (trafficView_) is added to the NSThemeFrame class, self.frameView
- (NSView*)frameView {
return [[self contentView] superview];
}
I do not why the buttons do not respond.
Could someone help on this?
Thanks in advance

Cocoa - How to distinguish between a click and scroll(continuous press of UP/DOWN key) inside a NSTableView

I want to fire a query when a particular record gets selected in an NSTableView, not when the user just scrolls down or scrolls up by continuously pressing UP/DOWN button to reach to a record.
My current implementation is
if ([notification object] == myTableView)
{
if ([myTableView selectedRow] >= 0) {
myCont = [[MyController alloc] init];
if([[detailsView subviews]count]>0)
[detailsView removeAllSubviews];
NSRect frameRect = [[scDetailsViewController view] frame];
frameRect.size.height = [detailsView frame].size.height;
frameRect.size.width = [detailsView frame].size.width;
[[myCont view] setFrame:frameRect];
[detailsView addSubview:[myCont view]];
//Firing the Query
[myCont populateDetails :[[self myList] entityAt:[myTableView selectedRow]]];
}
}
But in this way the query gets fired even if a long UP/DOWN press is done which is not intended.
Is there any way to distinguish between a click and scroll(continuous press of UP/DOWN key) inside an NSTableView just like the Mail application.
Just wrote the below piece of code in the tableViewSelectionDidChange method and that almost works perfectly.
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
if ([notification object] == myTableView)
{
NSTimeInterval delayInterval = 0.0;
NSEvent *event = [NSApp currentEvent];
if(event != nil && [event type] == NSKeyDown && [event isARepeat])
{
NSLog(#"Long press of UP and DOWn arrow key.");
delayInterval = [NSEvent keyRepeatInterval] + 0.01;
}
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(myMethod) object:nil];
[self performSelector:#selector(myMethod) withObject:nil afterDelay:delayInterval];
}
else
{
if ([[detailsView subviews] count]>0)
{
[detailsView removeAllSubviews];
}
}
}
Also written the code to fire the query in another method myMethod which is being called above in performSelector.
-(void) myMethod
{
if ([scenarioTableView selectedRow] >= 0) {
NSLog(#"Normal selection on table view row.");
scDetailsViewController = [[ScenarioDetailsViewController alloc] init];
if([[detailsView subviews]count]>0)
[detailsView removeAllSubviews];
NSRect frameRect = [[scDetailsViewController view] frame];
frameRect.size.height = [detailsView frame].size.height;
frameRect.size.width = [detailsView frame].size.width;
[[scDetailsViewController view] setFrame:frameRect];
[detailsView addSubview:[scDetailsViewController view]];
[scDetailsViewController populateScenarioDetails :[[self scenarioDetailsList] entityAt:[scenarioTableView selectedRow]]];
}
else {
if ([[detailsView subviews] count]>0)
{
[detailsView removeAllSubviews];
}
}
}
Modify your key event code something like this below and let me know if still you face any issue:-
NSInteger keyPress = [[NSApp currentEvent] type];
if (keyPress!=10)
{
}
NSLog(#"mouseclick");
}
else{
NSLog(#"keyboardClick");
}

Force a NSWindow to stay active like a NSAlert

I would like to have a progress bar in a secondary window that stays active just like a NSAlert window. I especially like how it plays a sound when the user tries to click off of it.
I figured out how to force the window to stay active by invoking:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(windowChange:)
name: NSWindowDidBecomeKeyNotification
object: nil];
and then in the notification method I do:
-(void) windowChange:(NSNotification*) notification {
NSWindow *window = [notification object];
if (window == myMainWindow) {
[myProgressWindow makeKeyAndOrderFront:nil];
}
}
This doesn't do everything I'd like. Mostly I want to stop the user from pressing anything on my main window and to keep my progress bar window active/front.
-GW
Try this one:
-(void) windowChange:(NSNotification*) notification
{
NSWindow *window = [notification object];
if (window == myMainWindow) {
[myProgressWindow makeKeyAndOrderFront:nil];
for (NSView *item in [self.myMainWindow.contentView subviews])
{
if ([item isKindOfClass:[NSTextField class]] || [item isKindOfClass:[NSButton class]])
{
[(id)item setEnabled:NO];
}
}
}
}

Scrolling NSTextView to bottom

I'm making a little server app for OS X and I'm using an NSTextView to log some info about connected clients.
Whenever I need to log something I'm appending the new message to the text of the NSTextView this way:
- (void)logMessage:(NSString *)message
{
if (message) {
self.textView.string = [self.textView.string stringByAppendingFormat:#"%#\n",message];
}
}
After this I'd like the NSTextField (or maybe I should say the NSClipView that contains it) to scroll down to show the last line of its text (obviously it should scroll only if the last line is not visible yet, in fact if then new line is the first line I log it is already on the screen so there is no need to scroll down).
How can I do that programmatically?
Found solution:
- (void)logMessage:(NSString *)message
{
if (message) {
[self appendMessage:message];
}
}
- (void)appendMessage:(NSString *)message
{
NSString *messageWithNewLine = [message stringByAppendingString:#"\n"];
// Smart Scrolling
BOOL scroll = (NSMaxY(self.textView.visibleRect) == NSMaxY(self.textView.bounds));
// Append string to textview
[self.textView.textStorage appendAttributedString:[[NSAttributedString alloc]initWithString:messageWithNewLine]];
if (scroll) // Scroll to end of the textview contents
[self.textView scrollRangeToVisible: NSMakeRange(self.textView.string.length, 0)];
}
As of OS 10.6 it's as simple as nsTextView.scrollToEndOfDocument(self).
Swift 4 + 5
let smartScroll = self.textView.visibleRect.maxY == self.textView.bounds.maxY
self.textView.textStorage?.append("new text")
if smartScroll{
self.textView.scrollToEndOfDocument(self)
}
I've been messing with this for a while, because I couldn't get it to work reliably. I've finally gotten my code working, so I'd like to post it as a reply.
My solution allows you to scroll manually, while output is being added to the view. As soon as you scroll to the absolute bottom of the NSTextView, the automatic scrolling will resume (if enabled, that is).
First a category to #import this only when needed...
FSScrollToBottomExtensions.h:
#interface NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom;
- (BOOL)isAtBottom;
- (void)scrollToBottom;
#end
FSScrollToBottomExtensions.m:
#implementation NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom
{
NSRect visRect;
NSRect boundsRect;
visRect = [self visibleRect];
boundsRect = [self bounds];
return(NSMaxY(visRect) - NSMaxY(boundsRect));
}
// Apple's suggestion did not work for me.
- (BOOL)isAtBottom
{
return([self distanceToBottom] == 0.0);
}
// The scrollToBottom method provided by Apple seems unreliable, so I wrote this one
- (void)scrollToBottom
{
NSPoint pt;
id scrollView;
id clipView;
pt.x = 0;
pt.y = 100000000000.0;
scrollView = [self enclosingScrollView];
clipView = [scrollView contentView];
pt = [clipView constrainScrollPoint:pt];
[clipView scrollToPoint:pt];
[scrollView reflectScrolledClipView:clipView];
}
#end
... create yourself an "OutputView", which is a subclass of NSTextView:
FSOutputView.h:
#interface FSOutputView : NSTextView
{
BOOL scrollToBottomPending;
}
FSOutputView.m:
#implementation FSOutputView
- (id)setup
{
...
return(self);
}
- (id)initWithCoder:(NSCoder *)aCoder
{
return([[super initWithCoder:aCoder] setup]);
}
- (id)initWithFrame:(NSRect)aFrame textContainer:(NSTextContainer *)aTextContainer
{
return([[super initWithFrame:aFrame textContainer:aTextContainer] setup]);
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
NSNotificationCenter *notificationCenter;
NSView *view;
// viewBoundsDidChange catches scrolling that happens when the caret
// moves, and scrolling caused by pressing the scrollbar arrows.
view = [self superview];
[notificationCenter addObserver:self
selector:#selector(viewBoundsDidChangeNotification:)
name:NSViewBoundsDidChangeNotification object:view];
[view setPostsBoundsChangedNotifications:YES];
// viewFrameDidChange catches scrolling that happens because text
// is inserted or deleted.
// it also catches situations, where window resizing causes changes.
[notificationCenter addObserver:self
selector:#selector(viewFrameDidChangeNotification:)
name:NSViewFrameDidChangeNotification object:self];
[self setPostsFrameChangedNotifications:YES];
}
- (void)handleScrollToBottom
{
if(scrollToBottomPending)
{
scrollToBottomPending = NO;
[self scrollToBottom];
}
}
- (void)viewBoundsDidChangeNotification:(NSNotification *)aNotification
{
[self handleScrollToBottom];
}
- (void)viewFrameDidChangeNotification:(NSNotification *)aNotification
{
[self handleScrollToBottom];
}
- (void)outputAttributedString:(NSAttributedString *)aAttributedString
flags:(int)aFlags
{
NSRange range;
BOOL wasAtBottom;
if(aAttributedString)
{
wasAtBottom = [self isAtBottom];
range = [self selectedRange];
if(aFlags & FSAppendString)
{
range = NSMakeRange([[self textStorage] length], 0);
}
if([self shouldChangeTextInRange:range
replacementString:[aAttributedString string]])
{
[[self textStorage] beginEditing];
[[self textStorage] replaceCharactersInRange:range
withAttributedString:aAttributedString];
[[self textStorage] endEditing];
}
range.location += [aAttributedString length];
range.length = 0;
if(!(aFlags & FSAppendString))
{
[self setSelectedRange:range];
}
if(wasAtBottom || (aFlags & FSForceScroll))
{
scrollToBottomPending = YES;
}
}
}
#end
... You can add a few more convenience methods to this class (I've stripped it down), so that you can output a formatted string.
- (void)outputString:(NSString *)aFormatString arguments:(va_list)aArguments attributeKey:(NSString *)aKey flags:(int)aFlags
{
NSMutableAttributedString *str;
str = [... generate attributed string from parameters ...];
[self outputAttributedString:str flags:aFlags];
}
- (void)outputLineWithFormat:(NSString *)aFormatString, ...
{
va_list args;
va_start(args, aFormatString);
[self outputString:aFormatString arguments:args attributeKey:NULL flags:FSAddNewLine];
va_end(args);
}
I have some customised NSTextView and custom input method so my option was to use:
self.scrollView.contentView.scroll(NSPoint(x: 1, y: self.textView.frame.size.height))

Cocoa nextEventMatchingMask not receiving NSMouseMoved event

I created a local event loop and showed up a borderless window (derived from NSPanel),
I found in the event loop there's no NSMouseMoved event received, although I can receive Mouse button down/up events.
What should I do to get the NSMouseMoved events? I found making the float window as key window can receive the NSMouseMoved events, but I don't want to change key window. And it appears this is possible, because I found after clicking the test App Icon in System Dock Bar, I can receive the mousemoved events, and the key window/mainwindow are unchanged.
Here's the my test code: (Create a Cocoa App project names FloatWindowTest, and put a button to link with the onClick: IBAction).
Thanks in advance!
-Jonny
#import "FloatWindowTestAppDelegate.h"
#interface FloatWindow : NSPanel
#end
#interface FloatWindowContentView : NSView
#end
#implementation FloatWindowTestAppDelegate
#synthesize window;
- (void)delayedAction:(id)sender
{
// What does this function do?
// 1. create a float window
// 2. create a local event loop
// 3. print out the events got from nextEventMatchingMask.
// 4. send it to float window.
// What is the problem?
// In local event loop, althrough the event mask has set NSMouseMovedMask
// there's no mouse moved messages received.
//
FloatWindow* floatWindow = [[FloatWindow alloc] init];
NSEvent* event = [NSApp currentEvent];
NSPoint screenOrigin = [[self window] convertBaseToScreen:[event locationInWindow]];
[floatWindow setFrameTopLeftPoint:screenOrigin];
[floatWindow orderFront:nil];
//Making the float window as Key window will work, however
//change active window is not anticipated.
//[floatWindow makeKeyAndOrderFront:nil];
BOOL done = NO;
while (!done)
{
NSAutoreleasePool* pool = [NSAutoreleasePool new];
NSUInteger eventMask = NSLeftMouseDownMask|
NSLeftMouseUpMask|
NSMouseMovedMask|
NSMouseEnteredMask|
NSMouseExitedMask|
NSLeftMouseDraggedMask;
NSEvent* event = [NSApp nextEventMatchingMask:eventMask
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
//why I cannot get NSMouseMoved event??
NSLog(#"new event %#", [event description]);
[floatWindow sendEvent:event];
[pool drain];
}
[floatWindow release];
return;
}
-(IBAction)onClick:(id)sender
{
//Tried to postpone the local event loop
//after return from button's mouse tracking loop.
//but not fixes this problem.
[[NSRunLoop currentRunLoop]
performSelector:#selector(delayedAction:)
target:self
argument:nil
order:0
modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
}
#end
#implementation FloatWindow
- (id)init
{
NSRect contentRect = NSMakeRect(200,300,200,300);
self = [super initWithContentRect:contentRect
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:YES];
if (self) {
[self setLevel:NSFloatingWindowLevel];
NSRect frameRect = [self frameRectForContentRect:contentRect];
NSView* view = [[[FloatWindowContentView alloc]
initWithFrame:frameRect] autorelease];
[self setContentView:view];
[self setAcceptsMouseMovedEvents:YES];
[self setIgnoresMouseEvents:NO];
}
return self;
}
- (BOOL)becomesKeyOnlyIfNeeded
{
return YES;
}
- (void)becomeMainWindow
{
NSLog(#"becomeMainWindow");
[super becomeMainWindow];
}
- (void)becomeKeyWindow
{
NSLog(#"becomeKeyWindow");
[super becomeKeyWindow];
}
#end
#implementation FloatWindowContentView
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
return YES;
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self) {
NSTrackingArea* area;
area = [[NSTrackingArea alloc] initWithRect:frameRect
options:NSTrackingActiveAlways|
NSTrackingMouseMoved|
NSTrackingMouseEnteredAndExited
owner:self
userInfo:nil];
[self addTrackingArea:area];
[area release];
}
return self;
}
- (void)drawRect:(NSRect)rect
{
[[NSColor redColor] set];
NSRectFill([self bounds]);
}
- (BOOL)becomeFirstResponder
{
NSLog(#"becomeFirstResponder");
return [super becomeFirstResponder];
}
#end
I think I find the right answer. This is somehow related to tell NSWindow to accept MouseMoved event. but what to my surprise is, The window should accept mouseMoved events isn't the Floating Window, but the current Key window. So the solution is simple, just enable key window to accept mouse moved events before starting tracking, and revert the switch after ending tracking.

Resources