浏览代码

Implements initial config dialog

fixes #24
Patrick Schreiber 8 年之前
父节点
当前提交
26a4f4a90d
共有 8 个文件被更改,包括 414 次插入74 次删除
  1. 14 4
      KCG/Documentation/source/Man/overview.rst
  2. 4 1
      KCG/Documentation/source/Man/plots.rst
  3. 2 2
      KCG/VERSION
  4. 1 0
      KCG/base/kcg.py
  5. 83 47
      KCG/config.py
  6. 7 4
      KCG/kcg.py
  7. 268 0
      KCG/widgets/initialconfig.py
  8. 35 16
      README.md

+ 14 - 4
KCG/Documentation/source/Man/overview.rst

@@ -19,9 +19,7 @@ there is a previous or next page) or press the small arrows in the top right cor
 Control View
 ------------
 
-After startup the Gui will be in the Control View as seen below.
-
-.. image:: _static/Controlwidget.png
+After startup the Gui will be in the Control View.
 
 Located in this View are several Buttons to control the KAPTURE Board.
 
@@ -33,8 +31,20 @@ Located in this View are several Buttons to control the KAPTURE Board.
     #. Soft Reset - Perform a Soft Reset
     #. Board Off - Shut the Board off
     #. Check Status - Check the Status of the Board
+    #. Skip Initialisation - Skip he intialisation process.
+
+Skip Initialisation
+'''''''''''''''''''
+
+This is useful when you need to restart the Gui but leave the board in a state it was before.
+Skip Initialisation will basically prepare the Gui rather than the board. It will set the Gui in a state where
+the board has to be calibrated, synchronized and default values setted.
+
+This means: if the board was not prepared before it will not work properly if at all.
+
+Skip Initialisation will also read settings from board and update the Gui with these settings.
 
-.. image:: _static/Controlwidget_buttons.png
+.. caution:: Skip Initialisation is dangerous.
 
 Multi View
 ----------

+ 4 - 1
KCG/Documentation/source/Man/plots.rst

@@ -10,7 +10,7 @@ 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.
 
-.. note::
+.. tip::
     You can open each of those data types directly as described in the corresponding section below.
 
 Live Data
@@ -62,6 +62,9 @@ FFT means to plot the fourertransformed data in a 2D plot with color coded value
 
 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::

+ 2 - 2
KCG/VERSION

@@ -1,4 +1,4 @@
-1.0.1 Beta
-Date: 28.08.15
+0.1.0915
+Date: 15.09.15
 
 NOTE: this program is still in beta state and may have bugs

+ 1 - 0
KCG/base/kcg.py

@@ -40,6 +40,7 @@ def readconfig(parent):
     :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',

+ 83 - 47
KCG/config.py

@@ -1,64 +1,100 @@
 import ConfigParser
 import ast
 import os
+import sys
 
-config = ConfigParser.ConfigParser()
-config.optionxform = str
-config.read(os.path.expanduser("~")+"/.kcg/config.cfg")
-defaultConfig = ConfigParser.ConfigParser()
-defaultConfig.optionxform = str
-defaultConfig.read(os.path.join(os.path.dirname(__file__), "config.cfg"))
+error = False
 
+class NoValueException(Exception):
+    pass
 
+def leval(string):
+    if string == "":
+        raise NoValueException("No value given for option")
+    else:
+        return ast.literal_eval(string)
 
-Machine_conf = ["bunches_per_turn", "save_header", "tRev"]
-Ui_conf = ["language", "default_save_location", "default_subdirectory_name", "force_ask", "show_advanced_control"]
-Logging_conf = ["epics_test_pv", "epics_base_path"]
 
-error = False
+def read():
+    global error
+    config = ConfigParser.ConfigParser()
+    config.optionxform = str
+    config.read(os.path.expanduser("~")+"/.kcg/config.cfg")
+    defaultConfig = ConfigParser.ConfigParser()
+    defaultConfig.optionxform = str
+    defaultConfig.read(os.path.join(os.path.dirname(__file__), "config.cfg"))
 
-try:
-    for conf in Machine_conf:
-        if config.has_option("Machine", conf):
-            globals()[conf] = ast.literal_eval(config.get('Machine', conf))
-        else:
-            print "Using default configuration value for " + conf
-            globals()[conf] = ast.literal_eval(defaultConfig.get("Machine", conf))
-    for conf in Ui_conf:
-        if config.has_option("Ui", conf):
-            globals()[conf] = ast.literal_eval(config.get('Ui', conf))
+    Machine_conf = ["bunches_per_turn", "save_header", "tRev"]
+    Ui_conf = ["language", "default_save_location", "default_subdirectory_name", "force_ask", "show_advanced_control"]
+    Logging_conf = ["epics_test_pv", "epics_base_path"]
+
+    error = False
+
+    try:
+        for conf in Machine_conf:
+            if config.has_option("Machine", conf):
+                try:
+                    globals()[conf] = leval(config.get('Machine', conf))
+                except NoValueException:
+                    error = True
+            else:
+                # print "Using default configuration value for " + conf
+                # globals()[conf] = leval(defaultConfig.get("Machine", conf))
+                error = True
+        for conf in Ui_conf:
+            if config.has_option("Ui", conf):
+                globals()[conf] = leval(config.get('Ui', conf))
+            else:
+                globals()[conf] = leval(defaultConfig.get("Ui", conf))
+                print "Using default configuration value for " + conf
+        for conf in Logging_conf:
+            if config.has_option("Logging", conf):
+                globals()[conf] = leval(config.get('Logging', conf))
+            else:
+                globals()[conf] = leval(defaultConfig.get("Logging", conf))
+                print "Using default configuration value for " + conf
+
+        if config.has_option("Logging", "epics_log_entry_pvs"):
+            globals()["epics_log_entry_pvs"] = leval(config.get('Logging', "epics_log_entry_pvs"))
         else:
-            globals()[conf] = ast.literal_eval(defaultConfig.get("Ui", conf))
-            print "Using default configuration value for " + conf
-    for conf in Logging_conf:
-        if config.has_option("Logging", conf):
-            globals()[conf] = ast.literal_eval(config.get('Logging', conf))
+            globals()["epics_log_entry_pvs"] = leval(defaultConfig.get('Logging', "epics_log_entry_pvs"))
+            print "Using default configuration value for epics_log_entry_pvs"
+
+        if config.has_option("Logging", "default_log_entries"):
+            globals()["default_log_entries"] = leval(config.get('Logging', "default_log_entries"))
         else:
-            globals()[conf] = ast.literal_eval(defaultConfig.get("Logging", conf))
-            print "Using default configuration value for " + conf
+            globals()["default_log_entries"] = leval(defaultConfig.get('Logging', "default_log_entries"))
+            print "Using default configuration value for default_log_entries"
 
-    if config.has_option("Logging", "epics_log_entry_pvs"):
-        globals()["epics_log_entry_pvs"] = ast.literal_eval(config.get('Logging', "epics_log_entry_pvs"))
-    else:
-        globals()["epics_log_entry_pvs"] = ast.literal_eval(defaultConfig.get('Logging', "epics_log_entry_pvs"))
-        print "Using default configuration value for epics_log_entry_pvs"
+        # -----[ Do non standard config options ]------------
+        if config.has_section('Misc'):
+            for conf, val in config.items("Misc"):
+                globals()[conf] = leval(val)
 
-    if config.has_option("Logging", "default_log_entries"):
-        globals()["default_log_entries"] = ast.literal_eval(config.get('Logging', "default_log_entries"))
-    else:
-        globals()["default_log_entries"] = ast.literal_eval(defaultConfig.get('Logging', "default_log_entries"))
-        print "Using default configuration value for default_log_entries"
+        for conf, val in defaultConfig.items("Misc"):
+            if not conf in globals().keys():
+                globals()[conf] = leval(val)
+
+    except (ConfigParser.NoOptionError, ConfigParser.NoSectionError) as e:
+        error = True
+        print "There was an error parsing configuration " + str(e)
 
-    # -----[ Do non standard config options ]------------
-    if config.has_section('Misc'):
-        for conf, val in config.items("Misc"):
-            globals()[conf] = ast.literal_eval(val)
 
-    for conf, val in defaultConfig.items("Misc"):
-        if not conf in globals().keys():
-            globals()[conf] = ast.literal_eval(val)
+def setup():
+    if not os.path.isfile(os.path.expanduser("~")+"/.kcg/config.cfg"):
+        doSetup()
 
-except (ConfigParser.NoOptionError, ConfigParser.NoSectionError) as e:
-    error = True
-    print "There was an error parsing configuration " + str(e)
+def doSetup():
+    from PyQt4 import QtGui, QtCore
+    from widgets.initialconfig import ConfigSetup
+    setupConfigApp = QtGui.QApplication([])
+    setupConfig = ConfigSetup()
+    setupConfig.show()
+    setupConfigApp.exec_()
+    if not setupConfig.result:
+        sys.exit(122)
+    global error
+    error = False
+    read()
 
+read()

+ 7 - 4
KCG/kcg.py

@@ -4,15 +4,21 @@ import sys
 import os
 
 import base.kcgwidget as kcgw
+import config
+
 translator = QtCore.QTranslator()
 kcgw.translator = translator
 # kcgw.tr = translator.translate
 kcgw.tr = QtCore.QCoreApplication.translate
-import config
 
 config.install_path = os.path.dirname(config.__file__) + "/"
 
+config.setup()
+while config.error:
+    config.doSetup()
+
 import base.kcg as kcg
+
 try: # Try to use Erax for exception logging
     from erax import Erax
 
@@ -28,11 +34,8 @@ if 'testing' in sys.argv:
 else:
     kcgw.testing = False
 
-gui=None
 def main():
-    global gui
     app = QtGui.QApplication(sys.argv)
-    import config
     app.installTranslator(translator)
     gui = kcg.Gui()
     gui.show()

+ 268 - 0
KCG/widgets/initialconfig.py

@@ -0,0 +1,268 @@
+from PyQt4 import QtGui, QtCore
+import ast
+from ..base import kcgwidget as kcgw
+import os
+from .. import config
+try:
+    from pprint import pformat
+except ImportError:
+    def pformat(string, width=None):
+        return string
+
+class ConfigEntry(kcgw.KCGWidgets):
+    def __init__(self, comm='', conf='', type=str, multiline=False):
+        super(ConfigEntry, self).__init__()
+        self.layout = QtGui.QVBoxLayout()
+        self.entryLayout = QtGui.QHBoxLayout()
+        self.setLayout(self.layout)
+        self.toggle = self.createButton("?", connect=self.toggle)
+        self.toggle.setFixedWidth(20)
+
+        if len(comm.split("\n")) <= 4:
+            self.comment = self.createLabel(comm)
+            self.comment.setStyleSheet("color: grey;")
+            self.comment.setAlignment(QtCore.Qt.AlignRight)
+
+        else:
+            self.commentText = self.createLabel(comm)
+            self.commentText.setStyleSheet("color: grey;")
+            self.commentText.setAlignment(QtCore.Qt.AlignRight)
+
+            self.commentWidget = QtGui.QWidget()
+            self.comment = QtGui.QScrollArea()
+            self.comment.setAlignment(QtCore.Qt.AlignRight)
+            self.commlayout = QtGui.QVBoxLayout()
+            self.commentWidget.setLayout(self.commlayout)
+            self.commlayout.addWidget(self.commentText, alignment=QtCore.Qt.AlignRight)
+            self.comment.setWidget(self.commentWidget)
+            self.comment.text = lambda: self.commentText.text()
+
+        self.config = self.createLabel(conf)
+        self.config.setFixedWidth(200)
+        self.multiline = multiline
+        self.type = type
+        if not multiline:
+            self.value = self.createInput(width=300)
+        else:
+            self.value = QtGui.QTextEdit()
+            self.value.setFixedWidth(500)
+        try:
+            self.value.setText(pformat(getattr(config, conf), width=60, indent=4))
+        except AttributeError:
+            pass
+        self.layout.addWidget(self.comment)
+        self.layout.addLayout(self.entryLayout)
+        self.entryLayout.addWidget(self.config)
+        self.entryLayout.addWidget(self.value)
+        self.entryLayout.addWidget(self.toggle)
+        self.comment.hide()
+
+    def toggle(self):
+        self.comment.setHidden(not self.comment.isHidden())
+
+    def set_comment(self, comm):
+        self.comment.setText(comm)
+
+    def set_config(self, conf):
+        self.config.setText(conf)
+
+    def validate(self, mark=False):
+        if self.multiline:
+            try:
+                v, e = self.san(str(self.value.toPlainText()))
+                if e:
+                    self.value.setText(v)
+                if type(config.leval(v)) == self.type:
+                    self.value.setStyleSheet("background-color: #FFFFFF;")
+                    return True
+            except (ValueError, config.NoValueException, SyntaxError):
+                pass
+        else:
+            try:
+                v, e = self.san(str(self.value.text()))
+                if e:
+                    self.value.setText(v)
+                if type(config.leval(v)) == self.type:
+                    self.value.setStyleSheet("background-color: #FFFFFF;")
+                    return True
+            except (ValueError, config.NoValueException, SyntaxError):
+                pass
+        if mark:
+            self.value.setStyleSheet("background-color: #FFAAAA;")
+        return False
+
+    def san(self, val):
+        err = False
+        if self.type == str:
+            val.replace('"', '\'')
+            if val[0] == "'":
+                if not val[-1] == "'":
+                    val += "'"
+                    err = True
+            elif val[-1] == "'":
+                val = "'" + val
+                err = True
+            else:
+                val = '"' + val + '"'
+                err = False
+        return val, err
+
+    def __str__(self):
+        comment = ''
+        for line in str(self.comment.text()).split('\n'):
+            comment += "# " + line + '\n'
+        if self.multiline:
+            val = self.value.toPlainText()
+        else:
+            val = self.value.text()
+
+        val, e = self.san(val)
+
+        return str(comment + self.config.text() + " = " + str(val))
+
+
+class ConfigSetup(kcgw.KCGWidgets):
+    machine_comments = ["Bunches per turn of accelerator (integer value)",
+                        "Save headerinformation in file (bool value)",
+                        "Revolution time (double value)"
+                        ]
+    ui_comments = ['NOTE: This value will be overwritten when the language is changed in the gui-settings\n'
+                        'possible languages:\n'
+                        '"en_GB" - English\n'
+                        '"de_DE" - German',
+                   'default_save_location: use "pwd" for current working\n'
+                        'directory KCG will always save in a subdirectory to\n'
+                        'this given path and save files in this directory',
+                   'default_subdirectory_name_format: this is the\n'
+                        'naming scheme for the subdirectory in which the\n'
+                        'files are saved. Format of this string:\n'
+                        '"{tag1}text{tag2}text" etc.\n'
+                        'possible tags:\n'
+                        '{dateG} will produce e.g. 04.01.2015\n'
+                        '{dateGd} will produce e.g. 04_01_2015\n'
+                        '{dateA} will pdoduce e.g. 01-04-2015\n'
+                        '{times} will produce e.g. 14_04\n'
+                        '{timel} will produce e.g. 14_04_12\n'
+                        '{d} the Day in 2 digit format\n'
+                        '{m} the Month in 2 digit format\n'
+                        '{y} the Year in 4 digit Format\n'
+                        '{H} the Hour in 2 digit format\n'
+                        '{M} the minute in 2 digit format\n'
+                        '{S} the seconds in 2 digit format\n'
+                        '{timestamp} unix timestamp without msec\n'
+                        '{user} the current logged in user\n'
+                        '{sessionname} Ask for session name at startup\n'
+                        '{ask} always ask for a foldername',
+                   'reask on cancel in dialog or use {user}_{dateGd}-{timel} as default when cancel is pressed?',
+                   'Show advanced table view per default? (boolean value)'
+                ]
+    logging_comments = ['These are PVs that will be possible to insert into log files\n'
+                            'This variable is to be a list consisting of touples of two entries,\n'
+                            'the first ist the Text that describes the value and the second is the EPICS PV that\n'
+                            'holds that value',
+                       'This pv is used to determine if epics pvs are accessible' ,
+                       'Path to your epics base installation',
+                       'List of Entries that are default to save in Log\n'
+                           'Possible Values are:\n'
+                           '"Number of Orbits" \n'
+                           '"Number of Skipped Orbits" \n'
+                           '"Number of Acquisitions" \n'
+                           '"Time between Acquisitions" \n'
+                           '"Pilot Bunch Simulator" \n'
+                           '"Header saved" \n'
+                           '"T/H Delay" \n'
+                           '"ADC 1 Delay" \n'
+                           '"ADC 2 Delay" \n'
+                           '"ADC 3 Delay" \n'
+                           '"ADC 4 Delay" \n'
+                           'All of the description text entries in epics_log_entry_pvs, see above \n'
+                           'NOTE: These entries have to match the aforementioned strings exactly'
+                    ]
+    machine_configs = ['bunches_per_turn',
+                       'save_header',
+                       'tRev']
+    ui_configs = ['language',
+                  'default_save_location',
+                  'default_subdirectory_name',
+                  'force_ask',
+                  'show_advanced_control']
+    logging_configs = ['epics_log_entry_pvs',
+                       'epics_test_pv',
+                       'epics_base_path',
+                       'default_log_entries']
+    machine_types = [int, bool, float]
+    ui_types = [str, str, str, bool, bool]
+    logging_types = [list, str, str, list]
+    def __init__(self):
+        super(ConfigSetup, self).__init__()
+        self.configs = []
+        self.result = False
+        self.resize(800, 600)
+        self.mainlayout = QtGui.QVBoxLayout()
+        # self.textEdit = QtGui.QTextEdit()
+        # self.layout.addWidget(self.textEdit)
+        # with open(os.path.dirname(__file__)+'/../config.cfg', 'r') as f:
+        #     self.textEdit.setText(f.read())
+        self.setLayout(self.mainlayout)
+        self.scrollWidget = QtGui.QScrollArea()
+        self.heading = self.createLabel("Initial Configuration Setup")
+        self.heading.setStyleSheet("font-size:25pt;")
+        self.mainlayout.addWidget(self.heading)
+        self.mainlayout.addWidget(self.scrollWidget)
+        self.saveButton = self.createButton("save", connect=self.save)
+        self.mainlayout.addWidget(self.saveButton)
+
+        self.wid = QtGui.QWidget()
+        self.layout = QtGui.QVBoxLayout()
+        self.wid.setLayout(self.layout)
+
+        self.machine_label = self.createLabel("Machine")
+        self.machine_label.setStyleSheet("font-size: 20pt;")
+        self.layout.addWidget(self.machine_label)
+        self.configs.append("\n[Machine]")
+        for comm, conf, type in zip(self.machine_comments, self.machine_configs, self.machine_types):
+            self.configs.append(ConfigEntry(comm, conf, type))
+            self.layout.addWidget(self.configs[-1])
+
+
+        self.ui_label = self.createLabel("Ui")
+        self.ui_label.setStyleSheet("font-size: 20pt;")
+        self.layout.addWidget(self.ui_label)
+        self.configs.append("\n[Ui]")
+        for comm, conf, type in zip(self.ui_comments, self.ui_configs, self.ui_types):
+            self.configs.append(ConfigEntry(comm, conf, type))
+            self.layout.addWidget(self.configs[-1])
+
+        self.logging_label = self.createLabel("Logging")
+        self.logging_label.setStyleSheet("font-size:20pt;")
+        self.layout.addWidget(self.logging_label)
+        self.configs.append("\n[Logging]")
+        for comm, conf, type in zip(self.logging_comments, self.logging_configs, self.logging_types):
+            if conf == 'epics_log_entry_pvs' or conf == 'default_log_entries':
+                self.configs.append(ConfigEntry(comm, conf, type, multiline=True))
+            else:
+                self.configs.append(ConfigEntry(comm, conf, type))
+            self.layout.addWidget(self.configs[-1])
+
+
+        self.scrollWidget.setWidget(self.wid)
+    def save(self):
+        if not os.path.isdir(os.path.expanduser("~")+"/.kcg"):
+            os.mkdir(os.path.expanduser("~")+"/.kcg")
+
+        abort = False
+        for item in self.configs:
+            if type(item) == str:
+                continue
+            if not item.validate(mark=True):
+                abort = True
+        if abort:
+            return
+
+        with open(os.path.expanduser("~")+'/.kcg/config.cfg', 'w+') as f:
+            for item in self.configs:
+                f.write(str(item)+'\n')
+        self.result = True
+        self.close()
+
+

+ 35 - 16
README.md

@@ -1,29 +1,48 @@
-KCG - KAPTURE Control Gui
-=========================
+# KCG - KAPTURE Control Gui
 
 KCG is the Graphical Control Interface to the KAPTURE-Readout-Board.  
 
-Prerequisites:
---------------
+## Prerequisites:
 
 - python 2.7.x
 - PyQt4 (python-qt)
 - pyqtgraph
 - numpy
 
-Usage/Installation
-------------------
+## Download
 
-- Clone this repository
-- Modify `config.py` in the KCG directory of this repository and make sure all the variables are set correctly for your needs.
-- To run KCG without installation simply run `kcg` in the root of this repository
-- To install KCG (either system- or userwide) use `python setup.py install` with optionally `--user` as flag to 
-install only for current user.
-- To start KCG after installation simply run kcg from whereever you are. 
-- If you chose to install for current user only you need to have $HOME/.local/bin in your $PATH in order to run kcg.
+#### Stable Version
 
+To get the latest stable version get the latest release from [here](https://psraspi.no-ip.biz/gogs/calipp/KCG/releases)
+#### Development Version
+
+To get the latest development Version clone this repository 
+
+*Some notes to those of you who want to test the newest features:  
+Branches other than the master branch will hold some newer (and potentially completely untested) additions. Feel free to test them.*
+
+**NOTE:** this might be unstable or not even work at all
+
+## Install/Run
+
+You can install KCG or run without intstallation
+
+### Install
+
+To install KCG (either system- or userwide) use `python setup.py install` with optionally `--user` as flag to install only for current user.  
+To start KCG after installation simply run `kcg` from whereever you are.  
+Upon first run you will be prompted to configure KCG this will set appropriate values and write them to ~/.kcg/config.cfg  
+
+#### NOTE: 
+If you chose to install for current user only you need to have $HOME/.local/bin in your $PATH in order to run kcg.
+
+### Run without installation
+
+You can run KCG directly without installation. To do so, simply run kcg in the outer KCG directory.
+    You will still be asked to configure KCG if it is the first run.
+
+
+### Unintentional Features
+If you find any unintentional features (bugs) or have questions please let me know via [issues](https://psraspi.no-ip.biz/gogs/calipp/KCG/issues).
 
-If you find any bugs or have questions please let me know via issues [here](https://psraspi.no-ip.biz/gogs/calipp/KCG/issues).
 
-Some notes to those of you who want to test the newest features:  
-The dev Branch will hold some newer additions than the master branch. Feel free to test them.