5477 |
5485 |
5478 ####################################################################### |
5486 ####################################################################### |
5479 ## Cooperation related methods |
5487 ## Cooperation related methods |
5480 ####################################################################### |
5488 ####################################################################### |
5481 |
5489 |
5482 def send(self, token, args): |
5490 def getSharingStatus(self): |
5483 """ |
5491 """ |
5484 Public method to send an editor command to remote editors. |
5492 Public method to get some share status info. |
|
5493 |
|
5494 @return tuple indicating, if the editor is sharable, the sharing status, |
|
5495 if it is inside a locally initiated shared edit session and |
|
5496 if it is inside a remotely initiated shared edit session |
|
5497 (boolean, boolean, boolean, boolean) |
|
5498 """ |
|
5499 project = e5App().getObject("Project") |
|
5500 return project.isOpen() and project.isProjectFile(self.fileName), \ |
|
5501 self.__isShared, self.__inSharedEdit, self.__inRemoteSharedEdit |
|
5502 |
|
5503 def shareConnected(self, connected): |
|
5504 """ |
|
5505 Public slot to handle a change of the connected state. |
|
5506 |
|
5507 @param connected flag indicating the connected state (boolean) |
|
5508 """ |
|
5509 if not connected: |
|
5510 self.__inRemoteSharedEdit = False |
|
5511 self.setReadOnly(False) |
|
5512 self.__updateReadOnly() |
|
5513 self.cancelSharedEdit(send = False) |
|
5514 self.__isSyncing = False |
|
5515 self.__receivedWhileSyncing = [] |
|
5516 |
|
5517 def shareEditor(self, share): |
|
5518 """ |
|
5519 Public slot to set the shared status of the editor. |
|
5520 |
|
5521 @param share flag indicating the share status (boolean) |
|
5522 """ |
|
5523 self.__isShared = share |
|
5524 if not share: |
|
5525 self.shareConnected(False) |
|
5526 |
|
5527 def startSharedEdit(self): |
|
5528 """ |
|
5529 Public slot to start a shared edit session for the editor. |
|
5530 """ |
|
5531 self.__inSharedEdit = True |
|
5532 self.__savedText = self.text() |
|
5533 hash = str( |
|
5534 QCryptographicHash.hash( |
|
5535 Utilities.encode(self.__savedText, self.encoding)[0], |
|
5536 QCryptographicHash.Sha1).toHex(), |
|
5537 encoding = "utf-8") |
|
5538 self.__send(Editor.StartEditToken, hash) |
|
5539 |
|
5540 def sendSharedEdit(self): |
|
5541 """ |
|
5542 Public slot to end a shared edit session for the editor and |
|
5543 send the changes. |
|
5544 """ |
|
5545 commands = self.__calculateChanges(self.__savedText, self.text()) |
|
5546 self.__send(Editor.EndEditToken, commands) |
|
5547 self.__inSharedEdit = False |
|
5548 self.__savedText = "" |
|
5549 |
|
5550 def cancelSharedEdit(self, send = True): |
|
5551 """ |
|
5552 Public slot to cancel a shared edit session for the editor. |
|
5553 |
|
5554 @keyparam send flag indicating to send the CancelEdit command (boolean) |
|
5555 """ |
|
5556 self.__inSharedEdit = False |
|
5557 self.__savedText = "" |
|
5558 if send: |
|
5559 self.__send(Editor.CancelEditToken) |
|
5560 |
|
5561 def __send(self, token, args = None): |
|
5562 """ |
|
5563 Private method to send an editor command to remote editors. |
5485 |
5564 |
5486 @param token command token (string) |
5565 @param token command token (string) |
5487 @param args arguments for the command (string) |
5566 @param args arguments for the command (string) |
5488 """ |
5567 """ |
5489 msg = "" |
5568 if self.vm.isConnected(): |
5490 if token == Editor.SelectionToken: |
5569 msg = "" |
5491 msg = "{0}{1}{2} {3} {4} {5}".format( |
5570 if token in (Editor.StartEditToken, |
5492 token, |
5571 Editor.EndEditToken, |
5493 Editor.Separator, |
5572 Editor.RequestSyncToken, |
5494 *args |
5573 Editor.SyncToken): |
5495 ) |
5574 msg = "{0}{1}{2}".format( |
5496 |
5575 token, |
5497 self.vm.send(self.fileName, msg) |
5576 Editor.Separator, |
|
5577 args |
|
5578 ) |
|
5579 elif token == Editor.CancelEditToken: |
|
5580 msg = "{0}{1}c".format( |
|
5581 token, |
|
5582 Editor.Separator |
|
5583 ) |
|
5584 |
|
5585 self.vm.send(self.fileName, msg) |
5498 |
5586 |
5499 def receive(self, command): |
5587 def receive(self, command): |
5500 """ |
5588 """ |
5501 Public slot to handle received editor commands. |
5589 Public slot to handle received editor commands. |
5502 |
5590 |
5503 @param command command string (string) |
5591 @param command command string (string) |
5504 """ |
5592 """ |
5505 token, argsString = command.split(Editor.Separator) |
5593 if self.__isShared: |
5506 if token == Editor.SelectionToken: |
5594 if self.__isSyncing and \ |
5507 self.__processSelectionCommand(argsString) |
5595 not command.startswith(Editor.SyncToken + Editor.Separator): |
5508 |
5596 self.__receivedWhileSyncing.append(command) |
5509 def __selectionChanged(self): |
5597 else: |
5510 """ |
5598 self.__dispatchCommand(command) |
5511 Private slot to handle a change of the selection. |
5599 |
5512 """ |
5600 def __dispatchCommand(self, command): |
5513 if self.vm.isConnected(): |
5601 """ |
5514 sel = self.getSelection() |
5602 Private method to dispatch received commands. |
5515 if sel != self.__lastSelection: |
5603 |
5516 self.send(Editor.SelectionToken, args = sel) |
5604 @param command command to be processed (string) |
5517 self.__lastSelection = sel |
5605 """ |
5518 |
5606 token, argsString = command.split(Editor.Separator, 1) |
5519 def __processSelectionCommand(self, argsString): |
5607 if token == Editor.StartEditToken: |
5520 """ |
5608 self.__processStartEditCommand(argsString) |
5521 Private slot to process a remote selection command |
5609 elif token == Editor.CancelEditToken: |
5522 |
5610 self.shareConnected(False) |
5523 @param argsString string containing the selection parameters (string) |
5611 elif token == Editor.EndEditToken: |
5524 """ |
5612 self.__processEndEditCommand(argsString) |
5525 self.selectionChanged.disconnect(self.__selectionChanged) |
5613 elif token == Editor.RequestSyncToken: |
5526 args = argsString.split() |
5614 self.__processRequestSyncCommand(argsString) |
5527 self.setSelection(int(args[0]), int(args[1]), int(args[2]), int(args[3])) |
5615 elif token == Editor.SyncToken: |
5528 self.ensureLineVisible(int(args[0])) |
5616 self.__processSyncCommand(argsString) |
5529 self.selectionChanged.connect(self.__selectionChanged) |
5617 |
|
5618 def __processStartEditCommand(self, argsString): |
|
5619 """ |
|
5620 Private slot to process a remote StartEdit command |
|
5621 |
|
5622 @param argsString string containing the command parameters (string) |
|
5623 """ |
|
5624 if not self.__inSharedEdit and not self.__inRemoteSharedEdit: |
|
5625 self.__inRemoteSharedEdit = True |
|
5626 self.setReadOnly(True) |
|
5627 self.__updateReadOnly() |
|
5628 hash = str( |
|
5629 QCryptographicHash.hash( |
|
5630 Utilities.encode(self.text(), self.encoding)[0], |
|
5631 QCryptographicHash.Sha1).toHex(), |
|
5632 encoding = "utf-8") |
|
5633 if hash != argsString: |
|
5634 # text is different to the remote site, request to sync it |
|
5635 self.__isSyncing = True |
|
5636 self.__send(Editor.RequestSyncToken, argsString) |
|
5637 |
|
5638 def __calculateChanges(self, old, new): |
|
5639 """ |
|
5640 Private method to determine change commands to convert old text into |
|
5641 new text. |
|
5642 |
|
5643 @param old old text (string) |
|
5644 @param new new text (string) |
|
5645 @return commands to change old into new (string) |
|
5646 """ |
|
5647 oldL = old.splitlines() |
|
5648 newL = new.splitlines() |
|
5649 matcher = difflib.SequenceMatcher(None, oldL, newL) |
|
5650 |
|
5651 formatStr = "@@{0} {1} {2} {3}" |
|
5652 commands = [] |
|
5653 for token, i1, i2, j1, j2 in matcher.get_opcodes(): |
|
5654 if token == "insert": |
|
5655 commands.append(formatStr.format("i", j1, j2 - j1, -1)) |
|
5656 commands.extend(newL[j1:j2]) |
|
5657 elif token == "delete": |
|
5658 commands.append(formatStr.format("d", j1, i2 - i1, -1)) |
|
5659 elif token == "replace": |
|
5660 commands.append(formatStr.format("r", j1, i2 - i1, j2 - j1)) |
|
5661 commands.extend(newL[j1:j2]) |
|
5662 |
|
5663 return "\n".join(commands) + "\n" |
|
5664 |
|
5665 def __processEndEditCommand(self, argsString): |
|
5666 """ |
|
5667 Private slot to process a remote EndEdit command |
|
5668 |
|
5669 @param argsString string containing the command parameters (string) |
|
5670 """ |
|
5671 commands = argsString.splitlines() |
|
5672 sep = self.getLineSeparator() |
|
5673 cur = self.getCursorPosition() |
|
5674 |
|
5675 self.setReadOnly(False) |
|
5676 self.beginUndoAction() |
|
5677 while commands: |
|
5678 commandLine = commands.pop(0) |
|
5679 if not commandLine.startswith("@@"): |
|
5680 continue |
|
5681 |
|
5682 command, *args = commandLine.split() |
|
5683 pos, l1, l2 = [int(arg) for arg in args] |
|
5684 if command == "@@i": |
|
5685 txt = sep.join(commands[0:l1]) + sep |
|
5686 self.insertAt(txt, pos, 0) |
|
5687 del commands[0:l1] |
|
5688 elif command == "@@d": |
|
5689 self.setSelection(pos, 0, pos + l1, 0) |
|
5690 self.removeSelectedText() |
|
5691 elif command == "@@r": |
|
5692 self.setSelection(pos, 0, pos + l1, 0) |
|
5693 self.removeSelectedText() |
|
5694 txt = sep.join(commands[0:l2]) + sep |
|
5695 self.insertAt(txt, pos, 0) |
|
5696 del commands[0:l2] |
|
5697 self.endUndoAction() |
|
5698 self.__updateReadOnly() |
|
5699 self.__inRemoteSharedEdit = False |
|
5700 |
|
5701 self.setCursorPosition(*cur) |
|
5702 |
|
5703 def __processRequestSyncCommand(self, argsString): |
|
5704 """ |
|
5705 Private slot to process a remote RequestSync command |
|
5706 |
|
5707 @param argsString string containing the command parameters (string) |
|
5708 """ |
|
5709 if self.__inSharedEdit: |
|
5710 hash = str( |
|
5711 QCryptographicHash.hash( |
|
5712 Utilities.encode(self.__savedText, self.encoding)[0], |
|
5713 QCryptographicHash.Sha1).toHex(), |
|
5714 encoding = "utf-8") |
|
5715 |
|
5716 if hash == argsString: |
|
5717 self.__send(Editor.SyncToken, self.__savedText) |
|
5718 |
|
5719 def __processSyncCommand(self, argsString): |
|
5720 """ |
|
5721 Private slot to process a remote Sync command |
|
5722 |
|
5723 @param argsString string containing the command parameters (string) |
|
5724 """ |
|
5725 if self.__isSyncing: |
|
5726 cur = self.getCursorPosition() |
|
5727 |
|
5728 self.setReadOnly(False) |
|
5729 self.beginUndoAction() |
|
5730 self.selectAll() |
|
5731 self.removeSelectedText() |
|
5732 self.insertAt(argsString, 0, 0) |
|
5733 self.endUndoAction() |
|
5734 self.setReadOnly(True) |
|
5735 |
|
5736 self.setCursorPosition(*cur) |
|
5737 |
|
5738 while self.__receivedWhileSyncing: |
|
5739 command = self.__receivedWhileSyncing.pop(0) |
|
5740 self.__dispatchCommand(command) |
|
5741 |
|
5742 self.__isSyncing = False |