From f5783649c12c7360976aacb1b589a4b02e9e1aa9 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 24 Oct 2014 12:57:16 +0000 Subject: [PATCH] * All App: get text selection in lists working with Alt/Cmd modifier and file drag-out with Shift+Alt/Cmd modifier r49174: Apply Ctrl+Alt keys in order to be able to distinguish between content selection and dnd for draggable actions r49176: * All Applications: Get all drag and drop action functionality working cross platform -Fix drag Out to desktop functionality with Command+Shift keys (for Mac) or Alt+Shift keys (for other platforms) -Fix content selection functionality with Command key (for Mac only) or Ctrl key (for other platforms) r49177: text and German translations for drag-n-drop modifier hints r49178: fix IDE warnings --- phpgwapi/js/egw_action/egw_action.js | 29 +++ phpgwapi/js/egw_action/egw_action_dragdrop.js | 203 +++++++++++------- phpgwapi/js/egw_action/egw_action_popup.js | 115 ++++++---- phpgwapi/lang/egw_de.lang | 4 + phpgwapi/lang/egw_en.lang | 4 + 5 files changed, 238 insertions(+), 117 deletions(-) diff --git a/phpgwapi/js/egw_action/egw_action.js b/phpgwapi/js/egw_action/egw_action.js index 99036178c8..b06c024143 100644 --- a/phpgwapi/js/egw_action/egw_action.js +++ b/phpgwapi/js/egw_action/egw_action.js @@ -2152,6 +2152,35 @@ egwActionObject.prototype.getActionImplementationGroups = function(_test, _group return _groups; }; +/** + * Check if user tries to get dragOut action + * + * keys for dragOut: + * -Mac: Command + Shift + * -Others: Alt + Shift + * + * @param {event} _event + * @return {boolean} return true if Alt+Shift keys and left mouse click arre pressed, otherwise false + */ +egwActionObject.prototype.isDragOut = function (_event) +{ + return (_event.altKey || _event.metaKey) && _event.shiftKey && _event.which == 1; +}; + +/** + * Check if user tries to get selection action + * + * Keys for selection: + * -Mac: Command key + * -Others: Ctrl key + * + * @param {type} _event + * @returns {Boolean} return true if left mouse click and Ctrl key are pressed, otherwise false + */ +egwActionObject.prototype.isSelection = function (_event) +{ + return !(_event.shiftKey) && _event.which == 1 && (_event.metaKey || _event.ctrlKey); +}; /** egwActionObjectInterface Interface **/ diff --git a/phpgwapi/js/egw_action/egw_action_dragdrop.js b/phpgwapi/js/egw_action/egw_action_dragdrop.js index fe12d96268..ea9797a56c 100644 --- a/phpgwapi/js/egw_action/egw_action_dragdrop.js +++ b/phpgwapi/js/egw_action/egw_action_dragdrop.js @@ -15,6 +15,7 @@ egw_action_popup; jquery.jquery; jquery.jquery-ui; + /phpgwapi/js/jquery/jquery-ui-touch-punch/touch.js; */ /** @@ -82,7 +83,7 @@ function egwDragActionImplementation() ai.helper = null; ai.ddTypes = []; ai.selected = []; - + // Define default helper DOM // default helper also can be called later in application code in order to customization ai.defaultDDHelper = function (_selected) @@ -93,13 +94,13 @@ function egwDragActionImplementation() var moreRow = $j(document.createElement('tr')).addClass('et2_egw_action_ddHelper_moreRow'); // Main div helper container var div = $j(document.createElement("div")).append(table); - + var rows = []; // Maximum number of rows to show var maxRows = 3; // item label var itemLabel = egw.lang(egw.link_get_registry(egw.app_name(),_selected.length > 1?'entries':'entry')||egw.app_name()); - + var index = 0; for (var i = 0; i < _selected.length;i++) { @@ -116,7 +117,7 @@ function egwDragActionImplementation() var spanCnt = $j(document.createElement('span')) .addClass('et2_egw_action_ddHelper_itemsCnt') .appendTo(div); - + spanCnt.text(_selected.length +' '+ itemLabel); // Number of not shown rows var restRows = _selected.length - maxRows; @@ -128,7 +129,7 @@ function egwDragActionImplementation() break; } } - + var text = $j(document.createElement('div')).addClass('et2_egw_action_ddHelper_tip'); div.append(text); @@ -136,13 +137,14 @@ function egwDragActionImplementation() if('draggable' in document.createElement('span') && navigator && navigator.userAgent.indexOf('Chrome') >= 0 && egw.app_name() == 'filemanager') // currently only filemanager supports drag out { - var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; - text.text(egw.lang('Hold %1 to drag %2 to your computer',key, itemLabel)); + var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? + egw.lang('Alt') : egw.lang('Command ⌘'); + text.text(egw.lang('Hold [%1] and [%2] key to drag %3 to your desktop', key, egw.lang('Shift ⇧'), itemLabel)); } // Final html DOM return as helper structor return div; - } - + }; + ai.doRegisterAction = function(_aoi, _callback, _context) { var node = _aoi.getDOMNode(); @@ -187,78 +189,121 @@ function egwDragActionImplementation() * This way we can at least toggle which one is operating, so they * both work alternately if not together. */ - // Native DnD - Doesn't play nice with jQueryUI Sortable - // Tell jQuery to include this property - jQuery.event.props.push('dataTransfer'); + // Native DnD - Doesn't play nice with jQueryUI Sortable + // Tell jQuery to include this property + jQuery.event.props.push('dataTransfer'); - $j(node).off("mousedown") - .on("mousedown", function(event) { - $j(node).draggable("option","disabled",event.ctrlKey || event.metaKey); - $j(this).attr("draggable", event.ctrlKey || event.metaKey ? "true" : ""); - - // Disabling draggable adds some UI classes, but we don't care so remove them - $j(node).removeClass("ui-draggable-disabled ui-state-disabled"); - if(!(event.ctrlKey || event.metaKey) || !this.addEventListener) return; - }) - .on("dragstart", function(event) { - if(event.dataTransfer == null) { - return; - } - event.dataTransfer.effectAllowed="copy"; - - // Get all selected - // Multiples aren't supported by event.dataTransfer, yet, so - // select only the row they clicked on. - // var selected = _context.getSelectedLinks('drag'); - var selected = [_context]; - _context.parent.setAllSelected(false); - _context.setSelected(true); - - // Set file data - for(var i = 0; i < selected.length; i++) - { - var data = selected[i].data || egw.dataGetUIDdata(selected[i].id).data || {}; - if(data && data.mime && data.download_url) - { - var url = data.download_url; - - // NEED an absolute URL - if (url[0] == '/') url = egw.link(url); - // egw.link adds the webserver, but that might not be an absolute URL - try again - if (url[0] == '/') url = window.location.origin+url; - - // Unfortunately, dragging files is currently only supported by Chrome - if(navigator && navigator.userAgent.indexOf('Chrome')) + $j(node).off("mousedown") + .on("mousedown", function(event) { + var dragOut = _context.isDragOut(event); + $j(this).attr("draggable", dragOut? "true" : ""); + $j(node).draggable("option","disabled",dragOut); + if (dragOut) { - event.dataTransfer.setData("DownloadURL", data.mime+':'+data.name+':'+url); + // Disabling draggable adds some UI classes, but we don't care so remove them + $j(node).removeClass("ui-draggable-disabled ui-state-disabled"); + } else { - // Include URL as a fallback - event.dataTransfer.setData("text/uri-list", url); + if (_context.isSelection(event)) + { + $j(node).draggable("disable"); + // Disabling draggable adds some UI classes, but we don't care so remove them + $j(node).removeClass("ui-draggable-disabled ui-state-disabled"); + } + else if(event.which != 3) + { + document.getSelection().removeAllRanges(); + } + if(!(dragOut) || !this.addEventListener) return; + } + }) + .on ("mouseup", function (event){ + if (_context.isSelection(event)) + $j(node).draggable("enable"); + }) + .on("dragstart", function(event) { + if(_context.isSelection(event)) return; + if(event.dataTransfer == null) { + return; + } + event.dataTransfer.effectAllowed="copy"; + + // Get all selected + // Multiples aren't supported by event.dataTransfer, yet, so + // select only the row they clicked on. + // var selected = _context.getSelectedLinks('drag'); + var selected = [_context]; + _context.parent.setAllSelected(false); + _context.setSelected(true); + + // Set file data + for(var i = 0; i < selected.length; i++) + { + var data = selected[i].data || egw.dataGetUIDdata(selected[i].id).data || {}; + if(data && data.mime && data.download_url) + { + var url = data.download_url; + + // NEED an absolute URL + if (url[0] == '/') url = egw.link(url); + // egw.link adds the webserver, but that might not be an absolute URL - try again + if (url[0] == '/') url = window.location.origin+url; + + // Unfortunately, dragging files is currently only supported by Chrome + if(navigator && navigator.userAgent.indexOf('Chrome')) + { + event.dataTransfer.setData("DownloadURL", data.mime+':'+data.name+':'+url); + } + else + { + // Include URL as a fallback + event.dataTransfer.setData("text/uri-list", url); + } } } - } - if(event.dataTransfer.types.length == 0) - { - // No file data? Abort: drag does nothing - event.preventDefault(); - return; - } + if(event.dataTransfer.types.length == 0) + { + // No file data? Abort: drag does nothing + event.preventDefault(); + return; + } - // Create drag icon - _callback.call(_context, _context, ai); - // Drag icon must be visible for setDragImage() - we'll remove it on drag - $j("body").append(ai.helper); - event.dataTransfer.setDragImage(ai.helper[0],-12,-12); - }) - .on("drag", function(e) { - // Remove the helper, it has been copied into the dataTransfer object now - // Hopefully user didn't notice it... - if(e.dataTransfer != null) - { - ai.helper.remove(); - } + // Create drag icon + _callback.call(_context, _context, ai); + // Drag icon must be visible for setDragImage() - we'll remove it on drag + $j("body").append(ai.helper); + event.dataTransfer.setDragImage(ai.helper[0],-12,-12); + }) + .on("drag", function(e) { + // Remove the helper, it has been copied into the dataTransfer object now + // Hopefully user didn't notice it... + if(e.dataTransfer != null) + { + ai.helper.remove(); + } + }); + } + else + { + // Use Ctrl key in order to select content + $j(node).off("mousedown") + .on({ + mousedown: function(event){ + if (_context.isSelection(event)){ + $j(node).draggable("disable"); + // Disabling draggable adds some UI classes, but we don't care so remove them + $j(node).removeClass("ui-draggable-disabled ui-state-disabled"); + } + else if(event.which != 3) + { + document.getSelection().removeAllRanges(); + } + }, + mouseup: function (){ + $j(node).draggable("enable"); + } }); } $j(node).draggable( @@ -283,7 +328,7 @@ function egwDragActionImplementation() { // Add a basic class to the helper in order to standardize the background layout ai.helper.addClass('et2_egw_action_ddHelper'); - + // Append the helper object to the body element - this // fixes a bug in IE: If the element isn't inserted into // the DOM-tree jquery appends it to the parent node. @@ -307,13 +352,13 @@ function egwDragActionImplementation() // to distinguish whether the action was intended for dragging or selecting content. var tipTelorance = 10; var helperTop = ai.helper.position().top; - - if (helperTop >= dTarget.offset().top + + if (helperTop >= dTarget.offset().top && helperTop <= (dTarget.height() + dTarget.offset().top) + tipTelorance) { - var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? 'Ctrl' : 'Command'; - // Comment this out ATM till we get the ctrl and content selection functionality working - //egw.message(egw.lang('Hold %1 key to select content.', key),'info'); + var key = ["Mac68K","MacPPC","MacIntel"].indexOf(window.navigator.platform) < 0 ? + egw.lang("Ctrl") : egw.lang("Command ⌘"); + egw.message(egw.lang('Hold [%1] key to select text eg. to copy it', key), 'info'); } // Invalid target return true; diff --git a/phpgwapi/js/egw_action/egw_action_popup.js b/phpgwapi/js/egw_action/egw_action_popup.js index c6a5646569..ca03fc24b7 100644 --- a/phpgwapi/js/egw_action/egw_action_popup.js +++ b/phpgwapi/js/egw_action/egw_action_popup.js @@ -16,11 +16,11 @@ */ if (typeof window._egwActionClasses == "undefined") - window._egwActionClasses = {} + window._egwActionClasses = {}; _egwActionClasses["popup"] = { "actionConstructor": egwPopupAction, "implementation": getPopupImplementation -} +}; function egwPopupAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple) { @@ -38,34 +38,34 @@ function egwPopupAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMult action.set_default = function(_value) { action["default"] = _value; - } + }; action.set_order = function(_value) { action.order = _value; - } + }; action.set_group = function(_value) { action.group = _value; - } + }; action.set_hint = function(_value) { action.hint = _value; - } + }; // If true, the action will be rendered as checkbox action.set_checkbox = function(_value) { action.checkbox = _value; - } + }; action.set_checked = function(_value) { action.checked = _value; - } + }; // If radioGroup is >0 and the element is a checkbox, radioGroup specifies // the group of radio buttons this one belongs to action.set_radioGroup = function(_value) { action.radioGroup = _value; - } + }; action.set_shortcut = function(_value) { if (_value) @@ -75,7 +75,7 @@ function egwPopupAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMult "shift": false, "ctrl": false, "alt": false - } + }; if (typeof _value == "object" && typeof _value.keyCode != "undefined" && typeof _value.caption != "undefined") @@ -93,7 +93,7 @@ function egwPopupAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMult { this.shortcut = false; } - } + }; return action; } @@ -107,7 +107,7 @@ function getPopupImplementation() { _popupActionImpl = new egwPopupActionImplementation(); } - return _popupActionImpl + return _popupActionImpl; } function egwPopupActionImplementation() @@ -118,8 +118,14 @@ function egwPopupActionImplementation() /** * Registers the handler for the default action + * + * @param {DOMNode} _node + * @param {function} _callback + * @param {object} _context + * @returns {boolean} */ - ai._registerDefault = function(_node, _callback, _context) { + ai._registerDefault = function(_node, _callback, _context) + { var defaultHandler = function(e) { if (typeof document.selection != "undefined" && typeof document.selection.empty != "undefined") { @@ -134,14 +140,14 @@ function egwPopupActionImplementation() _callback.call(_context, "default", ai); return false; - } + }; if (egwIsMobile()) { $j(_node).bind('click', defaultHandler); } else { _node.ondblclick = defaultHandler; } - } + }; ai._getDefaultLink = function(_links) { var defaultAction = null; @@ -155,7 +161,7 @@ function egwPopupActionImplementation() } return defaultAction; - } + }; ai._searchShortcut = function (_key, _objs, _links) { for (var i = 0; i < _objs.length; i++) @@ -174,7 +180,7 @@ function egwPopupActionImplementation() return obj; } } - } + }; ai._searchShortcutInLinks = function(_key, _links) { var objs = []; @@ -187,10 +193,16 @@ function egwPopupActionImplementation() } return ai._searchShortcut(_key, objs, _links); - } + }; /** * Handles a key press + * + * @param {object} _key + * @param {type} _selected + * @param {type} _links + * @param {type} _target + * @returns {Boolean} */ ai._handleKeyPress = function(_key, _selected, _links, _target) { // Handle the default @@ -208,7 +220,7 @@ function egwPopupActionImplementation() { return this.doExecuteImplementation({posx:0,posy:0}, _selected, _links, _target); } - + // Check whether the given shortcut exists var obj = this._searchShortcutInLinks(_key, _links); @@ -219,12 +231,18 @@ function egwPopupActionImplementation() } return false; - } + }; /** * Registers the handler for the context menu + * + * @param {DOMNode} _node + * @param {function} _callback + * @param {object} _context + * @returns {boolean} */ - ai._registerContext = function(_node, _callback, _context) { + ai._registerContext = function(_node, _callback, _context) + { var contextHandler = function(e) { //Obtain the event object if (!e) @@ -234,28 +252,28 @@ function egwPopupActionImplementation() if (_egw_active_menu) { - _egw_active_menu.hide() + _egw_active_menu.hide(); } - else if (!e.ctrlKey) + else if (!e.ctrlKey && e.which == 3) { - _xy = ai._getPageXY(e); + var _xy = ai._getPageXY(e); _callback.call(_context, _xy, ai); } - e.cancelBubble = !e.ctrlKey; - if (e.stopPropagation && !e.ctrlKey) + e.cancelBubble = !e.ctrlKey || e.which == 1; + if (e.stopPropagation && e.cancelBubble) { e.stopPropagation(); } - return e.ctrlKey; - } + return !e.cancelBubble; + }; if (egwIsMobile()) { $j(_node).bind('taphold', contextHandler); } else { $j(_node).on('contextmenu', contextHandler); } - } + }; ai.doRegisterAction = function(_aoi, _callback, _context) { @@ -268,15 +286,21 @@ function egwPopupActionImplementation() return true; } return false; - } + }; ai.doUnregisterAction = function(_aoi) { // - } + }; /** * Builds the context menu and shows it at the given position/DOM-Node. + * + * @param {object} _context + * @param {type} _selected + * @param {type} _links + * @param {type} _target + * @returns {Boolean} */ ai.doExecuteImplementation = function(_context, _selected, _links, _target) { @@ -301,7 +325,7 @@ function egwPopupActionImplementation() x = $j(node).offset().left; y = $j(node).offset().top; - _context = {"posx": x, "posy": y} + _context = {"posx": x, "posy": y}; } var menu = ai._buildMenu(_links, _selected, _target); @@ -319,10 +343,14 @@ function egwPopupActionImplementation() } return false; - } + }; /** * Groups and sorts the given action tree layer + * + * @param {type} _layer + * @param {type} _links + * @param {type} _parentGroup */ ai._groupLayers = function(_layer, _links, _parentGroup) { @@ -407,10 +435,16 @@ function egwPopupActionImplementation() } _parentGroup.groups = groups2; - } + }; /** * Build the menu layers + * + * @param {type} _menu + * @param {type} _groups + * @param {type} _selected + * @param {type} _enabled + * @param {type} _target */ ai._buildMenuLayer = function(_menu, _groups, _selected, _enabled, _target) { @@ -487,10 +521,15 @@ function egwPopupActionImplementation() firstGroup = firstGroup && firstElem; } - } + }; /** * Builds the context menu from the given action links + * + * @param {type} _links + * @param {type} _selected + * @param {type} _target + * @returns {egwMenu|egwActionImplementation._buildMenu.menu} */ ai._buildMenu = function(_links, _selected, _target) { @@ -502,7 +541,7 @@ function egwPopupActionImplementation() _links[k].actionObj.appendToTree(tree); } - // We need the dummy object container in order to pass the array by + // We need the dummy object container in order to pass the array by // reference var groups = { "groups": [] @@ -520,7 +559,7 @@ function egwPopupActionImplementation() this._buildMenuLayer(menu, groups.groups, _selected, true, _target); return menu; - } + }; ai._getPageXY = function getPageXY(event) { @@ -531,7 +570,7 @@ function egwPopupActionImplementation() document.documentElement.scrollLeft; return {'posx': (event.clientX + scrollLeft), 'posy': (event.clientY + scrollTop)}; - } + }; return ai; } diff --git a/phpgwapi/lang/egw_de.lang b/phpgwapi/lang/egw_de.lang index 52d55ab48f..519debe08a 100644 --- a/phpgwapi/lang/egw_de.lang +++ b/phpgwapi/lang/egw_de.lang @@ -185,6 +185,7 @@ cocos (keeling) islands common de COCOS INSELN collection empty. common de Collection ist leer collection listing common de Auflistung der Collection colombia common de KOLUMBIEN +command ⌘ common de Cmd ⌘ common preferences common de Allgemeine Einstellungen comoros common de KOMOREN company common de Unternehmen @@ -373,6 +374,8 @@ height common de Höhe help common de Hilfe high common de Hoch highest common de Höchste +hold [%1] and [%2] key to drag %3 to your desktop common de [%1] und [%2] Tasten halten um %3 auf Ihren Schreibtisch (Desktop) zu ziehen +hold [%1] key to select text eg. to copy it common de [%1] Taste halten um Text zu markieren um ihn z.B. zu kopieren holy see (vatican city state) common de VATICAN home common de Home home email common de private E-Mail @@ -730,6 +733,7 @@ setup common de Setup setup main menu common de Setup Hauptmenü seychelles common de SEYCHELLEN shift common de Shift +shift ⇧ common de Umschalten ⇧ show all common de alle anzeigen show all categorys common de Alle Kategorien anzeigen show as topmenu common de Menü oben anzeigen diff --git a/phpgwapi/lang/egw_en.lang b/phpgwapi/lang/egw_en.lang index ce13c2cbe3..e4577f03a0 100644 --- a/phpgwapi/lang/egw_en.lang +++ b/phpgwapi/lang/egw_en.lang @@ -185,6 +185,7 @@ cocos (keeling) islands common en COCOS (KEELING) ISLANDS collection empty. common en Collection empty. collection listing common en Collection listing colombia common en COLOMBIA +command ⌘ common en Command ⌘ common preferences common en Common preferences comoros common en COMOROS company common en Company @@ -373,6 +374,8 @@ height common en Height help common en Help high common en High highest common en Highest +hold [%1] and [%2] key to drag %3 to your desktop common en Hold [%1] and [%2] key to drag %3 to your desktop +hold [%1] key to select text eg. to copy it common en Hold [%1] key to select text eg. to copy it holy see (vatican city state) common en HOLY SEE (VATICAN CITY STATE) home common en Home home email common en Home email @@ -730,6 +733,7 @@ setup common en Setup setup main menu common en Setup main menu seychelles common en SEYCHELLES shift common en Shift +shift ⇧ common en Shift ⇧ show all common en Show all show all categorys common en Show all categories show as topmenu common en Show as top menu