;;; xwem-clgen.el --- Generic model to manage clients.

;; Copyright (C) 2004,2005 by XWEM Org.

;; Author: Zajcev Evgeny <zevlg@yandex.ru>
;;         Steve Youngs  <steve@youngs.au.com>
;; Created: Sat Aug 28 14:31:39 MSD 2004
;; Keywords: xwem
;; X-CVS: $Id: xwem-clgen.el,v 1.7 2006-04-01 17:11:04 lg Exp $

;; This file is part of XWEM.

;; XWEM is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; XWEM 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 General Public
;; License for more details.

;; You should have received a copy of the GNU General Public License
;; along with XEmacs; see the file COPYING.  If not, write to the Free
;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
;; 02111-1307, USA.

;;; Synched up with: Not in FSF

;;; Commentary:

;; Generic managing model.

;;; Code:

(require 'xwem-load)
(require 'xwem-manage)
(require 'xwem-misc)

;;; Customisation
(defgroup xwem-clgen nil
  "Group to customise management of generic clients."
  :prefix "xwem-clgen-"
  :group 'xwem-modes)

(defcustom xwem-clgen-other-strategy 'samewin
  "*Strategy used when searching for other client in window.
Possible values are:
  
  `samewin'   - Search for client managed in window.

  `sameframe-nonactive' - Search for nonactive client managed
                          in window's frame.

  `sameframe-any'       - Search for any client managed in window's
                          frame.

  `samemanda-nonactive' - Search for any nonactive client with same
                          manage entry as other client.

  `any-nonactive'       - Search for any nonactive client."
  :type '(choice (const :tag "Same Window" samewin)
                 (const :tag "Inactive in same frame" sameframe-nonactive)
                 (const :tag "Any in same frame" sameframe-any)
                 (const :tag "Inactive with same manda" samemanda-nonactive)
                 (const :tag "Any inactive" any-nonactive))
  :group 'xwem-clgen)

(defcustom xwem-clgen-other-on-split t
  "*Non-nil mean activate client in other window when doing window split."
  :type 'boolean
  :group 'xwem-clgen)

(defcustom xwem-clgen-other-split-type 'vertical
  "*Split type."
  :type '(choice (const :tag "Vertical" vertical)
                 (const :tag "Horizontal" horizontal))
  :group 'xwem-clgen)

(defcustom xwem-clgen-activate-new t
  "*Non-nil mean newly managed generic clients are activated in their windows."
  :type 'boolean
  :group 'xwem-clgen)

(defcustom xwem-clgen-select-new t
  "*Non-nil mean, select new clients managed in selected window.
This value overrides `xwem-clgen-activate-new' if window is selected."
  :type 'boolean
  :group 'xwem-clgen)

(defcustom xwem-clgen-allow-make-frame t
  "*Non-nil mean, clgen permited to create new frames."
  :type 'boolean
  :group 'xwem-clgen)

;;; Internal variables

(defvar xwem-clgen-map 
  (let ((map (make-sparse-keymap)))
    (define-key map (xwem-kbd "H-c H-o") 'xwem-clgen-toggle-other-on-split)
    map)
  "Local keymap for generic clients.")

;;; Macros
(defmacro xwem-cl-xparent (cl)
  `(xwem-cl-get-sys-prop ,cl 'parent-xwin))
(defsetf xwem-cl-xparent (cl) (parent)
  `(xwem-cl-put-sys-prop ,cl 'parent-xwin ,parent))

(defun xwem-clgen-other-client (cl &optional clients-list visible)
  "Search client other then CL in CLIENTS-LIST.
Default CLIENTS-LIST is win's clients where CL managed.
Note that at least one CL or CLIENTS-LIST should be non-nil, otherwise
nil will be returned."
  (when (or (and (xwem-cl-p cl) (xwem-win-p (xwem-cl-win cl)))
            clients-list)
    ;; XXX 
    (unless clients-list
      (setq clients-list (xwem-win-clients (xwem-cl-win cl))))
      
    ;; XXX sort clients by recency
    (setq clients-list
          (xwem-cl-list-sort-by-recency clients-list))

    (let (rcl notgoodcl)
      (while clients-list
        (when (and (not (eq (car clients-list) cl)) ; skip ourself
                   ;; skip iconified
                   (not (eq (xwem-cl-state (car clients-list)) 'iconified)))
          ;; exclude dummy clients
          (if (and (not (xwem-dummy-client-p (car clients-list)))
                   (or visible
                       (not (xwem-win-cl-current-p (car clients-list)))))
              (progn
                (setq rcl (car clients-list))
                (setq clients-list nil))

            (when (and visible (null notgoodcl))
              (setq notgoodcl (car clients-list)))))
        (setq clients-list (cdr clients-list)))
    
      (or rcl notgoodcl))))

(define-xwem-deffered xwem-clgen-activate-other (cl win)
  "Activate other client in WIN.
Clients list is either WIN's clients list or CL's win clients list if
WIN's clients list is empty."
  (when (and (xwem-win-alive-p win)
             (not (xwem-cl-alive-p (xwem-win-cl win))))
    ;; WIN is valid and no clients yet managed in WIN
    (let ((ocl (xwem-clgen-other-client cl (xwem-win-clients win)))
          (need-select (or (and (xwem-cl-selected-p cl)
                                (not (xwem-cl-active-p cl)))
                           (xwem-cl-selected-p (xwem-dummy-client)))))
      (when (xwem-cl-alive-p ocl)
        (xwem-win-set-cl win ocl)
        (when (and (xwem-win-selected-p win) need-select)
          (xwem-select-client ocl)))

      ;; If OCL wasnt selected, try last or other client
      (when (and (xwem-win-selected-p win)
                 (not (xwem-cl-selected-p ocl))
                 need-select)
        (setq ocl (xwem-clgen-other-client
                   cl (xwem-frame-clients (xwem-win-frame win)) t))
        (if (xwem-cl-alive-p ocl)
            (xwem-select-client ocl)
          (xwem-select-some-client))))))
  
(defun xwem-clgen-other-on-split (sp-win nwin)
  "NWIN has been created as a result of split.
Probably we want to manage some client in newly created window."
  (xwem-clgen-activate-other (xwem-cl-selected) nwin))

;;; Othen-on-Split commands
(define-xwem-command xwem-clgen-turn-on-other-on-split ()
  "Turn on `xwem-clgen-other-on-split' minor mode."
  (xwem-interactive)

  (setq xwem-clgen-other-on-split t)
  (add-hook 'xwem-win-split-hook 'xwem-clgen-other-on-split)

  (xwem-message 'info "Other on split minor mode is ON."))

(define-xwem-command xwem-clgen-turn-off-other-on-split ()
  "Turn off `xwem-clgen-other-on-split' minor mode."
  (xwem-interactive)

  (setq xwem-clgen-other-on-split nil)
  (remove-hook 'xwem-win-split-hook 'xwem-clgen-other-on-split)

  (xwem-message 'info "Other on split minor mode is OFF."))

(define-xwem-command xwem-clgen-toggle-other-on-split (arg)
  "Toggle `xwem-clgen-other-on-split' minor mode.
Negative ARG turns it off, positive turns it on."
  (xwem-interactive "P")

  (if (numberp arg)
      (if (> arg 0)
          (setq xwem-clgen-other-on-split nil)
        (setq xwem-clgen-other-on-split t)))

  (if xwem-clgen-other-on-split
      (xwem-clgen-turn-off-other-on-split)
    (xwem-clgen-turn-on-other-on-split)))

;;; Initialisation stuff
(defun xwem-clgen-init ()
  "Initialise clgen stuff."
  (xwem-message 'init "Initializing generic clients ...")

  (add-hook 'xwem-win-split-hook 'xwem-clgen-other-on-split)

  (xwem-message 'init "Initializing generic clients ... done"))

;;;; ---- Generic methods ----
(define-xwem-client-property expect-win nil
  "Expectance window."
  :type 'window
  :get 'xwem-cl-get-sys-prop
  :set 'xwem-cl-put-sys-prop)

(defun xwem-manage-generic (cl)
  "Manage method for generic clients."

  ;; Put this to notify `xwem-manage' that we will handle initial
  ;; state.
  (xwem-client-set-property cl 'skip-initial-state t)

  (let ((dwin (and (xwem-cl-was-expected-p cl)
                   (xwem-client-property cl 'expect-win))))
    (when (numberp dwin)
      (setq dwin (xwem-win-find-by-id dwin)))

    (if dwin
        (xwem-client-set-property cl 'expect-win nil)
      (if (xwem-frame-alive-p (xwem-frame-selected))
          (setq dwin (xwem-win-selected))
        (when xwem-clgen-allow-make-frame
          ;; Selected window is kinda dead
          (xwem-frame-fit-screen (xwem-make-frame-1 'desktop))
          (setq dwin (xwem-win-selected)))))

    (unless (xwem-win-alive-p dwin)
      (error 'xwem-error "Can't manage in dead window"))

    ;; Create parent window.
    ;; NOTE:
    ;;   Some applications, such as mozilla, when running with
    ;;   -remote tries to find another mozilla instance to run
    ;;   in it, it seaches lowerest client, but it is not
    ;;   guarantied, because xwem frame holds many clients.
    (unless (xwem-cl-xparent cl)
      (setf (xwem-cl-xparent cl)
            (XCreateWindow (xwem-dpy) nil 0 0 1 1 0 nil nil nil
                           (make-X-Attr :override-redirect t
                                        :event-mask 0.0))))

    ;; Set CL's window
    (xwem-cl-set-win cl dwin)

    ;; Install local keymap
    (xwem-use-local-map xwem-clgen-map cl)

    ;; Select newly managed client, if needed
    (if (eql (xwem-cl-get-init-state cl) X-IconicState)
        (xwem-iconify cl)

      ;; Activate client in window or put it in `inactive' state.
      (when xwem-clgen-activate-new
        (if (and (xwem-win-selected-p dwin)
                 (xwem-win-cl dwin))
            (xwem-client-change-state cl 'inactive)
        (xwem-win-set-cl (xwem-cl-win cl) cl)))

      ;; Select client
      (when (and (xwem-win-selected-p dwin)
                 (or xwem-clgen-select-new
                     (eq (xwem-win-cl dwin) cl)))
        (xwem-win-set-cl (xwem-cl-win cl) cl)
        (xwem-select-client cl)))))

(defun xwem-clgen-refit (cl)
  "Refit generic client CL."
  (let ((xwem-win (xwem-cl-win cl))
        hthi)
    (when (xwem-win-alive-p xwem-win)
      (setq hthi (xwem-win-border-width xwem-win))
      (when (and (xwem-cl-new-xgeom cl)
                 (X-Geom-border-width (xwem-cl-new-xgeom cl)))
        ;; Border width changed
        (setf (X-Geom-border-width (xwem-cl-xgeom cl))
              (X-Geom-border-width (xwem-cl-new-xgeom cl))))

      (xwem-cl-correct-size-for-size
       cl
       (make-X-Geom :x (+ (xwem-win-x xwem-win) hthi)
                    :y (+ (xwem-win-y xwem-win) hthi)
                    :width (- (xwem-win-width xwem-win) (* 2 hthi))
                    :height (- (xwem-win-height xwem-win) (* 2 hthi))
                    :border-width (X-Geom-border-width (xwem-cl-xgeom cl)))))))
  
(defun xwem-refit-generic (cl)
  "Refit method for generic client CL.
Correct CL geometry to fit into CL's window."
  (xwem-clgen-refit cl)
  (xwem-cl-apply-xgeom cl))

(defun xwem-clgen-has-xparent (cl xparent)
  "Return non-nil if generic client CL has XPARENT last-parent."
  (let ((clxp (xwem-cl-get-sys-prop cl 'last-xparent)))
    (and (X-Win-p clxp)
         (X-Win-equal clxp xparent))))

(defun xwem-clgen-xreparent (cl xparwin x y)
  (XReparentWindow (xwem-dpy) (xwem-cl-xwin cl) xparwin x y)
  (xwem-cl-put-sys-prop cl 'last-xparent xparwin))

(define-xwem-deffered xwem-clgen-apply-state (cl)
  "Apply CL's state to life for generic client CL."
  (when (and (xwem-cl-p cl)
             (eq (xwem-cl-manage-type cl) 'generic))
    (cond ((eq (xwem-cl-state cl) 'active)
           (xwem-clgen-refit cl)
           (when (and (xwem-cl-frame cl)
                      (not (xwem-clgen-has-xparent
                            cl (xwem-frame-xwin (xwem-cl-frame cl)))))
             (xwem-clgen-xreparent cl (xwem-frame-xwin (xwem-cl-frame cl))
                                   (X-Geom-x (xwem-cl-xgeom cl))
                                   (X-Geom-y (xwem-cl-xgeom cl))))
           (xwem-cl-apply-xgeom-1 cl)
           (XLowerWindow (xwem-dpy) (xwem-cl-xwin cl))
           (XMapWindow (xwem-dpy) (xwem-cl-xwin cl)))

          ((eq (xwem-cl-state cl) 'inactive)
           (unless (xwem-clgen-has-xparent
                    cl (xwem-cl-xparent cl))
             (xwem-clgen-xreparent cl (xwem-cl-xparent cl) 0 0)
             (XLowerWindow (xwem-dpy) (xwem-cl-xwin cl))
             (XUnmapWindow (xwem-dpy) (xwem-cl-xwin cl))))

          ((eq (xwem-cl-state cl) 'iconified)
           (unless (xwem-clgen-has-xparent
                    cl (xwem-cl-xparent cl))
             (xwem-clgen-xreparent cl (xwem-cl-xparent cl) 0 0)
             (XLowerWindow (xwem-dpy) (xwem-cl-xwin cl))
             (XUnmapWindow (xwem-dpy) (xwem-cl-xwin cl)))))
    ))

(defun xwem-activate-generic (cl &optional type)
  "Activate method for CL."
  (cond ((eq type 'select)
	 (unless (xwem-win-alive-p (xwem-cl-win cl))
	   (xwem-win-set-cl (xwem-win-selected) cl))
	 (xwem-select-window (xwem-cl-win cl)))

        ((eq type 'activate)
         (cond ((xwem-win-cl-current-p cl)
                (xwem-clgen-apply-state cl))

               ((xwem-win-alive-p (xwem-cl-win cl))
                (xwem-client-change-state cl 'inactive)
                (xwem-win-set-cl (xwem-cl-win cl) cl))))))

(defun xwem-deactivate-generic (cl &optional type)
  "Deactivate method for generic client CL."
  (cond ((eq type 'deactivate)
         (xwem-clgen-activate-other cl (xwem-cl-win cl))
         (xwem-clgen-apply-state cl))))

(defun xwem-iconify-generic (cl)
  "Iconify method for generic client CL."
  (xwem-win-rem-cl (xwem-cl-win cl) cl))

(defun xwem-withdraw-generic (cl)
  "Withdraw method for generic client CL."
  (let ((win (xwem-cl-win cl)))
    (when win
      (xwem-win-rem-cl win cl)
      (xwem-cl-set-win cl nil)

      ;; Activate other client in CL's window WIN
      ;; NOTE: Deffered
      (xwem-clgen-activate-other nil win))))

;;; Additional methods
(define-xwem-method on-type-change generic (cl &optional new)
  "Called when CL is about to change manda from generic."
  (xwem-cl-set-win cl nil)
  (xwem-cl-rem-sys-prop cl 'last-xparent))

(define-xwem-method on-kill generic (cl)
  "Called when CL is killed."
  (let ((win (xwem-cl-win cl)))
    ;; Destroy parent window
    (when (xwem-cl-xparent cl)
;; This XDestroyWindow causes some problems
;      (XDestroyWindow (xwem-dpy) (xwem-cl-xparent cl))
      (setf (xwem-cl-xparent cl) nil))

    ;; Activate other client in WIN
    (xwem-clgen-activate-other cl win)

    ;; Remove CL from WIN's clients list
    (when (xwem-win-alive-p win)
      (xwem-win-rem-cl win cl))
    ))

(define-xwem-method other-client generic (cl)
  "Method to return xwem generic client other then CL."
  (xwem-clgen-other-client cl))


(provide 'xwem-clgen)

;;;; On-load actions:
;; Register generic manage type.  Use APPEND because 'generic manage
;; type is most non-privileged and matches any client.
(define-xwem-manage-model generic
  "Generic manage model.
Manage clients that no-one elso wants to manage."
  :match-spec '(eval t)
  :append t

  :manage-properties '(win-support t)
  :manage-method 'xwem-manage-generic
  :activate-method 'xwem-activate-generic
  :deactivate-method 'xwem-deactivate-generic
  :refit-method 'xwem-refit-generic
  :iconify-method 'xwem-iconify-generic
  :withdraw-method 'xwem-withdraw-generic)

(if xwem-started
    (xwem-clgen-init)
  (add-hook 'xwem-before-init-wins-hook 'xwem-clgen-init))

;;; xwem-clgen.el ends here
