;;; transpose-frame.el --- Transpose windows arrangement in a frame

;; Copyright (c) 2011  S. Irie

;; Author: S. Irie
;; Keywords: window
;; Package-Version: 20220913.1749
;; Package-Commit: 7b7f8a1582436749a57ebbba6ead716b5a0edddc

;; This program is free software.

;; 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.

;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
;; OWNER 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.

;;; Commentary:

;; This program provides some interactive functions which allows users
;; to transpose windows arrangement in currently selected frame:
;;
;; `transpose-frame'  ...  Swap x-direction and y-direction
;;
;;        +------------+------------+      +----------------+--------+
;;        |            |     B      |      |        A       |        |
;;        |     A      +------------+      |                |        |
;;        |            |     C      |  =>  +--------+-------+   D    |
;;        +------------+------------+      |   B    |   C   |        |
;;        |            D            |      |        |       |        |
;;        +-------------------------+      +--------+-------+--------+
;;
;; `flip-frame'  ...  Flip vertically
;;
;;        +------------+------------+      +------------+------------+
;;        |            |     B      |      |            D            |
;;        |     A      +------------+      +------------+------------+
;;        |            |     C      |  =>  |            |     C      |
;;        +------------+------------+      |     A      +------------+
;;        |            D            |      |            |     B      |
;;        +-------------------------+      +------------+------------+
;;
;; `flop-frame'  ...  Flop horizontally
;;
;;        +------------+------------+      +------------+------------+
;;        |            |     B      |      |     B      |            |
;;        |     A      +------------+      +------------+     A      |
;;        |            |     C      |  =>  |     C      |            |
;;        +------------+------------+      +------------+------------+
;;        |            D            |      |            D            |
;;        +-------------------------+      +-------------------------+
;;
;; `rotate-frame'  ...  Rotate 180 degrees
;;
;;        +------------+------------+      +-------------------------+
;;        |            |     B      |      |            D            |
;;        |     A      +------------+      +------------+------------+
;;        |            |     C      |  =>  |     C      |            |
;;        +------------+------------+      +------------+     A      |
;;        |            D            |      |     B      |            |
;;        +-------------------------+      +------------+------------+
;;
;; `rotate-frame-clockwise'  ...  Rotate 90 degrees clockwise
;;
;;        +------------+------------+      +-------+-----------------+
;;        |            |     B      |      |       |        A        |
;;        |     A      +------------+      |       |                 |
;;        |            |     C      |  =>  |   D   +--------+--------+
;;        +------------+------------+      |       |   B    |   C    |
;;        |            D            |      |       |        |        |
;;        +-------------------------+      +-------+--------+--------+
;;
;; `rotate-frame-anticlockwise'  ...  Rotate 90 degrees anti-clockwise
;;
;;        +------------+------------+      +--------+--------+-------+
;;        |            |     B      |      |   B    |   C    |       |
;;        |     A      +------------+      |        |        |       |
;;        |            |     C      |  =>  +--------+--------+   D   |
;;        +------------+------------+      |        A        |       |
;;        |            D            |      |                 |       |
;;        +-------------------------+      +-----------------+-------+
;;
;; This program is tested on GNU Emacs 22, 23.

;;; Code:
;;; Internal functions

(defun transpose-frame-get-arrangement (&optional frame subtree)
  (let ((tree (or subtree
                  (car (window-tree frame)))))
    (if (windowp tree)
        (list (window-buffer tree)
              (window-start tree)
              (window-point tree)
              (window-hscroll tree)
              (window-margins tree)
              (window-fringes tree)
              (window-dedicated-p tree)
              (jit-lock-register tree)
              tree
              (eq tree (frame-selected-window frame)))
      (let* ((vertical (car tree))
             (edges (cadr tree))
             (length (float (if vertical
                                (- (nth 3 edges) (cadr edges))
                              (- (nth 2 edges) (car edges))))))
        (cons vertical
              (mapcar (lambda (subtree)
                        (cons (transpose-frame-get-arrangement frame subtree)
                              (/ (let ((edges (if (windowp subtree)
                                                  (window-edges subtree)
                                                (cadr subtree))))
                                   (if vertical
                                       (- (nth 3 edges) (cadr edges))
                                     (- (nth 2 edges) (car edges))))
                                 length)))
                      (cddr tree)))))))

(defun transpose-frame-set-arrangement (config &optional window-or-frame &rest how)
  (let ((window (if (windowp window-or-frame)
                    window-or-frame
                  (frame-selected-window window-or-frame))))
    (unless (windowp window-or-frame)
      (delete-other-windows window))
    (if (bufferp (car config))
        (let ((buffer (pop config)))
          (set-window-buffer window buffer)
          (set-window-start window (pop config))
          (set-window-point window (pop config))
          (set-window-hscroll window (pop config))
          (set-window-margins window (caar config) (cdr (pop config)))
          (apply 'set-window-fringes window (pop config))
          (set-window-dedicated-p window (pop config))
          (jit-lock-register window (pop config))
          (let* ((orig-window (pop config))
                 (ol-func (lambda (ol)
                            (when (eq (overlay-get ol 'window) orig-window)
                              (overlay-put ol 'window window))))
                 (ol-lists (with-current-buffer buffer
                             (overlay-lists))))
            (mapc ol-func (car ol-lists))
            (mapc ol-func (cdr ol-lists)))
          (if (car config) (select-window window)))
      (let* ((horizontal (if (memq 'transpose how)
                             (pop config)
                           (not (pop config))))
             (edges (window-edges window))
             (length (if horizontal
                         (- (nth 2 edges) (car edges))
                       (- (nth 3 edges) (cadr edges)))))
        (if (memq (if horizontal 'flop 'flip) how)
            (setq config (reverse config)))
        (while (cdr config)
          (setq window (prog1
                           (split-window window (round (* length (cdar config)))
                                         horizontal)
                         (apply 'transpose-frame-set-arrangement
                                (caar config) window how))
                config (cdr config)))
        (apply 'transpose-frame-set-arrangement
               (caar config) window how)))))

;;; User commands

;;;###autoload
(defun transpose-frame (&optional frame)
  "Transpose windows arrangement at FRAME.
Omitting FRAME means currently selected frame."
  (interactive)
  (transpose-frame-set-arrangement (transpose-frame-get-arrangement frame) frame
                                   'transpose)
  (when (called-interactively-p 'any) (recenter)))

;;;###autoload
(defun flip-frame (&optional frame)
  "Flip windows arrangement vertically at FRAME.
Omitting FRAME means currently selected frame."
  (interactive)
  (transpose-frame-set-arrangement (transpose-frame-get-arrangement frame) frame
                                   'flip))

;;;###autoload
(defun flop-frame (&optional frame)
  "Flop windows arrangement horizontally at FRAME.
Omitting FRAME means currently selected frame."
  (interactive)
  (transpose-frame-set-arrangement (transpose-frame-get-arrangement frame) frame
                                   'flop))

;;;###autoload
(defun rotate-frame (&optional frame)
  "Rotate windows arrangement 180 degrees at FRAME.
Omitting FRAME means currently selected frame."
  (interactive)
  (transpose-frame-set-arrangement (transpose-frame-get-arrangement frame) frame
                                   'flip 'flop))

;;;###autoload
(defun rotate-frame-clockwise (&optional frame)
  "Rotate windows arrangement 90 degrees clockwise at FRAME.
Omitting FRAME means currently selected frame."
  (interactive)
  (transpose-frame-set-arrangement (transpose-frame-get-arrangement frame) frame
                                   'transpose 'flop)
  (when (called-interactively-p 'any) (recenter)))

;;;###autoload
(defun rotate-frame-anticlockwise (&optional frame)
  "Rotate windows arrangement 90 degrees anti-clockwise at FRAME.
Omitting FRAME means currently selected frame."
  (interactive)
  (transpose-frame-set-arrangement (transpose-frame-get-arrangement frame) frame
                                   'transpose 'flip)
  (when (called-interactively-p 'any) (recenter)))

;;; _
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
(provide 'transpose-frame)
;;; transpose-frame.el ends here
