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