kit/ChildSession.cpp | 28 +++++++++++++ kit/ChildSession.hpp | 9 ++++ kit/Kit.cpp | 72 +++++++++++++++++++++++++++++++--- loleaflet/dist/editor.css | 18 ++++++++ loleaflet/dist/toolbar/toolbar.js | 71 +++++++++++++++++++++++++++++++-- loleaflet/src/layer/marker/Cursor.js | 6 +- loleaflet/src/layer/tile/TileLayer.js | 19 ++++++++ test/WhiteBoxTests.cpp | 9 ++++ 8 files changed, 220 insertions(+), 12 deletions(-)
New commits: commit 3a43f691167c6ad08ffc5670384ca3ab8cadcbe2 Author: Aditya Dewan <iit2015...@iiita.ac.in> Date: Tue Jun 27 17:14:50 2017 +0530 tdf#108341 automatic editor switch and follow added Change-Id: I5129256a0633916a3ca2cb05ccba39a5f4a5d398 Reviewed-on: https://gerrit.libreoffice.org/39299 Reviewed-by: pranavk <pran...@collabora.co.uk> Tested-by: pranavk <pran...@collabora.co.uk> diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp index fcc750b7..0a4a59e4 100644 --- a/kit/ChildSession.cpp +++ b/kit/ChildSession.cpp @@ -367,6 +367,8 @@ bool ChildSession::loadDocument(const char * /*buffer*/, int /*length*/, const s // Inform everyone (including this one) about updated view info _docManager.notifyViewInfo(); + sendTextFrame("editor: " + std::to_string(_docManager.getEditorId())); + LOG_INF("Loaded session " << getId()); return true; @@ -1027,6 +1029,31 @@ void ChildSession::rememberEventsForInactiveUser(const int type, const std::stri } } +void ChildSession::updateSpeed() { + + std::chrono::steady_clock::time_point now(std::chrono::steady_clock::now()); + + while(_cursorInvalidatedEvent.size() != 0 && + std::chrono::duration_cast<std::chrono::milliseconds>(now - _cursorInvalidatedEvent.front()).count() > _eventStorageIntervalMs) + { + _cursorInvalidatedEvent.pop(); + } + _cursorInvalidatedEvent.push(now); + _docManager.updateEditorSpeeds(_viewId, _cursorInvalidatedEvent.size()); +} + +int ChildSession::getSpeed() { + + std::chrono::steady_clock::time_point now(std::chrono::steady_clock::now()); + + while(_cursorInvalidatedEvent.size() > 0 && + std::chrono::duration_cast<std::chrono::milliseconds>(now - _cursorInvalidatedEvent.front()).count() > _eventStorageIntervalMs) + { + _cursorInvalidatedEvent.pop(); + } + return _cursorInvalidatedEvent.size(); +} + void ChildSession::loKitCallback(const int type, const std::string& payload) { const auto typeName = LOKitHelper::kitCallbackTypeToString(type); @@ -1101,6 +1128,7 @@ void ChildSession::loKitCallback(const int type, const std::string& payload) } break; case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + updateSpeed(); sendTextFrame("invalidatecursor: " + payload); break; case LOK_CALLBACK_TEXT_SELECTION: diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp index 7faef0ed..446ffca4 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -12,6 +12,7 @@ #include <mutex> #include <unordered_map> +#include <queue> #define LOK_USE_UNSTABLE_API #include <LibreOfficeKit/LibreOfficeKit.hxx> @@ -47,6 +48,9 @@ public: /// Send updated view info to all active sessions. virtual void notifyViewInfo() = 0; + virtual void updateEditorSpeeds(int id, int speed) = 0; + + virtual int getEditorId() = 0; /// Get a view ID <-> UserInfo map. virtual std::map<int, UserInfo> getViewInfo() = 0; @@ -139,6 +143,8 @@ public: const std::string& getViewUserId() const { return _userId; } const std::string& getViewUserName() const { return _userName; } const std::string& getViewUserExtraInfo() const { return _userExtraInfo; } + void updateSpeed(); + int getSpeed(); void loKitCallback(const int type, const std::string& payload); @@ -190,6 +196,9 @@ private: const std::string _jailId; IDocumentManager& _docManager; + std::queue<std::chrono::steady_clock::time_point> _cursorInvalidatedEvent; + const unsigned _eventStorageIntervalMs = 15*1000; + /// View ID, returned by createView() or 0 by default. int _viewId; diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 8036503b..d3d865d0 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -516,7 +516,9 @@ public: _isDocPasswordProtected(false), _docPasswordType(PasswordType::ToView), _stop(false), - _isLoading(0) + _isLoading(0), + _editorId(-1), + _editorChangeWarning(false) { LOG_INF("Document ctor for [" << _docKey << "] url [" << _url << "] on child [" << _jailId << @@ -561,6 +563,10 @@ public: auto session = std::make_shared<ChildSession>(sessionId, _jailId, *this); _sessions.emplace(sessionId, session); + int viewId = session->getViewId(); + _lastUpdatedAt[viewId] = std::chrono::steady_clock::now(); + _speedCount[viewId] = 0; + LOG_DBG("Sessions: " << _sessions.size()); return true; } @@ -1098,6 +1104,11 @@ private: return _tileQueue; } + int getEditorId() override + { + return _editorId; + } + /// Notify all views of viewId and their associated usernames void notifyViewInfo() override { @@ -1153,6 +1164,51 @@ private: sendTextFrame("client-all " + msg); } + void updateEditorSpeeds(int id, int speed) override + { + int maxSpeed = -1, fastestUser = -1; + + auto now = std::chrono::steady_clock::now(); + _lastUpdatedAt[id] = now; + _speedCount[id] = speed; + + for (const auto& it : _sessions) + { + const auto session = it.second; + int sessionId = session->getViewId(); + + auto duration = (_lastUpdatedAt[id] - now); + auto durationInMs = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); + if (_speedCount[sessionId] != 0 && durationInMs > 5000) + { + _speedCount[sessionId] = session->getSpeed(); + _lastUpdatedAt[sessionId] = now; + } + if (_speedCount[sessionId] > maxSpeed) + { + maxSpeed = _speedCount[sessionId]; + fastestUser = sessionId; + } + } + // 0 for preventing selection of the first always + // 1 for preventing the new users from directly beoming the editors + if (_editorId != fastestUser && (maxSpeed != 0 || maxSpeed != 1)) { + if (!_editorChangeWarning && _editorId != -1) + { + _editorChangeWarning = true; + } + else + { + _editorChangeWarning = false; + _editorId = fastestUser; + for (const auto& it : _sessions) + it.second->sendTextFrame("editor: " + std::to_string(_editorId)); + } + } + else + _editorChangeWarning = false; + } + private: // Get the color value for all author names from the core @@ -1351,6 +1407,9 @@ private: if (size == disconnect.size() && strncmp(data, disconnect.data(), disconnect.size()) == 0) { + if(session->getViewId() == _editorId) { + _editorId = -1; + } LOG_DBG("Removing ChildSession [" << sessionId << "]."); _sessions.erase(it); const auto count = _sessions.size(); @@ -1431,7 +1490,7 @@ private: LOG_DBG("Thread started."); - // Update memory stats every 5 seconds. + // Update memory stats and editor every 5 seconds. const auto memStatsPeriodMs = 5000; auto lastMemStatsTime = std::chrono::steady_clock::now(); sendTextFrame(Util::getMemoryStats(ProcSMapsFile)); @@ -1443,14 +1502,13 @@ private: const TileQueue::Payload input = _tileQueue->get(POLL_TIMEOUT_MS * 2); if (input.empty()) { - const auto duration = (std::chrono::steady_clock::now() - lastMemStatsTime); - const auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); + auto duration = (std::chrono::steady_clock::now() - lastMemStatsTime); + auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); if (durationMs > memStatsPeriodMs) { sendTextFrame(Util::getMemoryStats(ProcSMapsFile)); lastMemStatsTime = std::chrono::steady_clock::now(); } - continue; } @@ -1616,9 +1674,13 @@ private: std::condition_variable _cvLoading; std::atomic_size_t _isLoading; + int _editorId; + bool _editorChangeWarning; std::map<int, std::unique_ptr<CallbackDescriptor>> _viewIdToCallbackDescr; std::map<std::string, std::shared_ptr<ChildSession>> _sessions; + std::map<int, std::chrono::steady_clock::time_point> _lastUpdatedAt; + std::map<int, int> _speedCount; /// For showing disconnected user info in the doc repair dialog. std::map<int, UserInfo> _sessionUserInfo; Poco::Thread _callbackThread; diff --git a/loleaflet/dist/editor.css b/loleaflet/dist/editor.css index be24c214..5e3b0ea0 100644 --- a/loleaflet/dist/editor.css +++ b/loleaflet/dist/editor.css @@ -1,3 +1,21 @@ +#userlist_table { + width: 100%; +} .selected-user { background-color: darkgrey; +} +#editor-btn { + padding: 3px 5px; + max-width: 120px; + text-align: center; + vertical-align: middle; + width: 100%; + font-size: 14px; +} +#editor-btn #follow-checkbox{ + margin: 3px; +} +#currently-msg{ + text-align: center; + padding: 3px 5px; } \ No newline at end of file diff --git a/loleaflet/dist/toolbar/toolbar.js b/loleaflet/dist/toolbar/toolbar.js index b615af4d..e33ad8f7 100644 --- a/loleaflet/dist/toolbar/toolbar.js +++ b/loleaflet/dist/toolbar/toolbar.js @@ -678,7 +678,16 @@ $(function () { {type: 'html', id: 'right'}, {type: 'html', id: 'modifiedstatuslabel', html: '<div id="modifiedstatuslabel" class="loleaflet-font"></div>'}, {type: 'break', id: 'modifiedstatuslabelbreak'}, - {type: 'drop', id: 'userlist', text: _('No users'), html: '<div id="userlist_container"><table id="userlist_table"><tbody></tbody></table></div>' }, + {type: 'drop', id: 'userlist', text: _('No users'), html: '<div id="userlist_container"><table id="userlist_table"><tbody></tbody></table>' + + '<hr><table class="loleaflet-font" id="editor-btn">' + + '<tr>' + + '<td><input type="checkbox" name="alwaysFollow" id="follow-checkbox" onclick="editorUpdate(event)"></td>' + + '<td>Always follow the editor</td>' + + '</tr>' + + '</table>' + + '<p id="currently-msg">Current - <b><span id="current-editor">Dewan</span></b></p>' + + '</div>' + }, {type: 'break', id: 'userlistbreak'}, {type: 'button', id: 'prev', img: 'prev', hint: _('Previous page')}, {type: 'button', id: 'next', img: 'next', hint: _('Next page')}, @@ -690,6 +699,19 @@ $(function () { ], onClick: function (e) { if (e.item.id === 'userlist') { + setTimeout(function() { + var cBox = $('#follow-checkbox')[0]; + var docLayer = map._docLayer; + var editorId = docLayer._editorId; + + if (cBox) + cBox.checked = docLayer._followEditor; + + if (docLayer.editorId !== -1 && map._viewInfo[editorId]) + $('#current-editor').text(map._viewInfo[editorId].username); + else + $('#currently-msg').hide(); + }, 100); return; } onClick(e.target, e.item, e.subItem); @@ -1576,15 +1598,24 @@ map.on('keydown', function (e) { } }); -function onUseritemClicked(e) { +function goToViewId(id) { var docLayer = map._docLayer; - var viewId = parseInt(e.currentTarget.id.replace('user-', '')); + + if (id === -1) + return; if (map.getDocType() === 'spreadsheet') { - docLayer.goToCellViewCursor(viewId); + docLayer.goToCellViewCursor(id); } else if (map.getDocType() === 'text' || map.getDocType() === 'presentation') { - docLayer.goToViewCursor(viewId); + docLayer.goToViewCursor(id); } +} + +function onUseritemClicked(e) { + var docLayer = map._docLayer; + var viewId = parseInt(e.currentTarget.id.replace('user-', '')); + + goToViewId(viewId); if (viewId === map._docLayer._viewId) { $('#tb_toolbar-down_item_userlist').w2overlay(''); @@ -1600,6 +1631,31 @@ function onUseritemClicked(e) { selectUser(viewId); } +function editorUpdate(e) { + var docLayer = map._docLayer; + + if (e.target.checked) { + var editorId = docLayer._editorId; + var userlistItem = w2ui['toolbar-down'].get('userlist'); + + docLayer._followUser = false; + docLayer._followEditor = true; + if (editorId !== -1 && editorId !== docLayer.viewId) { + goToViewId(editorId); + docLayer._followThis = editorId; + } + + $('.selected-user').removeClass('selected-user'); + if ($(userlistItem.html).find('.selected-user').length !== 0) + userlistItem.html = $(userlistItem.html).find('.selected-user').removeClass('selected-user').parent().parent().parent()[0].outerHTML; + $('#tb_toolbar-down_item_userlist').w2overlay(''); + } + else { + docLayer._followEditor = false; + docLayer._followThis = -1; + } +} + function selectUser(viewId) { var userlistItem = w2ui['toolbar-down'].get('userlist'); userlistItem.html = $(userlistItem.html).find('#user-' + viewId).addClass('selected-user').parent().parent().parent()[0].outerHTML; @@ -1703,6 +1759,11 @@ map.on('removeview', function(e) { updateUserListCount(); }); +map.on('updateEditorName', function(e) { + $('#currently-msg').show() + $('#current-editor').text(e.username); +}); + map.on('setFollowOff', function(e) { var docLayer = map._docLayer; var viewId = docLayer._followThis; diff --git a/loleaflet/src/layer/marker/Cursor.js b/loleaflet/src/layer/marker/Cursor.js index 0a5e622d..792a1348 100644 --- a/loleaflet/src/layer/marker/Cursor.js +++ b/loleaflet/src/layer/marker/Cursor.js @@ -68,7 +68,8 @@ L.Cursor = L.Layer.extend({ if (this._cursorHeader) { L.DomUtil.setStyle(this._cursorHeader, 'visibility', 'visible'); - setTimeout(L.bind(function() { + clearTimeout(this._blinkTimeout); + this._blinkTimeout = setTimeout(L.bind(function() { L.DomUtil.setStyle(this._cursorHeader, 'visibility', 'hidden'); }, this), this.options.headerTimeout); } @@ -81,7 +82,8 @@ L.Cursor = L.Layer.extend({ this._cursorHeader.innerHTML = this.options.headerName; - setTimeout(L.bind(function() { + clearTimeout(this._blinkTimeout); + this._blinkTimeout = setTimeout(L.bind(function() { L.DomUtil.setStyle(this._cursorHeader, 'visibility', 'hidden'); }, this), this.options.headerTimeout); } diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 9c20b055..d909ad8f 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -143,6 +143,7 @@ L.TileLayer = L.GridLayer.extend({ 'tiletwipheight=' + this.options.tileHeightTwips; this._followThis = -1; + this._editorId = -1; this._followUser = false; this._followEditor = false; @@ -459,6 +460,9 @@ L.TileLayer = L.GridLayer.extend({ else if (textMsg.startsWith('graphicviewselection:')) { this._onGraphicViewSelectionMsg(textMsg); } + else if (textMsg.startsWith('editor:')) { + this._updateEditor(textMsg); + } }, toggleTileDebugMode: function() { @@ -723,6 +727,21 @@ L.TileLayer = L.GridLayer.extend({ this._onUpdateCursor(); }, + _updateEditor: function(textMsg) { + textMsg = textMsg.substring('editor:'.length + 1); + var editorId = parseInt(textMsg); + var docLayer = this._map._docLayer; + + docLayer._editorId = editorId; + + if (docLayer._followEditor) { + docLayer._followThis = editorId; + } + + if (this._map._viewInfo[editorId]) + this._map.fire('updateEditorName', {username: this._map._viewInfo[editorId].username}) + }, + _onInvalidateViewCursorMsg: function (textMsg) { textMsg = textMsg.substring('invalidateviewcursor:'.length + 1); var obj = JSON.parse(textMsg); diff --git a/test/WhiteBoxTests.cpp b/test/WhiteBoxTests.cpp index 3493bed1..7079c60a 100644 --- a/test/WhiteBoxTests.cpp +++ b/test/WhiteBoxTests.cpp @@ -351,6 +351,15 @@ public: { } + void updateEditorSpeeds(int, int) override + { + } + + int getEditorId() override + { + return -1; + } + std::map<int, UserInfo> getViewInfo() override { return {}; _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits