Source code for ccpn.ui.gui.modules.NotesEditor

#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2021"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
               "Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See http://www.ccpn.ac.uk/v3-software/downloads/license")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
                 "CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
                 "J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2021-10-07 10:56:35 +0100 (Thu, October 07, 2021) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Ed Brooksbank $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================

from PyQt5 import QtWidgets
from ccpn.ui.gui.modules.CcpnModule import CcpnModule
from ccpn.ui.gui.widgets.Frame import Frame, ScrollableFrame
from ccpn.ui.gui.widgets.Label import Label
from ccpn.ui.gui.widgets.LineEdit import LineEdit
from ccpn.ui.gui.widgets.TextEditor import TextEditor
from ccpn.ui.gui.widgets.PulldownListsForObjects import NotePulldown
from ccpn.ui.gui.widgets.Spacer import Spacer
from ccpn.core.lib.Notifiers import Notifier
from ccpn.core.Note import Note
from ccpn.util.Logging import getLogger
from ccpn.ui.gui.widgets.DropBase import DropBase
from ccpn.ui.gui.lib.GuiNotifier import GuiNotifier
from ccpn.core.lib.ContextManagers import undoBlockWithoutSideBar
from ccpn.ui.gui.widgets.MessageDialog import showWarning
from ccpn.ui.gui.guiSettings import BORDERNOFOCUS_COLOUR


logger = getLogger()

DEFAULTSPACING = (0, 0)
DEFAULTMARGINS = (0, 0, 0, 0)  # l, t, r, b


[docs]class NotesEditorModule(CcpnModule): """ This class implements the module for editing the NotesList. """ includeSettingsWidget = False maxSettingsState = 2 # states are defined as: 0: invisible, 1: both visible, 2: only settings visible settingsPosition = 'top' className = 'NotesEditorModule' attributeName = 'notes' # self.project.notes _includeInLastSeen = False def __init__(self, mainWindow=None, name='Notes Editor', note=None, selectFirstItem=False): """ Initialise the widgets for the module. :param mainWindow: required :param name: optional :param note: leave as None to let window handle item selection """ super().__init__(mainWindow=mainWindow, name=name) # Derive application, project, and current from mainWindow self.mainWindow = mainWindow if mainWindow: self.application = mainWindow.application self.project = mainWindow.application.project self.current = mainWindow.application.current else: self.application = None self.project = None self.current = None self.note = None # setup the widgets self._setupWidgets() self._noteNotifier = None self.droppedNotifier = None self._setNotifiers() if note is not None: self.selectNote(note) elif selectFirstItem: self.noWidget.selectFirstItem() def _setupWidgets(self): """ Setup the widgets in module """ self._widget = ScrollableFrame(self.mainWidget, setLayout=True, showBorder=False, scrollBarPolicies=('never', 'never'), spacing=DEFAULTSPACING, margins=DEFAULTMARGINS, grid=(2, 1)) self._widgetScrollArea = self._widget._scrollArea row = 0 self.spacer = Spacer(self._widget, 5, 5, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed, grid=(row, 0), gridSpan=(1, 1)) row += 1 self.noWidget = NotePulldown(parent=self._widget, mainWindow=self.mainWindow, default=None, grid=(row, 0), gridSpan=(1, 1), minimumWidths=(0, 100), showSelectName=True, sizeAdjustPolicy=QtWidgets.QComboBox.AdjustToContents, callback=self._selectionPulldownCallback) row += 1 self.spacer = Spacer(self._widget, 5, 5, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed, grid=(row, 0), gridSpan=(1, 1)) #~~~~~~~~~~ define noteWidget box to contain main editing row += 1 self.noteWidget = Frame(self._widget, grid=(row, 0), gridSpan=(4, 5), setLayout=True) self.noteWidget.hide() nRow = 1 self.label1 = Label(self.noteWidget, text='name', grid=(nRow, 0), vAlign='c', hAlign='r') self.lineEdit1 = LineEdit(self.noteWidget, grid=(nRow, 1), gridSpan=(1, 2), vAlign='top', textAlignment='l', backgroundText='> Enter name <') self.lineEdit1.editingFinished.connect(self._applyNote) # *1 nRow += 1 self.labelComment = Label(self.noteWidget, text='comment', grid=(nRow, 0), vAlign='c', hAlign='r') self.lineEditComment = LineEdit(self.noteWidget, grid=(nRow, 1), gridSpan=(1, 2), vAlign='top', textAlignment='l', backgroundText='> Optional <') self.lineEditComment.editingFinished.connect(self._applyNote) # *1 nRow += 1 self.spacer = Spacer(self.noteWidget, 5, 5, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed, grid=(nRow, 3), gridSpan=(1, 1)) nRow += 1 self.textBox = TextEditor(self.noteWidget, grid=(nRow, 0), gridSpan=(1, 6)) self.textBox.editingFinished.connect(self._applyNote) # *1 # NOTE: *1 Automatically save the note when it loses the focus. # Otherwise is very dangerous of losing all the carefully written notes if you forget to press the button apply! #~~~~~~~~~~ end of noteWidget box row += 1 # this spacer is expanding, will fill the space when the textbox is invisible self.spacer = Spacer(self._widget, 5, 5, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding, grid=(row, 4), gridSpan=(1, 1)) def _processDroppedItems(self, data): """ CallBack for Drop events """ pids = data.get('pids', []) from ccpn.ui.gui.lib.MenuActions import _openItemObject objs = [self.project.getByPid(pid) for pid in pids] selectableObjects = [obj for obj in objs if isinstance(obj, Note)] others = [obj for obj in objs if not isinstance(obj, Note)] if len(selectableObjects) > 0: self.selectNote(selectableObjects[0]) _openItemObject(self.mainWindow, selectableObjects[1:]) else: from ccpn.ui.gui.widgets.MessageDialog import showYesNo othersClassNames = list(set([obj.className for obj in others])) if len(othersClassNames) > 0: if len(othersClassNames) == 1: title, msg = 'Dropped wrong item.', 'Do you want to open the %s in a new module?' % ''.join(othersClassNames) else: title, msg = 'Dropped wrong items.', 'Do you want to open items in new modules?' openNew = showYesNo(title, msg) if openNew: _openItemObject(self.mainWindow, others)
[docs] def selectNote(self, note=None): """ Manually select a Note from the pullDown """ if note is None: # logger.warning('select: No Note selected') # raise ValueError('select: No Note selected') self.noWidget.selectFirstItem() else: if not isinstance(note, Note): logger.warning('select: Object is not of type Note') raise TypeError('select: Object is not of type Note') else: if note.pid in self.noWidget.textList: self.note = note self.noWidget.select(self.note.pid)
def _setNotifiers(self): """ Set a Notifier to call when a note is created/deleted/renamed/changed rename calls on name change calls on any other attribute """ self._noteNotifier = self.setNotifier(self.project, [Notifier.CREATE, Notifier.DELETE, Notifier.RENAME, Notifier.CHANGE], Note.__name__, self._updateCallback) self.droppedNotifier = self.setGuiNotifier(self.mainWidget, [GuiNotifier.DROPEVENT], [DropBase.PIDS], self._processDroppedItems) def _applyNote(self): """ Called by clicking the apply button in the module. Temporarily disable notifiers, and define commandEchoBlock so all changes are treated as a single undo/redo event """ if self.note: # calls _updateCallBack during _applyNote self.setBlankingAllNotifiers(True) # disable my own notifiers while updating object other name = self.lineEdit1.text() text = self.textBox.toPlainText() comment = self.lineEditComment.text() try: if name != self.note.name or text != self.note.text or comment != self.note.comment: with undoBlockWithoutSideBar(): if name != self.note.name: self.note.rename(name) self.note.text = text self.note.comment = comment except Exception as es: # need to immediately set back to stop error on loseFocus which also fires editingFinished self.lineEdit1.setText(self.note.name) showWarning('', str(es)) self.noWidget.select(self.note.pid) self.setBlankingAllNotifiers(False) def _reject(self): """ Closes the note editor ignoring all changes. """ self._closeModule() def _selectionPulldownCallback(self, item): """ Notifier Callback for selecting Note from the pull down menu """ self.note = self.project.getByPid(item) if self.note is not None: self._update(self.note) else: self.noteWidget.hide() def _updateCallback(self, data): """ Notifier callback for updating module when a Note is create/delete/rename/change """ thisNoteList = getattr(data[Notifier.THEOBJECT], self.attributeName) # get the notesList if self.note in thisNoteList: self._update(self.note) else: self.noteWidget.hide() def _update(self, note): """ Update the Note widgets """ self.textBox.setText(note.text) self.lineEdit1.setText(note.name) self.lineEditComment.setText(note.comment if note.comment else '') self.noteWidget.show() self.show() def _deleteNote(self): """ Delete the current note with the delete button """ if self.note: self.note.delete()