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

Reply via email to