/*
* Copyright 2015 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* One tab panel for tool in regions of the Ametys.ui.tool.layout.ZonedTabsToolsLayout.
* @private
*/
Ext.define("Ametys.ui.tool.layout.ZonedTabsToolsLayout.ZoneTabsToolsPanel",
{
extend: "Ext.tab.Panel",
xtype: 'zonetabpanel',
/**
* @cfg {String} [ui="tool-layoutzone"] @inheritdoc
*/
ui: 'tool-layoutzone',
/**
* @cfg {String} location The location of the zone. See locations in Ametys.ui.tool.layout.ZonedTabsToolsLayout.
*/
/**
* @property {String} _location See #cfg-location
* @private
*/
/**
* @cfg {Ametys.ui.tool.layout.ZonedTabsToolsLayout} toolsLayout The tools layout instance
*/
/**
* @property {Ametys.ui.tool.layout.ZonedTabsToolsLayout} _toolsLayout See #cfg-toolsLayout
* @private
*/
/** @cfg {String} typePrefixCls A css classname prefix. Concatenated with the type will give a CSS classname for the tool */
typePrefixCls: "ametys-tab-color-",
/**
* @private
* @property {String} _floatingCls The css class added when floating
*/
_floatingCls: "x-panel-tool-layoutzone-floating",
/**
* @private
* @property {String} _collapsibleDirectionCls The css class prefix added to collapsible panel upon its direction
*/
_collapsibleDirectionCls: "x-panel-tool-layoutzone-collapsible",
/**
* @property {Boolean} [_floating=false] Is the zone expanded over the others zones?
*/
_floating: false,
/**
* @cfg {Ext.resizer.Splitter} floatingSplit When non-null, the splitter to display on a floating panel
*/
/**
* @property {Object} _bodySizeBeforeCollapse The last known value of the size of the body when it was expanded. null if not collapsed.
* @property {Number} _bodySizeBeforeCollapse.width The width
* @property {Number} _bodySizeBeforeCollapse.height The height
*/
hidden: true,
hideCollapseTool: true,
deferredRender: false,
/**
* @private
* @property {Object} The splitter configuration
*/
split: { ui: 'tool-layout' },
/**
* @property {Ext.menu.Menu} _contextMenu The menu to show on right click on the tabs
* @private
*/
/**
* @property {String} _contextualMenuTargetToolId The target tool id of the right click when context menu is opened
* @private
*/
/**
* Creates a zone tabs
* @param {Object} config See configuration doc.
*/
constructor: function(config)
{
config = config || {};
this._contextMenu = new Ext.menu.Menu({
items: [{
text: "{{i18n PLUGINS_CORE_UI_MSG_TOOLS_REFRESH_TAB}}",
handler: this._refreshCurrentTab,
iconCls: "ametysicon-arrow-circle-right-double",
scope: this
},
{
text: "{{i18n PLUGINS_CORE_UI_MSG_TOOLS_CLOSE_TAB}}",
handler: this._closeCurrentTab,
iconCls: "ametysicon-sign-raw-cross",
scope: this
},
'-',
{
text: "{{i18n PLUGINS_CORE_UI_MSG_TOOLS_CLOSE_OTHERS}}",
handler: this._closeOthersTabs,
iconCls: "a-tools-menu-close-others",
scope: this
},
{
text: "{{i18n PLUGINS_CORE_UI_MSG_TOOLS_CLOSE_ALL}}",
handler: this._closeAllTabs,
iconCls: "a-tools-menu-close-all",
scope: this
}]
});
this._location = config.location;
this._toolsLayout = config.toolsLayout;
config.stateful = true;
config.stateId = this.self.getName() + "$" + this._location;
config.cls = Ext.Array.from(config.cls);
config.cls.push(this._collapsibleDirectionCls + "-" + (config.collapseDirection || 'top'));
config.tabBarHeaderPosition = 0;
config.tabBar = config.tabBar || {};
config.tabBar.flex = 1;
config.header = {
titlePosition: 1
};
config.title = {
text: '',
hidden: true,
flex: 0
};
if (config.floatingSplit)
{
config.floatingSplit.collapseTarget = this;
config.floatingSplit.dock = config.floatingSplit.renderData.collapseDir;
if (config.floatingSplit.renderData.collapseDir != 'top')
{
config.dockedItems = [config.floatingSplit];
}
else
{
// Let's make some room and put the splitter in the header (as dockedItem top would be after the header :()
config.tabBarHeaderPosition++;
config.header.titlePosition++;
config.header.itemPosition = 0;
config.header.items = [config.floatingSplit];
config.header.listeners = {
'beforerender': function() {
this.getLayout().setVertical(true);
this.getLayout().setAlign("stretch");
}
}
}
}
this.callParent(arguments);
this.on('beforeshow', this._onShow, this);
this.on('expand', this._onExpand, this);
this.on('collapse', this._onUnfloat, this);
this.on('unfloat', this._onUnfloat, this);
},
/**
* @private
* Function to expand or collapse the panel depending on its current #method-collapse state AND the #_floating state
*/
_expandOrCollapse: function()
{
if (this._floating)
{
// Let's do a collapse/expand in a row with no animation
this.collapse(null, false);
this.expand(false);
this._removeFloatingStyle();
}
else if (this.collapsed)
{
this.expand();
}
else
{
this.collapse();
}
},
getState: function()
{
var collapsed = this.collapsed;
this.collapsed = false;
var state = this.callParent();
if (this._floating || collapsed)
{
state.toCollapse = true;
}
this.collapsed = collapsed;
return state;
},
/**
* @private
* Listener on show
* @param {Ametys.ui.tool.layout.ZonedTabsToolsLayout.ZoneTabsToolsPanel} me This component
*/
_onShow: function(me)
{
if (this.toCollapse)
{
this.toCollapse = false;
window.setTimeout(Ext.bind(this.collapse, this), 1);
}
if (!this.flex)
{
var brother;
if (brother = this.nextSibling("zonetabpanel") || this.previousSibling("zonetabpanel"))
{
this.flex = brother.flex || 1;
}
}
},
/**
* @private
* Listener when user clicks on the tab bar
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
* @param {Object} eOpts The object registered with the listener.
*/
_onClickOnTabBar: function(e, t, eOpts)
{
var panel = this.getActiveTab()
if (panel != null)
{
this._toolsLayout.focusTool(panel);
}
},
/**
* @private
* Listener when user clicks on the tab bar
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
* @param {Object} eOpts The object registered with the listener.
*/
_onMiddleClick: function(e, t, eOpts)
{
if (e.button == 1)
{
var cmp = Ext.get(t.id).component;
var index = this.getTabBar().items.indexOf(cmp);
var panel = this.items.get(index);
if (panel == null)
{
return;
}
panel.close();
}
},
afterRender: function()
{
this.callParent(arguments);
// Context menu
this.getTabBar().mon(this.getTabBar().getEl(), {
scope: this,
contextmenu: this._onContextMenu,
delegate: '.x-tab'
});
// Click the bar will focus the current active tool
this.getTabBar().mon(this.getTabBar().getEl(), 'click', this._onClickOnTabBar, this);
this.getTabBar().mon(this.getTabBar().getEl(), 'auxclick', this._onMiddleClick, this);
},
/**
* @private
* Listener when the tabpanel is expanded
*/
_onExpand: function()
{
var panel = this.getActiveTab()
if (panel != null)
{
this._toolsLayout._onToolShown(panel);
}
},
/**
* @private
* Listener when the tabpanel is unfloated to focus another zone
*/
_onUnfloat: function()
{
this.getLogger().debug("Unfloating '" + this._location + "' zone")
var panel = this.getActiveTab()
if (panel != null)
{
if (this._toolsLayout.getFocusedTool() == panel)
{
this._toolsLayout._onToolBlurred(panel);
var centerTab = this._toolsLayout._panelHierarchy['cl'].getActiveTab() || this._toolsLayout._panelHierarchy['cr'].getActiveTab();
if (centerTab)
{
this._toolsLayout.focusTool(centerTab);
}
else
{
this._toolsLayout.focusTool(null);
}
}
}
},
onAdd: function(tool, index)
{
var me = this;
this.callParent(arguments);
function toolFocused()
{
me._toolsLayout._onToolFocused(tool);
}
function toolBlurred()
{
me._toolsLayout._onToolBlurred(tool);
}
function toolShown()
{
me._toolsLayout._onToolShown(tool);
}
function toolActivated()
{
me._toolsLayout._onToolActivated(tool);
}
function toolDeactivated()
{
me._toolsLayout._onToolDeactivated(tool);
}
// Enhance the tab button
var tabElBtn = this.getTabBar().items.get(index);
tabElBtn.addListener('click', toolFocused);
tabElBtn.addListener('activate', toolShown);
tabElBtn.addListener('activate', toolActivated);
tabElBtn.addListener('deactivate', toolDeactivated);
tabElBtn.on('render', Ext.bind(this._onTabEltRender, this, [tabElBtn, tool.getId()], false));
tabElBtn.addCls(this.typePrefixCls + tool.getType());
// Add tool's listeners on the ui panel
tool.addListener('render', function() {
// We manually handle this event to "capture" all clicks and avoid some CodeMiror click prevention
tool.el.dom.addEventListener('mousedown', toolFocused, true);
tool.el.dom.addEventListener('keydown', toolFocused, true);
tool.on('destroy', function() {
tool.el.dom.removeEventListener('mousedown', toolFocused, true);
tool.el.dom.removeEventListener('keydown', toolFocused, true);
});
});
// Ensure the owner tabpanel is visible
this.show();
},
/**
* @private
* Listener when the tab element button is rendered
* @param {Ext.Button} tabElBtn The button
* @param {String} toolId The id of the tool associated to the button
*/
_onTabEltRender: function(tabElBtn, toolId)
{
// Add a wrapping div element for css purposes
tabElBtn.getEl().first().wrap({});
// Activate drag'n'drop
var dragDropProxy = Ext.create("Ametys.ui.tool.layout.ZonedTabsToolsLayout.ZonedTabsDD", tabElBtn, 'toolstabsDDGroup', { isTarget: false, toolId: toolId, toolsLayout: this._toolsLayout });
tabElBtn.on('destroy', Ext.bind(dragDropProxy.destroy, dragDropProxy, [], false));
},
onRemove: function(panel, autoDestroy)
{
this.callParent(arguments);
if (this.items.length == 0)
{
if (this.floatedFromCollapse)
{
this.expand();
}
this.hide();
}
},
/**
* @private
* Determines if the panel is a main area of the Ametys.ui.tool.layout.ZonedTabsToolsLayout
* A main area does not pin and slide for example.
* @returns {Boolean} True if this is a main area
*/
_isMainArea: function()
{
return this._location == 'cl' || this._location == 'cr';
},
/**
* @private
* Listener when the element detects a context menu is required
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
* @param {Object} eOpts The object registered with the listener.
**/
_onContextMenu: function(e, t, eOpts)
{
e.stopEvent();
var cmp = Ext.getCmp(t.id);
var index = this.getTabBar().items.indexOf(cmp);
var panel = this.items.get(index);
if (panel == null)
{
return;
}
var toolId = panel.getId();
this._contextualMenuTargetToolId = toolId;
this._contextMenu.showAt(e.getXY());
},
/**
* @private
* Will close the currently activated tab
*/
_closeCurrentTab: function ()
{
var toolPanel = Ext.getCmp(this._contextualMenuTargetToolId);
toolPanel.close();
this._contextualMenuTargetToolId = null;
},
/**
* @private
* Will refresh the currently activated tab
*/
_refreshCurrentTab: function ()
{
var toolPanel = Ext.getCmp(this._contextualMenuTargetToolId);
Ametys.tool.ToolsManager.getTool(toolPanel.uiTool).refresh(true);
},
/**
* @private
* Will close all tabs BUT the currently activated tab
*/
_closeOthersTabs: function ()
{
Ext.suspendLayouts();
try
{
var _contextualMenuTargetToolId = this._contextualMenuTargetToolId;
this.items.each( function(item) {
if (_contextualMenuTargetToolId != item.getId())
{
item.close();
}
});
this._contextualMenuTargetToolId = null;
}
finally
{
Ext.resumeLayouts(true);
}
},
/**
* @private
* Will close all the tabs
*/
_closeAllTabs: function ()
{
Ext.suspendLayouts();
try
{
// The currently focused tool will be closed in last (to avoid, that closing it give focus to the newt one and so on...)
var remember = this._contextualMenuTargetToolId;
this._closeOthersTabs();
this._contextualMenuTargetToolId = remember;
this._closeCurrentTab();
}
finally
{
Ext.resumeLayouts(true);
}
},
/**
* @private
* For lateral zone, would display it over the others zones
*/
_slideIn: function()
{
if (!this._isMainArea() && this.collapsed)
{
this.addCls([this._floatingCls, this._floatingCls + "-" + this._location]);
this._floating = true;
this.expand();
this.floatingSplit && this.floatingSplit.show();
}
},
/**
* @private
* For lateral zone, would display it over the others zones
*/
_slideOut: function()
{
if (!this._isMainArea() && this._floating)
{
this.collapse();
this._removeFloatingStyle();
}
},
/**
* @private
* Remove the graphical stuff making the panel floating
*/
_removeFloatingStyle: function()
{
this.floatingSplit && this.floatingSplit.hide();
this.removeCls([this._floatingCls, this._floatingCls + "-" + this._location]);
}
}
);