/*-
 * Copyright (c) 2001 Jordan DeLong
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "wm.h"

/*
 * remove the input focus from a client; the client may or may
 * not actually be the one with the X input focus right now, but it
 * must be the "focused" client on it's workspace.  this function
 * first removes the focus from the client, and also cycles to the
 * next client in the focus list that this client is on, updating
 * the workspace focused pointer accordingly.
 *
 * for non click-to-focus modes this method is a lot simpler,
 * because we don't need to focus a new window.
 */
void focus_unfocus(client_t *client) {
	client_t *focusthis;

	/*
	 * make sure the window is focused (not neccessarily with the
	 * X input focus, but the 'focused' window on it's workspace).
	 */
	if (client->workspace->focused != client)
		return;

	/* cycle to the next one in the focus lists */
	if (options.focus == FOCUS_CLICK) {
		focusthis = TAILQ_NEXT(client, c_focus);
		if (focusthis)
			goto gotit;
		focusthis = TAILQ_FIRST(&client->workspace->w_foclist);
		if (focusthis && focusthis != client)
			goto gotit;
		goto nofocus;

gotit:
		focus_setfocused(focusthis);
		return;
	}

	/*
	 * for non click-to-focus modes, or when there is not a suitable
	 * client on the focus list we set focus to none.  either by just
	 * setting the workspace->focused pointer to nothing or if it's
	 * on the current workspace by doing a focus_none call.
	 */
nofocus:
	client->workspace->focused = NULL;
	if (client_focused == client)
		focus_none(client->screen);
}

/*
 * set the input focus to none: our dummy input focus window for
 * screen.  if there's a focused window we prepare it to lose the
 * focus by doing grabs for the click_focus stuff, and decor_unfocus.
 */
void focus_none(screen_t *screen) {
	if (client_focused) {
		if (options.focus == FOCUS_CLICK)
			XGrabButton(display, AnyButton, AnyModifier,
				client_focused->frame, 1, ButtonPressMask,
				GrabModeSync, GrabModeAsync, None, None);
		decor_unfocus(client_focused);
	}
	XSetInputFocus(display, screen->nofocus, RevertToNone, CurrentTime);
	client_focused = NULL;
	plugin_focus_change(NULL);
}

/*
 * focus a client but take into account that it might be on a different
 * workspace or desktop than is currently visible.  So either set
 * the client's workspace->focused pointer or call focus_client
 * depending on that.
 */
void focus_setfocused(client_t *client) {
	if (client->flags.nofocus)
		return;

	/*
	 * determine if the client is on the current workspace, or
	 * if it is on some other desktop/workspace.
	 */
	if (client->workspace == client->screen->desktop->current_space)
		focus_client(client);
	else
		client->workspace->focused = client;
}

/*
 * focus a specific client; this call is to actually set input focus
 * to a client, and shouldn't be used when the client may not be on
 * the current workspace (use focus_setfocused).
 */
void focus_client(client_t *client) {
	client_t *lastfocused;

	/* ignore clients w/ nofocus flag */
	if (client->flags.nofocus)
		return;

	/* prepare a focused client to lose focus */
	lastfocused = client_focused;
	if (lastfocused) {
		if (options.focus == FOCUS_CLICK)
			XGrabButton(display, AnyButton, AnyModifier, lastfocused->frame,
				1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
		decor_unfocus(lastfocused);
	}

	/* prepare this client to recieve the focus */
	if (options.focus == FOCUS_CLICK) {
		XUngrabButton(display, AnyButton, AnyModifier, client->frame);
		buttongrabhack(client->frame, AnyButton, options.mouse_modifier);
	}

	/* now we actually focus it */
	decor_focus(client);
	XSetInputFocus(display, client->window, RevertToNone, CurrentTime);
	client_focused = client;
	client->workspace->focused = client;
	plugin_focus_change(client);

#if 0
	/*
	 * XXX: this probably requires implementation of windows-style
	 * focus cycling.
	 */

	/*
	 * reorder this workspace's focus list to put the lastfocused
	 * window right before the newly focused one.
	 */
	if (lastfocused && lastfocused->workspace == client->workspace) {
		focus_list_rm(lastfocused);
		focus_list_add(lastfocused);
	}
#endif
}

/* add a client onto the focus list for it's workspace */
void focus_list_add(client_t *client) {
	if (client->workspace->focused)
		TAILQ_INSERT_BEFORE(client->workspace->focused, client, c_focus);
	else
		TAILQ_INSERT_HEAD(&client->workspace->w_foclist,
			client, c_focus);
}

/* remove a client from the focus list for it's workspace */
void focus_list_rm(client_t *client) {
	TAILQ_REMOVE(&client->workspace->w_foclist, client, c_focus);
}
