Browse Source

semifinal version of kcg

BlaXXuN 8 years ago
commit
e13b8e5c4c
100 changed files with 9186 additions and 0 deletions
  1. 41 0
      .gitignore
  2. 183 0
      KCG/Documentation/Makefile
  3. BIN
      KCG/Documentation/source/Dev/_static/example_widget.png
  4. 119 0
      KCG/Documentation/source/Dev/backendinterface.rst
  5. 105 0
      KCG/Documentation/source/Dev/groupedelements.rst
  6. 14 0
      KCG/Documentation/source/Dev/index.rst
  7. 142 0
      KCG/Documentation/source/Dev/kcgwidget.rst
  8. 31 0
      KCG/Documentation/source/Dev/multiWidget.rst
  9. 26 0
      KCG/Documentation/source/Dev/settings.rst
  10. 174 0
      KCG/Documentation/source/Dev/widgets.rst
  11. 177 0
      KCG/Documentation/source/Makefile
  12. 4 0
      KCG/Documentation/source/Man/_static/.directory
  13. BIN
      KCG/Documentation/source/Man/_static/AcquireSettings.png
  14. BIN
      KCG/Documentation/source/Man/_static/BitsTableView.png
  15. BIN
      KCG/Documentation/source/Man/_static/Controlwidget.png
  16. BIN
      KCG/Documentation/source/Man/_static/Controlwidget_Prepared.png
  17. BIN
      KCG/Documentation/source/Man/_static/Controlwidget_buttons.png
  18. BIN
      KCG/Documentation/source/Man/_static/Controlwidget_buttons.xcf
  19. BIN
      KCG/Documentation/source/Man/_static/MultiView.png
  20. BIN
      KCG/Documentation/source/Man/_static/MultiViewFile.png
  21. BIN
      KCG/Documentation/source/Man/_static/MultiViewFilePlot.png
  22. BIN
      KCG/Documentation/source/Man/_static/MultiViewFilePlotWindow.png
  23. BIN
      KCG/Documentation/source/Man/_static/MultiViewGetThere.png
  24. BIN
      KCG/Documentation/source/Man/_static/MultiViewLive.png
  25. BIN
      KCG/Documentation/source/Man/_static/MultiViewLivePlot.png
  26. BIN
      KCG/Documentation/source/Man/_static/NewPlotQuestion.png
  27. BIN
      KCG/Documentation/source/Man/_static/Sessionname.png
  28. BIN
      KCG/Documentation/source/Man/_static/Settings.png
  29. BIN
      KCG/Documentation/source/Man/_static/SingleAndContinuousRead.png
  30. BIN
      KCG/Documentation/source/Man/_static/TimeScanResult.png
  31. BIN
      KCG/Documentation/source/Man/_static/TimingWidget.png
  32. BIN
      KCG/Documentation/source/Man/_static/TimingWidgetTimeScan.png
  33. BIN
      KCG/Documentation/source/Man/_static/clock.png
  34. BIN
      KCG/Documentation/source/Man/_static/folder.png
  35. BIN
      KCG/Documentation/source/Man/_static/graph.png
  36. BIN
      KCG/Documentation/source/Man/_static/project.png
  37. BIN
      KCG/Documentation/source/Man/_static/wrench.png
  38. 38 0
      KCG/Documentation/source/Man/acquisition.rst
  39. 28 0
      KCG/Documentation/source/Man/config.rst
  40. 19 0
      KCG/Documentation/source/Man/index.rst
  41. 80 0
      KCG/Documentation/source/Man/overview.rst
  42. 76 0
      KCG/Documentation/source/Man/plots.rst
  43. 19 0
      KCG/Documentation/source/Man/settings.rst
  44. 31 0
      KCG/Documentation/source/Man/singleandcontinuousread.rst
  45. 54 0
      KCG/Documentation/source/Man/timing.rst
  46. 30 0
      KCG/Documentation/source/Requirements.rst
  47. 17 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/__init__.py
  48. 23 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/breadcrumbs.html
  49. 36 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/footer.html
  50. 181 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/layout.html
  51. 205 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/layout_old.html
  52. 50 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/search.html
  53. 9 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/searchbox.html
  54. 0 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/css/badge_only.css
  55. 0 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/css/theme.css
  56. BIN
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/Inconsolata-Bold.ttf
  57. BIN
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/Inconsolata.ttf
  58. 1 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/Lato-Bold.ttf
  59. 1 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/Lato-Regular.ttf
  60. BIN
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/RobotoSlab-Bold.ttf
  61. BIN
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/RobotoSlab-Regular.ttf
  62. BIN
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot
  63. 1 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg
  64. 1 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf
  65. 1 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff
  66. 3 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/js/modernizr.min.js
  67. 113 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/static/js/theme.js
  68. 9 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/theme.conf
  69. 37 0
      KCG/Documentation/source/_themes/sphinx_rtd_theme/versions.html
  70. 336 0
      KCG/Documentation/source/conf.py
  71. 34 0
      KCG/Documentation/source/index.rst
  72. 1 0
      KCG/Documentation/svg_png.sh
  73. 1 0
      KCG/VERSION
  74. 0 0
      KCG/__init__.py
  75. 4 0
      KCG/base/__init__.py
  76. 0 0
      KCG/base/backend/__init__.py
  77. 16 0
      KCG/base/backend/board/__init__.py
  78. 75 0
      KCG/base/backend/board/actions.py
  79. 386 0
      KCG/base/backend/board/board_config.py
  80. 153 0
      KCG/base/backend/board/boards_connected.py
  81. 195 0
      KCG/base/backend/board/communication.py
  82. 22 0
      KCG/base/backend/board/errors.py
  83. 151 0
      KCG/base/backend/board/sequences.py
  84. 18 0
      KCG/base/backend/board/status.py
  85. 147 0
      KCG/base/backend/board/utils.py
  86. 165 0
      KCG/base/backend/dataset.py
  87. 155 0
      KCG/base/backend/io.py
  88. 1444 0
      KCG/base/backendinterface.py
  89. 443 0
      KCG/base/bitsTable.py
  90. 99 0
      KCG/base/callbacks.py
  91. 360 0
      KCG/base/controlwidget.py
  92. 11 0
      KCG/base/globals.py
  93. 277 0
      KCG/base/groupedelements.py
  94. 496 0
      KCG/base/kcg.py
  95. 647 0
      KCG/base/kcgwidget.py
  96. 681 0
      KCG/base/leftbar.py
  97. 343 0
      KCG/base/log.py
  98. 105 0
      KCG/base/loghandler.py
  99. 142 0
      KCG/base/multiWidget.py
  100. 200 0
      KCG/base/multipage.py

+ 41 - 0
.gitignore

@@ -0,0 +1,41 @@
+*.py[cod]
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+__pycache__
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# pycharm
+.idea
+
+backup.kcf

+ 183 - 0
KCG/Documentation/Makefile

@@ -0,0 +1,183 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+UPLOAD_URL    = psraspi.no-ip.biz
+UPLOAD_PATH   = /var/www/kcg
+UPLOAD_USER   = www-data
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/KCG.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/KCG.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/KCG"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/KCG"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
+
+upload:
+	scp -r build/* $(UPLOAD_USER)@$(UPLOAD_URL):$(UPLOAD_PATH)

BIN
KCG/Documentation/source/Dev/_static/example_widget.png


+ 119 - 0
KCG/Documentation/source/Dev/backendinterface.rst

@@ -0,0 +1,119 @@
+.. _backendinterface:
+
+BackendInterface Module
+=======================
+
+.. module:: backendinterface
+
+This module contains several methods to interface with the board. It also contains several mostly internal
+used methods prefixed `_bif_`. Those methods will not be covered here and are not intended for public use.
+Methods intended for public use are prefixed `bk_`.
+
+.. py:method:: bk_start_board()
+
+    Method that handles starting of the board.
+
+.. py:method:: bk_calibrate()
+
+    Method that handles calibration of the board.
+
+.. py:method:: bk_sync_board()
+
+    Method that handles synchronisation of the board.
+
+.. py:method:: bk_write_values(defaults=False)
+
+    Method that handles writing values to the board.
+    :param bool defaults: If True default values will be written (and updated in board.config)
+
+.. py:method:: bk_stop_board()
+
+    Method that handles stopping of the board.
+
+.. py:method:: bk_soft_reset()
+
+    Method that handles soft resetting the board.
+
+.. py:method:: bk_update_config(key, value)
+
+    Method that updates the board config.
+
+    :param str key: Config key
+    :param any value: New value
+
+.. py:method:: bk_get_config(key)
+
+    Method to get the current config for key
+
+    :param str key: Config key
+
+.. py:method:: bk_change_num_of_orbits(value, [silent=False])
+
+    Method to change the number of orbits to observe on the board
+
+    :param int value: Number of orbits to observe
+    :param bool silent: Do not notify observers when updating the config
+
+.. py:method:: bk_change_num_of_skipped_orbits(value[, silent=False])
+
+    Method to change the number of orbits to skip on the board
+
+    :param int value: Number of orbits to skip
+    :param bool silent: Do not notify observers when updating the config
+
+.. py:method:: bk_change_count(value[, silent=False])
+
+    Method to change the number of acquisitions
+
+    :param int value: Number of acquisitions
+    :param bool silent: Do not notify observers when updating the config
+
+.. py:method:: bk_change_wait(value[, silent=False])
+
+    Method to change the wait time between acquisitions
+
+    :param int value: Wait time in seconds
+    :param bool silent: Do not notify observers when updating the config
+
+.. py:method:: bk_change_build_spectrograms(value[, silent=False])
+
+    Method to set whether to build spectrograms or not
+
+    :param bool value: Build or not
+    :param bool silent: Do not notify observers when updating the config
+
+.. py:method:: bk_change_pilot_bunch(value[, silent=False])
+
+    Method to set whether to simulate pilot bunch or not
+
+    :param bool value: Simulate or not
+    :param bool silent: Do not notify observers when updating the config
+
+.. py:method:: bk_acquire()
+
+    Toggle acquisition
+
+.. py:method:: bk_single_read()
+
+    Perform a single read
+
+.. py:method:: bk_continuous_read([interval=100])
+
+    Toggle continuous read
+
+    :param int interval: Interval in msec to wait between reads
+
+.. py:method:: bk_board_connected()
+
+    Check if board is connected and recognized
+
+    :return: True if board is connected and recognized, False if not
+    :rtype: bool
+
+.. py:method:: bk_get_temperature()
+
+    Get the current temperature of the board
+
+    :return: Current board temperature in degree celsius
+    :rtype: int
+

+ 105 - 0
KCG/Documentation/source/Dev/groupedelements.rst

@@ -0,0 +1,105 @@
+.. _groupedelements:
+
+Grouped Elements Module
+=======================
+
+.. module:: groupedelements
+
+.. py:class:: GroupWarning
+
+    This is a simple Warning issued when elements or groups are not existend
+
+.. py:class:: GroupedObjects
+
+    Class that keeps track of the groups and its objects and offers methods to enable/disable objects
+
+    .. py:method:: __init__(self)
+
+        Initialising the GroupedElementsObject
+
+    .. _add_item:
+
+    .. py:method:: addItem(self, group, item)
+
+        Add an element `item` to group `group`
+
+        :param str group: Desired group
+        :param any item: Item to add
+
+    .. py:method:: setChecked(self, group, state)
+
+        For CheckBoxes this will set the state of all Checkboxes in `group` to `state`.
+        This calls the setChecked method for all elements in group.
+
+        :param str group: Desired group
+        :param bool state: New state of the Checkboxes
+
+    .. py:method:: setEnabled(self, group, state)
+
+        Sets the elements to Enabled/Disabled if state is True/False.
+        This calls the setEnabled method for all elements in group.
+
+        :param str group: Desired group
+        :param bool state: New State
+
+    .. py:method:: addMenuItem(self, group, item)
+
+        Deprecated use :ref:`addItem <add_item>`.
+        Remains for backwards compatibility.
+
+    .. py:method:: addButton(self, group, item)
+
+        Deprecated use :ref:`addItem <add_item>`.
+        Remains for backwards compatibility.
+
+    .. py:method:: addCheckbox(self, group, item)
+
+        Deprecated use :ref:`addItem <add_item>`.
+        Remains for backwards compatibility.
+
+    .. py:method:: removeItem(self, group, item)
+
+        Remove an item from a group.
+
+        :param str group: Desired group
+        :param any item: Item to remove
+
+    .. py:method:: removeGroup(self, group)
+
+        Remove a group.
+
+        :param str group: Group to remove
+
+    .. py:method:: getElements(self, group)
+
+        Returns list of all elements in a group
+
+        :param str group: Desired group
+
+        :return: List of all elements in group
+        :rtype: list
+
+    .. py:method:: createEmptyGroup(self, group)
+
+        Creates a empty group
+
+        :param str group: Desired Group
+
+    .. py:attribute:: Buttons
+
+        GroupedObjects instance see :ref:`Elements <elements>`
+
+    .. py:attribute:: Checkboxes
+
+        GroupedObjects instance see :ref:`Elements <elements>`
+
+    .. py:attribute:: MenuItems
+
+        GroupedObjects instance see :ref:`Elements <elements>`
+
+    .. _elements:
+
+    .. py:attribute:: Elements
+
+        GroupedObjects instance. This is the same instance as Buttons, Checkboxes, MenuItems. Those are only
+        references to the same object and are only used to improve readability.

+ 14 - 0
KCG/Documentation/source/Dev/index.rst

@@ -0,0 +1,14 @@
+Developer documentation
+=======================
+
+KCG - KAPTURE Control Gui is the Graphical user interface for the KAPTURE board.
+
+.. toctree::
+    :maxdepth: 2
+
+    widgets
+    multiWidget
+    kcgwidget
+    groupedelements
+    backendinterface
+    settings

+ 142 - 0
KCG/Documentation/source/Dev/kcgwidget.rst

@@ -0,0 +1,142 @@
+KCGWidget Module
+================
+
+.. module:: kcgwidget
+
+This module is the backbone of the Gui.
+
+Classes
+-------
+
+.. _kcgwidgets_helper_methods:
+
+.. py:class:: KCGWidgets
+
+    This class is the base class to most of the used widget classes in the Gui.
+
+    .. py:method:: __init__(self [, name, parent ])
+
+        :param str name: Name of the widget
+        :param object parent: Parent object
+
+    .. py:method:: createButton(self [, text, x, y, dimensions, tooltip, connect, icon ])
+
+        Create a simple PushButton
+
+        :param str text: Text to be displayed on the button
+        :param int x, y: Coordinates of the Button (only if used without layout)
+        :param PyQt4.QtCore.QSize) dimensions: Size of the button
+        :param str tooltip: Tooltip
+        :param callable connect: Callable to call when button is pressed
+        :param PyQt4.QtGui.QIcon icon: Icon to display on the button
+
+        :return: New Button
+        :rtype: PyQt4.QtGui.QPushButton
+
+    .. py:method:: createLabel(self [, text, image, tooltip, [click=False, connect ] ])
+
+        Create a simple Label
+
+        :param str text: Text for the Label
+        :param PyQt4.QtGui.QPixmap) image: Pixmap to set as image
+        :param str tooltip: Tooltip
+        :param bool click: Whether this label is clickable or not
+        :param callable connect: If the label is clickable, connect the click signal to this callable
+
+        :return: New Label
+        :rtype: PyQt4.QtGui.QLabel
+
+
+    .. py:method:: createSpinbox(self, min, max [, interval=1, start_value=0, connect ])
+
+        Create a simple Spinbox
+
+        :param int min: Minimum value
+        :param int max: Maximum value
+        :param int interval: The Stepwidth
+        :param int start_value: Initial Value
+        :param callable connect: Connect changed signal to this callable
+
+        :return: Integer selection input field
+        :rtype: PyQt4.QtGui.QSpinbox
+
+
+    .. py:method:: createInput(self [, text, read_only=False, width ])
+
+        Create a simple text input field
+
+        :param str text: Initial text in the input field
+        :param bool read_only: Whether this field is editable
+        :param int width: Width of this field in pixels (None means auto-width)
+
+        :return: Text input field
+        :rtype: PyQt4.QtGui.QLineEdit
+
+
+
+    .. py:method:: createCheckbox(self [, text, tooltip, checked=False, connect ])
+
+        Create a simple Checkbox
+
+        :param str text: Text to show next to the checkbox
+        :param str tooltip: Tooltip
+        :param bool checked: Initial state of the checkbox
+        :param callable connect: Connect changed signal to this callable
+
+        :return: New Checkbox
+        :rtype: PyQt4.QtGui.QCheckBox
+
+    .. py:attribute:: theType
+
+        Type of the widget (int)
+
+    .. py:attribute:: theId
+
+        Unique ID of the widget (int)
+
+    .. py:attribute:: theName
+
+        Name of the widget (str)
+
+.. py:class:: BigIconButton
+
+    Button with a big Icon
+
+    .. py:method:: __init__(self, path, width, height [, connect, tooltip, parent ])
+
+
+        Initialize and create a BigIconButton
+
+        :param str path: Path to the icon file
+        :param int width: Width
+        :param int height: height
+        :param callable connect: Connect clicked signal to this callable
+        :param str tooltip: Tooltip
+        :param object parent: Parent object
+
+.. py:class:: clickLabel
+
+    Clickable Label
+
+
+
+.. _kcgwidget_register_widget:
+
+Registering Widgets
+-------------------
+
+.. py:method:: register_widget(icon, text, target, [shortcut])
+
+    When a widget is registered, all necessary steps to add a toolbar button and a menu entry for opening the widget are
+    automatically performed at the correct places.
+
+    :param PyQt4.QtGui.QIcon icon: The icon to use in the toolbar and menu.
+    :param str text: Text to use as tooltip for the toolbar button and as text in the menu.
+    :param callable target: The function that "opens" the widget see :ref:`add_widget_function`.
+    :param str shortcut: Keyboard shortcut to open the widget.
+
+.. py:method:: register_widget_creation_function(creation_func)
+
+    Use this function to register callables that have to be called after creation of the Gui
+
+    :param callable creation_func: The function that is to be called.

+ 31 - 0
KCG/Documentation/source/Dev/multiWidget.rst

@@ -0,0 +1,31 @@
+Multi Widget Module
+===================
+
+.. module:: multiWidget
+
+This module containts the definition of the MultiWidget Area and Toolbar
+
+.. class:: MDIArea
+
+    This class is the center of the MultiWidget. An Instance of this class is accessible throughout the whole Gui after
+    importing ``base.kcgwidget`` as ``base.kcgwidget.area``
+
+    .. _newWidget:
+
+    .. py:method:: newWidget(self, widget, name, unique_id, widget_type[, minSize=False])
+
+        This function adds the provided widget to the MultiWidget Area. It also handles all the things necessary to cleanly
+        close the widget from the viewpoint of the Area.
+
+        :param subclass of KCGWidgets widget: The widget that is to be added as subwindow
+        :param str name: The name of the widget
+        :param int unique_id: the unique id of the widget that is unique throughout the whole Gui
+        :param int widget_type: the type of the widget
+        :param bool minSize: Whether the window has a fixed minimum size or not
+
+Adding a Toolbar Button for a new Widget
+''''''''''''''''''''''''''''''''''''''''
+
+To add a Toolbar button for a new Widget you have to register the widget with the kcgwidget module:
+
+See :ref:`kcgwidget_register_widget`

+ 26 - 0
KCG/Documentation/source/Dev/settings.rst

@@ -0,0 +1,26 @@
+Settings
+========
+
+.. module:: settings
+
+.. py:class:: Settings
+
+    This is the settings window class.
+
+    You should not subclass or create instances of this class.
+
+    .. py:method:: build_new_setting(self, handle, value)
+
+        Method to build a new settings entry
+
+        :param object handle: The settins object (e.g. Input field or Checkbox etc.)
+        :param str value: The name of the setting
+        :return: handle
+
+Add new setting
+~~~~~~~~~~~~~~~
+
+As of this version settings can only be added for board configuration elements.
+
+To add a new settings entry, create the settings element in the ``__init__`` method using ``self.build_new_setting(...)``.
+Then add the new element in the ``initUI`` method to the layour.

+ 174 - 0
KCG/Documentation/source/Dev/widgets.rst

@@ -0,0 +1,174 @@
+New Widgets
+===========
+
+KCG is written in Python using PyQt4. Therefore all Widgets are and have to be written in Python with PyQt4 for the
+Gui toolkit.
+
+Creating Widgets
+----------------
+
+The following things are mandatory:
+
+    * A widget has to be defined in it's own class in it's own seperate module (file).
+    * The class for the widget has to be derived from base.kcgwidget.KCGWidgets.
+    * The __init__ method has to set self.par from `parent` parameter and self.id from `unique_id` parameter. It also has to call the KCGWidgets __init__ method. See :ref:`here <exampleInit>`.
+    * The module has to contain the variable __widget_id__ that is initially set to `None`
+    * The module has to contain a function to add the widget to the MultiWidget area as described :ref:`here <add_widget_function>`.
+    * The class has to override the method closeEvent(...). See :ref:`here <closeEvent>`.
+    * The module has to ``import base.kcgwidget as kcgw``
+
+
+.. note:: To activate a widget it has to be placed into the widget subfolder and the module name has to be added to the __all__
+    variable in the file ``__init__.py`` in the widget folder.
+
+
+To register a widget this line is needed:
+
+.. code:: python
+
+    kcgw.register_widget(QtGui.QIcon("path/to/icon"), "Name of Widget", addWidgetFunction, "Ctrl+e")
+
+Substitute ``"path/to/icon"``, ``"Name of Widget"``, ``addWidgetFunction`` (see :ref:`add_widget_function`) and ``"Ctrl+e"`` with
+appropriate values. For ``kcgw.register_widget()`` refer to the :ref:`kcgwidget module <kcgwidget_register_widget>`.
+
+.. note:: Collision of keyboard shortcuts is not handled by the gui.
+
+If a widget is activated and registered it will automatically be imported and the correct steps to integrate the widget are performed.
+
+
+.. hint:: Because every widget is derived from base.kcgwidget.KCGWidgets the helper methods defined :ref:`here <kcgwidgets_helper_methods>`
+    can be used.
+
+
+.. _exampleInit:
+
+Minimal __init__
+~~~~~~~~~~~~~~~~
+
+Assume: Widget class is called `ExampleWidget`
+
+.. code:: python
+
+    def __init__(self, unique_id, parent):
+        super(ExampleWidget, self).__init__()
+        self.id = unique_id
+        self.par = parent
+
+.. _add_widget_function:
+
+Function to add a widget
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Assume: base.kcgwiget is imported as kcgw
+
+.. code:: python
+
+    def addNewWidget():
+        global __widget_id__
+        if __widget_id__:
+            kcgw.area.widgets[__widget_id__].setFocus()
+        else:
+            nid = kcgw.idg.genid()
+            __widget_id__ = nid
+            w = acquireSettings(nid, kcgw.area)
+            kcgw.area.newWidget(w, "NewWidgetName", nid, widget_type=NewWidgetType_as_int, minSize=True)
+
+
+.. note:: It is highly recommended to use this function as is (with adjustments to the function name, ``NewWidgetName``
+ and ``NewWidgetType_as_int``). For ``newWidget(..)`` see :ref:`here <newWidget>`.
+
+.. _closeEvent:
+
+closeEvent method
+~~~~~~~~~~~~~~~~~
+
+.. code:: python
+
+    def closeEvent(self, event):
+        global __widget_id__
+        __widget_id__ = None
+        del self.par.widgets[self.id]
+
+This is a minimal construct that has to be present in a widget class.
+
+Interface to the GUI
+--------------------
+
+Sometimes elements (e.g. Buttons) have to be disabled at certain points. To group elements you can make use of the
+groupelements module.
+
+See :ref:`groupedelements`.
+
+Following is a list of used groups that will be activated and disabled at certain points in runtime:
+
+    * **after_start**: Elements in this group get enabled after board is started
+    * **continuous_read**: Elements in this group get enabled when a continuous read is possible
+    * **synchronize**: Elements in this group get enabled when the board is calibrated
+    * **set_defaults**: Elements in this group get enabled when the board is synchronized
+    * **timing**: Elements in this group get enabled when defaults are set
+
+.. caution:: Be careful when using one of this groups, as the names may mislead their point of activation.
+
+
+Interface to the board
+----------------------
+
+It is recommended to use the :ref:`backendinterface` to interface with the board
+
+Example Widget
+--------------
+
+This is a minimal example implementation of a widget.
+
+.. code:: python
+
+    from PyQt4 import QtGui
+
+    import base.kcgwidget as kcgw
+
+    __widget_id__ = None
+
+    class exampleWidget(kcgw.KCGWidgets):
+        def __init__(self, unique_id, parent):
+            super(exampleWidget, self).__init__()
+
+            self.id = unique_id
+            self.par = parent
+
+            self.layout = QtGui.QHBoxLayout()
+            self.setLayout(self.layout)
+
+            self.button1 = self.createButton("Button 1", connect=self.pressed)
+            self.button2 = self.createButton("Button 2", connect=self.pressed)
+
+            self.layout.addWidget(self.button1)
+            self.layout.addWidget(self.button2)
+
+            self.setWindowTitle("Example Widget")
+
+        def pressed(self):
+            print 'Pressed'
+
+        def closeEvent(self, event):
+            global __widget_id__
+            __widget_id__ = None
+            del self.par.widgets[self.id]
+
+    def addExampleWidget():
+        global __widget_id__
+        if __widget_id__:
+            kcgw.area.widgets[__widget_id__].setFocus()
+        else:
+            nid = kcgw.idg.genid()
+            __widget_id__ = nid
+            w = exampleWidget(nid, kcgw.area)
+            kcgw.area.newWidget(w, "Example Widget", nid, widget_type=4)
+
+    kcgw.register_widget(QtGui.QIcon("icons/project.svg"), "Example Widget", addExampleWidget, "Ctrl+e")
+
+Screenshot:
+
+.. image:: _static/example_widget.png
+
+.. caution:: Be careful when refering to this as this is a screenshot made with kde5 and PyQt4.
+        The design of the actual widget may vary between different desktop environments.

+ 177 - 0
KCG/Documentation/source/Makefile

@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/KCG.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/KCG.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/KCG"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/KCG"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

+ 4 - 0
KCG/Documentation/source/Man/_static/.directory

@@ -0,0 +1,4 @@
+[Dolphin]
+PreviewsShown=true
+Timestamp=2015,7,2,20,22,43
+Version=3

BIN
KCG/Documentation/source/Man/_static/AcquireSettings.png


BIN
KCG/Documentation/source/Man/_static/BitsTableView.png


BIN
KCG/Documentation/source/Man/_static/Controlwidget.png


BIN
KCG/Documentation/source/Man/_static/Controlwidget_Prepared.png


BIN
KCG/Documentation/source/Man/_static/Controlwidget_buttons.png


BIN
KCG/Documentation/source/Man/_static/Controlwidget_buttons.xcf


BIN
KCG/Documentation/source/Man/_static/MultiView.png


BIN
KCG/Documentation/source/Man/_static/MultiViewFile.png


BIN
KCG/Documentation/source/Man/_static/MultiViewFilePlot.png


BIN
KCG/Documentation/source/Man/_static/MultiViewFilePlotWindow.png


BIN
KCG/Documentation/source/Man/_static/MultiViewGetThere.png


BIN
KCG/Documentation/source/Man/_static/MultiViewLive.png


BIN
KCG/Documentation/source/Man/_static/MultiViewLivePlot.png


BIN
KCG/Documentation/source/Man/_static/NewPlotQuestion.png


BIN
KCG/Documentation/source/Man/_static/Sessionname.png


BIN
KCG/Documentation/source/Man/_static/Settings.png


BIN
KCG/Documentation/source/Man/_static/SingleAndContinuousRead.png


BIN
KCG/Documentation/source/Man/_static/TimeScanResult.png


BIN
KCG/Documentation/source/Man/_static/TimingWidget.png


BIN
KCG/Documentation/source/Man/_static/TimingWidgetTimeScan.png


BIN
KCG/Documentation/source/Man/_static/clock.png


BIN
KCG/Documentation/source/Man/_static/folder.png


BIN
KCG/Documentation/source/Man/_static/graph.png


BIN
KCG/Documentation/source/Man/_static/project.png


BIN
KCG/Documentation/source/Man/_static/wrench.png


+ 38 - 0
KCG/Documentation/source/Man/acquisition.rst

@@ -0,0 +1,38 @@
+Acquisition
+===========
+
+.. _acquire_settings_window:
+
+Acquisition settings window
+---------------------------
+
+To configure acquisitions open the acquisition settings subwindow. You can do so by clicking on the second toolbar
+button or in the menu under Windows -> Acquire Settings. The keyboard shortcut is Ctrl+A.
+
+Another way to open this window is to click on text on the bottom right part of the window just above the Acquire Button.
+
+Toolbar Symbol for acquisition settings:
+
+.. image:: _static/wrench.png
+
+The acquisition settings subwindow looks as follows.
+
+.. figure:: _static/AcquireSettings.png
+    :alt: Acquisition settings subwindow
+
+Here you can adjust how many orbits you want to observe, how many orbits you want to skip between each observed
+orbit, how many of those acquisitions you want to take and how long to wait between each acquisition.
+
+You can also enable or disable the pilot bunch generator.
+
+# TODO: pilot bunch generator and spectrograms
+
+Acquisition
+-----------
+
+After setting the desired values in the :ref:`acquire_settings_window` you can start the acquisition by clicking on the
+button in the bottom right corner of the window. You can also start it by using the menu: Acquire -> Start Acquisition.
+
+The acquired data will be saved in the location specified in the config.py file. See :ref:`config` for more details.
+
+If you want to change that location during runtime of the Gui, you can do so in the :ref:`settings`.

+ 28 - 0
KCG/Documentation/source/Man/config.rst

@@ -0,0 +1,28 @@
+.. _config:
+
+Configuration File
+==================
+
+KCG uses values saved in a configuration file. This file offers basic configuration possibilities.
+There are two files for configuration, one is the default file in the installation directory and the other
+is the user configuration file located in ~/.kcg/cofig.cfg.
+
+.. important:: Never change the default configuration file. Custom configuration always belongs into the
+        user configuration file ~/.kcg/config.cfg
+
+The possible variables for the general behaviour of the gui are listed below
+
+    * language - The language of the Gui
+    * default_save_location - The location where all your acquisitions will be saved (in subdirectories)
+    * default_subdirectory_name - The default pattern for the subdirectory name
+    * force_ask - Whether to ask accept "Cancel" when asked for subdirectory name parts
+    * show_advanced_control - Whether to show the advanced table view as default
+    * several variables holding the location of icons used for toolbars etc.
+
+There are some variables that affect the board
+
+    * bunches_per_turn - How many bunches fit in your ring
+    * save_header - whether to save the header to datafiles as default or not
+    * tRev - Revolution time in your ring
+
+.. note:: Check the configuration file and its comments for other variables and more important for possible values

+ 19 - 0
KCG/Documentation/source/Man/index.rst

@@ -0,0 +1,19 @@
+.. KCG documentation master file, created by
+   sphinx-quickstart on Wed Jul  1 18:18:47 2015.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+User documentation!
+===================
+
+.. toctree::
+   :maxdepth: 2
+
+   overview
+   plots
+   acquisition
+   singleandcontinuousread
+   timing
+   settings
+   config
+

+ 80 - 0
KCG/Documentation/source/Man/overview.rst

@@ -0,0 +1,80 @@
+Overview
+========
+
+Starting the Gui
+----------------
+
+When the Gui is started, it reads the config file (see :ref:`config`).
+
+If configured to ask for a sessionname or similar
+the Gui will ask on Startup using a small popup window that appears before the main Window will show.
+
+|gui_name| consists of mainly two page style views. You can change them by clicking on the large arrowhead shaped
+icons on the left and right of the gui or by clicking on the same icons in the top right hand corner.
+
+
+Control View
+------------
+
+|gui_name| will start in control view.
+
+Located in this View are several Buttons to control KAPTURE Boards.
+
+    #. :ref:`skip_init` - Skip the intialisation process.
+    #. Prepare Board - This Starts, Calibrates and Synchronizes the Board and sets Default values used for operation.
+    #. Soft Reset - Perform a Soft Reset
+    #. Board Off - Shut the Board off
+    #. Down Arrow - This will expand an advanced Control interface. The following buttons will be visible
+    #. Start Board - Send the Board the Signal to Start
+    #. Calibrate - Send the Board the Signal to Calibrate itself
+    #. Synchronize - Send the Board the Signal to Synchronize components
+    #. Set Defaults - Send the Board the Default Values
+    #. Check Status - Check the Status of the Board
+
+.. _skip_init:
+
+Skip Initialisation
+'''''''''''''''''''
+
+If you need to restart the Gui but do not want to do a full preparation (which will reset values you set before) you
+can use Skip Initialisation. This reads all necessary values from the Board and puts the Gui in a state where you can
+use it as if you hadn't closed it.
+
+.. caution:: Skip Initialisation is dangerous. If you did not prepare the board before using Skip Initialisation
+            It won't work properly if at all.
+
+Multi View
+----------
+
+Multi View is the second view of |gui_name|. This ist the main view during operation. From here
+you can control the boards various settings via sub-windows and you can start or stop acquisitions.
+
+Each sub-window controls different aspects of the board. To open them you can either click on one of the toolbar
+buttons at the top, use the main windows menu or use keyboard shortcuts (which are shown as tooltips on the
+toolbar buttons).
+
+You see on the left a pane with an (at the beginning) empty white box. This box is or will be the
+list of open plot windows. When ever you open a plot window (be it for live or for data plots) it will appear
+here and you can left click on each entry to bring this plot window to focus.
+
+Below that white box is a information section about the different connected boards. Clicking on the text there will
+open the acquire settings window.
+
+At the bottom of the pane on the left side are Buttons and tickboxes (if more than one board is connected) to start
+acquisitions or to create a manual measurement-log entry.
+
+Advanced Table View
+-------------------
+
+This View lets you view the current content of different registers on the board.
+
+To enable this View simply enable the "Enable Advanced Table View" option in the settings (see :ref:`settings`).
+
+Here you can also set certain bits in the editable register at the bottom of this view.
+
+.. note::
+    To actually write the values in the editable registertable to the board you have to press `Write Values to Board`.
+
+.. caution::
+    `Clear Input` will only clear the ticks in the gui. It will not change values on the board.
+

+ 76 - 0
KCG/Documentation/source/Man/plots.rst

@@ -0,0 +1,76 @@
+.. _plots:
+
+Plots
+=====
+
+KCG supports two types of plots - live data and saved data read from disk.
+
+To open a plot window select Windows -> New Plot in the Menu.
+
+
+You will be asked what type of data to open. The choices are: Live Data and Data read from disk.
+
+.. tip::
+    You can open each of those data types directly as described in the corresponding section below.
+
+Live Data
+---------
+
+If you choose Live Data you can go on with :ref:`openingplotwindows`
+
+To directly open a Live Data Plot press the first toolbar button or press Ctrl+L.
+
+Toolbar Symbol for new Live Plots:
+
+.. image:: _static/graph.png
+
+
+
+Data from disk
+--------------
+
+If you choose to read data from disk, you have to specify the file to read.
+
+To directly open a Data-from-disk Plot press the second toolbar button or press Ctrl+D.
+
+Toolbar Symbol for new Data Plots:
+
+.. image:: _static/folder.png
+
+
+
+.. _openingplotwindows:
+
+Opening Plot Windows
+--------------------
+
+After you chose the data type, you can open multiple plot Windows with different types. To do so, click on the "+"
+next to the newly created entry in the left listview. If you open a live plot window, data gets plottet whenever
+it is ready (that is when you acquire, when continuous read is enabled or when you perform a single read).
+
+.. note::
+    When you performed an acquisition and after that you open a live plot window, the last saved file
+    will be read and plotted in this live plot window
+
+You can choose the plot type as seen in the image below (It is shown for saved data, but analogous for live data):
+
+.. image:: _static/MultiViewFilePlot.png
+
+Heatmap means to plot the data in a 2D plot with color coded values.
+
+FFT means to plot the fourertransformed data in a 2D plot with color coded values.
+
+Trains means to plot the trains that were measured
+
+As of version 0.1.0915 there is an additional plot type: Compare. This means to plot two Heatmaps of different
+ADCs to compare those.
+
+Combined means to plot #TODO: was ???
+
+.. note::
+    If you have opened multiple windows it can be hard to find the correct window. That's where the left
+    part of the Multi View comes in handy: Simply click on the desired plot and it will be brought to focus.
+
+.. note::
+    You can close both, the windows and the data source, by right clicking on the corresponding entry in the left
+    part of the Multi View

+ 19 - 0
KCG/Documentation/source/Man/settings.rst

@@ -0,0 +1,19 @@
+.. _settings:
+
+Settings
+========
+
+There are a few settings available in the Settings window which looks as follows.
+
+.. figure:: _static/Settings.png
+    :alt: Settings window
+
+You can set whether the board will save a header containing basic metadata.
+
+You can set the location where the datafiles are saved. Save Location means the location where all the subdirectories containing
+the actual datafiles will be saved. Subdirectory name is the name of the subdirectory in Save Location in which
+the datafiles will be saved.
+
+You can set the language of the gui. Changing the language will take effect after a restart of the Gui.
+
+You can set whether the advanced Register view table is shown as view.

+ 31 - 0
KCG/Documentation/source/Man/singleandcontinuousread.rst

@@ -0,0 +1,31 @@
+Single and Continuous Read
+==========================
+
+Single read means to only take a "snapshot".
+
+Continuous Read means to read the detector periodically and possibly plot the data (if a Live plot window is open, see :ref:`plots`)
+but do not save the data.
+
+To open the single and continuous read window you can click on the third toolbar button, use the menu via Windows ->
+Single Read or press Ctrl+i on your keyboard.
+
+Toolbar Symbol for single and continuous read:
+
+.. image:: _static/project.png
+
+The single and continuous read window looks as follows
+
+.. figure:: _static/SingleAndContinuousRead.png
+    :alt: Single and Continous Read window
+
+# TODO: update SingleAndContinuousRead.png because Interval is without (ms)
+
+The first button `Single Read` will perform a single read.
+
+The second button `Start Continuous Read` will enable continuous read. That means it reads data periodically. To set
+the time between each read, you can use the field below the button, which specifies the time in milliseconds.
+
+To stop continuous read you click on the same button as you did to start it. The button is now labeled `Stop Continuous Read`.
+
+If a live plot window is open (see :ref:`plots`) the data gathered with either one of the above methods will be plotted
+in these windows.

+ 54 - 0
KCG/Documentation/source/Man/timing.rst

@@ -0,0 +1,54 @@
+Timing
+======
+
+KAPTURE is able to delay each of the four ADCs. To control this you can use the timing window.
+
+You can open it via the menu, Windows -> Timing, via the fourth toolbar button or by pressing Ctrl+t.
+
+Toolbar Symbol for timing window:
+
+.. image:: _static/clock.png
+
+The timing window looks as follows.
+
+.. figure:: _static/TimingWidget.png
+    :alt: Timing Window
+
+Here you can set the coarse delay which affects all four ADCs and the fine delay for each ADC individually.
+
+.. note:: These values are directly written to the board.
+
+.. note:: Timing settings and time scan is only available if you calibrated and synchronized your board
+
+Time Scan
+---------
+
+To detect the best delay settings you can use a feature called time scan.
+
+To perform a time scan you have to open the time scan part of the timing window by selecting Time Scan at the bottom
+of the timing window.
+
+The timing window with activated time scan part looks as follows.
+
+.. figure:: _static/TimingWidgetTimeScan.png
+    :alt: Timing Widget with time scan part enabled
+
+You can set the range for the coarse and fine delay. To start the time scan simply click on the `Start time scan` button.
+
+You can interrupt the time scan by clicking on the same button which reads now `Stop time scan`.
+
+After a time scan the result will be shown in a seperate window which consists of four plots, one for one ADC each.
+You can either manually read the best value or click on the region with the best setting. By clicking on the region,
+the values will automatically be taken into the timing window and are written to the board.
+
+.. caution::
+    The coarse delay will be updated no matter on wich ADC you click. That means always the last value you clicked
+    for the coarse delay will be taken.
+
+Above the plots the maxima for this ADC in terms of coarse delay and fine delay will be shown.
+
+The time scan results window looks as follows.
+
+.. figure:: _static/TimeScanResult.png
+    :alt: Time Scan result window
+

+ 30 - 0
KCG/Documentation/source/Requirements.rst

@@ -0,0 +1,30 @@
+Requirements
+============
+
+Software Requirements
+---------------------
+
+KAPTURE Control Gui (KCG) is written in python 2.7x and was tested with versions 2.7.3 - 2.7.10.
+It uses the following python packages
+
+    * `numpy`_
+    * `PyQt4`_
+    * `pyqtgraph`_
+
+and their respective dependencies.
+
+Those are only requirements for the Graphical user interface. Other requirements apply for the communication with
+a KAPTURE board.
+
+.. _numpy: http://numpy.org
+.. _PyQt4: http://www.riverbankcomputing.com/software/pyqt/intro
+.. _pyqtgraph: http://pyqtgraph.org/
+
+Hardware Requirements
+---------------------
+
+The Gui was not tested in respecto to minimum hardware requirements.
+
+Except for plotting it should run on every basic modern pc. For plotting of files bigger than 400MB it is recommended
+to use at least 12GB of ram. The bigger the files you want to plot the more ram you need (plotting needs more ram than the
+size of the file to plot)

+ 17 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/__init__.py

@@ -0,0 +1,17 @@
+"""Sphinx ReadTheDocs theme.
+
+From https://github.com/ryan-roemer/sphinx-bootstrap-theme.
+
+"""
+import os
+
+VERSION = (0, 1, 8)
+
+__version__ = ".".join(str(v) for v in VERSION)
+__version_full__ = __version__
+
+
+def get_html_theme_path():
+    """Return list of HTML theme paths."""
+    cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+    return cur_dir

+ 23 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/breadcrumbs.html

@@ -0,0 +1,23 @@
+<div role="navigation" aria-label="breadcrumbs navigation">
+  <ul class="wy-breadcrumbs">
+    <li><a href="{{ pathto(master_doc) }}">Docs</a> &raquo;</li>
+      {% for doc in parents %}
+          <li><a href="{{ doc.link|e }}">{{ doc.title }}</a> &raquo;</li>
+      {% endfor %}
+    <li>{{ title }}</li>
+      <li class="wy-breadcrumbs-aside">
+        {% if pagename != "search" %}
+          {% if display_github %}
+            <a href="https://{{ github_host|default("github.com") }}/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-github"> Edit on GitHub</a>
+          {% elif display_bitbucket %}
+            <a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-bitbucket"> Edit on Bitbucket</a>
+          {% elif show_source and source_url_prefix %}
+            <a href="{{ source_url_prefix }}{{ pagename }}{{ source_suffix }}">View page source</a>
+          {% elif show_source and has_source and sourcename %}
+            <a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> View page source</a>
+          {% endif %}
+        {% endif %}
+      </li>
+  </ul>
+  <hr/>
+</div>

+ 36 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/footer.html

@@ -0,0 +1,36 @@
+<footer>
+  {% if next or prev %}
+    <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
+      {% if next %}
+        <a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n">Next <span class="fa fa-arrow-circle-right"></span></a>
+      {% endif %}
+      {% if prev %}
+        <a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}" accesskey="p"><span class="fa fa-arrow-circle-left"></span> Previous</a>
+      {% endif %}
+    </div>
+  {% endif %}
+
+  <hr/>
+
+  <div role="contentinfo">
+    <p>
+    {%- if show_copyright %}
+      {%- if hasdoc('copyright') %}
+        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
+      {%- else %}
+        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
+      {%- endif %}
+    {%- endif %}
+
+    {%- if last_updated %}
+      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
+    {%- endif %}
+    </p>
+  </div>
+
+  {%- if show_sphinx %}
+  {% trans %}Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>{% endtrans %}.
+  {%- endif %}
+
+</footer>
+

+ 181 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/layout.html

@@ -0,0 +1,181 @@
+{# TEMPLATE VAR SETTINGS #}
+{%- set url_root = pathto('', 1) %}
+{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
+{%- if not embedded and docstitle %}
+  {%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
+{%- else %}
+  {%- set titlesuffix = "" %}
+{%- endif %}
+
+<!DOCTYPE html>
+<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
+<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
+<head>
+  <meta charset="utf-8">
+  {{ metatags }}
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  {% block htmltitle %}
+  <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
+  {% endblock %}
+
+  {# FAVICON #}
+  {% if favicon %}
+    <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
+  {% endif %}
+
+  {# CSS #}
+
+  {# OPENSEARCH #}
+  {% if not embedded %}
+    {% if use_opensearch %}
+      <link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/>
+    {% endif %}
+
+  {% endif %}
+
+  {# RTD hosts this file, so just load on non RTD builds #}
+  {% if not READTHEDOCS %}
+    <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
+  {% endif %}
+
+  {% for cssfile in css_files %}
+    <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
+  {% endfor %}
+
+  {% for cssfile in extra_css_files %}
+    <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
+  {% endfor %}
+
+  {%- block linktags %}
+    {%- if hasdoc('about') %}
+        <link rel="author" title="{{ _('About these documents') }}"
+              href="{{ pathto('about') }}"/>
+    {%- endif %}
+    {%- if hasdoc('genindex') %}
+        <link rel="index" title="{{ _('Index') }}"
+              href="{{ pathto('genindex') }}"/>
+    {%- endif %}
+    {%- if hasdoc('search') %}
+        <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
+    {%- endif %}
+    {%- if hasdoc('copyright') %}
+        <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/>
+    {%- endif %}
+    <link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}"/>
+    {%- if parents %}
+        <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}"/>
+    {%- endif %}
+    {%- if next %}
+        <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}"/>
+    {%- endif %}
+    {%- if prev %}
+        <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}"/>
+    {%- endif %}
+  {%- endblock %}
+  {%- block extrahead %} {% endblock %}
+
+  {# Keep modernizr in head - http://modernizr.com/docs/#installing #}
+  <script src="_static/js/modernizr.min.js"></script>
+
+</head>
+
+<body class="wy-body-for-nav" role="document">
+
+  <div class="wy-grid-for-nav">
+
+    {# SIDE NAV, TOGGLES ON MOBILE #}
+    <nav data-toggle="wy-nav-shift" class="wy-nav-side">
+      <div class="wy-side-nav-search">
+        {% block sidebartitle %}
+
+        {% if logo and theme_logo_only %}
+          <a href="{{ pathto(master_doc) }}">
+        {% else %}
+          <a href="{{ pathto(master_doc) }}" class="icon icon-home"> {{ project }}
+        {% endif %}
+
+        {% if logo %}
+          {# Not strictly valid HTML, but it's the only way to display/scale it properly, without weird scripting or heaps of work #}
+          <img src="{{ pathto('_static/' + logo, 1) }}" class="logo" />
+        {% endif %}
+        </a>
+
+        {% include "searchbox.html" %}
+
+        {% endblock %}
+      </div>
+
+      <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
+        {% block menu %}
+          {% set toctree = toctree(maxdepth=4, collapse=False, includehidden=True) %}
+          {% if toctree %}
+              {{ toctree }}
+          {% else %}
+              <!-- Local TOC -->
+              <div class="local-toc">{{ toc }}</div>
+          {% endif %}
+        {% endblock %}
+      </div>
+      &nbsp;
+    </nav>
+
+    <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
+
+      {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
+      <nav class="wy-nav-top" role="navigation" aria-label="top navigation">
+        <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
+        <a href="{{ pathto(master_doc) }}">{{ project }}</a>
+      </nav>
+
+
+      {# PAGE CONTENT #}
+      <div class="wy-nav-content">
+        <div class="rst-content">
+          {% include "breadcrumbs.html" %}
+          <div role="main" class="document">
+            {% block body %}{% endblock %}
+          </div>
+          {% include "footer.html" %}
+        </div>
+      </div>
+
+    </section>
+
+  </div>
+  {% include "versions.html" %}
+
+  {% if not embedded %}
+
+    <script type="text/javascript">
+        var DOCUMENTATION_OPTIONS = {
+            URL_ROOT:'{{ url_root }}',
+            VERSION:'{{ release|e }}',
+            COLLAPSE_INDEX:false,
+            FILE_SUFFIX:'{{ '' if no_search_suffix else file_suffix }}',
+            HAS_SOURCE:  {{ has_source|lower }}
+        };
+    </script>
+    {%- for scriptfile in script_files %}
+      <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
+    {%- endfor %}
+
+  {% endif %}
+
+  {# RTD hosts this file, so just load on non RTD builds #}
+  {% if not READTHEDOCS %}
+    <script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script>
+  {% endif %}
+
+  {# STICKY NAVIGATION #}
+  {% if theme_sticky_navigation %}
+  <script type="text/javascript">
+      jQuery(function () {
+          SphinxRtdTheme.StickyNav.enable();
+      });
+  </script>
+  {% endif %}
+
+  {%- block footer %} {% endblock %}
+
+</body>
+</html>

+ 205 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/layout_old.html

@@ -0,0 +1,205 @@
+{#
+    basic/layout.html
+    ~~~~~~~~~~~~~~~~~
+
+    Master layout template for Sphinx themes.
+
+    :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- block doctype -%}
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+{%- endblock %}
+{%- set reldelim1 = reldelim1 is not defined and ' &raquo;' or reldelim1 %}
+{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
+{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
+                         (sidebars != []) %}
+{%- set url_root = pathto('', 1) %}
+{# XXX necessary? #}
+{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
+{%- if not embedded and docstitle %}
+  {%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
+{%- else %}
+  {%- set titlesuffix = "" %}
+{%- endif %}
+
+{%- macro relbar() %}
+    <div class="related">
+      <h3>{{ _('Navigation') }}</h3>
+      <ul>
+        {%- for rellink in rellinks %}
+        <li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
+          <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
+             {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
+          {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
+        {%- endfor %}
+        {%- block rootrellink %}
+        <li><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
+        {%- endblock %}
+        {%- for parent in parents %}
+          <li><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
+        {%- endfor %}
+        {%- block relbaritems %} {% endblock %}
+      </ul>
+    </div>
+{%- endmacro %}
+
+{%- macro sidebar() %}
+      {%- if render_sidebar %}
+      <div class="sphinxsidebar">
+        <div class="sphinxsidebarwrapper">
+          {%- block sidebarlogo %}
+          {%- if logo %}
+            <p class="logo"><a href="{{ pathto(master_doc) }}">
+              <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+            </a></p>
+          {%- endif %}
+          {%- endblock %}
+          {%- if sidebars != None %}
+            {#- new style sidebar: explicitly include/exclude templates #}
+            {%- for sidebartemplate in sidebars %}
+            {%- include sidebartemplate %}
+            {%- endfor %}
+          {%- else %}
+            {#- old style sidebars: using blocks -- should be deprecated #}
+            {%- block sidebartoc %}
+            {%- include "localtoc.html" %}
+            {%- endblock %}
+            {%- block sidebarrel %}
+            {%- include "relations.html" %}
+            {%- endblock %}
+            {%- block sidebarsourcelink %}
+            {%- include "sourcelink.html" %}
+            {%- endblock %}
+            {%- if customsidebar %}
+            {%- include customsidebar %}
+            {%- endif %}
+            {%- block sidebarsearch %}
+            {%- include "searchbox.html" %}
+            {%- endblock %}
+          {%- endif %}
+        </div>
+      </div>
+      {%- endif %}
+{%- endmacro %}
+
+{%- macro script() %}
+    <script type="text/javascript">
+      var DOCUMENTATION_OPTIONS = {
+        URL_ROOT:    '{{ url_root }}',
+        VERSION:     '{{ release|e }}',
+        COLLAPSE_INDEX: false,
+        FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
+        HAS_SOURCE:  {{ has_source|lower }}
+      };
+    </script>
+    {%- for scriptfile in script_files %}
+    <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
+    {%- endfor %}
+{%- endmacro %}
+
+{%- macro css() %}
+    <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
+    <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
+    {%- for cssfile in css_files %}
+    <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
+    {%- endfor %}
+{%- endmacro %}
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
+    {{ metatags }}
+    {%- block htmltitle %}
+    <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
+    {%- endblock %}
+    {{ css() }}
+    {%- if not embedded %}
+    {{ script() }}
+    {%- if use_opensearch %}
+    <link rel="search" type="application/opensearchdescription+xml"
+          title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
+          href="{{ pathto('_static/opensearch.xml', 1) }}"/>
+    {%- endif %}
+    {%- if favicon %}
+    <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
+    {%- endif %}
+    {%- endif %}
+{%- block linktags %}
+    {%- if hasdoc('about') %}
+    <link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
+    {%- endif %}
+    {%- if hasdoc('genindex') %}
+    <link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
+    {%- endif %}
+    {%- if hasdoc('search') %}
+    <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
+    {%- endif %}
+    {%- if hasdoc('copyright') %}
+    <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
+    {%- endif %}
+    <link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
+    {%- if parents %}
+    <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
+    {%- endif %}
+    {%- if next %}
+    <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
+    {%- endif %}
+    {%- if prev %}
+    <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
+    {%- endif %}
+{%- endblock %}
+{%- block extrahead %} {% endblock %}
+  </head>
+  <body>
+{%- block header %}{% endblock %}
+
+{%- block relbar1 %}{{ relbar() }}{% endblock %}
+
+{%- block content %}
+  {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
+
+    <div class="document">
+  {%- block document %}
+      <div class="documentwrapper">
+      {%- if render_sidebar %}
+        <div class="bodywrapper">
+      {%- endif %}
+          <div class="body">
+            {% block body %} {% endblock %}
+          </div>
+      {%- if render_sidebar %}
+        </div>
+      {%- endif %}
+      </div>
+  {%- endblock %}
+
+  {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
+      <div class="clearer"></div>
+    </div>
+{%- endblock %}
+
+{%- block relbar2 %}{{ relbar() }}{% endblock %}
+
+{%- block footer %}
+    <div class="footer">
+    {%- if show_copyright %}
+      {%- if hasdoc('copyright') %}
+        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
+      {%- else %}
+        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
+      {%- endif %}
+    {%- endif %}
+    {%- if last_updated %}
+      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
+    {%- endif %}
+    {%- if show_sphinx %}
+      {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
+    {%- endif %}
+    </div>
+    <p>asdf asdf asdf asdf 22</p>
+{%- endblock %}
+  </body>
+</html>
+

+ 50 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/search.html

@@ -0,0 +1,50 @@
+{#
+    basic/search.html
+    ~~~~~~~~~~~~~~~~~
+
+    Template for the search page.
+
+    :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- extends "layout.html" %}
+{% set title = _('Search') %}
+{% set script_files = script_files + ['_static/searchtools.js'] %}
+{% block footer %}
+  <script type="text/javascript">
+    jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
+  </script>
+  {# this is used when loading the search index using $.ajax fails,
+     such as on Chrome for documents on localhost #}
+  <script type="text/javascript" id="searchindexloader"></script>
+  {{ super() }}
+{% endblock %}
+{% block body %}
+  <noscript>
+  <div id="fallback" class="admonition warning">
+    <p class="last">
+      {% trans %}Please activate JavaScript to enable the search
+      functionality.{% endtrans %}
+    </p>
+  </div>
+  </noscript>
+
+  {% if search_performed %}
+    <h2>{{ _('Search Results') }}</h2>
+    {% if not search_results %}
+      <p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
+    {% endif %}
+  {% endif %}
+  <div id="search-results">
+  {% if search_results %}
+    <ul>
+    {% for href, caption, context in search_results %}
+      <li>
+        <a href="{{ pathto(item.href) }}">{{ caption }}</a>
+        <p class="context">{{ context|e }}</p>
+      </li>
+    {% endfor %}
+    </ul>
+  {% endif %}
+  </div>
+{% endblock %}

+ 9 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/searchbox.html

@@ -0,0 +1,9 @@
+{%- if builder != 'singlehtml' %}
+<div role="search">
+  <form id="rtd-search-form" class="wy-form" action="{{ pathto('search') }}" method="get">
+    <input type="text" name="q" placeholder="Search docs" />
+    <input type="hidden" name="check_keywords" value="yes" />
+    <input type="hidden" name="area" value="default" />
+  </form>
+</div>
+{%- endif %}

File diff suppressed because it is too large
+ 0 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/css/badge_only.css


File diff suppressed because it is too large
+ 0 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/css/theme.css


BIN
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/Inconsolata-Bold.ttf


BIN
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/Inconsolata.ttf


+ 1 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/Lato-Bold.ttf

@@ -0,0 +1 @@
+/usr/share/fonts/lato/Lato-Bold.ttf

+ 1 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/Lato-Regular.ttf

@@ -0,0 +1 @@
+/usr/share/fonts/lato/Lato-Regular.ttf

BIN
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/RobotoSlab-Bold.ttf


BIN
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/RobotoSlab-Regular.ttf


BIN
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot


+ 1 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg

@@ -0,0 +1 @@
+/usr/share/fonts/fontawesome/fontawesome-webfont.svg

+ 1 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf

@@ -0,0 +1 @@
+/usr/share/fonts/fontawesome/fontawesome-webfont.ttf

+ 1 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff

@@ -0,0 +1 @@
+/usr/share/fonts/fontawesome/fontawesome-webfont.woff

File diff suppressed because it is too large
+ 3 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/js/modernizr.min.js


+ 113 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/static/js/theme.js

@@ -0,0 +1,113 @@
+function toggleCurrent (elem) {
+    var parent_li = elem.closest('li');
+    parent_li.siblings('li.current').removeClass('current');
+    parent_li.siblings().find('li.current').removeClass('current');
+    parent_li.find('> ul li.current').removeClass('current');
+    parent_li.toggleClass('current');
+}
+
+$(document).ready(function() {
+    // Shift nav in mobile when clicking the menu.
+    $(document).on('click', "[data-toggle='wy-nav-top']", function() {
+        $("[data-toggle='wy-nav-shift']").toggleClass("shift");
+        $("[data-toggle='rst-versions']").toggleClass("shift");
+    });
+    // Nav menu link click operations
+    $(document).on('click', ".wy-menu-vertical .current ul li a", function() {
+        var target = $(this);
+        // Close menu when you click a link.
+        $("[data-toggle='wy-nav-shift']").removeClass("shift");
+        $("[data-toggle='rst-versions']").toggleClass("shift");
+        // Handle dynamic display of l3 and l4 nav lists
+        toggleCurrent(target);
+        if (typeof(window.SphinxRtdTheme) != 'undefined') {
+            window.SphinxRtdTheme.StickyNav.hashChange();
+        }
+    });
+    $(document).on('click', "[data-toggle='rst-current-version']", function() {
+        $("[data-toggle='rst-versions']").toggleClass("shift-up");
+    });
+    // Make tables responsive
+    $("table.docutils:not(.field-list)").wrap("<div class='wy-table-responsive'></div>");
+
+    // Add expand links to all parents of nested ul
+    $('.wy-menu-vertical ul').siblings('a').each(function () {
+        var link = $(this);
+            expand = $('<span class="toctree-expand"></span>');
+        expand.on('click', function (ev) {
+            toggleCurrent(link);
+            ev.stopPropagation();
+            return false;
+        });
+        link.prepend(expand);
+    });
+});
+
+// Sphinx theme state
+window.SphinxRtdTheme = (function (jquery) {
+    var stickyNav = (function () {
+        var navBar,
+            win,
+            winScroll = false,
+            linkScroll = false,
+            winPosition = 0,
+            enable = function () {
+                init();
+                reset();
+                win.on('hashchange', reset);
+
+                // Set scrolling
+                win.on('scroll', function () {
+                    if (!linkScroll) {
+                        winScroll = true;
+                    }
+                });
+                setInterval(function () {
+                    if (winScroll) {
+                        winScroll = false;
+                        var newWinPosition = win.scrollTop(),
+                            navPosition = navBar.scrollTop(),
+                            newNavPosition = navPosition + (newWinPosition - winPosition);
+                        navBar.scrollTop(newNavPosition);
+                        winPosition = newWinPosition;
+                    }
+                }, 25);
+            },
+            init = function () {
+                navBar = jquery('nav.wy-nav-side:first');
+                win = jquery(window);
+            },
+            reset = function () {
+                // Get anchor from URL and open up nested nav
+                var anchor = encodeURI(window.location.hash);
+                if (anchor) {
+                    try {
+                        var link = $('.wy-menu-vertical')
+                            .find('[href="' + anchor + '"]');
+                        $('.wy-menu-vertical li.toctree-l1 li.current')
+                            .removeClass('current');
+                        link.closest('li.toctree-l2').addClass('current');
+                        link.closest('li.toctree-l3').addClass('current');
+                        link.closest('li.toctree-l4').addClass('current');
+                    }
+                    catch (err) {
+                        console.log("Error expanding nav for anchor", err);
+                    }
+                }
+            },
+            hashChange = function () {
+                linkScroll = true;
+                win.one('hashchange', function () {
+                    linkScroll = false;
+                });
+            };
+        jquery(init);
+        return {
+            enable: enable,
+            hashChange: hashChange
+        };
+    }());
+    return {
+        StickyNav: stickyNav
+    };
+}($));

+ 9 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/theme.conf

@@ -0,0 +1,9 @@
+[theme]
+inherit = basic
+stylesheet = css/theme.css
+
+[options]
+typekit_id = hiw1hhg
+analytics_id = 
+sticky_navigation = False
+logo_only =

+ 37 - 0
KCG/Documentation/source/_themes/sphinx_rtd_theme/versions.html

@@ -0,0 +1,37 @@
+{% if READTHEDOCS %}
+{# Add rst-badge after rst-versions for small badge style. #}
+  <div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
+    <span class="rst-current-version" data-toggle="rst-current-version">
+      <span class="fa fa-book"> Read the Docs</span>
+      v: {{ current_version }}
+      <span class="fa fa-caret-down"></span>
+    </span>
+    <div class="rst-other-versions">
+      <dl>
+        <dt>Versions</dt>
+        {% for slug, url in versions %}
+          <dd><a href="{{ url }}">{{ slug }}</a></dd>
+        {% endfor %}
+      </dl>
+      <dl>
+        <dt>Downloads</dt>
+        {% for type, url in downloads %}
+          <dd><a href="{{ url }}">{{ type }}</a></dd>
+        {% endfor %}
+      </dl>
+      <dl>
+        <dt>On Read the Docs</dt>
+          <dd>
+            <a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">Project Home</a>
+          </dd>
+          <dd>
+            <a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">Builds</a>
+          </dd>
+      </dl>
+      <hr/>
+      Free document hosting provided by <a href="http://www.readthedocs.org">Read the Docs</a>.
+
+    </div>
+  </div>
+{% endif %}
+

+ 336 - 0
KCG/Documentation/source/conf.py

@@ -0,0 +1,336 @@
+# -*- coding: utf-8 -*-
+#
+# KCG documentation build configuration file, created by
+# sphinx-quickstart on Wed Jul  1 18:18:47 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+import os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.mathjax',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'KCG'
+copyright = u'2015, Patrick Schreiber'
+
+rst_epilog = """
+.. |gui_name| replace:: {gui_name}
+""".format(gui_name=project)
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.4'
+# The full version, including alpha/beta/rc tags.
+release = '0.4b1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+# html_theme = 'default'
+html_theme = 'sphinx_rtd_theme'
+html_theme_path =['_themes', ]
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+# html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'KCGdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+  ('index', 'KCG.tex', u'KCG Documentation',
+   u'Patrick Schreiber', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'kcg', u'KCG Documentation',
+     [u'Patrick Schreiber'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'KCG', u'KCG Documentation',
+   u'Patrick Schreiber', 'KCG', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+
+# -- Options for Epub output ----------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = u'KCG'
+epub_author = u'Patrick Schreiber'
+epub_publisher = u'Patrick Schreiber'
+epub_copyright = u'2015, Patrick Schreiber'
+
+# The basename for the epub file. It defaults to the project name.
+#epub_basename = u'KCG'
+
+# The HTML theme for the epub output. Since the default themes are not optimized
+# for small screen space, using the same theme for HTML and epub output is
+# usually not wise. This defaults to 'epub', a theme designed to save visual
+# space.
+#epub_theme = 'epub'
+
+# The language of the text. It defaults to the language option
+# or en if the language is not set.
+#epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+#epub_scheme = ''
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#epub_identifier = ''
+
+# A unique identification for the text.
+#epub_uid = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+#epub_cover = ()
+
+# A sequence of (type, uri, title) tuples for the guide element of content.opf.
+#epub_guide = ()
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# The depth of the table of contents in toc.ncx.
+#epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+#epub_tocdup = True
+
+# Choose between 'default' and 'includehidden'.
+#epub_tocscope = 'default'
+
+# Fix unsupported image types using the PIL.
+#epub_fix_images = False
+
+# Scale large images.
+#epub_max_image_width = 0
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#epub_show_urls = 'inline'
+
+# If false, no index is generated.
+#epub_use_index = True

+ 34 - 0
KCG/Documentation/source/index.rst

@@ -0,0 +1,34 @@
+.. KCG documentation master file, created by
+   sphinx-quickstart on Wed Jul  1 18:18:47 2015.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to KCG's documentation!
+===============================
+
+This documentation consists of two parts, the user part and the developer part.
+
+The user part gives a brief explanation on how to use the gui during measurements etc.
+
+The developer part gives an overview on how to add custom widgets and gives insight in various interface methods. But keep
+in mind: this is NOT a full documentation of the source code.
+
+.. toctree::
+   :maxdepth: 3
+   :numbered:
+
+   Requirements
+
+   Man/index
+
+   Dev/index
+
+
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+

+ 1 - 0
KCG/Documentation/svg_png.sh

@@ -0,0 +1 @@
+nkscape -z -e $2 -w 64 -h 64 $1

+ 1 - 0
KCG/VERSION

@@ -0,0 +1 @@
+0.3.2.161.0331-beta

+ 0 - 0
KCG/__init__.py


+ 4 - 0
KCG/base/__init__.py

@@ -0,0 +1,4 @@
+__author__ = 'blaxxun'
+"""
+Base Stuff for the Gui
+"""

+ 0 - 0
KCG/base/backend/__init__.py


+ 16 - 0
KCG/base/backend/board/__init__.py

@@ -0,0 +1,16 @@
+# make everything conveniently available under the name board
+from actions import *
+from board_config import *
+from boards_connected import *
+from communication import *
+from errors import *
+from sequences import *
+from status import *
+from utils import *
+
+
+HEADER_SIZE_BYTES = 32
+
+pci = PCI()
+
+

+ 75 - 0
KCG/base/backend/board/actions.py

@@ -0,0 +1,75 @@
+"""
+Actions performed on the board.
+Fore some more actions also see sequences.py
+"""
+import time
+
+from communication import *
+from utils import wait_for_revolutions
+
+
+def acquire_data(board_id, filename, simulate=False):
+    if simulate:
+        start_pilot_bunch_emulator(board_id)
+
+    start_acquisition(board_id)
+    wait_for_revolutions(board_id)
+    stop_acquisition(board_id)
+    enable_transfer(board_id)
+    pci.read(board_id, dma='dma0', destination=filename)
+    flush_dma(board_id)
+
+
+def data_reset(board_id):
+    log.vinfo('Data reset')
+    pci.write(board_id, '000003f5', hex_mask='7')
+    time.sleep(0.05)
+    pci.write(board_id, '000003f0', hex_mask='7')
+    time.sleep(0.05)
+
+
+def flush_dma(board_id, dma='dma0'):
+    log.vinfo('Flushing DMA Pipeline')
+    pci.write(board_id, '03f0', hex_mask='CF0')
+    # TODO: implement identifier usage
+    pci.read(board_id, dma=dma, destination='/dev/null')
+
+
+def stop_board(board_id):
+    pci.write(board_id, '0x01', '0x9040')
+    pci.stop_dma(board_id)
+
+
+def soft_reset(board_id):
+    pci.write(board_id, '0x1', '0x9040', hex_mask='0x1')
+    time.sleep(1)
+    pci.write(board_id, '0x0', '0x9040', hex_mask='0x1')
+
+
+def start_pilot_bunch_emulator(board_id):
+    log.vinfo('Start pilot bunch emulator')
+    pci.write(board_id, '400003f0', hex_mask='400003F0')
+    time.sleep(0.005)
+    pci.write(board_id, '03f0', hex_mask='CF0')
+
+
+def start_acquisition(board_id):
+    log.vinfo('Start acquisition')
+    pci.write(board_id, '1', '4', hex_mask='1')  # what's this? write value 1 to register 4???
+    time.sleep(0.005)
+    pci.write(board_id, '00bf0', hex_mask='CF0')
+
+
+def stop_acquisition(board_id):
+    log.vinfo('Stop acquisition')
+    pci.write(board_id, '003f0', hex_mask='CF0')
+    time.sleep(0.005)
+
+
+def enable_transfer(board_id):
+    log.vinfo('Enable data transfer')
+    pci.write(board_id, '007f0', hex_mask='CF0')
+    time.sleep(0.005)
+
+
+

+ 386 - 0
KCG/base/backend/board/board_config.py

@@ -0,0 +1,386 @@
+"""
+Configuration for each board
+"""
+
+import ConfigParser
+import numpy as np
+import logging
+from PyQt4 import QtGui, QtCore
+
+from communication import *
+from .... import config as kcg_config
+
+
+class BoardConfiguration(QtGui.QWidget):
+    callback_signal = QtCore.pyqtSignal(str, list)
+
+    def __init__(self, identifier, config_file=None):
+        super(BoardConfiguration, self).__init__()
+        self.callback_signal.connect(self._notify_observers_receiver)
+        self.identifier = identifier
+        self._config = {}
+        self._observers = {}
+        self._observers_for_all = []
+        self._set_defaults()
+        self.set_default_observers()
+
+        if config_file:
+            self.load_config(config_file)
+
+    def _set_defaults(self):
+        self._config ={
+            'fpga_delay_max': 15,
+            'fpga_delay': 0,
+            'fpga_delay_factor': 150,
+
+            'chip_delay_max': 31,
+            'chip_1_delay': 4,
+            'chip_2_delay': 4,
+            'chip_3_delay': 4,
+            'chip_4_delay': 4,
+            'chip_delay_factor': 3,
+
+            'th_delay_max': 15,
+            'th_delay': 3,
+            'th_delay_factor': 150,
+
+            'adc_delay_max': 15,
+            'adc_1_delay': 4,
+            'adc_2_delay': 4,
+            'adc_3_delay': 4,
+            'adc_4_delay': 4,
+            'adc_delay_factor': 150,
+
+            'th_to_adc_cycles': 7,
+            'adc_1_delay_individual': -1,
+
+            'orbits_observe': 100,
+            'orbits_skip': 2,
+            'acquisition_count': 10,
+            'orbits_wait_time': 15,
+            'trigger_skip': 0,
+            'trigger_timeout': 12,
+            'trigger_method': 1,
+            'use_trigger': False,
+            'build_spectrograms': False,
+            'pilot_bunch': False,
+            'header': True if kcg_config.save_header is True else False
+        }
+
+    def set_default_observers(self):
+        self.observe(None, self.update_header, 'header')
+        self.observe(None, lambda x: pci.write(self.identifier, hex(x), '0x9020'), 'orbits_observe')
+        self.observe(None, lambda x: pci.write(self.identifier, hex(x), '0x9028'), 'orbits_skip')
+
+    def notify_all_observers(self):
+        for key, value in self._config.items():
+            self._notify_observers(key, value)
+            # observers = self._observers.get(key, None)
+            # if observers:
+            #     for (who, callback) in observers:
+            #         callback(self.get(key))
+
+
+    def load_config(self, filename):
+        if filename:
+            config = ConfigParser.RawConfigParser()
+            if not config.read(str(filename)):
+                return False
+
+            for key in self._config.keys():
+                try:
+                    if type(self._config[key]) == int:
+                        self._config[key] = config.getint('Config', key)
+                    elif type(self._config[key]) == bool:
+                        self._config[key] = config.getboolean('Config', key)
+                    logging.vinfo("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
+            return True
+        else:
+            return False
+
+    def save_config(self, filename):
+        if filename:
+            # with open(filename, 'w') as f:
+            try:
+                f = open(filename, 'w')
+                cp = ConfigParser.RawConfigParser()
+                cp.add_section('Config')
+                for key in self._config.keys():
+                    cp.set('Config', key, self._config[key])
+                f.write('#\n'
+                        '#  KCG   (KAPTURE Control Gui) Configuration file\n'
+                        '#\n'
+                        '#  (c) Karlsruhe Institute of Technology, 2015\n'
+                        '#  All rights reserved.\n'
+                        '#\n'
+                        '#  Applicable Gui Version(s): 1.0 - 1.0.2\n'
+                        '#\n'
+                        '#  Saved at: ' + time.asctime() + '\n'
+                        '#\n\n')
+                cp.write(f)
+            except (IOError, TypeError):
+                return False
+            return True
+        else:
+            return False
+
+    def get(self, key):
+        if not key in self._config:
+            raise NoSuchKeyError(key+" is not registered in BoardConfiguration for board "+str(self.identifier))
+        return self._config.get(key, None)
+
+    def dump(self):
+        s = ""
+        for key in self._config.keys():
+            s += key + ": " + str(self.get(key)) + ", "
+        return s[:-1]
+
+    def update(self, key, value):
+        self._config[key] = value
+        self._notify_observers(key, value)
+
+    def updateSilent(self, key, value):
+        self._config[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 observe_all(self, callback):
+        if callback not in self._observers_for_all:
+            self._observers_for_all.append(callback)
+        else:
+            raise ObserverError("Observer already registered")
+
+    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 unobserve_all_observer(self, callback):
+        if callback in self._observers_for_all:
+            del self._observers_for_all[self._observers_for_all.index(callback)]
+
+    def _notify_observers_receiver(self, key, value):
+        observers = self._observers.get(str(key), None)
+        value = value[0]
+        if observers is None:
+            return
+        for (who, callback) in observers:
+            callback(value)
+
+        for cb in self._observers_for_all:
+            cb(key, value)
+
+    def _notify_observers(self, key, value):
+        self.callback_signal.emit(key, [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 ValueError:
+            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"
+        pci.write(self.identifier, reg_value, '0x9060')
+        logging.vinfo("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:
+            logging.vinfo("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'
+
+        pci.write(self.identifier, reg_value, '0x9080', hex_mask=mask)
+        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 ','
+        logging.vinfo(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"
+        pci.write(self.identifier, reg_value, '0x9060')
+        logging.vinfo("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:
+            logging.vinfo("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)
+        pci.write(self.identifier, reg_value, '0x9060')
+        s = "Setting ADC_%i delay %i * %i --> %i picoseconds" % ((_adc+1), _val, self.get('adc_delay_factor'), _val*self.get('adc_delay_factor'))
+        logging.vinfo(s)
+        adc_s = 'adc_%i_delay'%(_adc+1)
+        self.update(adc_s, _val)
+
+    def set_delay(self, n, ignore_seperate_delay=False):
+        def write_delay(value, channel):
+            cmd = '00501' + '%01x' % value + str(channel)
+            pci.write(self.identifier, cmd, reg='0x9060')
+            time.sleep(0.005)
+
+        logging.vinfo("Setting T/H Delay: " + str(n))
+        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
+        if self.get('adc_1_delay_individual') > 0:
+            try:
+                delay = n + self.make_uint(self.get('adc_1_delay_individual'), 16, 'ADC 1 individual delay')
+                logging.vinfo("Setting ADC1 individual delay to " + str(delay))
+            except ValueError:
+                logging.vinfo(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 update_header(self, state):
+        """
+        Set the flag to write Header to files when acquiring.
+        :param state: True to enabling header and False to disable
+        :return: -
+        """
+        try:
+            control = pci.read(self.identifier, 1, '0x9040')[0]
+            control_bits = '{0:032b}'.format(int(control, 16))
+            if state:
+                control_bits = control_bits[:3] + '1' + control_bits[4:]
+            else:
+                control_bits = control_bits[:3] + '0' + control_bits[4:]
+            dec_val_bits = int(control_bits, 2)
+            pci.write(self.identifier, hex(dec_val_bits), '0x9040')
+        except BoardError as e:
+            reason = str(e) if str(e) != '' else "Unknown"
+            logging.error("Error in Board Communication, was unable to write value to board "+reason)
+
+    def read_from_board(self):
+        try:
+            settings = ['chip_1_delay','chip_2_delay','chip_3_delay','chip_4_delay']
+            # --[ read fine/chip delays ]
+            val = pci.read(self.identifier, reg='9080')[0]
+            # --[ set chip_1_delay ]--
+            self.update('chip_1_delay', int(val[6:8], 16))
+            # --[ set chip_2_delay ]--
+            self.update('chip_2_delay', int(val[4:6], 16))
+            # --[ set chip_3_delay ]--
+            self.update('chip_3_delay', int(val[2:4], 16))
+            # --[ set chip_4_delay ]--
+            self.update('chip_4_delay', int(val[0:2], 16))
+
+            # --[ read and set th delay ]--
+            val = pci.read(self.identifier, reg='90a0')[0]
+            self.update('th_delay', int(val, 16))
+
+            # --[ check for seperate adc1 delay ]--
+            val = pci.read(self.identifier, reg='9088')[0]
+            if int(val, 16) != self.get('th_delay') + self.get('adc_1_delay_individual'):
+                self.update('adc_1_delay_individual', int(val, 16)-self.get('th_delay'))
+            else:
+                self.update('adc_1_delay_individual', -1)
+
+            # --[ read and set number of orbits to acquire ]--
+            val = pci.read(self.identifier, reg='9020')[0]
+            self.update('orbits_observe', int(val, 16))
+
+            # --[ read and set number of orbits to skip ]--
+            val = pci.read(self.identifier, reg='9028')[0]
+            self.update('orbits_skip', int(val, 16))
+
+            # --[ read and update header ]--
+            control = pci.read(self.identifier, 1, '0x9040')[0]
+            control_bits = '{0:032b}'.format(int(control, 16))
+            if control_bits[3] == '1':
+                self.update('header', True)
+            else:
+                self.update('header', False)
+        except IndexError:
+            error(0x002, "Could not Read data from Board. Pci returned wrong amount of data.")
+

+ 153 - 0
KCG/base/backend/board/boards_connected.py

@@ -0,0 +1,153 @@
+"""
+Discover and manage connected boards
+"""
+
+import glob
+import logging as log
+
+from .... import config
+from ... kcgwidget import error
+from communication import pci
+
+DUMMY_PCI_i_OUTPUT = 'Vendor: 10ee, Device: 6028, Bus: 3, Slot: 0, Function: 0\n\
+ Interrupt - Pin: 1, Line: 255\n\
+ BAR 0 - MEM32, Start: 0xf5500000, Length: 0x  100000, Flags: 0x00040200\n\
+\n\
+DMA Engines: \n\
+ DMA  0 C2S - Type: Packet, Address Width: 32 bits\n\
+\n\
+Banks: \n\
+ 0x80 dma: DMA Registers\n\
+'
+
+class BoardsConnected(object):
+    """
+    Container for connected/available boards
+    This will work as a generator and yield the available board_ids
+    NOTE: this is subject to change. In the future this may yield board_names or tuples or something else
+    """
+    def __init__(self):
+        self._board_ids = {}
+        self._board_ids_reverse = {}
+        self._device_files = {}
+        self.count = 0
+
+    def _device_id(self, dev_file):
+        """
+        Get the device id
+        :param dev_file: the dev_file to use
+        :return: string for the dev id
+        """
+        info_string = pci.info(dev_file=dev_file)
+        # info_string = DUMMY_PCI_i_OUTPUT
+        try:
+            return info_string.split('Device: ')[1].split(',')[0]
+        except IndexError:
+            error(0x002, "Pci returned no or wrong information string. Device probably not found.", severe=True)
+
+    def discover(self):
+        """
+        Discover connected boards
+        This is either a dummy implementation that defaults to /dev/fpga0 for every board
+        it will create 'virtual' boards
+        OR:
+        If config.use_dev_fpga_for_detection == True use number of /dev/fpga devices and use them
+        """
+        def get(dict_, what):  # this will return the dict value for what (Key) or what if what not in dict_
+            return dict_.get(what, what)
+
+        if config.board_detection_method == 'dev':
+            # searchstring = '/dev/fpga'
+            searchstring = '/dev/lp'
+            device_list = glob.glob(searchstring+'*')
+            log.info("Found {num} devices.".format(num=len(device_list)))
+
+            self._board_ids = {int(i.replace(searchstring, '')): get(config.device_names, self._device_id(i))
+                               for i in device_list}
+            self._device_files = {int(i.replace(searchstring, '')): i for i in device_list}
+
+        elif config.board_detection_method == 'dummy':
+            num = 5
+            self._board_ids = {i: get(config.device_names, 'test'+str(i)) for i in range(num)}
+            self._device_files = {i: '/dev/fpga0' for i in self.board_ids}
+
+        elif config.board_detection_method == 'list':
+            self._board_ids = {i: get(config.device_names, self._device_id(d_f)) for i, d_f in enumerate(config.device_list)}
+            self._device_files = {i: d_f for i, d_f in enumerate(config.device_list)}
+
+        else:
+            raise config.MisconfigurationError("board_detection_method was misconfigured")
+
+        # if no boards detected create a dummy board (everything will be disabled in the gui) so the gui does
+        # not crash
+        if len(self._board_ids) == 0:
+            self._board_ids[0] = None
+
+        self._board_ids_reverse = {v: k for k, v in self._board_ids.items()}  # build reverse look up
+
+
+    @property
+    def board_ids(self):
+        """
+        Get Board Ids as list
+        """
+        return self._board_ids.keys()
+
+    @property
+    def board_names(self):
+        """
+        Get Board Names as list
+        :return:
+        """
+        return self._board_ids.values()
+
+    def get_board_name_from_id(self, board_id):
+        """
+        Get the name of a board with the given board_id
+        :param board_id: the board to get the name for
+        :return: the name of the board with id board_id
+        """
+        return self._board_ids[board_id]
+
+    def get_board_id_from_name(self, board_name):
+        """
+        Get the id of a board with the name board_name
+        :param board_name: the name of the board to get the id for
+        :return: the id of the board with name board_name
+        """
+        return self._board_ids_reverse[board_name]
+
+    def get_device_file(self, board_id):
+        """
+        Get the device file (/dev/...) to use when communicating with the board
+        :param board_id: the id of the board
+        :return: the device file as string
+        """
+        return self._device_files[board_id]
+
+    @property
+    def multi_board(self):
+        """
+        If multiple boards are connected this is true else false
+        """
+        if len(self.board_ids) > 1:
+            return True
+        return False
+
+    def __iter__(self):
+        self.__cur_idx = 0
+        return self
+
+    def next(self):
+        self.__cur_idx += 1
+        if self.__cur_idx > len(self.board_ids):
+            raise StopIteration
+        return self.board_ids[self.__cur_idx-1]
+
+    def __getitem__(self, item):
+        return self.board_ids[item]
+
+
+available_boards = BoardsConnected()
+available_boards.discover()
+

+ 195 - 0
KCG/base/backend/board/communication.py

@@ -0,0 +1,195 @@
+"""
+Communication part of the board package
+"""
+import subprocess
+import re
+import logging as log
+import time
+
+from errors import *
+from ...kcgwidget import error
+
+
+class PCI(object):
+    def __init__(self):
+        pass
+
+    def _safe_call(self, cmd):
+        log.debug(cmd)
+        try:
+            # if '-r' in cmd:
+            return subprocess.check_output(cmd)
+            # else:
+            #     subprocess.Popen(cmd, shell=False)
+        except subprocess.CalledProcessError as e:
+            raise BoardError(e.output)
+        except OSError as e:
+            if str(e) == "[Errno 2] No such file or directory":
+                error(0x003, "Pci command not found. Exiting.")
+                raise InterfaceNotFoundError("pci command was not found in your searchpath")
+            else:
+                raise BoardError('{}: {}'.format(' '.join(cmd), str(e)))
+
+    def _format(self, output):
+        output = output.split("\n")
+        lines = []
+        for line in output:
+            if line and line != "\n":
+                lines.append(line.split(":  ")[1])
+        formatted = []
+        for line in lines:
+            if line and line != "\n":
+                formatted += re.findall(r'[\da-fA-F]{8}', line)
+        return formatted
+
+    def read(self, board_id, amount=1, reg=None, dma=None, destination=None, decimal=False, timeout=None):
+        """
+        Read from boards
+
+        :param board_id: id of the board to write to (mandatory)
+        :param amount: number of 32byte blocks to read (default is 1) [1]
+        :param reg: register to read from [1]
+        :param dma: the dma to read from [1]
+        :param destination: the destination to write the retrieved data to ('memory' to return the data)
+        :param decimal: whether to return the result as decimal number (default is False)
+        :param timeout: the timeout for the read (only useful when reading data from dma)
+        :return:
+
+        [1]: If neither reg nor dma are given reg will default to 0x9000 and data from registers will be read
+             If reg is given, data from registers will be read
+             If dma is given, data from dma will be read and amount is ignored
+             If both reg and dma are given, an error will be raised
+        """
+        # either reg or dma have to be None
+        assert reg is None or dma is None, "read was called with reg and dma arguments."
+
+        if not reg and not dma:
+            reg = '0x9000'
+        source = reg if reg else dma
+        cmd = ['pci', '-d', available_boards.get_device_file(board_id), '-r', source]
+
+        dst = '/dev/stdout' if destination == 'memory' else destination
+
+        if reg:
+            cmd_extend = ["-s%i" % amount]
+        else:
+            cmd_extend = ['-o', dst, '--multipacket']
+            if timeout:
+                cmd_extend.extend(['--timeout', str(timeout)])
+            log.vinfo('Write data to {}'.format(dst))
+
+        cmd.extend(cmd_extend)
+        output = self._safe_call(cmd)
+
+        if reg:
+            formatted = self._format(output)
+            if decimal:
+                if dst:
+                    with open(dst, 'r+') as f:  # to be consistent with use of destination
+                        f.write(str([int(x, 16) for x in formatted]))
+                    return
+                else:
+                    return [int(x, 16) for x in formatted]
+            else:
+                if dst:
+                    with open(dst, 'r+') as f:  # to be consistent with use of destination
+                        f.write(str(formatted))
+                    return
+                return formatted
+        else:
+            if dst == '/dev/stdout':
+                return output.split('Writting')[0]
+            else:
+                self.write(board_id, '003f0', hex_mask='CF0')  # what does this do?
+
+    def write(self, board_id, value, reg='0x9040', hex_mask='FFFFFFFF'):
+        """
+        Write to boards
+        :param board_id: id of the board to write to (mandatory)
+        :param value: value to write (mandatory)
+        :param reg: register to write to (optional, default is '0x9040')
+        :param hex_mask: hex mask to apply to value before writing (optional)
+        :return:
+        """
+        assert len(hex_mask) <= 8, "Hex Mask has more than 32 bit."
+
+        if hex_mask != 'FFFFFFFF':
+            prev_val = self.read(board_id, 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', '-d', available_boards.get_device_file(board_id), '-w', reg, value]
+        self._safe_call(cmd)
+        log.debug('Written %str to register %s' % (value, reg))
+
+    def read_data_to_file(self, board_id, filename, dma='dma0', timeout=None):
+        """
+        Read data from board and write to file
+
+        :param board_id: the board to read from
+        :param filename: the filename to write to
+        :param dma: the dma to use?
+        :param timeout: if not None: the timeout for the underlying pci command
+        :return:
+        """
+        return self.read(board_id, dma=dma, destination=filename, timeout=timeout)
+
+    def read_data_to_variable(self, board_id, dma='dma0', timeout=None):
+        """
+        Read data and return it.
+
+        :param board_id: the board to read from
+        :param dma: the dma to use?
+        :param timeout: if not None: the timeout for the underlying pci command
+        :return: string with data read from board
+        """
+        return self.read(board_id, dma=dma, timeout=timeout, destination='memory')
+
+    def start_dma(self, board_id, dma='dma0r'):
+        """
+        Start dma engine.
+
+        :param board_id: the board to start the dma engine for
+        :param dma: the dma to use
+        :return:
+        """
+        # TODO: implement identifier usage
+        log.vinfo('Start DMA')
+        cmd = ['pci', '-d', available_boards.get_device_file(board_id), '--start-dma', dma]
+        self._safe_call(cmd)
+        time.sleep(0.05)
+
+    def stop_dma(self, board_id, dma='dma0r'):
+        """
+        Stop dma engine.
+
+        :param board_id: the board to stop the dma engine for
+        :param dma: the dma to use
+        :return:
+        """
+        # TODO: implement identifier usage
+        log.vinfo('Stop DMA')
+        cmd = ['pci', '-d', available_boards.get_device_file(board_id), '--stop-dma', dma]
+        self._safe_call(cmd)
+        time.sleep(0.05)
+
+    def info(self, board_id=None, dev_file=None):
+        """
+        Get Device info (output of pci -i)
+        :return: Information string returned by pci -i
+        """
+        assert board_id != dev_file, "info got both board_id and dev_file or got none of both"
+        if board_id is not None:
+            cmd = ['pci', '-d', available_boards.get_device_file(board_id), '-i']
+        else:
+            cmd = ['pci', '-d', dev_file, '-i']
+        return self._safe_call(cmd)
+
+pci = PCI()
+from boards_connected import available_boards  # this import has to be here as boards_connected imports pci

+ 22 - 0
KCG/base/backend/board/errors.py

@@ -0,0 +1,22 @@
+"""
+Errors for board actions
+"""
+class BoardError(Exception):
+    pass
+
+
+class InterfaceNotFoundError(Exception):
+    pass
+
+
+class ObserverError(Exception):
+    pass
+
+
+class NoBoardId(Exception):
+    pass
+
+
+class NoSuchKeyError(Exception):
+    pass
+

+ 151 - 0
KCG/base/backend/board/sequences.py

@@ -0,0 +1,151 @@
+"""
+Sequences for various board actions
+These methods are generators that will yield as first element the number
+of actions (other yields) excluding it self
+They will then perform the next action in row (setting registers or reading values from boards etc)
+and will yield True or False depending on the result of the action performed
+"""
+import logging
+
+from . import *
+from utils import *
+
+
+def startup_sequence(board_id):
+    NUMBER = 4
+    yield NUMBER
+
+    pci.start_dma(board_id)
+    pci.write(board_id, '0x20001000', '0x9100')
+    pci.write(board_id, '0x05', '0x9040')
+    yield True
+
+    pci.write(board_id, '0x3C1', '0x9040')
+    logging.info("9040:  " + str(pci.read(board_id, 1, '0x9040')[0]))
+    yield True
+
+    pci.write(board_id, '0x3F1', '0x9040')
+    logging.info("9040:  " + str(pci.read(board_id, 1, '0x9040')[0]))
+    yield True
+
+    pci.write(board_id, '0x3F0', '0x9040')
+    logging.info("9040:  " + str(pci.read(board_id, 1, '0x9040')[0]))
+    yield True
+
+
+def calibration_sequence(board_id):
+    NUMBER = 15
+    yield NUMBER
+
+    # Removing Board Reset
+    pci.write(board_id, '0x2000003f0', '0x9040')
+    yield True
+
+    # SPI Fanout Programming...
+    pci.write(board_id, '0x083', '0x9068')
+    yield True
+
+    pci.write(board_id, '0x6a2', '0x9068')
+    yield True
+
+    # PLL calibration start...
+    # PLL Reset...
+    pci.write(board_id, '0x80000000', '0x9060')
+    yield True
+
+    # Set CH_0 (FPGA) clock FPGA ...
+    pci.write(board_id, '0x00050000', '0x9060')
+    yield True
+
+    # Set CH_3 clock fanout ...
+    pci.write(board_id, '0x00050003', '0x9060')
+    yield True
+
+    # Set CH_4 clock ADC 1 ...
+    pci.write(board_id, '0x00050004', '0x9060')
+    yield True
+
+    # Set CH_5 clock ADC 2 ...
+    pci.write(board_id, '0x00050005', '0x9060')
+    yield True
+
+    # Set CH_6 clock ADC 3 ...
+    pci.write(board_id, '0x00050006', '0x9060')
+    yield True
+
+    # Set CH_7 clock ADC 4 ...
+    pci.write(board_id, '0x00050007', '0x9060')
+    yield True
+
+    # Set R8 ...
+    pci.write(board_id, '0x10000908', '0x9060')
+    yield True
+
+    # Set R11 ...
+    pci.write(board_id, '0x0082800B', '0x9060')
+    yield True
+
+    # Set R13 ...
+    pci.write(board_id, '0x029F400D', '0x9060')
+    yield True
+
+    # Set R14 (F_out and Global_EN => ON) ...
+    pci.write(board_id, '0x0830040E', '0x9060')
+    yield True
+
+    # Set R15 ...
+    pci.write(board_id, '0xD000100F', '0x9060')
+    yield True
+
+
+def synchronisation_sequence(board_id):
+    NUMBER = 2
+    yield NUMBER
+
+    # Send the PLL sync signals ...
+    pci.write(board_id, '0x1003f0', '0x9040')
+    yield True
+
+    pci.write(board_id, '0x0003f0', '0x9040')
+    yield True
+
+def write_value_sequence(board_id):
+    NUMBER = 8
+    yield NUMBER
+
+    # Set_FPGA_clock_delay.sh 0
+    get_board_config(board_id).set_fpga_delay(get_board_config(board_id).get('fpga_delay'))
+    yield True
+
+    # Set_Delay_chip.sh 16 16 16 16
+    factors = [get_board_config(board_id).get('chip_1_delay'), get_board_config(board_id).get('chip_2_delay'),
+               get_board_config(board_id).get('chip_3_delay'), get_board_config(board_id).get('chip_4_delay')]
+    get_board_config(board_id).set_chip_delay([0, 1, 2, 3], factors)
+    yield True
+
+    # Set_TH_Delay.sh 12
+    get_board_config(board_id).set_th_delay(get_board_config(board_id).get('th_delay'))
+    yield True
+
+    # Set_ADC_1_Delay.sh 4
+    get_board_config(board_id).set_adc_delay(0, get_board_config(board_id).get('adc_1_delay'))
+    yield True
+
+    # Set_ADC_2_Delay.sh 4
+    get_board_config(board_id).set_adc_delay(1, get_board_config(board_id).get('adc_2_delay'))
+    yield True
+
+    # Set_ADC_3_Delay.sh 4
+    get_board_config(board_id).set_adc_delay(2, get_board_config(board_id).get('adc_3_delay'))
+    yield True
+
+    # Set_ADC_4_Delay.sh 4
+    get_board_config(board_id).set_adc_delay(3, get_board_config(board_id).get('adc_4_delay'))
+    yield True
+
+    pci.write(board_id, '{0:08x}'.format(get_board_config(board_id).get('orbits_observe')), '0x9020')
+    yield True
+
+    pci.write(board_id, '{0:08x}'.format(get_board_config(board_id).get('orbits_skip')), '0x9028')
+    yield True
+

+ 18 - 0
KCG/base/backend/board/status.py

@@ -0,0 +1,18 @@
+"""
+Status for boards
+"""
+
+
+class StatusStorage(object):
+    """
+    Class used as Container for various storage purposes
+    """
+    def __getattr__(self, item):
+        try:
+            # return self.old_getattr(item)
+            return object.__getattribute__(self, item)
+        except AttributeError:
+            # print item+" was not set, None is returned"
+            return None
+
+

+ 147 - 0
KCG/base/backend/board/utils.py

@@ -0,0 +1,147 @@
+"""
+Helper methods
+"""
+import logging as log
+import time
+
+from communication import pci
+from errors import *
+from status import StatusStorage
+from board_config import BoardConfiguration
+from .... import config as kcg_config
+
+
+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 get_status(board_id):
+    """
+    Get the satatus of the board (this is used for the status leds)
+    :param board_id: the id of the board
+    :return: dictionary with the bits for each led (lower case led names are the keys of this dict)
+    """
+    registers = pci.read(board_id, 3, '0x9050', decimal=True)
+    bits = []
+    bits += ['{0:032b}'.format(registers[0])]
+    bits += ['{0:032b}'.format(registers[1])]
+    bits += ['{0:032b}'.format(registers[2])]
+
+    status = {}
+
+    s1 = get_dec_from_bits(bits[0], 2, 0)
+    if s1 == 0:
+        # Pipeline in reset mode
+        # self.pipeline_led.set_tri()
+        status['pipeline'] = 2
+    elif s1 == 1:
+        # Pipeline is idle
+        # self.pipeline_led.set_on()
+        status['pipeline'] = 3
+    elif s1 == 6:
+        # Pipeline in error state
+        # self.pipeline_led.set_off()
+        status['pipeline'] = 1
+    else:
+        # Should not happen!
+        # self.pipeline_led.set_out()
+        status['pipeline'] = 0
+
+    s2 = get_dec_from_bits(bits[0], 29, 26)
+    if s2 == 0:
+        # Master Control in reset mode
+        # self.master_control_led.set_tri()
+        status['master_control'] = 2
+    elif s2 == 1:
+        # Master Control is idle
+        # self.master_control_led.set_on()
+        status['master_control'] = 3
+    elif s2 == 8:
+        # Master Control in error state
+        # self.master_control_led.set_off()
+        status['master_control'] = 1
+    else:
+        # Should not happen!
+        # self.master_control_led.set_out()
+        status['master_control'] = 0
+
+    s3 = get_dec_from_bits(bits[2], 15, 12)
+    if s3 == 15:
+        # Data Check Idle
+        # self.data_check_led.set_on()
+        status['data_check'] = 3
+    else:
+        # Data Check Error
+        # self.data_check_led.set_off()
+        status['data_check'] = 1
+
+    s4 = int(bits[0][7])
+    if s4 == 0:
+        # PLL_LD not active
+        # self.pll_ld_led.set_tri()
+        status['PLL_LD'] = 2
+    elif s4 == 1:
+        # PLL_LD is active
+        # self.pll_ld_led.set_on()
+        status['PLL_LD'] = 3
+    else:
+        status['PLL_LD'] = 0
+
+    return status
+
+
+def is_conneced(board_id):
+    try:
+        pci.read(board_id, 1, '0x9040')
+        return True
+    except BoardError:
+        return False
+    except InterfaceNotFoundError:
+        return None
+
+
+def is_active(board_id):
+    control = pci.read(board_id, 1, '0x9040')[0]
+    control_bits = '{0:032b}'.format(int(control, 16))
+    return control_bits[22:26] == "1111"
+
+
+def wait_for_revolutions(board_id):
+    n = pci.read(board_id, 1, '0x9020', decimal=True)[0]  # Get the amount of orbits to observe
+    # n = 1 # Use this for debugging purposes if no board is connected
+    spin_time_ns = kcg_config.tRev * n
+    time.sleep(spin_time_ns * 1.1)  # 10% Safety margin
+
+
+_status = []
+_configs = []
+
+
+def create_new_board_config(identifier):
+    """
+    This creates a new instance of BoardConfiguration and also a new instance of StatusStorage
+    :param identifier: the identifier for this board (not the id)
+    :return:
+    """
+    global _configs
+    global _status
+    _configs.append(BoardConfiguration(identifier))
+    _status.append(StatusStorage())
+
+
+def get_board_config(id):
+    return _configs[id]
+
+
+def get_board_status(id):
+    return _status[id]

+ 165 - 0
KCG/base/backend/dataset.py

@@ -0,0 +1,165 @@
+import logging
+import time
+
+import numpy as np
+from numpy.polynomial.polynomial import polyval
+
+from ... import config
+
+
+def _pad_array(array):
+    height, width = array.shape
+
+    # Miriam uses floor hence the padding is actually a cutting. Will wait for
+    # response if this is desired ...
+    pwidth = 2**np.floor(np.log2(width))
+    padded = np.zeros((height, pwidth))
+    padded[:, :width] = array[:, :pwidth]
+    return padded
+
+try:
+    import reikna.cluda
+    import reikna.fft
+
+    _plans = {}
+    _in_buffers = {}
+    _out_buffers = {}
+
+    _api = reikna.cluda.ocl_api()
+    _thr = _api.Thread.create()
+
+    def _fft(array):
+        start = time.time()
+        padded = _pad_array(array).astype(np.complex64)
+        height, width = padded.shape
+
+        if width in _plans:
+            fft = _plans[width]
+            in_dev = _in_buffers[width]
+            out_dev = _out_buffers[width]
+        else:
+            fft = reikna.fft.FFT(padded, axes=(1,)).compile(_thr)
+            in_dev = _thr.to_device(padded)
+            out_dev = _thr.empty_like(in_dev)
+            _plans[width] = fft
+            _in_buffers[width] = in_dev
+            _out_buffers[width] = out_dev
+
+        fft(out_dev, in_dev)
+        logging.debug("GPU fft: {} s".format(time.time() - start))
+        return out_dev.get()[:, :width / 2 + 1]
+
+except ImportError:
+    logging.debug("Failed to import reikna package. Falling back to Numpy FFT.")
+    def _fft(array):
+        start = time.time()
+        freqs = np.fft.rfft(_pad_array(array))
+        logging.debug("np fft: {} s".format(time.time() - start))
+        return freqs
+
+
+BUNCHES_PER_TURN = 184
+HEADER_SIZE_BYTES = 32
+
+
+class DataSet(object):
+    def __init__(self, array, filename, header=None):
+        self.filename = filename
+        self.array = array
+        self._heatmaps = {}
+        self._ffts = {}
+        self.header = header
+
+    @property
+    def skipped_turns(self):
+        if self.header:
+            return self.header['skipped_turns']
+        else:
+            return 1
+
+    def bunch(self, number):
+        return self.array[self.array[:, 4] == number]
+
+    def num_bunches(self):
+        return self.array.shape[0]
+
+    def num_turns(self):
+        return self.num_bunches() / BUNCHES_PER_TURN
+
+    def heatmap(self, adc=1, frm=0, to=-1, bunch_frm=0, bunch_to=-1):
+        if not 1 <= adc <= 4:
+            raise ValueError('adc must be in [1,4]')
+
+        if not adc in self._heatmaps:
+            heatmap = self.array[:,adc-1].reshape(-1, BUNCHES_PER_TURN).transpose()
+            self._heatmaps[adc] = heatmap
+
+        return self._heatmaps[adc][bunch_frm:bunch_to, frm:to]
+
+    def fft(self, adc=1, frm=0, to=-1, drop_first_bin=False):
+        if not 1 <= adc <= 4:
+            raise ValueError('adc must be in [1,4]')
+
+        # if not adc in self._ffts:
+        #     heatmap = self.heatmap(adc, frm, to)
+        #     self._ffts[adc] = np.fft.fft2(heatmap, axes=[1])
+
+        # return self._ffts[adc]
+        heatmap = self.heatmap(adc, frm, to)
+        if drop_first_bin:
+            return _fft(heatmap)[:, 1:]
+        else:
+            return _fft(heatmap)
+
+    def fft_max_freq(self):
+        return (self.num_turns() // 2 + 1) * self.fft_freq_dist()
+    def fft_freq_dist(self):
+        return 1.0/(self.num_turns() * (self.skipped_turns + 1) * config.tRev)
+
+    def train(self, adc=1, frm=0, to=-1, **kwargs):
+        pdata = self.array[frm:to, :-1]
+        return pdata[:,adc-1]
+
+    def combined(self, frm=0, to=-1, show_reconstructed=True):
+        array = self.array[frm:to, :]
+
+        N_s = 16
+        N_p = array.shape[0]
+
+        # Remove bunch number and flatten array
+        array = array[:,:4].reshape((-1, 1))
+        array = array.flatten()
+
+        # plot original data
+        orig_xs = np.arange(0, array.shape[0])
+        # axis.plot(orig_xs, array, '.')
+        ret = [np.array([orig_xs, array])]
+
+        # 4 rows for each ADC
+        ys = array.reshape((4, -1), order='F')
+
+        # multi-fit for each column of 4 ADCs, unfortunately, xs' are always the
+        # same, so we have to offset them later when computing the value
+        xs = [1, 2, 3, 4]
+        c = np.polynomial.polynomial.polyfit(xs, ys, 6)
+
+        smooth_xs = np.linspace(1, 4, N_s)
+        fitted_ys = polyval(smooth_xs, c, tensor=True)
+
+        if True:
+            # Output maxima
+            ys = np.max(fitted_ys, axis=1)
+            xs = 4 * ((np.argmax(fitted_ys, axis=1) + 1) / float(N_s)) + orig_xs[::4] - .5
+
+            # axis.plot(xs, ys, '.', color="red")
+            ret.append(np.array([xs, ys]))
+
+        if False:
+            # Debug output
+            ys = fitted_ys.reshape((-1, 1))
+            ys = ys.flatten()
+            xs = np.repeat(np.arange(0, 4 * N_p, 4), N_s) + np.tile(np.linspace(0, 3, N_s), N_p)
+            # axis.plot(xs, ys, '.', alpha=0.3)
+            ret.append(np.array([xs, ys]))
+        return ret
+

+ 155 - 0
KCG/base/backend/io.py

@@ -0,0 +1,155 @@
+import os
+import math
+import logging
+
+import numpy as np
+
+# from ...config import bunches_per_turn as BUNCHES_PER_TURN
+from ... import config
+from board import HEADER_SIZE_BYTES
+from dataset import DataSet
+import board
+
+BUNCHES_PER_TURN = config.bunches_per_turn
+
+def is_data_consistent(dataset):
+    if len(dataset.array) == 0:
+        return False
+    bunch_numbers = dataset.array[:, -1]
+    expected = np.tile(np.arange(0, BUNCHES_PER_TURN), bunch_numbers.shape[0] / BUNCHES_PER_TURN)
+    wrong_indices = np.argwhere(bunch_numbers != expected)
+    if wrong_indices.shape[0] > 0:
+        first_error =  bunch_numbers.shape[0] - wrong_indices.shape[0]
+        logging.info('Data inconsistent at offset %i'%first_error)
+        np.savetxt('wrongdump', dataset.array[first_error - 3: first_error + 3])
+        filling = bunch_numbers[wrong_indices[0][0]:]
+        expected_filling = np.tile([222, 223], filling.shape[0] / 2)
+        wrong_filling_indices = np.argwhere(filling != expected_filling)
+        if wrong_filling_indices.shape[0] > 2:  # Some times filling does not start immediately... Why? I have NO IDEA!
+            return False
+        else:
+            return True
+    else:
+        return True
+
+
+def _cached_exist(filename):
+    return os.path.exists(os.path.abspath('{}.npy'.format(filename)))
+
+
+def decode_data(data):
+        # data = data[np.where(data != 0x01234567)]
+        data = data[np.where(data != 0xDEADDEAD)]  # This is the new filling
+
+        # Make sure we read multiple of fours
+        data = data[:4 * (math.floor(data.size / 4))]
+
+        bunch_low = data & 0xfff
+        bunch_high = np.right_shift(data, 12) & 0xfff
+        bunch_number = np.right_shift(data, 24) & 0xfff
+
+        bunch_low = bunch_low.reshape(-1, 4)
+        bunch_high = bunch_high.reshape(-1, 4)
+
+        result = np.empty((bunch_low.shape[0] + bunch_high.shape[0], 5), dtype=np.uint16)
+        result[0::2,:4] = bunch_low
+        result[1::2,:4] = bunch_high
+        result[0::2, 4] = bunch_number[::4]
+        result[1::2, 4] = bunch_number[::4] + 1
+
+        result = result[:184 * (math.floor(result.shape[0] / 184)), :]
+        return result
+
+def data_has_header(data):
+    possible_header = data[0:board.HEADER_SIZE_BYTES/4]
+    back = possible_header[-1] & 0xF8888888 == 0xF8888888
+    front = possible_header[0] & 0xF8888888 == 0xF8888888
+    return (front, back)
+
+def get_num_of_skipped_turns(data, header_info):
+    header = data[0:board.HEADER_SIZE_BYTES/4]
+    if header_info[0]:
+        return header[-1] & 0b00111111
+    elif header_info[1]:
+        return header[0] & 0b00111111
+
+
+def parse_header(data, header_info):
+    """
+    Parse the Header and return the values in a dictionary
+    :param data: the data which contains a header
+    :return: dictionary with header entries
+    """
+    dic = {"skipped_turns": get_num_of_skipped_turns(data, header_info)}
+    return dic
+
+
+def read_from_file(filename, force=False, header=False, cache=False):
+    """
+    Read data from file
+    :param filename: file to read
+    :param force: force reread and do not take values from cache
+    :param header: only for backwards compatibility
+    :param cache: save cache
+    :return: dataset
+    """
+    if _cached_exist(filename) and not force:
+        cached_filename = '{}.npy'.format(filename)
+        logging.vinfo("Read pre-computed data from {}".format(cached_filename))
+        return DataSet(np.load(cached_filename), filename)
+
+    with open(filename, 'rb') as f:
+        logging.vinfo("Read data from {}".format(filename))
+
+        data = np.fromfile(f, dtype=np.uint32)
+
+        if len(data) == 0:
+            logging.error("File with 0b read.")
+            return DataSet(data, filename, header)
+        # If header is sent with the data, truncate it
+        header_info = data_has_header(data)
+        if True in header_info:
+            logging.vinfo("Header detected.")
+            # We read words of 4 bytes each
+            header = parse_header(data, header_info)
+            splice_words = HEADER_SIZE_BYTES / 4
+            data = data[splice_words:]
+        else:
+            logging.vinfo("No Header detected.")
+            header = None
+        result = decode_data(data)
+        dataset = DataSet(result, filename, header)
+
+        if cache:
+            logging.vinfo('Saving pre-computed data')
+            np.save('{}.npy'.format(filename), result)
+
+        return dataset
+
+
+def read_from_string(raw_data, force=False, header=False, cache=False, cache_filename="_heb_data_cache"):
+    if _cached_exist(cache_filename) and not force:
+        cache_file = '{}.npy'.format(cache_filename)
+        logging.vinfo("Read pre-computed data from {}".format(cache_file))
+        return DataSet(np.load(cache_file), cache_filename)
+
+    logging.vinfo("Read data directly from device.")
+    logging.vinfo("Read %i bytes of data" % len(raw_data))
+    data = np.fromstring(raw_data, dtype=np.uint32)
+    #If header is sent with the data, truncate it
+    header_info = data_has_header(data)
+    if True in header_info:
+            # We read words of 4 bytes each
+            header = parse_header(data, header_info)
+            splice_words = HEADER_SIZE_BYTES / 4
+            data = data[splice_words:]
+    else:
+        header = None
+    result = decode_data(data)
+    dataset = DataSet(result, "HEB Live Data", header)
+
+    if cache:
+        logging.vinfo('Saving pre-computed data')
+        np.save('{}.npy'.format(cache_filename), result)
+
+    return dataset

+ 1444 - 0
KCG/base/backendinterface.py

@@ -0,0 +1,1444 @@
+"""
+This is the interface to the backend.
+It is used to make the backend easily interchangable.
+All Functions that interface directly with the backend are prefixed with be_
+Functions only used internal in this module will be prefixed _bif_
+"""
+
+import logging
+import time
+from datetime import datetime as dt
+import os
+import copy
+from PyQt4 import QtGui, QtCore
+import numpy as np
+from backend import board
+from backend.board import available_boards
+from backend import io
+from backend import dataset
+from groupedelements import Buttons, Elements, live_plot_windows
+import storage
+from .. import config
+import kcgwidget as kcgw
+from kcgwidget import error
+from callbacks import callbacks
+from log import log
+from globals import glob as global_objects
+
+tr = kcgw.tr
+
+livePlotData = None
+
+
+def initStatus(st):
+    """
+    Initialize Status variables. These variables are used to transfer status variables over different modules.
+    :param st: variable to use (most likely a DummyStorage instance)
+    :return: -
+    """
+    st.continuous_read = False
+    st.calibrated = False
+    st.synced = False
+    st.defaults_set = False
+    st.status_text = tr("sw", "Ready")
+    st.time_scan = False
+    st.wait = False
+    st.last_file = None
+    st.board_connected = True
+    st.continuous_interval = 1000
+
+
+# -----------[ Backend Interface ]----------------------
+def _bif_enable_wait_cursor():
+    """
+    Show the "Wait Cursor"
+    """
+    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
+
+
+def _bif_disable_wait_cursor():
+    """
+    Show the normal Cursor
+    """
+    QtGui.QApplication.restoreOverrideCursor()
+
+
+def _bif_continuous_read_is_enabled(board_id, popup_title_text=None):
+    """
+    Checks if continuous read is enabled and if yes shows a popup dialog to ask if it shall be disabled
+    :param board_id: id of the board do manipulate
+    :param popup_title_text: Text to display in the popup that asks to disable continuous read
+    :return: bool (True if continuous read is enabled and not disabled in popup else False)
+    """
+    if board.get_board_status(board_id).continuous_read:
+        if not available_boards.multi_board:
+            popup = PopupDialog(tr("Dialog", "Continuous read is currently active!\nStop continuous read and proceed?"),
+                                title=popup_title_text)
+        else:
+            popup = PopupDialog(tr("Dialog", "Board {board_id}\nContinuous read is currently active!\nStop continuous read and proceed?").format(board_id=board_id),
+                                title=popup_title_text)
+        popup.exec_()
+        popup.deleteLater()
+        if popup.get_return_value() is False:
+            return False
+        else:
+            _bif_set_continuous_read_inactive(board_id)
+            Elements.setChecked("continuousread_"+str(board_id), False)
+            return True
+    else:
+        return False
+
+
+def bk_status_readout():
+    for brd in available_boards.board_ids:
+        _bif_status_readout(brd)
+
+
+def _bif_status_readout(board_id):
+    """
+    Read Status from board and update variables
+    as well as enable and disable corresponding Buttons
+    :return: -
+    """
+    # part_TODO: NOTE: Problem with this is that certain buttons will be greyed out until other buttons are pressed
+    # part_TODO: even if only the gui is restarted and the board is in the same state
+    if kcgw.testing:
+        return
+    if board.is_active(board_id):
+        Buttons.setEnabled("after_start_{}".format(board_id), True)
+        Buttons.setEnabled("start_board_{}".format(board_id), False)
+        Buttons.setEnabled("continuous_read_{}".format(board_id), True)
+
+        # MenuItems.setEnabled("continuous_read", True)
+    else:
+        board.get_board_status(board_id).calibrated = False
+        board.get_board_status(board_id).synced = False
+        board.get_board_status(board_id).defaults_set = False
+        Buttons.setEnabled("after_start_{}".format(board_id), False)
+        Buttons.setEnabled("continuous_read_{}".format(board_id), False)
+    Buttons.setEnabled("synchronize_{}".format(board_id), board.get_board_status(board_id).calibrated)
+    Buttons.setEnabled("set_defaults_{}".format(board_id), board.get_board_status(board_id).synced)
+    Buttons.setEnabled("acquire_{}".format(board_id), board.get_board_status(board_id).synced)
+    Buttons.setEnabled("acquireTrigger_{}".format(board_id), board.get_board_status(board_id).synced)
+    Elements.setEnabled("timing_{}".format(board_id), board.get_board_status(board_id).synced)
+
+    storage.get_board_specific_storage(board_id).update_LED()
+
+
+backup_readout = bk_status_readout
+
+
+class PopupDialog(QtGui.QDialog):
+    """
+    Simple Class to show a popup dialog.
+    """
+
+    def __init__(self, text, title=None, parent=None):
+        QtGui.QDialog.__init__(self, parent)
+        self.text = text
+        self.setWindowTitle(title if title else tr("Dialog", "User action required"))
+        self.do_layout()
+        self.return_value = False
+
+    def do_layout(self):
+        size = QtCore.QSize(200, 200)
+        # self.setMaximumSize(size)
+        self.setMinimumSize(size)
+        box = QtGui.QVBoxLayout()
+        self.text_label = QtGui.QLabel(self.text)
+        self.text_label.setAlignment(QtCore.Qt.AlignCenter)
+        box.addWidget(self.text_label)
+        self.okay_btn = QtGui.QPushButton(tr("Button", "Ok"))
+        self.okay_btn.setStyleSheet("padding: 15px;")
+        self.okay_btn.clicked.connect(self.on_okay)
+        box.addWidget(self.okay_btn)
+        box.addSpacerItem(QtGui.QSpacerItem(1, 20))
+        self.cancel_btn = QtGui.QPushButton(tr("Button", "Cancel"))
+        self.cancel_btn.setStyleSheet("padding: 15px;")
+        self.cancel_btn.clicked.connect(self.on_cancel)
+        box.addWidget(self.cancel_btn)
+        self.setLayout(box)
+
+    def on_okay(self):
+        self.return_value = True
+        self.close()
+
+    def on_cancel(self):
+        self.close()
+
+    def get_return_value(self):
+        return self.return_value
+
+
+def bk_start_board(board_id):
+    """
+    Start the Board.
+    This will set initial Registers to power up the Board.
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    _bif_enable_wait_cursor()
+    log(board_id=board_id, additional="Starting Board - following values are probably default values")
+
+    sequence = board.startup_sequence(board_id)
+    number = sequence.next()
+
+    if _bif_continuous_read_is_enabled(board_id, tr("Button", "Start Board")):
+        _bif_disable_wait_cursor()
+        return False
+
+    try:
+        if board.is_active(board_id):
+            _bif_disable_wait_cursor()
+            bk_status_readout()
+            return
+
+        logging.info("Activating Board")
+        sequence.next()
+        time.sleep(1.0)
+        dialog1 = PopupDialog(tr("Button", "Switch On the power supply --> FIRST <-- (on board {0})".format(board_id)))
+        dialog1.exec_()
+        dialog1.deleteLater()
+
+        if not dialog1.get_return_value():
+            logging.error("Starting procedure canceled")
+            _bif_disable_wait_cursor()
+            return False
+
+        logging.info("Switch ON T/Hs")
+        sequence.next()
+        time.sleep(0.1)
+        dialog2 = PopupDialog(tr("Dialog", "Switch On the power supply --> SECOND <-- (on board {0})".format(board_id)))
+        dialog2.exec_()
+        dialog2.deleteLater()
+
+        if not dialog2.get_return_value():
+            logging.info("Starting procedure canceled")
+            _bif_disable_wait_cursor()
+            return False
+
+        logging.info("Switch ON ADCs")
+        sequence.next()
+        time.sleep(0.1)
+
+        sequence.next()
+        time.sleep(1.0)
+
+        for step in sequence:
+            time.sleep(0.1)
+
+        logging.info("Board started successfully!")
+    except board.BoardError as e:
+        logging.error("Starting board failed: {}".format(str(e)))
+
+    bk_update_config(board_id, 'header', bk_get_config(board_id, 'header'))
+    _bif_disable_wait_cursor()
+    bk_status_readout()
+
+
+class _bif_ProgressBar(QtGui.QProgressBar):
+    """
+    Simple Progressbar class.
+    """
+
+    def __init__(self, min, max, text):
+        super(_bif_ProgressBar, self).__init__()
+        self.setRange(min, max)
+        self.setMaximumHeight(18)
+        # kcgw.statusbar.clearMessage()
+        self.label = QtGui.QLabel(text)
+        global_objects.get_global('statusbar').insertWidget(0, self.label)
+        global_objects.get_global('statusbar').insertWidget(1, self)
+        self.setValue(0)
+        QtGui.qApp.processEvents()
+
+    def remove(self, timeout=None):
+        """
+        Remove this instance of a progressbar
+        :param timeout: the time from calling this function to wanishing of the progressbar
+        :return: -
+        """
+
+        def remove_progressbar():
+            global_objects.get_global('statusbar').removeWidget(self)
+            global_objects.get_global('statusbar').removeWidget(self.label)
+            # kcgw.statusbar.showMessage(board.status.status_text)
+            self.destroy()
+
+        if timeout:
+            QtCore.QTimer.singleShot(timeout, remove_progressbar)
+        else:
+            remove_progressbar()
+
+
+# thread = None
+# cal = None
+def bk_calibrate(board_id, do_the_rest=None):
+    """
+    Send commands to the board that will enable it to calibrate itself.
+    This function checks if a read command is still running. BUT: It does not check if
+    the board is acquiring or something like this.
+    So Another instance of KCG can still be acquiring or calibrating at the same time. This can be dangerous.
+    :param board_id: id of the board do manipulate
+    :param do_the_rest: function to call after calibration. This is used when "Prepare Board" is pressed.
+    :return: -
+    """
+    log(board_id=board_id, additional="Calibrate")
+    thread = storage.get_board_specific_storage(board_id).setdefault('CalibrateThread', storage.ThreadStorage())
+    if thread.running:
+        logging.info("Calibration already running")
+        return
+    _bif_enable_wait_cursor()
+    if _bif_continuous_read_is_enabled(board_id, tr("Button", "Calibrate Board")):
+        _bif_disable_wait_cursor()
+        return
+
+    sequence = board.calibration_sequence(board_id)
+    number = sequence.next()
+    progressbar = _bif_ProgressBar(0, number, tr("sw", "Calibrating"))
+
+    class Calibrate(QtCore.QObject):
+        """
+        Class to use as thread class. NOTE: this is not used at the moment.
+        """
+        update_progressbar_signal = QtCore.pyqtSignal(int)
+        finished = QtCore.pyqtSignal()
+
+        def calibrate(self):
+            try:
+                logging.info('Started Board Calibration')
+                for idx, step in enumerate(sequence):
+                    time.sleep(0.5)
+                    self.update_progressbar_signal.emit(idx)
+                board.get_board_status(board_id).calibrated = True
+            except board.BoardError as e:
+                logging.error("Calibration failed: {}".format(str(e)))
+                # self.do_status_readout()
+                self.finished.emit()
+                return
+
+            logging.info("Board Calibration successful!")
+            self.finished.emit()
+
+    def thread_quit():
+        thread.stop()
+        bk_status_readout()
+        progressbar.remove(0)
+        _bif_disable_wait_cursor()
+        if do_the_rest:  # execute sync and set defaults (this is set if prepare board was pressed)
+            do_the_rest(board_id)
+
+    cal = Calibrate()
+    thread.register(cal)
+    thread.connect('update_progressbar_signal', progressbar.setValue)
+    thread.connect('finished', thread_quit)
+    thread.start('calibrate')
+
+def bk_sync_board(board_id):
+    """
+    Sends commands to the board to sync with triggers.
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    _bif_enable_wait_cursor()
+    log(board_id=board_id, additional="Synchronize")
+    if _bif_continuous_read_is_enabled(board_id, tr("Dialog", "Synchronize Board")):
+        _bif_disable_wait_cursor()
+        return
+
+    progressbar = _bif_ProgressBar(0, 100, tr("sw", "Synchronizing"))
+    sequence = board.synchronisation_sequence(board_id)
+    try:
+        sequence.next()  # skip number
+        logging.info("Synchronize PLLs")
+        sequence.next()
+        for i in range(1, 101):
+            time.sleep(0.01)
+            progressbar.setValue(i)
+        sequence.next()
+    except board.BoardError as e:
+        logging.error("Synchronization failed: {}".format(str(e)))
+        progressbar.remove(0)
+        _bif_disable_wait_cursor()
+        bk_status_readout()
+        return
+
+    progressbar.remove(0)
+
+    logging.info("Board synchronization successful!")
+    # self.set_defaults_button.setEnabled(True)
+    board.get_board_status(board_id).synced = True
+    _bif_disable_wait_cursor()
+    bk_status_readout()
+
+
+def bk_write_values(board_id, defaults=False):
+    """
+    Write values to board.
+    :param board_id: id of the board do manipulate
+    :param defaults: (bool) if True Writes default values
+    :return: -
+    """
+    _bif_enable_wait_cursor()
+    if defaults:
+        log(board_id=board_id, additional="Set Default Values")
+    else:
+        log(board_id=board_id, additional="Update Values on board")
+    if _bif_continuous_read_is_enabled(board_id, tr("Dialog", "Update Values on Board")):
+        _bif_disable_wait_cursor()
+        return
+
+    sequence = board.write_value_sequence(board_id)
+    number = sequence.next()
+    if defaults:
+        board.get_board_config(board_id)._set_defaults()
+        progressbar = _bif_ProgressBar(0, number, tr("sw", "Setting Defaults"))
+        logging.info("Setting default Values")
+    else:
+        progressbar = _bif_ProgressBar(0, number, tr("sw", "Updating Values on Board"))
+        logging.info("Updating Values")
+
+    try:
+        for idx, step in enumerate(sequence):
+            time.sleep(0.1)
+            progressbar.setValue(idx)
+    except board.BoardError as e:
+        logging.error("Updating Values failed: {}".format(str(e)))
+        progressbar.remove(0)
+        _bif_disable_wait_cursor()
+        bk_status_readout()
+        return
+
+    board.get_board_status(board_id).defaults_set = True
+    progressbar.remove(0)
+
+    if defaults:
+        logging.info("Default values set successfully!")
+    else:
+        logging.info("Updated values successfully!")
+    _bif_disable_wait_cursor()
+    bk_status_readout()
+    board.get_board_config(board_id).notify_all_observers()
+
+
+def bk_stop_board(board_id):
+    """
+    Stops the board and shuts it down
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    _bif_enable_wait_cursor()
+    log(board_id=board_id, additional="Stop Board")
+    if _bif_continuous_read_is_enabled(board_id, tr("Dialog", "Soft Reset Board")):
+        _bif_disable_wait_cursor()
+        return
+
+    try:
+        logging.info("Switching Off Board {}".format(board_id))
+        board.pci.write('0x01', '0x9040')
+        board.pci.stop_dma(board_id)
+        board.stop_board(board_id)
+        time.sleep(0.5)
+    except board.BoardError as e:
+        logging.error("Sequence failed: {}".format(str(e)))
+        _bif_disable_wait_cursor()
+        bk_status_readout()
+        return
+
+    logging.info("Board switched off successfully!")
+    Buttons.setEnabled("after_start_{}".format(board_id), False)
+    Buttons.setEnabled("start_board_{}".format(board_id), True)
+    board.get_board_status(board_id).calibrated = False
+    _bif_disable_wait_cursor()
+    bk_status_readout()
+
+def bk_soft_reset(board_id):
+    """
+    Perform a soft reset.
+    :param board_id: id of the board do manipulate
+    :return:
+    """
+    _bif_enable_wait_cursor()
+    log(board_id=board_id, additional="Soft Reset")
+    if _bif_continuous_read_is_enabled(board_id, tr("Dialog", "Soft Reset Board")):
+        _bif_disable_wait_cursor()
+        return
+
+    try:
+        logging.info("Soft-Resetting Board {}...".format(board_id))
+        board.soft_reset(board_id)
+    except board.BoardError as e:
+        logging.error("Sequence failed: {}".format(str(e)))
+        _bif_disable_wait_cursor()
+        bk_status_readout()
+        return
+
+    _bif_disable_wait_cursor()
+    bk_status_readout()
+    board.get_board_config(board_id).update('header', True)  # reset header (might be reset by soft reset)
+    logging.info("Soft-Reset successful.")
+
+
+def bk_update_config(board_id, key, value, silent=False):
+    """
+    Interface to the update command of the BoardConfiguration class.
+    :param board_id: id of the board do manipulate
+    :param key: Key to update
+    :param value: Value to set for key
+    :param silent: (bool) if True do not inform observers on update
+    :return: -
+    """
+    try:
+        if silent:
+            board.get_board_config(board_id).updateSilent(key, value)
+        else:
+            board.get_board_config(board_id).update(key, value)
+    except board.BoardError as e:
+        logging.error("Setting value of {} failed: {}".format(key, str(e)))
+
+
+def bk_get_config(board_id, key):
+    """
+    Interface to the get command of the BoardConfiguration class.
+    :param board_id: id of the board do manipulate
+    :param key: Key to get the value for
+    :return: value stored for key
+    """
+    return board.get_board_config(board_id).get(key)
+
+
+def bk_get_board_status(board_id, status_variable):
+    """
+    Interface to the status class for each board.
+    :param board_id: id of the board do manipulate
+    :param status_variable: Key to get the value for
+    :return: value stored for key
+    """
+    return getattr(board.get_board_status(board_id), status_variable, None)
+
+
+def bk_get_status(board_id):
+    """
+    Interface to the get_status of the board
+    NOTE: This is not get_board_status
+    :return: status dictionary
+    """
+    return board.get_status(board_id)
+
+
+def bk_get_board_config(board_id):
+    """
+    Get the board config instance
+    :param board_id: the id of the board
+    :return: the config instance
+    """
+    return board.get_board_config(board_id)
+
+def bk_change_num_of_orbits(board_id, value, silent=False):
+    """
+    Send new number of orbits to board and update in config
+    :param board_id: id of the board do manipulate
+    :param value: the value to send
+    :param silent: (bool) if True do not inform observers on update
+    :return: -
+    """
+    bk_update_config(board_id, "orbits_observe", value, silent=silent)
+
+
+def bk_change_num_of_skipped_orbits(board_id, value, silent=False):
+    """
+    Send new number of orbits to skip to board and update in config
+    :param board_id: id of the board do manipulate
+    :param value: the value to send
+    :param silent: (bool) if True do not inform observers on update
+    :return: -
+    """
+    bk_update_config(board_id, "orbits_skip", value, silent=silent)
+
+
+def bk_change_count(board_id, value, silent=False):
+    """
+    Change the number of acquisitions you want to make.
+    :param board_id: id of the board do manipulate
+    :param value: (int) Number of acquisitions
+    :param silent: (bool) if True do not inform observers on update
+    :return: -
+    """
+    bk_update_config(board_id, "acquisition_count", value, silent=silent)
+
+
+def bk_change_wait(board_id, value, silent=False):
+    """
+    Change the time between acquisitions.
+    :param board_id: id of the board do manipulate
+    :param value: (bool) Time in seconds
+    :param silent: (bool) if True do not inform observers on update
+    :return: -
+    """
+    bk_update_config(board_id, "orbits_wait_time", value, silent=silent)
+
+
+def bk_change_build_spectrograms(board_id, value, silent=False):
+    """
+    Change if spectrograms are built or not)
+    :param board_id: id of the board do manipulate
+    :param value: (bool) True or False built or not
+    :param silent: (bool) if True do not inform observers on update
+    :return:
+    """
+    bk_update_config(board_id, "build_spectrograms", value, silent=silent)
+
+
+def bk_change_pilot_bunch(board_id, value, silent=False):
+    """
+    Change if pilot bunch is simulated
+    :param board_id: id of the board do manipulate
+    :param value: (bool) True or False to simulate or not
+    :param silent: (bool) if True do not inform observers on update
+    :return:
+    """
+    bk_update_config(board_id, "pilot_bunch", value, silent=silent)
+
+
+def _bif_iterate_spectrograms(board_id, path):
+    """
+    BROKEN (DOES NOT GET ANY DATA)
+    Built Spectrograms line by line
+    :param board_id: id of the board do manipulate
+    :param path: where to built the spectrogram
+    :return: -
+    """
+    return  # because it is broken
+    if not os.path.isdir(str(path)):
+        return
+
+    # how does this get data? dataset.data does not exist
+    transform = dataset.data.fft(1, frm=0, to=-1)
+    for i in range(config.bunches_per_turn - 1):
+        filename = os.path.join(storage.storage.save_location, storage.storage.subdirname, str(path), "%i.hsp" % i)
+        write_header = False
+        if not os.path.isfile(filename):
+            write_header = True
+        f = open(filename, 'ab')
+        if write_header:
+            f.write("#hsp\n")  # heb spectrogram magic number
+            f.write("#"+str(board.get_board_config(board_id).get("orbits_skip")))
+            f.write("\n")
+        line = transform[i, :]
+        f.write('{:0.3f} '.format(time.time()))
+        for e in line:
+            f.write("%s " % np.absolute(e))
+        f.write("\n")
+        f.close()
+
+
+def _bif_read_data_and_save(board_id):
+    """
+    Tell the pci command to start acquisition and save data
+    Also generates the filename from settings
+    :param board_id: id of the board do manipulate
+    :return:
+    """
+    now = time.time()
+    if not os.path.isdir(str(storage.storage.save_location + '/' + storage.storage.subdirname)):
+        os.makedirs(str(storage.storage.save_location + '/' + storage.storage.subdirname))
+    filename = storage.storage.save_location + '/' + storage.storage.subdirname+'/{:0.3f}.out'.format(now)
+    board.get_board_status(board_id).last_file = filename
+
+    try:
+        simulate = board.get_board_config(board_id).get("pilot_bunch")
+        try:
+            board.acquire_data(board_id, filename, simulate=simulate)
+            if not os.path.isfile(filename):
+                    error(0x001, "No File Created")
+        except IndexError:
+            error(0x002, "Unexpected output of pci for number of orbits to observe. Returning")
+            return
+        _bif_read_and_update_data_from_file(board_id, filename)
+    except board.BoardError as e:
+        logging.error("Reading failed: {}".format(str(e)))
+
+
+def _bif_read_and_update_data_from_file(board_id, filename):
+    """
+    Proxy function for _bif_read_and_update to call with correct read_func
+    :param board_id: id of the board do manipulate
+    :param filename: filename to read data from
+    :return: -
+    """
+    _bif_read_and_update(board_id, io.read_from_file, str(filename))
+
+
+def _bif_read_and_update_data_from_string(board_id, raw_data):
+    """
+    Proxy function for _bif_read_and_update to call with correct read_func
+    :param board_id: id of the board do manipulate
+    :param raw_data: Data as string
+    :return: -
+    """
+    _bif_read_and_update(board_id, io.read_from_string, raw_data)
+
+
+def _bif_read_and_update(board_id, read_func, *args):
+    """
+    Function to read data from file or string (depending on read_func) and update plots etc.
+    :param board_id: id of the board do manipulate
+    :param read_func: function to use to read data
+    :param args: filename or raw_data (see _bif_read_and_update_from_{filename, string}
+    :return: -
+    """
+    _bif_enable_wait_cursor()
+
+    header = board.get_board_config(board_id).get('header')
+    # TODO: force_read: meaning ignore cache and read new -> in the old gui this was a commandline option
+    # TODO: cache_data: meaning cache already processed numpy data -> in the old gui this was a commandline option
+    if live_plot_windows.hasWindows(board_id):
+        data = read_func(*args, force=False, header=header, cache=False)
+
+        if not io.is_data_consistent(data):
+            callbacks.async_callback('update_consistency', False)
+            global_objects.get_global('statusbar').showMessage(tr("Dialog", "Data is inconsistent!"))
+            if read_func == io.read_from_string:
+                logging.info("Data is inconsistent")
+            else:
+                logging.info("Data is inconsistent - file: " + args[0])
+        else:
+            callbacks.async_callback('update_consistency', True)
+
+        for plotwin in live_plot_windows.getWindows(board_id):
+            plotwin.plot_live(data=data)
+            QtGui.qApp.processEvents()
+    else:
+        callbacks.async_callback('update_consistency', None)
+
+    _bif_disable_wait_cursor()
+
+
+def bk_acquire(board_id):
+    """
+    Toggle Acqisition
+    :param board_id: id of the board do manipulate
+    :return:
+    """
+    if not bk_get_config(board_id, 'use_trigger'):
+        if board.get_board_status(board_id).acquisition == True:
+            log(board_id=board_id, additional="Manually Stopped Acquisition\nPerformed Acquisitions: " + str(storage.storage.current_acquisition))
+            _bif_stop_acquisition(board_id)
+        else:
+            _bif_start_acquisition(board_id)
+    else:
+        bk_toggle_wait_on_trigger(board_id)
+
+
+def _bif_stop_acquisition(board_id):
+    """
+    Stop acquisition
+    This does stop the timer started by _bif_start_acquisition()
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    board.get_board_status(board_id).acquisition = False
+    storage.get_board_specific_storage(board_id).acquisition_progressbar.remove(0)
+    storage.get_board_specific_storage(board_id).acquisition_timer.stop()
+    # for elem in Elements.getElements("acquireTrigger_{}".format(board_id)):
+    #     if isinstance(elem, QtGui.QShortcut) or isinstance(elem, QtGui.QCheckBox):
+    #         continue
+        # elem.setIcon(QtGui.QIcon(config.install_path + config.startIcon))
+        # elem.setText(tr("Button", "Start Acquisition"))
+    Elements.setEnabled('acquire_{}'.format(board_id), True)
+    callbacks.callback('acquisition_stopped', board_id)
+
+
+def _bif_start_acquisition(board_id):
+    """
+    Start acquisition.
+    This will start a timer to automatically acquire data.
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    log(board_id=board_id, additional="Started Acquisition")
+    board.get_board_status(board_id).acquisition = True
+    Elements.setEnabled('acquire_{}'.format(board_id), False)
+    # for elem in Elements.getElements("acquireTrigger_{}".format(board_id)):
+    #     if isinstance(elem, QtGui.QShortcut) or isinstance(elem, QtGui.QCheckBox):
+    #         continue
+        # elem.setIcon(QtGui.QIcon(config.install_path + config.stopIcon))
+        # elem.setText(tr("Button", "Stop Acquisition"))
+    callbacks.callback('acquisition_started', board_id)
+    storage.get_board_specific_storage(board_id).acquisition_timer = QtCore.QTimer()
+    num_acquisitions = board.get_board_config(board_id).get("acquisition_count")
+    storage.get_board_specific_storage(board_id).acquisition_progressbar = \
+        _bif_ProgressBar(0, num_acquisitions, tr("sw", "Acquiring with board ")+str(board_id))
+    # storage.storage.acquisition_progressbar = acquisition_progressbar
+
+    # We increase already once because we do a single acquisition before the
+    # timer is started, otherwise we have to wait until the timer fires the
+    # first time.
+    try:
+        if isinstance(storage.storage.current_acquisition, dict):
+            storage.storage.current_acquisition[board_id] = 1
+        else:
+            storage.storage.current_acquisition = {board_id: 1}
+    except storage.StorageError:
+        storage.storage.current_acquisition = {board_id: 1}
+    _bif_read_data_and_save(board_id)
+
+    if board.get_board_config(board_id).get("build_spectrograms"):
+        spectrogram_dir = storage.storage.save_location + '/' + storage.storage.subdirname+"/spectrograms_{:0.3f}".format(time.time())
+        os.makedirs(spectrogram_dir)
+        _bif_iterate_spectrograms(board_id, spectrogram_dir) # TODO: not here?
+
+    storage.get_board_specific_storage(board_id).acquisition_progressbar.setValue(storage.storage.current_acquisition[board_id])
+
+    def on_timeout():
+        if storage.storage.current_acquisition[board_id] < num_acquisitions:
+            storage.storage.current_acquisition[board_id] += 1
+            storage.get_board_specific_storage(board_id).acquisition_progressbar.setValue(storage.storage.current_acquisition[board_id])
+            _bif_read_data_and_save(board_id)
+            if board.get_board_config(board_id).get("build_spectrograms"):
+                _bif_iterate_spectrograms(board_id, spectrogram_dir)  # TODO: not here ?
+        else:
+            log(board_id=board_id, additional="Stopped Acquisition")
+            _bif_stop_acquisition(board_id)
+            storage.get_board_specific_storage(board_id).acquisition_progressbar.remove(0)
+
+    storage.get_board_specific_storage(board_id).acquisition_timer.timeout.connect(on_timeout)
+    storage.get_board_specific_storage(board_id).acquisition_timer.start(board.get_board_config(board_id).get('orbits_wait_time') * 1000)
+
+
+def bk_single_read(board_id):
+    """
+    Perform a single read of data
+    :param board_id: id of the board do manipulate
+    :return:
+    """
+    Elements.setEnabled("acquire_{}".format(board_id), False)
+    _bif_read_data_and_save(board_id)
+    log(board_id=board_id, additional="Single Read\nFilename: "+board.get_board_status(board_id).last_file.split('/')[-1])
+    Elements.setEnabled("acquire_{}".format(board_id), True)
+
+
+def _bif_set_continuous_read_active(board_id):
+    """
+    Enable continuous read
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    Elements.setEnabled("acquire_{}".format(board_id), False)
+    Elements.setEnabled("acquireTrigger_{}".format(board_id), False)
+    Elements.setEnabled("continuous_read_{}".format(board_id), True)
+    board.get_board_status(board_id).continuous_read = True
+
+
+def _bif_set_continuous_read_inactive(board_id):
+    """
+    Disable continuous read
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    if board.get_board_status(board_id).continuous_read:
+        board.get_board_status(board_id).continuous_read = False
+        storage.get_board_specific_storage(board_id).continuous_read_timer.stop()
+    Elements.setEnabled('acquire_{}'.format(board_id), True)
+    Elements.setEnabled('acquireTrigger_{}'.format(board_id), True)
+
+
+def bk_continuous_read(board_id, interval=100):
+    """
+    Toggle continuous read
+    :param board_id: id of the board do manipulate
+    :param interval: Time between two consecutive reads.
+    :return: -
+    """
+    if not board.get_board_status(board_id).continuous_read:
+        _bif_set_continuous_read_active(board_id)
+        _bif_continuous_read(board_id, interval)
+    else:
+        _bif_set_continuous_read_inactive(board_id)
+
+
+def _bif_continuous_read(board_id, interval=None):
+    """
+    Perform continuous read based on a timer.
+    :param interval:
+    :return:
+    """
+    if interval is not None:
+        # TODO: ueberall checken, dass der board specific storage verwendet wird
+        storage.get_board_specific_storage(board_id).continuous_interval = interval
+    storage.get_board_specific_storage(board_id).continuous_read_timer = QtCore.QTimer()
+    logging.info("Start continuous read")
+
+    def continuous_read_step():
+        if board.get_board_status(board_id).continuous_read:
+            _bif_read_data(board_id)
+            storage.get_board_specific_storage(board_id).continuous_read_timer.singleShot(storage.storage.continuous_interval, continuous_read_step)
+
+    storage.get_board_specific_storage(board_id).continuous_read_timer.singleShot(storage.storage.continuous_interval, continuous_read_step)
+
+
+def _bif_read_data(board_id):
+    """
+    Reads data acquired by board.
+    :param board_id: id of the board do manipulate
+    :return:
+    """
+    try:
+        if board.get_board_config(board_id).get('pilot_bunch'):
+            board.start_pilot_bunch_emulator(board_id)
+
+        board.start_acquisition(board_id)
+        try:
+            board.wait_for_revolutions(board_id)
+        except IndexError:
+            error(0x002, "Unexpected output of pci for number of orbits to observe. Returning")
+            return
+        board.stop_acquisition(board_id)
+        board.enable_transfer(board_id)
+        data_raw = board.pci.read_data_to_variable(board_id)
+        _bif_read_and_update_data_from_string(board_id, data_raw)
+    except board.BoardError as e:
+        logging.error("Reading failed: {}".format(str(e)))  # TODO: board id
+
+
+def bk_board_connected(board_id):
+    """
+    Interface to the board to check if it is connected.
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    # return board.is_conneced(board_id)
+    if len(available_boards.board_ids) > 0:
+        return True
+    return False
+
+
+def bk_get_temperature(board_id):
+    """
+    Get Temperature from board and format it
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    fpga_temp_raw_hex = board.pci.read(board_id, 1, '0x9110')[0]
+    fpga_temp_raw_hex = fpga_temp_raw_hex[-3:]
+    fpga_temp_raw_bin = '{0:012b}'.format(int(fpga_temp_raw_hex, 16))
+    fpga_temp_encoded = board.get_dec_from_bits(fpga_temp_raw_bin, 9, 0)
+    fpga_temp_celsius = '{0:2.2f}'.format(((fpga_temp_encoded * 503.975) / 1024) - 273.15)
+    return fpga_temp_celsius
+
+
+backup_get_temp = bk_get_temperature
+
+
+def bk_time_scan(board_id, c_frm, c_to, f_frm, f_to, ts_pbar, plot_func, orbits_observe=None, orbits_skip=None):
+    """
+    Toggle Timescan.
+    :param board_id: id of the board do manipulate
+    :param c_frm: (int) From value for Coarse scan
+    :param c_to:  (int) To value for Coarse scan
+    :param f_frm: (int) From value for Fine scan
+    :param f_to: (int) To value for fine scan
+    :param ts_pbar: Handle to the Timescan Progressbar
+    :param plot_func: Function to plot when timescan ended.
+    :param orbits_observe: Number of orbits to observe for the timescan (original values will be restored after timescan)
+    :param orbits_skip: Number of orbits to skipfor the timescan (original values will be restored after timescan)
+    :return: -
+    """
+    if board.get_board_status(board_id).time_scan:
+        _bif_stop_time_scan(board_id, ts_pbar)
+    else:
+        _bif_start_time_scan(board_id, c_frm, c_to, f_frm, f_to, ts_pbar, plot_func, orbits_observe, orbits_skip)
+
+
+def _bif_stop_time_scan(board_id, ts_pbar):
+    """
+    Stop the timescan. This stops the timer.
+    :param board_id: id of the board do manipulate
+    :param ts_pbar: Timescan Progressbar handle
+    :return: -
+    """
+    Elements.getElements("start_time_scan_{}".format(board_id))[0].setText(tr("Button", "Start time scan"))
+    board.get_board_status(board_id).time_scan = False
+    board.get_board_config(board_id).set_delay(storage.storage.th_old[board_id])
+    board.get_board_config(board_id).set_chip_delay(
+            [0, 1, 2, 3],
+            [
+                storage.storage.chip_1_old[board_id],
+                storage.storage.chip_2_old[board_id],
+                storage.storage.chip_3_old[board_id],
+                storage.storage.chip_4_old[board_id]
+            ]
+    )
+    ts_pbar.reset()
+
+
+# tst = None  # Ugly but necessary for the thread not to be killed when function ends (which is almost immediately after start)
+# thread_ts = None
+
+
+def _bif_start_time_scan(board_id, c_frm, c_to, f_frm, f_to, timescan_progressbar, plot_func, orbits_observe, orbits_skip):
+    """
+    Start the timscan. This starts the timer
+    :param board_id: id of the board do manipulate
+    :param c_frm: From value for coarse scan
+    :param c_to: To value for coarse scan
+    :param f_frm: From value for fine scan
+    :param f_to: To value for fine scan
+    :param timescan_progressbar: Handle for the timescanprogressbar
+    :param plot_func: Function to use to plot the data
+    :param orbits_observe: Number of orbits to observe for the timescan (original values will be restored after timescan)
+    :param orbits_skip: Number of orbits to skipfor the timescan (original values will be restored after timescan)
+    :return: -
+    """
+    thread = storage.get_board_specific_storage(board_id).setdefault("TimeScanThread", storage.ThreadStorage())
+    # if thread_ts is not None:
+    #     logging.info("Time scan already running")
+    #     return
+    if thread.running:
+        logging.info("Time scan already running")
+        return
+
+    board.get_board_status(board_id).time_scan = True
+    Elements.getElements("start_time_scan_{}".format(board_id))[0].setText(tr("Button", "Stop time scan"))
+    if c_frm > c_to:
+        logging.info('Coarse Scan Interval is invalid: (%i > %i)' % (c_frm, c_to))
+        return
+    if f_frm > f_to:
+        logging.info('Fine Scan Interval is invalid: (%i > %i)' % (f_frm, f_to))
+        return
+
+    # the following could be made nicer with the use of setdefault and the use of get_board_specific_storage
+    if not hasattr(storage.storage, 'th_old'):
+        storage.storage.th_old = {}
+        storage.storage.chip_1_old = {}
+        storage.storage.chip_2_old = {}
+        storage.storage.chip_3_old = {}
+        storage.storage.chip_4_old = {}
+
+    storage.storage.th_old[board_id] = board.get_board_config(board_id).get('th_delay')
+    storage.storage.chip_1_old[board_id] = board.get_board_config(board_id).get('chip_1_delay')
+    storage.storage.chip_2_old[board_id] = board.get_board_config(board_id).get('chip_2_delay')
+    storage.storage.chip_3_old[board_id] = board.get_board_config(board_id).get('chip_3_delay')
+    storage.storage.chip_4_old[board_id] = board.get_board_config(board_id).get('chip_4_delay')
+
+    minimum = [None, None, None, None]
+    maximum = np.zeros((4, 3))
+    heatmap = np.zeros((4, (f_to - f_frm + 1), (c_to - c_frm + 1)))
+    timescan_progressbar.setRange(1, ((f_to - f_frm) + 1) * ((c_to - c_frm) + 1))
+
+    class thread_time_scan(QtCore.QObject):
+        pbarSignal = QtCore.pyqtSignal(int)
+        stopSignal = QtCore.pyqtSignal()
+        finished = QtCore.pyqtSignal()
+
+        def __init__(self, c_frm, c_to, f_frm, f_to, timescan_progressbar):
+            super(thread_time_scan, self).__init__()
+            self.c_frm = c_frm
+            self.c_to = c_to
+            self.f_frm = f_frm
+            self.f_to = f_to
+            self.timescan_progressbar = timescan_progressbar
+
+        def time_scan(self):
+            Elements.setEnabled('acquire_{}'.format(board_id), False, exclude=Elements.getElements('start_time_scan_{}'.format(board_id)))
+            if orbits_observe:
+                if not hasattr(storage.storage, 'orbits_observe_before_timescan'):
+                    storage.storage.orbits_observe_before_timescan = {}
+                storage.storage.orbits_observe_before_timescan[board_id] = board.get_board_config(board_id).get("orbits_observe") # save old values to restore after timescan
+                board.get_board_config(board_id).update("orbits_observe", orbits_observe)
+                bk_change_num_of_orbits(board_id, orbits_observe)
+            if orbits_skip:
+                if not hasattr(storage.storage, 'orbits_skip_before_timescan'):
+                    storage.storage.orbits_skip_before_timescan = {}
+                storage.storage.orbits_skip_before_timescan[board_id] = board.get_board_config(board_id).get("orbits_skip")
+                board.get_board_config(board_id).update("orbits_skip", orbits_skip)
+                bk_change_num_of_skipped_orbits(board_id, orbits_skip)
+
+            c_step = 0
+            for coarse in range(self.c_frm, self.c_to + 1):
+                try:
+                    board.get_board_config(board_id).set_delay(coarse)
+                except board.BoardError as e:
+                    self.stopSignal.emit()
+                    self.finished.emit()
+                    return
+
+                f_step = 0
+                for fine in range(self.f_frm, self.f_to + 1):
+                    board.get_board_config(board_id).set_chip_delay([0, 1, 2, 3], [fine, fine, fine, fine])
+
+                    try:
+                        if bk_get_config(board_id, 'pilot_bunch') is True:
+                            board.start_pilot_bunch_emulator(board_id)
+
+                        board.start_acquisition(board_id)
+                        try:
+                            board.wait_for_revolutions(board_id)  # Wait before asking for data
+                        except IndexError:
+                            error(0x002, "Unexpected output of pci for number of orbits to observe. Stopping Timescan")
+                            self.stopSignal.emit()
+                            return
+                        board.stop_acquisition(board_id)
+                        board.enable_transfer(board_id)
+
+                        # -----------[ IMPORTANT ]---------------------
+                        if not kcgw.testing:
+                            data_raw = board.pci.read_data_to_variable(board_id)
+                            board.flush_dma(board_id)
+                        # ----------------------------------------------
+                        else:
+                            f_name = "/home/blaxxun/Documents/Hiwi/KaptureSimulator/timescan/" + str(
+                                coarse) + "_" + str(fine) + ".str"
+                            f = open(f_name, 'r')
+                            data_raw = f.read()
+
+                        # The PCI software not only prints the desired data but also some additional information.
+                        # This information has to be removed here.
+                        # To do so we split the output string from PCI at "Writting" (Note: Writting is correct as
+                        # this is a typo in the PCI driver)
+                        # TODO: does this need board_id? (was there)
+                        data = io.read_from_string(data_raw, force=True, cache=False)
+                    except board.BoardError as e:
+                        self.stopSignal.emit()
+                        self.finished.emit()
+                        return
+
+                    for adc in range(4):
+                        buckets = data.array[:, adc:adc + 1].reshape(-1)
+                        heatmap[adc, f_step, c_step] = float(buckets.sum()) / buckets.shape[0]
+                        if heatmap[adc, f_step, c_step] > maximum[adc, 0]:
+                            maximum[adc, 0] = heatmap[adc, f_step, c_step]
+                            maximum[adc, 1] = coarse
+                            maximum[adc, 2] = fine
+                        if minimum[adc] is None or minimum[adc] > heatmap[adc, f_step, c_step]:
+                            minimum[adc] = heatmap[adc, f_step, c_step]
+
+                    self.pbarSignal.emit(((c_step * (f_to - f_frm + 1)) + f_step) + 1)
+
+                    #GUI is blocked in our tight loop. Give it an opportunity to handle events
+                    # QtGui.QApplication.processEvents() # remove this if moved to thread again
+                    if board.get_board_status(board_id).time_scan is False:
+                        # Time Scan Stop is already performed by button press. Nothing else to do but leave
+                        self.finished.emit()
+                        return
+                    f_step += 1
+                c_step += 1
+            self.finished.emit()
+
+    def finished(timescan_progressbar):
+        thread.stop()
+        _bif_stop_time_scan(board_id, timescan_progressbar)
+        Elements.setEnabled('acquire_{}'.format(board_id), True, exclude=Elements.getElements('start_time_scan_{}'.format(board_id)))
+        if orbits_observe:
+            board.get_board_config(board_id).update("orbits_observe", storage.storage.orbits_observe_before_timescan[board_id]) # restore values
+            bk_change_num_of_orbits(board_id, storage.storage.orbits_observe_before_timescan[board_id])
+        if orbits_skip:
+            board.get_board_config(board_id).update("orbits_skip", storage.storage.orbits_skip_before_timescan[board_id])
+            bk_change_num_of_skipped_orbits(board_id, storage.storage.orbits_skip_before_timescan[board_id])
+            board.get_board_config(board_id).set_delay(storage.storage.th_old[board_id])
+
+        board.get_board_config(board_id).set_chip_delay(
+                [0, 1, 2, 3],
+                [
+                    storage.storage.chip_1_old[board_id],
+                    storage.storage.chip_2_old[board_id],
+                    storage.storage.chip_3_old[board_id],
+                    storage.storage.chip_4_old[board_id]
+                ]
+        )
+
+
+        m = [np.min(heatmap[heatmap != 0]), np.max(heatmap)]  # this gives the same levels for all 4 adcs
+        plot_func(heatmap, levels=m, newTitle=str(tr("sw", "Coarserange:{c_f}-{c_t} ; Finerange:{f_f}-{f_t}")).format(
+            c_f=c_frm,
+            c_t=c_to,
+            f_f=f_frm,
+            f_t=f_to),
+                  maxima=maximum
+                  )
+
+        now = time.time()
+        if not os.path.isdir(str(storage.storage.save_location + '/' + storage.storage.subdirname + '/timescan')):
+            os.makedirs((storage.storage.save_location + '/' + storage.storage.subdirname + '/timescan'))
+        filename = storage.storage.save_location + '/' + storage.storage.subdirname + '/timescan/timescan_{:0.3f}.out'.format(
+            now)
+        f = open(filename, 'wr')
+
+        for adc in range(4):
+            f.write("#ADC_%s\n" % adc)
+            for coarse, curr_cor in enumerate(np.transpose(heatmap[adc])):
+                for fine, value in enumerate(curr_cor):
+                    f.write("%i;%i;%f\n" % ((coarse + c_frm), (fine + f_frm), value))
+            f.write('\n')
+
+        f.close()
+        f = open(filename + '.gnuplot', 'wr')
+        f.write('set datafile separator ";"\n')
+        f.write('set multiplot layout 2,2\n')
+        f.write('unset key\n')
+        for i in range(4):
+            f.write('set label 1 "ADC_%i" at graph 0.7,0.95 font ",8"\n' % (i + 1))
+            f.write('plot "%s" every :::%i::%i using 3 with lines\n' % (filename, i, i))
+        f.write('unset multiplot\n')
+        f.close()
+
+        return
+
+    tst = thread_time_scan(c_frm, c_to, f_frm, f_to, timescan_progressbar)
+    thread.register(tst)
+    thread.connect('pbarSignal', timescan_progressbar.setValue)
+    thread.connect('finished', lambda: finished(timescan_progressbar))
+    thread.connect('stopSignal', lambda: _bif_stop_time_scan(board_id, timescan_progressbar))
+    thread.start('time_scan')
+
+
+def bk_check_for_board(board_id):
+    """
+    Check if board is connected
+    Also overrides the bk_status_readout function with a function that does nothing (suppresses read attempts that
+    generate errors - if no board is connected, there is nothing to read from)
+    Also overrides the bk_get_temperature function as of the same reasons
+    :param board_id: id of the board do manipulate
+    :return: -
+    """
+    global bk_status_readout, bk_get_temperature
+    board_status = bk_board_connected(board_id)
+    if board_status:
+        if not hasattr(board.get_board_status(board_id), 'board_connected') or \
+                not board.get_board_status(board_id).board_connected:
+            bk_status_readout = backup_readout
+            bk_get_temperature = backup_get_temp
+        board.get_board_status(board_id).board_connected = True
+
+    else:
+        Elements.setEnabled('no_board_{}'.format(board_id), False)
+
+        def do_nothing():
+            pass
+
+        def no_temp(board_id):
+            return "-"
+
+        bk_status_readout = do_nothing
+        bk_get_temperature = no_temp
+        board.get_board_status(board_id).board_connected = False
+        if board_status == False:
+            board.get_board_status(board_id).status_text = tr("sw", "Board {} not connected".format(board_id))
+        elif board_status == None:
+            board.get_board_status(board_id).status_text = tr("sw", "Software Interface not found")
+
+
+def bk_toggle_wait_on_trigger(board_id, num_of_acquisitions=None, skip=None, timeout=None, method=None):
+    """
+    Toggle waiting for trigger signal to acquire
+    :param board_id: id of the board do manipulate
+    :param num_of_acquisitions: number of acquisitions to wait for
+    :param skip: how much trigger signals to skip between acquisitions
+    :param timeout: the timeout for the pci to wait for date
+    :param method: wait method to use
+            1 for wait in pci command and 2 for waiting until register is set that KAPTURE has read data
+            NOTE: this also means that method 1 enables simultaneous read and write to dma and method 2 does write and
+            read sequentially
+    :return:
+    """
+
+    thread = storage.get_board_specific_storage(board_id).setdefault('TriggerThread', storage.ThreadStorage())
+    if thread.running:
+        # Elements.getElements("acquireTrigger_{}".format(board_id))[0].setText(tr("Button", "Start Acquisition"))
+        log(board_id=board_id, additional="Stop wait on trigger on board {}".format(board_id))
+        thread.stop()
+        # for elem in Elements.getElements("acquire_{}".format(board_id)):
+        #     if isinstance(elem, QtGui.QShortcut):
+        #         continue
+            # elem.setText(tr("Button", "Stopping Acquisition"))
+            # elem.setEnabled(False)
+    else:
+        log(board_id=board_id, additional="Start wait on trigger on board {}".format(board_id))
+        # for elem in Elements.getElements("acquireTrigger_{}".format(board_id)):
+        #     if isinstance(elem, QtGui.QShortcut):
+        #         continue
+            # elem.setIcon(QtGui.QIcon(config.install_path + config.stopIcon))
+            # elem.setText(tr("Button", "Stop Acquisition"))
+        Elements.setEnabled('acquire_{}'.format(board_id), False)
+        callbacks.callback('acquisition_started', board_id)
+        _bif_start_wait_on_trigger(board_id, num_of_acquisitions, skip, timeout, method)
+
+
+def _bif_start_wait_on_trigger(board_id, num_of_acquisitions=None, skip=None, timeout=None, method=None):
+    """
+    Start waiting on external acquisition trigger. This starts the timer
+    :param board_id: id of the board do manipulate
+    :param num_of_acquisitions: number of acquisitions to do
+    :param count_label: Handle for the countlabel
+    :param method: wait method to use
+            1 for wait in pci command and 2 for waiting until register is set that KAPTURE has read data
+            NOTE: this also means that method 1 enables simultaneous read and write to dma and method 2 does write and
+            read sequentially
+    :return: -
+    """
+    thread = storage.get_board_specific_storage(board_id).setdefault('TriggerThread', storage.ThreadStorage())
+    if thread.running:
+        logging.info("Wait already running on board {}".format(board_id))
+        return
+    log(board_id=board_id, additional="Start wait on trigger")
+    board.get_board_status(board_id).wait_on_trigger = True
+    if not os.path.isdir(str(storage.storage.save_location + '/' + storage.storage.subdirname)):
+        os.makedirs(str(storage.storage.save_location + '/' + storage.storage.subdirname))
+
+    if not num_of_acquisitions:
+        num_of_acquisitions = bk_get_config(board_id, 'acquisition_count')
+    if not skip:
+        skip = bk_get_config(board_id, 'trigger_skip')
+    if not timeout:
+        timeout = bk_get_config(board_id, 'trigger_timeout')
+    if not method:
+        method = bk_get_config(board_id, 'trigger_method')
+
+    storage.get_board_specific_storage(board_id).trigger_progressbar = \
+        _bif_ProgressBar(0, num_of_acquisitions, tr("sw", "Acquiring with board ")+str(board_id))
+    board.pci.write(board_id, hex(num_of_acquisitions), "9024")
+    time.sleep(0.1)
+    board.pci.write(board_id, hex(skip), "902C")
+    time.sleep(0.1)
+    board.pci.write(board_id, 'ff0', hex_mask='ff0')  # TODO: This writes t/h 3/4 but enable_transfer etc do not
+
+    # This seems to sometimes lead to segfaults of python it self. An Idea to prevent this
+    # is to use copy.deepcopy in __init__. But there is no reason to think that that causes the problem. In fact
+    # I don't have an idea why it crashes.
+    # A possible reason could the os.rename be (or even the board.safe_call as that saves data to the disk) But I think
+    # this is rather unlikely
+    # ~~NOTE~~: the thread of calibration also triggered segfaults sometimes. But this seems to be miraculously solved.
+    # Something that is likely to cause the problem is the log.debug in board.safe_call
+    # Logging (using the logging module) is directly connected to the main thread and could cause problems
+    class thread_wait_on_signal(QtCore.QObject):
+        countUpdate = QtCore.pyqtSignal(int)
+        stopSignal = QtCore.pyqtSignal()
+        finished = QtCore.pyqtSignal()
+        liveplot = QtCore.pyqtSignal(str)
+
+        def __init__(self, num_of_acquisitions, path, timeout):
+            super(thread_wait_on_signal, self).__init__()
+            self.noa = num_of_acquisitions
+            self.path = path
+            self.timeout = timeout
+            self._quit = False
+
+            # Elements.setEnabled('acquireTrigger_{}'.format(board_id), False) # exclude=Elements.getElements('wait_on_trigger_{}'.format(board_id)))
+
+        def wait_rw_simul(self):
+            board.pci.write(board_id, 'ff0', hex_mask='ff0')  # TODO: This writes t/h 3/4 but enable_transfer etc do not
+            for num_of_acq in xrange(self.noa):
+                # def step():
+                filename = self.path +'/{:0.3f}.out'.format(time.time())
+                board.pci.read_data_to_file(board_id, filename=filename, timeout=(self.timeout*1000000))
+                # rename with correct timestamp - last modified time
+                # TODO: Exception handling when pci does not create file
+                self.countUpdate.emit(num_of_acquisitions + 1)
+                if self._quit:
+                    break
+
+                # file operations
+                if not os.path.isfile(filename):
+                    error(0x001, "No File Created")
+                    continue
+
+                newfile = '{path}/trigger_{num:05}_{htime}_{unixtime}.out'.format(
+                    num=num_of_acquisitions,
+                    htime=dt.fromtimestamp(os.path.getmtime(filename)).strftime('%Y-%m-%dT%Hh%Mm%Ss%f'),
+                    unixtime=int(os.path.getmtime(filename)),
+                    path=self.path
+                )
+                os.rename(filename, newfile)
+                if os.path.getsize(newfile) > 0:
+                    self.liveplot.emit(newfile)
+                else:
+                    logging.info("Acquired 0b, possible trigger timeout.")
+
+            self.finished.emit()
+
+        def wait_rw_seq(self):
+            for num_of_acq in xrange(self.noa):
+                board.pci.write(board_id, '00bf0', hex_mask='CF0')  # enable readout
+                pre_acq_num = board.pci.read(board_id, 1, '9034')[0]
+                time_a = time.time()
+                timeout = False
+                while pre_acq_num == board.pci.read(board_id, 1, '9034')[0]:
+                    if time.time() - time_a > self.timeout:
+                        timeout = True
+                        break
+                    if self._quit:
+                        self.finished.emit()
+                        return
+                if not timeout:
+                    board.pci.write(board_id, '000f0', hex_mask='8F0')  # disable readout
+                    board.pci.write(board_id, '007f0', hex_mask='CF0')  # enable transfer
+                    filename = self.path +'/{:0.3f}.out'.format(time.time())
+                    board.pci.read_data_to_file(board_id, filename=filename, timeout=(self.timeout*1000000))
+                    board.pci.write(board_id, '000f0', hex_mask='4F0') # disable transfer
+                    self.countUpdate.emit(copy.deepcopy(num_of_acq+1))
+                    if self._quit:
+                        break
+
+                    if not os.path.isfile(filename):
+                        error(0x001, "No File Created")
+                        continue
+
+                    newfile = '{path}/trigger_{num:05}_{htime}_{unixtime}.out'.format(
+                        num=num_of_acquisitions,
+                        htime=dt.fromtimestamp(os.path.getmtime(filename)).strftime('%Y-%m-%dT%Hh%Mm%Ss%f'),
+                        unixtime=int(os.path.getmtime(filename)),
+                        path=self.path
+                    )
+                    os.rename(filename, newfile)
+                    self.liveplot.emit(newfile)
+                else:
+                    logging.info("Trigger timeout.")
+
+
+            self.finished.emit()
+
+        def quit(self):
+            self._quit = True
+
+        def __del__(self):
+            board.pci.write(board_id, '0', '9024')
+            time.sleep(0.1)
+            board.pci.write(board_id, '0', '902C')
+            time.sleep(0.1)
+            board.pci.write(board_id, '3f0', hex_mask='ff0')  # TODO: This writes t/h 3/4 but enable_transfer etc do not
+
+    def finished():
+        board.pci.write(board_id, '0', '9024')
+        time.sleep(0.1)
+        board.pci.write(board_id, '0', '902C')
+        time.sleep(0.1)
+        board.pci.write(board_id, '3f0', hex_mask='ff0')  # TODO: This writes t/h 3/4 but enable_transfer etc do not
+
+        thread.stop()
+        board.get_board_status(board_id).wait = False
+        storage.get_board_specific_storage(board_id).trigger_progressbar.remove(0)
+        log(board_id=board_id, additional="Stop wait on trigger")
+        Elements.setEnabled('acquire_{}'.format(board_id), True)
+        callbacks.callback('acquisition_stopped', board_id)
+
+        # for elem in Elements.getElements("acquire_{}".format(board_id)):
+        #     if isinstance(elem, QtGui.QShortcut):
+        #         continue
+            # elem.setIcon(QtGui.QIcon(config.install_path + config.startIcon))
+            # elem.setText(tr("Button", "Start Acquisition"))
+            # elem.setEnabled(True)
+
+        return
+
+    twt = thread_wait_on_signal(num_of_acquisitions, storage.storage.save_location + '/' + storage.storage.subdirname,
+                                timeout)
+    thread.register(twt)
+    thread.connect('countUpdate', storage.get_board_specific_storage(board_id).trigger_progressbar.setValue)
+    thread.connect('finished', finished)
+    thread.connect('liveplot', _bif_read_and_update_data_from_file)
+    if method == 1:
+        thread.start('wait_rw_simul')
+    elif method == 2:
+        thread.start('wait_rw_seq')
+    else:
+        raise ValueError("Wrong method")

+ 443 - 0
KCG/base/bitsTable.py

@@ -0,0 +1,443 @@
+"""
+This defines the Tables used as Bits display
+"""
+
+from PyQt4 import QtGui, QtCore
+import logging
+from backend import board
+from backend.board import available_boards
+import backendinterface as bif
+import kcgwidget as kcgw
+tr = kcgw.tr
+
+class BitsDisplayTable(QtGui.QTableWidget):
+    """
+    Widget to use to display the Bits (as table)
+    """
+    def __init__(self, value, parent=None, optimalSize=True):
+        QtGui.QTableWidget.__init__(self, parent)
+        self.numbers = str(value)
+        if len(self.numbers) == 0:
+            raise ValueError("Cant create a table for a value of length 0.")
+        self.length = len(self.numbers)
+        self.do_style()
+        if optimalSize is True:
+            self.do_optimal_size()
+
+    def do_style(self):
+        self.horizontalHeader().setDefaultSectionSize(35)
+        self.horizontalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
+        self.horizontalHeader().setVisible(True)
+        self.verticalHeader().setDefaultSectionSize(17)
+        self.verticalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
+        self.verticalHeader().setVisible(False)
+        self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
+        self.setRowCount(1)
+        self.setColumnCount(self.length)
+
+        # If self.length would be 5, this line would generate ('4', '3',
+        # '2', '1', '0')
+        headers = tuple([str(i) for i in reversed(range(0, self.length))])
+        self.setHorizontalHeaderLabels(headers)
+
+        for i in range(len(self.numbers)):
+            item = QtGui.QTableWidgetItem(self.numbers[i])
+            item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
+            self.setItem(0, i, item)
+
+    def width(self):
+        width = 6
+        if self.verticalHeader().isHidden() is False:
+            width = self.verticalHeader().width() + 6
+
+        for i in range(self.columnCount()):
+            width = width + self.columnWidth(i)
+
+        return width
+
+    def height(self):
+        height = 6
+        if self.horizontalHeader().isHidden() is False:
+            height = self.horizontalHeader().height() + 6
+        for i in range(self.rowCount()):
+            height = height + self.rowHeight(i)
+
+        return height
+
+    def do_optimal_size(self):
+        size = QtCore.QSize(self.width(), self.height())
+        self.setMaximumSize(size)
+        self.setMinimumSize(size)
+
+    def stretch_to_width(self, width_in):
+        width = self.width()
+        if width >= width_in:
+            return
+
+        factor = width_in/float(width)
+        error = 0
+        for i in range(self.length):
+            current_cell_size = self.columnWidth(i)
+            new_cell_size = int(current_cell_size * factor)
+            error += new_cell_size - (current_cell_size * factor)
+            if (error >= 1.0) or (error <= -1.0):
+                new_cell_size -= int(error)
+                error -= int(error)
+            self.horizontalHeader().resizeSection(i, new_cell_size)
+
+        self.do_optimal_size()
+
+    def set_item(self, row, col, value):
+        item = QtGui.QTableWidgetItem(str(value))
+        item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
+        width = self._get_table_item_width(QtGui.QTableWidgetItem(value))
+        if width > self.columnWidth(col):
+            self.horizontalHeader().resizeSection(col, width)
+        self.setItem(row, col, item)
+
+    def set_numbers(self, value):
+        new_numbers = str(value)
+        if len(new_numbers) == 0:
+            raise ValueError("Cant create a table for a value of length 0.")
+        if len(new_numbers) != len(self.numbers):
+            raise ValueError("New Values for table don't match size."
+                             "Expected size %i but got %i" % (len(self.numbers), len(new_numbers)))
+        self.numbers = new_numbers
+        for i in range(len(self.numbers)):
+            item = self.item(0, i)
+            item.setText(self.numbers[i])
+
+    def set_label(self, start, end, label, color=None):
+        if (start < 0) or (end > self.columnCount()-1) or (start > end):
+            raise ValueError("Invalid Start and End positions for Label: %s" % label)
+        if self.rowCount() < 2:
+            self.insertRow(1)
+            for i in range(self.length):
+                self.setItem(1, i, QtGui.QTableWidgetItem(''))
+
+        span = (end-start)+1
+        if span > 1:
+            self.setSpan(1, start, 1, span)
+        item = QtGui.QTableWidgetItem(label)
+        item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
+        if color:
+            item.setBackground(color)
+        self.setItem(1, start, item)
+
+        # Check if the label is larger then then cells it spans and resize the cells
+        # accordingly, if the label ends up larger then the cells.
+        label_width = self._get_table_item_width(QtGui.QTableWidgetItem(label))
+        cells_width = 0
+        for i in range(start, end+1):
+            cells_width = cells_width + self.columnWidth(i)
+
+        if label_width > cells_width:
+            new_cell_size = label_width/span
+            for i in range(start, end+1):
+                self.horizontalHeader().resizeSection(i, new_cell_size)
+
+        self.do_optimal_size()
+
+    def grey_out_column(self, column):
+        if (column < 0) or (column > self.length):
+            raise ValueError("Supplied column is out of range for this table")
+        for i in range(self.rowCount()):
+            self.item(i, column).setForeground(QtGui.QColor(120, 120, 120))
+            self.item(i, column).setBackground(QtGui.QColor(200, 200, 200))
+
+    def undo_grey_out_column(self, column):
+        if (column < 0) or (column > self.length):
+            raise ValueError("Supplied column is out of range for this table")
+        for i in range(self.rowCount()):
+            self.item(i, column).setForeground(QtGui.QColor(0, 0, 0))
+            self.item(i, column).setBackground(QtGui.QColor(255, 255, 255))
+
+    # Create a table just to insert our item and show how large its width ends up...
+    # We have to use this stupid workaround since QTableWidgetItem has no width() property...
+    def _get_table_item_width(self, item):
+        table = QtGui.QTableWidget()
+        table.horizontalHeader().setResizeMode(QtGui.QHeaderView.ResizeToContents)
+        table.setRowCount(1)
+        table.setColumnCount(1)
+        table.setItem(0, 0, item)
+        width = table.columnWidth(0)
+        table.deleteLater()
+        return width
+
+
+class BitsEditTable(BitsDisplayTable):
+
+    def __init__(self, value, parent=None, optimalSize=True):
+        BitsDisplayTable.__init__(self, value, parent, optimalSize)
+        self.checkboxes = []
+        self.populate_checkboxes()
+        self.verticalHeader().setDefaultSectionSize(35)
+        self.do_optimal_size()
+
+    def populate_checkboxes(self):
+        for i in range(self.length):
+            widget = QtGui.QWidget()
+            self.checkboxes += [QtGui.QCheckBox()]
+            layout = QtGui.QHBoxLayout(widget)
+            layout.addWidget(self.checkboxes[i])
+            layout.setAlignment(QtCore.Qt.AlignCenter)
+            layout.setContentsMargins(0, 0, 0, 0)
+            widget.setLayout(layout)
+            self.setCellWidget(0, i, widget)
+
+    def set_numbers(self, value):
+            new_numbers = str(value)
+            if len(new_numbers) == 0:
+                raise ValueError("Cant create a table for a value of length 0.")
+            if len(new_numbers) != len(self.numbers):
+                raise ValueError("New Values for table dont match size."
+                                 "Expected size %i but got %i" % (len(self.numbers), len(new_numbers)))
+            self.numbers = new_numbers
+            for i in range(len(self.numbers)):
+                if self.numbers[i] == '1':
+                    self.checkboxes[i].setChecked(True)
+                else:
+                    self.checkboxes[i].setChecked(False)
+
+    def get_bits(self):
+        bits = ''
+        for i in range(self.length):
+            if self.checkboxes[i].isChecked():
+                bits += '1'
+            else:
+                bits += '0'
+        return bits
+
+    def get_bit(self, bit):
+        if bit > self.length:
+            return None
+        return self.checkboxes[(self.length - bit) - 1].isChecked()
+
+    def clear_all_bits(self):
+        for i in range(self.length):
+            self.checkboxes[i].setChecked(False)
+
+
+class AdvancedBoardInterface(QtGui.QWidget):
+
+    def __init__(self, parent=None, board_id=None):
+        QtGui.QWidget.__init__(self, parent)
+        self.parent = parent
+        self.do_layout()
+        self.data_flow_pipeline_status = None
+        # self.do_status_update() # Do not update status at init - gets updated whenever page is "opened"
+        if board_id is None:
+            print "No Valid board id specified for AdvancedBoardInterface."
+            raise ValueError("No Valid board id specified for AdvancedBoardInterface.")
+        self.board_id = board_id
+
+    def do_layout(self):
+
+        self.table_grid = QtGui.QGridLayout()
+        self.setLayout(self.table_grid)
+
+        self.table_grid.addWidget(QtGui.QLabel("Status1 Register 0x9050 (Readonly)"), 0, 0)
+        self.t1 = QtGui.QScrollArea()
+        self.table_grid.addWidget(self.t1, 1, 0)
+        self.status1_table = BitsDisplayTable(32*'0', self)
+        self.do_status1_table_layout(self.status1_table)
+        # self.table_grid.addWidget(self.status1_table, 1, 0)
+        self.t1.setWidget(self.status1_table)
+        self.t1.setFixedHeight(self.status1_table.height()+24)
+
+        self.table_grid.addWidget(QtGui.QLabel("Status2 Register 0x9054 (Readonly)"), 2, 0)
+        self.t2 = QtGui.QScrollArea()
+        self.table_grid.addWidget(self.t2, 3, 0)
+        self.status2_table = BitsDisplayTable(32*'0', self)
+        self.do_status2_table_layout(self.status2_table)
+        # self.table_grid.addWidget(self.status2_table, 3, 0)
+        self.t2.setWidget(self.status2_table)
+        self.t2.setFixedHeight(self.status2_table.height()+24)
+
+        self.table_grid.addWidget(QtGui.QLabel("Status3 Register 0x9058 (Readonly)"), 4, 0)
+        self.t3 = QtGui.QScrollArea()
+        self.table_grid.addWidget(self.t3, 5, 0)
+        self.status3_table = BitsDisplayTable(32*'0', self)
+        self.do_status3_table_layout(self.status3_table)
+        # self.table_grid.addWidget(self.status3_table, 5, 0)
+        self.t3.setWidget(self.status3_table)
+        self.t3.setFixedHeight(self.status3_table.height()+24)
+
+        # self.table_grid.addItem(QtGui.QSpacerItem(1, 20), 6, 0)
+        self.table_grid.addWidget(QtGui.QLabel("Control Register 0x9040"), 7, 0)
+
+        buttons_box = QtGui.QHBoxLayout()
+        buttons_box.setAlignment(QtCore.Qt.AlignLeft)
+        self.write_control_button = QtGui.QPushButton("Write Values to board")
+        self.write_control_button.setMaximumWidth(200)
+        buttons_box.addWidget(self.write_control_button)
+        self.clear_control_button = QtGui.QPushButton("Clear Input")
+        self.clear_control_button.setMaximumWidth(200)
+        buttons_box.addWidget(self.clear_control_button)
+        self.check_status_control_button = QtGui.QPushButton("Check Status")
+        self.check_status_control_button.setMaximumWidth(200)
+        buttons_box.addWidget(self.check_status_control_button)
+        buttons_widget = QtGui.QWidget()
+        buttons_widget.setLayout(buttons_box)
+        self.table_grid.addWidget(buttons_widget, 8, 0)
+
+        self.tedit = QtGui.QScrollArea()
+        self.table_grid.addWidget(self.tedit, 9, 0)
+        self.control_table = BitsEditTable(32*'0', self)
+        self.do_control_table_layout(self.control_table)
+        self.tedit.setWidget(self.control_table)
+        self.tedit.setFixedHeight(self.control_table.height()+24)
+        # self.table_grid.addWidget(self.control_table, 9, 0)
+
+        self.write_control_button.clicked.connect(self.send_control_to_board)
+        self.clear_control_button.clicked.connect(self.control_table.clear_all_bits)
+        self.check_status_control_button.clicked.connect(self.do_status_update)
+        self.check_status_control_button.setShortcut("F5")
+
+        width1 = self.status1_table.width()
+        width2 = self.status2_table.width()
+        width3 = self.status3_table.width()
+        width4 = self.control_table.width()
+        max_width = max(width1, max(width2, max(width3, width4)))
+        self.status1_table.stretch_to_width(max_width)
+        self.status2_table.stretch_to_width(max_width)
+        self.status3_table.stretch_to_width(max_width)
+        self.control_table.stretch_to_width(max_width)
+
+
+    def send_control_to_board(self):
+        if bif._bif_continuous_read_is_enabled(self.board_id, tr("Heading", "Write Registers")):
+            logging.log("Cant write to board while continuous readout is active")
+            return
+
+        bits = self.control_table.get_bits()
+        # print bits
+        dec_val_bits = int(bits, 2)
+        # print dec_val_bits
+        # self.parent.text_area.write("Writing to board Register 0x9040: %s" % ('0x{0:08x}'.format(dec_val_bits)))
+        logging.info("Writing to board Register 0x9040: %s" % ('0x{0:08x}'.format(dec_val_bits)))
+        try:
+            board.write_pci(self.board_id, hex(dec_val_bits), '0x9040')
+        except board.BoardError as e:
+            QtGui.QMessageBox.critical(self, "Board communication",
+                                       "Was unable to write value to board!\nReason: "+str(e)+"\nBoard: "+str(self.board_id))
+
+        # self.parent.do_status_readout()
+        self.do_status_update()
+
+    def do_status1_table_layout(self, table):
+        # from right to left
+        table.set_label(29, 31, "FSM_Data_Pipeline_Status", QtCore.Qt.green)
+        table.grey_out_column(28)
+        table.set_label(27, 27, "FULL", QtCore.Qt.green)
+        table.set_label(26, 26, "EMPTY", QtCore.Qt.green)
+        table.grey_out_column(25)
+        table.grey_out_column(24)
+        table.set_label(14, 23, "RD_data_Counts", QtCore.Qt.green)
+        table.set_label(13, 13, "OVR_ADC", QtGui.QColor(210, 210, 0))
+        table.grey_out_column(12)
+        table.grey_out_column(11)
+        table.grey_out_column(10)
+        table.grey_out_column(9)
+        table.grey_out_column(8)
+        table.set_label(7, 7, "PLL_LD", QtGui.QColor(210, 210, 0))
+        table.grey_out_column(6)
+        table.set_label(2, 5, "Master Control", QtCore.Qt.green)
+        table.grey_out_column(1)
+        table.set_label(0, 0, "1")
+
+    def do_status2_table_layout(self, table):
+        #from right to left
+        table.set_label(31, 31, "FIFO 128 255 empty", QtCore.Qt.green)
+        table.set_label(30, 30, "FIFO 128 255 full", QtCore.Qt.green)
+        table.grey_out_column(29)
+        table.grey_out_column(28)
+        table.set_label(17, 27, "wr data count 128 255", QtGui.QColor(210, 210, 0))
+        table.grey_out_column(16)
+        table.set_label(15, 15, "FIFO 255 64 empty", QtCore.Qt.green)
+        table.set_label(14, 14, "FIFO 255 64 full", QtCore.Qt.green)
+        table.grey_out_column(13)
+        table.grey_out_column(12)
+        table.set_label(2, 11, "rd data count 255 64", QtGui.QColor(210, 210, 0))
+        table.grey_out_column(1)
+        table.grey_out_column(0)
+
+    def do_status3_table_layout(self, table):
+        #from right to left
+        table.set_label(29, 31, "FSM_ARBITER_DDR3", QtCore.Qt.green)
+        table.grey_out_column(28)
+        table.set_label(25, 27, "FSM_WR_DDR3", QtCore.Qt.green)
+        table.grey_out_column(24)
+        table.set_label(21, 23, "FSM_R_DDR3", QtCore.Qt.green)
+        table.grey_out_column(20)
+        table.set_label(16, 19, "BC_ERROR", QtGui.QColor(210, 210, 0))
+        table.set_label(1, 15, "Number of wrong BC", QtGui.QColor(255, 255, 0))
+        table.grey_out_column(0)
+
+    def do_control_table_layout(self, table):
+        #from right to left
+        table.set_label(31, 31, "reset", QtGui.QColor(0, 255, 255))
+        table.grey_out_column(30)
+        table.grey_out_column(29)
+        table.grey_out_column(28)
+        table.set_label(27, 27, "ADC_1(A+D)", QtGui.QColor(0, 255, 255))
+        table.set_label(26, 26, "ADC_2(A+D)2", QtGui.QColor(0, 255, 255))
+        table.set_label(25, 25, "T/H_1", QtGui.QColor(210, 210, 0))
+        table.set_label(24, 24, "T/H_2", QtGui.QColor(210, 210, 0))
+        table.set_label(23, 23, "T/H_3", QtGui.QColor(210, 210, 0))
+        table.set_label(22, 22, "T/H_4", QtGui.QColor(210, 210, 0))
+        table.set_label(21, 21, "EN_data_Trans", QtCore.Qt.yellow)
+        table.set_label(20, 20, "EN_readout", QtCore.Qt.yellow)
+        table.set_label(18, 19, "ADC_1", QtCore.Qt.yellow)
+        table.set_label(16, 17, "ADC_2", QtCore.Qt.yellow)
+        table.set_label(14, 15, "ADC_3", QtCore.Qt.yellow)
+        table.set_label(12, 13, "ADC_4", QtCore.Qt.yellow)
+        table.grey_out_column(11)
+        table.grey_out_column(10)
+        table.grey_out_column(9)
+        table.grey_out_column(8)
+        table.grey_out_column(7)
+        table.grey_out_column(6)
+        table.grey_out_column(5)
+        table.grey_out_column(4)
+        table.set_label(3, 3, "Header", QtCore.Qt.green)
+        table.grey_out_column(2)
+        table.set_label(1, 1, "Pilot Bunch by FPGA", QtGui.QColor(0, 255, 255))
+        table.set_label(0, 0, "FPGA Temp monitor Reset", QtGui.QColor(0, 255, 255))
+
+    def update_status(self, registers):
+        try:
+            self.status1_table.set_numbers('{0:032b}'.format(registers[0]))
+            self.status2_table.set_numbers('{0:032b}'.format(registers[1]))
+            self.status3_table.set_numbers('{0:032b}'.format(registers[2]))
+        except Exception:
+            return
+
+    def do_status_update(self):
+        if board.get_board_status(self.board_id).board_connected:
+            registers = board.pci.read(self.board_id, 3, '0x9050', decimal=True)
+            # TODO: KEEPING read_pci as this entire thing will be removed hopefully
+            self.update_status(registers)
+            control = board.pci.read(self.board_id, 1, '0x9040')[0]
+            control_bits = '{0:032b}'.format(int(control, 16))
+            self.control_table.set_numbers(control_bits)
+
+
+class AdvanceControlView(kcgw.KCGWidgets):
+    def __init__(self):
+        super(AdvanceControlView, self).__init__()
+        self.layout = QtGui.QHBoxLayout()
+        self.setLayout(self.layout)
+        self.tabs = QtGui.QTabWidget()
+        self.layout.addWidget(self.tabs)
+
+        self.tables = []
+
+        for bid in available_boards:
+            self.tables.append(AdvancedBoardInterface(board_id=bid))
+            self.tabs.addTab(self.tables[-1], str(bid))
+
+    def pages_update_function(self):
+        for table in self.tables:
+            table.do_status_update()
+

+ 99 - 0
KCG/base/callbacks.py

@@ -0,0 +1,99 @@
+from PyQt4 import QtCore
+
+
+class NoCallbackError(Exception):
+    pass
+
+
+class CallbackExistsError(Exception):
+    pass
+
+
+class CallbackHandler(QtCore.QObject):
+    """
+    Handler for custom callbacks.
+    It can handle synchronous callbacks as well as async callbacks (using pyqtSignals)
+    """
+
+    callback_signal = QtCore.pyqtSignal(str, list, dict)
+
+    def __init__(self):
+        super(CallbackHandler, self).__init__()
+        self._callbacks = {}
+        self.callback_signal.connect(self.__async_callback_receiver)
+
+    def callback(self, name, *args, **kwargs):
+        """
+        Call all registered callback method for name
+        This passes all additional arguments and keyword arguments down to the callbacks
+        NOTE: all callbacks therefore need to accept the same number of arguments
+        :param name: the name for which the callbacks are to be called
+        :param args: arguments to be passed to the callbacks
+        :param kwargs: keyword arguments to be passed to the callbacks
+        """
+        if name in self._callbacks:
+            for callback in self._callbacks[name]:
+                callback(*args, **kwargs)
+        else:
+            raise NoCallbackError("Callback " + name + " not registered.")
+
+    def __async_callback_receiver(self, name, *args):
+        """
+        Internal Method (Called when async callbacks are to be executed)
+        """
+        self.callback(str(name), *args[0], **args[1])
+
+    def async_callback(self, name, *args, **kwargs):
+        """
+        Perform a async callback (same as callback but through pyqtSignal and therefore allowed in threads)
+        :param name: the name for which the callbacks are to be called
+        :param args: arguments to be passed to the callbacks
+        :param kwargs: keyword arguments to be passed to the callbacks
+        """
+        if name in self._callbacks:
+            self.callback_signal.emit(name, list(args), dict(kwargs))
+        else:
+            raise NoCallbackError("Callback " + name + " not registered.")
+
+    def add_callback(self, name, callback):
+        """
+        Register a callback for name
+        :param name: the name to register against
+        :param callback: the callback to register
+        """
+        if name in self._callbacks:
+            if callback in self._callbacks[name]:
+                raise CallbackExistsError("Callback " + name + " already registered.")
+            else:
+                self._callbacks[name].append(callback)
+        else:
+            self._callbacks[name] = [callback, ]
+
+    def delete_callback(self, name, callback):
+        """
+        Delete a callback from name
+        if no callback for name is left the whole group is deleted
+        :param name: the name to delete the callback from
+        :param callback: the callback to delete
+        """
+        if name not in self._callbacks:
+            raise NoCallbackError("Callback " + name + " not registered.")
+        else:
+            if callback in self._callbacks[name]:
+                self._callbacks[name].remove(callback)
+            else:
+                raise NoCallbackError("Callback " + str(callback) + " not registered in " + name+".")
+            if len(self._callbacks[name]) == 0:
+                self.delete_callback_class(name)
+
+    def delete_callback_class(self, name):
+        """
+        Delete a whole callback class
+        :param name: the name of the class
+        """
+        if name in self._callbacks:
+            del self._callbacks[name]
+        else:
+            raise NoCallbackError("Callback " + name + " not registered.")
+
+callbacks = CallbackHandler()

+ 360 - 0
KCG/base/controlwidget.py

@@ -0,0 +1,360 @@
+"""
+This module defines the Initial view of the gui
+"""
+
+from PyQt4 import QtGui, QtCore
+import logging
+
+import kcgwidget as kcgw
+from kcgwidget import error
+from backend import board
+from backend.board import available_boards
+from groupedelements import Checkboxes, Buttons, Elements
+import backendinterface as bif
+from loghandler import LogArea
+import storage
+from .. import config
+
+tr = kcgw.tr
+
+
+class LED(QtGui.QWidget):
+    """
+    Produces a graphical LED
+    """
+    def __init__(self, parent=None, status=1, height=10, width=10):
+        """
+        Initialize a LED
+        :param parent: (QWidget) parent of this widget
+        :param status: (int) (0=out, 1=off, 2=orange, 3=on) initial status of this LED
+        :param height: (int) height of the LED
+        :param width: (int) width of the LED
+        :return: -
+        """
+        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 == 3:
+            self.set_on()
+        elif status == 1:
+            self.set_off()
+        elif status == 0:
+            self.set_out()
+        elif status == 2:
+            self.set_tri()
+        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):
+        """
+        Set the LED to "on" state
+        :return: -
+        """
+        self.color = QtGui.QColor(0, 255, 0)
+        self.update()
+        self.status = 3
+
+    def set_off(self):
+        """
+        Set the LED to "off" state
+        :return: -
+        """
+        self.color = QtGui.QColor(255, 0, 0)
+        self.update()
+        self.status = 1
+
+    def set_out(self):
+        """
+        Set the LED to "OUT" state (that is like an LED without power)
+        :return: -
+        """
+        self.color = QtGui.QColor(150, 150, 150)
+        self.update()
+        self.status = 0
+
+    def set_tri(self):
+        """
+        Set the LED to "TRI" state (that is led is orange)
+        :return: -
+        """
+        self.color = QtGui.QColor(255, 255, 0)
+        self.update()
+        self.status = 2
+
+    def set_status(self, status):
+        """
+        Set the status of the led
+        :param status: status (in 0, 1, 2, 3)
+        """
+        if status == 0:
+            self.set_out()
+        elif status == 1:
+            self.set_off()
+        elif status == 2:
+            self.set_tri()
+        elif status == 3:
+            self.set_on()
+
+
+class StatusLED(QtGui.QWidget):
+    """
+    Create a Status LED with Label next to it
+    """
+    def __init__(self, text, status=None):
+        """
+        Initialise StatusLED
+        :param text: label text next to the LED
+        :param status: initial status of the LED
+        :return: -
+        """
+        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):
+        """ See set_on of LED Class """
+        self.led.set_on()
+
+    def set_off(self):
+        """ See set_off of LED Class """
+        self.led.set_off()
+
+    def set_out(self):
+        """ See set_out of LED Class """
+        self.led.set_out()
+
+    def set_tri(self):
+        """ See set_tri of LED Class """
+        self.led.set_tri()
+
+    def set_status(self, status):
+        """
+        See set_status of LED Class
+        :param status: the status to set the led to
+        """
+        self.led.set_status(status)
+
+
+class BoardControl(kcgw.KCGWidgets):
+    def __init__(self, board_id, single=False):
+        super(BoardControl, self).__init__()
+        self.board_id = board_id
+        self.layout = QtGui.QVBoxLayout()
+        self.setLayout(self.layout)
+
+        if not single:
+            self.header_layout = QtGui.QHBoxLayout()
+            left_line = QtGui.QFrame()
+            left_line.setFrameShape(QtGui.QFrame.HLine)
+            left_line.setFrameShadow(QtGui.QFrame.Sunken)
+            right_line = QtGui.QFrame()
+            right_line.setFrameShape(QtGui.QFrame.HLine)
+            right_line.setFrameShadow(QtGui.QFrame.Sunken)
+            self.header_layout.addWidget(left_line)
+            header_label = self.createLabel("Board: {}".format(available_boards.get_board_name_from_id(board_id)))
+            header_label.setFixedWidth(header_label.sizeHint().width())
+            self.header_layout.addWidget(header_label)
+            self.header_layout.addWidget(right_line)
+
+            self.layout.addLayout(self.header_layout)
+
+        self.mainControlLayout = QtGui.QHBoxLayout()
+        self.subControlLayout = QtGui.QHBoxLayout()
+        self.subControlWidget = QtGui.QWidget()
+        self.subControlWidget.setLayout(self.subControlLayout)
+        self.statusLayout = QtGui.QHBoxLayout()
+        self.layout.addLayout(self.mainControlLayout)
+        self.layout.addWidget(self.subControlWidget)
+        self.layout.addLayout(self.statusLayout)
+
+        # ----------[ LED Status ]---------------
+        self.pipeline_led = StatusLED(tr("Label", "DataFlow Pipeline"))
+        self.master_control_led = StatusLED(tr("Label", "Master Control"))
+        self.data_check_led = StatusLED(tr("Label", "Data Check"))
+        self.pll_ld_led = StatusLED(tr("Label", "PLL_LD"))
+        self.statusLayout.addWidget(self.pipeline_led)
+        self.statusLayout.addWidget(self.master_control_led)
+        self.statusLayout.addWidget(self.data_check_led)
+        self.statusLayout.addWidget(self.pll_ld_led)
+
+        # -----------[ Buttons ]--------------
+        self.all_in_one_button = self.createButton(text=tr("Button", "Prepare Board"), connect=self.all_in_one,
+                                                   tooltip=tr("Tooltip", "Start, Calibrate, Synchronize and set Defaults\nCtrl+A"))
+        self.toggleButton = self.createButton(text="", connect=self.toggle_sub_control)
+        self.toggleButton.setIcon(QtGui.QIcon(config.install_path+"icons/chevron-bottom.svg"))
+        self.toggleButton.setFixedWidth(50)
+        # self.all_in_one_button.setShortcut("Ctrl+A")
+        # self.all_in_one_button.setObjectName("all_in_one")
+
+        self.skip_init_button = self.createButton(text=tr("Button", "Skip Initialisation"), connect=self.skip_init,
+                                                  tooltip=tr("Tooltip", "Skip Initialisation and read values from board.\n"
+                                                                        "NOTE: ONLY DO THIS IF BOARD WAS CALIBRATED AND SYNCHRONIZED BEFORE"))
+
+        self.start_button = self.createButton(text=tr("Button", "Start Board"), connect=lambda x: bif.bk_start_board(board_id=board_id))
+        self.calibrate_button = self.createButton(text=tr("Button", "Calibrate"), connect=lambda x: bif.bk_calibrate(board_id=board_id))
+        self.syncronize_button = self.createButton(text=tr("Button", "Synchronize"), connect=lambda x: bif.bk_sync_board(board_id=board_id))
+        self.set_default_button = self.createButton(text=tr("Button", "Set Defaults"), connect=lambda x: bif.bk_write_values(board_id=board_id, defaults=True))
+        self.soft_reset_button = self.createButton(text=tr("Button", "Soft Reset"), connect=lambda x: bif.bk_soft_reset(board_id=board_id))
+        self.off_button = self.createButton(text=tr("Button", "Board Off"), connect=lambda x: bif.bk_stop_board(board_id=board_id))
+        self.off_button.setObjectName("off")
+        self.check_status_button = self.createButton(text=tr("Button", "Check Status"), connect=lambda x: bif._bif_status_readout(board_id=board_id))
+
+        Buttons.addButton('start_board_{}'.format(board_id), self.start_button)
+        Buttons.addButton(['calibrate_{}'.format(board_id), 'after_start_{}'.format(board_id)], self.calibrate_button)
+        Buttons.addButton(['synchronize_{}'.format(board_id), 'after_start_{}'.format(board_id)], self.syncronize_button)
+        Buttons.addButton(['set_defaults_{}'.format(board_id), 'after_start_{}'.format(board_id)], self.set_default_button)
+        Elements.addItem('no_board_{}'.format(board_id),
+                         [
+                             self.start_button,
+                             self.calibrate_button,
+                             self.syncronize_button,
+                             self.set_default_button,
+                             self.soft_reset_button,
+                             self.off_button,
+                             self.all_in_one_button
+                         ])
+
+        self.mainControlLayout.addWidget(self.skip_init_button)
+        self.mainControlLayout.addWidget(self.all_in_one_button)
+        self.mainControlLayout.addWidget(self.toggleButton)
+        self.mainControlLayout.addWidget(self.soft_reset_button)
+        self.mainControlLayout.addWidget(self.off_button)
+
+        self.statusLayout.addWidget(self.check_status_button)
+
+        self.subControlLayout.addWidget(self.start_button)
+        self.subControlLayout.addWidget(self.calibrate_button)
+        self.subControlLayout.addWidget(self.syncronize_button)
+        self.subControlLayout.addWidget(self.set_default_button)
+
+        # register the led updater function (used in backendinterface.bk_status_readout)
+        storage.get_board_specific_storage(board_id).update_LED = self.on_check
+        self.geo = self.saveGeometry()
+        self.subControlWidget.hide()
+
+    def toggle_sub_control(self):
+        self.subControlWidget.setHidden(not self.subControlWidget.isHidden())
+        if self.subControlWidget.isHidden():
+            self.toggleButton.setIcon(QtGui.QIcon(config.install_path+"icons/chevron-bottom.svg"))
+        else:
+            self.toggleButton.setIcon(QtGui.QIcon(config.install_path+"icons/chevron-top.svg"))
+        self.parent().adjustSize()
+
+    def all_in_one(self):
+        """
+        Function that gets called when the Prepare Board Button is pressed.
+        It Starts the board, syncs it and sets defaults.
+        This is accomplished via the backendinterface module
+        :return: -
+        """
+        if bif.bk_start_board(self.board_id) is not False:
+
+            def do_the_rest(board_id):
+                bif.bk_sync_board(board_id)
+                bif.bk_write_values(board_id, defaults=True)
+            bif.bk_calibrate(self.board_id, do_the_rest)
+
+    def skip_init(self):
+        board.get_board_config(self.board_id).read_from_board()
+        board.get_board_status(self.board_id).calibrated = True
+        board.get_board_status(self.board_id).synced = True
+        board.get_board_status(self.board_id).defaults_set = True
+        bif.bk_status_readout()
+
+    def on_check(self):
+        """
+        This function is the handler for the status leds on the ControlWidget View.
+        Parses the registers and sets the colors of the leds according.
+        :return: -
+        """
+        try:
+            status = bif.bk_get_status(self.board_id)
+        except IndexError:
+            error(0x002, "Pci returned not enough registers to update LEDs.")
+            return
+
+        for led, st in status.items():
+            getattr(self, led.lower()+"_led").set_status(st)
+
+
+class ControlWidget(kcgw.KCGWidgets):
+    """
+    Main Widget that is shown at start of gui.
+    """
+    def __init__(self):
+        super(ControlWidget, self).__init__()
+
+        self.overlayout = QtGui.QVBoxLayout()
+        self.setLayout(self.overlayout)
+
+        self.splitter = QtGui.QSplitter(QtCore.Qt.Vertical)
+        self.overlayout.addWidget(self.splitter)
+
+
+        self.board_control_list = {i: BoardControl(i, not available_boards.multi_board) for i in available_boards}
+        self.board_widget = QtGui.QWidget()
+        self.board_widget_layout = QtGui.QVBoxLayout()
+        self.board_widget.setLayout(self.board_widget_layout)
+        for bc in self.board_control_list.values():
+            self.board_widget_layout.addWidget(bc)
+        self.board_widget_layout.addStretch(1)
+
+        if available_boards.multi_board:
+            self.scroll_widget = QtGui.QScrollArea()
+            # self.scroll_widget.setMaximumWidth(1.1 * self.board_control_list.values()[0].minimumSizeHint().width())
+            self.scroll_widget.setWidgetResizable(True)
+            self.scroll_widget.setWidget(self.board_widget)
+            self.scroll_widget_container = QtGui.QWidget()
+            self.scroll_widget_layout = QtGui.QHBoxLayout()
+            self.scroll_widget_layout.addWidget(self.scroll_widget)
+            self.scroll_widget_layout.setAlignment(QtCore.Qt.AlignLeft)
+            self.scroll_widget_container.setLayout(self.scroll_widget_layout)
+            self.splitter.addWidget(self.scroll_widget_container)
+        else:
+            self.splitter.addWidget(self.board_widget)
+
+        # self.log_area = QtGui.QTextEdit()
+        self.log_area = LogArea()
+        self.log_area.setReadOnly(True)
+        self.splitter.addWidget(self.log_area)
+        self.log_area.init_logging()
+        self.log_area.setKeywords([
+            "Activating Board",
+            "Started Board Calibration",
+            "Synchronize PLLs",
+            "Setting default Values",
+            "Updating Values"
+        ])
+
+        # self.setTabOrder(self.soft_reset_button, self.check_status_button)
+        # self.setTabOrder(self.check_status_button, self.off_button)
+
+        # ------------------[ Logging ]----------------------
+        # log_handler = LogHandler(self.log_area)
+        # self.logger = logging.getLogger()
+        # self.logger.addHandler(log_handler)
+        # self.logger.setLevel(config.log_level)
+        # logging.logger = self.logger
+        # logging.logger.addHandler(log_handler)

+ 11 - 0
KCG/base/globals.py

@@ -0,0 +1,11 @@
+class Globals(object):
+    def __init__(self):
+        self._globals = dict()
+
+    def get_global(self, item):
+        return self._globals.get(item, None)
+
+    def set_global(self, key, value):
+        self._globals[key] = value
+
+glob = Globals()

+ 277 - 0
KCG/base/groupedelements.py

@@ -0,0 +1,277 @@
+"""
+Module to easily group elements of a gui
+"""
+from PyQt4 import QtGui
+import warnings
+from itertools import chain
+
+
+class GroupWarning(Warning):
+    """
+    General Warning Class for GroupedObjects
+    """
+    pass
+
+warnings.simplefilter('always', GroupWarning)
+
+
+class GroupedObjects:
+    """
+    This class enables grouping of objects to easily access them as groups throughout the gui.
+    """
+    def __init__(self):
+        """
+        Initialise this object
+        :return:
+        """
+        self._objects = {}
+        self._status = {}
+        self.warn = False
+        self.autoremove = False
+        self.exception_on_deleted = False
+        self.notify_deletion = False
+
+    def setFlags(self, flagDict):
+        """
+        Set Flags that define the behaviour of GroupedObjects in various events.
+        :param flagDict: Dictionary containing the Flags.
+                        Possible Flags are: (They are to be of type bool)
+                        warn: If an element is deleted and the corresponding entry is encountered by setEnabled, a
+                            Warning is raised if warn is True
+                        autoremove: If an element is deleted and the corresponding entry is encountered by setEnabled,
+                            the Element will be removed from GroupedObjects if autoremove is True
+                        exception_on_deleted: If an element is deleted and the corresponding entry is encountered by
+                            setEnabled an Exception will be raised if exception_on_deleted is True
+                        notify_deletion: If this is set to True a notification will be printed to STDOUT whenever an
+                            autoremove is performed (see above)
+        :return: -
+        """
+        self.warn = flagDict.get('warn', False)
+        self.autoremove = flagDict.get('autoremove', False)
+        self.exception_on_deleted = flagDict.get('exception_on_deleted', False)
+        self.notify_deletion = flagDict.get('notify_deletion', False)
+
+    def createEmptyGroup(self, group):
+        """
+        Create an empty group
+        :param group: (str) the name of the group
+        :return: -
+        """
+        if group in self._objects:
+            raise GroupWarning("Specified Group \""+group+"\" already in list")
+        else:
+            self._objects[group] = []
+
+    def addItem(self, group, item):
+        """
+        Add a item or items to a group and thus register with the GroupedObjects object
+        :param group: (list or str) List of groups or single group where the item is to be added
+        :param item: (single item or list) What item/s
+        :return: -
+        """
+        group = group if isinstance(group, list) else [group]
+        item = item if isinstance(item, list) else [item]
+        exclude = list(chain(*[self.getElements(i) for i in group]))
+        for gr in group:
+            if not (gr in self._status):
+                self._status[gr] = True
+            if gr in self._objects and isinstance(self._objects[gr], list):
+                self._objects[gr].extend(item)
+            else:
+                self._objects[gr] = item
+            if not self._status[gr]: # for the case when the status is set before the object is registered or the object is recreated
+                self.setEnabled(gr, False, exclude=exclude)
+
+    def setChecked(self, group, state):
+        """
+        Set the state of all the checkboxes in the group
+        :param group: What group
+        :param state: True for checked, False for unchecked
+        :return: -
+        """
+        if group in self._objects:
+            self._status[group] = state
+            for obj in self._objects[group]:
+                if isinstance(obj, QtGui.QCheckBox):
+                    obj.setChecked(state)
+        else:
+            warnings.warn("Specified Group \""+group+"\" not in list", GroupWarning, stacklevel=2)
+
+    def setEnabled(self, group, state, exclude=None):
+        """
+        Set the state of all the items in the group
+        :param group: What group
+        :param state: True for enabled, False for disabled
+        :param exclude: Exclude this item
+        :return: -
+        """
+        to_remove = []
+        if group in self._objects:
+            self._status[group] = state
+            if self._objects[group] == []:  # untested if explicit test for empty list is necessary
+                return
+            for obj in self._objects[group]:
+                try:
+                    if exclude and obj in exclude:
+                        continue
+                    if isinstance(obj, QtGui.QAction):
+                        obj.setEnabled(state)
+                    elif isinstance(obj, QtGui.QMenu):
+                        obj.menuAction().setVisible(state)
+                    else:
+                        obj.setEnabled(state)
+                except RuntimeError, e:
+                    if "deleted" in str(e):
+                        # self.removeItem(group, obj)
+                        if self.autoremove:
+                            to_remove.append([group, obj])
+                        if self.warn:
+                            warnings.warn(str(e), GroupWarning, stacklevel=3)
+                        if self.exception_on_deleted:
+                            raise e
+                    else:
+                        raise e
+            if to_remove and self.autoremove:
+                for rm in to_remove:
+                    if self.notify_deletion:
+                        print "Autoremoving element from group '" + rm[0] + "'"
+                    self.removeItem(*rm)
+        else:
+            self._status[group] = state
+            warnings.warn("Specified Group \""+group+"\" not in Elements", GroupWarning, stacklevel=2)
+
+    def addMenuItem(self, group, item):
+        """
+        Deprecated. Use addItem.
+        """
+        self.addItem(group, item)
+
+    def addButton(self, group, item):
+        """
+        Deprecated. Use addItem.
+        """
+        self.addItem(group, item)
+
+    def addCheckbox(self, group, item):
+        """
+        Deprecated. Use addItem.
+        """
+        self.addItem(group, item)
+
+    def removeItem(self, group, item):
+        """
+        Remove an element from a gropu
+        :param group: (list or str) list of groups or groupname
+        :param item:  (list or item) list of items or item to remove
+        :return: -
+        """
+        groups = group if isinstance(group, list) else [group]
+        items = item if isinstance(item, list) else [item]
+        for gr in groups:
+            if not gr in self._objects:
+                # return
+                continue
+            for it in items:
+                if it in self._objects[gr]:
+                    del self._objects[gr][self._objects[gr].index(it)]
+
+    def removeGroup(self, group):
+        """
+        Remove a group from GroupedObjects. If the group is not registered, a warning will be raised.
+        :param group: (str) the group to remove
+        :return:
+        """
+        if group in self._objects:
+            del self._objects[group]
+        else:
+            warnings.warn("Group was not registered - Nothing removed", GroupWarning, stacklevel=2)
+
+    def emptyGroup(self, group):
+        """
+        Unregister all elements from group. (This will delete the group and recreate it.
+        :param group: (str) the group to clean out.
+        :return: -
+        """
+        self.removeGroup(group)
+        self.createEmptyGroup(group)
+
+    def getElements(self, group):
+        """
+        Get the elements of a group as list.
+        :param group: (str) the gorup you want the elements of
+        :return: (list) Elements in group
+        """
+        if group in self._objects:
+            return self._objects[group]
+        else:
+            return []
+
+    def isEnabled(self, group):
+        """
+        Check if a group is enabled.
+        :param group: (str) the gorup to check.
+        :return: (bool) State of group
+        """
+        if group in self._status:
+            return self._status[group]
+        else:
+            return True  # Default for elements is enabled
+
+class LivePlotWindows():
+    """
+    Container class to hold open LivePlotWindows.
+    Added LivePlotWindows will automatically be plotted to on the event of new data.
+    """
+    def __init__(self):
+        self.plotWindows = {}
+
+    def addWindow(self, board_id, window):
+        """
+        Register a Window.
+        :param window: (PlotWidget) The window to be added.
+        :return: -
+        """
+        if board_id in self.plotWindows:
+            self.plotWindows[board_id].append(window)
+        else:
+            self.plotWindows[board_id] = [window, ]
+    def getWindows(self, board_id):
+        """
+        Get the list of registered plot windows.
+        :return: (list) List of plotWindows
+        """
+        return self.plotWindows[board_id]
+    def hasWindows(self, board_id):
+        """
+        Check if Windows are registered.
+        :return: (bool) True if there are windows and False if not.
+        """
+        if board_id in self.plotWindows and len(self.plotWindows[board_id]) > 0:
+            return True
+        else:
+            return False
+    def removeWindow(self, board_id, window):
+        """
+        Remove a window from open plot windows
+        :param window: the window to remove
+        :return:
+        """
+        del self.plotWindows[board_id][self.plotWindows[board_id].index(window)]
+
+# Every Element is acessible through every Variable set here. Checkboxes, Buttons and MenuItems variables are set
+# to improve readability of the code. They refer to the same Object
+class Proxy(object):
+    def __init__(self, target):
+        object.__setattr__(self, 'target', target)
+    def __setattr__(self, key, value):
+        setattr(object.__getattribute__(self, 'target')[object.__getattribute__(self, 'id')], key, value)
+    def __set_id__(self, id):
+        object.__setattr__(self, 'id', id)
+    def __getattr__(self, item):
+        if item == '__set_id__':
+            return object.__getattribute__(self, item)
+        else:
+            return getattr(object.__getattribute__(self, 'target')[object.__getattribute__(self, 'id')], item)
+
+Elements = Checkboxes = Buttons = MenuItems = GroupedObjects()
+live_plot_windows = LivePlotWindows()

+ 496 - 0
KCG/base/kcg.py

@@ -0,0 +1,496 @@
+from PyQt4 import QtGui, QtCore
+
+import os
+import logging
+
+# --------[ Backend ]---------
+import backendinterface as bif
+# --------[ Essentials ]---------
+import storage
+from settings import Settings
+# --------[ Necessary Widgets ]------
+import kcgwidget as kcgw
+from controlwidget import ControlWidget
+from multiWidget import MultiWidget
+from groupedelements import MenuItems, Elements
+from backend.board import available_boards
+from backend import board
+from multipage import MultiPage
+from globals import glob as global_objects
+import bitsTable as bt
+import log
+from ..widgets import initialconfig
+# ---------[ Widgets IMPORTANT!!! ]------------------
+# this enables widgets. If this is not imported (even though it is not directly used) no widgets will be available
+from ..widgets import *
+# from widgets import *  # copy in case the above line gets removed by ide
+# ---------[ IMPORTANT ]---------------------
+
+tr = kcgw.tr
+
+from .. import config
+import time
+import getpass
+
+
+def readconfig(parent):
+    """
+    Reads the config and evalues certain variables
+    Also: Validates config to check if all necessary values are there
+    :param parent: parent for popup windows
+    :return: -
+    """
+
+    nec_conf = ['acquireSettingsIcon', 'bunches_per_turn', 'default_log_entries', 'default_save_location', 'default_subdirectory_name',
+                 'epics_base_path', 'epics_log_entry_pvs', 'epics_test_pv', 'force_ask', 'guiIcon', 'language', 'logCommentIcon',
+                 'logIcon', 'newPlotDataIcon', 'newPlotLiveIcon', 'save_header', 'show_advanced_control', 'singleReadIcon', 'startIcon',
+                 'stopIcon', 'style', 'tRev', 'timingIcon']
+    missing_conf = []
+    for c in nec_conf:
+        if c not in dir(config):
+            missing_conf.append(c)
+    if missing_conf:
+        class ConfigError(Exception):
+            pass
+        raise ConfigError('The Following variables are missing in config.py: "' + '", "'.join(missing_conf)+'"')
+
+    if config.language != "en_GB":
+        kcgw.translator.load(config.install_path+'lang/'+ config.language)
+    else:
+        global tr
+        kcgw.tr = lambda _, x: x
+        tr = lambda _, x: x
+    dateG = "{d}.{m}.{y}"
+    dateGd = "{d}_{m}_{y}"
+    dateA = "{m}-{d}-{y}"
+    times = "{H}_{M}"
+    timel = "{H}_{M}_{S}"
+    session = ""
+    if "{ask}" in config.default_subdirectory_name:
+        status = False
+        while not status:
+            text, status = QtGui.QInputDialog.getText(parent, tr("Heading", "Subdirectory"),
+                                                      tr("Dialog", "Enter a name for the Subdirectory\n"
+                                                                            "in which data will be saved:\n"
+                                                                            "NOTE: You are being asked because it "
+                                                                            "was set this way in the config file."))
+            if not status and not config.force_ask:
+                config.default_subdirectory_name = "{user}_{dateGd}-{timel}"
+                break
+            else:
+                config.subdirectory_name = text.replace(" ", "_")
+                return
+    if "{sessionname}" in config.default_subdirectory_name:
+        status = False
+        while not status:
+            text, status = QtGui.QInputDialog.getText(parent, tr("Heading", "Sessionname"),
+                                                      tr("Dialog", "Enter Sessionname\n"
+                                                                           "NOTE: You are being asked because it "
+                                                                           "was set this way in the config file.:"))
+            if not status and not config.force_ask:
+                config.default_subdirectory_name = "{user}_{dateGd}-{timel}"
+                break
+            else:
+                session = text.replace(" ", "_")
+
+    config.default_subdirectory_name = config.default_subdirectory_name.format(
+        dateG=dateG, dateGd=dateGd, dateA=dateA, times=times, timel=timel,
+        d=time.strftime("%d"), m=time.strftime("%m"), y=time.strftime("%y"),
+        H=time.strftime("%H"), M=time.strftime("%M"), S=time.strftime("%S"),
+        timestamp=time.localtime(), user=getpass.getuser(), sessionname=session
+    )
+    config.subdirectory_name = config.default_subdirectory_name.format(
+        d=time.strftime("%d"), m=time.strftime("%m"), y=time.strftime("%y"),
+        H=time.strftime("%H"), M=time.strftime("%M"), S=time.strftime("%S"),
+        timestamp=time.localtime(), user=getpass.getuser()
+    )
+    if config.default_save_location == "pwd":
+        import os
+        config.save_location = os.getcwd()
+    else:
+        config.save_location = config.default_save_location
+
+
+_MultiView_Name_ = "MultiView"
+
+class CentralWidget(kcgw.KCGWidgets):
+    """
+    Central Widget for the KCG gui main window
+    """
+    def __init__(self, parent=None):
+        super(CentralWidget, self).__init__(parent=parent)
+
+
+        # -------[ Create empty Groups to avoid warnings ]---------
+        MenuItems.createEmptyGroup('Setup/Control')
+        MenuItems.createEmptyGroup('Bits Table')
+        # -------[ END ]---------------
+
+        self.layout = QtGui.QHBoxLayout()
+        self.setLayout(self.layout)
+        self.pagesWidget = MultiPage(self)
+        self.layout.addWidget(self.pagesWidget)
+        self.mainControlWidget = ControlWidget()
+        self.pagesWidget.addPage(self.mainControlWidget, "Setup/Control")
+        self.mainMultiWidget = MultiWidget()
+        self.pagesWidget.addPage(self.mainMultiWidget, "MultiView")
+        # self.tableWidget = bt.AdvancedBoardInterface(parent=self)
+        self.tableWidget = bt.AdvanceControlView()
+        self.tableWidget.hide()
+
+
+
+class Gui(QtGui.QMainWindow):
+    """
+    Main Window of the KCG gui
+    """
+    def __init__(self):
+        super(Gui, self).__init__()
+
+        self.createEmptyGroups()
+        # -------[ Check for boards and create corresponding objects ]------
+        for board_id in available_boards:
+            board.create_new_board_config(board_id)
+            # board.get_board_config(board_id).observe(None, lambda x: bif.update_header(board_id), 'header') #  Set update_header as function to call when header config is changed
+        for board_id in available_boards:
+            bif.initStatus(board.get_board_status(board_id))  # fill status storage with correct variables
+
+        readconfig(self)
+
+        # ----------[ Set Variables and create objects ]-----------------
+        # self.storage = storage.Storage()
+        self.storage = storage.storage
+        # storage.storage = self.storage
+        self.settings = None  # (this holds the settings window) Only create Window when used
+        self.statusbar = self.statusBar()
+        # kcgw.statusbar = self.statusbar # set status bar to kcgw to easily access from other sources
+        global_objects.set_global('statusbar', self.statusbar)
+        self.pageIndicator = QtGui.QLabel()
+        self.statusbar.addPermanentWidget(self.pageIndicator)
+        self.cw = CentralWidget(self)
+        self.doMenu()
+        self.setCentralWidget(self.cw)
+
+        self.initUI()
+        self.finalizeInit()
+
+        self.after_start_status_handler()
+        self.setContentsMargins(0, -10, 0, 0)
+
+
+    def initUI(self):
+        """
+        Initialize ui
+        :return: -
+        """
+        self.setWindowTitle("KCG - Kapture Control Gui")
+        self.setWindowIcon(QtGui.QIcon(config.install_path + config.guiIcon))
+        # QtGui.QApplication.setStyle("Oxygen") # Make it look less blown up in Gnome for example
+
+    def createEmptyGroups(self):
+        """
+        This creates empty groups with the GroupedObjects class in groupedelements module.
+        This has to be done to avoid warnings when groups are enabled or disabled before creation.
+        :return: -
+        """
+        for board_id in available_boards:
+            Elements.createEmptyGroup("acquire_{}".format(board_id))
+            Elements.createEmptyGroup("timing_{}".format(board_id))
+            Elements.createEmptyGroup("no_board_{}".format(board_id))
+            Elements.createEmptyGroup("continuous_read_{}".format(board_id))
+
+    def finalizeInit(self):
+        """
+        Final things done at initialisation
+        :return: -
+        """
+        self.populate_storage()
+
+        with open(config.install_path+"style/style.css") as f:
+            styleSheet = f.read()
+        if config.style == 'blue':
+            with open(config.install_path+'style/blue.css') as f:
+                styleSheet += f.read()
+        self.setStyleSheet(styleSheet)
+
+
+        # evaluate config file regarding advanced_control
+        self.showAdvancedControl(config.show_advanced_control)
+        self.storage.advanced_control = config.show_advanced_control
+
+        if not os.path.isdir(storage.storage.save_location + '/' + storage.storage.subdirname):
+            os.makedirs(storage.storage.save_location + '/' + storage.storage.subdirname)
+        self.measurementLogger = log.MeasurementLogger()
+        log.logger = self.measurementLogger
+        logStrings = []
+        functionAndParameter = []
+        for par in self.measurementLogger.predefined_parameters:  # get strings and functions in seperate lists
+            logStrings.append(par[0])
+            functionAndParameter.append(par[1])
+        for e in config.default_log_entries:  # for every entry:
+            if e in logStrings:
+                self.measurementLogger.register_parameter(e, functionAndParameter[logStrings.index(e)][0], functionAndParameter[logStrings.index(e)][1])
+        # self.measurementLogger.register_dumper(board.config.dump)  # TODO: register dumper for all boards
+        if log.no_epics and log.epics_reachable:
+            logging.error("Epics installation not found. Logfiles will not contain information that is to be "
+                         "obtained via epics.")
+        if not log.epics_reachable:
+            logging.error("Epics PVs could not be accessed. Check Internet connection and Epics PV provider. Logfiles will not contain"
+                        "information that is to be obtained via epics.")
+
+
+
+    def doMenu(self):
+        """
+        Create and show the menu and it's entries
+        :return: -
+        """
+        self.menu = self.menuBar()
+
+        self.fileMenu = self.menu.addMenu("&"+tr("Button", "File"))
+        self.saveConfigAction = self.fileMenu.addAction(tr("Button", "Save Board Configuration"), self.saveConfig)
+        self.saveConfigAction = self.fileMenu.addAction(tr("Button", "Load Board Configuration"), self.loadConfig)
+        self.settingsAction = self.fileMenu.addAction(tr("Button", "Settings"), self.showSettings, "Ctrl+P")
+        self.configAction = self.fileMenu.addAction(tr("Button", "Rerun Configuration Wizard"), self.rerunConfig)
+        self.quitAction = self.fileMenu.addAction(QtGui.QIcon(config.install_path + "icons/exit.png"), tr("Button", "Quit"), self.close, "Ctrl+Q")
+        self.menu.setCornerWidget(self.cw.pagesWidget.leftright)
+
+        # ----------[ Page specific Menu Entries ]-------------
+        self.multiMenu = self.menu.addMenu("&"+tr("Button", "Windows"))
+        MenuItems.addMenuItem(_MultiView_Name_, self.multiMenu)
+        self.plotAction = self.multiMenu.addAction(QtGui.QIcon(config.install_path + config.newPlotLiveIcon), tr("Button", "New Plot"), self.cw.mainMultiWidget.leftBar.add_plot)
+        self.addWindowMenuEntries()
+
+        if not available_boards.multi_board:
+            self.acquireMenu = self.menu.addMenu("&"+tr("Button", "Acquire"))
+            MenuItems.addMenuItem(_MultiView_Name_, self.acquireMenu)
+            self.startAcquisitionAction = self.acquireMenu.addAction(QtGui.QIcon(config.install_path + config.startIcon),
+                         tr("Button", "Start Acquisition"), lambda: bif.bk_acquire(available_boards[0]))
+            self.startAcquisitionAction.setObjectName("start_acquisition_action")
+
+            MenuItems.addMenuItem("continuous_read_{}".format(available_boards[0]), self.startAcquisitionAction)
+            MenuItems.addMenuItem("acquireTrigger_{}".format(available_boards[0]), self.startAcquisitionAction)
+
+        # -----[ disable Menu Items for MultiView as it is not the startup page ]-------------
+        # this could be avoided if menu is created before the multipage widget
+        MenuItems.setEnabled(_MultiView_Name_, False)
+
+        self.help = self.menu.addMenu("&"+tr("Button", "Help"))
+        import webbrowser
+        self.help.addAction(tr("Button", "Open Manual"), lambda: webbrowser.open(config.install_path + "Documentation/build/html/index.html"))
+        self.help.addAction(tr("Button", "About"), self.showAbout)
+
+    def saveConfig(self, board_id):
+        filenameDialog = QtGui.QFileDialog(self, tr("Heading", "Save Configuration"), '', 'KAPTURE Configuration File (*.kcf)')
+        filenameDialog.setDefaultSuffix("kcf")
+        filenameDialog.setAcceptMode(filenameDialog.AcceptSave)
+        filenameDialog.exec_()
+        filename = filenameDialog.selectedFiles()
+        if filename[0]:
+            if not board.get_board_config(board_id).save_config(filename[0]):
+                QtGui.QMessageBox.critical(self, tr("Heading", "Error Saving Config"), tr("Dialog", "There was an error saving to a config file."))
+        else:
+            QtGui.QMessageBox.critical(self, tr("Heading", "Error Saving Config"), tr("Dialog", "There was an error saving to a config file."))
+
+    def loadConfig(self, board_id):
+        filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Configuration', '', 'KAPTURE Configuration File (*.kcf)')
+        if not filename:
+            return
+        if board.get_board_config(board_id).load_config(filename):
+            bif.bk_write_values(board_id, defaults=False)
+        else:
+            QtGui.QMessageBox.critical(self, tr("Heading", "Error Loading Config"), tr("Dialog", "There was an error loading the config file, make sure it is valid and try again."))
+
+    def rerunConfig(self):
+        self.setupConfig = initialconfig.ConfigSetup(restart=True)
+        self.setupConfig.setWindowModality(QtCore.Qt.ApplicationModal)
+        def restart():
+            import subprocess
+            import sys
+            import os
+            try:
+                subprocess.Popen(['kcg'])
+            except OSError as exception:
+                try:
+                    path = config.install_path[:-4]+'kcg'
+                    subprocess.Popen([sys.executable, path])
+                except:
+                    print('ERROR: could not restart aplication:')
+                    print('  %s' % str(exception))
+                else:
+                    QtGui.qApp.quit()
+            else:
+                QtGui.qApp.quit()
+        self.setupConfig.success_signal.connect(restart)
+        self.setupConfig.show()
+
+    def showAbout(self):
+        """
+        Show the about window.
+        :return: -
+        """
+        version = open(config.install_path+"VERSION").read()
+        about = QtGui.QDialog(self) # TODO: read about text externally? read version externally?
+        about.setWindowTitle("KCG - About")
+        about_label = QtGui.QLabel(tr("About", "KAPTURE Control Gui\n"
+                                      "KCG is a graphical control interface to the KAPTURE board\n\n"
+                                      "Author: Patrick Schreiber\n\n"
+                                      "Version:\n")+version)
+        about_label.setAlignment(QtCore.Qt.AlignCenter)
+        header_label = QtGui.QLabel(tr("About", "KCG"))
+        header_label.setStyleSheet("font-size: 25pt; text-align: center;")
+        header_label.setAlignment(QtCore.Qt.AlignCenter)
+        footer_label = QtGui.QLabel(tr("About", "\nKAPTURE - Karlsruhe Pulse-Taking and Ultrafast Readout Electronics"))
+        footer_label.setStyleSheet("font-size: 7pt;")
+        footer_label.setAlignment(QtCore.Qt.AlignRight)
+
+        about_layout = QtGui.QHBoxLayout()
+        about_text_layout = QtGui.QVBoxLayout()
+        about.setLayout(about_layout)
+
+        # pxm = QtGui.QPixmap(config.guiIcon)
+
+        # icon_layout = QtGui.QVBoxLayout()
+        # icon_label = QtGui.QLabel("")
+        # icon_label.setPixmap(pxm.scaled(QtCore.QSize(128, 128), QtCore.Qt.KeepAspectRatio))
+        # icon_label.setFixedSize(130, 130)
+        # icon_layout.addWidget(icon_label)
+        # icon_layout.addStretch(1)
+
+        # about_layout.addLayout(icon_layout)
+        about_layout.addLayout(about_text_layout)
+        about_text_layout.addWidget(header_label)
+        about_text_layout.addWidget(about_label)
+        about_text_layout.addWidget(footer_label)
+
+        about.setFixedSize(400, 230)
+        about.setStyleSheet("background-color: darkgrey;")
+        about.exec_()
+
+    def addWindowMenuEntries(self):
+        """
+        Adds Window Menu entries for custom widgets
+        :return: -
+        """
+        for f in kcgw.get_registered_widgets():
+            self.multiMenu.addAction(*f[:3]) # TODO: icon - ???
+
+    def showSettings(self):
+        """
+        Create and show settings window
+        :return: -
+        """
+        if self.settings: # use preopened window
+            self.settings.show()
+            self.settings.raise_()
+            self.settings.activateWindow()
+        else:
+            self.settings = Settings(self.storage)
+            self.settings.changed.connect(self.updateSettings)
+
+    def updateSettings(self, changedsettings):
+        """
+        Update settings in storage if settings were changed in the settings window.
+        :param changedsettings: list of settings that have been changed
+        :return: -
+        """
+        for setting in changedsettings:
+            if setting == 'language':
+                lang = getattr(self.storage, setting)
+                self.update_configuration_file({'language':'"'+str(lang)+'"'})
+                QtGui.QMessageBox.information(self, "Change Language", "Language change takes effect after Gui restart", 1)
+            if setting == 'advanced_control':
+                self.showAdvancedControl(getattr(self.storage, setting))
+            if bif.bk_get_config(setting) != None:
+                bif.bk_update_config(setting, getattr(self.storage, setting))
+
+    def showAdvancedControl(self, value):
+        """
+        Enable or disable advanced table control view (Tables for registers)
+        :param value: (bool) True to show and False to hide advanced view
+        :return: -
+        """
+        if value:
+            if self.cw.tableWidget.isHidden():
+                self.cw.pagesWidget.addPage(self.cw.tableWidget, 'Bits Table', set_to_first=False)
+                self.cw.tableWidget.show()
+        else:
+            if not self.cw.tableWidget.isHidden():
+                self.cw.pagesWidget.removePage(self.cw.tableWidget)
+                self.cw.tableWidget.hide()
+
+    def after_start_status_handler(self):
+            bif.bk_status_readout()
+
+    def populate_storage(self):
+        """
+        Initially fills storage with predefined settings and configuration values
+        :return: -
+        """
+        self.storage.header = config.save_header
+        self.storage.subdirname = config.subdirectory_name
+        self.storage.save_location = config.save_location
+        self.storage.language = config.language
+        self.storage.advanced_control = False
+        def update_header(val):
+            self.storage.header = val
+            if self.settings:
+                self.settings.headerTick.setChecked(val)
+        board.get_board_config(available_boards[0]).observe(self.storage.header, update_header, 'header')  # TODO: header at one place for all boards? (here it uses the first board)
+
+    def update_configuration_file(self, new_conf):
+        """
+        Update variablevalues in config file
+        NOTE: this doesn't use standard ConfigParser as that would delete comments
+        :param new_conf: Dictionary with variable, value pair
+        :return:
+        """
+        import re
+        # filename = "config.py"
+        filename = os.path.expanduser("~")+"/.kcg/config.cfg"
+        RE = '(('+'|'.join(new_conf.keys())+')\s*=)[^\r\n]*?(\r?\n|\r)'
+        pat = re.compile(RE)
+
+        def jojo(mat,dic = new_conf ):
+            return dic[mat.group(2)].join(mat.group(1,3))
+
+        with open(filename,'rb') as f:
+            content = f.read()
+
+        with open(filename,'wb') as f:
+            f.write(pat.sub(jojo,content))
+
+    def closeEvent(self, ev):
+        """
+        Handles closing of the GUI - this function is called by pyqt upon a close event.
+        Asks if user really wants to close the gui
+        :param ev: event
+        :return: -
+        """
+        extra = ""
+        for b in available_boards:
+            if board.get_board_status(b).wait:
+                extra += '\n'+tr('Dialog', 'Waiting on external trigger is still enabled.')
+            if board.get_board_status(b).continuous_read:
+                extra += '\n'+tr('Dialog', 'Continuous read is still enabled.')
+            if extra:
+                break
+        cl = None
+        if extra:
+            cl = QtGui.QMessageBox.critical(self, tr("Heading", "Close KCG"),
+                    tr("Dialog", "Close KCG?")+extra,
+                    QtGui.QMessageBox.No | QtGui.QMessageBox.Yes,
+                    QtGui.QMessageBox.No)
+        if not cl or cl == QtGui.QMessageBox.Yes:
+            cl = QtGui.QMessageBox.question(self, tr("Heading", "Close KCG"),
+                                            tr("Dialog", "Close KCG?\nYou will loose the state of open plots etc."),
+                                            QtGui.QMessageBox.No | QtGui.QMessageBox.Yes,
+                                            QtGui.QMessageBox.No)
+
+        if cl == QtGui.QMessageBox.Yes:
+            if self.settings:
+                self.settings.close()
+            ev.accept()
+        else:
+            ev.ignore()
+

+ 647 - 0
KCG/base/kcgwidget.py

@@ -0,0 +1,647 @@
+"""
+Base Classes used in KCG.
+This module also contains various helpful Classes to make live easier ;)
+"""
+from PyQt4 import QtGui, QtCore, QtSvg
+import logging
+import sys
+
+from .. import config
+
+
+def tr(_, x):
+    return x
+
+
+class BigIconButton(QtGui.QPushButton):
+    """
+    This is a Button with a big Icon (that can fill the whole button)
+    """
+    def __init__(self, path, width, height, connect=None, tooltip=None, parent=None):
+        """
+        Setting various properties
+        :param path: (str) the path to the icon
+        :param width: (int) with of the button
+        :param height: (int) height of the button
+        :param connect: (callable) function to call when button is pressed
+        :param tooltip: (str) tool tip to show
+        :param parent: (QWidget) parent widget.
+        :return: -
+        """
+        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):
+    """
+    Clickable Label
+    """
+    clicked = QtCore.pyqtSignal()
+    def mouseReleaseEvent(self, QMouseEvent):
+        self.clicked.emit()
+
+class ClickableHBoxLayout(QtGui.QHBoxLayout):
+    clicked = QtCore.pyqtSignal()
+    def event(self, QEvent):
+        self.clicked.emit()
+        if QEvent.type() == QtCore.QEvent.MouseButtonRelease:
+            self.clicked.emit()
+            return True
+        else:
+            super(ClickableHBoxLayout, self).event(QEvent)
+            return True
+
+class switchLabel(QtGui.QLabel):
+    """
+    This implements a Switch.
+    It switches between left and right.
+    """
+    clicked = QtCore.pyqtSignal()
+
+    def __init__(self, startRight=False): # self.state: True->Right False->Left
+        """
+        Initialise switchLabel
+        As a normal Button it emits the clicked event when clicked.
+        It does NOT have two events for each side at the moment.
+        :param startRight: (bool) whether the switch is initially set to the right position (default is left (False))
+        :return: -
+        """
+        super(switchLabel, self).__init__()
+        self.leftSwitch = QtGui.QPixmap(config.install_path+"icons/SwitchButtonLeft.png")
+        self.leftSwitch = self.leftSwitch.scaled(40, 20, transformMode=QtCore.Qt.SmoothTransformation)
+        self.rightSwitch = QtGui.QPixmap(config.install_path+"icons/SwitchButtonRight.png")
+        self.rightSwitch = self.rightSwitch.scaled(40, 20, transformMode=QtCore.Qt.SmoothTransformation)
+        if startRight:
+            self.setPixmap(self.rightSwitch)
+            self.state = True
+        else:
+            self.setPixmap(self.leftSwitch)
+            self.state = False
+    def mouseReleaseEvent(self, QMouseEvent):
+        if self.state:
+            self.state = False
+            self.setPixmap(self.leftSwitch)
+        else:
+            self.state = True
+            self.setPixmap(self.rightSwitch)
+        self.clicked.emit()
+
+class Switch(QtGui.QWidget):
+    clicked = QtCore.pyqtSignal()
+    def __init__(self, leftLabel, rightLabel, startRight=False):
+        super(Switch, self).__init__()
+        self.layout = QtGui.QHBoxLayout()
+        self.switch = switchLabel(startRight)
+        self.switch.clicked.connect(self.clicked.emit)
+        self.setLayout(self.layout)
+        self.layout.addStretch(1)
+        self.layout.addWidget(QtGui.QLabel(leftLabel))
+        self.layout.addWidget(self.switch)
+        self.layout.addWidget(QtGui.QLabel(rightLabel))
+        self.layout.addStretch(1)
+
+    def state(self):
+        return self.switch.state
+
+
+class ClickableSVG(QtGui.QWidget):
+    """
+    This implements a clickable SVG Image
+    """
+    clicked = QtCore.pyqtSignal()
+
+    def __init__(self, path, width, height, wwidth=None, wheight=None, parent=None):
+        """
+        Initialisation of ClickabeSVG
+        :param path: (str) path to the svg file
+        :param width: (int) width of the svg
+        :param height: (int) height of the svg
+        :param wwidth: (int) width of the widget (not the svg)
+        :param wheight: (int) height of the widget (not the svg)
+        :param parent: (QWidget) parent widget of the ClickableSVG
+        :return: -
+        """
+        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):
+        """
+        Change the SVG of this widget
+        :param path: (str) path to the new svg file
+        :return: -
+        """
+        self.svg.load(path)
+
+
+class KCGWidgets(QtGui.QWidget):
+    """
+    Base Class for alsmost all Widgets used in KCG.
+    It holds various properties as well as methods that simplify creation of certain objects such as labels, buttons ...
+    """
+    closeSignal = QtCore.pyqtSignal()
+
+    def __init__(self, name=None, parent=None):
+        """
+        Initialise this baseclass
+        :param name: (str) name of the widget
+        :param parent: (QWidget) parent of this widget
+        :return: -
+        """
+        super(KCGWidgets, self).__init__(parent)
+        self._id = None
+        self._type = None
+        self._name = None
+        if name:
+            self.theName = name
+
+    @property
+    def theType(self):
+        """
+        Type of this widget
+        """
+        return self._type
+
+    @property
+    def theId(self):
+        """
+        ID of this widget
+        """
+        return self._id
+
+    @property
+    def theName(self):
+        """
+        Name of this widget
+        """
+        return self._name
+
+    @theType.setter
+    def theType(self, t):
+        """
+        Setter for the type of this widget
+        :param t: (int) type
+        """
+        self._type = t # TODO: ??
+
+    @theId.setter
+    def theId(self, i):
+        """
+        Setter for the id of this widget
+        :param i: (int) id
+        """
+        self._id = i # TODO: ??
+
+    @theName.setter
+    def theName(self, n):
+        """
+        Setter for the name of this widget
+        :param n: (str) name
+        """
+        self._name = n # TODO: ??
+
+    def createButton(self, text="", x=None, y=None, dimensions=None, tooltip="", connect=False, icon=None):
+        """
+        Create a Button
+        :param text: (str) Text to display on the button
+        :param x: (int) x-position
+        :param y: (int) y-position
+        :param dimensions: (QSize) dimensions of the button
+        :param tooltip: (str) tooltip to display
+        :param connect: (callable) connect the button pressed event to this callable
+        :param icon: (QIcon) Icon to display on the button
+        :return: -
+        """
+        button = QtGui.QPushButton(text, self)
+        if tooltip:
+            button.setToolTip(tooltip)
+        if not dimensions:
+            button.resize(button.sizeHint())
+        else:
+            button.resize(dimensions)
+        if x and y:
+            button.move(x, y)
+        if connect:
+            button.clicked.connect(connect)
+        if icon:
+            button.setIcon(icon)
+        return button
+
+    def createLabel(self, text=None, image=None, tooltip=None, click=False, connect=None):
+        """
+        Create a Label
+        :param text: (str) Text to display on this label
+        :param image: (QPixmap) Image to display on this label
+        :param tooltip: (str) tooltip to display
+        :param click: (bool) make this a clickable label?
+        :param connect: (callable) if click is true, connect the clicked event to this callable
+        :return: -
+        """
+        if click:
+            label = clickLabel(self)
+            if connect:
+                label.clicked.connect(connect)
+        else:
+            label = QtGui.QLabel(self)
+        if text:
+            label.setText(text)
+        if image:
+            label.setPixmap(image)
+        if tooltip:
+            label.setToolTip(tooltip)
+        return label
+
+    def createSpinbox(self, min, max, interval=1, start_value=0, connect=None):
+        """
+        create a Spinbox
+        :param min: (int) minimum Value
+        :param max: (int) maximum Value
+        :param interval: (int) interval
+        :param start_value: (int) start Value
+        :param connect: (callable) function to call on value change
+        :return: -
+        """
+        spinbox = QtGui.QSpinBox()
+        spinbox.setRange(min, max)
+        spinbox.setSingleStep(interval)
+        spinbox.setValue(start_value)
+        if connect:
+            spinbox.valueChanged.connect(connect)
+        return spinbox
+
+    def createInput(self, text=None, read_only=False, width=None):
+        """
+        Create Input
+        :param text: (str) Default Text
+        :param read_only: (bool) set input as read only
+        :param width: (int) width of the input field in pixels
+        :return: -
+        """
+        input = QtGui.QLineEdit()
+        if text:
+            input.setText(text)
+        if width:
+            input.setMinimumWidth(width)
+        input.setReadOnly(read_only)
+        return input
+
+    def createCheckbox(self, text="", tooltip="", checked=False, connect=None):
+        """
+        Create Checkbox
+        :param tooltip: (str) what tooltip text to display
+        :param checked: (bool) Checkt as default?
+        :param connect: (callable) function to connect
+        :return:
+        """
+        cb = QtGui.QCheckBox(text)
+        cb.setToolTip(tooltip)
+        cb.setChecked(checked)
+        if connect:
+            cb.clicked.connect(connect)
+        return cb
+
+    def createSwitch(self, startRight=False, connect=None):
+        """
+        Create a Switch
+        :param startRight: (bool) if this is True the initial setting is set to right (default is left)
+        :param connect: (callable) connect the switches clicked event to this callable
+        :return: -
+        """
+        sw = switchLabel(startRight)
+        if connect:
+            sw.clicked.connect(connect)
+        return sw
+
+    def closeEvent(self, event):
+        super(KCGWidgets, self).closeEvent(event)
+        self.closeSignal.emit()
+
+
+class KCGSubWidget(QtGui.QMdiSubWindow):
+    """
+    Base Class for Subwindows in the KCG Gui
+    """
+    _id = None
+    _name = None
+    _type = None
+
+    def __init__(self, name=None, unique_id=None, typ=None, minSize=False):
+        """
+        Initialise a Subwindow
+        :param name: (str) name of this window
+        :param unique_id: (int) unique id of this window
+        :param typ: (int) type of this window
+        :param minSize: (bool) whether the window is to be resized to its minimum size
+        :return: -
+        """
+        super(KCGSubWidget, self).__init__()
+        if name != None:
+            self.theName = name
+        if unique_id != None:
+            self.theId = unique_id
+        if typ != None:
+            self.theType = typ
+
+
+    @property
+    def theType(self):
+        return self._type
+
+    @property
+    def theId(self):
+        return self._id
+
+    @property
+    def theName(self):
+        return self._name
+
+    @theType.setter
+    def theType(self, t):
+        self._type = t # TODO: ??
+
+    @theId.setter
+    def theId(self, i):
+        self._id = i # TODO: ??
+
+    @theName.setter
+    def theName(self, n):
+        self._name = n # TODO: ??
+
+class AccordionClickLine(QtGui.QWidget):
+    clicked = QtCore.pyqtSignal()
+    def __init__(self, text):
+        super(AccordionClickLine, self).__init__()
+        self.layout = QtGui.QHBoxLayout()
+        self.setLayout(self.layout)
+        self.layout.setSpacing(0)
+        self.setContentsMargins(0, -5, 0, -5)
+        self.layout.addWidget(QtGui.QLabel(text))
+        self.layout.addStretch(1)
+        self.down_chev = QtSvg.QSvgWidget(config.install_path+"icons/chevron-bottom.svg")
+        self.down_chev.setFixedSize(10, 10)
+        self.left_chev = QtSvg.QSvgWidget(config.install_path+"icons/chevron-left.svg")
+        self.left_chev.setFixedSize(10, 10)
+        self.down_chev.hide()
+        self.layout.addWidget(self.left_chev)
+
+        self.expanded = True
+
+    def mouseReleaseEvent(self, QMouseEvent):
+        self.clicked.emit()
+        super(AccordionClickLine, self).mouseReleaseEvent(QMouseEvent)
+
+    def expand(self, state):
+        if state:  # if you want to expand
+            if not self.expanded:  # and it is not already expanded
+                self.layout.removeWidget(self.down_chev)
+                self.down_chev.hide()
+                self.left_chev.show()
+                self.layout.addWidget(self.left_chev)
+                self.expanded = True
+
+        else:  # if you want to unexpand
+            if self.expanded:  # and it is epanded
+                self.layout.removeWidget(self.left_chev)
+                self.left_chev.hide()
+                self.down_chev.show()
+                self.layout.addWidget(self.down_chev)
+                self.expanded = False
+
+
+class AccordionWidget(QtGui.QWidget):  # TODO: accordion or accordeon?
+    """
+    Simple accordion widget similar to QToolBox
+    """
+    def __init__(self):
+        """
+        Initialise the accordion widget
+        """
+        super(AccordionWidget, self).__init__()
+        self.layout = QtGui.QVBoxLayout()
+        self.layout.setSpacing(0)
+        self.setLayout(self.layout)
+        self.widgets = []
+        self.headers = []
+
+    def resizeEvent(self, QResizeEvent):
+        self.setMaximumHeight(self.layout.sizeHint().height())
+        super(AccordionWidget, self).resizeEvent(QResizeEvent)
+
+    def addItem(self, widget, text):
+        """
+        Add a Widget to the Accordion widget
+        :param widget: the widget to add
+        :param text: the text for this widget
+        """
+        hline2 = QtGui.QFrame()
+        hline2.setFrameShape(QtGui.QFrame.HLine)
+        hline2.setFrameShadow(QtGui.QFrame.Sunken)
+        cidx = len(self.widgets)
+        header = AccordionClickLine(text)
+        header.clicked.connect(lambda: self.toggleIndex(cidx))
+        self.headers.append(header)
+        self.layout.addWidget(hline2)
+        self.layout.addWidget(header)
+        self.layout.addWidget(widget)
+        self.widgets.append(widget)
+
+    def toggleIndex(self, index):
+        """
+        Toggle the visibility of the widget with index index
+        :param index: the index to toggle
+        :return:
+        """
+        if self.widgets[index].isHidden():
+            self.expandIndex(index)
+        else:
+            self.hideIndex(index)
+    def expandIndex(self, index):
+        """
+        Expand the widget with index index
+        :param index: the index of the widget to show
+        """
+        self.widgets[index].show()
+        self.headers[index].expand(True)
+        self.resize(1, 1)  # this will trigger a resizeEvent and thus will set the correct size
+    def hideIndex(self, index):
+        """
+        Hide the widget with the index index
+        :param index: the index of the widget to hide
+        """
+        self.widgets[index].hide()
+        self.headers[index].expand(False)
+        self.resize(1, 1)  # this will trigger a resizeEvent and thus will set the correct size
+    def hideAll(self):
+        """
+        Hide all widgets
+        """
+        for wid, header in zip(self.widgets, self.headers):
+            wid.hide()
+            header.expand(False)
+
+    def showAll(self):
+        """
+        Show all widgets
+        """
+        for wid, header in zip(self.widgets, self.headers):
+            wid.show()
+            header.expand(True)
+
+
+class MultilineInputDialog(QtGui.QDialog):
+    """
+    Multiline Input Dialog
+    When using this dialog, create is and open it with get_text. this also returns the entered text.
+    """
+    def __init__(self):
+        super(MultilineInputDialog, self).__init__()
+        self.answer = False
+
+    def fill(self, heading, label):
+        """
+        Fill the widget with elements
+        :param heading: (str) the heading of this widget
+        :param label: (str) the text to show above the input field
+        :return: -
+        """
+        self.setWindowTitle(heading)
+        self.layout = QtGui.QVBoxLayout()
+        self.setLayout(self.layout)
+
+        self.tl = QtGui.QLabel(label)
+        self.layout.addWidget(self.tl)
+        self.ti = QtGui.QPlainTextEdit()
+        self.layout.addWidget(self.ti)
+
+        # Advanced method to activate function call on Ctrl+Return (in this case the ok function aka press ok button)
+        # def ctrlEnter(event):
+        #     if event.key() == QtCore.Qt.Key_Return:
+        #         if event.modifiers() == QtCore.Qt.ControlModifier:
+        #             self.ok()
+        #     QtGui.QPlainTextEdit.keyPressEvent(self.ti, event)
+        # self.ti.keyPressEvent = ctrlEnter
+
+        self.blayout = QtGui.QHBoxLayout()
+        self.layout.addLayout(self.blayout)
+        self.okButton = QtGui.QPushButton(tr("Button", "OK"))
+        self.okButton.pressed.connect(self.ok)
+        self.okButton.setShortcut("Ctrl+Return")
+        self.cancelButton = QtGui.QPushButton(tr("Button", "Cancel"))
+        self.cancelButton.pressed.connect(self.cancel)
+        self.cancelButton.setShortcut("Esc")
+        self.blayout.addWidget(self.okButton)
+        self.blayout.addWidget(self.cancelButton)
+
+    def ok(self):
+        """
+        This gets executed when the ok button is pressed
+        """
+        self.answer = True
+        self.close()
+
+    def cancel(self):
+        """
+        This gets executed when the cancel button is pressed
+        """
+        self.answer = False
+        self.close()
+
+    def get_text(self, heading, label):
+        """
+        This function is the main entry point.
+        :param heading: (str) the heading of the widget
+        :param label: (str) the text to show above the input field
+        :return: (unicode) the entered text
+        """
+        self.fill(heading, label)
+        self.exec_()
+        return unicode(self.ti.toPlainText()), self.answer
+
+
+class IdGenerator():
+    """
+    Generate Unique Id for every subwindow
+    """
+    highest_id = 0
+    def genid(self):
+        self.highest_id += 1
+        return self.highest_id
+
+idg = IdGenerator()  # declare idgenerator instance to use globally
+
+
+_registered_possible_widgets = []
+_registered_possible_widget_functions = []
+
+
+def register_widget_creation_function(creation_func):
+    """
+    Register the function to create a certain widget
+    :param creation_func: (callable) function to call when the widget is to be created
+    :return: -
+    """
+    _registered_possible_widget_functions.append(creation_func)
+
+
+def register_widget(icon, text, target, shortcut=None):
+    """
+    Register a widget
+    :param icon: (QIcon) Icon to show on the toolbar button
+    :param text: (str) tooltip for the toolbar button
+    :param target: (callable) the function to create the widget
+    :param shortcut: (str) keyboard shortcut to open this widget
+    :return: -
+    """
+    _registered_possible_widgets.append([icon, text, target, shortcut])
+
+
+def get_registered_widgets():
+    """
+    Get the registered widgets
+    :return: (list) list of registered widgets
+    """
+    return _registered_possible_widgets
+
+
+def get_registered_widget_functions():
+    """
+    Get the functions that are registered
+    :return: (list) list of functions
+    """
+    return _registered_possible_widget_functions
+
+
+def error(code, text, severe=False):
+    """
+    Log an error using the logging module
+    :param code: the error code
+    :param text: the text to show
+    :param severe: if it is a severe error that has to quit the program
+    :return:
+    """
+    # TODO: implement error logging to file
+    if isinstance(code, str) and code[0:2] == '0x':
+        cde = code
+    elif isinstance(code, str):
+        cde = '0x'+code
+    else:
+        cde = '0x'+format(code, '03x')
+    logging.critical('{code}: {text}'.format(code=cde, text=text))
+    if severe:
+        sys.exit(int(cde, 16))

+ 681 - 0
KCG/base/leftbar.py

@@ -0,0 +1,681 @@
+# -*- coding: utf-8 -*-
+from PyQt4 import QtGui, QtCore
+
+import kcgwidget as kcgw
+from callbacks import callbacks
+from plotWidget import PlotWidget
+from backend.board import available_boards
+from backend import io
+import storage
+from ..widgets import acquiresettings as acqs
+from groupedelements import Elements
+import backendinterface as bif
+import log
+
+
+tr = kcgw.tr
+
+from .. import config
+
+LIVE = 1
+FILE = 2
+
+
+class BoardSpecificInformation(kcgw.KCGWidgets):
+    def __init__(self, board_id):
+        """
+        This creates the information part for board with id board_id.
+        This is used to be able to create a variable number of board information areas.
+        :param board_id: the id of the board
+        """
+        super(BoardSpecificInformation, self).__init__()
+        self.temperature = self.createLabel(tr("Label", "Temp:"))
+
+        def update_temp():
+            if bif.bk_get_board_status(board_id, 'board_connected'):
+                self.temperature.setText("Temp: " + bif.bk_get_temperature(board_id) + u" °C")
+        try:
+            update_temp()
+        except Exception:  # TODO: Find correct exception to catch
+            print "cannot get temperature"
+
+        self.timer = QtCore.QTimer()
+        self.timer.timeout.connect(update_temp)
+        self.timer.start(30000)
+
+        self.skipturns = self.createLabel(
+            tr("Label", "O_skip:") + " " + str(bif.bk_get_config(board_id, 'orbits_skip')),
+            tooltip=tr("Tooltip", "Skipped orbits\nKlick to open acquire settings"),
+            click=True,
+            connect=lambda: acqs.add_acquire_settings_widget(board_id=board_id)
+        )
+        bif.bk_get_board_config(board_id).observe(self.skipturns,
+                                                 lambda x: self.skipturns.setText(tr("Label", "O_skip:")+" "+str(x)),
+                                                 'orbits_skip')
+        self.orbitsobserved = self.createLabel(
+            tr("Label", "O_obs:") + " " + str(bif.bk_get_config(board_id, 'orbits_observe')),
+            tooltip=tr("Tooltip", "Number of observed Orbits\nKlick to open acquire settings"),
+            click=True,
+            connect=lambda: acqs.add_acquire_settings_widget(board_id=board_id)
+        )
+        bif.bk_get_board_config(board_id).observe(
+                self.orbitsobserved,
+                lambda x: self.orbitsobserved.setText(tr("Label", "O_obs:") + " " + str(x)), 'orbits_observe')
+        self.orbitscount = self.createLabel(
+            tr("Label", "Count:") + " " + str(bif.bk_get_config(board_id, 'acquisition_count')),
+            tooltip=tr("Tooltip", "Number of acquisitions\nKlick to open acquire settings"),
+            click=True,
+            connect=lambda: acqs.add_acquire_settings_widget(board_id=board_id)
+        )
+        bif.bk_get_board_config(board_id).observe(
+                self.orbitscount,
+                lambda x: self.orbitscount.setText(tr("Label", "Count:") + " " + str(x)), 'acquisition_count')
+        self.orbitswait = self.createLabel(
+            tr("Label", "T_wait:") + " " + str(bif.bk_get_config(board_id, 'orbits_wait_time')),
+            tooltip=tr("Tooltip", "Time in seconds to wait between acquisitions\nKlick to open acquire settings"),
+            click=True,
+            connect=lambda: acqs.add_acquire_settings_widget(board_id=board_id)
+        )
+        bif.bk_get_board_config(board_id).observe(
+                self.orbitswait,
+                lambda x: self.orbitswait.setText(tr("Label", "T_wait:") + " " + str(x)), 'orbits_wait_time')
+
+        self.boardSpecificLayout = QtGui.QVBoxLayout()
+        self.setLayout(self.boardSpecificLayout)
+        self.layout = QtGui.QGridLayout()  # TODO: add text for build_spectrograms ? was???
+        self.layout.addWidget(self.temperature, 0, 0)
+        self.layout.addWidget(self.skipturns, 1, 1)
+        self.layout.addWidget(self.orbitsobserved, 1, 0)
+        self.layout.addWidget(self.orbitscount, 2, 0)
+        self.layout.addWidget(self.orbitswait, 2, 1)
+        self.boardSpecificLayout.addLayout(self.layout)
+
+
+class AcquisitionAndInfo(kcgw.KCGWidgets):
+    """
+    Widget to show below the plot list.
+    Show certain information.
+    Provide Start Acquisition button and settings.
+    """
+    # consistency_updated = QtCore.pyqtSignal(bool)
+    # acquisition_started_signal = QtCore.pyqtSignal(int)
+    def __init__(self):
+        super(AcquisitionAndInfo, self).__init__()
+        self.layout = QtGui.QVBoxLayout()
+        self.layout.setContentsMargins(0, 0, 0, 0)
+        self.setLayout(self.layout)
+
+
+        if available_boards.multi_board:
+            self.scroll_area = QtGui.QScrollArea()
+            self.layout.addWidget(self.scroll_area)
+            self.information_box = kcgw.AccordionWidget()
+            for brd in available_boards.board_ids:
+                self.information_box.addItem(BoardSpecificInformation(brd),
+                                             available_boards.get_board_name_from_id(brd))
+            self.scroll_area.setWidget(self.information_box)
+            self.scroll_area.setWidgetResizable(True)
+            self.information_box.hideAll()
+            self.information_box.expandIndex(0)
+        else:
+            self.layout.addWidget(BoardSpecificInformation(available_boards.board_ids[0]))
+
+        # -----[ Constistency ]-----------
+        self.consistency = QtGui.QHBoxLayout()
+        self.layout.addLayout(self.consistency)
+        self.consistency_label = self.createLabel("Consistency:")
+        self.consistency_content = self.createLabel(u"●")
+        self.consistency_content.setStyleSheet("color: lightgrey;")
+        self.consistency.addWidget(self.consistency_label)
+        self.consistency.addWidget(self.consistency_content)
+
+        # -------[ acquire ]--------------
+        self.acquire = self.createButton(tr("Button", "Start Acquisition"),
+                                         icon=QtGui.QIcon(config.install_path + config.startIcon),
+                                         connect=self.toggle_acquisition)
+        if not available_boards.multi_board:
+            Elements.addItem('acquireTrigger_{}'.format(available_boards.board_ids[0]), self.acquire)
+
+        if available_boards.multi_board:
+            self.list_to_acquire = {
+                i: self.createCheckbox(
+                        str(i),
+                        tooltip=str(tr("Tooltip",
+                                       "Check this checkbox if you want to acquire with board {board}")
+                                    ). format(board=i),
+                        connect=self.update_acq_tickboxes) for i in available_boards}
+            self.list_of_active_boards = {i: False for i in available_boards}
+            self.acq_list_layout = QtGui.QHBoxLayout()
+            for b_id, cb in self.list_to_acquire.iteritems():
+                self.acq_list_layout.addWidget(cb)
+                Elements.addItem('acquireTrigger_{}'.format(b_id), cb)
+                self.acquisition_stopped(b_id)
+
+        # -----[ log ]-------
+        self.log = self.createButton(tr("Button", "Log"),
+                                     icon=QtGui.QIcon(config.install_path + config.logIcon),
+                                     connect=lambda: log.log(additional="Manual Log"),
+                                     tooltip="Rightclick to Configure")
+        self.log_w_comment = self.createButton(tr("Button", "Log"),
+                                               icon=QtGui.QIcon(config.install_path + config.logCommentIcon),
+                                               connect=self.do_log_w_comment,
+                                               tooltip="Log with Comment\nRightclick to Configure")
+        self.log.contextMenuEvent = self.log_configure_context_menu
+        self.log_w_comment.contextMenuEvent = self.log_configure_context_menu
+
+        # self.layout.addLayout(self.consistency, 0, 0)  # TODO: für welches board? zum board???
+        if available_boards.multi_board:
+            self.layout.addLayout(self.acq_list_layout)
+        self.layout.addWidget(self.acquire)
+        self.logLayout = QtGui.QHBoxLayout()
+        self.logLayout.addWidget(self.log)
+        self.logLayout.addWidget(self.log_w_comment)
+        self.layout.addLayout(self.logLayout)
+
+        callbacks.add_callback('update_consistency', self.update_consistency)
+        callbacks.add_callback('acquisition_started', self.acquisition_started)
+        callbacks.add_callback('acquisition_stopped', self.acquisition_stopped)
+
+    def toggle_acquisition(self):
+        """
+        Turn acquisition on/off. This gets the information about which board to use by checking state of the checkboxes.
+        :return:
+        """
+        if not available_boards.multi_board:
+            bif.bk_acquire(available_boards.board_ids[0])
+            return
+        for b_id, cb in self.list_to_acquire.iteritems():
+            if cb.isChecked():
+                bif.bk_acquire(b_id)
+        for cb in self.list_to_acquire.values():
+            cb.setChecked(False)
+        self.update_acq_tickboxes()
+
+    def update_acq_tickboxes(self):
+        """
+        If at least one checkbox is checked, set all checkboxes with other state (acquiring or not)
+        to disabled. This prevents the user to start with one board and stop with the other simultaneously.
+        TODO: add option in settings to allow the now eliminated behaviour
+        """
+        active = False
+        inactive = False
+        for b_id, cb in self.list_to_acquire.iteritems():
+            if cb.isChecked():  # search for first checked box
+                if self.list_of_active_boards[b_id] is True:  # if board is acquiring
+                    active = True
+                    break
+                else:
+                    inactive = True
+                    break
+        if active:  # if checked box is for acquiring boards (meaning to stop them)
+            for b_id, state in self.list_of_active_boards.iteritems():
+                if Elements.isEnabled('acquireTrigger_{}'.format(b_id)):
+                    self.list_to_acquire[b_id].setEnabled(state)  # set all active boards to enabled
+            self.acquire.setIcon(QtGui.QIcon(config.install_path + config.stopIcon))
+            self.acquire.setText(tr("Button", "Stop Acquisition"))
+        elif inactive:
+            for b_id, state in self.list_of_active_boards.iteritems():
+                if Elements.isEnabled('acquireTrigger_{}'.format(b_id)) or bif.bk_get_board_status(b_id, 'acquisition'):
+                    self.list_to_acquire[b_id].setEnabled(not state)  # set all active boards to not enabled
+            self.acquire.setIcon(QtGui.QIcon(config.install_path + config.startIcon))
+            self.acquire.setText(tr("Button", "Start Acquisition"))
+        else:  # if no board is selected
+            for b_id, cb in self.list_to_acquire.items():
+                if Elements.isEnabled('acquireTrigger_{}'.format(b_id)):
+                    cb.setEnabled(True)
+            self.acquire.setIcon(QtGui.QIcon(config.install_path + config.startIcon))
+            self.acquire.setText(tr("Button", "Start Acquisition"))
+
+    def acquisition_stopped(self, board_id):
+        """
+        This is called when a acquisition is stopped.
+        (has to be registered in Callbacks under 'acquisition_stopped'
+        :param board_id: id of the board
+        """
+        if not available_boards.multi_board:
+            for elem in Elements.getElements("acquireTrigger_{}".format(board_id)):
+                if isinstance(elem, QtGui.QShortcut) or isinstance(elem, QtGui.QCheckBox):
+                    continue
+                elem.setIcon(QtGui.QIcon(config.install_path + config.startIcon))
+                elem.setText(tr("Button", "Start Acquisition"))
+            return
+        self.list_to_acquire[board_id].setStyleSheet('')
+        self.list_of_active_boards[board_id] = False
+        self.update_acq_tickboxes()
+
+    def acquisition_started(self, board_id):
+        """
+        This is called when a acquisition is started.
+        (has to be registered in Callbacks under 'acquisition_started'
+        :param board_id: id of the board
+        """
+        if not available_boards.multi_board:
+            for elem in Elements.getElements("acquireTrigger_{}".format(board_id)):
+                if isinstance(elem, QtGui.QShortcut) or isinstance(elem, QtGui.QCheckBox):
+                    continue
+                elem.setIcon(QtGui.QIcon(config.install_path + config.stopIcon))
+                elem.setText(tr("Button", "Stop Acquisition"))
+            return
+        self.list_to_acquire[board_id].setStyleSheet("background-color: lightgreen;")
+        self.list_of_active_boards[board_id] = True
+        self.update_acq_tickboxes()
+
+    def do_log_w_comment(self):
+        """
+        Function to handle logging with comments
+        """
+        # text, ok = QtGui.QInputDialog.getText(self, tr("Heading", 'Log Comment'), tr("Label", 'Log Comment:'))
+        text, ok = kcgw.MultilineInputDialog().get_text(tr("Heading", "Log Comment"), tr("Label", "Log Comment:"))
+        if ok:
+            log.log(additional=text)
+
+    def log_configure_context_menu(self, event):
+        """
+        Function that creates the context menu for the log buttons
+        :param event: (QEvent) the event
+        :return: -
+        """
+        pos = event.globalPos()
+
+        def configure_log():
+            c = log.ConfigureLog(self)
+            c.exec_()
+            del c
+        if pos is not None:
+            menu = QtGui.QMenu(self)
+            configure = menu.addAction(tr("Button", "Configure Log"))
+            configure.triggered.connect(configure_log)
+            menu.popup(pos)
+        event.accept()
+
+    def update_consistency(self, status):
+        """
+        Set consistency indicator
+        :param status: True if consistent, False if inconsistent, None is nothing
+        :return: -
+        """
+        if status is True:
+            self.consistency_content.setStyleSheet("color: green;")
+        elif status is False:
+            self.consistency_content.setStyleSheet("color: red;")
+        else:
+            self.consistency_content.setStyleSheet("color: lightgrey;")
+
+
+class LeftBar(kcgw.KCGWidgets):
+    """
+    Left bar in the main view of the gui.
+    Shows plot list and acquisition and info panel
+    """
+    # general notes:
+    # uid = unique_id
+    # usid = unique_sub_id
+    # upid = unique_parent_id
+    possiblePlots = ["Heatmap", "FFT", "Trains", "Combined", "Compare"]
+
+    def __init__(self, parent):
+        super(LeftBar, self).__init__("LeftBar")
+        self.parent = parent
+
+        self.openData = {}
+        self.openDataNames = {}
+        self.openPlots = {}
+        self.openPlotWindows = {}
+        self.Data = {}
+
+        self.initUI()
+
+    def initUI(self):
+        self.layout = QtGui.QVBoxLayout()
+        self.layout.setContentsMargins(0, 0, 5, 0)
+        self.setMinimumWidth(230)
+
+        self.treeWidget = QtGui.QTreeWidget()
+        self.treeWidget.setObjectName("tree_view")
+        self.treeWidget.setColumnCount(2)
+        self.treeWidget.resizeColumnToContents(1)
+#        self.treeWidget.header().resizeSection(0, 110)
+        self.treeWidget.setHeaderHidden(True)
+        self.treeWidget.itemClicked.connect(self.plot_clicked)
+        self.treeWidget.contextMenuEvent = self.contextMenuEventListView
+        self.rootItem = self.treeWidget.invisibleRootItem()
+
+        self.infoAndAcquisitionWidget = AcquisitionAndInfo()
+
+        self.layout.addWidget(self.treeWidget)
+        self.layout.addWidget(self.infoAndAcquisitionWidget)
+
+        self.setLayout(self.layout)
+
+    def _generate_menu(self, menu, item):
+        heatmap = menu.addAction(tr("Button", "Heatmap"))
+        fft = menu.addAction(tr("Button", "FFT"))
+        trains = menu.addAction(tr("Button", "Trains"))
+        combined = menu.addAction(tr("Button", "Combined"))
+        compare = menu.addAction(tr("Button", "Compare"))
+
+        heatmap.triggered.connect(lambda: self.add_plot_node(
+                text=tr("Label", 'Heatmap'), unique_id=item.uid, d_type=0, datatype=item.datatype))
+        fft.triggered.connect(lambda: self.add_plot_node(
+                text=tr("Label", 'FFT'), unique_id=item.uid, d_type=1, datatype=item.datatype))
+        trains.triggered.connect(lambda: self.add_plot_node(
+                text=tr("Label", 'Trains'), unique_id=item.uid, d_type=2, datatype=item.datatype))
+        combined.triggered.connect(lambda: self.add_plot_node(
+                text=tr("Label", 'Combined'), unique_id=item.uid, d_type=3, datatype=item.datatype))
+        compare.triggered.connect(lambda: self.add_plot_node(
+                text=tr("Label", 'Combined'), unique_id=item.uid, d_type=4, datatype=item.datatype))
+
+    def contextMenuEventListView(self, event):
+        """
+        Gets called when right mouse button is clicked
+        :param event: the event that causes the call of this function
+        :return: -
+        """
+        pos = event.globalPos()
+        try:
+            item = self.treeWidget.selectedItems()[0]
+        except IndexError:
+            return
+        if pos is not None:
+            menu = QtGui.QMenu(self)
+            close = menu.addAction(tr("Button", "Close"))
+            if item.is_child:
+                close.triggered.connect(lambda: self.remove_plot(item.usid, silent=False, close_plot_window=True))
+            else:
+                close.triggered.connect(lambda: self.remove_plot_tree(item.uid))
+                self._generate_menu(menu, item)
+
+            menu.popup(pos)
+        event.accept()
+
+    def plot_clicked(self, item, i):
+        """
+        Function to handle when a plot item is clicked.
+        :param item: what item is clicked
+        :param i: what column
+        :return: -
+        """
+        if item.is_child:
+            self.openPlotWindows[item.usid].setFocus()
+        else:
+            if not item.ready:
+                return
+            if i == 1:
+                position = QtGui.QCursor.pos()
+                menu = QtGui.QMenu(self)
+
+                self._generate_menu(menu, item)
+
+                menu.popup(position)
+
+    def add_plot_tree(self, text=False, unique_id=None, datatype=None):
+        """
+        Add a top node to the plot list.
+        :param text: (str) text to be displayed in the plot list
+        :param unique_id: (int) unique id of this top node
+        :param datatype: (int) live or data from file
+        :return: -
+        """
+        if datatype == FILE:
+            file_name = QtGui.QFileDialog.getOpenFileName()
+            if file_name == "":
+                return
+            if file_name in self.openDataNames.itervalues():  # is this to prevent double opening?
+                return
+            self.openDataNames[unique_id] = file_name
+        else:
+            if text in self.openDataNames.itervalues():
+                return
+            self.openDataNames[unique_id] = QtCore.QString(text)  # Add a qstring for comparison
+        self.openData[unique_id] = QtGui.QTreeWidgetItem()
+        self.openData[unique_id].uid = unique_id
+        self.openData[unique_id].is_child = False
+        self.openData[unique_id].datatype = datatype
+        if datatype == FILE:
+            self.openData[unique_id].file_name = file_name
+        self.openData[unique_id].setText(0, "Live" if datatype == LIVE else file_name.split('/')[-1])
+        self.openData[unique_id].setText(1, "Reading")
+        if datatype == LIVE:  # Insert LIVE on top
+            self.treeWidget.insertTopLevelItem(0, self.openData[unique_id])
+        else:
+            self.treeWidget.addTopLevelItem(self.openData[unique_id])
+        self.treeWidget.expandItem(self.openData[unique_id])
+        self.treeWidget.resizeColumnToContents(0)
+        self.openData[unique_id].ready = False  # indicates whether data was completely read or not
+        QtGui.qApp.processEvents()
+        try:
+            self.read_data(datatype, unique_id, file_name if datatype == FILE else None)
+        except Exception:
+            self.Data[unique_id] = None
+            self.remove_plot_tree(unique_id, silent=True)
+            QtGui.QMessageBox.critical(
+                    self,
+                    tr("Heading", "Error Reading Data"),
+                    tr("Dialog", "There was an error reading the datafile, make shure it is valid and try again."))
+        self.openData[unique_id].ready = True  # indicates whether data was completely read or not
+        self.openData[unique_id].setText(1, "+")
+        self.add_plot_node(d_type=0, unique_id=unique_id, datatype=datatype)
+
+    def read_data(self, d_type, uid, file_name):
+        """
+        Reads datafile
+        :param d_type: FILE or LIVE - type of datasource
+        :param uid: unique id for the treeelement
+        :param file_name: filename if type is FILE
+        :return: -
+        """
+        if d_type == FILE:
+            self.Data[uid] = io.read_from_file(file_name, header=storage.storage.header)
+        else:
+            self.Data[uid] = bif.livePlotData
+
+    def add_plot_node(self, board_id=None, text=False, unique_id=None, d_type=None, datatype=None):
+        """
+        Actually open a new plot window.
+        :param board_id: the id of the board to which the plot corresponds
+        :param text: (str) text that is displayed in the plot list
+        :param unique_id: (int) unique id of this plot window
+        :param d_type: (int) type of this plot window
+        :param datatype: (int) live or data from file
+        :return: -
+        """
+        if datatype == LIVE and board_id is None:  # get board_id for live plot from popup menu
+            # board_id = 0
+            if available_boards.multi_board:
+                # raise NotImplementedError("Dialog to ask for board is not implemented yet.")
+                position = QtGui.QCursor.pos()
+                menu = QtGui.QMenu(self)
+                for bid in available_boards:
+                    tmp = menu.addAction(available_boards.get_board_name_from_id(bid))
+                    tmp.triggered.connect(lambda _, b=bid: self.add_plot_node(board_id=b,
+                                                                           text=text,
+                                                                           unique_id=unique_id,
+                                                                           d_type=d_type,
+                                                                           datatype=datatype))
+                    # NOTE to the above: the lambda needs b=bid to capture the variable for the lambda
+                    # additionally the _ is needed because the call to the lambda includes an optional parameter
+                    # that is true or false, passed by pyqt
+                menu.popup(position)
+                return
+
+            else:
+                board_id = available_boards.board_ids[0]
+        # if not board_id:
+        #     TODO: Activate this (No Board Id Given)
+            # pass
+           # raise NoBoardId("No Board Id was given.")
+
+        unique_sub_id = kcgw.idg.genid()
+        nr = self.openData[unique_id].childCount()
+        if datatype == FILE:
+            file_name = self.openData[unique_id].file_name
+        else:
+            file_name = None
+        name = "Live" if not file_name else file_name.split('/')[-1]
+
+        self.openPlotWindows[unique_sub_id] = PlotWidget(
+                board_id=board_id,
+                parent=self,
+                name=name,
+                unique_id=unique_sub_id,
+                type=d_type,
+                datatype=datatype,
+                prefix=nr,
+                fName=file_name,
+                data=self.Data[unique_id]
+        )
+        self.openPlotWindows[unique_sub_id].change_type_signal.connect(self.update_plot)
+        self.openPlotWindows[unique_sub_id].upid = unique_id
+        self.parent.area.newWidget(
+                self.openPlotWindows[unique_sub_id],
+                name=text,
+                unique_id=unique_sub_id,
+                widget_type=d_type
+        )
+        self.openPlots[unique_sub_id] = QtGui.QTreeWidgetItem()
+        if datatype == LIVE:
+            brd = " B: "+available_boards.get_board_name_from_id(board_id)
+        else:
+            brd = ""
+        self.openPlots[unique_sub_id].setText(0, str(nr) + "." + self.possiblePlots[d_type] + " - ADC 1"+brd)
+        if d_type == 4:  # Compare Plot
+            self.openPlots[unique_sub_id].setText(0, str(nr) + "." + self.possiblePlots[d_type] + " - ADC 1+2"+brd)
+        self.openPlots[unique_sub_id].is_child = True
+        self.openPlots[unique_sub_id].usid = unique_sub_id
+        self.openPlots[unique_sub_id].nr = nr
+        self.openData[unique_id].addChild(self.openPlots[unique_sub_id])
+        self.treeWidget.resizeColumnToContents(0)
+
+    @QtCore.pyqtSlot()
+    def remove_plot(self, unique_id, silent=False, close_plot_window=False):
+        """
+        Removes a plot from the plot list.
+        :param unique_id: (int) the unique id of the plot to remove
+        :param silent: (bool) Ask to close the plot source node in the list if the last plot window is closed
+                        (if False the source node will not be closed)
+        :param close_plot_window: (bool) close the corresponding plot window
+        :return: -
+        """
+        parent = self.openData[self.openPlotWindows[unique_id].upid]
+        if not silent:
+            reply = QtGui.QMessageBox()
+            reply.setIcon(QtGui.QMessageBox.Question)
+            reply.setWindowTitle(tr("Heading", 'Close Plot'))
+            reply.setText(tr("Dialog", "Close this plot?"))
+            reply.addButton(QtGui.QMessageBox.Yes)
+            reply.addButton(QtGui.QMessageBox.No)
+            reply.setDefaultButton(QtGui.QMessageBox.No)
+
+            if parent.childCount() == 1:
+                reply.addButton("Close Plot Source", QtGui.QMessageBox.ResetRole)
+
+            reply.exec_()
+
+            if reply.buttonRole(reply.clickedButton()) == QtGui.QMessageBox.ResetRole:
+                self.remove_plot_tree(parent.uid, silent=True)
+                return True
+            elif reply.buttonRole(reply.clickedButton()) == QtGui.QMessageBox.NoRole:
+                return False
+        if close_plot_window:
+            self.openPlotWindows[unique_id].close_silent = True
+            self.openPlotWindows[unique_id].close()
+        parent.removeChild(self.openPlots[unique_id])
+        self.openPlotWindows[unique_id] = None
+        self.openPlots[unique_id] = None
+        del self.openPlotWindows[unique_id]
+        del self.openPlots[unique_id]
+        return True
+
+    def remove_plot_tree(self, unique_id, silent=False):
+        """
+        Remove the top node from the plot list
+        :param unique_id: (int) the id of the top node
+        :param silent: (bool) ask to close even if corresponding plot windows are open
+        :return: -
+        """
+        if self.openData[unique_id].childCount() != 0 and not silent:
+            reply = QtGui.QMessageBox.question(
+                    self, tr("Heading", 'Close Data Source'), tr(
+                            "Dialog", "There are open plot windows.\n"
+                            "Close this plot source and all corresponding plot windows?"),
+                    QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+                    QtGui.QMessageBox.No)
+            if reply == QtGui.QMessageBox.No:
+                return
+        for plot in self.openData[unique_id].takeChildren():
+            self.openPlotWindows[plot.usid].close_silent = True
+            self.openPlotWindows[plot.usid].close()
+            self.openData[unique_id].removeChild(plot)
+        self.rootItem.removeChild(self.openData[unique_id])
+        del self.openData[unique_id]
+        del self.openDataNames[unique_id]
+        del self.Data[unique_id]
+
+    @QtCore.pyqtSlot(int, int, str)
+    def update_plot(self, uid, b_type, adc):
+        """
+        Updates the plot list when the type of a plot window changes
+        :param uid: (int) unique id of the plot window to update
+        :param b_type: (int) type of that plot window
+        :param int adc: the adc to update
+        :return: -
+        """
+        self.openPlots[uid].setText(
+                0, str(self.openPlots[uid].nr) + "." + self.possiblePlots[b_type] + " - ADC " + str(adc)
+        )
+        self.treeWidget.resizeColumnToContents(0)
+
+    def handle_dialog(self, value, diag=None,):
+        """
+        Function that handles the return of the dialog that asks for live or data from file
+        :param value: (int) the value of the pressed button in the dialog
+        :param diag: (QDialog) the dialog that is to be closed (optional)
+        :return: -
+        """
+        if diag:
+            diag.close()
+        s = kcgw.idg.genid()
+        if value == 1:
+            if tr("Label", "Live") in self.openDataNames.itervalues():
+                x = [bid for bid, obj in self.openData.iteritems() if obj.datatype == LIVE][0]
+                self.add_plot_node(unique_id=x, d_type=0, datatype=LIVE)
+            else:
+                self.add_plot_tree(text=tr("Label", "Live"), unique_id=s, datatype=LIVE)
+        elif value == 2:
+            self.add_plot_tree(text=tr("Label", "File"), unique_id=s, datatype=FILE)
+        else:
+            pass
+
+    def add_plot(self, d_type=None):
+        """
+        Handles the creation of a plot.
+        Also shows the dialog that asks for the data source.
+        :param d_type: the type of the plot to add
+        :return: -
+        """
+        if d_type == LIVE or d_type == FILE:
+            self.handle_dialog(value=d_type)
+            return
+        ask = QtGui.QDialog()
+        window = self.parent.geometry()
+        ask_layout = QtGui.QVBoxLayout()
+        ask_layout.addWidget(QtGui.QLabel(tr("Dialog", "Open file from disk or read live data?")))
+
+        button_layout = QtGui.QHBoxLayout()
+        ask_button_live = QtGui.QPushButton(tr("Button", "Live"))
+        ask_button_file = QtGui.QPushButton(tr("Button", "Open File"))
+        ask_button_cancel = QtGui.QPushButton(tr("Button", "Cancel"))
+
+        ask_button_live.pressed.connect(lambda: self.handle_dialog(diag=ask, value=1))
+        ask_button_file.pressed.connect(lambda: self.handle_dialog(diag=ask, value=2))
+        ask_button_cancel.pressed.connect(lambda: self.handle_dialog(diag=ask, value=3))
+
+        button_layout.addWidget(ask_button_live)
+        button_layout.addWidget(ask_button_file)
+        button_layout.addWidget(ask_button_cancel)
+
+        ask_layout.addLayout(button_layout)
+        ask.setGeometry(window.width()/2.-100, window.height()/2.-50, 200, 100)
+
+        ask.setLayout(ask_layout)
+        ask.exec_()

+ 343 - 0
KCG/base/log.py

@@ -0,0 +1,343 @@
+"""
+This is a custom logfile creation module
+"""
+
+import datetime
+import os
+from PyQt4 import QtGui, QtCore
+# from backend import board
+from backend.board import available_boards
+import backendinterface as bif
+import numpy as np
+import codecs
+from .. import config
+import storage
+import logging
+
+epics_reachable = True
+try:  # try to import epics
+    import epics
+    no_epics = False
+
+    os.environ["EPICS_BASE"] = config.epics_base_path
+
+    try:  # if import was successful, try to find libca and test for connectivity with config.epics_test_pv
+        # sys.stderr = os.devnull  # epics.ca.find_libca() prints a useless None to stderr if libca is not found
+        epics.ca.find_libca()
+        # sys.stderr = sys.__stderr__
+        if epics.caget(config.epics_test_pv) == None:
+            no_epics = True
+            epics_reachable = False
+            logging.error("Epics is not accessible (possible Timeout)")
+
+    except epics.ca.ChannelAccessException as e:
+        if str(e) == "cannot find Epics CA DLL":
+            no_epics = True
+            logging.error("Epics CA DLL not found")
+
+
+except ImportError:
+    no_epics = True
+
+# after = datetime.datetime.now()
+# if (after-before).total_seconds() > 3.0:
+#     no_epics = True
+#     logging.error("Epics is not accessible (Timeout)")
+
+# tr = kcgw.tr
+tr = lambda _, x: x  # log entries will not be translated
+
+
+class LogLevels:  # Keep every new value an integer. Every level with higher value will include those with lower level
+    NONE = -1
+    INFO = 0
+    DEBUG = 1
+
+TWO_COLUMNS = False
+BOARDID = "BOARD_ID"
+
+
+class MeasurementLogger(object):
+    """
+    Logfile creator class
+    It will automatically get the info needed to create a logfile entry from registered functions.
+    """
+    def __init__(self, filename=None, level=LogLevels.INFO, oneline=False):
+        """
+        Initialise the logfile creator
+        :param filename: (str) filename of the logfile THIS IS IGNORED AS OF NOW
+        :param level: (int) valid log level (see LogLevels)
+        :param oneline: (bool) whether to format the logfile entries in multilines or one line
+        :return: -
+        """
+        self.level = level
+        self.filename = filename
+        self.parameter_functions = []
+        self.oneline = oneline
+        self.dumper = None
+
+        self.predefined_parameters = [  # format: [logstring, [function (args,)]]
+            ["Number of Orbits", [bif.bk_get_config, (BOARDID, 'orbits_observe')]],
+            ["Number of Skipped Orbits", [bif.bk_get_config, (BOARDID, 'orbits_skip')]],
+            ["Number of Acquisitions", [bif.bk_get_config, (BOARDID, 'acquisition_count')]],
+            ["Time between Acquisitions", [bif.bk_get_config, (BOARDID, 'orbits_wait_time')]],
+            ["Pilot Bunch Simulator", [bif.bk_get_config, (BOARDID, 'pilot_bunch')]],
+            ["Header saved", [bif.bk_get_config, (BOARDID, 'header')]],
+            ["T/H Delay", [bif.bk_get_config, (BOARDID, 'th_delay')]],
+            ["ADC 1 Delay", [bif.bk_get_config, (BOARDID, 'chip_1_delay')]],
+            ["ADC 2 Delay", [bif.bk_get_config, (BOARDID, 'chip_2_delay')]],
+            ["ADC 3 Delay", [bif.bk_get_config, (BOARDID, 'chip_3_delay')]],
+            ["ADC 4 Delay", [bif.bk_get_config, (BOARDID, 'chip_4_delay')]]
+        ]
+        self.gui_values_number = len(self.predefined_parameters)
+        if not no_epics:
+            for entr in config.epics_log_entry_pvs:
+                self.predefined_parameters.append([entr[0], [epics.caget, entr[1]]])
+
+    def __now(self):
+        """
+        Get the current time in %Y-%m-%d %H:%M:%S format
+        :return: the time
+        """
+        return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+
+    def _logging_formatter(self, info):
+        """
+        Formatter for the logfile entries
+        Override this to implement custom formatters
+        """
+        if self.oneline:
+            return self.__now() + " " + ", ".join(info)
+        return self.__now() + "\n\t" + "\n\t".join(info)
+
+    def _log(self, board_id, stuff):
+        """
+        Write entries to logfile
+        This will also make shure the directory the logfile is located is available and if not creates it
+        :param stuff: (str) the entrie to write
+        :return: -
+        """
+        # if not self.filename:
+        #     filename = storage.storage.save_location + '/' + storage.storage.subdirname + "/Measurement.log"
+        # else:
+        #     filename = self.filename
+        filename = storage.storage.save_location + '/' + \
+            storage.storage.subdirname + '/' + "/Measurement_board_" + \
+            available_boards.get_board_name_from_id(board_id).replace(' ', '_') + ".log"
+        if not os.path.isdir(str(storage.storage.save_location + '/' + storage.storage.subdirname)):
+            os.makedirs(str(storage.storage.save_location + '/' + storage.storage.subdirname))
+        f = codecs.open(filename, 'a', encoding='utf-8')
+        f.write(stuff+'\n\n')
+        f.close()
+
+    def register_parameter(self, logstring, func, args):
+        """
+        This function is used to register parameters for the logfile entries
+        :param logstring: (str) the string to describe the value in the logfile
+        :param func: (callable) the function used to get the value
+        :param args: arguments to pass to func upon gathering of information
+        :return: -
+        """
+        if not isinstance(args, tuple):
+            args = (args,)
+        self.parameter_functions.append([func, args, logstring])
+        # self.parameter_functions[logstring] = [func, args, logstring]
+
+    def reset_parameters(self):
+        """
+        Resets all parameters (unregisteres all parameters)
+        """
+        self.parameter_functions = []
+
+    def register_dumper(self, func):
+        """
+        Register a dumper that is used to dump a log of data into the logfile
+        :param func: (callable) Function that is used as dumper
+        :return: -
+        """
+        self.dumper = func
+
+    def dump(self, board_id):
+        """
+        Perform a dump (see register_dumper)
+        :return: -
+        """
+        self.do_log(board_id=board_id, c=self.__now() + " " + self.dumper())
+
+    def do_log(self, c=None, additional=None, board_id=None, level=LogLevels.INFO):
+        """
+        Perform a log. This creates a log entry in the logfile
+        :param c: (str) if this is not None this is the only text written to the logfile (in addition to the current time)
+        :param additional: (str) This text is written below the current time to customize log entries
+        :param board_id: the id of the board to log for, if this is not given, all boards are logged
+        :return: -
+        """
+        if level > self.level:
+            return
+        if c:
+            if board_id is not None:
+                self._log(board_id, c)
+            else:
+                for bid in available_boards:
+                    self._log(bid, c)
+        else:
+            def do_the_log(bid):
+                st = []
+                if additional:
+                    s = additional.split('\n')[0]
+                    for line in additional.split('\n')[1:]:
+                        s += "\n\t"+line
+                    st.append(s)
+                for par in self.parameter_functions:
+                    par[1] = list(par[1])
+                    for i, p in enumerate(par[1]):
+                        if p == BOARDID:
+                            par[1][i] = bid
+                    st.append(par[2] + ": " + str(par[0](*par[1])))
+                return st
+            if board_id is not None:
+                st = do_the_log(board_id)
+                self._log(board_id, self._logging_formatter(st))
+            else:
+                for bid in available_boards:
+                    st = do_the_log(bid)
+                    self._log(bid, self._logging_formatter(st))
+
+logger = None
+dump_general = False
+
+
+def log(c=None, additional=None, dump=False, board_id=None, level=LogLevels.INFO):
+    """
+    Execute logging if logger was registered
+    :param (str) c: if this is given c is the only entry that is created (additional is ignored)
+    :param (str) additional: the additional text that is written below the current time
+    :param (bool) dump: if this is true, a dump of every log value is performed
+    :param board_id: the board id to log for, if this is not given, all boards are logged
+    :return: -
+    """
+    global logger, dump_general
+    if logger:
+        if dump or dump_general:
+            logger.dump(board_id=board_id)
+        else:
+            logger.do_log(c, additional, board_id=board_id, level=level)
+    else:
+        print "No Logger registered"
+
+
+class ConfigureLog(QtGui.QDialog):
+    """
+    Class that is basically a QDialog to configure the logger
+    """
+    def __init__(self, parent=None):  # parameter parent does nothing when adding parent to the folowing __init__
+                                        # no entry in taskbar is made
+        super(ConfigureLog, self).__init__()
+
+        self.setWindowTitle("Configure Measurement Log Entries")
+        self.setWindowIcon(QtGui.QIcon(config.install_path + config.guiIcon))
+
+        self.sl = QtGui.QVBoxLayout()
+        self.setLayout(self.sl)
+        self.scrollArea = QtGui.QScrollArea()
+        if TWO_COLUMNS:
+            self.scrollArea.setMinimumSize(410, 300)
+        else:
+            self.scrollArea.setMinimumSize(250, 520)
+        self.sl.addWidget(self.scrollArea)
+        self.widget = QtGui.QWidget()
+
+        if TWO_COLUMNS:
+            self.layout = QtGui.QGridLayout()
+        else:
+            self.layout = QtGui.QVBoxLayout()
+        self.widget.setLayout(self.layout)
+
+        self.parameter = []
+
+        for par in logger.predefined_parameters:
+            self.parameter.append((QtGui.QCheckBox(par[0]), par[1]))
+
+        self.i = 0
+        def add(w):
+            if w == 'sep':
+                sep = QtGui.QFrame()
+                sep.setFrameShape(QtGui.QFrame.HLine)
+                w = sep
+            self.layout.addWidget(w, self.i//2, self.i%2)
+            self.i += 1
+        for i, (cb, _) in enumerate(self.parameter):
+            if TWO_COLUMNS:
+                if i == logger.gui_values_number:
+                    # sep1 = QtGui.QFrame()
+                    # sep1.setFrameShape(QtGui.QFrame.HLine)
+                    # sep2 = QtGui.QFrame()
+                    # sep2.setFrameShape(QtGui.QFrame.HLine)
+                    if logger.gui_values_number%2 == 1:
+                        self.i += 1
+                    # add(sep1)
+                    # add(sep2)
+                    add('sep')
+                    add('sep')
+                add(cb)
+
+            else:
+                if i == logger.gui_values_number:
+                    sep = QtGui.QFrame()
+                    sep.setFrameShape(QtGui.QFrame.HLine)
+                    self.layout.addWidget(sep)
+                self.layout.addWidget(cb)
+
+
+        self.all = QtGui.QCheckBox(tr("Label", "All of the above"))
+        self.all.clicked.connect(self.all_changed)
+
+        if TWO_COLUMNS:
+            if (len(logger.predefined_parameters)-1)%2 == 1:
+                self.i += 1
+            add('sep')
+            add('sep')
+            add(self.all)
+        else:
+            sepa = QtGui.QFrame()
+            sepa.setFrameShape(QtGui.QFrame.HLine)
+            self.layout.addWidget(sepa)
+            self.layout.addWidget(self.all)
+
+        self.buttonLayout = QtGui.QHBoxLayout()
+        self.sl.addLayout(self.buttonLayout)
+        self.ok = QtGui.QPushButton(tr("Button", "OK"))
+        self.cancel = QtGui.QPushButton(tr("Button", "Cancel"))
+        self.buttonLayout.addWidget(self.ok)
+        self.buttonLayout.addWidget(self.cancel)
+        self.ok.pressed.connect(self.do)
+        self.cancel.pressed.connect(self.close)
+
+        self.scrollArea.setWidget(self.widget)
+
+        self.initial_ticks()
+
+    def initial_ticks(self):
+        pf = np.array(logger.parameter_functions)
+        for cb, _ in self.parameter:
+            cb.setChecked(str(cb.text()) in pf[:, 2])
+
+    def all_changed(self):
+        if self.all.isChecked():
+            for cb, _ in self.parameter:
+                cb.setEnabled(False)
+        else:
+             for cb, _ in self.parameter:
+                cb.setEnabled(True)
+
+    def do(self):
+        global logger
+        logger.reset_parameters()
+        def nif(cb, f, args):
+            if cb.isChecked() or self.all.isChecked():
+                logger.register_parameter(str(cb.text()), f, args)
+        for cb, par in self.parameter:
+            nif(cb, par[0], par[1])
+
+        self.close()
+

+ 105 - 0
KCG/base/loghandler.py

@@ -0,0 +1,105 @@
+import logging
+from PyQt4 import QtGui, QtCore
+
+"""
+This intends to be a QThread save logging handler with syntax highlighting
+"""
+
+
+class LogHandler(logging.Handler):
+    """
+    Handler Class to configure Logging
+    This also enables logging to the output field in the gui
+    """
+    def __init__(self, log_area):
+        logging.Handler.__init__(self)
+        self.text_area = log_area
+        self.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s: %(message)s'))
+        self._normal_weight = self.text_area.fontWeight()
+        self._bold = 100
+
+    def emit(self, record):
+        self.acquire()
+        self.text_area.append_signal.emit(record)
+        self.release()
+
+
+class Highlighter(QtGui.QSyntaxHighlighter):
+    def __init__(self, parent, theme):
+        super(Highlighter, self).__init__(parent, )
+        self.parent = parent
+        self.highlightingRules = []
+
+        timestamp = QtGui.QTextCharFormat()
+        timestamp.setForeground(QtCore.Qt.blue)
+        timestamp.setFontWeight(QtGui.QFont.Bold)
+        self.timestamp_pattern = QtCore.QRegExp(r"^.{23}")
+        self.timestamp_pattern.setMinimal(True)
+        self.highlightingRules.append((self.timestamp_pattern, timestamp))
+
+        loglevel = QtGui.QTextCharFormat()
+        loglevel.setForeground(QtCore.Qt.black)
+        loglevel.setFontWeight(QtGui.QFont.Bold)
+        self.loglevel_pattern = QtCore.QRegExp(r":[A-Z\_]{3,13}:")
+        self.loglevel_pattern.setMinimal(True)
+        self.highlightingRules.append((self.loglevel_pattern, loglevel))
+
+        success = QtGui.QTextCharFormat()
+        success.setForeground(QtGui.QColor(30, 210, 0))
+        success.setFontWeight(QtGui.QFont.Bold)
+        pattern = QtCore.QRegExp(r".*(S|s)uccess.*")
+        self.highlightingRules.append((pattern, success))
+
+        error = QtGui.QTextCharFormat()
+        error.setBackground(QtGui.QColor(255, 50, 50))
+        self.error_pattern = QtCore.QRegExp("ERROR")
+        self.error_pattern.setMinimal(True)
+        self.highlightingRules.append((self.error_pattern, error))
+
+    def setKeywords(self, kw):
+        keyword = QtGui.QTextCharFormat()
+        keyword.setForeground(QtCore.Qt.darkBlue)
+        keyword.setFontWeight(QtGui.QFont.Bold)
+        keywords = QtCore.QStringList(kw)
+        for word in keywords:
+            pattern = QtCore.QRegExp("\\b" + word + "\\b")
+            self.highlightingRules.append((pattern, keyword))
+
+    def highlightBlock(self, text):
+        for pattern, format in self.highlightingRules:
+            start = 0
+            if pattern not in [self.timestamp_pattern, self.error_pattern, self.loglevel_pattern]:
+                start = self.loglevel_pattern.indexIn(text)
+                start += self.loglevel_pattern.matchedLength()
+
+            index = pattern.indexIn(text, offset=start)
+
+            while index >= 0:
+                length = pattern.matchedLength()
+                pref_format = self.format(index)
+                pref_format.merge(format)
+                self.setFormat(index, length, pref_format)
+                index = text.indexOf(pattern, index + length)
+
+        self.setCurrentBlockState(0)
+
+
+class LogArea(QtGui.QTextEdit):
+    append_signal = QtCore.pyqtSignal(logging.LogRecord)
+
+    def __init__(self):
+        super(LogArea, self).__init__()
+        self.highlighter = Highlighter(self, 'Classic')
+        self.logHandler = None
+        self.append_signal.connect(self.append)
+
+    def init_logging(self):
+        self.logHandler = LogHandler(self)
+        logging.getLogger().addHandler(self.logHandler)
+
+    def append(self, record):
+        super(LogArea, self).append(self.logHandler.format(record))
+        self.ensureCursorVisible()
+
+    def setKeywords(self, kw):
+        self.highlighter.setKeywords(kw)

+ 142 - 0
KCG/base/multiWidget.py

@@ -0,0 +1,142 @@
+"""
+This is the container widget for multiple subwindows
+"""
+from PyQt4 import QtGui, QtCore
+
+import kcgwidget as kcgw
+from leftbar import LeftBar
+import leftbar
+from globals import glob as global_objects
+from .. import config
+
+tr = kcgw.tr
+
+
+class WidgetTypeError(Exception):
+    """
+    Simple error that describes when a wrong window type gets added
+    """
+    pass
+
+
+class MDIArea(kcgw.KCGWidgets):
+    """
+    The MDI Area used by Multiwidget
+    """
+    def __init__(self, parent):
+        super(MDIArea, self).__init__("MDIArea")
+        self.parent = parent
+        self.layout = QtGui.QHBoxLayout()
+        self.layout.setContentsMargins(0, 0, 0, 0)
+        self.setLayout(self.layout)
+
+        self.widgets = {}
+        self.area = QtGui.QMdiArea()
+        self.area.setFrameStyle(self.area.StyledPanel | self.area.Sunken)
+        self.layout.addWidget(self.area)
+        brush = QtGui.QBrush(QtGui.QColor('white'))
+        self.area.setBackground(brush)
+
+    def newWidget(self, widget, name, unique_id, widget_type, minSize=False):
+        """
+        Add a new Widget to the MDIArea
+        :param widget: (subclass of QMdiSubWindow) The widget to show
+        :param name: (str) name of the window
+        :param unique_id: (int) unique id of the window
+        :param widget_type: (int) the type of this window
+        :param minSize: (bool) whether to shrink the window to minimum size upon creation
+        :return: -
+        """
+        if not isinstance(widget, kcgw.KCGWidgets):
+            raise WidgetTypeError("Newly Created Widget is of type " +
+                                  type(widget).__name__ +
+                                  ". Expected class derived of " +
+                                  kcgw.KCGWidgets.__name__ + ".")
+        subWidget = kcgw.KCGSubWidget(name=name, unique_id=unique_id, typ=widget_type, minSize=minSize)
+        subWidget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+        widget.setParent(subWidget)
+
+        if minSize:
+            subWidget.resize(widget.minimumSizeHint()*1.2)
+
+
+        widget.closeSignal.connect(subWidget.close)
+        subWidget.setWidget(widget)
+        self.area.addSubWindow(subWidget)
+        subWidget.show()
+        self.widgets[unique_id] = widget
+
+class MultiWidget(QtGui.QWidget):
+    """
+    The Widget used as Multiwidget. This is the main View during operation with KCG.
+    """
+    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)
+        self.area.setObjectName("MDIArea")
+
+        self.toolbar = QtGui.QToolBar()
+        global_objects.set_global('area', self.area)
+
+        # --------[ ToolBar ]--------
+        self.new_live_action = QtGui.QAction(QtGui.QIcon(config.install_path + config.newPlotLiveIcon), tr("Button", "New Live Plot"), self)
+        self.new_live_action.setShortcut("Ctrl+L")
+        self.toolbar.addAction(self.new_live_action)
+        self.new_live_action.triggered.connect(lambda: self.leftBar.add_plot(leftbar.LIVE))
+        self.new_live_action.setToolTip(tr("Button", "New Live Plot") + "\n(Ctrl+L)")
+
+        self.new_data_action = QtGui.QAction(QtGui.QIcon(config.install_path + config.newPlotDataIcon), tr("Button", "New Data Plot"), self)
+        self.new_data_action.setShortcuts(["Ctrl+D", "Ctrl+O"])
+        self.toolbar.addAction(self.new_data_action)
+        self.new_data_action.triggered.connect(lambda: self.leftBar.add_plot(leftbar.FILE))
+        self.new_data_action.setToolTip(tr("Button", "New Data Plot") + "\n(Ctrl+D/O)")
+
+        self.layout.addWidget(self.toolbar)
+        # ------[ End Tool Bar ]-----
+
+        self.splitter.addWidget(self.leftBar)
+        self.splitter.addWidget(self.area)
+        self.layout.addWidget(self.splitter)
+
+        self.evaluate_registered_widgets()
+        self.evaluate_registered_widget_functions()
+
+    def addToolbarButton(self, icon, text, target, shortcut=None):
+        """
+        Add a toolbar button.
+        :param icon: (QIcon) The icon to show on the toolbar button
+        :param text: (str) tooltip for this button
+        :param target: (callable) The function to call upon press on button
+        :param shortcut: (str) Keyboard shortcut to call target
+        :return: -
+        """
+        na = QtGui.QAction(icon, text, self)
+        if shortcut:
+            na.setShortcut(shortcut)
+            na.setToolTip(text + "\n("+shortcut+")")
+        self.toolbar.addAction(na)
+        na.triggered.connect(target)
+
+    def evaluate_registered_widgets(self):
+        """
+        Evaluate all the registered widgets and add a toolbar button for those
+        :return:
+        """
+        for f in kcgw.get_registered_widgets():
+            self.addToolbarButton(*f)
+
+    def evaluate_registered_widget_functions(self):
+        """
+        Evaluate the functions that are registered
+        Those are in general functions that have to be called after widgets are created
+        :return:
+        """
+        for f in kcgw.get_registered_widget_functions():
+            f()
+

+ 200 - 0
KCG/base/multipage.py

@@ -0,0 +1,200 @@
+from PyQt4 import QtGui, QtCore
+from groupedelements import Elements, MenuItems
+import kcgwidget as kcgw
+from .. import config
+
+class RightSwitch(kcgw.ClickableSVG):
+    """
+    Buttons to change the Page in a MultiPage
+    """
+    def __init__(self, pagesWidget, width=10, height=20, wwidth=None, hidden=False):
+        """
+        Initialise a RightSwitch
+        :param pagesWidget: (MultiPage) The MultiPage widget to switch pages upon press
+        :param width: (int) width of the icon shown on the switch
+        :param height: (int) height of the icon shown on the switch
+        :param wwidth: (int) Width of the switch
+        :param hidden: (bool) whether this switch is shown or hidden
+        :return: -
+        """
+        super(RightSwitch, self).__init__(config.install_path+"icons/chevron-right.svg", width, height, wwidth)
+        self.setObjectName("right_switch")
+        if hidden:
+            self.hide()
+        self.clicked.connect(lambda: pagesWidget.setCurrentIndex(pagesWidget.currentIndex()+1))
+
+
+class LeftSwitch(kcgw.ClickableSVG):
+    """
+    Buttons to change the Page in a MultiPage Widget
+    """
+    def __init__(self, pagesWidget, width=10, height=20, wwidth=None, hidden=False):
+        """
+        Initialise a LeftSwitch
+        :param pagesWidget: (MultiPage) The MultiPage widget to switch pages upon press
+        :param width: (int) width of the icon shown on the switch
+        :param height: (int) height of the icon shown on the switch
+        :param wwidth: (int) Width of the switch
+        :param hidden: (bool) whether this switch is shown or hidden
+        :return: -
+        """
+        super(LeftSwitch, self).__init__(config.install_path+"icons/chevron-left.svg", width, height, wwidth)
+        self.setObjectName("left_switch")
+        if hidden:
+            self.hide()
+        self.clicked.connect(lambda: pagesWidget.setCurrentIndex(pagesWidget.currentIndex()-1))
+
+
+class LeftRightSwitch(QtGui.QWidget):
+    """
+    Small Buttons to change the Page in a MultiPage Widget
+    """
+    def __init__(self, pagesWidget):
+        """
+        Initialise a combination of left and right switch
+        :param pagesWidget: (MultiPage) The multipage widget instance to change pages on
+        """
+        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):
+        """
+        Disable the switch to the page left of the current one
+        :return: -
+        """
+        # self.left.setStyleSheet("border-radius: 4px; background-color: lightgrey;")
+        self.left.setStyleSheet("#leftright:hover { background-color: none;}")
+        self.left.changeSvg(config.install_path+"icons/chevron-left-grey.svg")
+
+    def enable_left(self):
+        """
+        Enable the switch to the page left of the current one
+        :return: -
+        """
+        self.left.setStyleSheet("")
+        self.left.changeSvg(config.install_path+"icons/chevron-left.svg")
+
+    def disable_right(self):
+        """
+        Disable the switch to the page right of the current one
+        :return: -
+        """
+        # self.right.setStyleSheet("border-radius: 4px; background-color: lightgrey;")
+        self.right.setStyleSheet("#leftright:hover { background-color: none;}")
+        self.right.changeSvg(config.install_path+"icons/chevron-right-grey.svg")
+
+    def enable_right(self):
+        """
+        Enable the switch to the page right of the current one
+        :return: -
+        """
+        self.right.setStyleSheet("")
+        self.right.changeSvg(config.install_path+"icons/chevron-right.svg")
+
+
+class MultiPage(QtGui.QStackedWidget):
+    """
+    Implementation of a Paginated View Widget
+    """
+    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)
+        self.leftShortcut = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Left"), self, self.left)
+        self.rightShortcut = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Right"), self, self.right)
+        self.lSwitches = []
+        self.rSwitches = []
+
+    def addPage(self, NewPage, name=None, set_to_first=True):
+        """
+        Add a page (a Widget) to the MultiPage Widget
+        :param NewPage: widget to add as new page
+        :param name: name of that page (e.g. to show in the status bar)
+        :param bool set_to_first: Set the current page to first page
+        :return: -
+        """
+        self.leftright.show()
+        self.numOfPages += 1
+        self.pages.append(QtGui.QWidget())
+        self.pages[-1].widget = NewPage
+        self.rSwitches.append(RightSwitch(self, wwidth=20, hidden=True))
+        self.lSwitches.append(LeftSwitch(self, wwidth=20, hidden=True))
+        NewPage.index = len(self.pages)-1
+        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.rSwitches[-2].show()
+            self.pages[-2].layout.addWidget(self.rSwitches[-2])
+            self.lSwitches[-1].show()
+            self.pages[-1].layout.addWidget(self.lSwitches[-1])
+            self.pages[-1].layout.addWidget(NewPage)
+        self.addWidget(self.pages[-1])
+        if set_to_first:
+            self.setCurrentIndex(0)
+        else:
+            name = self.pages[self.currentIndex()].name+" " if self.pages[self.currentIndex()].name else " "
+            self.window().pageIndicator.setText(name+"| "+str(self.currentIndex()+1)+"/"+str(self.numOfPages+1))
+
+
+    def removePage(self, page):
+        """
+        Removes a page from the pages widget and adjusts switches accordingly
+        :param page: what page to remove
+        :return: -
+        """
+        if self.numOfPages == 1:
+            raise IndexError("Not enough pages left")
+        self.numOfPages -=1
+        idx = page.index
+        self.removeWidget(self.pages[idx])
+        del self.pages[idx]
+        del self.lSwitches[idx]
+        del self.rSwitches[idx]
+        self.pages[-1].layout.removeWidget(self.rSwitches[-1])
+        self.rSwitches[-1].hide()
+        name = self.pages[self.currentIndex()].name+" " if self.pages[self.currentIndex()].name else " "
+        self.window().pageIndicator.setText(name+"| "+str(self.currentIndex()+1)+"/"+str(self.numOfPages+1))
+
+    def left(self):
+        self.setCurrentIndex(self.currentIndex()-1)
+
+    def right(self):
+        self.setCurrentIndex(self.currentIndex()+1)
+
+    def setCurrentIndex(self, p_int):
+        """
+        Set te current Index of the MultiPage Widget (e.g. set the current page)
+        :param p_int: (int) what page
+        :return: -
+        """
+        if self.currentIndex() >= 0 and p_int <= self.numOfPages:
+            MenuItems.setEnabled(self.pages[self.currentIndex()].name.strip(), False)
+        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)
+        MenuItems.setEnabled(name.strip(), True)
+        if getattr(self.pages[self.currentIndex()].widget, 'pages_update_function', None) is not None:
+            self.pages[self.currentIndex()].widget.pages_update_function()

Some files were not shown because too many files changed in this diff