How do I make my app expand/collapse from/to a Finder icon? - cocoa

You know how sometimes when you close a Finder window or document, it shrinks toward its representation in Finder. I want my application to be able to do this, too. Is there an API for this? I can't find any.

Executive summary: Use the NSDocument system if you want this behavior.
Details:
It looks like you're using TextEdit in your GIF. As it happens, Apple publishes the source code for TextEdit as sample code. So we can look and see if it does anything special to make this happen.
I couldn't find anything in the TextEdit source code. I poked around for a while and set some breakpoints but didn't find any evidence that TextEdit does this “manually”.
I discovered that if you open the file using File > Open (instead of double-clicking the file in the Finder), you do not get the animated closing window, even if the file is visible in the Finder.
But if you open the file using File > Open, then (without closing that window) double-click the file in the Finder, then you do get the animated closing window.
So I poked around a bit more, setting breakpoints and looking at disassembler listings, and I found what I think is the important bits, in -[NSWindow _close]. It goes basically like this:
- (void)_close {
if (!_wFlags.windowDying) { return };
if (_auxiliaryStorage->_auxWFlags.windowClosed) { return; }
void (^actuallyCloseMyself)() = ^{ ... code to actually close the window ... };
NSWindowController *controller = self.windowController;
if (![controller respondsToSelector:#selector(document)]) { goto noCloser; }
NSDocument *document = controller.document;
if (![document respondsToSelector:#selector(fileURL)]) { goto noCloser; }
QLSeamlessDocumentCloser *closer = [[NSDocumentController _seamlessDocumentCloserClass] seamlessDocumentCloserForURL:document.fileURL];
if (closer == nil) { goto noCloser; }
CGRect frame = NSRectZero;
[closer closeWindow:self contentFrame:&frame withBlock:actuallyCloseMyself];
goto done;
noCloser:
actuallyCloseMyself();
done:
_auxiliaryStorage->_auxWFlags.wantsHideOnDeactivate = YES;
}
So basically, if your window is attached to an NSWindowController, and the controller has an NSDocument, then AppKit will try to set up a “seamless close” using a QLSeamlessDocumentCloser. The QL prefix means it's part of QuickLook (the class is in fact found in the QuickLookUI framework, which is part of the Quartz framework).
I guess what happens is, when you open a file in the Finder, the Finder tells the QuickLook system (probably the quicklookd process) where on the screen it is displaying the icon for the file. When the closer is called (in TextEdit), if QuickLook has a frame to close to, it animates the window down to that frame before actually closing the window. If you didn't open the file via the Finder, QuickLook doesn't have a frame to animate to, so it presumably just calls the actuallyCloseMyself block immediately.

Related

MFC: how to show a welcome dialog first?

When launch the program, I want to show a welcome dialog first before showing the main window. My current approach is like below.
BOOL CMyApp::InitInstance()
{
...
// The one and only window has been initialized, so show and update it
m_pMainWnd->ShowWindow(SW_HIDE);
//m_pMainWnd->UpdateWindow();
// call DragAcceptFiles only if there's a suffix
// In an SDI app, this should occur after ProcessShellCommand
CWelcomeDialog welcome_dialog;
welcome_dialog.DoModal();
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
Towards the end of InitInstance(), originally it uses (SW_SHOW). At first, I try commenting it out but it still shows. So I change to (SW_HIDE). It works but has unpleasant visual artifacts. Is there way to stop showing the main window as early as possible?
Another issue is that when I hide main window and show the dialog, the dialog is not in the center position of the main window.
In general, how to implement a welcome dialog and to show it well-centered before anything else?

Cocoa: Activating window:shouldPopUpDocumentPathMenu:?

I have a document window with two NSWindowDelegate methods implemented in its NSDelegate:
windowWillReturnUndoManager:
window:shouldPopUpDocumentPathMenu:
The first one, windowWillReturnUndoManager, works as expected, which appears to indicate that the NSDelegate is set up correctly.
The second one, window:shouldPopUpDocumentPathMenu appears never to be called, even when command-clicking in the middle of the title bar of the window. A breakpoint set within it at "return TRUE;" never stops program operation.
Is there something else I need to do to get window:shouldPopUpDocumentPathMenu to be called?
As an alternative approach to this same issue, I downloaded the source code to TextEdit. It has the capability provided by window:shouldPopUpDocumentPathMenu—i.e. when you command-click in the title bar of a TextEdit window, you see the drop-down menu of the path to the file. But a search of the TextEdit source code for shouldPopUpDocumentPathMenu returns no results. Is window:shouldPopUpDocumentPathMenu: not required to get this functionality?
Thanks in advance to all for any info!
Best,
-Vik
Found it! All I had to do was add:
[myWindow setRepresentedURL:[self fileURL]];
... to my NSDocument's awakeFromNib method.
The document path popup now appears in the window title when the window name is command-clicked.

XLIB Decoration questions

I'm writing a small window manager, that add a basic decoration around a window, but actually i have several question about adding/remove decoration of a window.
First Question
Actually the decoration is added during MapNotify event, but it seems to be not a good idea, because it add decoration also to a menu opened by an application everytime the mapnotify is fired with a new window, but i want only to add decoration to main window. Maybe i have to check if the current window is a child of another window ? Actually my code just create the decoration window with a specific name, so at every MapNotify request i give the decoration window a dummy name (Parent) to distinguish it from all other windows in that way if the MapNotify event is launched on a decoration window, at least it doesn't add another decoration.
But i don't understand if MapNotify is launched not only for parent window but also for childrend probably the risk is that i add more than one decoration window.
The actual code is the following:
void map_notify_handler(XEvent local_event, Display* display, ScreenInfos infos){
printf("Map Notify\n");
XWindowAttributes win_attr;
char *child_name;
XGetWindowAttributes(display, local_event.xmap.window, &win_attr);
XFetchName(display, local_event.xmap.window, &child_name);
printf("Attributes: W: %d - H: %d - Name: %s\n", win_attr.width, win_attr.height, child_name);
if(child_name!=NULL){
if(strcmp(child_name, "Parent")){
Window new_win = draw_window_with_name(display, RootWindow(display, infos.screen_num), "Parent", infos.screen_num,
win_attr.x, win_attr.y, win_attr.width, win_attr.height+DECORATION_HEIGHT, 0,
BlackPixel(display, infos.screen_num));
XMapWindow(display, new_win);
XReparentWindow(display,local_event.xmap.window, new_win,0, DECORATION_HEIGHT);
XSelectInput(display, local_event.xmap.window, SubstructureNotifyMask);
put_text(display, new_win, child_name, "9x15", 10, 10, BlackPixel(display,infos.screen_num), WhitePixel(display, infos.screen_num));
}
}
XFree(child_name);
}
So how to avoid adding of decoration on every window except the main application window (or the popup windows, there is a way to distinguish the type of window? How can i figure it out?)
Second Question
WHen i exit a program the window that is destroyed is just the application window not the parent decoration, how to destroy the current window and also the decoration?
I tried with the following:
void destroy_notify_handler(XEvent local_event, Display *display){
Window window = local_event.xdestroywindow.event;
XDestroyWindow(display, window);
}
But i receive the following error:
Error occurred: BadWindow (invalid Window parameter)
I use event instead of window because it seems that it contains the parent window (i read it from there: http://tronche.com/gui/x/xlib/events/window-state-change/destroy.html)
But even if i use window i have the same problem.
Or maybe i have to destroy the parent window earlier? Maybe during UnMapNotify? But how to understand if the event is launched just because the window is going to be closed or for some other reasons?
Thanks for the help :)
Read EWMH spec and you'll find answers to all questions.
Check "override redirect" window flag
You are trying
to destroy window which is already destroyed. Instead of using
event.xdestroywindow.event window id just delete your decoration
window.
Don't forget to add client window to save set if you are
writing reparenting WM. That way if you kill wm application windows
are not destroyed but reparented back to root window

Cocoa - go to foreground/background programmatically

I have an application with LSUIElement set to 1. It has a built-in editor, so I want the application to appear in Cmd+Tab cycle when the editor is open.
-(void)stepIntoForeground
{
if (NSAppKitVersionNumber < NSAppKitVersionNumber10_7) return;
if (counter == 0) {
ProcessSerialNumber psn = {0, kCurrentProcess};
OSStatus osstatus = TransformProcessType(&psn, kProcessTransformToForegroundApplication);
if (osstatus == 0) {
++counter;
} else {
//...
}
}
}
-(void)stepIntoBackground
{
if (NSAppKitVersionNumber < NSAppKitVersionNumber10_7) return;
if (counter == 0) return;
if (counter == 1) {
ProcessSerialNumber psn = {0, kCurrentProcess};
OSStatus osstatus = TransformProcessType(&psn, kProcessTransformToUIElementApplication);
if (osstatus == 0) {
--counter;
} else {
//..
}
}
}
The problems are:
there's also a Dock icon (not a big deal);
there's also Menu, that is not a big deal too, but they appear not always.
Is there any way to disable menu at all or to make it appear always in foreground? Thanks in advance.
This is how we do it.
(Works 10.7+)
DO NOT USE LSBackgroundOnly NOR LSUIElement in the app plist
Add and init your menu and NSStatusBar menu
After app initialized but not yet shown any window take a place where you might want to show the first window if any. We use applicationDidFinishLaunching.
If you do not want to show any window yet after app initialized use
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
on 10.9 you can use at last the otherwise much correct
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
If you should open any window after app init finished than simply show the main window
Maintain your list of windows
If last window closed, call
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
on 10.9 you can use at last the otherwise much correct
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
When your first window shown next time, call
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
[[self window] makeKeyAndOrderFront:nil];
This should do the trick, if at least one app window is visible you will have menu, dock icon with state signaled, and cmd+tab element with your app, if last app window closed only your NSStatusBar element stays.
Known issues:
The first step is important because without that if a system modal dialog suspends your startup (f.e. your app is downloaded from the net and become quarantined a confirmation dialog might appear at first startup depending on your security settings) your menubar might not be owned by your app after your first app window shown.
Workaround: Starting as normal app (step 1.) would solve this problem, but will cause another small one, your app icon might appear for a moment in the dock at startup even if you would like to startup without any window shown. (but we can deal with this, not owning the menubar was a bigger problem for us, so we chose this instead)
Changing between NSApplicationActivationPolicyRegular and NSApplicationActivationPolicyAccessory (or NSApplicationActivationPolicyProhibited on OSes bellow 10.9) will kill your tooltip of status bar menu element, the tooltip will be shown initially but will not ever after the second call of NSApplicationActivationPolicyAccessory -> NSApplicationActivationPolicyProhibited
Workaround: We could not find a working workaround for this and reported to Apple as a bug.
Changing from NSApplicationActivationPolicyRegular to NSApplicationActivationPolicyAccessory has other problems on some OS versions like there might be no more mouse events in visible app windows sometimes
Workaround: switch first to NSApplicationActivationPolicyProhibited (take care this leads to unwanted app messages, like NSApplicationWillResignActiveNotification, NSWindowDidResignMainNotification, etc. !)
Changing from NSApplicationActivationPolicyAccessory to NSApplicationActivationPolicyRegular is bogus as on some OS versions
the app main menu is frozen till the first app front status change
the app activated after this policy not always get placed front in the application order
Workaround: switch first to NSApplicationActivationPolicyProhibited, take care the final switch to the desired NSApplicationActivationPolicyRegular should be made delayed, use f.e. dispatch_async or similar
With swift 4, in applicationDidfinishLaunching(_:Notification)
NSApplication.shared.setActivationPolicy(.regular)
did the trick for me, but I was only trying to get keyboard focus to my programmatically created window. Thanks.
You can set App "Application is agent (UIElement)" to YES in your plist file.
EDIT:
I think there are some hacks to do this.
But it's really not the way it's meant to be.
Cmd+tab is for getting an application to foreground, but if you don't have a menu bar, it doesn't look like foreground to the user.
I'd rather make a menu bar to access the app.

In X11, how do I set the window title before creating it?

Context:
I use glfw under xmonad. Glfw apparently sets the window title after creating the window, thus not allowing xmonad to properly handle it. I want to modify the glfw source so that I can set the window title before creating the window.
Problem:
So I download glfw-2.6, and I look into lib/x11/x11_window.c ; the lines causing the trouble are:
// Create a window
_glfwWin.Win = XCreateWindow(
_glfwLibrary.Dpy,
RootWindow( _glfwLibrary.Dpy, _glfwWin.VI->screen ),
0, 0, // Upper left corner
_glfwWin.Width, _glfwWin.Height, // Width, height
0, // Borderwidth
_glfwWin.VI->depth, // Depth
InputOutput,
_glfwWin.VI->visual,
CWBorderPixel | CWColormap | CWEventMask,
&wa
);
Followed sometime later by:
_glfwPlatformSetWindowTitle( "GLFW Window" );
where
void _glfwPlatformSetWindowTitle( const char *title )
{
// Set window & icon title
XStoreName( _glfwLibrary.Dpy, _glfwWin.Win, title );
XSetIconName( _glfwLibrary.Dpy, _glfwWin.Win, title );
}
Now, if I tr yto move the glfwPlatformSetWindowTitle call before the CreateWindow call, I get a segfault -- as I should, since _glfwWin.win would not be defined.
I don't know how to solve this problem since to set the window title, I need _glfwWin.Win to be initialized, but to initialize it, I need to create the window.
Thus, I ask: in X11, what is the proper way to set the window title before creating the window?
Thanks!
This is not possible in X11, but also not necessary for stuff to work. There must be a bug somewhere causing the symptoms you're seeing. The window title is just a property on the window, and properties can't exist until there's a window for them to be on.
You say "not allowing xmonad to properly handle it" which implies it isn't coping with changes to the name; window managers absolutely must handle setting the title at any time, including changing the title long after a window is created.
What the spec says (http://www.x.org/docs/ICCCM/icccm.pdf) is:
"The window manager will examine the contents of these properties when the window makes the
transition from the Withdrawn state and will monitor some properties for changes while the window is in the Iconic or Normal state."
The "transition from the Withdrawn state" is the point where glfw calls XMapWindow(). At that point, the window will remain unmapped but the WM will receive a MapRequest. The WM would then read properties and such and then map the window. All window managers I've ever seen also handle later changes to the property because changing the window title is pretty normal. For example web browsers the page title on every url.
If xmonad doesn't handle changes maybe it at least waits for the map, so maybe you just need to set title before XMapWindow(). Really all setup should be done before MapWindow though only a few properties are required to be before it by the specs. The props that must be before it generally can't be changed without unmapping.
Incidentally, _glfwPlatformSetWindowTitle won't work for anything but Latin-1. The modern way to do it is to set _NET_WM_NAME and _NET_WM_ICON_NAME with XChangeProperty() (setting the old Latin-1 WM_NAME is fine too but only as a fallback).

Resources