Example CCPN Graphical Interface
General Principles
This final section goes step-by-step through a real program with a graphical interface that has been written to extend CcpNmr Analysis. This will use some of the graphical widgets discussed previously, and will connect them to real manipulations of the CCPN data model, via the Python API. When undertaking such a tasks there are a few guiding principles to be mindful of:
Data Model Event Notification
In order to assist with the design of user interfaces, CCPN has an inbuilt system to give notification of when objects in the CCPN data model change. In this way the graphical interface can be kept up-to-date with the current state of the underlying data; an imperative for good design. The notification system is fairly easy to use; one simply makes a registration in the system that links a particular user-defined function (e.g. a visual update) with a data model call (e.g. deleting an object, setting its name etc.). With this system you can separate the functions within your program into those that change the state of the data and those that reflect the state graphically. Thus you rarely need to explicitly manage the links between the two.
Update Funnelling
This is
GUI Example: A RADAR reader
A
import os
from memops.gui.Entry import Entry
from memops.gui.FileSelect import FileType
from memops.gui.FileSelectPopup import FileSelectPopup
from memops.gui.Label import Label
from memops.gui.PulldownList import PulldownList
from memops.gui.ButtonList import ButtonList, UtilityButtonList
from memops.gui.Button import Button
from memops.gui.LabelFrame import LabelFrame
from memops.gui.MessageReporter import showWarning, showOkCancel, showYesNo, showInfo
from ccpnmr.analysis.popups.BasePopup import BasePopup
from ccpnmr.analysis.core.PeakBasic import pickPeak
from ccpnmr.analysis.core.ExperimentBasic import getDataDimIsotopes, getOnebondDataDims
from ccpnmr.analysis.core.ExperimentBasic import getThroughSpacePeakLists
from ccpnmr.analysis.core.Util import getAnalysisPeakList
TEMP_LIST_NAME = '__RadarSuperimposeTemp'
READABLE = os.R_OK
def radarSuperimposeMacro(argServer):
popup = RadarSuperimposePopup(argServer.parent)
popup.open()
A
class RadarSuperimposePopup(BasePopup):
def __init__(self, parent, *args, **kw):
self.spectrum = None
self.dir = '.'
BasePopup.__init__(self, parent, *args, **kw)
A
def body(self, guiFrame):
frame = LabelFrame(guiFrame, text='Options', grid=(0,0))
label = Label(frame, text='Destination Spectrum:', grid=(0,0))
self.specPulldown = PulldownList(frame, callback=self.changeSpec, grid=(0,1))
label = Label(frame, text='Peaklist File:', grid=(1,0))
self.peakListEntry = Entry(frame, text='', width=64, grid=(1,1))
button = Button(frame, command=self.importPeakFile,
grid=(1,2), text='Choose File')
label = Label(frame, text='Resonance file:', grid=(2,0))
self.resonanceFileEntry = Entry(frame, text='', width=64, grid=(2,1))
button = Button(frame, command=self.importResonanceFile,
grid=(2,2), text='Choose File')
texts = ['Make Peak List','Remove Peak List']
commands = [self.makePeakList, self.removePeakList]
buttons = UtilityButtonList(guiFrame, texts=texts,
commands=commands, grid=(1,0))
self.updateSpecPulldown()
# Notifiers
A
def updateSpecPulldown(self):
names = []
index = 0
spectra = []
peakLists = getThroughSpacePeakLists(self.project)
for peakList in peakLists:
spectrum = peakList.dataSource
if spectrum not in spectra:
names.append('%s:%s' % (spectrum.experiment.name, spectrum.name))
spectra.append(spectrum)
if spectra:
if self.spectrum not in spectra:
self.spectrum = spectra[0]
index = spectra.index(self.spectrum)
else:
self.spectrum = None
self.specPulldown.setup(names, spectra, index)
A
def changeSpec(self, spectrum):
if spectrum is not self.spectrum:
self.spectrum = spectrum
A
def importResonanceFile(self):
fileTypes = [ FileType('XEasy', ['*.resonances']), FileType('All', ['*']) ]
fileSelectPopup = FileSelectPopup(self, file_types=fileTypes, directory=self.dir,
title='Import Xeasy Resonance file', dismiss_text='Cancel',
selected_file_must_exist=True, multiSelect=False,)
file = fileSelectPopup.getFile()
self.dir = fileSelectPopup.getDirectory()
self.resonanceFileEntry.set(file)
A
def importPeakFile(self):
fileTypes = [ FileType('XEasy', ['*.peaks']), FileType('All', ['*'])]
fileSelectPopup = FileSelectPopup(self, file_types=fileTypes, directory=self.dir,
title='Import XEasy peak file', dismiss_text='Cancel',
selected_file_must_exist=True, multiSelect=False,)
file = fileSelectPopup.getFile()
self.dir = fileSelectPopup.getDirectory()
self.peakListEntry.set(file)
A
def warning(self, msg, title='Failure'):
showWarning(title, msg, parent=self)
A
def makePeakList(self):
if not self.spectrum:
self.warning('No spectrum')
return
peakFile = self.peakListEntry.get()
resonanceFile = self.resonanceFileEntry.get()
if not peakFile:
self.warning('No peak file specified')
return
if not resonanceFile:
self.warning('No resonance file specified')
return
if not os.path.exists(peakFile):
self.warning('Specified peak file does not exist')
return
if not os.path.exists(resonanceFile):
self.warning('Specified resonance file does not exist',)
return
if not os.access(peakFile, READABLE):
self.warning('Specified peak file not readable')
return
if not os.access(resonanceFile, READABLE):
self.warning('Specified resonance file not readable')
return
A
if peakList:
peaks = peakList.peaks
if peaks:
msg = 'Destination peak list already contains %d peaks.' % (len(peaks))
msg += ' Remove these first?'
if showYesNo('Query', msg, parent=self):
for peak in peaks:
peak.delete()
else:
peakList = self.spectrum.newPeakList(isSimulated=True,
name=TEMP_LIST_NAME)
analysisPeakList = getAnalysisPeakList(peakList)
analysisPeakList.symbolStyle = '+'
analysisPeakList.symbolColor = '#FF0000'
analysisPeakList.textColor = '#BB0000'
A
resonanceDict = readResonanceData(resonanceFile)
nDim, dTypes, peakData = readPeakData(peakFile)
A
# Work out dim mapping
xIsotope = None
xAtom = None
for atom, isotope in (('N', '15N'), ('C', '13C')):
if atom in dTypes:
xIsotope = isotope
xAtom = atom
dataDims = self.spectrum.sortedDataDims()
boundDims = {}
for dataDim0, dataDim1 in getOnebondDataDims(self.spectrum):
boundDims[dataDim0] = True
boundDims[dataDim1] = True
dimCols = []
if xAtom:
for dataDim in dataDims:
isotopes = getDataDimIsotopes(dataDim)
if '1H' in isotopes:
if boundDims.get(dataDim):
col = dTypes.index('H'+xAtom)
else:
col = dTypes.index('H')
elif xIsotope in isotopes:
col = dTypes.index(xAtom)
dimCols.append(col)
else:
# 2D NOESY - symmetric, can flip if wrong
dimCols = [0,1]
A
# Write peaks
nDim = len(dataDims)
dims = range(nDim)
c = 0
for num, ppms, inten, assign, dist in peakData:
position = [None] * nDim
for i in dims:
position[i] = ppms[dimCols[i]]
peak = pickPeak(peakList, position, unit='ppm')
c += 1
labels = ['-'] * nDim
for i, peakDim in enumerate(peak.sortedPeakDims()):
j = assign[dimCols[i]]
if j: # Not zero either
resonanceInfo = resonanceDict.get(j)
if resonanceInfo:
resNum, atom, ppm, sd = resonanceInfo
labels[i] = '%d%s' % (resNum, atom)
peak.annotation = dist + ':' + ','.join(labels)
showInfo('Done', 'Made %d peaks' % c, parent=self)
A
def removePeakList(self):
if self.spectrum:
peakList = self.spectrum.findFirstPeakList(name=TEMP_LIST_NAME,
isSimulated=True)
if not peakList:
self.warning('No temporary peak list found in this spectrum')
return
name = '%s:%s' % (self.spectrum.name, self.spectrum.experiment.name)
msg = 'Really remove temporary peak list in spectrum %s?' % name
if showOkCancel('Confirm', msg, parent=self):
peakList.delete()
A
def readResonanceData(resonanceFile):
resonanceDict = {}
fileObj = open(resonanceFile, 'r')
line = fileObj.readline()
while line:
array = line.split()
if (len(array) == 5) and (array[0][0] != '#'):
num, ppm, sd, atom, resNum = array
resonanceDict[int(num)] = (int(resNum), atom, ppm, sd)
line = fileObj.readline()
fileObj.close()
return resonanceDict
A
def readPeakData(peakFile):
nDim = 3
dTypes = [None] * 4
peakData = []
fileObj = open(peakFile, 'r')
line = fileObj.readline()
while line:
array = line.split()
if array:
if array[0][0] == '#':
if 'dimensions' in line.lower():
nDim = int(array[-1])
elif 'INAME' in line.upper():
dTypes[int(array[-2])-1] = array[-1]
elif len(array) == 2*nDim+11:
num = int(array[0])
ppms = [float(x) for x in array[1:nDim+1]]
inten = [float(x) for x in array[nDim+3:nDim+4]]
assign = [int(x) for x in array[nDim+7:nDim+nDim+7]]
dist = array[-1]
peakData.append((num, ppms, inten, assign, dist))
line = fileObj.readline()
fileObj.close()
while dTypes[-1] is None:
dTypes.pop()
return nDim, dTypes, peakData