Commit 4bd5e5cb authored by Yori Fournier's avatar Yori Fournier
Browse files

Merge branch '5-feature-color-the-text-live-while-writting' into 'master'

Resolve "FEATURE: Color the text live while writting"

Closes #5

See merge request !3
parents 3c207b77 9c52f8b3
......@@ -12,16 +12,30 @@ except:
QApplication = QtGui.QApplication
QWidget = QtGui.QWidget
QVBoxLayout = QtGui.QVBoxLayout
QGridLayout = QtGui.QGridLayout
QPushButton = QtGui.QPushButton
QMainWindow = QtGui.QMainWindow
QTabWidget = QtGui.QTabWidget
QFont = QtGui.QFont
QLabel = QtGui.QLabel
QLineEdit = QtGui.QLineEdit
QTextEdit = QtGui.QTextEdit
QPlainTextEdit = QtGui.QPlainTextEdit
# MatPlotLib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as QCanvas
# MyPlotLib
from myplotlib import MyAxes, MyFig, MyData
from myplotlib.test import FigTest1, readStupidData, readStupidData2
from .mplDesign import MyDesign, AxDesign, FigDesign
# Local Import
from .pythonHighlighter import PythonHighlighter
from .qMyFigContainer import QMyFigContainer
from .qTabMyAxes import QTabMyAxes
from .qTabMyFig import QTabMyFig
from .qMyDesignTabs import QMyDesignTabs
from .qMyDesignWidget import QMyDesignWidget
from .qMyDesignGui import QMyDesignGui
from . import MyAxes
from . import MyFig
DEFAULT_DECLARE_KEYWORDS = '''
self.keywords = {}
'''
DEFAULT_FORMAT_RAWDATA = '''
self.data = {'xdata': [-5, 2],
'ydata': [-2, 4]}
'''
DEFAULT_PLOTTING = '''
self.plot(self.data['xdata'], self.data['ydata'])
'''
_DEFAULT_DESIGN = {'declareKeywords': DEFAULT_DECLARE_KEYWORDS,
'formatRawData': DEFAULT_FORMAT_RAWDATA,
'plotting': DEFAULT_PLOTTING}
DEFAULT_ADDAXES = '''
myAxesDesign = self.design.get('MyAxes', _DEFAULT_DESIGN)
ratio = 6./8.
frame = [0.2, 0.15, 0.6, 0.7]
self.add_axes(AxDesign(self, ratio, frame, design=myAxesDesign), 'ax1')
'''
class MyDesign(dict):
def __init__(self):
dict.__init__(self)
self.figClassName = None
self.axesClassName = []
class FigDesign(MyFig):
FIGSIZE = (8., 6.)
def __init__(self, rawdata, design, *args, **kwargs):
self.design = design
MyFig.__init__(self, rawdata, *args, **kwargs)
def addAxes(self):
figDesign = self.design.get(self.design.figClassName, DEFAULT_ADDAXES)
if 'addAxes' in figDesign:
exec(''.join(str(figDesign['addAxes'])))
else:
pass
class AxDesign(MyAxes):
def __init__(self, fig, ratio, frameRect, design=_DEFAULT_DESIGN, *args, **kwargs):
self.design = design
MyAxes.__init__(self, fig, ratio, frameRect, *args, **kwargs)
def declareKeywords(self):
if 'declareKeywords' in self.design:
exec(''.join(str(self.design['declareKeywords'])))
else:
pass
def plotting(self):
exec(''.join(str(self.design['plotting'])))
return(True)
def formatRawData(self, rawdata):
exec(''.join(str(self.design['formatRawData'])))
return(True)
# syntax.py
from . import sys
from PyQt4.QtCore import QRegExp
from PyQt4.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter
def format(color, style=''):
"""Return a QTextCharFormat with the given attributes.
"""
_color = QColor()
_color.setNamedColor(color)
_format = QTextCharFormat()
_format.setForeground(_color)
if 'bold' in style:
_format.setFontWeight(QFont.Bold)
if 'italic' in style:
_format.setFontItalic(True)
return _format
# Syntax styles that can be shared by all languages
STYLES = {
'keyword': format('blue'),
'operator': format('black'),
'brace': format('darkGray'),
'defclass': format('green', 'bold'),
'string': format('magenta'),
'string2': format('darkMagenta'),
'comment': format('red'),
'self': format('green'),
'numbers': format('brown'),
}
class PythonHighlighter (QSyntaxHighlighter):
"""Syntax highlighter for the Python language.
"""
# Python keywords
keywords = [
'and', 'assert', 'break', 'class', 'continue', 'def',
'del', 'elif', 'else', 'except', 'exec', 'finally',
'for', 'from', 'global', 'if', 'import', 'in',
'is', 'lambda', 'not', 'or', 'pass', 'print',
'raise', 'return', 'try', 'while', 'yield',
'None', 'True', 'False',
]
# Python operators
operators = [
'=',
# Comparison
'==', '!=', '<', '<=', '>', '>=',
# Arithmetic
'\+', '-', '\*', '/', '//', '\%', '\*\*',
# In-place
'\+=', '-=', '\*=', '/=', '\%=',
# Bitwise
'\^', '\|', '\&', '\~', '>>', '<<',
]
# Python braces
braces = [
'\{', '\}', '\(', '\)', '\[', '\]',
]
def __init__(self, document):
QSyntaxHighlighter.__init__(self, document)
# Multi-line strings (expression, flag, style)
# FIXME: The triple-quotes in these two lines will mess up the
# syntax highlighting from this point onward
self.tri_single = (QRegExp("'''"), 1, STYLES['string2'])
self.tri_double = (QRegExp('"""'), 2, STYLES['string2'])
rules = []
# Keyword, operator, and brace rules
rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
for w in PythonHighlighter.keywords]
rules += [(r'%s' % o, 0, STYLES['operator'])
for o in PythonHighlighter.operators]
rules += [(r'%s' % b, 0, STYLES['brace'])
for b in PythonHighlighter.braces]
# All other rules
rules += [
# 'self'
(r'\bself\b', 0, STYLES['self']),
# Double-quoted string, possibly containing escape sequences
(r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
# Single-quoted string, possibly containing escape sequences
(r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
# 'def' followed by an identifier
(r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
# 'class' followed by an identifier
(r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
# From '#' until a newline
(r'#[^\n]*', 0, STYLES['comment']),
# Numeric literals
(r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']),
]
# Build a QRegExp for each pattern
self.rules = [(QRegExp(pat), index, fmt)
for (pat, index, fmt) in rules]
def highlightBlock(self, text):
"""Apply syntax highlighting to the given block of text.
"""
# Do other syntax formatting
for expression, nth, format in self.rules:
index = expression.indexIn(text, 0)
while index >= 0:
# We actually want the index of the nth match
index = expression.pos(nth)
length = len(expression.cap(nth))
self.setFormat(index, length, format)
index = expression.indexIn(text, index + length)
self.setCurrentBlockState(0)
# Do multi-line strings
in_multiline = self.match_multiline(text, *self.tri_single)
if not in_multiline:
in_multiline = self.match_multiline(text, *self.tri_double)
def match_multiline(self, text, delimiter, in_state, style):
"""Do highlighting of multi-line strings. ``delimiter`` should be a
``QRegExp`` for triple-single-quotes or triple-double-quotes, and
``in_state`` should be a unique integer to represent the corresponding
state changes when inside those strings. Returns True if we're still
inside a multi-line string when this function is finished.
"""
# If inside triple-single quotes, start at 0
if self.previousBlockState() == in_state:
start = 0
add = 0
# Otherwise, look for the delimiter on this line
else:
start = delimiter.indexIn(text)
# Move past this match
add = delimiter.matchedLength()
# As long as there's a delimiter match on this line...
while start >= 0:
# Look for the ending delimiter
end = delimiter.indexIn(text, start + add)
# Ending delimiter on this line?
if end >= add:
length = end - start + add + delimiter.matchedLength()
self.setCurrentBlockState(0)
# No; multi-line string
else:
self.setCurrentBlockState(in_state)
length = text.length() - start + add
# Apply formatting
self.setFormat(start, length, style)
# Look for the next match
start = delimiter.indexIn(text, start + length)
# Return True if still inside a multi-line string, False otherwise
if self.currentBlockState() == in_state:
return True
else:
return False
......@@ -9,15 +9,8 @@ class QMyDesignGui(QMainWindow):
super(QMyDesignGui, self).__init__(parent)
self.statusBar() # an ugly wrapper to create the statusBar
self.setCentralWidget(QMyDesignWidget(self))
self.centralWidget().refreshed.connect(self.refreshed)
self.resize(self.layout().sizeHint()) # For QMainWindow layout is a function that returns the QLayout object.
self.setWindowTitle('My Design Gui')
self.show()
def refreshed(self):
self.resize(self.layout().sizeHint()) # For QMainWindow layout is a function that returns the QLayout object.
self.statusBar().showMessage("> status: Figure has been refreshed.")
from . import QTabWidget
from . import QWidget
from . import QTabMyAxes
from . import QTabMyFig
from . import MyDesign
class QMyDesignTabs(QTabWidget):
def __init__(self, parent=None):
super(QMyDesignTabs, self).__init__(parent)
self.figTab = QTabMyFig(self)
self.axesTab = QTabMyAxes(self)
self.addTab(self.figTab, "MyFigure")
self.addTab(self.axesTab, "MyAxes")
def getPlotDesign(self):
plotDesign = MyDesign()
plotDesign.figClassName = self.figTab.getClassName()
plotDesign.axesClassName.append(self.axesTab.getClassName())
plotDesign.update({plotDesign.figClassName: self.figTab.getFigFunctions(),
plotDesign.axesClassName[0]: self.axesTab.getAxesFunctions()})
print('qMyDesignTab: PlotDesign: ' +str(plotDesign))
return(plotDesign)
from . import QWidget
from . import QVBoxLayout
from . import QGridLayout
from . import QPushButton
from . import QMyFigContainer
from . import QMyDesignTabs
from . import Signal
......@@ -14,21 +15,27 @@ class QMyDesignWidget(QWidget):
super(QMyDesignWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
self.figContainer = QMyFigContainer(self)
self.layout = QGridLayout(self)
self.layout.setSpacing(10)
self.designTabs = QMyDesignTabs(self)
plotDesign = self.designTabs.getPlotDesign()
self.figContainer = QMyFigContainer(plotDesign, self)
self.refreshButton = QPushButton("Refresh", self)
self.layout.addWidget(self.figContainer)
self.layout.addWidget(self.refreshButton)
self.layout.addWidget(self.figContainer, 0, 1)
self.layout.addWidget(self.refreshButton, 1, 1)
self.layout.addWidget(self.designTabs, 0, 0, 2, 1)
self.refreshButton.clicked.connect(self.refresh)
def refresh(self):
self.figContainer.refresh() # Recreate the figure
self.resize(self.layout.sizeHint()) # Resize it-self depending on the figure.
self.refreshed.emit() # Emit refreshed such that the window knows it can refresh it-self
self.repaint() # Only then repaint to avoid blinking
plotDesign = self.designTabs.getPlotDesign()
self.figContainer.refresh(plotDesign) # Recreate the figure
self.repaint() # This will call paintEvent when all events are treated
def paintEvent(self, event):
......
......@@ -3,19 +3,18 @@ from . import QVBoxLayout
from . import QCanvas
from . import FigTest1
from . import readStupidData, readStupidData2
from . import FigDesign
from . import MyDesign
from . import MyData
class QMyFigContainer(QWidget):
def __init__(self, parent=None):
def __init__(self, plotDesign, parent=None):
super(QMyFigContainer, self).__init__(parent)
data = readStupidData('data1')
self.figure = FigTest1((data,))
self.figure = FigDesign(MyData(), plotDesign)
self.canvas = QCanvas(self.figure)
self.layout = QVBoxLayout(self)
......@@ -24,19 +23,15 @@ class QMyFigContainer(QWidget):
self.figure.plot()
self.canvas.draw()
def refresh(self):
# Just a stupid data switching to see an effect
if self.figure.rawdata[0].name == 'data1':
data = readStupidData2('data2')
else:
data = readStupidData('data1')
# Destroy the figure
del self.figure
def refresh(self, plotDesign):
# Recreate a figure
self.figure = FigTest1((data,))
try:
self.figure = FigDesign(MyData(), plotDesign)
except:
self.figure = FigDesign(MyData(), MyDesign())
print('AXES: ' + str(self.figure.get_axes()))
# Link the figure with the canvas
self.figure.set_canvas(self.canvas)
......@@ -46,12 +41,19 @@ class QMyFigContainer(QWidget):
self.figure.plot()
# RESIZE
self.setSizeToFigure()
def setSizeToFigure(self):
# Get the size of the figure
w, h = self.canvas.get_width_height()
# Resize the canvas to figure size
self.canvas.resize(w, h)
# Resize the Widget to the Layout size (Canvas + borders)
self.resize(self.layout.sizeHint())
def resizeEvent(self, event):
self.setSizeToFigure()
def paint(self):
......
from . import QWidget
from . import QGridLayout
from . import QFont
from . import QLabel
from . import QLineEdit
from . import QTextEdit
from . import QPlainTextEdit
from . import PythonHighlighter
class QTabMyAxes(QWidget):
def __init__(self, parent=None):
super(QTabMyAxes, self).__init__(parent)
self.layout = QGridLayout(self)
self.labelName = QLabel('Class Name', self)
self.editName = QLineEdit(self)
self.editName.setText('MyAxes')
self.labelDeclareKeywords = QLabel('def declareKeywords(self):', self)
self.editDeclareKeywords = QPlainTextEdit(self)
self.editDeclareKeywords.setFont(QFont('Monospace'))
self.highlightDeclareKeywords = PythonHighlighter(self.editDeclareKeywords.document())
self.editDeclareKeywords.insertPlainText(DEFAULT_DECLARE_KEYWORDS)
self.labelFormatRawdata = QLabel('def formatRawdata(self, rawdata):', self)
self.editFormatRawdata = QPlainTextEdit(self)
self.editFormatRawdata.setFont(QFont('Monospace'))
self.highlightFormatRawdata = PythonHighlighter(self.editFormatRawdata.document())
self.editFormatRawdata.insertPlainText(DEFAULT_FORMAT_RAWDATA)
self.labelPlotting = QLabel('def plotting(self):', self)
self.editPlotting = QPlainTextEdit(self)
self.editPlotting.setFont(QFont('Monospace'))
self.highlightPlotting = PythonHighlighter(self.editPlotting.document())
self.editPlotting.insertPlainText(DEFAULT_PLOTTING)
self.layout.addWidget(self.labelName, 0, 0)
self.layout.addWidget(self.editName, 0, 1)
self.layout.addWidget(self.labelDeclareKeywords, 1, 0, 1, 2)
self.layout.addWidget(self.editDeclareKeywords, 2, 0, 3, 2)
self.layout.addWidget(self.labelFormatRawdata, 5, 0, 1, 2)
self.layout.addWidget(self.editFormatRawdata, 6, 0, 5, 2)
self.layout.addWidget(self.labelPlotting, 11, 0, 1, 2)
self.layout.addWidget(self.editPlotting, 12, 0, 10, 2)
def getAxesFunctions(self):
functions = {'declareKeywords': u'',
'formatRawData': u'',
'plotting': u''}
functions.update({'declareKeywords': str(self.editDeclareKeywords.toPlainText())})
functions.update({'formatRawData': str(self.editFormatRawdata.toPlainText())})
functions.update({'plotting': str(self.editPlotting.toPlainText())})
return(functions)
def getClassName(self):
name = str(self.editName.text())
return(name)
DEFAULT_DECLARE_KEYWORDS = '''
self.keywords = {}
'''
DEFAULT_FORMAT_RAWDATA = '''
self.data = {'xdata': [-5, 2],
'ydata': [-2, 4]}
'''
DEFAULT_PLOTTING = '''
self.plot(self.data['xdata'], self.data['ydata'])
'''
from . import QWidget
from . import QGridLayout
from . import QFont
from . import QLabel
from . import QLineEdit
from . import QTextEdit
from . import QPlainTextEdit
from . import PythonHighlighter
class QTabMyFig(QWidget):
def __init__(self, parent=None):
super(QTabMyFig, self).__init__(parent)
self.layout = QGridLayout(self)
self.labelName = QLabel('Class Name', self)
self.editName = QLineEdit(self)
self.editName.setText('MyFig')
self.labelDeclareAliases = QLabel('def declareAliases(self):', self)
self.editDeclareAliases = QPlainTextEdit(self)
self.editDeclareAliases.setFont(QFont('Monospace'))
self.highlightDeclareAliases = PythonHighlighter(self.editDeclareAliases.document())
self.editDeclareAliases.insertPlainText(DEFAULT_DECLARE_ALIASES)
self.labelAddAxes = QLabel('def addAxes(self):', self)
self.editAddAxes = QPlainTextEdit(self)
self.editAddAxes.setFont(QFont('Monospace'))
self.highlightAddAxes = PythonHighlighter(self.editAddAxes.document())
self.editAddAxes.insertPlainText(DEFAULT_ADDAXES)
self.layout.addWidget(self.labelName, 0, 0)
self.layout.addWidget(self.editName, 0, 1)
self.layout.addWidget(self.labelDeclareAliases, 1, 0, 1, 2)
self.layout.addWidget(self.editDeclareAliases, 2, 0, 3, 2)
self.layout.addWidget(self.labelAddAxes, 5, 0, 1, 2)
self.layout.addWidget(self.editAddAxes, 6, 0, 10, 2)
def getFigFunctions(self):
functions = {'declareAliases': u'',
'addAxes': u''}
functions.update({'declareAliases': str(self.editDeclareAliases.toPlainText())})
functions.update({'addAxes': str(self.editAddAxes.toPlainText())})
return(functions)
def getClassName(self):
name = str(self.editName.text())
return(name)
<