2""" @brief  Windows that display Genomes, Labyrinth and Social networks 
for Evolife.
 
    5    - Genome_window:  An image area that displays binary genomes
 
    6    - Network_window: A drawing area that displays social links
 
    7    - Field_window:   A drawing area that displays agent movements
 
   10#============================================================================# 
   11# EVOLIFE  http://evolife.telecom-paris.fr             Jean-Louis Dessalles   
   27if __name__ == 
'__main__':  sys.path.append(
'../..')  
 
   30    from PyQt5 
import QtGui, QtCore, QtWidgets
 
   32    grabWidget = QtWidgets.QWidget.grab
 
   34    from PyQt4 
import QtGui, QtCore
 
   35    from PyQt4 
import QtGui 
as QtWidgets
 
   36    grabWidget = QtGui.QPixmap.grabWidget
 
   53    """ returned information after click  
   55    def __init__(self, EmittingClass, EventType, Info):
 
   69    """ Standard canvas plus resizing capabilities 
   71    def __init__(self, AreaType=Image_Area, parent=None, image=None, width=400, height=300, zoom=1):
 
   72        """ Defining View: a window (QGraphicsView) that contains a plot area (QGraphicsScene) 
   74        QtWidgets.QGraphicsView.__init__(self, parent)     
   78            self.setScene(self.
Area)
 
   79            self.resize(self.
Area.W, self.
Area.H)
 
   80        else:   
error(
"AreaView", 
"No area to display")
 
   81        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 
 
   82        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
 
   86        """ calls Qt's paintEvent 
   88        QtWidgets.QGraphicsView.paintEvent(self,e) 
   91        """ calls Qt's resizeEvent 
   93        if self.
Area is not None:
 
   94            self.
Area.resize(e.size().width(), e.size().height())
 
   95        QtWidgets.QGraphicsView.resizeEvent(self,e)
 
  102    def photo(self, Name, FrameNumber=-1, outputDir='.', extension='png'):
 
  103        """ takes a snapshot and saves it to a new file 
  112        picture.save(os.path.join(outputDir, ImFileName + 
'.' + extension))
 
  116        """ Does nothing here. To be overloaded 
  127    """ An Active_frame reacts to basic keyboard control 
  129    def __init__(self, AreaType=None, parent=None, control=None, image=None, width=400, height=300, zoom=1):
 
  130        """ Creates a window (AreaView) with the appropriate AreaType (Draw_Area, Ground...) 
  132        if AreaType 
is not None:
 
  134            AreaView.__init__(self, AreaType=AreaType, parent=parent, image=image, 
 
  135                                width=width, height=height, zoom=zoom)  
 
  138            QtWidgets.QWidget.__init__(self, parent=parent)
 
  147        """ Definition of keyboard shortcuts 
  149        if e.key() 
in [QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape]:
 
  151        elif e.key() == QtCore.Qt.Key_M:
 
  159        """ puts the window on top of display 
  162        self.activateWindow() 
  173    """ Satellite windows are floating windows with zooming abilities 
  175    def __init__(self, AreaType=None, control=None, Wtitle='', image=None, width=400, height=300, zoom=1):
 
  176        """ calls the parents' constructor with the appropriate area (QGraphicsScene) type 
  178        Active_Frame.__init__(self, AreaType=AreaType, control=control, image=image,  
  179                        width=width, height=height, zoom=zoom)   
  181        self.setWindowTitle(Wtitle) 
  184        self.setMinimumSize(int(180*zoom), int(180*zoom))    
  186        self.
Zoom(ZoomFactor=zoom)
 
  191        if len(geometry) == 1:  
 
  196            geometry = list(geometry)   
 
  198            geometry += [0, 0][:4-len(geometry)]
 
  200            height, width, posy, posx = geometry
 
  201        if posx 
and posy:   self.setGeometry(posx, posy, width, height)
 
  202        else:   self.resize(width, height)  
 
  206        """ adds resizing keys (+ and -) to Active_Frame's shortcuts 
  208        Active_Frame.keyPressEvent(self,e) 
  210        if e.key() 
in [QtCore.Qt.Key_Z, QtCore.Qt.Key_Minus]:
 
  212        if e.key() 
in [QtCore.Qt.Key_Plus]:
 
  216        """ display an image, with possible resizing of the window 
  218        if Image 
is None or not os.path.exists(str(Image)):   
return 
  219        self.
AreaArea.Board = QtGui.QPixmap(Image) 
 
  222            newWidth = min(800, self.
AreaArea.Board.width())
 
  223            newHeight = min(600, self.
AreaArea.Board.height())
 
  224            try:    zoomFactor = min(float(newWidth) / self.
AreaArea.Board.width(), float(newHeight) / self.
AreaArea.Board.height())
 
  225            except  ZeroDivisionError:  zoomFactor = 1
 
  226            self.resize(int(self.
AreaArea.Board.width()*zoomFactor), int(self.
AreaArea.Board.height()*zoomFactor))
 
  229        self.setWindowTitle(self.
Title + 
' - ' + Image)
 
  231    def Zoom(self, ZoomFactor=1.1):
 
  232        """ increase the window's size 
  234        self.resize(int(self.width()*ZoomFactor),int(self.height()*ZoomFactor)) 
  237        """ decrease the window's size 
  239        self.resize(int(self.width()*DeZoomFactor),int(self.height()*DeZoomFactor)) 
  242        """ destroys the window 
  247            except Exception 
as Msg: print(Msg)
 
  255    """ Image_window: Merely contains an image area 
  257    def __init__(self, control=None, Wtitle='', outputDir='.'):
 
  258        """ calls Satellite_window's constructor 
  264        Satellite_window.__init__(self, Draw_Area, control=control, Wtitle=
'Images', width=self.
W, height=self.
H)
 
  265        self.
AreaArea.set_margins(1, 1, 1, 1)
 
  273    """ Genome_window: An image area that displays binary genomes 
  275    def __init__(self, control=None, image=None, genome=None, gene_pattern=None, outputDir='.', zoom=1):
 
  276        """ calls Satellite_window's constructor 
  277            and performs first display
 
  284        if genome 
is not None:
 
  286            self.
W = len(genome[0])
 
  288        Satellite_window.__init__(self, Draw_Area, control=control, Wtitle=
'Genomes', image=image, width=self.
W, height=self.
H, zoom=zoom)
 
  290        self.
AreaArea.set_margins(1, 1, 1, 1)
 
  291        if genome 
is not None:
 
  296        """ draws separation between genes 
  299        gridPen = QtGui.QPen()
 
  300        gridPen.setColor(QtGui.QColor(
'#FF0000'))   
 
  307            HPos += (pattern.index(G) * self.
AreaArea.W)/self.
W 
  310            del pattern[:pattern.index(G)]
 
  313    def genome_display(self, genome=None, gene_pattern=(), Photo=0, CurrentFrame=-1, Prefix=
''):
 
  314        """ genome gives, for each individual, the sequence of binary nucleotides  
  315            gene_pattern is a binary flag to signal gene alternation
 
  320            if Prefix == 
'':    Prefix = 
'___Genome_' 
  321            PhotoName = self.
photo(Prefix, CurrentFrame, outputDir=self.
OutputDir)
 
  323        if genome 
is None or len(genome) == 0:  
return '' 
  324        if gene_pattern 
is not None:    self.
gene_pattern = gene_pattern
 
  326        self.
W = len(genome[0])
 
  332        GenomeImg = QtGui.QImage(self.
W, self.
H, QtGui.QImage.Format_Mono)
 
  334        for line 
in range(self.
H):
 
  335            for pixel 
in range(self.
W):
 
  336                if genome[line][pixel]:
 
  337                    GenomeImg.setPixel(pixel,line, 1)
 
  339                    GenomeImg.setPixel(pixel,line, 0)
 
  351    """ Network_window: A drawing area that displays social links 
  352        The population is displayed twice, on two horizontal axes.
 
  353        Social links are displayed 
as ascending vectors 
from one individual
 
  354        on the bottom line to another on the upper line.
 
  356    def __init__(self, control, image=None, outputDir='.', width=540, height=200, zoom=1): 
  357        """ calls Satellite_window's constructor 
  359        Satellite_window.__init__(self, Draw_Area, control=control, Wtitle='Social network',
 
  360                                  width=width, height=height, image=image, zoom=zoom)
 
  364        self.
AreaArea.set_margins(20,20,20,20)
 
  370        """ Draws two horizontal axes; each axis represents the population; 
  371            social links are shown as vectors going 
from the lower line
 
  380        """ Social links are displayed as ascending vectors from one individual 
  381            on the bottom line to another on the upper line. 
  385            PhotoName = self.Dump_network(self.
friends, CurrentFrame, Prefix=Prefix)
 
  388        if not network: 
return '' 
  389        positions = dict([L 
for L 
in Layout 
if len(L) == 2 
and type(L[1]) == tuple]) 
 
  390        if positions == {}: 
return None 
  392        self.
AreaArea.scaleX = max(self.
AreaArea.scaleX, max([positions[individual][0] 
for individual 
in positions]))
 
  395        for individual 
in self.
friends:
 
  396            if len(self.
friends[individual]):
 
  397                bestFriend = self.
friends[individual][0]
 
  398                self.
AreaArea.move(6, (positions[individual][0],0))
 
  401                except KeyError:    
warning(
'friend has vanished', bestFriend)
 
  410        """ stores social links into a matrix written into a file 
  412        if Prefix == 
'':    Prefix = 
'___Network_' 
  413        PhotoName = self.photo(Prefix, CurrentFrame, outputDir=self.OutputDir)
 
  414        MatrixFileName = os.path.join(self.OutputDir, Prefix + 
Nb2A0(self.FrameNumber) + 
'.txt')
 
  415        MatrixFile = open(MatrixFileName,
'w')
 
  416        for Individual 
in friends:
 
  417            MatrixFile.write(str(Individual))
 
  418            for F 
in friends[Individual]:
 
  419                MatrixFile.write(
'\t%s' % F)
 
  420            MatrixFile.write(
'\n')
 
  430    """ Field: A 2D widget that displays agent movements 
  433    def __init__(self, control=None, Wtitle='', image=None, outputDir='.', width=400, height=300, zoom=1):
 
  435            Satellite_window.__init__(self, Ground, control=control, Wtitle=Wtitle, image=image, zoom=zoom)
 
  438            Satellite_window.__init__(self, Ground, control=control, Wtitle=Wtitle, image=
None, width=width, height=height, zoom=zoom)
 
  447    def Field_display(self, Layout=None, Photo=0, CurrentFrame=-1, Ongoing=False, Prefix=''):
 
  448        """ displays agents at indicated positions 
  449            If Ongoing is false, agents that are 
not given positions are removed
 
  451             Layout may come 
with two syntaxes:
 
  452                - ((Agent1Id, Coordinates1), (Agent2Id, Coordinates2), ...)
 
  453                - (Coordinates1, Coordinates2, ...)
 
  454             The first format allows to move 
and erase agents
 
  455             The second format 
is merely used to draw permanent blobs 
and lines
 
  456             Coordinates have the following form:
 
  457                (x, y, colour, size, ToX, ToY, segmentColour, segmentThickness, 
'shape=<form>')
 
  458                (shorter tuples are automatically continued 
with default values - 
'shape=...' can be inserted anywhere)
 
  459             The effect 
is that an object of size 
'size' is drawn at location (x,y) (your coordinates, 
not pixels)
 
  460             and a segment starting 
from that blob 
is drawn to (ToX, ToY) (
if these values are given)
 
  461             If you change the coordinates of an agent 
in the next call, it will be moved.
 
  462             'size' is in pixels 
and is not resized 
in case of zoom. However, 
if negative, it 
is interpreted 
in your coordinates 
and it will be resized/
 
  463             'size' may be a fractional number (float). It 
is then understood 
as a fraction of the window size.
 
  464             The value assigned to 
'shape' in the string 
'shape=...' can be 
'ellipse' (=default) 
or 'rectangle' or 
  465             any image. If it 
is an image, 
'colour' is interpreted 
as an angle. The image 
is scaled to fit 
'size' (but aspect ratio 
is preserved)
 
  467             These two forms of vectors can be used to draw 
in two windows:  
'Trajectories' and 'Field'.
 
  468             Use the order: record(vector, Window=<
'Field'|
'Trajectories'>)
 
  469             or:                record([vectors], Window=<
'Field'|
'Trajectories'>)
 
  470             'Field' is default. The latter order 
is used to send a list of vectors.
 
  472             The 
'Field' window comes 
in two modes, 
'F' and 'R' (see option F 
and R at Evolife
's start) 
  473                - In the 'F' mode, all agents should be given positions at each call.
 
  474                  Missing agents are destroyed 
from display.
 
  475                - In the 
'R'  (
'Region') mode, you may indicates positions only 
for relevant agents
 
  476                  To destroy an agent 
from display, give a negative value to its colour.
 
  481            if Prefix == 
'':    Prefix = 
'___Field_' 
  482            PhotoName = self.
photo(Prefix, CurrentFrame, outputDir=self.
OutputDir)
 
  484        if not Layout:   
return '' 
  488        AgentLayout = dict([L 
for L 
in Layout 
if len(L) == 2 
and type(L[1]) == tuple]) 
 
  489        DrawingLayout = [L 
for L 
in Layout 
if len(L) != 2 
or type(L[1]) != tuple]
 
  493            if DrawingLayout == [
'erase']:  
 
  499                for Pos 
in DrawingLayout:
 
  508            if not Ongoing: self.
AreaArea.remove_absent(AgentLayout.keys())
 
  509            for Individual 
in AgentLayout:
 
  510                self.
AreaArea.move_agent(Individual, AgentLayout[Individual])
 
  524        """ rescales widget if items land outside (only for max values, not for negative ones) 
  526        newScaleX = max(self.AreaArea.scaleX, max([pos[0] for pos 
in Layout]))
 
  527        newScaleY = max(self.
AreaArea.scaleY, max([pos[1] 
for pos 
in Layout]))
 
  529            self.
AreaArea.scaleX, self.
AreaArea.scaleY = ceil(newScaleX), ceil(newScaleY)
 
  537    """ Synonymous for Field_window 
  542class Help_window(QtWidgets.QTextBrowser):
 
  543    """ Displays a text file supposed to provide help 
  546        """ calls Qt's QTextBrowser 
  548        QtWidgets.QTextBrowser.__init__(self) 
  549        self.setWindowTitle(Wtitle) 
  553        """ Definition of keyboard shortcuts 
  555        if e.key() 
in [QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape]:
 
  557        elif e.key() == QtCore.Qt.Key_M:
 
  565        self.setPlainText(open(HelpFilename).read()) 
  566        self.setOverwriteMode(False)
 
  570        """ puts help widget in front of display 
  573        self.activateWindow() 
  576        """ destroys help widget 
  586    """ displays legend for curves  
  589        """ legend window is copied from help window 
  591        Help_window.__init__(self, Control=Control, Wtitle=Wtitle) 
  594        """ Legend comes as a list of couples (ColourName, Meaning)  
  597        self.setOverwriteMode(
False)
 
  600        self.insertHtml(
'<P><u>Curves</u>:<br>')
 
  602            for (CID, Ccolour, Ccolourname, CName, CLegend) 
in Legend:
 
  605                    self.insertHtml(
'<br><b><font color="black">%s:</font></b>' % (Ccolour))
 
  607                    self.insertHtml(
'<br><b><font color="%s">%s:</font></b>' % (Ccolour, Ccolourname))
 
  608                self.insertPlainText(
'\t')
 
  609                self.insertHtml(
'%s' % CLegend)
 
  611            error(
"Curves: unknown Curve ID")
 
  614            self.insertPlainText(
'\n')
 
  615            self.insertHtml(
'%s' % Comments)
 
  616        self.insertPlainText(
'\n=============\n( [Esc] to close )')
 
  619        text = self.document().toPlainText()    
 
  620        font = self.document().defaultFont()    
 
  621        fontMetrics = QtGui.QFontMetrics(font)      
 
  622        textSize = fontMetrics.size(0, text)
 
  623        textWidth = textSize.width() + 30       
 
  624        textHeight = textSize.height() + 30     
 
  625        self.setMinimumSize(textWidth, textHeight)  
 
  628        self.moveCursor(QtGui.QTextCursor.Start)
 
  629        self.ensureCursorVisible() ;
 
  641if __name__ == 
"__main__":
 
  646__author__ = 
'Dessalles' 
An Active_frame reacts to basic keyboard control.
def EventInterpreter(self, Event)
Does nothing here.
def Raise(self)
puts the window on top of display
def __init__(self, AreaType=None, parent=None, control=None, image=None, width=400, height=300, zoom=1)
Creates a window (AreaView) with the appropriate AreaType (Draw_Area, Ground...)
def keyPressEvent(self, e)
Definition of keyboard shortcuts.
Standard canvas plus resizing capabilities.
def updateScene(self, L)
unused
def photo(self, Name, FrameNumber=-1, outputDir='.', extension='png')
takes a snapshot and saves it to a new file
def resizeEvent(self, e)
calls Qt's resizeEvent
def EventInterpreter(self, Event)
Does nothing here.
def paintEvent(self, e)
calls Qt's paintEvent
def __init__(self, AreaType=Image_Area, parent=None, image=None, width=400, height=300, zoom=1)
Defining View: a window (QGraphicsView) that contains a plot area (QGraphicsScene)
A graphic area that displays moving agents #.
def __init__(self, control=None, Wtitle='', image=None, outputDir='.', width=400, height=300, zoom=1)
calls the parents' constructor with the appropriate area (QGraphicsScene) type
def erase(self)
calls Area erase
def adaptscale(self, Layout)
rescales widget if items land outside (only for max values, not for negative ones)
def Field_display(self, Layout=None, Photo=0, CurrentFrame=-1, Ongoing=False, Prefix='')
displays agents at indicated positions If Ongoing is false, agents that are not given positions are r...
Genome_window: An image area that displays binary genomes.
def genome_display(self, genome=None, gene_pattern=(), Photo=0, CurrentFrame=-1, Prefix='')
genome gives, for each individual, the sequence of binary nucleotides gene_pattern is a binary flag t...
def axes(self)
draws separation between genes
def __init__(self, control=None, image=None, genome=None, gene_pattern=None, outputDir='.', zoom=1)
calls Satellite_window's constructor and performs first display
Displays a text file supposed to provide help.
def keyPressEvent(self, e)
Definition of keyboard shortcuts.
def __init__(self, Control=None, Wtitle='Help')
calls Qt's QTextBrowser
def display(self, HelpFilename)
show help window
def closeEvent(self, event)
destroys help widget
def Raise(self)
puts help widget in front of display
Image_window: Merely contains an image area.
def __init__(self, control=None, Wtitle='', outputDir='.')
calls Satellite_window's constructor
displays legend for curves
def __init__(self, Control=None, Wtitle='Legend')
legend window is copied from help window
def display(self, Legend, Comments='')
Legend comes as a list of couples (ColourName, Meaning)
Network_window: A drawing area that displays social links The population is displayed twice,...
def Network_display(self, Layout, network=None, Photo=0, CurrentFrame=-1, Prefix='')
Social links are displayed as ascending vectors from one individual on the bottom line to another on ...
def axes(self)
Draws two horizontal axes; each axis represents the population; social links are shown as vectors goi...
Satellite windows are floating windows with zooming abilities.
def keyPressEvent(self, e)
adds resizing keys (+ and -) to Active_Frame's shortcuts
def Zoom(self, ZoomFactor=1.1)
increase the window's size
def DeZoom(self, DeZoomFactor=0.91)
decrease the window's size
def dimension(self, *geometry)
sets dimensions
def __init__(self, AreaType=None, control=None, Wtitle='', image=None, width=400, height=300, zoom=1)
calls the parents' constructor with the appropriate area (QGraphicsScene) type
def image_display(self, Image, windowResize=True)
display an image, with possible resizing of the window
def closeEvent(self, event)
destroys the window
Synonymous for Field_window.
def __init__(self, EmittingClass, EventType, Info)
def Dump_network(self, friends, CurrentFrame=-1, Prefix='')
if len(self.friends[friend]) and individual == self.friends[friend][0]: self.plot(6,...
def SWDestroyed(self, SW)
A satellite window has been destroyed - removes it from the list.
Windows that display Curves or images.