ソースを参照

FFT Thread

Among others the calculation of the fft is now performed in a seperate
Thread
Patrick Schreiber 9 年 前
コミット
8886af2d1f

+ 0 - 0
base/backend/__init__.py


+ 435 - 0
base/backend/board.py

@@ -0,0 +1,435 @@
+import re
+import time
+import logging
+import subprocess
+import ConfigParser
+import numpy as np
+
+log = logging.getLogger(__name__)
+
+
+class BoardError(Exception):
+    pass
+
+
+class ObserverError(Exception):
+    pass
+
+
+class BoardConfiguration():
+
+    def __init__(self, config_file=None, logger=None):
+        self._config = {}
+        self._observers = {}
+        self.logger = logger
+        self._set_defaults()
+
+        if config_file:
+            self.load_config(config_file)
+
+    def _set_defaults(self):
+        c = self._config
+        c['fpga_delay_max']       = 15
+        c['fpga_delay']           = 0
+        c['fpga_delay_factor']    = 150
+        
+        c['chip_delay_max']       = 31
+        c['chip_1_delay']         = 4
+        c['chip_2_delay']         = 4
+        c['chip_3_delay']         = 4
+        c['chip_4_delay']         = 4
+        c['chip_delay_factor']    = 3
+
+        c['th_delay_max']         = 15
+        c['th_delay']             = 3
+        c['th_delay_factor']      = 150
+        
+        c['adc_delay_max']        = 15
+        c['adc_1_delay']          = 4
+        c['adc_2_delay']          = 4
+        c['adc_3_delay']          = 4
+        c['adc_4_delay']          = 4
+        c['adc_delay_factor']     = 150
+        
+        c['th_to_adc_cycles']     = 7
+        c['adc_1_delay_individual'] = -1  #      -1 = 'ADC 1 delay is the same as th_to_adc_cycles'
+                                          # 0 .. 16 = 'Use this offset instead'
+        
+        c['orbits_observe']       = 100
+        c['orbits_skip']          = 2
+
+    def register_logger(self, logger):
+        self.logger = logger
+
+    def load_config(self, filename):
+        if filename:
+            config = ConfigParser.RawConfigParser()
+            if not config.read(str(filename)):
+                return
+
+            for key in self._config.keys():
+                try:
+                    self._config[key] = config.getint('Config', key)
+                    if self.logger:
+                        self.logger.info("Read '%s' for '%s' from '%s'"%(str(self._config[key]), key, str(filename)))
+                except ConfigParser.NoOptionError as e:
+                    pass
+                except ConfigParser.NoSectionError as e:
+                    pass
+
+    def save_config(self, filename):
+        if filename:
+            with open(str(filename), 'w') as f:
+                cp = ConfigParser.RawConfigParser()
+                cp.add_section('Config')
+                for key in self._config.keys():
+                    cp.set('Config', key, self._config[key])
+                f.write('#\n'
+                        '#     HEB   (Hot Electron Bolometer) Configuration file\n'
+                        '#\n'
+                        '#  (c) Karlsruhe Institute of Technology, 2014\n'
+                        '#  All rights reserved.\n'
+                        '#\n'
+                        '#  Applicable Firmware Version(s):\n'
+                        '#\n\n')
+                cp.write(f)
+
+    def get(self, key):
+        return self._config.get(key, None)
+
+    def update(self, key, value):
+        self._config[key] = value
+        self._notify_observers(key, value)
+
+    def get(self, key):
+        return self._config.get(key, None)
+
+    def update(self, key, value):
+        self._config[key] = value
+        self._notify_observers(key, value)
+
+    def observe(self, who, callback, key):
+        if key not in self._config.keys():
+            raise ObserverError(str("Key '%s' is unknown."%key))
+        
+        if self._observers.get(key, None) is None:
+            self._observers[key] = []
+        
+        self._observers[key].append([who, callback])
+        
+    def unobserve(self, who, key=None):
+        if key is not None:
+            observers = np.array(self._observers.get(key, None))
+            if observers is None:
+                return
+            if who not in observers[:, 0]:
+                return               
+            for i, _obs in enumerate(self._observers[key]):
+                if _obs[0] is who:
+                    del self._observers[key][i]
+                    if not self._observers[key]:
+                        del self._observers[key]
+            return
+        
+        for _key in self._observers.keys():
+            for i, _obs in enumerate(self._observers[_key]):
+                if _obs[0] is who:
+                    del self._observers[_key][i]
+                    if not self._observers[_key]:
+                        del self._observers[_key]
+
+    def _notify_observers(self, key, value):
+        observers = self._observers.get(key, None)
+        if observers is None:
+            return
+        for (who, callback) in observers:
+            callback(value)
+        
+    def make_uint(self, value, maximum, name=None):
+        if value is None:
+            raise ValueError(str("%s Value is invalid (None)" % name))
+        
+        val = None
+        try:
+            val = int(value)
+        except:
+            raise ValueError(str("%s Value is not a valid number" % name))
+
+        if maximum is not None:    
+            if val > maximum:
+                raise ValueError(str("%s Value is too large (>%i)" % (name, maximum)))
+                
+        if val < 0:
+            raise ValueError(str("%s Values below 0 are not allowed" % name))
+        
+        return val
+
+    def set_fpga_delay(self, value):
+        time_factor = self.make_uint(value, self.get('fpga_delay_max'), 'FPGA_Delay')
+        reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "0"
+        write_pci(reg_value, '0x9060')
+        if self.logger:
+            self.logger.info("Set FPGA clock delay %i * %i --> %i picoseconds" % (time_factor, self.get('fpga_delay_factor'), time_factor*self.get('fpga_delay_factor')))
+        self.update('fpga_delay', value)
+
+    def set_chip_delay(self, adcs, values):
+        if not adcs or not values:
+            if self.logger:
+                self.logger.info("Nothing to do for chip delay.")
+            return
+
+        _adcs = []
+        for adc in adcs:
+            _adcs.append(self.make_uint(adc, 3, 'ADC_'))
+            
+        _values = []
+        for value in values:
+            _values.append(self.make_uint(value, self.get('chip_delay_max'), 'ADC_Value'))
+
+        a_v = zip(_adcs, _values) 
+            
+        factors = [None, None, None, None]
+        for (adc, value) in a_v:
+            factors[adc] = value
+            
+        reg_value = ''
+        mask = ''
+        # Chip Delays are stored as 'ADC_4 ADC_3 ADC_2 ADC_1' in the register.
+        # Therefore, we need to traverse the factors array in reverse order
+        for value in reversed(factors):
+            if value is not None:
+                reg_value += '{0:02x}'.format(value)
+                mask += 'ff'
+            else:
+                reg_value += '00'
+                mask += '00'
+               
+        write_pci(reg_value, '0x9080', hex_mask=mask)
+        if self.logger:
+            s = "Setting ADC Delays:"
+            for (adc, value) in a_v:
+                s += ' ADC_%i Fine Delay: %i,' % (adc, value)
+            s = s[:-1]  # cut away the last dangling ','
+            self.logger.info(s)
+        for (adc, value) in a_v:
+            s = 'chip_%i_delay'%(adc+1)
+            self.update(s, value)
+
+    def set_th_delay(self, value):
+        time_factor = self.make_uint(value, self.get('th_delay_max'), 'TH_Delay')
+        reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "3"
+        write_pci(reg_value, '0x9060')
+        if self.logger:
+            self.logger.info("Set T/H Signal delay %i * %i --> %i picoseconds" % (time_factor, self.get('th_delay_factor'), time_factor*self.get('th_delay_factor')))
+        self.update('th_delay', value)
+
+    def set_adc_delay(self, adc, value):
+        if adc is None or value is None:
+            if self.logger:
+                self.logger.info("Nothing to do for ADC delay.")
+            return
+        _adc = self.make_uint(adc, 3, 'ADC Number')    
+        _val = self.make_uint(value, self.get('adc_delay_max'), 'ADC Delay')
+        reg_value = "0x000501" + '{0:01x}'.format(_val) + "%i" % (_adc+4)
+        write_pci(reg_value, '0x9060')
+        if self.logger:
+            s = "Setting ADC_%i delay %i * %i --> %i picoseconds" % ((_adc+1), _val, self.get('adc_delay_factor'), _val*self.get('adc_delay_factor'))
+            self.logger.info(s)
+        adc_s = 'adc_%i_delay'%(_adc+1)
+        self.update(adc_s, _val)
+
+    def set_delay(self, n):
+        def write_delay(value, channel):
+            cmd = '00501' + '%01x' % value + str(channel)
+            write_pci(cmd, reg='0x9060')
+            time.sleep(0.005)
+
+        write_delay(n, 3)
+        self.update('th_delay', n)
+
+        delay = n + self.get('th_to_adc_cycles')
+
+        if delay > self.get('adc_delay_max'):
+            delay -= self.get('adc_delay_max') + 1
+
+        write_delay(delay, 5)
+        self.update('adc_2_delay', delay)
+        write_delay(delay, 6)
+        self.update('adc_3_delay', delay)
+        write_delay(delay, 7)
+        self.update('adc_4_delay', delay)
+
+        #ADC 1 might have an individual delay
+        try:
+            delay = n + self.make_uint(self.get('adc_1_delay_individual'), 16, 'ADC 1 individual delay')
+        except ValueError:
+            if self.logger:
+                self.logger.info(r"'adc_1_delay_individual' not set or inactive. Using default.")
+
+        if delay > self.get('adc_delay_max'):
+            delay -= self.get('adc_delay_max') + 1
+        write_delay(delay, 4)
+        self.update('adc_1_delay', delay)
+
+
+# def safe_call(cmd):
+#     try:
+#         return subprocess.check_output(cmd)
+#     except subprocess.CalledProcessError as e:
+#         raise BoardError(e.output)
+#     except OSError as e:
+#         raise BoardError('{}: {}'.format(' '.join(cmd), str(e)))
+def safe_call(cmd): # TODO: For Production restore save_call
+    log.info(cmd)
+    return True
+
+def write_pci(value, reg='0x9040', opts=[], hex_mask='FFFFFFFF'):
+
+    if hex_mask != 'FFFFFFFF':
+        if len(hex_mask) > 8:
+            hex_mask = hex_mask[-8:]
+        prev_val = read_pci(1, reg)[0]
+        prev_bits = '{0:032b}'.format(int(prev_val,16))
+        mask_bits = '{0:032b}'.format(int(hex_mask,16))
+        value_bits = '{0:032b}'.format(int(value,16))
+        new_bits = list(prev_bits)
+        
+        for i, bit in enumerate(mask_bits):
+            if bit == '1':
+                new_bits[i] = value_bits[i]
+                
+        value = hex(int("".join(new_bits),2))
+
+    cmd = ['pci', '-w', reg, value]
+    cmd.extend(opts)
+    safe_call(cmd)
+    log.debug('Written %str to register %s'%(value, reg))
+    
+
+def read_pci(amount=1, reg='0x9000', opts=[], decimal=False):
+    cmd = ['pci', '-r', reg, "-s%i"%amount]
+    cmd.extend(opts)
+    output = safe_call(cmd).split("\n")
+    lines = []
+    for line in output:
+        if line and line != "\n":
+            lines.append(line.split(":  ")[1])
+    formated = []
+    for line in lines:
+        if line and line != "\n":
+            formated += re.findall(r'[\da-fA-F]{8}', line) 
+    if decimal:
+        return [int(x,16) for x in formated]
+    else:
+        return formated
+
+    
+def get_dec_from_bits(bits, msb=-1, lsb=-1):
+    rtrnValue = 0
+    
+    if (msb < 0 or lsb < 0):
+        rtrnValue = int(bits)
+    elif (msb < lsb) or (msb > 31) or (lsb > 31):
+        log.info("Bit range for msb and lsb of get_dec_from_bits was wrong. Not truncating")
+        rtrnValue = int(bits, 2)
+    else:
+        chunk = ('{0:032b}'.format(int(bits, 2)))[31-msb:32-lsb]
+        rtrnValue = int(chunk, 2)
+        
+    return rtrnValue
+
+
+def start_dma(dma='dma0r'):
+    log.info('Start DMA')
+    cmd = ['pci', '--start-dma', dma]
+    safe_call(cmd)
+    time.sleep(0.05)
+    
+
+def stop_dma(dma='dma0r'):
+    log.info('Stop DMA')
+    cmd = ['pci', '--stop-dma', dma]
+    safe_call(cmd)
+    time.sleep(0.05)
+
+
+def data_reset():
+    log.info('Data reset')
+    write_pci('000003f5', hex_mask='7')
+    time.sleep(0.05)
+    write_pci('000003f0', hex_mask='7')
+    time.sleep(0.05)
+
+
+def flush_dma(dma='dma0'):
+    log.info('Flushing DMA Pipeline')
+    write_pci('03f0', hex_mask='CF0')
+    cmd = ['pci', '-r', dma, '-o', '/dev/null', '--multipacket']
+    safe_call(cmd)
+
+
+def is_active():
+    control = read_pci(1, '0x9040')[0]
+    control_bits = '{0:032b}'.format(int(control, 16))
+    return control_bits[22:26] == "1111"
+
+
+def start_pilot_bunch_emulator():
+    log.info('Start pilot bunch emulator')
+    write_pci('400003f0', hex_mask='400003F0')
+    time.sleep(0.05)
+    write_pci('03f0', hex_mask='CF0')
+
+
+def start_acquisition():
+    log.info('Start acquisition')
+    write_pci('1', '4', hex_mask='1')
+    time.sleep(0.05)
+    write_pci('00bf0', hex_mask='CF0')
+
+
+def stop_acquisition():
+    log.info('Stop acquisition')
+    write_pci('003f0', hex_mask='CF0')
+    time.sleep(0.05)
+
+
+def enable_transfer():
+    log.info('Enable data transfer')
+    write_pci('007f0', hex_mask='CF0')
+    time.sleep(0.05)
+
+
+def write_data(filename, dma='dma0'):
+    log.info('Write data to {}'.format(filename))
+    cmd = ['pci', '-r', dma, '-o', filename, '--multipacket']
+    safe_call(cmd)
+    write_pci('003f0', hex_mask='CF0')
+
+
+def run_status(script_path):
+    cmd = ['bash', script_path]
+    output = safe_call(cmd)
+    log.info(output)
+
+
+def acquire_data(filename, duration=1.0, simulate=False):
+    #start_dma()  #Dont start dma when it is already active! (Causes data corruption in the driver)
+
+    if simulate:
+        start_pilot_bunch_emulator()
+
+    start_acquisition()
+    #time.sleep(duraition) #calculate the time instead
+    wait_for_revolutions()
+    stop_acquisition()
+    enable_transfer()
+    write_data(filename)
+    flush_dma()
+
+
+def wait_for_revolutions():
+    n = read_pci(1, '0x9020', decimal=True)[0]  # Get the ammount of orbits to observe
+    spin_time_ns = 368 * n
+    wait_time_s = spin_time_ns / 1000.0 / 1000.0 / 1000.0   # Milli, Micro, Nano
+    time.sleep(wait_time_s * 1.4)  # 40% Safety margin

+ 7 - 3
base/controlwidget.py

@@ -2,6 +2,7 @@ from PyQt4 import QtGui, QtCore
 import logging
 
 import kcgwidget as kcgw
+from backend import board
 
 # class StatusLED(QtGui.QWidget):
 #     def __init__(self, status):
@@ -104,7 +105,8 @@ class ControlWidget(kcgw.KCGWidgets):
         self.initUi()
 
     def dummy_button_press_function(self, w=None):
-        logging.info("Button Pressed - {}".format(w))
+        logging.info("Action: {}".format(w))
+        board.write_pci(4)
     def initUi(self):
 
         self.overlayout = QtGui.QVBoxLayout()
@@ -119,8 +121,8 @@ class ControlWidget(kcgw.KCGWidgets):
         self.calibrate_action = QtGui.QAction(QtGui.QIcon("icons/orange/cog.svg"), "Calibrate", self)
         self.sync_action = QtGui.QAction(QtGui.QIcon("icons/green/loop-circular.svg"), "Syncronize", self)
         self.default_action = QtGui.QAction(QtGui.QIcon("icons/orange/box.svg"), "Set Defaults", self)
-        self.reset_action = QtGui.QAction(QtGui.QIcon("icons/red/restore-red.svg"), "Soft Reset", self)
-        self.stop_action = QtGui.QAction(QtGui.QIcon("icons/red/power-standby-red.svg"), "Board Off", self)
+        self.reset_action = QtGui.QAction(QtGui.QIcon("icons/red/restore.svg"), "Soft Reset", self)
+        self.stop_action = QtGui.QAction(QtGui.QIcon("icons/red/power-standby.svg"), "Board Off", self)
 
         self.toolbar.addActions([self.start_action,
                                  self.calibrate_action,
@@ -130,6 +132,7 @@ class ControlWidget(kcgw.KCGWidgets):
                                  self.stop_action])
         self.toolbar.insertSeparator(self.calibrate_action)
         self.toolbar.insertSeparator(self.stop_action)
+        # ----------[ End Toolbar ]------------
 
         self.overlayout.insertWidget(0, self.toolbar)
         self.overlayout.insertSpacing(1, 10)
@@ -170,6 +173,7 @@ class ControlWidget(kcgw.KCGWidgets):
         log_handler = LogHandler(self.log_area)
         log_handler.setFormatter(logging.Formatter('%(asctime)s - %(funcName)s(): %(message)s'))
         self.logger = logging.getLogger()
+        board.config.register_logger(self.logger)
         self.logger.addHandler(log_handler)
         self.logger.setLevel(logging.INFO)
 

+ 171 - 0
base/controlwidget2.py

@@ -0,0 +1,171 @@
+from PyQt4 import QtGui, QtCore
+import logging
+
+import kcgwidget as kcgw
+
+class LED(QtGui.QWidget):
+
+    def __init__(self, parent=None, status=None, height=10, width=10):
+        QtGui.QWidget.__init__(self, parent)
+        colorRGB=(255, 0, 0)
+        self.width = width
+        self.height = height
+        self.color = QtGui.QColor(colorRGB[0], colorRGB[1], colorRGB[2])
+        self.center = QtCore.QPoint(width, height)
+        self.setMinimumSize(2 * width, 2 * height)
+        self.setMaximumSize(2 * width, 2 * height)
+        if status:
+            self.set_on()
+        elif status == False:
+            self.set_off()
+        else:
+            self.set_out()
+        self.status = status
+
+    def paintEvent(self, event):
+        paint = QtGui.QPainter()
+        paint.begin(self)
+        paint.setRenderHint(QtGui.QPainter.Antialiasing)
+
+        # draw a grey 'socket' for the LED
+        paint.setPen(QtGui.QColor(160, 160, 160))
+        paint.setBrush(QtGui.QColor(180, 180, 180))
+        paint.drawEllipse(self.center, self.width, self.height)
+
+        # draw the body of the LED
+        paint.setBrush(self.color)
+        paint.drawEllipse(self.center, self.width*0.85, self.height*0.85)
+
+    def set_on(self):
+        self.color = QtGui.QColor(0, 255, 0)
+        self.update()
+        self.status = True
+    def set_off(self):
+        self.color = QtGui.QColor(255, 0, 0)
+        self.update()
+        self.status = False
+    def set_out(self):
+        self.color = QtGui.QColor(150, 150, 150)
+        self.update()
+        self.status = None
+
+class StatusLED(QtGui.QWidget):
+    def __init__(self, text, status=None):
+        super(StatusLED, self).__init__()
+        self.layout = QtGui.QHBoxLayout()
+        self.label = QtGui.QLabel(text)
+        self.led = LED(status=status, width=9, height=9)
+        self.layout.addWidget(self.led)
+        self.layout.addWidget(self.label)
+        self.setLayout(self.layout)
+    def set_on(self):
+        self.led.set_on()
+    def set_off(self):
+        self.led.set_off()
+    def set_out(self):
+        self.led.set_out()
+    def toggle(self):
+        if self.led.status:
+            self.led.set_off()
+        elif self.led.status == False:
+            self.led.set_on()
+        else:
+            self.led.set_on()
+
+class LogHandler(logging.Handler):
+    def __init__(self, log_area):
+        logging.Handler.__init__(self)
+        self.text_area = log_area
+
+    def emit(self, record):
+        self.text_area.append(self.format(record))
+
+
+
+class ControlWidget(kcgw.KCGWidgets):
+    def __init__(self, parent=None):
+        super(ControlWidget, self).__init__(parent=parent)
+        self.initUi()
+
+    def dummy_button_press_function(self, w=None):
+        logging.info("Action: {}".format(w))
+
+    def initUi(self):
+        self.overlayout = QtGui.QVBoxLayout()
+        self.setLayout(self.overlayout)
+        self.layout = QtGui.QGridLayout()
+        self.overlayout.addLayout(self.layout)
+        b1 = kcgw.ClickableSVG("icons/media-play.svg", 60, 60)
+        b1.setStyleSheet("border: 1px solid grey; border-radius: 5px;")
+        b1.show()
+        b1.setFixedSize(100, 100)
+        b2 = kcgw.ClickableSVG("icons/orange/cog.svg", 60, 60)
+        b2.setStyleSheet("border: 1px solid grey; border-radius: 5px;")
+        b2.show()
+        b2.setFixedSize(100, 100)
+        b3 = kcgw.ClickableSVG("icons/green/loop-circular.svg", 60, 60)
+        b3.setStyleSheet("border: 1px solid grey; border-radius: 5px;")
+        b3.show()
+        b3.setFixedSize(100, 100)
+        b4 = kcgw.ClickableSVG("icons/orange/box.svg", 60, 60)
+        b4.setStyleSheet("border: 1px solid grey; border-radius: 5px;")
+        b4.show()
+        b4.setFixedSize(100, 100)
+        b5 = kcgw.ClickableSVG("icons/red/restore.svg", 60, 60)
+        b5.setStyleSheet("border: 1px solid grey; border-radius: 5px;")
+        b5.show()
+        b5.setFixedSize(100, 100)
+        b6 = kcgw.ClickableSVG("icons/red/power-standby.svg", 60, 60)
+        b6.setStyleSheet("border: 1px solid grey; border-radius: 5px;")
+        b6.show()
+        b6.setFixedSize(100, 100)
+        b7 = kcgw.BigIconButton("icons/media-play.svg", 100, 100, connect=lambda: self.dummy_button_press_function("test"), tooltip="ich bin ein tooltip<hr>Ein Tooltip ist ein <br>Text der was erkl&auml;rt.")
+        b8 = kcgw.BigIconButton("icons/orange/cog.svg", 80, 80)
+        b9 = kcgw.BigIconButton("icons/green/loop-circular.svg", 60, 60)
+        b10 = kcgw.BigIconButton("icons/orange/box.svg", 40, 40)
+        b11 = kcgw.BigIconButton("icons/red/restore.svg", 30, 30)
+        b12 = kcgw.BigIconButton("icons/red/power-standby.svg", 20, 20)
+
+        self.window().statusbar.showMessage('test')
+
+        b1.clicked.connect(lambda: self.dummy_button_press_function("Start Board"))
+        b2.clicked.connect(lambda: self.dummy_button_press_function("Calibrate"))
+        b3.clicked.connect(lambda: self.dummy_button_press_function("Synchronize"))
+        b4.clicked.connect(lambda: self.dummy_button_press_function("Load Defaults"))
+        b5.clicked.connect(lambda: self.dummy_button_press_function("Soft Reset"))
+        b6.clicked.connect(lambda: self.dummy_button_press_function("Power Off"))
+
+        self.layout.addWidget(b1, 0, 0)
+        self.layout.addWidget(b2, 0, 1)
+        self.layout.addWidget(b3, 0, 2)
+        self.layout.addWidget(b4, 2, 0)
+        self.layout.addWidget(b5, 2, 1)
+        self.layout.addWidget(b6, 2, 2)
+        self.layout.addWidget(b7, 3, 0)
+        self.layout.addWidget(b8, 3, 1)
+        self.layout.addWidget(b9, 3, 2)
+        self.layout.addWidget(b10, 4, 0)
+        self.layout.addWidget(b11, 4, 1)
+        self.layout.addWidget(b12, 4, 2)
+        self.overlayout.addStretch(1)
+
+        # Logging
+        self.log_area = QtGui.QTextEdit()
+        self.log_area.setReadOnly(True)
+        self.overlayout.addWidget(self.log_area)
+        log_handler = LogHandler(self.log_area)
+        log_handler.setFormatter(logging.Formatter('%(asctime)s - %(funcName)s(): %(message)s'))
+        self.logger = logging.getLogger()
+        self.logger.addHandler(log_handler)
+        self.logger.setLevel(logging.INFO)
+
+    def on_check(self):
+        import random
+        r = [random.random()+0.5 for i in range(4)]
+        ledlist = [self.pipeline_led, self.master_control_led, self.data_check_led, self.pll_ld_led]
+        for i, led in enumerate(ledlist):
+            if int(r[i]) == 1:
+                led.set_on()
+            else:
+                led.set_off()
+        logging.info("Checked: {}".format(str([int(i) for i in r])))

+ 165 - 64
base/kcg.py

@@ -1,10 +1,17 @@
-from PyQt4 import QtGui, QtCore
+# TODO: use wait_cursor?
+from PyQt4 import QtGui, QtCore, QtSvg
 import numpy as np
 import pyqtgraph as pg
 
+from backend import board
 import kcgwidget as kcgw
 from leftbar import LeftBar
 from controlwidget import ControlWidget
+from multiWidget import MultiWidget
+from storage import Storage
+from settings import Settings
+
+board.config = board.BoardConfiguration()
 
 class IdGenerator():
     highest_id = 0
@@ -16,88 +23,182 @@ class IdGenerator():
 idg = IdGenerator()
 kcgw.idg = idg
 
-class MDIArea(kcgw.KCGWidgets):
-    def __init__(self, parent):
-        super(MDIArea, self).__init__("MDIArea")
-        self.parent = parent
-
-        self.initArea()
-
-    def initArea(self):
-        self.area = QtGui.QMdiArea()
-
-    def newWidget(self, widget, name, unique_id, type):
-        subWidget = kcgw.KCGSubWidget(name=name, unique_id=unique_id, typ=type)
-        subWidget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
-        widget.setParent(subWidget)
-        subWidget.setWidget(widget)
-        self.area.addSubWindow(subWidget)
-        subWidget.show()
+class RightSwitch(kcgw.ClickableSVG):
+    def __init__(self, pagesWidget, width=10, height=20, wwidth=None):
+        kcgw.ClickableSVG.__init__(self, "icons/chevron-right.svg", width, height, wwidth)
+        self.setObjectName("right_switch")
+        self.show()
+        self.clicked.connect(lambda: pagesWidget.setCurrentIndex(pagesWidget.currentIndex()+1))
+
+
+class LeftSwitch(kcgw.ClickableSVG):
+    def __init__(self, pagesWidget, width=10, height=20, wwidth=None):
+        kcgw.ClickableSVG.__init__(self, "icons/chevron-left.svg", width, height, wwidth)
+        self.setObjectName("left_switch")
+        self.show()
+        self.clicked.connect(lambda: pagesWidget.setCurrentIndex(pagesWidget.currentIndex()-1))
+
+
+class LeftRightSwitch(QtGui.QWidget):
+    def __init__(self, pagesWidget):
+        super(LeftRightSwitch, self).__init__()
+        self.right = RightSwitch(pagesWidget, width=10, height=10)
+        self.right.setObjectName("leftright")
+        self.left = LeftSwitch(pagesWidget, width=10, height=10)
+        self.left.setObjectName("leftright")
+        self.layout = QtGui.QHBoxLayout()
+        self.setLayout(self.layout)
+        self.layout.addWidget(self.left)
+        self.layout.addWidget(self.right)
+    def disable_left(self):
+        # self.left.setStyleSheet("border-radius: 4px; background-color: lightgrey;")
+        self.left.setStyleSheet("#leftright:hover { background-color: none;}")
+        self.left.changeSvg("icons/grey/chevron-left.svg")
+    def enable_left(self):
+        self.left.setStyleSheet("")
+        self.left.changeSvg("icons/chevron-left.svg")
+    def disable_right(self):
+        # self.right.setStyleSheet("border-radius: 4px; background-color: lightgrey;")
+        self.right.setStyleSheet("#leftright:hover { background-color: none;}")
+        self.right.changeSvg("icons/grey/chevron-right.svg")
+    def enable_right(self):
+        self.right.setStyleSheet("")
+        self.right.changeSvg("icons/chevron-right.svg")
+
+
+class MultiPage(QtGui.QStackedWidget):
+    def __init__(self, parent=None):
+        super(MultiPage, self).__init__(parent)
+        self.pages = []
+        self.numOfPages = -1
+        self.leftright = LeftRightSwitch(self)
+        self.leftright.hide()
+        self.setCurrentIndex(0)
+
+    def addPage(self, NewPage, name=None):
+        self.leftright.show()
+        self.numOfPages += 1
+        self.pages.append(QtGui.QWidget())
+        self.pages[-1].name = name
+        self.pages[-1].layout = QtGui.QHBoxLayout()
+        self.pages[-1].setLayout(self.pages[-1].layout)
+        if len(self.pages) == 1:
+            self.pages[-1].layout.addWidget(NewPage)
+        if len(self.pages) > 1:
+            self.pages[-2].layout.addWidget(RightSwitch(self, wwidth=20))
+            self.pages[-1].layout.addWidget(LeftSwitch(self, wwidth=20))
+            self.pages[-1].layout.addWidget(NewPage)
+        self.addWidget(self.pages[-1])
+        self.setCurrentIndex(0)
+
+    def setCurrentIndex(self, p_int):
+        if p_int > self.numOfPages or p_int < 0:
+            return
+        if p_int <= 0:
+            self.leftright.disable_left()
+        else:
+            self.leftright.enable_left()
+        if p_int >= self.numOfPages:
+            self.leftright.disable_right()
+        else:
+            self.leftright.enable_right()
+        name = self.pages[p_int].name+" " if self.pages[p_int].name else " "
+        self.window().pageIndicator.setText(name+"| "+str(p_int+1)+"/"+str(self.numOfPages+1))
+        super(MultiPage, self).setCurrentIndex(p_int)
 
 
 class CentralWidget(kcgw.KCGWidgets):
-    def __init__(self):
-        super(CentralWidget, self).__init__()
-
+    def __init__(self, parent=None):
+        super(CentralWidget, self).__init__(parent=parent)
         self.initUI()
 
     def initUI(self):
-        self.layout = QtGui.QVBoxLayout()
-        self.sWidget = QtGui.QStackedWidget()
-        self.layout.addWidget(self.sWidget)
+        self.layout = QtGui.QHBoxLayout()
         self.setLayout(self.layout)
-        self.cWidget()
-        self.mWidget()
-        self.sWidget.addWidget(self.controlWidget)
-        self.sWidget.addWidget(self.multiWidget)
-        self.sWidget.setCurrentIndex(0)
-        self.leftLabel.clicked.connect(lambda: self.sWidget.setCurrentIndex(0))
-        self.rightLabel.clicked.connect(lambda: self.sWidget.setCurrentIndex(1))
-    def cWidget(self):
-        self.controlWidget = kcgw.KCGWidgets()
-        self.controlWidget.layout = QtGui.QHBoxLayout()
-
-        self.rightLabel = self.createLabel(">", click=True)
-        self.rightLabel.setFixedWidth(20)
-
+        self.pagesWidget = MultiPage(self)
+        self.layout.addWidget(self.pagesWidget)
         self.mainControlWidget = ControlWidget()
-        self.controlWidget.layout.addWidget(self.mainControlWidget)
-        self.controlWidget.layout.addWidget(self.rightLabel)
-        self.controlWidget.setLayout(self.controlWidget.layout)
-
-    def mWidget(self):
-        self.multiWidget = QtGui.QWidget()
-        self.multiWidget.layout = QtGui.QHBoxLayout(self.multiWidget)
-        self.multiWidget.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
-
-        self.multiWidget.leftBar = LeftBar(self.multiWidget)
-        self.multiWidget.area = MDIArea(self.multiWidget)
+        self.mainMultiWidget = MultiWidget()
+        from controlwidget2 import ControlWidget as ControlWidget2
+        self.cwa  = ControlWidget2(parent=self)
+        self.pagesWidget.addPage(self.mainControlWidget, "Setup/Control")
+        self.pagesWidget.addPage(self.mainMultiWidget, "MultiView")
+        self.pagesWidget.addPage(self.cwa)
 
-        self.multiWidget.splitter.addWidget(self.multiWidget.leftBar)
-        self.multiWidget.splitter.addWidget(self.multiWidget.area.area)
-
-        self.leftLabel = self.createLabel("<", click=True)
-        self.leftLabel.setFixedWidth(20)
-        self.multiWidget.layout.addWidget(self.leftLabel)
-        self.multiWidget.layout.addWidget(self.multiWidget.splitter)
-        self.multiWidget.setLayout(self.multiWidget.layout)
 
 class Gui(QtGui.QMainWindow):
     def __init__(self, app):
         super(Gui, self).__init__()
         self.setWindowTitle("BestGuiEver!!!11elf")
-
+        self.storage = Storage({"header":True})
+        self.settings = None # Only create Window when used
+        self.statusbar = self.statusBar()
+        self.pageIndicator = QtGui.QLabel()
+        self.statusbar.addPermanentWidget(self.pageIndicator)
         self.initUI()
 
     def initUI(self):
-        self.cw = CentralWidget()
+        self.cw = CentralWidget(self)
         self.setCentralWidget(self.cw)
         self.menu = self.menuBar()
 
-        newPlotAction = QtGui.QAction("New Plot", self)
-        newPlotAction.setShortcut("Ctrl+N")
-
-        newPlotAction.triggered.connect(self.cw.multiWidget.leftBar.add_plot)
+        # newPlotAction = QtGui.QAction("New Plot", self)
+        # newPlotAction.triggered.connect(lambda: self.changePage_and_execute(1 ,self.cw.mainMultiWidget.leftBar.add_plot))
 
         self.fileMenu = self.menu.addMenu("&File")
-        self.fileMenu.addAction(newPlotAction)
+        self.settingsAction = self.fileMenu.addAction("Settings", lambda: self.showSettings())
+        self.quitAction = self.fileMenu.addAction(QtGui.QIcon("icons/exit.png"), "Quit", self.close)
+        self.menu.setCornerWidget(self.cw.pagesWidget.leftright)
+
+
+        with open("style/style.css") as f:
+            styleSheet = f.read()
+        self.setStyleSheet(styleSheet)
+
+        # ------[ Style :) ]----------
+        def changestyle(style):
+            self.setStyleSheet(styleSheet+style)
+
+        self.t = QtCore.QTimer()
+        self.t.timeout.connect(lambda: blink("do"))
+        self.state = 9
+        def blink(b):
+            stlist = ["\nGui {background-color: red;}", "\nGui {background-color: lime;}", "\nGui {background-color: none;}"]
+            if b == "do":
+                self.state = (self.state + 1) % 2
+                changestyle(stlist[self.state])
+            elif self.state == 9:
+                self.t.start(150)
+            else:
+                self.t.stop()
+                self.state = 9
+
+        self.styleMenu = self.menu.addMenu("&Style")
+        defstyleAction = QtGui.QAction("Default", self)
+        defstyleAction.triggered.connect(lambda: changestyle(""))
+        pinkAction = QtGui.QAction("Pink", self)
+        pinkAction.triggered.connect(lambda: changestyle("\nGui {background-color: pink;}"))
+        blueAction = QtGui.QAction("Blue", self)
+        blueAction.triggered.connect(lambda: changestyle("\nGui {background-color: lightblue;}"))
+        blinkAction = QtGui.QAction("Blink", self)
+        blinkAction.triggered.connect(lambda: blink(True))
+        self.styleMenu.addAction(defstyleAction)
+        self.styleMenu.addAction(pinkAction)
+        self.styleMenu.addAction(blueAction)
+        self.styleMenu.addAction(blinkAction)
+        # ------[ End Style ]-----------
+
+
+    def changePage_and_execute(self, page, conn):
+        self.cw.pagesWidget.setCurrentIndex(page)
+        conn()
+
+    def showSettings(self):
+        if self.settings: # use preopened window
+            self.settings.show()
+        else:
+            self.settings = Settings(self.storage)
+            self.settings.changed.connect(self.updateSettings)
+
+    def updateSettings(self):
+        print 'settings changed' # TODO: implement settings updater

+ 46 - 14
base/kcgwidget.py

@@ -1,19 +1,48 @@
 """
 Class containing the creators for buttons etc
 """
-from PyQt4 import QtGui, QtCore
+from PyQt4 import QtGui, QtCore, QtSvg
 
 idg = None
 
+class BigIconButton(QtGui.QPushButton):
+    def __init__(self, path, width, height, connect=None, tooltip=None, parent=None):
+        super(BigIconButton, self).__init__(parent)
+        self.setIcon(QtGui.QIcon(path))
+        self.setFixedSize(width, height)
+        self.setIconSize(QtCore.QSize(width*0.7, height*0.7))
+        if connect:
+            self.clicked.connect(connect)
+        if tooltip:
+            self.setToolTip(tooltip)
+
 class clickLabel(QtGui.QLabel):
     clicked = QtCore.pyqtSignal()
-    def mousePressEvent(self, QMouseEvent):
+    def mouseReleaseEvent(self, QMouseEvent):
+        self.clicked.emit()
+
+class ClickableSVG(QtGui.QWidget):
+    clicked = QtCore.pyqtSignal()
+    def __init__(self, path, width, height, wwidth=None, wheight=None, parent=None):
+        super(ClickableSVG, self).__init__(parent)
+        self.svg = QtSvg.QSvgWidget(path)
+        self.svg.setFixedSize(width, height)
+        layout = QtGui.QHBoxLayout()
+        layout.addWidget(self.svg)
+        self.setLayout(layout)
+        if wwidth:
+            self.setFixedWidth(wwidth)
+        if wheight:
+            self.setFixedHeight(wheight)
+    def mouseReleaseEvent(self, QMouseEvent):
         self.clicked.emit()
+    def changeSvg(self, path):
+        self.svg.load(path)
 
 
 class KCGWidgets(QtGui.QWidget):
-    def __init__(self, name=None):
-        super(KCGWidgets, self).__init__()
+    def __init__(self, name=None, parent=None):
+        super(KCGWidgets, self).__init__(parent)
         self._id = None # TODO: get or generate id
         self._type = None # TODO: get or generate id
         self._name = None
@@ -44,7 +73,7 @@ class KCGWidgets(QtGui.QWidget):
     def theName(self, n):
         self._name = n # TODO: ??
 
-    def createButton(self, x=None, y=None, dimensions=None, text="", tooltip="", connect=False):
+    def createButton(self, text="", x=None, y=None, dimensions=None, tooltip="", connect=False):
         button = QtGui.QPushButton(text, self)
         if tooltip:
             button.setToolTip(tooltip)
@@ -58,12 +87,15 @@ class KCGWidgets(QtGui.QWidget):
             button.clicked.connect(connect)
         return button
 
-    def createLabel(self, text, tooltip=None, click=False):
+    def createLabel(self, text=None, image=None, tooltip=None, click=False):
         if click:
             label = clickLabel(self)
         else:
             label = QtGui.QLabel(self)
-        label.setText(text)
+        if text:
+            label.setText(text)
+        if image:
+            label.setPixmap(image)
         if tooltip:
             label.setToolTip(tooltip)
         return label
@@ -121,15 +153,15 @@ class KCGSubWidget(QtGui.QMdiSubWindow):
             button.clicked.connect(connect)
         return button
 
-    def createLabel(self, text, tooltip=None, vertical=False):
-        if vertical:
-            label = VerticalLabel(self)
+    def createLabel(self, text=None, image=None, tooltip=None, click=False):
+        if click:
+            label = clickLabel(self)
         else:
             label = QtGui.QLabel(self)
-        label.setText(text)
+        if text:
+            label.setText(text)
+        if image:
+            label.setPixmap(image)
         if tooltip:
             label.setToolTip(tooltip)
         return label
-
-
-

+ 1 - 1
base/leftbar.py

@@ -57,7 +57,7 @@ class LeftBar(kcgw.KCGWidgets):
 
         self.info = Info()
 
-        self.layout.addWidget(self.createLabel("Plots"))
+        # self.layout.addWidget(self.createLabel("Plots"))
         self.layout.addWidget(self.treeWidget)
         self.layout.addWidget(self.info)
 

+ 59 - 0
base/multiWidget.py

@@ -0,0 +1,59 @@
+from PyQt4 import QtGui, QtCore
+
+import kcgwidget as kcgw
+from leftbar import LeftBar
+
+class MDIArea(kcgw.KCGWidgets):
+    def __init__(self, parent):
+        super(MDIArea, self).__init__("MDIArea")
+        self.parent = parent
+
+        self.initArea()
+
+    def initArea(self):
+        self.area = QtGui.QMdiArea()
+
+    def newWidget(self, widget, name, unique_id, type):
+        subWidget = kcgw.KCGSubWidget(name=name, unique_id=unique_id, typ=type)
+        subWidget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+        widget.setParent(subWidget)
+        subWidget.setWidget(widget)
+        self.area.addSubWindow(subWidget)
+        subWidget.show()
+
+class MultiWidget(QtGui.QWidget):
+    def __init__(self):
+        super(MultiWidget, self).__init__()
+        self.layout = QtGui.QVBoxLayout()
+        self.setLayout(self.layout)
+
+
+        self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
+
+        self.leftBar = LeftBar(self)
+        self.area = MDIArea(self)
+
+        # --------[ Menu Bar ]--------
+        self.menu = QtGui.QMenuBar()
+        newPlotAction = QtGui.QAction("New Plot", self)
+        newPlotAction.triggered.connect(lambda: self.changePage_and_execute(1 ,self.cw.mainMultiWidget.leftBar.add_plot))
+        self.fileMenu = self.menu.addMenu("&File")
+        self.fileMenu.addAction(newPlotAction)
+        # ------[ End Menu Bar ]-----
+
+
+        # --------[ ToolBar ]--------
+        self.toolbar = QtGui.QToolBar()
+        self.new_action = QtGui.QAction(QtGui.QIcon("icons/graph.svg"), "New Plot", self)
+        self.new_action.setShortcut("Ctrl+N")
+        self.toolbar.addAction(self.new_action)
+        self.new_action.triggered.connect(self.leftBar.add_plot)
+        self.layout.addWidget(self.toolbar)
+        # ------[ End Tool Bar ]-----
+
+        self.splitter.addWidget(self.leftBar)
+        self.splitter.addWidget(self.area.area)
+        self.layout.addWidget(self.splitter)
+
+
+

+ 55 - 11
base/plotWidget.py

@@ -1,9 +1,11 @@
 import pyqtgraph as pg
+from threading import Thread
 import numpy as np
 from PyQt4 import QtCore, QtGui
 
 import random
 
+
 import kcgwidget as kcgw
 
 LIVE = 1
@@ -16,6 +18,17 @@ class Enum():
             setattr(self, i, self.idx)
             self.idx += 1
 
+class ThreadFFT(Thread):
+    def __init__(self, data, callback_signal):
+        self.data = data
+        self.sig = callback_signal
+        super(ThreadFFT, self).__init__()
+    def run(self):
+        freq = np.fft.rfftfreq(self.data.shape[0])
+        self.sig.emit(np.fft.rfft(self.data), (min(freq), max(freq)))
+
+
+
 plotList = ["Heatmap", "FFT", "Trains", "Combined"]
 PlotType = Enum(*plotList)
 
@@ -52,6 +65,7 @@ class SubPlotWidget(pg.PlotWidget):
         self.img.setLookupTable(lut)
         self.img.setLevels([100, 10000])
 
+    @QtCore.pyqtSlot(np.ndarray, tuple, tuple)
     def plot(self, data, xvalueborders=None, yvalueborders=None):
         self.i+=1
         self.i = self.i % 3
@@ -65,11 +79,15 @@ class SubPlotWidget(pg.PlotWidget):
         if yvalueborders:
             self.img.translate(0, yvalueborders[0])
             self.plotItem.getAxis('left').setScale(yvalueborders[1]/float(data.shape[1]))
+
+        self.plotItem.setClipToView(True)
+        # self.setCacheMode(self.CacheNone)
         # self.box.autoRange(padding=0, items=[self.img])
 
 class PlotWidget(kcgw.KCGWidgets):
     close_signal = QtCore.pyqtSignal()
     change_type_signal = QtCore.pyqtSignal(int, int)
+    fft_done_signal = QtCore.pyqtSignal(np.ndarray, tuple)
     def __init__(self, name=None, unique_id=None, type=None, datatype=None, prefix=None, fName=None):
         super(PlotWidget, self).__init__()
         if name != None:
@@ -85,9 +103,13 @@ class PlotWidget(kcgw.KCGWidgets):
             self.thePrefix = prefix
         if self.theDataType == FILE:
             self.fName = fName
-        self.initUI()
-        self.data = [np.array([[random.random()*i*j+100 for i in xrange(int(1e3))] for j in xrange(int(1e3))]) for k in range(5)]
+
+        self.thread = Thread()
         self.i = 0
+        self.fft_done_signal.connect(self.fft_done)
+        self.initUI()
+        # self.data = [np.array([[np.int16(np.random.random()*i*j/270.+100) for i in xrange(int(184))] for j in xrange(int(270e3))]) for k in range(1)]
+        self.data = np.array([[np.random.randint(100, 100000, 184) for i in xrange(int(2**18))] for i in range(3)])
         self.setWindowTitle(self.theName + " - " + plotList[type] + " - " + str(self.thePrefix))
         self.plot(type)
 
@@ -95,17 +117,27 @@ class PlotWidget(kcgw.KCGWidgets):
         self.plot_widget = SubPlotWidget()
         self.layout = QtGui.QVBoxLayout()
         self.top_sub_layout = QtGui.QHBoxLayout()
-        self.top_sub_layout.addWidget(self.createButton(text="Heatmap", connect=lambda: self.plot(type=0)))
-        self.top_sub_layout.addWidget(self.createButton(text="FFT", connect=lambda: self.plot(type=1)))
-        self.top_sub_layout.addWidget(self.createButton(text="Trains", connect=lambda: self.plot(type=2)))
-        self.top_sub_layout.addWidget(self.createButton(text="Combined", connect=lambda: self.plot(type=3)))
+        self.heatmap_button = self.createButton(text="Heatmap", connect=lambda: self.plot(type=0))
+        self.fft_button = self.createButton(text="FFT", connect=lambda: self.plot(type=1))
+        self.trains_button = self.createButton(text="Trains", connect=lambda: self.plot(type=2))
+        self.combined_button = self.createButton(text="Combined", connect=lambda: self.plot(type=3))
+        self.top_sub_layout.addWidget(self.heatmap_button)
+        self.top_sub_layout.addWidget(self.fft_button)
+        self.top_sub_layout.addWidget(self.trains_button)
+        self.top_sub_layout.addWidget(self.combined_button)
         self.layout.addLayout(self.top_sub_layout)
         self.layout.addWidget(self.plot_widget)
         self.setLayout(self.layout)
         if self.theDataType and self.theDataType == LIVE:
             self.timer = QtCore.QTimer()
             self.timer.timeout.connect(lambda: self.plot(self.theType))
-            self.timer.start(33)
+            self.timer.start(2)
+
+    def disable_buttons(self, b_bool):
+        self.heatmap_button.setDisabled(b_bool)
+        self.fft_button.setDisabled(b_bool)
+        self.trains_button.setDisabled(b_bool)
+        self.combined_button.setDisabled(b_bool)
 
     def plot(self, type):
         self.i += 1
@@ -117,10 +149,18 @@ class PlotWidget(kcgw.KCGWidgets):
 
         if type == PlotType.FFT:
             newData = []
-            for line in self.data[self.i].transpose():
-                newData.append(np.abs(np.fft.rfft(line)[1:]))
-            freq = np.fft.rfftfreq(self.data[self.i].shape[0])
-            self.plot_widget.plot(np.asarray(newData).transpose(), xvalueborders=(min(freq), max(freq)))
+            # for line in self.data[self.i].transpose():
+            #     newData.append(np.abs(np.fft.rfft(line)[1:]))
+            print 'start', self.i, self.data[self.i].shape
+            # newData = np.fft.rfft(self.data[self.i])
+            print 'end', self.i
+
+            # freq = np.fft.rfftfreq(self.data[self.i].shape[0])
+            if not self.thread.is_alive():
+                self.disable_buttons(True)
+                self.thread = ThreadFFT(self.data[self.i], self.fft_done_signal)
+                self.thread.start()
+            # self.plot_widget.plot(np.asarray(newData), xvalueborders=(min(freq), max(freq)))
         if type == PlotType.Heatmap:
             self.plot_widget.plot(self.data[self.i], xvalueborders=(0, 10000), yvalueborders=(0, 0.1))
         if type == PlotType.Trains:
@@ -135,6 +175,10 @@ class PlotWidget(kcgw.KCGWidgets):
                                             [0, 0, 1, 1, 1, 1, 0, 0],
                                             [0, 0, 0, 0, 0, 0, 0, 0]]).transpose())
 
+    @QtCore.pyqtSlot(np.ndarray, tuple)
+    def fft_done(self, data, xborders):
+        self.plot_widget.plot(data, xvalueborders=xborders)
+        self.disable_buttons(False)
 
     def closeEvent(self, event):
         self.close_signal.emit()

+ 71 - 0
base/settings.py

@@ -0,0 +1,71 @@
+from PyQt4 import QtGui, QtCore
+import kcgwidget as kcgw
+
+class Settings(kcgw.KCGWidgets):
+    changed = QtCore.pyqtSignal()
+    def __init__(self, storage_handler):
+        super(Settings, self).__init__()
+        self.storage_handler = storage_handler
+        self.local_storage = []
+
+        # --------[ Instance Objects ]-----------
+        self.layout = QtGui.QGridLayout()
+
+        self.headerTick = self.build_new_setting(QtGui.QCheckBox("Header"), 'header')
+        self.local_storage.append(self.headerTick)
+        self.applyButton = self.createButton("Apply", connect=self.apply)
+        self.okButton = self.createButton("OK", connect=self.ok)
+        self.cancelButton = self.createButton("Cancel", connect=self.cancel)
+        # ------[ End Instance Objects ]--------
+
+        # ------[ Apply Layout and read Settings ] ---------
+        self.initUI()
+        self.setSettings()
+        # ------[ End Apply... ]----------------------------
+
+        self.show()
+
+    def initUI(self):
+        self.setLayout(self.layout)
+
+        self.headerTick.setToolTip("Save header in output file") # TODO: better tooltip
+        self.applyButton.setToolTip("Save settings")
+        self.okButton.setToolTip("Save settings and close settings-window")
+        self.cancelButton.setToolTip("Discard changes and close settings-window")
+        self.layout.addWidget(self.createLabel("Settings"), 0, 0)
+        self.layout.addWidget(self.headerTick, 1, 0)
+        self.layout.addWidget(self.okButton, 2, 0)
+        self.layout.addWidget(self.applyButton, 2, 1)
+        self.layout.addWidget(self.cancelButton, 2, 2)
+
+    def build_new_setting(self, handle, value):
+        self.local_storage.append(handle)
+        self.local_storage[-1].setting_name = value
+        return handle
+
+    def read_setting_from_object(self, object):
+        if type(object).__name__ == 'QCheckBox':
+            return object.isChecked()
+
+    def setSettings(self):
+        self.headerTick.setChecked(self.storage_handler.header)
+
+    def apply(self):
+        hasChanged = False
+        for object in self.local_storage:
+            if self.storage_handler.__getattr__(object.setting_name) != self.read_setting_from_object(object):
+                hasChanged = True
+                self.storage_handler.__setattr__(object.setting_name, self.read_setting_from_object(object))
+        if hasChanged:
+            self.changed.emit()
+
+    def ok(self):
+        self.apply()
+        self.close()
+
+    def cancel(self):
+        self.close()
+
+    def show(self):
+        self.setSettings()
+        super(Settings, self).show()

+ 17 - 19
base/storage.py

@@ -1,21 +1,19 @@
-__author__ = 'blaxxun'
-class Storage():
-    _subWindows = {}
-    remove_plot = None
-    def __init__(self):
-        pass
+class Storage(object):
+    _storage = {}
+    def __init__(self, default=None):
+        if default:
+            self._storage = default
 
-    def register_subwindow(self, name, unique_id, type, window):
-        self._subWindows[unique_id] = [name, type, window]
+    def __setattr__(self, key, value):
+        if key == '_storage':
+            super(Storage, self).__setattr__(key, value)
+        else:
+            self._storage[key] = value
 
-    def get_details_on_subwindow(self, unique_id):
-        return self._subWindows[unique_id]
-
-    def get_window(self, unique_id):
-        return self._subWindows[unique_id][2]
-
-    def genid(self):
-        return len(self._subWindows)
-
-    def unregister(self, unique_id):
-        del self._subWindows[unique_id]
+    def __getattr__(self, item):
+        if item == '_storage':
+            return super(Storage, self).__getattr__(item)
+        if item in self._storage:
+            return self._storage[item]
+        else:
+            raise ValueError("'"+item+"' Not saved in Storage")

+ 102 - 0
icons/application-exit.svg

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 9.0, SVG Export Plug-In  -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="48"
+   height="48"
+   viewBox="0 0 48 48"
+   xml:space="preserve"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.44"
+   sodipodi:docname="application-exit.svg"
+   sodipodi:docbase="/home/luca/Artwork/blackwhite-icon-theme/scalable/actions"
+   version="1.0"><metadata
+   id="metadata27"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>Application Exit</dc:title><dc:subject><rdf:Bag><rdf:li>exit</rdf:li><rdf:li>quit</rdf:li><rdf:li>application</rdf:li><rdf:li>program</rdf:li></rdf:Bag></dc:subject><cc:license
+         rdf:resource="http://creativecommons.org/licenses/LGPL/2.1/" /></cc:Work><cc:License
+       rdf:about="http://creativecommons.org/licenses/LGPL/2.1/"><cc:permits
+         rdf:resource="http://web.resource.org/cc/Reproduction" /><cc:permits
+         rdf:resource="http://web.resource.org/cc/Distribution" /><cc:requires
+         rdf:resource="http://web.resource.org/cc/Notice" /><cc:permits
+         rdf:resource="http://web.resource.org/cc/DerivativeWorks" /><cc:requires
+         rdf:resource="http://web.resource.org/cc/ShareAlike" /><cc:requires
+         rdf:resource="http://web.resource.org/cc/SourceCode" /></cc:License></rdf:RDF></metadata><defs
+   id="defs25">
+		
+	
+			
+			
+			
+			
+			
+			
+			
+		</defs><sodipodi:namedview
+   inkscape:window-height="944"
+   inkscape:window-width="1270"
+   inkscape:pageshadow="2"
+   inkscape:pageopacity="0.0"
+   guidetolerance="10.0"
+   gridtolerance="10.0"
+   objecttolerance="10.0"
+   borderopacity="1.0"
+   bordercolor="#666666"
+   pagecolor="#ffffff"
+   id="base"
+   showgrid="true"
+   inkscape:grid-points="true"
+   inkscape:zoom="12.183333"
+   inkscape:cx="30"
+   inkscape:cy="30"
+   inkscape:window-x="0"
+   inkscape:window-y="25"
+   inkscape:current-layer="svg2"
+   width="48px"
+   height="48px" />
+	<rect
+   style="fill:none;fill-opacity:0.33602151;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+   id="rect1906"
+   width="48"
+   height="48"
+   x="0"
+   y="-2.6645353e-15" /><path
+   style="fill-rule:nonzero;stroke:white;stroke-width:6.05700016;stroke-linejoin:round;stroke-miterlimit:4"
+   d="M 12,4.8 C 10.8,4.8 8.8,6.4 8.8,8 L 8.8,40 C 8.8,41.6 10.4,43.2 12,43.2 L 37.6,43.2 C 39.2,43.2 40.8,41.6 40.8,40 L 40.8,8 C 40.8,6.4 39.2,4.8 37.6,4.8 L 12,4.8 z "
+   id="path7"
+   sodipodi:nodetypes="ccccccccc" /><path
+   style="fill-rule:nonzero;stroke:none;stroke-miterlimit:4"
+   d="M 12,4.8 C 10.4,4.8 8.8,6.4 8.8,8 L 8.8,40 C 8.8,41.6 10.4,43.2 12,43.2 L 37.6,43.2 C 39.2,43.2 40.8,41.6 40.8,40 L 40.8,8 C 40.8,6.4 39.2,4.8 37.6,4.8 L 12,4.8 z "
+   id="path9"
+   sodipodi:nodetypes="ccccccccc" /><path
+   style="fill:white;fill-rule:nonzero;stroke:none;stroke-width:0.75709999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4"
+   d="M 28.1,29.6 C 28.1,30 28,30.2 27.7,30.4 L 13.6,40 C 12.64323,39.828686 11.998969,38.799683 12,38.4 L 12,8.8 C 12,8.4 12.4,8 12.8,8 L 27.5,6.8 C 27.9,6.8 28.2,7.1 28.2,7.5 L 28.2,29.7 L 28.1,29.6 z "
+   id="path13"
+   sodipodi:nodetypes="cccccccccc" /><path
+   style="fill-rule:nonzero;stroke:none;stroke-miterlimit:4"
+   d="M 25.5,15.1 C 25.5,16.3 24.5,17.3 23.3,17.3 C 22.1,17.3 21.1,16.3 21.1,15.1 C 21.1,13.9 22.1,12.9 23.3,12.9 C 24.5,12.9 25.5,13.9 25.5,15.1 L 25.5,15.1 z "
+   id="path15" /><path
+   style="fill:white;fill-rule:nonzero;stroke:none;stroke-miterlimit:4"
+   d="M 12.8,12.3 C 12.2,12.6 12.8,13 12.8,13.6 C 12.8,13.6 12.8,15.1 12.8,16.8 C 10.8,16.8 5.6,16.8 5.6,16.8 C 4,16.8 2.4,18.4 2.4,20 L 2.4,28 C 2.4,29.6 4,31.2 5.6,31.2 C 5.6,31.2 10.8,31.2 12.8,31.2 C 12.8,32.8 12.8,33.6 12.8,33.6 C 12.8,34.3 12.2,34.9 12.8,35.1 C 13.4,35.4 14.1,35.2 14.6,34.7 L 24.2,25.1 C 25.2,24.1 24.9,23 24.2,22.4 L 24.2,22.4 L 14.6,12.7 C 14.1,12.2 13.4,12.1 12.7,12.3 L 12.8,12.3 z "
+   id="path17"
+   sodipodi:nodetypes="ccccccccccccccccc" /><path
+   style="fill-rule:nonzero;stroke:none;stroke-miterlimit:4"
+   d="M 14.4,13.6 C 14.4,13.4 14.6,13.4 14.7,13.5 L 23.2,23.4 C 23.5,23.7 23.4,23.8 23.2,24 L 14.6,33.7 C 14.5,33.8 14.4,33.7 14.4,33.6 L 14.4,28 L 6.4,28 C 6.4,28 5.6,28 5.6,27.2 L 5.6,20.8 C 5.6,20 6.4,20 6.4,20 L 14.4,20 L 14.4,13.6 z "
+   id="path19"
+   sodipodi:nodetypes="ccccccccccccc" />
+	<g
+   id="crop_x0020_marks"
+   style="fill-rule:nonzero;stroke:black;stroke-miterlimit:4">
+		<path
+   style="fill:none;stroke:none"
+   d="M 48,48 L 0,48 L 0,0 L 48,0 L 48,48 z "
+   id="path22" />
+	</g>
+</svg>

+ 3 - 0
icons/chevron-left.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z" transform="translate(1)" />
+</svg>

+ 3 - 0
icons/chevron-right.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z" transform="translate(1)" />
+</svg>

BIN
icons/exit.png


+ 3 - 0
icons/graph.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M7.03 0l-3.03 3-1-1-3 3.03 1 1 2-2.03 1 1 4-4-.97-1zm-7.03 7v1h8v-1h-8z" />
+</svg>

+ 3 - 0
icons/grey/box.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M0 0v1h8v-1h-8zm0 2v5.91c0 .05.04.09.09.09h7.81c.05 0 .09-.04.09-.09v-5.91h-2.97v1.03h-2.03v-1.03h-3z" 
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

+ 3 - 0
icons/grey/chevron-left.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z" transform="translate(1)" 
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

+ 3 - 0
icons/grey/chevron-right.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z" transform="translate(1)" 
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

+ 4 - 0
icons/grey/cog.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M3.5 0l-.5 1.19c-.1.03-.19.08-.28.13l-1.19-.5-.72.72.5 1.19c-.05.1-.09.18-.13.28l-1.19.5v1l1.19.5c.04.1.08.18.13.28l-.5 1.19.72.72 1.19-.5c.09.04.18.09.28.13l.5 1.19h1l.5-1.19c.09-.04.19-.08.28-.13l1.19.5.72-.72-.5-1.19c.04-.09.09-.19.13-.28l1.19-.5v-1l-1.19-.5c-.03-.09-.08-.19-.13-.28l.5-1.19-.72-.72-1.19.5c-.09-.04-.19-.09-.28-.13l-.5-1.19h-1zm.5 2.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5z"
+  
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

+ 3 - 0
icons/grey/graph.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M7.03 0l-3.03 3-1-1-3 3.03 1 1 2-2.03 1 1 4-4-.97-1zm-7.03 7v1h8v-1h-8z" 
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

+ 3 - 0
icons/grey/loop-circular.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M4 0c-1.65 0-3 1.35-3 3h-1l1.5 2 1.5-2h-1c0-1.11.89-2 2-2v-1zm2.5 1l-1.5 2h1c0 1.11-.89 2-2 2v1c1.65 0 3-1.35 3-3h1l-1.5-2z" transform="translate(0 1)" 
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

+ 8 - 0
icons/grey/media-play.svg

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg>
+  <path
+     d="M0 0v6l6-3-6-3z"
+     transform="translate(1 1)"
+     id="path4"
+     style="fill:#bcbcbc;fill-opacity:1" />
+</svg>

+ 3 - 0
icons/grey/plus.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z" 
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

+ 4 - 0
icons/grey/power-standby.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M3 0v4h1v-4h-1zm-1.28 1.44l-.38.31c-.81.64-1.34 1.64-1.34 2.75 0 1.93 1.57 3.5 3.5 3.5s3.5-1.57 3.5-3.5c0-1.11-.53-2.11-1.34-2.75l-.38-.31-.63.78.38.31c.58.46.97 1.17.97 1.97 0 1.39-1.11 2.5-2.5 2.5s-2.5-1.11-2.5-2.5c0-.8.36-1.51.94-1.97l.41-.31-.63-.78z"
+  
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

+ 6 - 0
icons/grey/restore.svg

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg>
+  <path
+     d="m 3.99,0 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 C 2.89,8 1.87,7.57 1.15,6.84 L 1.87,6.12 C 2.41,6.66 3.16,7 4,7 5.66,7 7,5.66 7,4 7,2.34 5.66,1 4,1 3.17,1 2.45,1.36 1.91,1.91 L 3,3 0,3 0,0 1.19,1.19 C 1.91,0.47 2.9,0 4,0 Z"
+     id="path4" 
+style="fill:#bcbcbc;fill-opacity:1"/></svg>

BIN
icons/m.png


+ 3 - 0
icons/plus.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z" />
+</svg>

+ 2 - 0
kcg.py

@@ -3,7 +3,9 @@ import sys
 
 import base.kcg as kcg
 
+gui=None
 def main():
+    global gui
     app = QtGui.QApplication(sys.argv)
     gui = kcg.Gui(app)
     gui.show()

+ 22 - 0
style/style.css

@@ -0,0 +1,22 @@
+#left_switch {
+    border-right: 1px solid grey;
+    border-top: 1px solid grey;
+    border-bottom: 1px solid grey;
+    border-radius: 2px;
+    background-color: rgb(180, 180, 180);
+}
+#right_switch{
+    border-left: 1px solid grey;
+    border-top: 1px solid grey;
+    border-bottom: 1px solid grey;
+    border-radius: 2px;
+    background-color: rgb(180, 180, 180);
+}
+
+#leftright {
+    border: 1px solid grey;
+    border-radius: 2px;
+}
+#leftright:hover {
+    background-color: grey;
+}