widgets/window.js

/*
 * This file is part of AUX.
 *
 * AUX 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 3 of the License, or (at your option) any later version.
 *
 * AUX 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 this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

/* jshint -W014 */
/* jshint -W079 */

import { defineChildWidget } from '../child_widget.js';
import { Container } from './container.js';
import { Icon } from './icon.js';
import { Label } from './label.js';
import { Button } from './button.js';
import { Drag } from '../modules/drag.js';
import { Resize } from '../modules/resize.js';
import { setGlobalCursor, unsetGlobalCursor } from '../utils/global_cursor.js';
import { translateAnchor } from '../utils/anchor.js';
import {
  addClass,
  removeClass,
  outerWidth,
  outerHeight,
  positionLeft,
  positionTop,
  width,
  innerWidth,
  innerHeight,
  height,
  toggleClass,
  setContent,
} from '../utils/dom.js';

function headerAction() {
  const that = this.parent;
  switch (that.options.header_action) {
    case 'shrink':
      that.toggleShrink();
      break;
    case 'maximize':
      that.toggleMaximize();
      break;
    case 'maximizehorizontal':
      that.toggleMaximizeHorizontal();
      break;
    case 'maximizevertical':
      that.toggleMaximizeVertical();
      break;
    case 'minimize':
      that.toggleMinimize();
      break;
    case 'close':
      that.destroy();
      break;
  }
  /**
   * The user double-clicked on the header.
   * @event Window.headeraction
   * @param {string} action - The function which was executed, e.g. <code>shrink</code>, <code>maximize</code> or <code>close</code>.
   */
  that.emit('headeraction', that.options.header_action);
}
function mout() {
  if (this.options.auto_active && !this.dragging && !this.resizing)
    removeClass(this.element, 'aux-active');
}
function mover() {
  if (this.options.auto_active) addClass(this.element, 'aux-active');
}
function maxHeight() {
  // returns the max height of the window
  return this.options.max_height < 0
    ? Number.MAX_SAFE_INTEGER
    : this.options.max_height;
}
function maxWidth() {
  // returns the max width of the window
  return this.options.max_width < 0
    ? Number.MAX_SAFE_INTEGER
    : this.options.max_width;
}
function close() {
  /**
   * The user clicked the close button.
   * @event Window.closeclicked
   */
  this.emit('closeclicked');
  if (this.options.auto_close) this.destroy();
}
function maximize() {
  if (this.options.auto_maximize) this.toggleMaximize();
  /**
   * The user clicked the maximize button.
   * @event Window.maximizeclicked
   * @param {Object} maximize - The maximize option.
   */
  this.emit('maximizeclicked', this.options.maximize);
}
function maximizeVertical() {
  if (this.options.auto_maximize) this.toggleMaximizeVertical();
  /**
   * The user clicked the maximize-vertical button.
   * @event Window.maximizeverticalclicked
   * @param {Object} maximize - The maximize option.
   */
  this.emit('maximizeverticalclicked', this.options.maximize.y);
}
function maximizeHorizontal() {
  if (this.options.auto_maximize) this.toggleMaximizeHorizontal();
  /**
   * The user clicked the maximize-horizontal button.
   * @event Window.maximizehorizontalclicked
   * @param {Object} maximize - The maximize option.
   */
  this.emit('maximizehorizontalclicked', this.options.maximize.x);
}
function minimize() {
  if (this.options.auto_minimize) this.toggleMinimize();
  /**
   * The user clicked the minimize button.
   * @event Window.minimizeclicked
   * @param {Object} minimize - The minimize option.
   */
  this.emit('minimizeclicked', this.options.minimize);
}
function shrink() {
  if (this.options.auto_shrink) this.toggleShrink();
  /**
   * The user clicked the shrink button.
   * @event Window.shrinkclicked
   * @param {Object} shrink - The shrink option.
   */
  this.emit('shrinkclicked', this.options.shrink);
}
function startResize(el, ev) {
  setGlobalCursor('se-resize');
  this.resizing = true;
  addClass(this.element, 'aux-resizing');
  /**
   * The user starts resizing the window.
   * @event Window.startresize
   * @param {DOMEvent} event - The DOM event.
   */
  this.emit('startresize', ev);
}
function stopResize(el, ev) {
  unsetGlobalCursor('se-resize');
  this.resizing = false;
  removeClass(this.element, 'aux-resizing');
  this.triggerResizeChildren();
  calculateDimensions.call(this);
  /**
   * The user stops resizing the window.
   * @event Window.stopresize
   * @param {DOMEvent} event - The DOM event.
   */
  this.emit('stopresize', ev);
}
function resizing(el, ev) {
  if (this.options.resizing === 'continuous') {
    this.triggerResizeChildren();
    calculateDimensions.call(this);
  }
  /**
   * The user resizes the window.
   * @event Window.resizing
   * @param {DOMEvent} event - The DOM event.
   */
  this.emit('resizing', ev);
}
function calculateDimensions() {
  const x = outerWidth(this.element, true);
  const y = outerHeight(this.element, true);
  this.dimensions.width = this.options.width = x;
  this.dimensions.height = this.options.height = y;
  this.dimensions.x2 = x + this.dimensions.x1;
  this.dimensions.y2 = y + this.dimensions.y1;
}
function calculatePosition() {
  const posx = positionLeft(this.element);
  const posy = positionTop(this.element);
  const pos1 = translateAnchor(
    this.options.anchor,
    posx,
    posy,
    this.options.width,
    this.options.height
  );
  this.dimensions.x = this.options.x = pos1.x;
  this.dimensions.y = this.options.y = pos1.y;
  this.dimensions.x1 = posx;
  this.dimensions.y1 = posy;
  this.dimensions.x2 = posx + this.dimensions.width;
  this.dimensions.y2 = posy + this.dimensions.height;
}
function horizMax() {
  // returns true if maximized horizontally
  return this.options.maximize.x;
}
function vertMax() {
  // returns if maximized vertically
  return this.options.maximize.y;
}
function startDrag(ev) {
  setGlobalCursor('move');
  addClass(this.element, 'aux-dragging');
  // if window is maximized, we have to replace the window according
  // to the position of the mouse
  let y = 0,
    x = 0;
  if (vertMax.call(this)) {
    y = !this.options.fixed ? window.scrollY : 0;
  }
  if (horizMax.call(this)) {
    x = ev.clientX - (ev.clientX / width()) * this.options.width;
    x += !this.options.fixed ? window.scrollX : 0;
  }
  const pos = translateAnchor(
    this.options.anchor,
    x,
    y,
    this.options.width,
    this.options.height
  );

  if (horizMax.call(this)) this.options.x = pos.x;
  if (vertMax.call(this)) this.options.y = pos.y;

  this.drag._xpos += x;
  this.drag._ypos += y;

  /**
   * The user starts dragging the window.
   * @event Window.startdrag
   * @param {DOMEvent} event - The DOM event.
   */
  this.emit('startdrag', ev);
}
function stopDrag(ev) {
  this.dragging = false;
  calculatePosition.call(this);
  unsetGlobalCursor('move');
  /**
   * The user stops dragging the window.
   * @event Window.stopdrag
   * @param {DOMEvent} event - The DOM event.
   */
  this.emit('stopdrag', ev);
}
function dragging(ev) {
  if (!this.dragging) {
    this.dragging = true;
    // un-maximize
    if (horizMax.call(this)) {
      this.set('maximize', { x: false });
    }
    if (vertMax.call(this)) {
      this.set('maximize', { y: false });
    }
  }
  calculatePosition.call(this);
  /**
   * The user is dragging the window.
   * @event Window.dragging
   * @param {DOMEvent} event - The DOM event.
   */
  this.emit('dragging', ev);
}
function initPosition(pos) {
  const O = this.options;
  if (pos) {
    const x0 = O.fixed ? 0 : window.scrollX;
    const y0 = O.fixed ? 0 : window.scrollY;
    const pos1 = translateAnchor(
      O.open,
      x0,
      y0,
      window.innerWidth - O.width,
      window.innerHeight - O.height
    );
    const pos2 = translateAnchor(O.anchor, pos1.x, pos1.y, O.width, O.height);
    O.x = pos2.x;
    O.y = pos2.y;
  }
  setDimensions.call(this);
  setPosition.call(this);
}
function setPosition() {
  const O = this.options;
  const D = this.dimensions;
  const _width = innerWidth(this.element);
  const _height = innerHeight(this.element);
  const pos = translateAnchor(O.anchor, O.x, O.y, -_width, -_height);
  if (horizMax.call(this)) {
    this.element.style.left = (O.fixed ? 0 : window.scrollX) + 'px';
  } else {
    this.element.style.left = pos.x + 'px';
  }
  if (vertMax.call(this)) {
    this.element.style.top = (O.fixed ? 0 : window.scrollY) + 'px';
  } else {
    this.element.style.top = pos.y + 'px';
  }
  D.x = O.x;
  D.y = O.y;
  D.x1 = pos.x;
  D.y1 = pos.y;
  D.x2 = pos.x + D.width;
  D.y2 = pos.y + D.height;
  /**
   * The position of the window changed.
   * @event Window.positionchanged
   * @param {Object} event - The {@link Window#dimensions} dimensions object.
   */
  this.emit('positionchanged', D);
}
function setDimensions() {
  const O = this.options;
  const D = this.dimensions;
  if (O.width >= 0) {
    O.width = Math.min(maxWidth.call(this), Math.max(O.width, O.min_width));
    if (horizMax.call(this)) {
      outerWidth(this.element, true, width());
      D.width = width();
    } else {
      outerWidth(this.element, true, O.width);
      D.width = O.width;
    }
  } else {
    D.width = outerWidth(this.element);
  }
  if (O.height >= 0) {
    O.height = Math.min(maxHeight.call(this), Math.max(O.height, O.min_height));
    if (vertMax.call(this)) {
      outerHeight(this.element, true, height());
      D.height = height();
    } else {
      outerHeight(this.element, true, O.height);
      D.height = O.height;
    }
  } else {
    D.height = outerHeight(this.element, true);
  }
  D.x2 = D.x1 + D.width;
  D.y2 = D.y1 + D.height;
  /**
   * The dimensions of the window changed.
   * @event Window.dimensionschanged
   * @param {Object} event - The {@link Window#dimensions} dimensions object.
   */
  this.emit('dimensionschanged', this.dimensions);
}
function buildHeader() {
  buildFromConst.call(this, 'header');
  if (!this.drag) {
    this.drag = new Drag({
      node: this.element,
      handle: this.header.element,
      onStartdrag: startDrag.bind(this),
      onStopdrag: stopDrag.bind(this),
      onDragging: dragging.bind(this),
      min: { x: 0 - this.options.width + 20, y: 0 },
      max: { x: width() - 20, y: height() - 20 },
    });
    //this.header.on("dblclick", headerAction.bind(this));
  }
  /**
   * The header changed.
   * @event Window.headerchanged
   */
  this.emit('headerchanged');
}
function buildFooter() {
  buildFromConst.call(this, 'footer');
  /**
   * The footer changed.
   * @event Window.footerchanged
   */
  this.emit('footerchanged');
}
function buildFromConst(element) {
  const E = this[element].element;
  const L = this.options[element];
  const O = this.options;
  while (E.firstChild) E.firstChild.remove();
  if (!L) return;
  for (let i = 0; i < L.length; i++) {
    if (L[i] !== 'spacer') {
      this.set('show_' + L[i], true);
      E.appendChild(this[L[i]].element);
      if (L[i] == 'size' && !this.resize && this.size) {
        this.resize = new Resize({
          node: this.element,
          handle: this.size.element,
          min: { x: O.min_width, y: O.min_height },
          max: { x: maxWidth.call(this), y: maxHeight.call(this) },
          onResizestart: startResize.bind(this),
          onResizestop: stopResize.bind(this),
          onResizing: resizing.bind(this),
          active: O.resizable,
        });
      }
    } else {
      E.appendChild(element('div', 'aux-spacer'));
    }
  }
}

function statusTimeout() {
  const O = this.options;
  if (this.__status_to !== false) window.clearTimeout(this.__status_to);
  if (!O.hide_status) return;
  if (O.status)
    this.__status_to = window.setTimeout(
      function () {
        this.set('status', '');
        this.__status_to = false;
      }.bind(this),
      O.hide_status
    );
}

/**
 * This widget is a flexible overlay window.
 *
 * @class Window
 *
 * @extends Container
 *
 * @param {Object} [options={ }] - An object containing initial options.
 *
 * @property {Number} [options.width=500] - Initial width, can be a CSS length or an integer (pixels).
 * @property {Number} [options.height=200] - Initial height, can be a CSS length or an integer (pixels).
 * @property {Number} [options.x=0] - X position of the window.
 * @property {Number} [options.y=0] - Y position of the window.
 * @property {Number} [options.min_width=64] - Minimum width of the window.
 * @property {Number} [options.max_width=-1] - Maximum width of the window, -1 ~ infinite.
 * @property {Number} [options.min_height=64] - Minimum height of the window.
 * @property {Number} [options.max_height=-1] - Maximum height of the window, -1 ~ infinite.
 * @property {String} [options.anchor="top-left"] - Anchor of the window, can be one out of
 *   `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right`
 * @property {Boolean} [options.modal=false] - If modal window blocks all other elements
 * @property {String} [options.dock=false] - Docking of the window, can be one out of
 *   `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right`
 * @property {Object|Boolean} [options.maximize=false] - Boolean or object with members <code>x</code> and <code>y</code> as boolean to determine the maximized state.
 * @property {Boolean} [options.minimize=false] - Minimize window (does only make sense with a
 *   window manager application to keep track of it)
 * @property {Boolean} [options.shrink=false] - Shrink rolls the window up into the title bar.
 * @property {String|HTMLElement|Container} [options.content=""] - The content of the window.
 *   Can be either a string, a HTMLElement or a {@link Container} to append to the content area.
 * @property {String} [options.open="center"] - initial position of the window, can be one out of
 *   `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right`
 * @property {Integer} [options.z_index=10000] - Z index for piling windows. does make more sense
 *   when used together with a window manager
 * @property {String|Array<String>} [options.header=["title", "maximize", "close"]] - Single element or array of
 *   `title`, `icon`, `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal`, `status`, `resize`, `spacer`.
 * @property {String|Array<String>} [options.footer=false] - Single element or array of
 *   `title`, `icon`, `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal`, `status`, `resize`, `spacer`.
 * @property {String} [options.title=false] - Window title.
 * @property {String} [options.status=false] Window status.
 * @property {String} [options.icon=false] URL to window icon.
 * @property {Boolean} [options.fixed=true] - Whether the window sticks to the viewport rather than the document
 * @property {Boolean} [options.auto_active=false] - Auto-toggle the active-class when mouseovered
 * @property {Boolean} [options.auto_close=true] - Set whether close destroys the window or not
 * @property {Boolean} [options.auto_maximize=true] - Set whether maximize toggles the window or not
 * @property {Boolean} [options.auto_minimize=true] - Set whether minimize toggles the window or not
 * @property {Boolean} [options.auto_shrink=true] - Set whether shrink toggles the window or not
 * @property {Boolean} [options.draggable=true] - Set whether the window is draggable
 * @property {Boolean} [options.resizable=true] - Set whether the window is resizable
 * @property {String} [options.resizing="continuous"] - Resizing policy, `continuous` or `stop`.
 *   The first one resizes all children continuously while resizing.
 * @property {String} [options.header_action="maximize"] - Action for double clicking the window header, one out of
 *   `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal`
 * @property {Boolean} [options.active=true] - Active state of the window.
 * @property {Integer} [options.hide_status=0] - If set to !0 status message hides after [n] milliseconds.
 */

/**
 * @member {Drag} Window#drag - The {@link Drag} module.
 */
/**
 * @member {Resize} Window#resize - The {@link Resize} module.
 */

export class Window extends Container {
  static get _options() {
    return Object.assign({}, Container.getOptionTypes(), {
      width: 'number',
      height: 'number',
      x: 'number',
      y: 'number',
      min_width: 'number',
      max_width: 'number',
      min_height: 'number',
      max_height: 'number',
      anchor: 'string',
      modal: 'boolean',
      dock: 'boolean',
      maximize: 'boolean',
      minimize: 'boolean',
      shrink: 'boolean',
      open: 'int',
      z_index: 'int',
      header: 'array',
      footer: 'array',
      title: 'string',
      status: 'string',
      icon: 'string',
      fixed: 'boolean',
      auto_active: 'boolean',
      auto_close: 'boolean',
      auto_maximize: 'boolean',
      auto_minimize: 'boolean',
      auto_shrink: 'boolean',
      draggable: 'boolean',
      resizable: 'boolean',
      resizing: 'int',
      header_action: 'string',
      active: 'boolean',
      hide_status: 'int',
    });
  }

  static get options() {
    return {
      width: 500,
      height: 200,
      x: 0,
      y: 0,
      min_width: 64,
      max_width: -1,
      min_height: 64,
      max_height: -1,
      anchor: 'top-left',
      modal: false,
      dock: false,
      maximize: false,
      minimize: false,
      shrink: false,
      content: '',
      open: 'center',
      z_index: 10000,
      header: ['title', 'maximize', 'close'],
      footer: false,
      title: false,
      status: false,
      icon: false,
      fixed: true,
      auto_active: false,
      auto_close: true,
      auto_maximize: true,
      auto_minimize: true,
      auto_shrink: true,
      draggable: true,
      resizable: true,
      resizing: 'continuous',
      header_action: 'maximize',
      active: true,
      hide_status: 0,
      role: 'dialog',
    };
  }

  static get static_events() {
    return {
      mouseenter: mover,
      mouseleave: mout,
    };
  }

  initialize(options) {
    this.dimensions = {
      anchor: 'top-left',
      x: 0,
      x1: 0,
      x2: 0,
      y: 0,
      y1: 0,
      y2: 0,
      width: 0,
      height: 0,
    };
    super.initialize(options);
    this.__status_to = false;
    initPosition.call(this, this.options.open);
    this.set('maximize', this.options.maximize);
    this.set('minimize', this.options.minimize);
  }

  /**
   * Appends a new child to the window content area.
   * @method Window#appendChild
   * @param {Widget} child - The child widget to add to the windows content area.
   */
  appendChild(child) {
    this.content.appendChild(child.element);
    this.addChild(child);
  }

  /**
   * Toggles the overall maximize state of the window.
   * @method Window#toggleMaximize
   * @param {Boolean} maximize - State of maximization. If window is already
   *   maximized in one or both directions it is un-maximized, otherwise maximized.
   */
  toggleMaximize() {
    if (!vertMax.call(this) || !horizMax.call(this))
      this.set('maximize', { x: true, y: true });
    else this.set('maximize', { x: false, y: false });
  }

  /**
   * Toggles the vertical maximize state of the window.
   * @method Window#toggleMaximizeVertical
   * @param {Boolean} maximize - The new vertical maximization.
   */
  toggleMaximizeVertical() {
    this.set('maximize', { y: !this.options.maximize.y });
  }

  /**
   * Toggles the horizontal maximize state of the window.
   * @method Window#toggleMaximizeHorizontal
   * @param {Boolean} maximize - The new horizontal maximization.
   */
  toggleMaximizeHorizontal() {
    this.set('maximize', { x: !this.options.maximize.x });
  }

  /**
   * Toggles the minimize state of the window.
   * @method Window#toggleMinimize
   * @param {Boolean} minimize - The new minimization.
   */
  toggleMinimize() {
    this.set('minimize', !this.options.minimize);
  }

  /**
   * Toggles the shrink state of the window.
   * @method Window#toggleShrink
   * @param {Boolean} shrink - The new shrink state.
   */
  toggleShrink() {
    this.set('shrink', !this.options.shrink);
  }

  resize() {
    this.drag.set('min', { x: 0 - this.options.width + 20, y: 0 });
    this.drag.set('max', { x: width() - 20, y: height() - 20 });
    super.resize();
  }

  draw(O, element) {
    addClass(element, 'aux-window');

    super.draw(O, element);
  }

  redraw() {
    const I = this.invalid;
    const O = this.options;

    let setP = false;
    let setD = false;

    if (I.maximize) {
      I.maximize = false;
      if (O.shrink) {
        O.shrink = false;
        I.shrink = true;
      }
      toggleClass(this.element, 'aux-maximized-horizontal', O.maximize.x);
      toggleClass(this.element, 'aux-maximized-vertical', O.maximize.y);
      setD = true;
    }
    if (I.anchor) {
      I.anchor = false;
      this.dimensions.anchor = O.anchor;
      setP = setD = true;
    }
    if (I.width || I.height) {
      I.width = I.height = false;
      setD = true;
    }
    if (I.x || I.y) {
      I.x = I.y = false;
      setP = true;
    }
    if (I.z_index) {
      I.z_index = false;
      this.element.style.zIndex = O.z_index;
    }
    if (I.header) {
      I.header = false;
      this.set('show_header', !!O.header);
      if (O.header) buildHeader.call(this);
    }
    if (I.footer) {
      I.footer = false;
      this.set('show_footer', !!O.footer);
      if (O.footer) buildFooter.call(this);
    }
    if (I.status) {
      I.status = false;
      statusTimeout.call(this);
    }
    if (I.fixed) {
      this.element.style.position = O.fixed ? 'fixed' : 'absolute';
      setP = true;
    }
    if (I.active) {
      I.active = false;
      toggleClass(this.element, 'aux-active', O.active);
    }
    if (I.shrink) {
      I.shrink = false;
      this.options.maximize.y = false;
      toggleClass(this.element, 'aux-shrinked', O.shrink);
    }
    if (I.draggable) {
      I.draggable = false;
      toggleClass(this.element, 'aux-draggable', O.draggable);
    }
    if (I.resizable) {
      I.resizable = false;
      toggleClass(this.element, 'aux-resizable', O.resizable);
    }
    if (I.content) {
      I.content = false;
      if (O.content) {
        if (
          Object.prototype.isPrototypeOf.call(Container.prototype, O.content)
        ) {
          setContent(this.content.element, '');
          this.appendChild(O.content);
        } else {
          setContent(this.content.element, O.content);
        }
      }
      setD = true;
      setP = true;
    }

    if (setD) setDimensions.call(this);
    if (setP) setPosition.call(this);
    super.redraw();
  }

  set(key, value) {
    const O = this.options;
    const E = this.element;

    if (key == 'maximize') {
      if (value === false)
        value = this.options.maximize = { x: false, y: false };
      else if (value === true)
        value = this.options.maximize = { x: true, y: true };
      else value = Object.assign(this.options.maximize, value);
    }
    O[key] = value;

    switch (key) {
      case 'shrink':
        O.maximize.y = false;
        break;
      case 'minimize':
        if (value) {
          if (!this.options.container && E.parentElement)
            O.container = E.parentElement;
          E.remove();
        } else if (O.container) {
          this.set('container', O.container);
        }
        break;
      case 'resizable':
        this.resize.set('active', value);
        break;
    }
    return super.set(key, value);
  }
}

/**
 * @member {Icon} Window#icon - A {@link Icon} widget to display the window icon.
 */
defineChildWidget(Window, 'icon', {
  create: Icon,
  map_options: { icon: 'icon' },
  toggle_class: true,
});
/**
 * @member {Label} Window#title - A {@link Label} to display the window title.
 */
defineChildWidget(Window, 'title', {
  create: Label,
  default_options: { class: 'aux-title' },
  map_options: { title: 'label' },
  toggle_class: true,
});
/**
 * @member {Label} Window#status - A {@link Label} to display the window status.
 */
defineChildWidget(Window, 'status', {
  create: Label,
  default_options: { class: 'aux-status' },
  map_options: { status: 'label' },
  toggle_class: true,
});
/**
 * @member {Button} Window#close - The close button.
 */
/**
 * @member {Button} Window#minimize - The minimize button.
 */
/**
 * @member {Button} Window#maximize - The maximize button.
 */
/**
 * @member {Button} Window#maximizevertical - The maximizevertical button.
 */
/**
 * @member {Button} Window#maximizehorizontal - The maximizehorizontal button.
 */
/**
 * @member {Button} Window#shrink - The shrink button.
 */

function bFactory(name, handler) {
  defineChildWidget(Window, name, {
    create: Button,
    default_options: {
      class: 'aux-' + name,
      icon: 'window' + name,
    },
    static_events: {
      click: function (e) {
        handler.call(this.parent, e);
      },
      mousedown: function (e) {
        e.stopPropagation();
      },
    },
  });
}
bFactory('close', close);
bFactory('minimize', minimize);
bFactory('maximize', maximize);
bFactory('maximizevertical', maximizeVertical);
bFactory('maximizehorizontal', maximizeHorizontal);
bFactory('shrink', shrink);

/**
 * @member {Icon} Window#size - A {@link Icon} acting as handle for window resize.
 */
defineChildWidget(Window, 'size', {
  create: Icon,
  default_options: { icon: 'windowresize', class: 'aux-size' },
});
/**
 * @member {Container} Window#content - A {@link Container} for the window content.
 */
defineChildWidget(Window, 'content', {
  create: Container,
  toggle_class: true,
  show: true,
  default_options: { class: 'aux-content' },
});
/**
 * @member {Container} Window#header - The top header bar.
 */
defineChildWidget(Window, 'header', {
  create: Container,
  toggle_class: true,
  show: true,
  default_options: { class: 'aux-header' },
  static_events: {
    dblclick: headerAction,
  },
  append: function () {
    buildHeader.call(this);
    this.element.appendChild(this.header.element);
  },
});
/**
 * @member {Container} Window#footer - The bottom footer bar.
 */
defineChildWidget(Window, 'footer', {
  create: Container,
  toggle_class: true,
  show: false,
  default_options: { class: 'aux-footer' },
  append: function () {
    buildFooter.call(this);
    this.element.appendChild(this.footer.element);
  },
});