diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a1a85970635606d0513da94c62e1ca7dcd0bda91..ccc2977f043b6c3287d88055a1e4667a379855f0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -27,7 +27,7 @@ flake8:
     - passoft
   stage: Build Env and Test
   script:
-    - flake8 --exclude sohstationviewer/view/ui sohstationviewer
+    - flake8 --exclude sohstationviewer/view/ui,sohstationviewer/controller/core/ sohstationviewer
     - flake8 tests
 
 python3.7:
diff --git a/sohstationviewer.py b/sohstationviewer.py
old mode 100644
new mode 100755
index db43d6719cd02cf4b1216ab104172f4a5609b797..135ac8ed8533b8870838a64ced8f57ed69818d2a
--- a/sohstationviewer.py
+++ b/sohstationviewer.py
@@ -1,10 +1,23 @@
+#!/usr/bin/env python3
+import platform
+import os
 import sys
-
 from PySide2 import QtWidgets
 
 from sohstationviewer.view.mainwindow import MainWindow
 
 
+# Enable Layer-backing for MacOs version >= 11
+# Only needed if using the pyside2 library with version>=5.15.
+# Layer-backing is always enabled in pyside6.
+os_name, version, *_ = platform.platform().split('-')
+# if os_name == 'macOS' and version >= '11':
+# mac OSX 11.6 appear to be 10.16 when read with python and still required this
+# environment variable
+if os_name == 'macOS':
+    os.environ['QT_MAC_WANTS_LAYER'] = '1'
+
+
 def main():
     app = QtWidgets.QApplication(sys.argv)
     wnd = MainWindow()
diff --git a/sohstationviewer/conf/colorSettings.py b/sohstationviewer/conf/colorSettings.py
new file mode 100755
index 0000000000000000000000000000000000000000..d38e6e7c316431fb6e9a1eada437613bcb8b48bf
--- /dev/null
+++ b/sohstationviewer/conf/colorSettings.py
@@ -0,0 +1,92 @@
+# Just using RGB for everything since some things don't handle color names
+# correctly, like PIL on macOS doesn't handle "green" very well.
+# b = dark blue, was the U value for years, but it can be hard to see, so U
+#     was lightened up a bit.
+# Orange should be #FF7F00, but #DD5F00 is easier to see on a white background
+# and it still looks OK on a black background.
+# Purple should be A020F0, but that was a little dark.
+# "X" should not be used. Including X at the end of a passed color pair (or by
+# itself) indicates that a Toplevel or dialog box should use grab_set_global()
+# which is not a color.
+
+Clr = {"B": "#000000", "C": "#00FFFF", "G": "#00FF00", "M": "#FF00FF",
+       "R": "#FF0000", "O": "#FF7F00", "W": "#FFFFFF", "Y": "#FFFF00",
+       "E": "#DFDFDF", "A": "#8F8F8F", "K": "#3F3F3F", "U": "#0070FF",
+       "N": "#007F00", "S": "#7F0000", "y": "#7F7F00", "u": "#ADD8E6",
+       "s": "#FA8072", "p": "#FFB6C1", "g": "#90EE90", "r": "#EFEFEF",
+       "P": "#AA22FF", "b": "#0000FF"}
+
+# This is just if the program wants to let the user know what the possibilities
+# are.
+ClrDesc = {"B": "black", "C": "cyan", "G": "green", "M": "magenta",
+           "R": "red", "O": "orange", "W": "white", "Y": "yellow",
+           "E": "light gray", "A": "gray", "K": "dark gray", "U": "blue",
+           "N": "dark green", "S": "dark red", "y": "dark yellow",
+           "u": "light blue", "s": "salmon", "p": "light pink",
+           "g": "light green", "r": "very light gray", "P": "purple",
+           "b": "dark blue"}
+
+
+def set_colors(mode):
+    """
+    get the display_color dict according to mode
+    :param mode: "B" or "W"
+    """
+    display_color = {}
+    if mode == "B":
+        # Main form background
+        display_color["MF"] = Clr["B"]
+        # GPS background and dots.
+        display_color["GS"] = Clr["K"]
+        display_color["GD"] = Clr["W"]
+        # Time rule time and rule.
+        display_color["TM"] = Clr["W"]
+        display_color["TR"] = Clr["Y"]
+        # Zoom markers
+        display_color["ZM"] = Clr["O"]
+        # Time grid lines.
+        display_color["GR"] = Clr["y"]
+        # Date/time and ticks. Different things want the colors expressed in
+        # different ways.
+        display_color["T0"] = Clr["W"]
+        display_color["TL"] = Clr["W"]
+        # Labels.
+        display_color["LB"] = Clr["C"]
+        # Text like the title.
+        display_color["TX"] = Clr["W"]
+        # The plot center line or the upper and lower bounds lines.
+        display_color["P0"] = Clr["A"]
+        # Line connecting dots.
+        display_color["PL"] = Clr["A"]
+        # Selector rules
+        display_color["SR"] = Clr["Y"]
+        # TPS Canvas
+        display_color["TC"] = Clr["B"]
+        # The main plot time rule created by others (like by TPS clicking).
+        display_color["OR"] = Clr["O"]
+        # Gap Gap and Overlap.
+        display_color["GG"] = Clr["R"]
+        display_color["GO"] = Clr["R"]
+        # RT130:
+        display_color["BAD/OFF"] = Clr["R"]
+        display_color["GOOD/ON"] = Clr["G"]
+
+    elif mode == "W":
+        display_color["MF"] = Clr["W"]
+        display_color["GS"] = Clr["W"]
+        display_color["GD"] = Clr["B"]
+        display_color["TM"] = Clr["B"]
+        display_color["TR"] = Clr["U"]
+        display_color["GR"] = Clr["E"]
+        display_color["T0"] = Clr["B"]
+        display_color["TL"] = Clr["B"]
+        display_color["LB"] = Clr["B"]
+        display_color["TX"] = Clr["B"]
+        display_color["P0"] = Clr["A"]
+        display_color["PL"] = Clr["E"]
+        display_color["SR"] = Clr["A"]
+        display_color["TC"] = Clr["W"]
+        display_color["OR"] = Clr["U"]
+        display_color["GG"] = Clr["R"]
+        display_color["GO"] = Clr["R"]
+    return display_color
diff --git a/sohstationviewer/conf/constants.py b/sohstationviewer/conf/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..d27768d785c5935791658d0f0a79c8b214b5ee4a
--- /dev/null
+++ b/sohstationviewer/conf/constants.py
@@ -0,0 +1 @@
+HIGHEST_INT = 1E100
diff --git a/sohstationviewer/conf/dbSettings.py b/sohstationviewer/conf/dbSettings.py
new file mode 100755
index 0000000000000000000000000000000000000000..de734017dc7217de7e4c51ade5ef3f3d52dad9d9
--- /dev/null
+++ b/sohstationviewer/conf/dbSettings.py
@@ -0,0 +1,16 @@
+import re
+
+"""
+seisRE: Seismic data channels' regex:
+First letter(Band Code): ABCDEFGHLMOPQRSTUV
+Second letter (Instrument Code): GHLMN
+Third letter (Orientation Code): ZNE123
+=> to check if the channel is seismic data: if conf['seisRE'].match(chan):
+"""
+
+conf = {
+    'dbpath': 'sohstationviewer/database/soh.db',
+    'seisRE': re.compile('[A-HLM-V][GHLMN][ZNE123]'),
+    # +0.2:Y
+    'valColRE': re.compile('^\+?\-?[0-9]+\.?[0-9]?:[RYGMC]')     # noqa: W605
+}
diff --git a/sohstationviewer/controller/core/__init__.py b/sohstationviewer/controller/core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/sohstationviewer/controller/core/time_tmp.py b/sohstationviewer/controller/core/time_tmp.py
index 74e9df879818ee984dd65266c0652cb780ec2e51..4f3c52f16b8aa53754cd7d0f46792c29c0543adf 100644
--- a/sohstationviewer/controller/core/time_tmp.py
+++ b/sohstationviewer/controller/core/time_tmp.py
@@ -3,9 +3,9 @@ Processing time  functions that are derived exactly from qpeek/logpeek
 """
 from time import gmtime, localtime, sleep, strftime, time
 from struct import pack, unpack
-from sohstationviewer.controller.core.utils import intt, floatt, rtnPattern
+# from sohstationviewer.controller.core.utils import intt, floatt, rtnPattern
 from obspy import UTCDateTime
-
+from sohstationviewer.controller.util import displayTrackingInfo
 # First day of the month for each non-leap year month MINUS 1. This will get
 # subtracted from the DOY, so a DOY of 91, minus the first day of April 90
 # (91-90) will leave the 1st of April. The 365 is the 1st of Jan of the next
@@ -44,8 +44,8 @@ def formatTime(parent, time, dateMode, timeMode=None):
         format = '%Y%m%d'
     elif dateMode == 'YYYY:DOY':
         format = '%Y:%j'
-    elif parent is not None:
-        parent.displayTrackingInfo("not defined format:'%s'" % dateMode,
+    else:
+        displayTrackingInfo(parent, "not defined format:'%s'" % dateMode,
                                    "error")
     if timeMode == 'HH:MM:SS':
         format += " %H:%M:%S"
diff --git a/sohstationviewer/controller/plottingData.py b/sohstationviewer/controller/plottingData.py
new file mode 100755
index 0000000000000000000000000000000000000000..545be6195fade756195bbfeb29ceaf04dea5485f
--- /dev/null
+++ b/sohstationviewer/controller/plottingData.py
@@ -0,0 +1,214 @@
+"""
+Functions that process data before plotting
+"""
+
+import math
+
+from obspy import UTCDateTime
+
+from sohstationviewer.controller.util import displayTrackingInfo
+from sohstationviewer.conf.dbSettings import conf
+
+
+maxInt = 1E100
+maxFloat = 1.0E100
+# TODO: put this in DB
+MassPosVoltRanges = {"regular": [0.5, 2.0, 4.0, 7.0],
+                     "trillium": [0.5, 1.8, 2.4, 3.5]}
+MassPosColorPallets = {"B": ["C", "G", "Y", "R", "M"],
+                       "W": ["B", "B", "B", "B", "B"]}
+
+
+def getMassposValueColors(rangeOpt, chan, cMode, errors, retType='str'):
+    if rangeOpt.lower() not in MassPosVoltRanges.keys():
+        errors.append("%s: The current selected Mass Position color range "
+                      "is '%s' isn't allowable. The accept ranges are:  %s"
+                      % (chan, rangeOpt, ', '.join(MassPosVoltRanges.keys())))
+        print("ERRORS:", errors)
+        return
+    massPosVoltRange = MassPosVoltRanges[rangeOpt]
+    massPosColorPallet = MassPosColorPallets[cMode]
+    mul = -1
+    valueColors = []
+    """
+    remove size for masspos because there are two many points for one plot
+    which make the line thicks
+    TODO: show something to let user know that it is clickable.
+    """
+    for i in range(len(massPosVoltRange)):
+        if i % 2 == 0:
+            mul += 1
+        if retType == 'str':
+            valueColors.append(
+                "%s:%s" % (massPosVoltRange[i], massPosColorPallet[i]))
+        else:
+            valueColors.append((massPosVoltRange[i], massPosColorPallet[i]))
+        if i == len(massPosVoltRange) - 1:
+            if retType == 'str':
+                valueColors.append(
+                    "%s:+%s" % (massPosVoltRange[i], massPosColorPallet[i+1]))
+            else:
+                valueColors.append(
+                    (massPosVoltRange[i], massPosColorPallet[i + 1]))
+    if retType == 'str':
+        return '|'.join(valueColors)
+    return valueColors
+
+
+def formatTime(parent, time, dateMode, timeMode=None):
+    """
+    :param parent: parent GUI to display tracking info
+    :param time: time to be format, can be UTCDateTime or epoch time
+    :param dateMode: the format of date
+    :param timeMode: the format of time
+    :return: the formated time string
+    """
+    if isinstance(time, UTCDateTime):
+        t = time
+    else:
+        t = UTCDateTime(time)
+
+    # https://docs.python.org/3/library/datetime.html#
+    # strftime-and-strptime-format-codes
+    format = ''
+    if dateMode == 'YYYY-MM-DD':
+        format = '%Y-%m-%d'
+    elif dateMode == 'YYYYMMDD':
+        format = '%Y%m%d'
+    elif dateMode == 'YYYY:DOY':
+        format = '%Y:%j'
+    else:
+        displayTrackingInfo(parent,
+                            "not defined format:'%s'" % dateMode,
+                            "error")
+    if timeMode == 'HH:MM:SS':
+        format += " %H:%M:%S"
+
+    ret = t.strftime(format)
+    return ret
+
+
+def getTitle(parent, setID, plottingData, dateMode):
+    """
+    :param setID: (netID, statID, locID)
+    :param plottingData: a ditionary including:
+        { gaps: [(t1,t2),(t1,t2),...]   (in epoch time)
+          channels:[cha: {netID, statID, locID, chanID, times, data,
+                          startTmEpoch, endTmEpoch}
+          earliestUTC: the earliest time of all channels
+          latestUTC: the latest time of all channels
+    :return: title for the plot
+    """
+    diff = plottingData['latestUTC'] - plottingData['earliestUTC']
+    hours = diff/3600
+    return ("Station: %s  %s  to  %s  (%s)" %
+            (setID[1],
+             formatTime(parent, plottingData['earliestUTC'],
+                        dateMode, "HH:MM:SS"),
+             formatTime(parent, plottingData['latestUTC'],
+                        dateMode, "HH:MM:SS"),
+             round(hours, 2))
+            )
+
+
+def getGaps(gaps, gapMin):
+    """
+    :param gaps: list of gaps
+    :param gapMin: minimum of gaps count in minutes
+    return list of gaps of which gaps smaller than gapMin have been removed
+    """
+    gapMinSec = gapMin * 60
+    return [g for g in gaps if (g[1] - g[0]) >= gapMinSec]
+
+
+def getTimeTicks(earliest, latest, dateFormat, labelTotal):
+    """
+    split time range into parts to use for tick labels
+    Ex: getTimeTicks(1595542860.0, 1595607663.91, 'YYYY-MM-DD', 3)
+    :param earliest: earliest epoch time
+    :param latest: latest epoch time
+    :param dateFormat: YYYY:DOY, YYYY-MM-DD or YYYYMMMDD
+    :param labelTotal: number of time label to be displayed,
+     others will show as ticks oly
+    :return:
+        times: list of times
+        majorTimes: list of time of lables to be displayed
+        majorTimelabels: list of labels displayed
+    """
+    timeRange = latest - earliest
+    if timeRange >= 2592000.0:
+        mode = "DD"
+        # Time of the nearest midnight before the earliest time.
+        time = (earliest // 86400.0) * 86400
+        interval = 864000.0
+    elif timeRange >= 864000.0:
+        mode = "D"
+        time = (earliest // 86400.0) * 86400
+        interval = 86400.0
+    elif timeRange >= 3600.0:
+        mode = "H"
+        # Nearest hour.
+        time = (earliest // 3600.0) * 3600
+        interval = 3600.0
+    elif timeRange >= 60.0:
+        mode = "M"
+        # Nearest minute.
+        time = (earliest // 60.0) * 60
+        interval = 60.0
+    else:
+        mode = "S"
+        time = (earliest // 1)
+        interval = 1.0
+    times = []
+    timeLabels = []
+    time += interval
+    while time < latest:
+        times.append(time)
+        timeLabel = formatTime(None, time, dateFormat, 'HH:MM:SS')
+        if mode == "DD" or mode == "D":
+            timeLabel = timeLabel[:-9]
+        elif mode == "H":
+            timeLabel = timeLabel[:-3]
+        elif mode == "M" or mode == "S":
+            timeLabel = timeLabel
+        timeLabels.append(timeLabel)
+        time += interval
+
+    ln = len(timeLabels)
+    d = math.ceil(len(timeLabels) / labelTotal)
+    majorTimes = [times[i] for i in range(ln) if i % d == 0]
+    majorTimelabels = [timeLabels[i] for i in range(ln) if i % d == 0]
+    return times, majorTimes, majorTimelabels
+
+
+def getUnitBitweight(chanDB, bitweightOpt):
+    """
+    :param chanDB: channel's info got from database
+    :param bitweightOpt: qpeek's OPTBitWeightRVar
+        Commands: Show Seismic Data In Counts - None
+                  Use Q330 Low Gain - Low
+                  Use Q330 High Gain - High
+    :return: unit with fixed point decimal and bitweight if applicable
+    """
+    plotType = chanDB['plotType']
+    # Not all channels use/have this field.
+    unit = chanDB['unit']
+    try:
+        fixPoint = chanDB['fixPoint']
+    except Exception:
+        fixPoint = 0
+    unitBitweight = ''
+    if plotType in ['linesDots', 'linesSRate', 'linesMasspos']:
+        if fixPoint == 0:
+            unitBitweight = "{}%s" % unit
+        else:
+            unitBitweight = "{:.%sf}%s" % (fixPoint, unit)
+    if conf['seisRE'].match(chanDB['channel']):
+        if fixPoint != 0:
+            unitBitweight = "{:.%sf}%s" % (fixPoint, unit)
+        else:
+            if bitweightOpt in ["low", "high"]:
+                unitBitweight = "{}V"
+            else:
+                unitBitweight = "{}%s" % unit
+    return unitBitweight
diff --git a/sohstationviewer/controller/processing.py b/sohstationviewer/controller/processing.py
old mode 100755
new mode 100644
index 14281dbd7807c78f3a24c491c124dd0208480756..87173891420575428f2adc25bb60c646522f27fc
--- a/sohstationviewer/controller/processing.py
+++ b/sohstationviewer/controller/processing.py
@@ -1,17 +1,32 @@
-from sohstationviewer.model.mseed_text import MSeed_Text
+import os
+import json
+import re
+from obspy.core import read as read_ms
+from obspy.io.reftek.core import Reftek130, Reftek130Exception
 
+from sohstationviewer.model.mseed.mseed import MSeed
+from sohstationviewer.model.reftek.reftek import RT130
+from sohstationviewer.database.extractData import signatureChannels
+from sohstationviewer.controller.util import validateFile, displayTrackingInfo
 
-def loadData(parent, listOfDir, reqInfoChans):
+
+def loadData(dataType, parent, listOfDir, reqInfoChans, reqDSs):
     """
     Go through root dir and read all files in that dir and its subdirs
     """
     dataObject = None
     for d in listOfDir:
         if dataObject is None:
-            # dataObject = Reftek.Reftek(parent, d)
-            # if dataObject.hasData():
-            #    continue
-            dataObject = MSeed_Text(parent, d, reqInfoChans)
+            if dataType == 'RT130':
+                dataObject = RT130(parent, d, reqInfoChans, reqDSs=reqDSs)
+            else:
+                try:
+                    dataObject = MSeed(parent, d, reqInfoChans)
+                except Exception as e:
+                    msg = f"Dir {d} can't be read due to error: {str(e)}"
+                    displayTrackingInfo(parent, msg, "Warning")
+                    pass
+
             if dataObject.hasData():
                 continue
             # If no data can be read from the first dir, throw exception
@@ -20,3 +35,89 @@ def loadData(parent, listOfDir, reqInfoChans):
             dataObject.readDir(d)
 
     return dataObject.plottingData
+
+
+def readChannels(parent, listOfDir):
+    """
+    Scan available channels for channel prefer dialog
+    """
+    dataObject = None
+    for d in listOfDir:
+        if dataObject is None:
+            # dataObject = Reftek.Reftek(parent, d)
+            # if dataObject.hasData():
+            #     continue
+            dataObject = MSeed(parent, d, readChanOnly=True)
+            if len(dataObject.channels) == 0:
+                # If no data can be read from the first dir, throw exception
+                raise Exception("No data can be read from ", d)
+        else:
+            dataObject.readDir(d, readChanOnly=True)
+    return dataObject.channels
+
+
+def detectDataType(parent, listOfDir):
+    """
+    For each dir in listOfDir,  use getDataTypeFromFile to identify the type
+    of data for that dir if find a signature channel
+    return:
+        + None if there are more than one types of data detected
+        + dataType found, Unknown data maybe return
+    """
+    # Looks like an import was missing -- I'm guessing this wasn't
+    # running before the reftek stuff was put in??
+    # sign_chan_dataType_dict = extractData.signatureChannels()
+    sign_chan_dataType_dict = signatureChannels()
+    dirDataTypeDict = {}
+    for d in listOfDir:
+        dataType = "Unknown"
+        for path, subdirs, files in os.walk(d):
+            for fileName in files:
+                if not validateFile(path, fileName):
+                    continue
+                ret = getDataTypeFromFile(os.path.join(path, fileName),
+                                          sign_chan_dataType_dict)
+                if ret is not None:
+                    dataType = ret
+                    break
+            if dataType != "Unknown":
+                break
+        dirDataTypeDict[d] = dataType
+    dataTypeList = {d for d in dirDataTypeDict.values()}
+    if len(dataTypeList) > 1:
+        dirDataTypeStr = json.dumps(dirDataTypeDict)
+        dirDataTypeStr = re.sub(r'\{|\}|"', '', dirDataTypeStr)
+        dirDataTypeStr = re.sub(r', ', '\n', dirDataTypeStr)
+        msg = (f"There are more than one types of data detected:\n"
+               f"{dirDataTypeStr}\n\n"
+               f"Please have only data that related to each other.")
+        displayTrackingInfo(parent, msg, "error")
+        return
+    # elif list(dirDataTypeDict.values())[0] == "Can't be identified.":
+    #     msg = (f"There are no known data detected.\n"
+    #            f"Please select different folder(s).")
+    #     displayTrackingInfo(parent, msg, "error")
+    #     return
+    return list(dirDataTypeDict.values())[0]
+
+
+def getDataTypeFromFile(filePath, sign_chan_dataType_dict):
+    stream = None
+    try:
+        stream = read_ms(os.path.join(filePath))
+    except TypeError:
+        return
+    except Reftek130Exception:
+        pass
+
+    if not stream:
+        try:
+            Reftek130.from_file(os.path.join(filePath))
+        except (TypeError, Reftek130Exception):
+            return
+        return 'RT130'
+
+    for trace in stream:
+        chan = trace.stats['channel']
+        if chan in sign_chan_dataType_dict.keys():
+            return sign_chan_dataType_dict[chan]
diff --git a/sohstationviewer/controller/util.py b/sohstationviewer/controller/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..76e632bd63641f6e962c9ea31581e90cdc22f3fc
--- /dev/null
+++ b/sohstationviewer/controller/util.py
@@ -0,0 +1,123 @@
+import os
+import re
+from datetime import datetime
+from obspy import UTCDateTime
+
+
+def validateFile(path, fileName):
+    if fileName.strip() == '.DS_Store' or fileName.startswith('._'):
+        # skip mac's info file
+        return False
+
+    if not os.path.isfile(os.path.join(path, fileName)):
+        return False
+    return True
+
+
+def displayTrackingInfo(parent, text, type='info'):
+    if parent is None:
+        print(f"{type}: {text}")
+        return
+
+    msg = {'text': text}
+    if type == 'error':
+        msg['color'] = 'white'
+        msg['bgcolor'] = '#e46269'
+    elif type == 'warning':
+        msg['color'] = '#ffd966'
+        msg['bgcolor'] = 'orange'
+    else:
+        msg['color'] = 'blue'
+        msg['bgcolor'] = 'white'
+    htmlText = """<body>
+        <div style='color:%(color)s; background-color:%(bgcolor)s'>
+            %(text)s
+        </div>
+        </body>"""
+    parent.trackingInfoTextBrowser.setHtml(htmlText % msg)
+    parent.update()
+
+
+def getTime6(timeStr):
+    """
+    Get time from 6 parts string.Ex: 01:251:09:41:35:656/2001:251:09:41:35:656
+    """
+    year = timeStr.split(':')[0]
+    if len(year) == 2:
+        return getTime6_2y(timeStr)
+    else:
+        return getTime6_4y(timeStr)
+
+
+def getTime6_2y(timeStr):
+    """
+    Get time from 6 parts string.Ex: 01:251:09:41:35:656
+    """
+    # pad 0 so the last part has 6 digits to match with the format str
+    timeStr = timeStr.ljust(22, "0")
+    time = datetime.strptime(timeStr, "%y:%j:%H:%M:%S:%f")
+    utcTime = UTCDateTime(time)
+    return utcTime.timestamp, time.year
+
+
+def getTime6_4y(timeStr):
+    """
+    Get time from 6 parts string.Ex: 2001:251:09:41:35:656
+    """
+    # pad 0 so the last part has 6 digits to match with the format str
+    timeStr = timeStr.ljust(24, "0")
+    time = datetime.strptime(timeStr, "%Y:%j:%H:%M:%S:%f")
+    utcTime = UTCDateTime(time)
+    return utcTime.timestamp, time.year
+
+
+def getTime4(timeStr, trackingYear, yAdded):
+    """
+    Get time from 4 parts string. Ex: 253:19:41:42
+    """
+    if not yAdded:
+        # first day => move to next year
+        doy = timeStr.split(':')
+        if doy == 1:
+            trackingYear += 1
+            yAdded = True
+    timeStr = f'{str(trackingYear)}:{timeStr}'
+    time = datetime.strptime(timeStr, "%Y:%j:%H:%M:%S")
+    utcTime = UTCDateTime(time)
+    return utcTime.timestamp, time.year, yAdded
+
+
+def getVal(text):
+    """
+    return value part including +/-, remove str that follows
+    """
+    REVal = '^\+?\-?[0-9]+\.?[0-9]?'            # noqa: W605
+    return float(re.search(REVal, text).group())
+
+
+######################################
+# BEGIN: rtnPattern(In, Upper = False)
+# LIB:rtnPattern():2006.114 - Logpeek
+def rtnPattern(text, upper=False):
+    """
+    return format of the string with:
+     + 0 for digit
+     + a for lowercase
+     + A for upper case
+     + remain special character
+    """
+    rtn = ""
+    for c in text:
+        if c.isdigit():
+            rtn += "0"
+        elif c.isupper():
+            rtn += "A"
+        elif c.islower():
+            rtn += "a"
+        else:
+            rtn += c
+    # So the A/a chars will always be A, so the caller knows what to look for
+    if upper is True:
+        return rtn.upper()
+    return rtn
+# END: rtnPattern
diff --git a/sohstationviewer/database/extractData.py b/sohstationviewer/database/extractData.py
new file mode 100755
index 0000000000000000000000000000000000000000..215e563879c081ff221edf10769ffed942a3700a
--- /dev/null
+++ b/sohstationviewer/database/extractData.py
@@ -0,0 +1,73 @@
+from sohstationviewer.database.proccessDB import executeDB_dict
+from sohstationviewer.conf.dbSettings import conf
+
+
+# key is last char of chan
+SEIS_LABEL = {'1': 'NS', '2': 'EW',
+              'N': 'NS', 'E': 'EW', 'Z': 'V'}
+
+
+def getChanPlotInfo(orgChan, dataType):
+    """
+    Given chanID read from raw data file and detected dataType
+    Return plotting info from DB for that channel
+    """
+    chan = orgChan
+    if orgChan.startswith('EX'):
+        chan = 'EX?'
+    if orgChan.startswith('VM'):
+        chan = 'VM?'
+    if orgChan.startswith('MP'):
+        chan = 'MP?'
+    if orgChan.startswith('Event DS'):
+        chan = 'Event DS?'
+    if orgChan.startswith('DS'):
+        chan = 'DS?'
+    if orgChan.startswith('Disk Usage'):
+        chan = 'Disk Usage?'
+    if conf['seisRE'].match(chan):
+        chan = 'SEISMIC'
+
+    sql = ("SELECT channel, plotType, height, unit, linkedChan,"
+           " convertFactor, label, fixPoint, valueColors "
+           "FROM Channels as C, Parameters as P")
+    if dataType == 'Unknown':
+        sql = f"{sql} WHERE channel='{chan}' and C.param=P.param"
+    else:
+        sql = (f"{sql} WHERE channel='{chan}' and C.param=P.param"
+               f" and dataType='{dataType}'")
+    print("SQL:", sql)
+    chanInfo = executeDB_dict(sql)
+
+    if len(chanInfo) == 0:
+        chanInfo = executeDB_dict(
+            f"{sql} WHERE channel='DEFAULT' and C.param=P.param")
+    else:
+        if chanInfo[0]['channel'] == 'SEISMIC':
+            chanInfo[0]['label'] = SEIS_LABEL[orgChan[-1]]
+        chanInfo[0]['channel'] = orgChan
+
+    chanInfo[0]['label'] = (
+        '' if chanInfo[0]['label'] is None else chanInfo[0]['label'])
+    chanInfo[0]['unit'] = (
+        '' if chanInfo[0]['unit'] is None else chanInfo[0]['unit'])
+    chanInfo[0]['fixPoint'] = (
+        0 if chanInfo[0]['fixPoint'] is None else chanInfo[0]['fixPoint'])
+    if chanInfo[0]['label'].strip() == '':
+        chanInfo[0]['label'] = chanInfo[0]['channel']
+    else:
+        chanInfo[0]['label'] = '-'.join([chanInfo[0]['channel'],
+                                         chanInfo[0]['label']])
+    return chanInfo[0]
+
+
+def signatureChannels():
+    """
+    return the dict {channel: dataType} in which channel is unique for dataType
+    """
+    sql = ("SELECT channel, dataType FROM Channels where channel in"
+           "(SELECT channel FROM Channels GROUP BY channel"
+           " HAVING COUNT(channel)=1)")
+    rows = executeDB_dict(sql)
+    sign_chan_dataType_dict = {r['channel']: r['dataType'] for r in rows}
+    return sign_chan_dataType_dict
diff --git a/sohstationviewer/database/proccessDB.py b/sohstationviewer/database/proccessDB.py
new file mode 100755
index 0000000000000000000000000000000000000000..19edc92afe84072cda2abb62a10e98a7d1f7ba7e
--- /dev/null
+++ b/sohstationviewer/database/proccessDB.py
@@ -0,0 +1,72 @@
+import sqlite3
+
+from sohstationviewer.conf.dbSettings import conf
+
+
+def executeDB(sql):
+    """
+    used for both execute and query data
+    """
+    conn = sqlite3.connect(conf['dbpath'])
+    cur = conn.cursor()
+    try:
+        cur.execute(sql)
+    except sqlite3.OperationalError as e:
+        print("sqlite3.OperationalError:%s\n\tSQL%s" % (str(e), sql))
+    rows = cur.fetchall()
+    conn.commit()       # used for execute: update/insert/delete
+    cur.close()
+    conn.close()
+    return rows
+
+
+def executeDB_dict(sql):
+    """
+    query data and return rows in dictionary with fields as keys
+    """
+    conn = sqlite3.connect(conf['dbpath'])
+    conn.row_factory = sqlite3.Row
+    cur = conn.cursor()
+    try:
+        cur.execute(sql)
+    except sqlite3.OperationalError as e:
+        print("sqlite3.OperationalError:%s\n\tSQL%s" % (str(e), sql))
+    rows = cur.fetchall()
+    cur.close()
+    conn.close()
+    return [dict(row) for row in rows]
+
+
+def trunc_addDB(table, sqls):
+    """
+    truncate table and refill with new data
+    """
+    try:
+        conn = sqlite3.connect(conf['dbpath'])
+        cur = conn.cursor()
+        cur.execute('BEGIN')
+        cur.execute(f'DELETE FROM {table}')
+        for sql in sqls:
+            print("sql:", sql)
+            cur.execute(sql)
+        cur.execute('COMMIT')
+    except sqlite3.Error as e:
+        try:
+            cur.execute('ROLLBACK')
+        except Exception:
+            pass
+        return (f'Cannot write to table {table}'
+                f'because of database error:\n{str(e)}')
+    except Exception as e:
+        try:
+            cur.execute('ROLLBACK')
+        except Exception:
+            pass
+        return (f'Cannot write to table {table} '
+                f'because of code error:\n{str(e)}')
+    try:
+        cur.close()
+        conn.close()
+    except Exception:
+        pass
+    return True
diff --git a/sohstationviewer/database/resources/channels.csv b/sohstationviewer/database/resources/channels.csv
new file mode 100755
index 0000000000000000000000000000000000000000..d06df94f3d5553564b8f9cb9befe7d93783e1654
--- /dev/null
+++ b/sohstationviewer/database/resources/channels.csv
@@ -0,0 +1,76 @@
+channel,label,param,convertFactor,unit,fixPoint,device
+Internal Clock Phase Error,,Clock phase error,1,us,,RT130
+SEISMIC,,Seismic data,1,,,RT130
+VM?,,Mass position,1,v,1,RT130
+Battery voltage,,Input power supply voltage,1,v,,RT130
+Temperature,,Internal temperature,1,C,,RT130
+Disk usage disk 1/disk 2 (display both disks),,Buffer usage,1,GB,,RT130
+Latitude,,GNSS latitude,1,deg,,RT130
+Longitude,,GNSS longitude,1,deg,,RT130
+Elevation,,GNSS number of satellites used,1,m,,RT130
+DSP-clock difference,,Time uncertainty,1,ms,,RT130
+GPS On/Off/Error,,GPS On/Off/Error,1,,,RT130
+Jerks/DSP sets,,Jerks/DSP sets,1,,,RT130
+GPS Lock/Unlock,,GPS Lock/Unlock,1,,,RT130
+GPS Clock Power,,GPS Clock Power,1,,,RT130
+Dump called/Dump complete,,Dump call,1,,,RT130
+Acquisition started/Acquisition stopped,,Acquisition status,1,,,RT130
+Reset/power up,,Reset/power up,1,,,RT130
+Error/warnings,,Error/warnings,1,,,RT130
+Discrepancies,,Discrepancies,1,,,RT130
+SOH data definitions,,SOH data definitions,1,,,RT130
+Net Up/down,,Net Up/down,1,,,RT130
+Event DS1/DS2 etc,,Event DS1/DS2 etc,1,,,RT130
+Mass re-center,,Mass re-center,1,,,RT130
+LCE,PhaseError,Clock phase error,1,us,,Q330
+LCQ,ClockQual,Clock Quality,1,%,,Q330
+SEISMIC,SeismicData,Seismic data,1,,,Q330
+VM?,MassPos,Mass position,0.1,V,1,Q330
+VCO,VoltControllOscil,Oscillator value,1,,,Q330
+VEA,AntAmps,GPS antenna current,1,mA,,Q330
+VEC,SysAmps,Input power supply current,1,mA,,Q330
+VEP,InputVolts,Input power supply voltage,0.15,V,2,Q330
+VKI,SysTemp,Internal temperature,1,C,,Q330
+VPB,BufferUsed,Buffer usage,0.1,%,,Q330
+ACE,,Logging,1,,,Q330
+OCF,,Logging,1,,,Q330
+LOG,,Logging,1,,,Q330
+LCE,PhaseError,Clock phase error,1,us,,Centaur
+LCQ,ClockQual,Clock Quality,1,%,,Centaur
+SEISMIC,SeismicData,Seismic data,1,,,Centaur
+VM?,MassPos,Mass position,0.001,V,1,Centaur
+VCO,VoltControllOsci,Oscillator value,1,,,Centaur
+VEC,AntAmps,Input power supply current,1,,,Centaur
+VEI,SupplyVolts,Input power supply voltage,0.001,V,,Centaur
+VDT,SysTemp,Internal temperature,0.001,C,,Centaur
+VPB,BufferUsed,Buffer usage,1,%,,Centaur
+EX?,ExtSOH,External SOH channels,1,uV,,Centaur
+GAN,AntStatus,GNSS antenna status,1,,,Centaur
+GEL,Elev,GNSS elevation,1,um,,Centaur
+GLA,Lat,GNSS latitude,1,microdegrees,,Centaur
+GLO,Lon,GNSS longitude,1,microdegrees,,Centaur
+GNS,SatsUsed,GNSS number of satellites used,1,,,Centaur
+GPL,PhaseLock,GNSS PPL status,1,,,Centaur
+GST,GPS Off/Un/Lk,GNSS status,1,,,Centaur
+LDO,BarometricPress,Barometric pressure,1,passcals,,Centaur
+LIO,OutHumidity,Outdoor relative humidity,1,%,,Centaur
+LKO,OutTemp,Outdoor temperature,1,mC,,Centaur
+LWD,WindDir,Wind direction,1,degrees,,Centaur
+LWS,WindSpeed,Wind speed,1,cm/s,,Centaur
+VCE,AbsPhaseErr,Clock phase error,1,us,,Pegasus
+VCQ,ClockQual,Clock Quality,1,%,,Pegasus
+SEISMIC,SeismicData,Seismic data,1,,,Pegasus
+VM?,MassPos,Mass position,0.000001,V,1,Pegasus
+VCO,VoltControllOsci,Oscillator value,1,,,Pegasus
+VE1,AntAmps,Input power supply current,0.001,mA,,Pegasus
+VEI,SupplyVolts,Input power supply voltage,0.000001,V,,Pegasus
+VDT,SysTemp,Internal temperature,0.000001,C,,Pegasus
+VAN,AntStatus,GNSS antenna status,1,,1,Pegasus
+VEL,Elev,GNSS elevation,1,,,Pegasus
+VLA,Lat,GNSS latitude,1,,,Pegasus
+VLO,Lon,GNSS longitude,1,,,Pegasus
+VNS,SatsUsed,GNSS number of satellites used,1,,,Pegasus
+VPL,PhaseLock,GNSS PPL status,1,,,Pegasus
+VST,GPS Off/Un/Lk,GNSS status,1,,,Pegasus
+VE2,SensorCurrent,Sensor current,1,,,Pegasus
+ATU,TimeUncert,Time uncertainty,1,,,Pegasus
\ No newline at end of file
diff --git a/sohstationviewer/database/resources/dataTypes.csv b/sohstationviewer/database/resources/dataTypes.csv
new file mode 100755
index 0000000000000000000000000000000000000000..bb329e5423457f87ed553aa2e775c238361d9565
--- /dev/null
+++ b/sohstationviewer/database/resources/dataTypes.csv
@@ -0,0 +1,5 @@
+dataType
+RT130
+Q330
+Centaur
+Pegasus
\ No newline at end of file
diff --git a/sohstationviewer/database/resources/database_note.txt b/sohstationviewer/database/resources/database_note.txt
new file mode 100755
index 0000000000000000000000000000000000000000..c773ef96f516ee7193a74234785d294240cc69d1
--- /dev/null
+++ b/sohstationviewer/database/resources/database_note.txt
@@ -0,0 +1,61 @@
+SQLITE: https://www.tutorialspoint.com/sqlite/
+controller field$ sqlite3 soh.db
+sqlite> .databases
+main: /Users/field/Documents/GIT/sohstationviewer/sohstationviewer/controller/soh.db r/w
+sqlite> create table test(
+   ...> col1   text,
+   ...> col2   int);
+sqlite> .tables
+sqlite> insert into test values ('lan', 43);
+sqlite> select * from test;
+sqlite> quit.
+
+PYTHON: https://www.datacamp.com/community/tutorials/sqlite-in-python
+GUI for sqllite:
+	+ download: https://nightlies.sqlitebrowser.org/osx/2020-01/  
+	+ Start: open DB Browser for SQLite in Applications
+
+
+Files - Import - Table from CSV file
+Right-click on table choose modify table to add Primary key, Foreign key
+Add foreign key: roll all the way to the right, double click on Foreign Key
+column, the editing boxes will show up
+
+
+import sqlite3
+conn = sqlite3.connect("soh.db")
+cur = conn.cursor()
+cur.execute('SELECT * from test')
+# print("one row:", cur.fetchone())
+print("all rows:", cur.fetchall())
+
+CREATE TABLE "DataTypes" (
+	"dataType"	TEXT NOT NULL,
+	PRIMARY KEY("dataType")
+);
+CREATE TABLE "Parameters" (
+	"param"	TEXT NOT NULL,
+	"plotType"	TEXT,
+	"height"	INTEGER,
+	PRIMARY KEY("param")
+);
+CREATE TABLE "Channels" (
+	"channel"	TEXT,
+	"param"	TEXT,
+	"convertFactor"	INTEGER,
+	"unit"	TEXT,
+	"dataType"	TEXT,
+	FOREIGN KEY("param") REFERENCES Parameters(param),
+	FOREIGN KEY("dataType") REFERENCES DataTypes(dataType),
+	PRIMARY KEY("channel","dataType")
+);
+CREATE TABLE "ChannelPrefer" (
+	"name"	TEXT NOT NULL,
+	"IDs"	TEXT NOT NULL,
+	"dataType"	TEXT,
+	"current"	INTEGER,
+	PRIMARY KEY("name")
+);
+
+INSERT INTO Channels
+SELECT * FROM chan where dataType="RT130";
\ No newline at end of file
diff --git a/sohstationviewer/database/resources/parameters.csv b/sohstationviewer/database/resources/parameters.csv
new file mode 100755
index 0000000000000000000000000000000000000000..f53a904c3c6b0a4267afd83310d66bf620489c86
--- /dev/null
+++ b/sohstationviewer/database/resources/parameters.csv
@@ -0,0 +1,40 @@
+param,plotType,height
+Clock phase error,lines,3
+Clock Quality,lines,3
+Seismic data,linesSRate,4
+Mass position,linesMasspos,4
+Oscillator value,lines,2
+GPS antenna current,lines,2
+Input power supply current,lines,2
+Input power supply voltage,lines,2
+Internal temperature,lines,2
+Buffer usage,lines,2
+External SOH channels,,0
+GNSS antenna status,dotsRM,2
+GNSS elevation,lines,2
+GNSS latitude,lines,2
+GNSS longitude,lines,2
+GNSS number of satellites used,lines,2
+GNSS PPL status,dotsRYGM,2
+GNSS status,dotsRM,2
+Barometric pressure,,0
+Outdoor relative humdity,,0
+Outdoor temperature,,0
+Wind direction,,0
+Wind speed,,0
+Sensor current,lines,2
+Time uncertainty,lines,2
+GPS On/Off/Error,dotZeroOneRG,2
+Jerks/DSP sets,dotZeroOneRY,2
+GPS Lock/Unlock,lines,2
+GPS Clock Power,dotsGCYRM,2
+Dump call,dotZeroOneRG,2
+Acquisition status,dotZeroOneRG,2
+Reset/power up,dotZeroOneWC,2
+Error/warnings,dotZeroOneRY,2
+Discrepancies,dotsRYGM,2
+SOH data definitions,dotZeroOneWC,2
+Net Up/down,dotZeroOneRG,2
+Event DS1/DS2 etc,linesDots,2
+Mass re-center,dotForTime,2
+Logging,,0
\ No newline at end of file
diff --git a/sohstationviewer/database/soh.db b/sohstationviewer/database/soh.db
new file mode 100755
index 0000000000000000000000000000000000000000..5896e88fc9b570cf28bdca5cc1510bc76cde45f8
Binary files /dev/null and b/sohstationviewer/database/soh.db differ
diff --git a/sohstationviewer/model/core/dataType.py b/sohstationviewer/model/core/dataType.py
deleted file mode 100755
index a7be3da3fef32e50679049ecbf9f6039db05fa00..0000000000000000000000000000000000000000
--- a/sohstationviewer/model/core/dataType.py
+++ /dev/null
@@ -1,64 +0,0 @@
-import os
-
-
-class WrongDataTypeError(Exception):
-    def __init__(self, *args, **kwargs):
-        self.args = (args, kwargs)
-
-
-class DataType:
-    def __init__(self, parent, dir, reqInfoChans=[]):
-        self.parent = parent
-        self.dir = dir
-        self.reqInfoChans = reqInfoChans
-        self.noneReqChans = set()
-        self.curNetStatLoc = ('_', '_', '_')
-        self.processingLog = []     # [(message, type)]
-        self.logData = {}
-        self.plottingData = {}
-        self.streams = {}
-
-        self.readDir(dir)
-
-    def readFile(self, pathToFile):
-        pass
-
-    def combineData(self):
-        """
-        Merge channels in each stream
-        """
-        pass
-
-    def readDir(self, dir):
-        count = 0
-        for path, subdirs, files in os.walk(dir):
-
-            for fileName in files:
-                if fileName.startswith('._'):
-                    # skip mac's info file
-                    continue
-                if not os.path.isfile(os.path.join(path, fileName)):
-                    continue
-                self.readFile(path, fileName)
-                count += 1
-                if count % 50 == 0:
-                    self.displayTrackingInfo("Reading file %s" % count,
-                                             'info')
-        self.combineData()
-
-    def hasData(self):
-        if len(self.logData) == 0 and len(self.plottingData) == 0:
-            return False
-        return True
-
-    def displayTrackingInfo(self, text, type):
-        """
-        :param text: text to be displayed
-        :param type: info/warning/error
-        """
-        print("displayTrackingInfo:", text)
-        self.parent.displayTrackingInfo(text, type)
-
-    def trackInfo(self, text, type):
-        self.displayTrackingInfo(text, type)
-        self.processingLog.append((text, type))
diff --git a/sohstationviewer/model/dataType.py b/sohstationviewer/model/dataType.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5c37ecc626a6b72ab296ed405e9e8827acc5d5e
--- /dev/null
+++ b/sohstationviewer/model/dataType.py
@@ -0,0 +1,102 @@
+import os
+
+from sohstationviewer.controller.util import validateFile, displayTrackingInfo
+from sohstationviewer.conf.dbSettings import conf
+
+
+class WrongDataTypeError(Exception):
+    def __init__(self, *args, **kwargs):
+        self.args = (args, kwargs)
+
+
+class DataType():
+    def __init__(self, parent, dir, reqInfoChans=[],
+                 readChanOnly=False, reqDSs=[]):
+        print("reqDSs:", reqDSs)
+        self.parentGUI = parent
+        self.dir = dir
+        self.reqInfoChans = reqInfoChans
+        self.noneReqChans = set()
+        self.curKey = ('_', '_', '_')
+        self.processingLog = []     # [(message, type)]
+        self.logData = {}
+        self.plottingData = {}
+        self.streams = {}
+        self.channels = set()
+        self.reqDSs = reqDSs
+        self.readDir(dir, readChanOnly)
+
+    def checkChan(self, chanID):
+        if self.reqInfoChans == []:
+            return True
+        if chanID in self.reqInfoChans:
+            return True
+        if 'EX?' in self.reqInfoChans and chanID.startswith('EX'):
+            if chanID[2] in ['1', '2', '3']:
+                return True
+        if 'VM?' in self.reqInfoChans and chanID.startswith('VM'):
+            if chanID[2] in ['0', '1', '2', '3', '4', '5', '6']:
+                return True
+        if 'SEISMIC' in self.reqInfoChans and conf['seisRE'].match(chanID):
+            return True
+        return False
+
+    def readFile(self, pathToFile):
+        pass
+
+    def combineData(self):
+        """
+        Merge channels in each stream
+        """
+        pass
+
+    def readDir(self, dir, readChanOnly=False):
+        self.readChanOnly = readChanOnly
+        count = 0
+        for path, subdirs, files in os.walk(dir):
+            for fileName in files:
+                if not validateFile(path, fileName):
+                    continue
+                self.readFile(path, fileName)
+                count += 1
+                if count % 50 == 0:
+                    displayTrackingInfo(
+                        self.parentGUI, "Reading file %s" % count)
+            if readChanOnly:
+                return
+        self.combineData()
+        # print("logs:", self.logData[('A195', 0, 19)].keys())
+        # print("logs:", self.logData[('A195', 0, 19)]['SH'])
+
+    def hasData(self):
+        if len(self.logData) == 0 and len(self.plottingData) == 0:
+            return False
+        return True
+
+    def trackInfo(self, text, type):
+        displayTrackingInfo(self.parent, text, type)
+        if type != 'info':
+            self.processingLog.append((text, type))
+
+    def readText(self, path, fileName):
+        """
+        Read log file and add to logData under channel TEXT
+        """
+        if self.readChanOnly and 'LOG' in self.channels:
+            return
+        with open(os.path.join(path, fileName), 'r') as file:
+            try:
+                content = file.read()
+            except UnicodeDecodeError:
+                self.trackInfo("Can't process file: %s" % fileName, 'error')
+                return
+            if self.readChanOnly:
+                self.channels.add('LOG')
+            logText = "\n\n** STATE OF HEALTH: %s\n" % fileName
+            logText += content
+            self.addLog('TEXT', logText)
+
+    def addLog(self, chan_pkt, logInfo):
+        if chan_pkt not in self.logData[self.curKey].keys():
+            self.logData[self.curKey][chan_pkt] = []
+        self.logData[self.curKey][chan_pkt].append(logInfo)
diff --git a/sohstationviewer/model/mseed/__init__.py b/sohstationviewer/model/mseed/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/sohstationviewer/model/mseed/blockettes_reader.py b/sohstationviewer/model/mseed/blockettes_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..a627fd3d4cccaad00c907a7982262313997d6385
--- /dev/null
+++ b/sohstationviewer/model/mseed/blockettes_reader.py
@@ -0,0 +1,127 @@
+import os
+from struct import unpack
+
+
+class ReadBlocketteError(Exception):
+    def __init__(self, errno, msg):
+        self.msg = msg
+
+
+def readASCII(path, fileName, byteorder):
+    """
+    test function
+    """
+    file = open(os.path.join(path, fileName), 'rb')
+    databytes = file.read()
+    file.close()
+    followingBlktsTotal = unpack('%s%s' % (byteorder, 'B'),
+                                 databytes[39:40])[0]
+    if followingBlktsTotal > 1:
+        nextBlktByteNo = 48 + 8  # header + blkt1000(SEED info)
+    else:
+        nextBlktByteNo = 0
+    logText = ""
+    while nextBlktByteNo != 0:
+        nextBlktByteNo, info = readNextBlkt(
+            nextBlktByteNo, databytes, byteorder)
+        logText += info
+
+    print("INFO:\n", logText)
+
+
+# ________________ based on mseedpeek.libtrace.Mseed.blkXXX________________
+# https://docs.python.org/3/library/struct.html
+# SEED Manual V2.4 - Chapter 8 - Data Record
+def readNextBlkt(bNo, databytes, byteorder):
+    """
+    :param bNo: next blockette Byte Number
+    :param databytes: file's data in byte
+    :param byteorder: big/little endian
+    :param key: (net, stat, loc)
+    :param chanID: channel
+    :return:
+        nextBNo: byte number of next blockette
+        info: info read from this blockette
+    """
+    blocketteType = unpack('%s%s' % (byteorder, 'H'),
+                           databytes[bNo:bNo + 2])[0]
+    nextBNo = unpack('%s%s' % (byteorder, 'H'),
+                     databytes[bNo + 2:bNo + 4])[0]
+
+    try:
+        # readBlkt will skip first 4 bytes (HH) as they are already read
+        info = eval("readBlkt%s(%s, %s, '%s')" %
+                    (blocketteType, bNo, databytes, byteorder))
+    except NameError:
+        raise ReadBlocketteError(
+            f"Function to read blockette {blocketteType} isn't implemented "
+            f"yet. Implementer needs to write and add the function to the "
+            f"dict self.readBlkt in Mseed.__init__()")
+    return nextBNo, info
+
+
+def readBlkt500(bNo, databytes, byteorder):
+    logText = "\nVCO Correction: %s" % unpack(
+        '%s%s' % (byteorder, 'f'), databytes[bNo + 4:bNo + 8])[0]
+    t = {}
+    (t['year'], t['doy'], t['hour'], t['min'], t['sec'],
+     junk, t['micro']) = unpack(
+        '%s%s' % (byteorder, 'HHBBBBH'), databytes[bNo + 8:bNo + 18])
+    logText += ("\nTime of exception: %(year)s:%(doy)s:%(hour)s:%(min)s:"
+                "%(sec)s:%(micro)s" % t)
+    logText += "\nMicro sec: %s" % unpack(
+        '%s%s' % (byteorder, 'B'), databytes[bNo + 18:bNo + 19])[0]
+    logText += "\nReception Quality: %s" % unpack(
+        '%s%s' % (byteorder, 'B'), databytes[bNo + 19:bNo + 20])[0]
+    logText += "\nException Count: %s" % unpack(
+        '%s%s' % (byteorder, 'I'), databytes[bNo + 20:bNo + 24])[0]
+    logText += "\nException Type: %s" % unpack(
+        '%s%s' % (byteorder, '16s'), databytes[bNo + 24:bNo + 40])[0].strip()
+    logText += "\nClock Model: %s" % unpack(
+        '%s%s' % (byteorder, '32s'), databytes[bNo + 40:bNo + 72])[0].strip()
+    logText += "\nClock Status: %s" % unpack(
+        '%s%s' % (byteorder, '128s'), databytes[bNo + 72:bNo + 200])[0].strip()
+    return logText
+
+
+def readBlkt2000(bNo, databytes, byteorder):
+    blktLen = unpack(
+        '%s%s' % (byteorder, 'H'), databytes[bNo + 4:bNo + 6])[0]
+    logText = "\nTotal Blockette length: %s bytes" % blktLen
+    logText += "\nOffset to Opaque Data: %s" % unpack(
+        '%s%s' % (byteorder, 'H'), databytes[bNo + 6:bNo + 8])[0]
+    logText += "\nRecord number: %s" % unpack(
+        '%s%s' % (byteorder, 'I'), databytes[bNo + 8:bNo + 12])[0]
+    logText += "\nData Word order: %s" % unpack(
+        '%s%s' % (byteorder, 'B'), databytes[bNo + 12:bNo + 13])[0]
+    logText += "\nOpaque Data flags: %s" % unpack(
+        '%s%s' % (byteorder, 'B'), databytes[bNo + 13:bNo + 14])[0]
+    opaqueHeaderTotal = unpack(
+        '%s%s' % (byteorder, 'B'), databytes[bNo + 14:bNo + 15])[0]
+    logText += "\nNumber of Opaque Header fields: %s" % opaqueHeaderTotal
+    n = bNo + 15
+    c = 0
+    headerLen = 0
+    for i in range(opaqueHeaderTotal):
+        hfield = ''
+        hchar = ''
+        while hchar != '~':
+            hfield += hchar
+            hchar = unpack(
+                '%s%s' % (byteorder, '1s'), databytes[n + c:n + c + 1])[
+                0].decode()
+            headerLen = c + 1
+            c += 1
+        logText += "\nOpaque Header %s: %s" % (i, hfield)
+
+    opaqueDataLength = blktLen - 15 - headerLen
+    logText += "\nOpaque Data: %s" % unpack(
+        '%s%s' % (byteorder, '%ss' % opaqueDataLength),
+        databytes[n + headerLen: n + headerLen + opaqueDataLength])
+
+    return logText
+
+
+if __name__ == '__main__':
+    readASCII(
+        "/Volumes/UNTITLED/fromCloud/qpeek/5244.sdr", "DT0001__.OCF", ">")
diff --git a/sohstationviewer/model/mseed_text.py b/sohstationviewer/model/mseed/mseed.py
old mode 100755
new mode 100644
similarity index 71%
rename from sohstationviewer/model/mseed_text.py
rename to sohstationviewer/model/mseed/mseed.py
index 7218004d9ef1bccbc8cca68faaf3e5d8d456b248..41fc063e3d25bfb4aa8196a2606a1405a9c8559e
--- a/sohstationviewer/model/mseed_text.py
+++ b/sohstationviewer/model/mseed/mseed.py
@@ -4,13 +4,16 @@ from obspy.core import Stream, read as read_ms
 from obspy import UTCDateTime
 from struct import unpack
 
-from sohstationviewer.model.core.dataType import DataType
+from sohstationviewer.model.dataType import DataType
 
+from sohstationviewer.model.mseed.blockettes_reader import (
+    readNextBlkt, ReadBlocketteError)
+from sohstationviewer.conf import constants
 
-class MSeed_Text(DataType):
-    def __init__(self, *kwarg):
-        self.readBlkt = {500: self.readBlkt500}
-        super().__init__(*kwarg)
+
+class MSeed(DataType):
+    def __init__(self, parent, dir, reqInfoChans=[], readChanOnly=False):
+        super().__init__(parent, dir, reqInfoChans, readChanOnly)
 
     def readFile(self, path, fileName):
         if not self.readMiniseed(path, fileName):
@@ -36,8 +39,8 @@ class MSeed_Text(DataType):
                     minStarttime = startTm
                 if endTm > maxEndtime:
                     maxEndtime = endTm
-            self.plottingData[k]['earliestUTC'] = minStarttime
-            self.plottingData[k]['latestUTC'] = maxEndtime
+            self.plottingData[k]['earliestUTC'] = minStarttime.timestamp
+            self.plottingData[k]['latestUTC'] = maxEndtime.timestamp
 
     def addLog(self, chan, logText):
         if chan not in self.logData[self.curNetStatLoc].keys():
@@ -48,12 +51,16 @@ class MSeed_Text(DataType):
         """
         Read log file and add to logData under channel TEXT
         """
+        if self.readChanOnly and 'LOG' in self.channels:
+            return
         with open(os.path.join(path, fileName), 'r') as file:
             try:
                 content = file.read()
             except UnicodeDecodeError:
                 self.trackInfo("Can't process file: %s" % fileName, 'error')
                 return
+            if self.readChanOnly:
+                self.channels.add('LOG')
             logText = "\n\n** STATE OF HEALTH: %s\n" % fileName
             logText += content
             self.addLog('TEXT', logText)
@@ -67,11 +74,13 @@ class MSeed_Text(DataType):
             stream = read_ms(os.path.join(path, fileName))
         except TypeError:
             return False
-
         file = None
         for trace in stream:
             chanID = trace.stats['channel']
-            if chanID not in self.reqInfoChans:
+            if self.readChanOnly:
+                self.channels.add(chanID)
+                continue
+            if not self.checkChan(chanID):
                 self.noneReqChans.add(chanID)
                 continue
             netID = trace.stats['network']
@@ -79,7 +88,7 @@ class MSeed_Text(DataType):
             locID = trace.stats['location']
             self.curNetStatLoc = k = (netID, statID, locID)
             if trace.stats.mseed['encoding'] == 'ASCII':
-                file = self.readASCII(path, fileName, file, trace, k)
+                file = self.readASCII(path, fileName, file, trace, k, chanID)
             else:
                 if k not in self.streams.keys():
                     self.streams[k] = Stream()
@@ -88,58 +97,6 @@ class MSeed_Text(DataType):
             file.close()
         return True
 
-    def readASCII(self, path, fileName, file, trace, k):
-        byteorder = trace.stats.mseed['byteorder']
-        h = trace.stats
-        logText = "\n\n**** STATE OF HEALTH: "
-        logText += ("From:%s  To:%s\n" % (h.starttime, h.endtime))
-        textFromData = trace.data.tobytes().decode()
-        if textFromData != '':
-            logText += textFromData
-        else:
-            recLen = h.mseed['record_length']
-            if file is None:
-                file = open(os.path.join(path, fileName), 'rb')
-            databytes = file.read(recLen)
-            followingBlktNum = unpack('%s%s' % (byteorder, 'B'),
-                                      databytes[39:40])[0]
-            if followingBlktNum > 1:
-                # skip blockette 1000
-                nextBlocketteType = unpack('%s%s' % (byteorder, 'H'),
-                                           databytes[56:58])[0]
-                try:
-                    logText += self.readBlkt[nextBlocketteType](databytes,
-                                                                byteorder)
-                except KeyError:
-                    msg = ("Function to read blockette %s isn't implemented "
-                           "yet. Implementer needs to write and add the "
-                           "function to the dict self.readBlkt in Mseed_Text."
-                           "__init__()" % nextBlocketteType)
-                    self.trackInfo(msg, 'error')
-                    return file
-        if k not in self.logData:
-            self.logData[k] = {}
-        if h.channel not in self.logData[k]:
-            self.logData[k][h.channel] = []
-        self.logData[k][h.channel].append(logText)
-        return file
-
-    def readBlkt500(self, databytes, byteorder):
-        t = {}
-        (vcocorr, t['year'], t['doy'], t['hour'], t['min'], t['sec'],
-         junk, t['micro'], t['micro2'], qual, cnt, type, mod, stat
-         ) = unpack('%s%s' % (byteorder, 'fHHBBBBHBBI16s32s128s'),
-                    databytes[60:256])
-        logText = "\nVCO Correction: %s" % vcocorr
-        logText += ("\nTime of exception: %(year)s:%(doy)s:%(hour)s:%(min)s:"
-                    "%(sec)s:%(micro)s:%(micro2)s" % t)
-        logText += "\nReception Quality: %s" % qual
-        logText += "\nException Count: %s" % cnt
-        logText += "\nException Type: %s" % type.strip()
-        logText += "\nClock Model: %s" % mod.strip()
-        logText += "\nClock Status: %s" % stat.strip()
-        return logText
-
     def readTrace(self, trace, channels):
         chan = {}
         chan['netID'] = trace.stats['network']
@@ -188,12 +145,12 @@ class MSeed_Text(DataType):
             msg = "Number of Gaps for different channel are not equal.\n"
             for k in gaps_dict.keys():
                 msg += "%s: %s\n" % (k, len(gaps_dict[k]))
-            self.displayTrackingInfo(msg, 'error')
+            self.trackInfo(msg, 'error')
             return []
         squashedGaps = []
         for i in range(firstLen):
             maxEndtime = 0
-            minStarttime = 1E100
+            minStarttime = constants.HIGHEST_INT
             for val in gaps_dict.values():
                 if val[i][0] < minStarttime:
                     minStarttime = val[i][0]
@@ -201,3 +158,37 @@ class MSeed_Text(DataType):
                     maxEndtime = val[i][1]
             squashedGaps.append((minStarttime, maxEndtime))
         return squashedGaps
+
+    def readASCII(self, path, fileName, file, trace, key, chanID):
+        byteorder = trace.stats.mseed['byteorder']
+        h = trace.stats
+        logText = "\n\n**** STATE OF HEALTH: "
+        logText += ("From:%s  To:%s\n" % (h.starttime, h.endtime))
+        textFromData = trace.data.tobytes().decode()
+        if textFromData != '':
+            logText += textFromData
+        else:
+            recLen = h.mseed['record_length']
+            if file is None:
+                file = open(os.path.join(path, fileName), 'rb')
+            databytes = file.read(recLen)
+            followingBlktsTotal = unpack('%s%s' % (byteorder, 'B'),
+                                         databytes[39:40])[0]
+            if followingBlktsTotal > 1:
+                nextBlktByteNo = 48 + 8  # header + blkt1000(SEED info)
+            else:
+                nextBlktByteNo = 0
+            while nextBlktByteNo != 0:
+                try:
+                    nextBlktByteNo, info = readNextBlkt(
+                        nextBlktByteNo, databytes, byteorder)
+                    logText += info
+                except ReadBlocketteError as e:
+                    self.trackInfo(f"{key} - {chanID}: {e.msg}", 'error')
+
+        if key not in self.logData:
+            self.logData[key] = {}
+        if h.channel not in self.logData[key]:
+            self.logData[key][h.channel] = []
+        self.logData[key][h.channel].append(logText)
+        return file
diff --git a/sohstationviewer/model/reftek/__init__.py b/sohstationviewer/model/reftek/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/sohstationviewer/model/reftek/from_rt2ms/__init__.py b/sohstationviewer/model/reftek/from_rt2ms/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3158d957c5862b567d641bd24c72c6a1397464a
--- /dev/null
+++ b/sohstationviewer/model/reftek/from_rt2ms/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+"""Top-level package for from_rt2ms."""
+
+__author__ = """IRIS PASSCAL"""
+__email__ = 'software-support@passcal.nmt.edu'
+__version__ = '2021.238'
diff --git a/sohstationviewer/model/reftek/from_rt2ms/core.py b/sohstationviewer/model/reftek/from_rt2ms/core.py
new file mode 100644
index 0000000000000000000000000000000000000000..68f2931f2898761ddcfbc324f08ea2cf75c565a8
--- /dev/null
+++ b/sohstationviewer/model/reftek/from_rt2ms/core.py
@@ -0,0 +1,234 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Suggested updates to obspy.io.reftek.core:
+- modify section of the code that deals with upacking data
+  with '16' and '32' encodings to account for number of samples in each data
+  packet. Mass position channels data are '16' encoded.
+
+Maeva Pourpoint IRIS/PASSCAL
+"""
+
+
+import copy
+import obspy.io.reftek.core as obspy_rt130_core
+import warnings
+
+import numpy as np
+
+from obspy import Trace, Stream, UTCDateTime
+from obspy.core.util.obspy_types import ObsPyException
+from obspy.io.reftek.packet import _unpack_C0_C2_data
+from sohstationviewer.model.reftek.from_rt2ms.packet import EHPacket
+
+
+class Reftek130Exception(ObsPyException):
+    pass
+
+
+class Reftek130(obspy_rt130_core.Reftek130):
+
+    def to_stream(self, network="", location="", component_codes=None,
+                  headonly=False, verbose=False,
+                  sort_permuted_package_sequence=False):
+        """
+        :type headonly: bool
+        :param headonly: Determines whether or not to unpack the data or just
+            read the headers.
+        """
+        if verbose:
+            print(self)
+        if not len(self._data):
+            msg = "No packet data in Reftek130 object (file: {})"
+            raise Reftek130Exception(msg.format(self._filename))
+        self.check_packet_sequence_and_sort(sort_permuted_package_sequence)
+        self.check_packet_sequence_contiguous()
+        self.drop_not_implemented_packet_types()
+        if not len(self._data):
+            msg = ("No packet data left in Reftek130 object after dropping "
+                   "non-implemented packets (file: {})").format(self._filename)
+            raise Reftek130Exception(msg)
+        st = Stream()
+        for event_number in np.unique(self._data['event_number']):
+            data = self._data[self._data['event_number'] == event_number]
+            # we should have exactly one EH and one ET packet, truncated data
+            # sometimes misses the header or trailer packet.
+            eh_packets = data[data['packet_type'] == b"EH"]
+            et_packets = data[data['packet_type'] == b"ET"]
+            if len(eh_packets) == 0 and len(et_packets) == 0:
+                msg = ("Reftek data (file: {}) contain data packets without "
+                       "corresponding header or trailer packet."
+                       .format(self._filename))
+                raise Reftek130Exception(msg)
+            if len(eh_packets) > 1 or len(et_packets) > 1:
+                msg = ("Reftek data (file: {}) contain data packets with "
+                       "multiple corresponding header or trailer packets."
+                       .format(self._filename))
+                raise Reftek130Exception(msg)
+            if len(eh_packets) != 1:
+                msg = ("No event header (EH) packets in packet sequence. "
+                       "File ({}) might be truncated.".format(self._filename))
+                warnings.warn(msg)
+            if len(et_packets) != 1:
+                msg = ("No event trailer (ET) packets in packet sequence. "
+                       "File ({}) might be truncated.".format(self._filename))
+                warnings.warn(msg)
+            # use either the EH or ET packet, they have the same content (only
+            # trigger stop time is not in EH)
+            if len(eh_packets):
+                eh = EHPacket(eh_packets[0])
+            else:
+                eh = EHPacket(et_packets[0])
+            # only C0, C2, 16, 32 encodings supported right now
+            if eh.data_format == b"C0":
+                encoding = 'C0'
+            elif eh.data_format == b"C2":
+                encoding = 'C2'
+            elif eh.data_format == b"16":
+                encoding = '16'
+            elif eh.data_format == b"32":
+                encoding = '32'
+            else:
+                msg = ("Reftek data encoding '{}' not implemented yet. Please "
+                       "open an issue on GitHub and provide a small (< 50kb) "
+                       "test file.").format(eh.data_format)
+                raise NotImplementedError(msg)
+            header = {
+                "unit_id": self._data['unit_id'][0],
+                "experiment_number": self._data['experiment_number'][0],
+                "network": network,
+                "station": (eh.station_name +
+                            eh.station_name_extension).strip(),
+                "location": location, "sampling_rate": eh.sampling_rate,
+                "reftek130": eh._to_dict()}
+            delta = 1.0 / eh.sampling_rate
+            delta_nanoseconds = int(delta * 1e9)
+            inds_dt = data['packet_type'] == b"DT"
+            data_channels = np.unique(data[inds_dt]['channel_number'])
+            for channel_number in data_channels:
+                inds = data['channel_number'] == channel_number
+                # channel number of EH/ET packets also equals zero (one of the
+                # three unused bytes in the extended header of EH/ET packets)
+                inds &= data['packet_type'] == b"DT"
+                packets = data[inds]
+
+                # split into contiguous blocks, i.e. find gaps. packet sequence
+                # was sorted already..
+                endtimes = (
+                    packets[:-1]["time"] +
+                    packets[:-1]["number_of_samples"].astype(np.int64) *
+                    delta_nanoseconds)
+                # check if next starttime matches seamless to last chunk
+                # 1e-3 seconds == 1e6 nanoseconds is the smallest time
+                # difference reftek130 format can represent, so anything larger
+                # or equal means a gap/overlap.
+                time_diffs_milliseconds_abs = np.abs(
+                    packets[1:]["time"] - endtimes) / 1000000
+                gaps = time_diffs_milliseconds_abs >= 1
+                if np.any(gaps):
+                    gap_split_indices = np.nonzero(gaps)[0] + 1
+                    contiguous = np.array_split(packets, gap_split_indices)
+                else:
+                    contiguous = [packets]
+
+                for packets_ in contiguous:
+                    starttime = packets_[0]['time']
+
+                    if headonly:
+                        sample_data = np.array([], dtype=np.int32)
+                        npts = packets_["number_of_samples"].sum()
+                    else:
+                        if encoding in ('C0', 'C2'):
+                            sample_data = _unpack_C0_C2_data(packets_,
+                                                             encoding)
+                        elif encoding in ('16', '32'):
+                            # rt130 stores in big endian
+                            dtype = {'16': '>i2', '32': '>i4'}[encoding]
+                            # just fix endianness and use correct dtype
+                            sample_data = np.require(
+                                packets_['payload'],
+                                requirements=['C_CONTIGUOUS'])
+                            # either int16 or int32
+                            sample_data = sample_data.view(dtype)
+                            # account for number of samples, i.e. some packets
+                            # might not use the full payload size but have
+                            # empty parts at the end that need to be cut away
+                            number_of_samples_max = sample_data.shape[1]
+                            sample_data = sample_data.flatten()
+                            # go through packets starting at the back,
+                            # otherwise indices of later packets would change
+                            # while looping
+                            for ind, num_samps in reversed([
+                                    (ind, num_samps) for ind, num_samps in
+                                    enumerate(packets_["number_of_samples"])
+                                    if num_samps != number_of_samples_max]):
+                                # looping backwards we can easily find the
+                                # start of each packet, since the earlier
+                                # packets are still untouched and at maximum
+                                # sample length in our big array with all
+                                # packets
+                                start_of_packet = ind * number_of_samples_max
+                                start_empty_part = start_of_packet + num_samps
+                                end_empty_part = (start_of_packet +
+                                                  number_of_samples_max)
+                                sample_data = np.delete(
+                                    sample_data,
+                                    slice(start_empty_part, end_empty_part))
+                        npts = len(sample_data)
+
+                    tr = Trace(data=sample_data, header=copy.deepcopy(header))
+                    # channel number is not included in the EH/ET packet
+                    # payload, so add it to stats as well..
+                    tr.stats.reftek130['channel_number'] = channel_number
+                    if headonly:
+                        tr.stats.npts = npts
+                    tr.stats.starttime = UTCDateTime(ns=starttime)
+                    """
+                    if component codes were explicitly provided, use them
+                    together with the stream label
+                    if component_codes is not None:
+                        tr.stats.channel = (eh.stream_name.strip() +
+                                            component_codes[channel_number])
+                    # otherwise check if channel code is set for the given
+                    # channel (seems to be not the case usually)
+                    elif eh.channel_code[channel_number] is not None:
+                        tr.stats.channel = eh.channel_code[channel_number]
+                    # otherwise fall back to using the stream label together
+                    # with the number of the channel in the file (starting with
+                    # 0, as Z-1-2 is common use for data streams not oriented
+                    # against North)
+                    else:
+                        msg = ("No channel code specified in the data file "
+                               "and no component codes specified. Using "
+                               "stream label and number of channel in file as "
+                               "channel codes.")
+                        warnings.warn(msg)
+                        tr.stats.channel = (
+                            eh.stream_name.strip() + str(channel_number))
+                    """
+                    DS = self._data['data_stream_number'][0] + 1
+                    if DS != 9:
+                        tr.stats.channel = "DS%s-%s" % (DS, channel_number + 1)
+                    else:
+                        tr.stats.channel = "MP-%s" % (channel_number + 1)
+                        # check if endtime of trace is consistent
+                    t_last = packets_[-1]['time']
+                    npts_last = packets_[-1]['number_of_samples']
+                    try:
+                        if not headonly:
+                            assert npts == len(sample_data)
+                        if npts_last:
+                            assert tr.stats.endtime == UTCDateTime(
+                                ns=t_last) + (npts_last - 1) * delta
+                        if npts:
+                            assert tr.stats.endtime == (
+                                tr.stats.starttime + (npts - 1) * delta)
+                    except AssertionError:
+                        msg = ("Reftek file has a trace with an inconsistent "
+                               "endtime or number of samples. Please open an "
+                               "issue on GitHub and provide your file for"
+                               "testing.")
+                        raise Reftek130Exception(msg)
+                    st += tr
+        return st
diff --git a/sohstationviewer/model/reftek/from_rt2ms/packet.py b/sohstationviewer/model/reftek/from_rt2ms/packet.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3ddb8865c877ef54805ca1bf72277623fe88f44
--- /dev/null
+++ b/sohstationviewer/model/reftek/from_rt2ms/packet.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Suggested updates to obspy.io.reftek.packet:
+- Change type of EH_PAYLOAD['sampling_rate'] from int to float.
+- Add eh_et_info method to EHPacket class. This method compiles EH and ET info
+  and returns a list of strings used to write the rt2ms log file.
+
+Maeva Pourpoint IRIS/PASSCAL
+"""
+
+import obspy.io.reftek.packet as obspy_rt130_packet
+
+from obspy import UTCDateTime
+from obspy.io.reftek.util import (_decode_ascii,
+                                  _parse_long_time,
+                                  _16_tuple_ascii,
+                                  _16_tuple_int,
+                                  _16_tuple_float)
+from sohstationviewer.model.reftek.from_rt2ms.soh_packet import Packet
+
+
+class Reftek130UnpackPacketError(ValueError):
+    pass
+
+
+# name, offset, length (bytes) and converter routine for EH/ET packet payload
+EH_PAYLOAD = {
+    "trigger_time_message": (0, 33, _decode_ascii),
+    "time_source": (33, 1, _decode_ascii),
+    "time_quality": (34, 1, _decode_ascii),
+    "station_name_extension": (35, 1, _decode_ascii),
+    "station_name": (36, 4, _decode_ascii),
+    "stream_name": (40, 16, _decode_ascii),
+    "_reserved_2": (56, 8, _decode_ascii),
+    "sampling_rate": (64, 4, float),
+    "trigger_type": (68, 4, _decode_ascii),
+    "trigger_time": (72, 16, _parse_long_time),
+    "first_sample_time": (88, 16, _parse_long_time),
+    "detrigger_time": (104, 16, _parse_long_time),
+    "last_sample_time": (120, 16, _parse_long_time),
+    "channel_adjusted_nominal_bit_weights": (136, 128, _16_tuple_ascii),
+    "channel_true_bit_weights": (264, 128, _16_tuple_ascii),
+    "channel_gain_code": (392, 16, _16_tuple_ascii),
+    "channel_ad_resolution_code": (408, 16, _16_tuple_ascii),
+    "channel_fsa_code": (424, 16, _16_tuple_ascii),
+    "channel_code": (440, 64, _16_tuple_ascii),
+    "channel_sensor_fsa_code": (504, 16, _16_tuple_ascii),
+    "channel_sensor_vpu": (520, 96, _16_tuple_float),
+    "channel_sensor_units_code": (616, 16, _16_tuple_ascii),
+    "station_channel_number": (632, 48, _16_tuple_int),
+    "_reserved_3": (680, 156, _decode_ascii),
+    "total_installed_channels": (836, 2, int),
+    "station_comment": (838, 40, _decode_ascii),
+    "digital_filter_list": (878, 16, _decode_ascii),
+    "position": (894, 26, _decode_ascii),
+    "reftek_120": (920, 80, None)}
+
+obspy_rt130_packet.EH_PAYLOAD = EH_PAYLOAD
+
+
+class EHPacket(obspy_rt130_packet.EHPacket):
+    def __str__(self, compact=False):
+        if compact:
+            sta = (self.station_name.strip() +
+                   self.station_name_extension.strip())
+            info = ("{:04d} {:2s} {:4s} {:2d} {:4d} {:4d} {:2d} {:2s} "
+                    "{:5s}  {:4s}        {!s}").format(
+                        self.packet_sequence, self.type.decode(),
+                        self.unit_id.decode(), self.experiment_number,
+                        self.byte_count, self.event_number,
+                        self.data_stream_number, self.data_format.decode(),
+                        sta, str(self.sampling_rate)[:4], self.time)
+        else:
+            info = []
+            for key in self._headers:
+                value = getattr(self, key)
+                if key in ("unit_id", "data_format"):
+                    value = value.decode()
+                info.append("{}: {}".format(key, value))
+            info.append("-" * 20)
+            for key in sorted(EH_PAYLOAD.keys()):
+                value = getattr(self, key)
+                if key in ("trigger_time", "detrigger_time",
+                           "first_sample_time", "last_sample_time"):
+                    if value is not None:
+                        value = UTCDateTime(ns=value)
+                info.append("{}: {}".format(key, value))
+            info = "{} Packet\n\t{}".format(self.type.decode(),
+                                            "\n\t".join(info))
+        return info
+
+    def eh_et_info(self, nbr_DT_samples):
+        """
+        Compile EH and ET info to write to log file.
+        Returns list of strings.
+        Formatting of strings is based on earlier version of rt2ms.
+        """
+        info = []
+        # packet_tagline1 = ("\n\n{:s} exp {:02d} bytes {:04d} {:s} ID: {:s} "
+        #                    "seq {:04d}".format(self.type.decode(),
+        #                                        self.experiment_number,
+        #                                        self.byte_count,
+        #                                        Packet.time_tag(self.time),
+        #                                        self.unit_id.decode(),
+        #                                        self.packet_sequence))
+        # info.append(packet_tagline1)
+        # if self.type.decode('ASCII') == 'EH':
+        #     nbr_DT_samples = 0
+        #     info.append("\nEvent Header")
+        # else:
+        #     info.append("\nEvent Trailer")
+        # info.append("\n  event = " + str(self.event_number))
+        # info.append("\n  stream = " + str(self.data_stream_number + 1))
+        # info.append("\n  format = " + self.data_format.decode('ASCII'))
+        # info.append("\n  stream name = " + self.stream_name)
+        # info.append("\n  sample rate = " + str(self.sampling_rate))
+        # info.append("\n  trigger type = " + self.trigger_type)
+        trigger_time = Packet.time_tag(UTCDateTime(ns=self.trigger_time))
+        # info.append("\n  trigger time = " + trigger_time)
+        first_sample_time = Packet.time_tag(UTCDateTime(ns=self.first_sample_time))  # noqa: E501
+        # info.append("\n  first sample = " + first_sample_time)
+        # if self.last_sample_time:
+        #     info.append("\n  last sample = " + Packet.time_tag(UTCDateTime(ns=self.last_sample_time)))  # noqa: E501
+        # info.append("\n  bit weights = " + " ".join([val for val in self.channel_adjusted_nominal_bit_weights if val]))  # noqa: E501
+        # info.append("\n  true weights = " + " ".join([val for val in self.channel_true_bit_weights if val]))  # noqa: E501
+        packet_tagline2 = ("\nDAS: {:s} EV: {:04d} DS: {:d} FST = {:s} TT = "
+                           "{:s} NS: {:d} SPS: {:.1f} ETO: 0"
+                           .format(self.unit_id.decode(),
+                                   self.event_number,
+                                   self.data_stream_number + 1,
+                                   first_sample_time,
+                                   trigger_time,
+                                   nbr_DT_samples,
+                                   self.sampling_rate))
+        info.append(packet_tagline2)
+        return info
diff --git a/sohstationviewer/model/reftek/from_rt2ms/soh_packet.py b/sohstationviewer/model/reftek/from_rt2ms/soh_packet.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff54fe1539c53d352b8efa036bc66ed0f6c7eb0b
--- /dev/null
+++ b/sohstationviewer/model/reftek/from_rt2ms/soh_packet.py
@@ -0,0 +1,928 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Routines building upon obspy.io.reftek.packet.
+Redefine packet header (PACKET) based on rt130 manual.
+Handles all state of health (SOH) packets:
+- SH: State-Of-Health Packet
+- SC: Station/Channel Parameter Packet
+- OM: Operating Mode Parameter Packet
+- DS: Data Stream Parameter Packet
+- AD: Auxiliary Data Parameter Packet
+- CD: Calibration Parameter Packet
+- FD: Filter Description Packet
+
+Maeva Pourpoint IRIS/PASSCAL
+"""
+
+import obspy.io.reftek.packet as obspy_rt130_packet
+import numpy as np
+import warnings
+
+from obspy import UTCDateTime
+from obspy.core.compatibility import from_buffer
+from obspy.io.reftek.util import (bcd, bcd_hex, _decode_ascii,
+                                  bcd_julian_day_string_to_nanoseconds_of_year,
+                                  bcd_16bit_int, _parse_long_time,
+                                  _get_nanoseconds_for_start_of_year)
+
+
+class Reftek130UnpackPacketError(ValueError):
+    pass
+
+
+AD_DS_RECORDING_DEST = ["RAM", "Disk", "Ethernet", "Serial"]
+CD_MAX_NBR_STRUCT = 4
+DS_MAX_NBR_ST = 4
+SC_MAX_NBR_CHA = 5
+
+# Packet header for rt130 state of health packets.
+# Tuples are:
+#  - field name
+#  - dtype during initial reading
+#  - conversion routine (if any)
+#  - dtype after conversion
+PACKET = [
+    ("packet_type", "|S2", None, "S2"),
+    ("experiment_number", np.uint8, bcd, np.uint8),
+    ("year", np.uint8, bcd, np.uint8),
+    ("unit_id", (np.uint8, 2), bcd_hex, "S4"),
+    ("time", (np.uint8, 6), bcd_julian_day_string_to_nanoseconds_of_year, np.int64),  # noqa: E501
+    ("byte_count", (np.uint8, 2), bcd_16bit_int, np.uint16),
+    ("packet_sequence", (np.uint8, 2), bcd_16bit_int, np.uint16),
+    ("payload", (np.uint8, 1008), None, (np.uint8, 1008))]
+
+PACKET_INITIAL_UNPACK_DTYPE = np.dtype([(name, dtype_initial)
+                                        for name, dtype_initial, converter,
+                                        dtype_final in PACKET])
+PACKET_FINAL_DTYPE = np.dtype([(name, dtype_final) for name, dtype_initial,
+                               converter, dtype_final in PACKET])
+
+# name, length (bytes) and converter routine for packet payload
+SH_PAYLOAD = {
+    "reserved": (8, _decode_ascii),
+    "information": (1000, _decode_ascii)}
+
+SC_PAYLOAD = {
+    "experiment_number_sc": (2, _decode_ascii),
+    "experiment_name": (24, _decode_ascii),
+    "experiment_comment": (40, _decode_ascii),
+    "station_number": (4, _decode_ascii),
+    "station_name": (24, _decode_ascii),
+    "station_comment": (40, _decode_ascii),
+    "das_model": (12, _decode_ascii),
+    "das_serial": (12, _decode_ascii),
+    "experiment_start": (14, _decode_ascii),
+    "time_clock_type": (4, _decode_ascii),
+    "time_clock_sn": (10, _decode_ascii),
+    "sc_info": (730, None),
+    "reserved": (76, _decode_ascii),
+    "implement_time": (16, _parse_long_time)}
+
+SC_INFO = {
+    "_number": (2, _decode_ascii),
+    "_name": (10, _decode_ascii),
+    "_azimuth": (10, _decode_ascii),
+    "_inclination": (10, _decode_ascii),
+    "_x_coordinate": (10, _decode_ascii),
+    "_y_coordinate": (10, _decode_ascii),
+    "_z_coordinate": (10, _decode_ascii),
+    "_xy_unit": (4, _decode_ascii),
+    "_z_unit": (4, _decode_ascii),
+    "_preamp_gain": (4, _decode_ascii),
+    "_sensor_model": (12, _decode_ascii),
+    "_sensor_serial": (12, _decode_ascii),
+    "_comments": (40, _decode_ascii),
+    "_adjusted_nominal_bit_weight": (8, _decode_ascii)}
+
+OM_PAYLOAD = {
+    "_72A_power_state": (2, _decode_ascii),
+    "recording_mode": (2, _decode_ascii),
+    "disk_reserved_1": (4, _decode_ascii),
+    "auto_dump_on_ET": (1, _decode_ascii),
+    "disk_reserved_2": (1, _decode_ascii),
+    "auto_dump_threshold": (2, _decode_ascii),
+    "_72A_power_down_delay": (4, _decode_ascii),
+    "disk_wrap": (1, _decode_ascii),
+    "disk_reserved_3": (1, _decode_ascii),
+    "_72A_disk_power": (1, _decode_ascii),
+    "_72A_terminator_power": (1, _decode_ascii),
+    "disk_retry": (1, _decode_ascii),
+    "disk_reserved_4": (11, _decode_ascii),
+    "_72A_wakeup_reserved_1": (2, _decode_ascii),
+    "_72A_wakeup_start_time": (12, _decode_ascii),
+    "_72A_wakeup_duration": (6, _decode_ascii),
+    "_72A_wakeup_repeat_interval": (6, _decode_ascii),
+    "_72A_wakeup_number_intervals": (2, _decode_ascii),
+    "_72A_wakeup_reserved_2": (484, _decode_ascii),
+    "reserved": (448, _decode_ascii),
+    "implement_time": (16, _parse_long_time)}
+
+DS_PAYLOAD = {
+    "ds_info": (920, None),
+    "reserved": (72, _decode_ascii),
+    "implement_time": (16, _parse_long_time)}
+
+DS_INFO = {
+    "_number": (2, _decode_ascii),
+    "_name": (16, _decode_ascii),
+    "_recording_destination": (4, _decode_ascii),
+    "_reserved_1": (4, _decode_ascii),
+    "_channels_included": (16, _decode_ascii),
+    "_sample_rate": (4, _decode_ascii),
+    "_data_format": (2, _decode_ascii),
+    "_reserved_2": (16, _decode_ascii),
+    "_trigger_type": (4, _decode_ascii),
+    "_trigger_description": (162, None)}
+
+DS_TRIGGER = {
+    "CON": {
+        "RecordLength": (8, _decode_ascii),
+        "StartTime": (14, _decode_ascii),
+        "Reserved": (140, _decode_ascii)},
+    "CRS": {
+        "TriggerStreamNo": (2, _decode_ascii),
+        "PretriggerLength": (8, _decode_ascii),
+        "RecordLength": (8, _decode_ascii),
+        "Reserved": (144, _decode_ascii)},
+    "EVT": {
+        "TriggerChannels": (16, _decode_ascii),
+        "MinimumChannels": (2, _decode_ascii),
+        "TriggerWindow": (8, _decode_ascii),
+        "PretriggerLength": (8, _decode_ascii),
+        "PosttriggerLength": (8, _decode_ascii),
+        "RecordLength": (8, _decode_ascii),
+        "Reserved1": (8, _decode_ascii),
+        "STALength": (8, _decode_ascii),
+        "LTALength": (8, _decode_ascii),
+        "MeanRemoval": (8, _decode_ascii),
+        "TriggerRatio": (8, _decode_ascii),
+        "DetriggerRatio": (8, _decode_ascii),
+        "LTAHold": (4, _decode_ascii),
+        "LowPassCornerFreq": (4, _decode_ascii),
+        "HighPassCornerFreq": (4, _decode_ascii),
+        "Reserved2": (52, _decode_ascii)},
+    "EXT": {
+        "PretriggerLength": (8, _decode_ascii),
+        "RecordLength": (8, _decode_ascii),
+        "Reserved": (146, _decode_ascii)},
+    "LEV": {
+        "Level": (8, _decode_ascii),
+        "PretriggerLength": (8, _decode_ascii),
+        "RecordLength": (8, _decode_ascii),
+        "LowPassCornerFreq": (4, _decode_ascii),
+        "HighPassCornerFreq": (4, _decode_ascii),
+        "Reserved": (130, _decode_ascii)},
+    "TIM": {
+        "StartTime": (14, _decode_ascii),
+        "RepeatInterval": (8, _decode_ascii),
+        "Intervals": (4, _decode_ascii),
+        "Reserved1": (8, _decode_ascii),
+        "RecordLength": (8, _decode_ascii),
+        "Reserved2": (120, _decode_ascii)},
+    "TML": {
+        "StartTime01": (14, _decode_ascii),
+        "StartTime02": (14, _decode_ascii),
+        "StartTime03": (14, _decode_ascii),
+        "StartTime04": (14, _decode_ascii),
+        "StartTime05": (14, _decode_ascii),
+        "StartTime06": (14, _decode_ascii),
+        "StartTime07": (14, _decode_ascii),
+        "StartTime08": (14, _decode_ascii),
+        "StartTime09": (14, _decode_ascii),
+        "StartTime10": (14, _decode_ascii),
+        "StartTime11": (14, _decode_ascii),
+        "RecordLength": (8, _decode_ascii)}}
+
+AD_PAYLOAD = {
+    "marker": (2, _decode_ascii),
+    "channels": (16, _decode_ascii),
+    "sample_period": (8, _decode_ascii),
+    "data_format": (2, _decode_ascii),
+    "record_length": (8, _decode_ascii),
+    "recording_destination": (4, _decode_ascii),
+    "reserved": (952, _decode_ascii),
+    "implement_time": (16, _parse_long_time)}
+
+CD_PAYLOAD = {
+    "_72A_start_time": (14, _decode_ascii),
+    "_72A_repeat_interval": (8, _decode_ascii),
+    "_72A_number_intervals": (4, _decode_ascii),
+    "_72A_length": (8, _decode_ascii),
+    "_72A_step_onoff": (4, _decode_ascii),
+    "_72A_step_period": (8, _decode_ascii),
+    "_72A_step_size": (8, _decode_ascii),
+    "_72A_step_amplitude": (8, _decode_ascii),
+    "_72A_step_output": (4, _decode_ascii),
+    "_72A_reserved": (48, _decode_ascii),
+    "_130_autocenter": (64, None),
+    "_130_signal": (112, None),
+    "_130_sequence": (232, None),
+    "reserved": (470, _decode_ascii),
+    "implement_time": (16, _parse_long_time)}
+
+CD_130_AUTOCENTER = {
+    "_sensor": (1, _decode_ascii),
+    "_enable": (1, _decode_ascii),
+    "_reading_interval": (4, _decode_ascii),
+    "_cycle_interval": (2, _decode_ascii),
+    "_level": (4, _decode_ascii),
+    "_attempts": (2, _decode_ascii),
+    "_attempts_interval": (2, _decode_ascii)}
+
+CD_130_SIGNAL = {
+    "_sensor": (1, _decode_ascii),
+    "_enable": (1, _decode_ascii),
+    "_reserved": (2, _decode_ascii),
+    "_duration": (4, _decode_ascii),
+    "_amplitude": (4, _decode_ascii),
+    "_signal": (4, _decode_ascii),
+    "_step_interval": (4, _decode_ascii),
+    "_step_width": (4, _decode_ascii),
+    "_sine_frequency": (4, _decode_ascii)}
+
+CD_130_SEQUENCE = {
+    "_sequence": (1, _decode_ascii),
+    "_enable": (1, _decode_ascii),
+    "_reserved_1": (2, _decode_ascii),
+    "_start_time": (14, _decode_ascii),
+    "_interval": (8, _decode_ascii),
+    "_count": (2, _decode_ascii),
+    "_record_length": (8, _decode_ascii),
+    "_sensor": (4, _decode_ascii),
+    "_reserved_2": (18, _decode_ascii)}
+
+FD_PAYLOAD = {
+    "fd_info": (992, None),
+    "implement_time": (16, _parse_long_time)}
+
+FD_INFO = {
+    "_filter_block_count": (1, int),
+    "_filter_ID": (1, _decode_ascii),
+    "_filter_decimation": (1, int),
+    "_filter_scalar": (1, int),
+    "_filter_coeff_count": (1, int),
+    "_packet_coeff_count": (1, int),
+    "_coeff_packet_count": (1, int),
+    "_coeff_format": (1, bcd),
+    "_coeff": (984, None)}
+
+
+class Packet(obspy_rt130_packet.Packet):
+    """Class used to define shared tools for the SOH packets"""
+
+    _headers = ('experiment_number', 'unit_id', 'byte_count',
+                'packet_sequence', 'time')
+
+    @staticmethod
+    def from_data(data):
+        """
+        Checks for valid packet type identifier and returns appropriate
+        packet object
+        """
+        packet_type = data['packet_type'].decode("ASCII", "ignore")
+        if packet_type == "SH":
+            return SHPacket(data)
+        elif packet_type == "SC":
+            return SCPacket(data)
+        elif packet_type == "OM":
+            return OMPacket(data)
+        elif packet_type == "DS":
+            return DSPacket(data)
+        elif packet_type == "AD":
+            return ADPacket(data)
+        elif packet_type == "CD":
+            return CDPacket(data)
+        elif packet_type == "FD":
+            return FDPacket(data)
+        else:
+            msg = "Can not create Reftek SOH packet for packet type '{}'"
+            raise NotImplementedError(msg.format(packet_type))
+
+    @staticmethod
+    def time_tag(time, implement_time=None):
+        if implement_time is not None and time > UTCDateTime(ns=implement_time):  # noqa: E501
+            time = UTCDateTime(ns=implement_time)
+        return "{:04d}:{:03d}:{:02d}:{:02d}:{:02d}:{:03d}".format(time.year,
+                                                                  time.julday,
+                                                                  time.hour,
+                                                                  time.minute,
+                                                                  time.second,
+                                                                  time.microsecond)  # noqa: E501
+
+    @property
+    def packet_tagline(self):
+        return "\n"
+        # return "\n\n{:s} exp {:02d} bytes {:04d} {:s} ID: {:s} seq {:04d}".format(self.type.decode(),  # noqa: E501
+        #                                                                           self.experiment_number,  # noqa: E501
+        #                                                                           self.byte_count,  # noqa: E501
+        #                                                                           self.time_tag(self.time),  # noqa: E501
+        #                                                                           self.unit_id.decode(),  # noqa: E501
+        #                                                                           self.packet_sequence)  # noqa: E501
+
+
+class SHPacket(Packet):
+    """Class used to parse and generate string representation for SH packets"""
+
+    def __init__(self, data):
+        self._data = data
+        payload = self._data["payload"].tobytes()
+        start_sh = 0
+        for name, (length, converter) in SH_PAYLOAD.items():
+            data = payload[start_sh:start_sh + length]
+            if converter is not None:
+                try:
+                    data = converter(data)
+                except ValueError:
+                    msg = ("SH packet, wrong conversion routine for input "
+                           "variable: {}".format(name))
+                    warnings.warn(msg)
+                    data = ''
+            setattr(self, name, data)
+            start_sh = start_sh + length
+
+    def __str__(self):
+        info = []
+        # info.append(self.packet_tagline)
+        packet_soh_string = ("\nState of Health  {:s}   ST: {:s}"
+                             .format(self.time_tag(self.time)[2:],
+                                     self.unit_id.decode()))
+        info.append(packet_soh_string)
+        info.append("\n" + self.information.strip())
+        return info
+
+
+class SCPacket(Packet):
+    """Class used to parse and generate string representation for SC packets"""
+
+    def __init__(self, data):
+        # Station/Channel payload
+        self._data = data
+        payload = self._data["payload"].tobytes()
+        start_sc = 0
+        for name, (length, converter) in SC_PAYLOAD.items():
+            data = payload[start_sc:start_sc + length]
+            if converter is not None:
+                try:
+                    data = converter(data)
+                except ValueError:
+                    msg = ("SC packet, wrong conversion routine for input "
+                           "variable: {}".format(name))
+                    warnings.warn(msg)
+                    data = ''
+            setattr(self, name, data)
+            start_sc = start_sc + length
+        # Detailed info for each channel - Up to 5 channels
+        start_info = 0
+        for ind_sc in range(1, SC_MAX_NBR_CHA + 1):
+            for name, (length, converter) in SC_INFO.items():
+                data = self.sc_info[start_info:start_info + length]
+                if converter is not None:
+                    try:
+                        data = converter(data)
+                    except ValueError:
+                        msg = ("SC packet, channel info, wrong conversion "
+                               "routine for input variable : {}".format(name))
+                        warnings.warn(msg)
+                        data = ''
+                name = "sc" + str(ind_sc) + name
+                setattr(self, name, data)
+                start_info = start_info + length
+
+    def __str__(self):
+        info = []
+        # info.append(self.packet_tagline)
+        # packet_soh_string = ("\nStation Channel Definition  {:s}   ST: {:s}"
+        #                      .format(self.time_tag(self.time, implement_time=self.implement_time),  # noqa: E501
+        #                              self.unit_id.decode()))
+        packet_soh_string = ("\nStation Channel Definition  {:s}   ST: {:s}"
+                             .format(self.time_tag(self.time),
+                                     self.unit_id.decode()))
+        info.append(packet_soh_string)
+        info.append("\n Experiment Number = " + self.experiment_number_sc)
+        info.append("\n Experiment Name = " + self.experiment_name)
+        info.append("\n  Comments - " + self.experiment_comment)
+        info.append("\n Station Number = " + self.station_number)
+        info.append("\n Station Name = " + self.station_name)
+        info.append("\n  Station Comments - " + self.station_comment)
+        info.append("\n DAS Model Number = " + self.das_model)
+        info.append("\n DAS Serial Number = " + self.das_serial)
+        info.append("\n Experiment Start Time = " + self.experiment_start)
+        info.append("\n Time Clock Type = " + self.time_clock_type)
+        info.append("\n Clock Serial Number = " + self.time_clock_sn)
+        for ind_sc in range(1, SC_MAX_NBR_CHA + 1):
+            channel_number = getattr(self, 'sc' + str(ind_sc) + '_number')
+            if channel_number.strip():
+                info.append("\n  Channel Number = " + channel_number)
+                info.append("\n     Name - " + getattr(self, 'sc' + str(ind_sc) + '_name'))  # noqa: E501
+                info.append("\n     Azimuth - " + getattr(self, 'sc' + str(ind_sc) + '_azimuth'))  # noqa: E501
+                info.append("\n     Inclination - " + getattr(self, 'sc' + str(ind_sc) + '_inclination'))  # noqa: E501
+                info.append("\n     Location")
+                info.append("\n     X - " + getattr(self, 'sc' + str(ind_sc) + '_x_coordinate'))  # noqa: E501
+                info.append("  Y - " + getattr(self, 'sc' + str(ind_sc) + '_y_coordinate'))  # noqa: E501
+                info.append("  Z - " + getattr(self, 'sc' + str(ind_sc) + '_z_coordinate'))  # noqa: E501
+                info.append("\n     XY Units - " + getattr(self, 'sc' + str(ind_sc) + '_xy_unit'))  # noqa: E501
+                info.append("  Z Units - " + getattr(self, 'sc' + str(ind_sc) + '_z_unit'))  # noqa: E501
+                info.append("\n     Preamplifier Gain = " + getattr(self, 'sc' + str(ind_sc) + '_preamp_gain'))  # noqa: E501
+                info.append("\n     Sensor Model - " + getattr(self, 'sc' + str(ind_sc) + '_sensor_model'))  # noqa: E501
+                info.append("\n     Sensor Serial Number - " + getattr(self, 'sc' + str(ind_sc) + '_sensor_serial'))  # noqa: E501
+                info.append("\n     Volts per Bit = " + getattr(self, 'sc' + str(ind_sc) + '_adjusted_nominal_bit_weight'))  # noqa: E501
+                info.append("\n     Comments - " + getattr(self, 'sc' + str(ind_sc) + '_comments'))  # noqa: E501
+        return info
+
+    def get_info(self, infos):
+        """
+        Compile relevant information - unit id, reference channel, network
+        code, station code, component code, gain and implementation time - for
+        given SC packet
+        """
+        implement_time = UTCDateTime(ns=self.implement_time)
+        for ind_sc in range(1, SC_MAX_NBR_CHA + 1):
+            channel_number = getattr(self, 'sc' + str(ind_sc) + '_number')
+            if channel_number.strip():
+                gain = getattr(self, 'sc' + str(ind_sc) + '_preamp_gain')
+                if gain is None:
+                    msg = ("No gain available for parameter packet "
+                           "implemented at {} - refchan {}"
+                           .format(implement_time, channel_number))
+                    warnings.warn(msg)
+                    continue
+                # order: #das, refchan, netcode, station, channel[3],
+                # gain, implement_time
+                info = [self.unit_id.decode(),
+                        channel_number.strip(),
+                        self.experiment_number_sc.strip(),
+                        self.station_name.strip(),
+                        getattr(self, 'sc' + str(ind_sc) + '_name').strip(),
+                        gain.strip(),
+                        implement_time]
+                if info not in infos:
+                    infos.append(info)
+        return infos
+
+
+class OMPacket(Packet):
+    """Class used to parse and generate string representation for OM packets"""
+
+    def __init__(self, data):
+        self._data = data
+        payload = self._data["payload"].tobytes()
+        start_om = 0
+        for name, (length, converter) in OM_PAYLOAD.items():
+            data = payload[start_om:start_om + length]
+            if converter is not None:
+                try:
+                    data = converter(data)
+                except ValueError:
+                    msg = ("OM packet, wrong conversion routine for input "
+                           "variable: {}".format(name))
+                    warnings.warn(msg)
+                    data = ''
+            setattr(self, name, data)
+            start_om = start_om + length
+
+    def __str__(self):
+        info = []
+        # info.append(self.packet_tagline)
+        packet_soh_string = ("\nOperating Mode Definition  {:s}   ST: {:s}"
+                             .format(self.time_tag(self.time, implement_time=self.implement_time),  # noqa: E501
+                                     self.unit_id.decode()))
+        info.append(packet_soh_string)
+        info.append("\n  Operating Mode 72A Power State " + self._72A_power_state)  # noqa: E501
+        info.append("\n  Operating Mode Recording Mode " + self.recording_mode)
+        info.append("\n  Operating Mode Auto Dump on ET " + self.auto_dump_on_ET)  # noqa: E501
+        info.append("\n  Operating Mode Auto Dump Threshold " + self.auto_dump_threshold)  # noqa: E501
+        info.append("\n  Operating Mode 72A Power Down Delay " + self._72A_power_down_delay)  # noqa: E501
+        info.append("\n  Operating Mode Disk Wrap " + self.disk_wrap)
+        info.append("\n  Operating Mode 72A Disk Power " + self._72A_disk_power)  # noqa: E501
+        info.append("\n  Operating Mode 72A Terminator Power " + self._72A_terminator_power)  # noqa: E501
+        info.append("\n  Operating Mode 72A Wake Up Start Time " + self._72A_wakeup_start_time)  # noqa: E501
+        info.append("\n  Operating Mode 72A Wake Up Duration " + self._72A_wakeup_duration)  # noqa: E501
+        info.append("\n  Operating Mode 72A Wake Up Repeat Interval " + self._72A_wakeup_repeat_interval)  # noqa: E501
+        info.append("\n  Operating Mode 72A Number of Wake Up Intervals " + self._72A_wakeup_number_intervals)  # noqa: E501
+        return info
+
+
+class DSPacket(Packet):
+    """Class used to parse and generate string representation for DS packets"""
+
+    def __init__(self, data):
+        # Data Stream payload
+        self._data = data
+        payload = self._data["payload"].tobytes()
+        start_ds = 0
+        for name, (length, converter) in DS_PAYLOAD.items():
+            data = payload[start_ds:start_ds + length]
+            if converter is not None:
+                try:
+                    data = converter(data)
+                except ValueError:
+                    msg = ("DS packet, wrong conversion routine for input "
+                           "variable: {}".format(name))
+                    warnings.warn(msg)
+                    data = ''
+            setattr(self, name, data)
+            start_ds = start_ds + length
+        # Detailed info for each stream - Up to 4 streams
+        start_info = 0
+        for ind_ds in range(1, DS_MAX_NBR_ST + 1):
+            for name, (length, converter) in DS_INFO.items():
+                data = self.ds_info[start_info:start_info + length]
+                if converter is not None:
+                    try:
+                        data = converter(data)
+                    except ValueError:
+                        msg = ("DS packet, data stream info, wrong conversion "
+                               "routine for input variable: {}".format(name))
+                        warnings.warn(msg)
+                        data = ''
+                name = "ds" + str(ind_ds) + name
+                setattr(self, name, data)
+                start_info = start_info + length
+            # Detailed info for each trigger
+            trigger_type = getattr(self, "ds" + str(ind_ds) + "_trigger_type").strip()  # noqa: E501
+            if trigger_type in DS_TRIGGER:
+                start_trigger = 0
+                for name, (length, converter) in DS_TRIGGER[trigger_type].items():  # noqa: E501
+                    data = getattr(self, "ds" + str(ind_ds) + "_trigger_description")[start_trigger:start_trigger + length]  # noqa: E501
+                    if converter is not None:
+                        try:
+                            data = converter(data)
+                        except ValueError:
+                            msg = ("DS packet, trigger info, wrong conversion "
+                                   "routine for input variable: {}"
+                                   .format(name))
+                            warnings.warn(msg)
+                            data = ''
+                    name = "ds" + str(ind_ds) + "_trigger_" + name
+                    setattr(self, name, data)
+                    start_trigger = start_trigger + length
+            elif trigger_type.strip():
+                msg = ("Trigger type {:s} not found".format(trigger_type))
+                warnings.warn(msg)
+
+    def __str__(self):
+        info = []
+        info.append(self.packet_tagline)
+        packet_soh_string = ("\nData Stream Definition  {:s}   ST: {:s}"
+                             .format(self.time_tag(self.time, implement_time=self.implement_time),  # noqa: E501
+                                     self.unit_id.decode()))
+        info.append(packet_soh_string)
+        for ind_ds in range(1, DS_MAX_NBR_ST + 1):
+            stream_number = getattr(self, "ds" + str(ind_ds) + "_number")
+            if stream_number.strip():
+                recording_dest = [AD_DS_RECORDING_DEST[ind_rd]
+                                  for ind_rd, val in enumerate(getattr(self, "ds" + str(ind_ds) + "_recording_destination"))  # noqa: E501
+                                  if val.strip()]
+                info.append(" ".join(["\n  Data Stream",
+                                      stream_number,
+                                      getattr(self, "ds" + str(ind_ds) + "_name"),  # noqa: E501
+                                      ", ".join(recording_dest)]))
+                channels = getattr(self, "ds" + str(ind_ds) + "_channels_included")  # noqa: E501
+                channel_nbr = [str(ind_chan) for ind_chan, val in enumerate(channels, 1) if val.strip()]  # noqa: E501
+                info.append("\n  Channels " + ", ".join(channel_nbr))
+                info.append("\n  Sample rate " + getattr(self, "ds" + str(ind_ds) + "_sample_rate") + " samples per second")  # noqa: E501
+                info.append("\n  Data Format " + getattr(self, "ds" + str(ind_ds) + "_data_format"))  # noqa: E501
+                trigger_type = getattr(self, "ds" + str(ind_ds) + "_trigger_type").strip()  # noqa: E501
+                info.append("\n  Trigger Type " + trigger_type)
+                if trigger_type in DS_TRIGGER:
+                    for key in DS_TRIGGER[trigger_type].keys():
+                        if "reserved" not in key.lower():
+                            trigger_info = getattr(self, "ds" + str(ind_ds) + "_trigger_" + key)  # noqa: E501
+                            if trigger_info:
+                                if trigger_type == "EVT" and key == "TriggerChannels":  # noqa: E501
+                                    channel_nbr = [str(ind_chan) for ind_chan, val in enumerate(trigger_info, 1) if val.strip()]  # noqa: E501
+                                    info.append(" ".join(["\n     Trigger", key, ", ".join(channel_nbr)]))  # noqa: E501
+                                else:
+                                    info.append(" ".join(["\n     Trigger", key, trigger_info]))  # noqa: E501
+        return info
+
+    def get_info(self, infos):
+        """
+        Compile relevant information - reference data stream, band and
+        instrument codes, sample rate and implementation time - for given DS
+        packet
+        """
+        implement_time = UTCDateTime(ns=self.implement_time)
+        for ind_ds in range(1, DS_MAX_NBR_ST + 1):
+            stream_number = getattr(self, "ds" + str(ind_ds) + "_number")
+            if stream_number.strip():
+                samplerate = getattr(self, "ds" + str(ind_ds) + "_sample_rate")
+                if samplerate is None:
+                    msg = ("No sampling rate available for parameter packet "
+                           "implemented at {} - refstrm {}"
+                           .format(implement_time, stream_number))
+                    warnings.warn(msg)
+                    continue
+                # order: refstrm, channel[0:2], samplerate, implement_time
+                info = [stream_number.strip(),
+                        getattr(self, "ds" + str(ind_ds) + "_name").strip(),
+                        samplerate.strip(),
+                        implement_time]
+                if info not in infos:
+                    infos.append(info)
+        return infos
+
+
+class ADPacket(Packet):
+    """Class used to parse and generate string representation for AD packets"""
+
+    def __init__(self, data):
+        self._data = data
+        payload = self._data["payload"].tobytes()
+        start_ad = 0
+        for name, (length, converter) in AD_PAYLOAD.items():
+            data = payload[start_ad:start_ad + length]
+            if converter is not None:
+                try:
+                    data = converter(data)
+                except ValueError:
+                    msg = ("AD packet, wrong conversion routine for input "
+                           "variable: {}".format(name))
+                    warnings.warn(msg)
+                    data = ''
+            setattr(self, name, data)
+            start_ad = start_ad + length
+
+    def __str__(self):
+        info = []
+        # info.append(self.packet_tagline)
+        packet_soh_string = ("\nAuxiliary Data Parameter  {:s}   ST: {:s}"
+                             .format(self.time_tag(self.time, implement_time=self.implement_time),  # noqa: E501
+                                     self.unit_id.decode()))
+        info.append(packet_soh_string)
+        channel_nbr = [str(ind_chan) for ind_chan, val in
+                       enumerate(self.channels, 1) if val.strip()]
+        info.append("\n  Channels " + ", ".join(channel_nbr))
+        info.append("\n  Sample Period " + self.sample_period)
+        info.append("\n  Data Format " + self.data_format)
+        info.append("\n  Record Length " + self.record_length)
+        recording_dest = [AD_DS_RECORDING_DEST[ind_rd] for ind_rd, val
+                          in enumerate(self.recording_destination)
+                          if val.strip()]
+        info.append("\n  Recording Destination " + ", ".join(recording_dest))
+        return info
+
+
+class CDPacket(Packet):
+    """Class used to parse and generate string representation for CD packets"""
+
+    def __init__(self, data):
+        # Calibration parameter payload
+        self._data = data
+        payload = self._data["payload"].tobytes()
+        start_cd = 0
+        for name, (length, converter) in CD_PAYLOAD.items():
+            data = payload[start_cd:start_cd + length]
+            if converter is not None:
+                try:
+                    data = converter(data)
+                except ValueError:
+                    msg = ("CD packet, wrong conversion routine for input "
+                           "variable: {}".format(name))
+                    warnings.warn(msg)
+                    data = ''
+            setattr(self, name, data)
+            start_cd = start_cd + length
+
+        start_info_ac = 0
+        start_info_sig = 0
+        start_info_seq = 0
+        for ind_cd in range(1, CD_MAX_NBR_STRUCT + 1):
+            # Detailed info for 130 Sensor Auto-Center - Up to 4 structures
+            for name, (length, converter) in CD_130_AUTOCENTER.items():
+                data = self._130_autocenter[start_info_ac:start_info_ac + length]  # noqa: E501
+                if converter is not None:
+                    try:
+                        data = converter(data)
+                    except ValueError:
+                        msg = ("CD packet, auto-center info, wrong conversion "
+                               "routine for input variable: {}".format(name))
+                        warnings.warn(msg)
+                        data = ''
+                name = "cd_130_autocenter_" + str(ind_cd) + name
+                setattr(self, name, data)
+                start_info_ac = start_info_ac + length
+            # Detailed info for 130 Sensor Calibration Signal
+            # Up to 4 structures
+            for name, (length, converter) in CD_130_SIGNAL.items():
+                data = self._130_signal[start_info_sig:start_info_sig + length]
+                if converter is not None:
+                    try:
+                        data = converter(data)
+                    except ValueError:
+                        msg = ("CD packet, calibration signal info, wrong "
+                               "conversion routine for input variable: {}"
+                               .format(name))
+                        warnings.warn(msg)
+                        data = ''
+                name = "cd_130_signal_" + str(ind_cd) + name
+                setattr(self, name, data)
+                start_info_sig = start_info_sig + length
+            # Detailed info for 130 Sensor Calibration Sequence
+            # Up to 4 structures
+            for name, (length, converter) in CD_130_SEQUENCE.items():
+                data = self._130_sequence[start_info_seq:start_info_seq + length]  # noqa: E501
+                if converter is not None:
+                    try:
+                        data = converter(data)
+                    except ValueError:
+                        msg = ("CD packet, calibration sequence info, wrong "
+                               "conversion routine for input variable: {}"
+                               .format(name))
+                        warnings.warn(msg)
+                        data = ''
+                name = "cd_130_sequence_" + str(ind_cd) + name
+                setattr(self, name, data)
+                start_info_seq = start_info_seq + length
+
+    def __str__(self):
+        info = []
+        # info.append(self.packet_tagline)
+        packet_soh_string = ("\nCalibration Definition  {:s}   ST: {:s}"
+                             .format(self.time_tag(self.time, implement_time=self.implement_time),  # noqa: E501
+                                     self.unit_id.decode()))
+        info.append(packet_soh_string)
+
+        if self._72A_start_time.split():
+            info.append("\n  72A Calibration Start Time " + self._72A_start_time)  # noqa: E501
+            info.append("\n  72A Calibration Repeat Interval " + self._72A_repeat_interval)  # noqa: E501
+            info.append("\n  72A Calibration Intervals " + self._72A_number_intervals)  # noqa: E501
+            info.append("\n  72A Calibration Length " + self._72A_length)
+            info.append("\n  72A Calibration Step On/Off " + self._72A_step_onoff)  # noqa: E501
+            info.append("\n  72A Calibration Step Period " + self._72A_step_period)  # noqa: E501
+            info.append("\n  72A Calibration Step Size " + self._72A_step_size)
+            info.append("\n  72A Calibration Step Amplitude " + self._72A_step_amplitude)  # noqa: E501
+            info.append("\n  72A Calibration Step Output " + self._72A_step_output)  # noqa: E501
+
+        for ind_cd in range(1, CD_MAX_NBR_STRUCT + 1):
+            autocenter_sensor = getattr(self, "cd_130_autocenter_" + str(ind_cd) + '_sensor')  # noqa: E501
+            if autocenter_sensor.strip():
+                info.append("\n  130 Auto Center Sensor " + autocenter_sensor)
+                info.append("\n  130 Auto Center Enable " + getattr(self, "cd_130_autocenter_" + str(ind_cd) + '_enable'))  # noqa: E501
+                info.append("\n  130 Auto Center Reading Interval " + getattr(self, "cd_130_autocenter_" + str(ind_cd) + '_reading_interval'))  # noqa: E501
+                info.append("\n  130 Auto Center Cycle Interval " + getattr(self, "cd_130_autocenter_" + str(ind_cd) + '_cycle_interval'))  # noqa: E501
+                info.append("\n  130 Auto Center Level " + getattr(self, "cd_130_autocenter_" + str(ind_cd) + '_level'))  # noqa: E501
+                info.append("\n  130 Auto Center Attempts " + getattr(self, "cd_130_autocenter_" + str(ind_cd) + '_attempts'))  # noqa: E501
+                info.append("\n  130 Auto Center Attempt Interval " + getattr(self, "cd_130_autocenter_" + str(ind_cd) + '_attempts_interval'))  # noqa: E501
+
+        for ind_cd in range(1, CD_MAX_NBR_STRUCT + 1):
+            signal_sensor = getattr(self, "cd_130_signal_" + str(ind_cd) + '_sensor')  # noqa: E501
+            if signal_sensor.strip():
+                info.append("\n  130 Calibration Sensor " + signal_sensor)
+                info.append("\n  130 Calibration Enable " + getattr(self, "cd_130_signal_" + str(ind_cd) + '_enable'))  # noqa: E501
+                info.append("\n  130 Calibration Duration " + getattr(self, "cd_130_signal_" + str(ind_cd) + '_duration'))  # noqa: E501
+                info.append("\n  130 Calibration Amplitude " + getattr(self, "cd_130_signal_" + str(ind_cd) + '_amplitude'))  # noqa: E501
+                info.append("\n  130 Calibration Signal " + getattr(self, "cd_130_signal_" + str(ind_cd) + '_signal'))  # noqa: E501
+                info.append("\n  130 Calibration Step Interval " + getattr(self, "cd_130_signal_" + str(ind_cd) + '_step_interval'))  # noqa: E501
+                info.append("\n  130 Calibration Step Width " + getattr(self, "cd_130_signal_" + str(ind_cd) + '_step_width'))  # noqa: E501
+                info.append("\n  130 Calibration Sine Frequency " + getattr(self, "cd_130_signal_" + str(ind_cd) + '_sine_frequency'))  # noqa: E501
+
+        for ind_cd in range(1, CD_MAX_NBR_STRUCT + 1):
+            sequence_sensor = getattr(self, "cd_130_sequence_" + str(ind_cd) + '_sensor')  # noqa: E501
+            if sequence_sensor.strip():
+                info.append("\n  130 Calibration Sequence " + sequence_sensor)
+                info.append("\n  130 Calibration Sequence Enable " + getattr(self, "cd_130_sequence_" + str(ind_cd) + '_enable'))  # noqa: E501
+                info.append("\n  130 Calibration Sequence Start Time " + getattr(self, "cd_130_sequence_" + str(ind_cd) + '_start_time'))  # noqa: E501
+                info.append("\n  130 Calibration Sequence Interval " + getattr(self, "cd_130_sequence_" + str(ind_cd) + '_interval'))  # noqa: E501
+                info.append("\n  130 Calibration Sequence Count " + getattr(self, "cd_130_sequence_" + str(ind_cd) + '_count'))  # noqa: E501
+                info.append("\n  130 Calibration Sequence Record Length " + getattr(self, "cd_130_sequence_" + str(ind_cd) + '_record_length'))  # noqa: E501
+        return info
+
+
+class FDPacket(Packet):
+    """Class used to parse and generate string representation for FD packets"""
+
+    def __init__(self, data):
+        # Filter description payload
+        self._data = data
+        payload = self._data["payload"]
+        start_fd = 0
+        for name, (length, converter) in FD_PAYLOAD.items():
+            data = payload[start_fd:start_fd + length]
+            if converter is not None and data.size != 0:
+                try:
+                    data = converter(data.tobytes())
+                except ValueError:
+                    msg = ("FD packet, wrong conversion routine for input "
+                           "variable: {}".format(name))
+                    warnings.warn(msg)
+                    data = ''
+            setattr(self, name, data)
+            start_fd = start_fd + length
+        # Detailed info for filter block(s) (fb)
+        # Warning - rt130 manual section on FD packets is not very clear.
+        # The following code needs to be further tested if FD packets
+        # contains several filter blocks or if packet coefficient count
+        # in a given filter block is superior to 248 32-bit coefficients.
+        # Test data including these scenarios is however lacking.
+        start_info = 0
+        nbr_fbs = self.fd_info[0] + 1
+        setattr(self, 'nbr_fbs', nbr_fbs)
+        if nbr_fbs > 1:
+            msg = ("FD packet contains more than one filter block - "
+                   "Handling of additional block has not been fully tested")
+            warnings.warn(msg)
+        for ind_fb in range(1, nbr_fbs + 1):
+            for name, (length, converter) in FD_INFO.items():
+                if name == "_coeff":
+                    coeff_size = int(getattr(self, "fb" + str(ind_fb) + "_coeff_format")[0] / 8)  # noqa: E501
+                    setattr(self, "fb" + str(ind_fb) + '_coeff_size', coeff_size)  # noqa: E501
+                    length = getattr(self, "fb" + str(ind_fb) + "_packet_coeff_count") * coeff_size  # noqa: E501
+                data = self.fd_info[start_info:start_info + length]
+                if converter is not None:
+                    if converter is _decode_ascii:
+                        data = data.tobytes()
+                    try:
+                        data = converter(data)
+                    except ValueError:
+                        msg = ("FD packet, filter block info, wrong "
+                               "conversion routine for input variable: {}"
+                               .format(name))
+                        warnings.warn(msg)
+                        data = ''
+                name = "fb" + str(ind_fb) + name
+                setattr(self, name, data)
+                start_info = start_info + length
+
+    def __str__(self):
+        info = []
+        # info.append(self.packet_tagline)
+        packet_soh_string = ("\nFilter Description  {:s}   ST: {:s}"
+                             .format(self.time_tag(self.time, implement_time=self.implement_time),  # noqa: E501
+                                     self.unit_id.decode()))
+        info.append(packet_soh_string)
+        for ind_fb in range(1, self.nbr_fbs + 1):
+            info.append("\n     Filter Block Count " + str(getattr(self, "fb" + str(ind_fb) + "_filter_block_count")))  # noqa: E501
+            info.append("\n     Filter ID " + getattr(self, "fb" + str(ind_fb) + "_filter_ID"))  # noqa: E501
+            info.append("\n     Filter Decimation " + str(getattr(self, "fb" + str(ind_fb) + "_filter_decimation")))  # noqa: E501
+            info.append("\n     Filter Scalar " + str(getattr(self, "fb" + str(ind_fb) + "_filter_scalar")))  # noqa: E501
+            info.append("\n     Filter Coefficient Count " + str(getattr(self, "fb" + str(ind_fb) + "_filter_coeff_count")))  # noqa: E501
+            info.append("\n     Filter Packet Coefficient Count " + str(getattr(self, "fb" + str(ind_fb) + "_packet_coeff_count")))  # noqa: E501
+            info.append("\n     Filter Coefficient Packet Count " + str(getattr(self, "fb" + str(ind_fb) + "_coeff_packet_count")))  # noqa: E501
+            info.append("\n     Filter Coefficient Format " + str(getattr(self, "fb" + str(ind_fb) + "_coeff_format")[0]))  # noqa: E501
+            info.append("\n     Filter Coefficients:")
+            start_coeff = 0
+            for inf_coeff in range(0, getattr(self, "fb" + str(ind_fb) + "_packet_coeff_count")):  # noqa: E501
+                length = getattr(self, "fb" + str(ind_fb) + '_coeff_size')  # noqa: E501
+                coeff = getattr(self, "fb" + str(ind_fb) + "_coeff")[start_coeff:start_coeff + length]  # noqa: E501
+                start_coeff = start_coeff + length
+                twosCom_bin = ''.join([self.twosCom_dec2bin(x, 8) for x in coeff])  # noqa: E501
+                coeff_dec = self.twosCom_bin2dec(twosCom_bin, length * 8)
+                info.append("  " + str(coeff_dec))
+        return info
+
+    @staticmethod
+    def twosCom_bin2dec(bin_, digit):
+        while len(bin_) < digit:
+            bin_ = '0' + bin_
+        if bin_[0] == '0':
+            return int(bin_, 2)
+        else:
+            return -1 * (int(''.join('1' if x == '0' else '0' for x in bin_), 2) + 1)  # noqa: E501
+
+    @staticmethod
+    def twosCom_dec2bin(dec, digit):
+        if dec >= 0:
+            bin_ = bin(dec).split("0b")[1]
+            while len(bin_) < digit:
+                bin_ = '0' + bin_
+            return bin_
+        else:
+            bin_ = -1 * dec
+            return bin(dec - pow(2, digit)).split("0b")[1]
+
+
+def _initial_unpack_packets_soh(bytestring):
+    """
+    First unpack data with dtype matching itemsize of storage in the reftek
+    file, than allocate result array with dtypes for storage of python
+    objects/arrays and fill it with the unpacked data.
+    """
+    if not len(bytestring):
+        return np.array([], dtype=PACKET_FINAL_DTYPE)
+
+    if len(bytestring) % 1024 != 0:
+        tail = len(bytestring) % 1024
+        bytestring = bytestring[:-tail]
+        msg = ("Length of data not a multiple of 1024. Data might be "
+               "truncated. Dropping {:d} byte(s) at the end.").format(tail)
+        warnings.warn(msg)
+    data = from_buffer(
+        bytestring, dtype=PACKET_INITIAL_UNPACK_DTYPE)
+    result = np.empty_like(data, dtype=PACKET_FINAL_DTYPE)
+
+    for name, dtype_initial, converter, dtype_final in PACKET:
+        if converter is None:
+            result[name][:] = data[name][:]
+        else:
+            try:
+                result[name][:] = converter(data[name])
+            except Exception as e:
+                raise Reftek130UnpackPacketError(str(e))
+    # time unpacking is special and needs some additional work.
+    # we need to add the POSIX timestamp of the start of respective year to the
+    # already unpacked seconds into the respective year..
+    result['time'][:] += [_get_nanoseconds_for_start_of_year(y)
+                          for y in result['year']]
+    return result
diff --git a/sohstationviewer/model/reftek/logInfo.py b/sohstationviewer/model/reftek/logInfo.py
new file mode 100644
index 0000000000000000000000000000000000000000..1947dbe7adf2717999bbe1feab5e0f36cb86f81d
--- /dev/null
+++ b/sohstationviewer/model/reftek/logInfo.py
@@ -0,0 +1,406 @@
+
+from sohstationviewer.conf import constants
+from sohstationviewer.controller.util import (
+    displayTrackingInfo, getTime6, getTime4, getVal,
+    rtnPattern)
+
+
+class LogInfo():
+    def __init__(self, parent, parentGUI, logText, key, packetType, reqDSs,
+                 isLogFile=False):
+        self.packetType = packetType
+        self.parent = parent
+        self.parentGUI = parentGUI
+        self.logText = logText
+        self.key = key
+        self.unitID, self.expNo = key
+        self.reqDSs = reqDSs
+        self.isLogFile = isLogFile
+        """
+        trackYear to add year to time since year time not include year after
+        header.
+        yearChanged: if doy=1, trackYear is added 1, yAdded is marked
+        this has happened to only added 1 once.
+        """
+        self.yAdded = False
+        self.trackYear = 0
+        if self.unitID >= "9000":
+            self.model = "RT130"
+        else:
+            self.model = "72A"
+        self.maxEpoch = 0
+        self.minEpoch = constants.HIGHEST_INT
+        self.chans = self.parent.plottingData[self.key]['channels']
+        self.CPUVers = set()
+        self.GPSVers = set()
+        self.extractInfo()
+
+    def readEVT(self, line):
+        # Ex: DAS: 0108 EV: 2632 DS: 2 FST = 2001:253:15:13:59:768
+        #       TT =2001:253:15:13:59:768 NS: 144005 SPS: 40 ETO: 0
+        parts = line.split()
+        DS = int(parts[5])
+        if DS not in self.parent.reqDSs:
+            return (0, 0)
+        try:
+            if parts[8].startswith("00:000"):
+                if parts[11].startswith("00:000"):
+                    return -1, 0
+                epoch, _ = getTime6(parts[11])
+            else:
+                epoch, _ = getTime6(parts[8])
+        except AttributeError:
+            self.parent.processingLog.append(line, 'error')
+            return False
+        if epoch > 0:
+            self.minEpoch = min(epoch, self.minEpoch)
+            self.maxEpoch = max(epoch, self.maxEpoch)
+        else:
+            return 0, 0
+        return epoch, DS
+
+    def readSHHeader(self, line):
+        # Ex: State of Health  01:251:09:41:35:656   ST: 0108
+        parts = line.split()
+        try:
+            epoch, self.trackYear = getTime6(parts[3])
+        except AttributeError:
+            self.parent.processingLog.append(line, 'error')
+            return False
+        self.yAdded = False         # reset yAdded
+        self.minEpoch = min(epoch, self.minEpoch)
+        self.maxEpoch = max(epoch, self.maxEpoch)
+        unitID = parts[5].strip()
+        if unitID != self.unitID:
+            msg = ("The State Of Health messages for DAS %s were being "
+                   "read, but now there is information for DAS %s in "
+                   "the same State Of Health messages.\n"
+                   "Skip reading State Of Health." % (self.unitID, unitID))
+            displayTrackingInfo(self.parentGUI, msg, 'error')
+            False
+        return epoch
+
+    def simpleRead(self, line):
+        # Ex: 186:21:41:35 <content>
+        parts = line.split()
+        try:
+            epoch, self.trackYear, self.yAdded = getTime4(
+                parts[0], self.trackYear, self.yAdded)
+        except AttributeError:
+            self.parent.processingLog.append(line, 'error')
+            return False
+        self.maxEpoch = max(epoch, self.maxEpoch)
+        return parts, epoch
+
+    def readIntClockPhaseErr(self, line):
+        # Ex: 253:19:41:42 INTERNAL CLOCK PHASE ERROR OF 4823 USECONDS
+        ret = self.simpleRead(line)
+        if not ret:
+            return False
+        parts, epoch = ret
+        error = float(parts[-2])
+        # if parts[-1].startswith("USEC"): bc requested unit is us
+        #     error /= 1000.0
+        if parts[-1].startswith("SEC"):
+            error *= 1000000.0
+        return epoch, error
+
+    def readBatTemBkup(self, line):
+        # 72A's:
+        # Ex: 186:14:33:58 BATTERY VOLTAGE = 13.6V, TEMPERATURE = 26C
+        # RT130:
+        # Ex: 186:14:33:58 BATTERY VOLTAGE = 13.6V, TEMPERATURE = 26C,
+        #    BACKUP = 3.3V
+        parts, epoch = self.simpleRead(line)
+        try:
+            volts = getVal(parts[4])
+            temp = getVal(parts[7])
+        except AttributeError:
+            self.parent.processingLog.append(line, 'error')
+            return False
+        if self.model == "RT130":
+            bkupV = getVal(parts[10])
+        else:
+            bkupV = 0.0
+        return epoch, volts, temp, bkupV
+
+    def readDiskUsage(self, line):
+        # RT130:
+        # Ex: 186:14:33:58 DISK 1: USED: 89744 AVAIL:...
+        # Ex: 186:14:33:58 DISK 2* USED: 89744 AVAIL:...
+        parts, epoch = self.simpleRead(line)
+        try:
+            disk = getVal(parts[2])
+            val = getVal(parts[4])
+        except AttributeError:
+            self.parent.processingLog.append(line, 'error')
+            return False
+        return epoch, disk, val
+
+    def readDPS_ClockDiff(self, line):
+        # Ex: 245:07:41:45 DSP CLOCK DIFFERENCE: 0 SECS AND -989 MSECS
+        parts, epoch = self.simpleRead(line)
+        try:
+            secs = getVal(parts[4])
+            msecs = getVal(parts[-2])
+        except AttributeError:
+            self.parent.processingLog.append(line, 'error')
+            return False
+        total = abs(secs) * 1000.0 + abs(msecs)
+        if secs < 0.0 or msecs < 0.0:
+            total *= -1.0
+        return epoch, total
+
+    def readDefs(self, line):
+        # Ex: Station Channel Definition   01:330:19:24:42:978    ST: 7095
+        # Ex: Data Stream Definition   01:330:19:24:42:978    ST: 7095
+        # Ex: Calibration Definition   01:330:19:24:42:978    ST: 7095
+        # Ex: STATION CHANNEL DEFINITION  2020:066:19:00:56:000   ST: 92E9
+        parts = line.split()
+        # Lines from a .log file may be just the first time the parameters were
+        # saved  until the DAS is reset, so if this is a log file then use the
+        # current SOH time for plotting the points, instead of what is in the
+        # message line.
+        # if parts[0] in ["STATION", "DATA", "CALIBRATION"]:
+        # TODO: check if want to plot other def
+        if parts[0] in ["STATION"]:
+            if self.isLogFile is False:
+                try:
+                    epoch, _ = getTime6(parts[-3])
+                except AttributeError:
+                    self.parent.processingLog.append(line, 'error')
+                    return False
+            else:
+                epoch = self.maxEpoch
+        else:
+            return False
+        # These will not be allowed to set TimeMin since the RT130's save
+        # multiple  copies of the parameters in the log files (one per day),
+        # but they all have the date/timestamp of the first time the parameters
+        # were written (unless a new copy is written because the parameters
+        # were changed, of course).
+        return epoch
+
+    def readCPUVer(self, line):
+        # Ex: 341:22:05:41 CPU SOFTWARE V03.00H  (72A and older 130 FW)
+        # Ex: 341:22:05:41 REF TEK 130  (no version number at all)
+        # Ex: 341:22:05:41 Ref Tek 130  2.8.8S (2007:163)
+        # Ex: 341:22:05:41 CPU SOFTWARE V 3.0.0 (2009:152)
+        parts = line.split()
+        if parts[1].startswith("CPU"):
+            CPUVer = " ".join(parts[3:])
+        elif parts[1].upper().startswith("REF"):
+            # There may not be any version info in this line:
+            # Ex: REF TEK 130...then nothing
+            # but accessing non-existant InParts doesn't matter.
+            # CPUVer will just be "".
+            CPUVer = " ".join(parts[4:])
+        return CPUVer
+
+    def readGPSVer(self, line):
+        parts = line.split()
+        verParts = [p.strip() for p in parts]
+        if "GPS FIRMWARE VERSION:" in line:
+            # 130 Ex: 291:19:54:29 GPS FIRMWARE VERSION: GPS 16-HVS Ver. 3.20
+            GPSVer = ' '.join(verParts[4:])
+        else:
+            # 72A: Ex: 159:15:41:05 GPS: V03.30    (17JAN2000)
+            GPSVer = " ".join(verParts[2:])
+        return GPSVer
+
+    def addChanInfo(self, chanName, t, d, idx):
+        if chanName not in self.chans:
+            self.chans[chanName] = {
+                'unitID': self.unitID,
+                'expNo': self.expNo,
+                'times': [],
+                'data': [],
+                'logIdx': []}
+        self.chans[chanName]['times'].append(t)
+        self.chans[chanName]['data'].append(d)
+        self.chans[chanName]['logIdx'].append(idx)
+
+    def extractInfo(self):
+
+        lines = [ln.strip() for ln in self.logText.splitlines() if ln != '']
+        sohEpoch = 0
+
+        for idx, line in enumerate(lines):
+            line = line.upper()
+            if 'FST' in line:
+                ret = self.readEVT(line)
+                if ret:
+                    epoch, DS = ret
+                    if DS in self.reqDSs:
+                        if epoch > 0:
+                            chanName = 'Event DS%s' % DS
+                            self.addChanInfo(chanName, epoch, 1, idx)
+                        elif epoch == 0:
+                            self.parent.processingLog.append(line, 'warning')
+                        else:
+                            self.parent.processingLog.append(line, 'error')
+
+            elif line.startswith("STATE OF HEALTH"):
+                epoch = self.readSHHeader(line)
+                if epoch:
+                    self.addChanInfo('SOH/Data Def', epoch, 1, idx)
+            elif "DEFINITION" in line:
+                epoch = self.readDefs(line)
+                if epoch:
+                    self.addChanInfo('SOH/Data Def', epoch, 0, idx)
+
+            if "INTERNAL CLOCK PHASE ERROR" in line:
+                ret = self.readIntClockPhaseErr(line)
+                if ret:
+                    epoch, err = ret
+                    self.addChanInfo('Clk Phase Err', epoch, err, idx)
+
+            elif "POSSIBLE DISCREPANCY" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Discrepancies', epoch, 1, idx)
+
+            elif "BATTERY VOLTAGE" in line:
+                ret = self.readBatTemBkup(line)
+                if ret:
+                    epoch, volts, temp, bkupV = ret
+                    self.addChanInfo('Battery Volt', epoch, volts, idx)
+                    self.addChanInfo('DAS Temp', epoch, temp, idx)
+                    # bkupV: x<3, 3<=x<3.3, >3.3
+                    self.addChanInfo('Backup Volt', epoch, bkupV, idx)
+
+            elif all(x in line for x in ['DISK', 'USED']):
+                ret = self.readDiskUsage(line)
+                if ret:
+                    epoch, disk, val = ret
+                    self.addChanInfo(f'Disk Usage{int(disk)}', epoch, val, idx)
+
+            elif "AUTO DUMP CALLED" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Dump Called/Comp', epoch, 1, idx)
+            elif "AUTO DUMP COMPLETE" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Dump Called/Comp', epoch, 0, idx)
+
+            elif "DSP CLOCK SET" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Jerks/DSP Sets', epoch, 1, idx)
+            elif 'JERK' in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Jerks/DSP Sets', epoch, 0, idx)
+
+            elif "DPS clock diff" in line:
+                ret = self.readDPS_ClockDiff()
+                if ret:
+                    epoch, total = ret
+                    self.addChanInfo('DPS Clock Diff', epoch, total, idx)
+
+            elif "ACQUISITION STARTED" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('ACQ On/Off', epoch, 1, idx)
+            elif "ACQUISITION STOPPED" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('ACQ On/Off', epoch, 0, idx)
+
+            elif "NETWORK LAYER IS UP" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Net Up/Down', epoch, 0, idx)
+            elif "NETWORK LAYER IS DOWN" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Net Up/Down', epoch, 1, idx)
+
+            elif "MASS RE-CENTER" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Mass Re-center', epoch, idx)
+
+            elif any(x in line for x in ["SYSTEM RESET", "FORCE RESET"]):
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Reset/Power-up', epoch, 0, idx)
+            elif "SYSTEM POWERUP" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Reset/Power-up', epoch, 1, idx)
+
+            # ================= GPS ==================================
+            elif "EXTERNAL CLOCK IS UNLOCKED" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('GPS Lk/Unlk', epoch, 0, idx)
+            elif "EXTERNAL CLOCK IS LOCKED" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('GPS Lk/Unlk', epoch, 1, idx)
+
+            elif any(x in line for x in ["EXTERNAL CLOCK POWER IS TURNED ON",
+                                         "EXTERNAL CLOCK WAKEUP",
+                                         "GPS: POWER IS TURNED ON",
+                                         "GPS: POWER IS CONTINUOUS"]
+                     ):
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('GPS On/Off/Err', epoch, 1, idx)
+            elif any(x in line for x in ["EXTERNAL CLOCK POWER IS TURNED OFF",
+                                         "EXTERNAL CLOCK SLEEP",
+                                         "GPS: POWER IS TURNED OFF",
+                                         "NO EXTERNAL CLOCK"]
+                     ):
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('GPS On/Off/Err', epoch, 0, idx)
+            elif "EXTERNAL CLOCK ERROR" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('GPS On/Off/Err', epoch, -1, idx)
+
+            elif "EXTERNAL CLOCK CYCLE" in line:
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('GPS Clock Power', epoch, 1, idx)
+
+            # ================= VERSIONS ==============================
+            elif any(x in line for x in ["REF TEK", "CPU SOFTWARE"]):
+                CPUVer = self.readCPUVer(line)
+                self.CPUVers.add(CPUVer)
+
+            elif any(x in line for x in ["GPS FIRMWARE VERSION:", "GPS: V"]):
+                GPSVer = self.readGPSVer(line)
+                self.GPSVers.add(GPSVer)
+
+            # ================= ERROR/WARNING =========================
+            elif "ERROR:" in line:
+                # These lines are generated by programs like ref2segy and do
+                # not have any time associated with them so just use whatever
+                # time was last in Time.
+                self.addChanInfo('Error/Warning', sohEpoch, -2, idx)
+            elif any(x in line for x in ["NO VALID DISK", "FAILED"]):
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Error/Warning', epoch, -1, idx)
+
+            elif "WARNING" in line:
+                # Warings come in lines with the time from the RT130 and lines
+                # created by programs without times.
+                if rtnPattern(line[:12]) == "000:00:00:00":
+                    epoch = self.simpleRead(line)[1]
+                    if epoch:
+                        self.addChanInfo('Error/Warning', epoch, 0, idx)
+                else:
+                    # Just use whatever time was last in Time.
+                    # The 2 means that the dots will be a little different.
+                    self.addChanInfo('Error/Warning', sohEpoch, 1, idx)
+
+            elif (all(x in line for x in ["BAD", "MAKER"]) or
+                  any(x in line for x in ["ERTFS", "IDE BUSY"])):
+                epoch = self.simpleRead(line)[1]
+                if epoch:
+                    self.addChanInfo('Error/Warning', epoch, 0, idx)
diff --git a/sohstationviewer/model/reftek/reftek.py b/sohstationviewer/model/reftek/reftek.py
new file mode 100755
index 0000000000000000000000000000000000000000..48e3a4e891c4f1e88e579f61c8da5fcff220cb45
--- /dev/null
+++ b/sohstationviewer/model/reftek/reftek.py
@@ -0,0 +1,210 @@
+
+import os
+import numpy as np
+
+from obspy import UTCDateTime
+from obspy.core import Stream
+
+from sohstationviewer.model.reftek.from_rt2ms import (
+    core, soh_packet, packet)
+from sohstationviewer.model.reftek.logInfo import LogInfo
+from sohstationviewer.conf import constants
+
+from sohstationviewer.model.dataType import DataType
+
+
+class RT130(DataType):
+    def __init__(self, *args, **kwarg):
+        self.EH = {}
+        super().__init__(*args, **kwarg)
+
+    def readFile(self, path, fileName):
+        if not self.readReftek130(path, fileName):
+            self.readText(path, fileName)
+
+    def readReftek130(self, path, fileName):
+        path2file = os.path.join(path, fileName)
+        rt130 = core.Reftek130.from_file(path2file)
+        unique, counts = np.unique(rt130._data["packet_type"],
+                                   return_counts=True)
+        nbr_packet_type = dict(zip(unique, counts))
+
+        if b"SH" in nbr_packet_type:
+            self.readSH(path2file)
+        if b"EH" in nbr_packet_type or b"ET" in nbr_packet_type:
+            self.readEHET(rt130)
+        return True
+
+    def readEHET(self, rt130):
+        DS = rt130._data['data_stream_number'][0] + 1
+        if DS not in self.reqDSs + [9]:
+            return
+
+        ind_EHET = [ind for ind, val in
+                    enumerate(rt130._data["packet_type"])
+                    if val in [b"EH"]]     # on ly need event header
+        nbr_DT_samples = sum(
+            [rt130._data[ind]["number_of_samples"]
+             for ind in range(0, len(rt130._data))
+             if ind not in ind_EHET])
+
+        for ind in ind_EHET:
+            d = rt130._data[ind]
+            self.curKey = (d['unit_id'].decode(),
+                           d['experiment_number'])
+            if self.curKey not in self.logData:
+                self.logData[self.curKey] = {}
+            logs = packet.EHPacket(d).eh_et_info(nbr_DT_samples)
+            self.addLog('EHET', (d['time'], logs))
+
+        stream = core.Reftek130.to_stream(
+            rt130,
+            headonly=False,
+            verbose=False,
+            sort_permuted_package_sequence=True)
+        for trace in stream:
+            k = (d['unit_id'].decode(), d['experiment_number'])
+            if k not in self.streams.keys():
+                self.streams[self.curKey] = Stream()
+            self.streams[k].append(trace)
+
+    def readSH(self, path2file):
+        with open(path2file, "rb") as fh:
+            str = fh.read()
+        data = soh_packet._initial_unpack_packets_soh(str)
+        for ind, d in enumerate(data):
+            self.curKey = (d['unit_id'].decode(),
+                           d['experiment_number'])
+            if self.curKey not in self.logData:
+                self.logData[self.curKey] = {}
+            logs = soh_packet.Packet.from_data(d).__str__()
+            # self.addLog(d['packet_type'].decode(), (d['time'], logs))
+            self.addLog('SOH', (d['time'], logs))
+
+    def readTrace(self, trace, channels):
+        chan = {}
+        chan['netID'] = trace.stats['network']
+        chan['statID'] = trace.stats['station']
+        chan['locID'] = trace.stats['location']
+        chanID = chan['chanID'] = trace.stats['channel']
+
+        chan['samplerate'] = trace.stats['sampling_rate']
+        chan['startTmEpoch'] = trace.stats['starttime'].timestamp
+        chan['endTmEpoch'] = trace.stats['endtime'].timestamp
+        """
+        trace time start with 0 => need to add with epoch starttime
+        """
+        chan['times'] = trace.times() + trace.stats['starttime'].timestamp
+        chan['data'] = trace.data
+        if chanID.startswith('MP'):
+            # calculation based on Logpeek's rt130MPDecode()
+            # TODO: MP only has 4 different values, data can be simplified
+            # by removing data point with same values in a row
+            # after converting, the variety of values for MP even reduce more
+            chan['data'] = np.round_(chan['data'] / 3276.7, 1)
+
+        if chanID not in channels.keys():
+            channels[chanID] = chan
+        else:
+            msg = ("Something wrong with code logic."
+                   "After stream is merged there should be only one trace "
+                   "for channel %s: %s" % (chanID, trace))
+            self.trackInfo(msg, "error")
+        return trace.stats['starttime'], trace.stats['endtime']
+
+    def combineData(self):
+        for k in self.logData:
+            self.plottingData[k] = {
+                'gaps': [],
+                'channels': {}
+            }
+            logs = []
+            for pktType in ['SOH', 'EHET']:
+                if pktType == 'EHET':
+                    logs += '\n\nEvents:'
+                try:
+                    for log in sorted(self.logData[k][pktType],
+                                      key=lambda x: x[0]):
+                        # sort log data according to time
+                        logs += log[1]
+                        if pktType == 'SOH':
+                            logs += '\n'
+                except KeyError:
+                    pass
+            logStr = ''.join(logs)
+            self.logData[k][pktType] = logStr
+            logObj = LogInfo(
+                self, self.parentGUI, logStr, k, pktType, self.reqDSs)
+            self.plottingData[k]['earliestUTC'] = logObj.minEpoch
+            self.plottingData[k]['latestUTC'] = logObj.maxEpoch
+            for cName in self.plottingData[k]['channels']:
+                c = self.plottingData[k]['channels'][cName]
+                c['times'] = np.array(c['times'])
+                c['data'] = np.array(c['data'])
+                c['logIdx'] = np.array(c['logIdx'])
+
+        for k in self.streams:
+            stream = self.streams[k]
+            stream.merge()
+            # gaps is list of [network, station, location, channel, starttime,
+            # endtime, duration, number of missing samples]
+            gaps = stream.get_gaps()
+            # stream.print_gaps()
+            gaps = self.squashGaps(gaps)
+            if k not in self.plottingData:
+                self.plottingData[k] = {'gaps': gaps,
+                                        'channels': {}}
+            maxEndtime = UTCDateTime(0)
+            minStarttime = UTCDateTime(20E10)
+            for trace in stream:
+                startTm, endTm = self.readTrace(
+                    trace, self.plottingData[k]['channels'])
+                if startTm < minStarttime:
+                    minStarttime = startTm
+                if endTm > maxEndtime:
+                    maxEndtime = endTm
+            self.plottingData[k]['earliestUTC'] = min(
+                minStarttime.timestamp,
+                self.plottingData[k]['earliestUTC'])
+            self.plottingData[k]['latestUTC'] = max(
+                maxEndtime.timestamp,
+                self.plottingData[k]['latestUTC'])
+
+    def squashGaps(self, gaps):
+        """
+        Squash different lists of gaps for all channels to be one list of
+        (minStarttime, maxEndtime) gaps
+        : param gaps: list of [network, station, location, channel, starttime,
+        endtime, duration, number of missing samples]
+        : return squashedGaps
+        """
+        if len(gaps) == 0:
+            return gaps
+
+        # create dict consist of list of gaps for each channel
+        gaps_dict = {}
+        for g in gaps:
+            if g[3] not in gaps_dict:
+                gaps_dict[g[3]] = []
+            gaps_dict[g[3]].append((g[4].timestamp, g[5].timestamp))
+
+        firstLen = len(gaps_dict[gaps[0][3]])
+        diffLenList = [len(gaps_dict[k]) for k in gaps_dict.keys()
+                       if len(gaps_dict[k]) != firstLen]
+        if len(diffLenList) > 0:
+            msg = "Number of Gaps for different channel are not equal.\n"
+            for k in gaps_dict.keys():
+                msg += "%s: %s\n" % (k, len(gaps_dict[k]))
+            self.trackInfo(msg, 'error')
+            return []
+        squashedGaps = []
+        for i in range(firstLen):
+            maxEndtime = 0
+            minStarttime = constants.HIGHEST_INT
+            for val in gaps_dict.values():
+                if val[i][0] < minStarttime:
+                    minStarttime = val[i][0]
+                if val[i][1] > maxEndtime:
+                    maxEndtime = val[i][1]
+            squashedGaps.append((minStarttime, maxEndtime))
+        return squashedGaps
diff --git a/sohstationviewer/sohstationviewer.py b/sohstationviewer/sohstationviewer.py
deleted file mode 100644
index d0c9153db95f19821308220463d5f97cf7ed16b1..0000000000000000000000000000000000000000
--- a/sohstationviewer/sohstationviewer.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import sys
-from PySide2 import QtWidgets
-
-
-def main():
-    app = QtWidgets.QApplication()
-    # gui = MainGUI
-    sys.exit(app.exec_())
-
-
-if __name__ == '__main__':
-    main()
diff --git a/sohstationviewer/view/calendardialog.py b/sohstationviewer/view/calendardialog.py
index 0303bce3a98955f7759085a1e5b7ab11435b1120..afb18249a3ae8f3fe3ffad6941f1913f4c2d5246 100644
--- a/sohstationviewer/view/calendardialog.py
+++ b/sohstationviewer/view/calendardialog.py
@@ -1,6 +1,6 @@
 from PySide2 import QtWidgets
 
-from sohstationviewer.view.ui.calendar_ui import Ui_CalendarDialog
+from sohstationviewer.view.ui.calendar_ui_qtdesigner import Ui_CalendarDialog
 
 
 class CalendarDialog(QtWidgets.QDialog, Ui_CalendarDialog):
diff --git a/sohstationviewer/view/channeldialog.py b/sohstationviewer/view/channeldialog.py
new file mode 100755
index 0000000000000000000000000000000000000000..205f701d65d7738a4c7f4467a024b9c96db360d3
--- /dev/null
+++ b/sohstationviewer/view/channeldialog.py
@@ -0,0 +1,88 @@
+"""
+channeldialog.py
+GUI to add/edit/remove channels
+"""
+
+from sohstationviewer.view.core.dbgui_superclass import Ui_DBInfoDialog
+from sohstationviewer.database.proccessDB import executeDB
+
+
+class ChannelDialog(Ui_DBInfoDialog):
+    def __init__(parent, self):
+        super().__init__(
+            parent, ['No.', 'Channel', 'Label', 'Param',
+                     'ConvertFactor', 'Unit', 'FixPoint'],
+            'channel', 'channels', resizeContentColumns=[0, 4, 5, 6],
+            needDataTypeChoice=True, requestedColumns={3: 'Param'},
+            checkFK=False)
+        self.setWindowTitle("Edit/Add/Delete Channels")
+
+    def updateDataTableWidgetItems(self):
+        paraRows = executeDB("SELECT param from parameters")
+        self.paramChoices = [''] + sorted([d[0] for d in paraRows])
+        super(ChannelDialog, self).updateDataTableWidgetItems()
+
+    def clearFirstRow(self):
+        """
+        device with no channels yet, there will be empty channel left
+        """
+        self.dataTableWidget.cellWidget(0, 1).setText('')
+        self.dataTableWidget.cellWidget(0, 2).setText('')
+        self.dataTableWidget.cellWidget(0, 3).setCurrentIndex(-1)
+        self.dataTableWidget.cellWidget(0, 4).setText('1')
+        self.dataTableWidget.cellWidget(0, 5).setText('')
+        self.dataTableWidget.cellWidget(0, 6).setValue(0)
+
+    def addRow(self, rowidx, fk=False):
+        self.addWidget(None, rowidx, 0)       # No.
+        self.addWidget(self.dataList, rowidx, 1, foreignkey=fk)  # chanID
+        self.addWidget(self.dataList, rowidx, 2)                 # label
+        self.addWidget(self.dataList, rowidx, 3, choices=self.paramChoices)
+        self.addWidget(self.dataList, rowidx, 4,
+                       fieldName='convertFactor')
+        self.addWidget(self.dataList, rowidx, 5)                 # unit
+        self.addWidget(self.dataList, rowidx, 6,
+                       range=[0, 5])                     # fixPoint
+
+    def dataTypeChanged(self):
+        self.dataType = self.dataTypeCombobox.currentText()
+        self.updateDataTableWidgetItems()
+
+    def getDataList(self):
+        channelRows = executeDB(
+            f"SELECT channel, label, param, convertFactor, unit, fixPoint "
+            f"FROM Channels "
+            f"WHERE dataType='{self.dataType}'")
+        return [[d[0], d[1], d[2], d[3],
+                 '' if d[4] is None else d[4],
+                 d[5]]
+                for d in channelRows]
+
+    def getRowInputs(self, rowidx):
+        return [
+            self.dataTableWidget.cellWidget(rowidx, 1).text().strip(),
+            self.dataTableWidget.cellWidget(rowidx, 2).text().strip(),
+            self.dataTableWidget.cellWidget(rowidx, 3).currentText().strip(),
+            float(self.dataTableWidget.cellWidget(rowidx, 4).text()),
+            self.dataTableWidget.cellWidget(rowidx, 5).text(),
+            self.dataTableWidget.cellWidget(rowidx, 6).value()
+        ]
+
+    def removeRow(self, removeRowidx):
+        self.dataTableWidget.removeRow(removeRowidx)
+        for i in range(removeRowidx, self.dataTableWidget.rowCount()):
+            cellWget = self.dataTableWidget.cellWidget(i, 0)
+            cellWget.setText(str(i))
+
+    def updateData(self, row, widgetidx, listidx):
+        insertsql = (f"INSERT INTO Channels VALUES"
+                     f"('{row[0]}', '{row[1]}', '{row[2]}',"
+                     f" {row[3]}, '{row[4]}', {row[5]}, '{self.dataType}')")
+        updatesql = (f"UPDATE Channels SET channel='{row[0]}', "
+                     f"label='{row[1]}', param='{row[2]}', "
+                     f"convertFactor={row[3]}, unit='{row[4]}' "
+                     f"fixPoint={row[5]} "
+                     f"WHERE channel='%s'"
+                     f" AND dataType='{self.dataType}'")
+        return super().updateData(
+            row, widgetidx, listidx, insertsql, updatesql)
diff --git a/sohstationviewer/view/channelpreferdialog.py b/sohstationviewer/view/channelpreferdialog.py
new file mode 100755
index 0000000000000000000000000000000000000000..c246e70dac29fc99fada0590b57401d986a14168
--- /dev/null
+++ b/sohstationviewer/view/channelpreferdialog.py
@@ -0,0 +1,299 @@
+from PySide2 import QtWidgets, QtCore
+
+from sohstationviewer.database.proccessDB import (
+    executeDB, trunc_addDB, executeDB_dict)
+from sohstationviewer.controller.processing import readChannels, detectDataType
+from sohstationviewer.controller.util import displayTrackingInfo
+
+INSTRUCTION = """
+Place lists of channels to be read in the IDs field.\n
+Select the radiobutton for the list to be used in plotting.
+"""
+TOTAL_ROW = 20
+
+COL = {'sel': 0, 'name': 1, 'dataType': 2, 'IDs': 3, 'clr': 4}
+
+
+class ChannelPreferDialog(QtWidgets.QWidget):
+    def __init__(self, parent, dirnames):
+        super(ChannelPreferDialog, self).__init__()
+        self.parent = parent
+        self.dirnames = dirnames
+        self.setWindowTitle("Channel Preferences")
+        self.setGeometry(100, 100, 1100, 800)
+        mainLayout = QtWidgets.QVBoxLayout()
+        mainLayout.setContentsMargins(7, 7, 7, 7)
+        self.setLayout(mainLayout)
+
+        mainLayout.addWidget(QtWidgets.QLabel(INSTRUCTION))
+
+        self.createIDsTableWidget()
+        mainLayout.addWidget(self.IDsTableWidget, 1)
+
+        buttonLayout = self.createButtonsSection()
+        mainLayout.addLayout(buttonLayout)
+
+        self.trackingInfoTextBrowser = QtWidgets.QTextBrowser(self)
+        self.trackingInfoTextBrowser.setFixedHeight(60)
+        mainLayout.addWidget(self.trackingInfoTextBrowser)
+        self.changed = False
+
+    def createButtonsSection(self):
+        hLayout = QtWidgets.QHBoxLayout()
+        self.addDBChanBtn = QtWidgets.QPushButton(
+            self, text='Add DB Channels')
+        self.addDBChanBtn.clicked.connect(self.addDBChannels)
+        hLayout.addWidget(self.addDBChanBtn)
+
+        self.scanChanBtn = QtWidgets.QPushButton(
+            self, text='Scan Channels from Data Source')
+        self.scanChanBtn.clicked.connect(self.scanChannels)
+        hLayout.addWidget(self.scanChanBtn)
+
+        self.saveBtn = QtWidgets.QPushButton(self, text='Save')
+        self.saveBtn.clicked.connect(self.save)
+        hLayout.addWidget(self.saveBtn)
+
+        self.save_addMainBtn = QtWidgets.QPushButton(
+            self, text='Save - Add to Main')
+        self.save_addMainBtn.clicked.connect(self.save_addToMainNClose)
+        hLayout.addWidget(self.save_addMainBtn)
+
+        self.closeBtn = QtWidgets.QPushButton(self, text='Cancel')
+        self.closeBtn.clicked.connect(self.close)
+        hLayout.addWidget(self.closeBtn)
+        return hLayout
+
+    def createIDsTableWidget(self):
+        self.IDsTableWidget = QtWidgets.QTableWidget(self)
+        # self.IDsTableWidget.verticalHeader().hide()
+        self.IDsTableWidget.setColumnCount(5)
+        colHeaders = ['', 'Name', 'DataType', 'IDs', 'Clear']
+        self.IDsTableWidget.setHorizontalHeaderLabels(colHeaders)
+        header = self.IDsTableWidget.horizontalHeader()
+        header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
+
+        self.IDsTableWidget.setRowCount(TOTAL_ROW)
+        self.availDataTypes = self.getDataTypes()
+        for rowidx in range(TOTAL_ROW):
+            self.addRow(rowidx)
+
+        self.currRow = -1
+        self.updateDataTableWidgetItems()
+
+    @QtCore.Slot()
+    def addRow(self, rowidx):
+        currSelRadioBtn = QtWidgets.QRadioButton(self)
+        currSelRadioBtn.clicked.connect(
+            lambda checked: self.currSelChanged(rowidx))
+        self.IDsTableWidget.setCellWidget(
+            rowidx, COL['sel'], currSelRadioBtn)
+
+        nameLineEdit = QtWidgets.QLineEdit(self)
+        nameLineEdit.textChanged.connect(self.inputChanged)
+        self.IDsTableWidget.setCellWidget(
+            rowidx, COL['name'], nameLineEdit)
+
+        dataTypeCombobox = QtWidgets.QComboBox(self)
+        dataTypeCombobox.currentIndexChanged.connect(self.inputChanged)
+        dataTypeCombobox.addItems(['Unknown'] + self.availDataTypes)
+        dataTypeCombobox.setCurrentIndex(-1)
+        self.IDsTableWidget.setCellWidget(
+            rowidx, COL['dataType'], dataTypeCombobox)
+
+        IDsLineEdit = QtWidgets.QLineEdit(self)
+        IDsLineEdit.textChanged.connect(self.inputChanged)
+        self.IDsTableWidget.setCellWidget(
+            rowidx, COL['IDs'], IDsLineEdit)
+
+        delButton = QtWidgets.QPushButton(self, text='CLR')
+        delButton.clicked.connect(lambda arg: self.clearIDs(rowidx))
+        self.IDsTableWidget.setCellWidget(
+            rowidx, COL['clr'], delButton)
+
+    @QtCore.Slot()
+    def inputChanged(self):
+        self.changed = True
+
+    def updateDataTableWidgetItems(self):
+        IDsRows = self.getIDsRows()
+        # first row
+        self.IDsTableWidget.cellWidget(0, COL['sel']).setChecked(True)
+        self.currSelChanged(0)
+        count = 0
+        for r in IDsRows:
+            self.IDsTableWidget.cellWidget(
+                count, COL['sel']).setChecked(
+                True if ['current'] == 1 else False)
+            self.IDsTableWidget.cellWidget(
+                count, COL['name']).setText(r['name'])
+            self.IDsTableWidget.cellWidget(
+                count, COL['dataType']).setCurrentText(r['dataType'])
+            self.IDsTableWidget.cellWidget(
+                count, COL['IDs']).setText(r['IDs'])
+            self.IDsTableWidget.cellWidget(
+                count, COL['sel']).setChecked(
+                True if r['current'] == 1 else False)
+            if ['current'] == 1:
+                self.currSelChanged(count)
+            count += 1
+        self.update()
+
+    def getRow(self, rowidx):
+        nameWget = self.IDsTableWidget.cellWidget(rowidx, COL['name'])
+        dataTypeWget = self.IDsTableWidget.cellWidget(rowidx, COL['dataType'])
+        IDsWget = self.IDsTableWidget.cellWidget(rowidx, COL['IDs'])
+        clearWget = self.IDsTableWidget.cellWidget(rowidx, COL['clr'])
+        return (nameWget, dataTypeWget, IDsWget, clearWget)
+
+    @QtCore.Slot()
+    def currSelChanged(self, rowidx):
+        self.currRow = rowidx
+        (self.nameWget, self.dataTypeWget,
+         self.IDsWget, self.clearWget) = self.getRow(rowidx)
+        self.inputChanged()
+
+    @QtCore.Slot()
+    def clearIDs(self, rowidx):
+        (nameWget, dataTypeWget, IDsWget, clearWget) = self.getRow(rowidx)
+        if IDsWget.text().strip() != "":
+            msg = ("Are you sure you want to delete the channel IDs of  "
+                   "row #%s?" % (rowidx+1))
+            result = QtWidgets.QMessageBox.question(
+                self, "Confirmation", msg,
+                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+            if result == QtWidgets.QMessageBox.No:
+                return
+            self.changed = True
+        if nameWget.text() != '':
+            self.changed = True
+        nameWget.setText('')
+        if dataTypeWget.currentText != '':
+            self.changed = True
+        dataTypeWget.setCurrentIndex(-1)
+        if IDsWget.text() != '':
+            self.changed = True
+        IDsWget.setText('')
+
+    def validateRow(self, checkDataType=False):
+        if self.currRow == -1:
+            msg = ("Please select a row.")
+            QtWidgets.QMessageBox.information(self, "Select row", msg)
+            return False
+
+        if checkDataType:
+            self.dataType = self.dataTypeWget.currentText()
+            if self.dataType not in self.availDataTypes:
+                msg = ("Please select a data type that isn't 'Unknown' for "
+                       "the selected row.")
+                QtWidgets.QMessageBox.information(
+                    self, "Select data type", msg)
+                return False
+        # # check IDs
+        # if self.IDsWget.text().strip() != '':
+        #     msg = ("The selected row's IDs will be overwritten.\n"
+        #            "Do you want to continue?")
+        #     result = QtWidgets.QMessageBox.question(
+        #         self, "Confirmation", msg,
+        #         QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+        #     if result == QtWidgets.QMessageBox.No:
+        #         return False
+        return True
+
+    @QtCore.Slot()
+    def addDBChannels(self):
+        if not self.validateRow(checkDataType=True):
+            return
+
+        dbChannels = self.getDBChannels(self.dataType)
+        self.IDsWget.setText(','.join(dbChannels))
+
+    @QtCore.Slot()
+    def scanChannels(self):
+        if not self.validateRow():
+            return
+
+        dataType = detectDataType(self, self.dirnames)
+        if dataType in self.availDataTypes:
+            self.dataTypeWget.setCurrentText(dataType)
+        else:
+            self.dataTypeWget.setCurrenText('Unknown')
+        scannedChannels = readChannels(self, self.dirnames)
+        self.IDsWget.setText(','.join(scannedChannels))
+
+    @QtCore.Slot()
+    def save(self):
+        if not self.validateRow():
+            return
+        if self.changed:
+            msg = ("All IDs in the database will be overwritten with "
+                   "current IDs in the dialog.\nClick Cancel to stop updating "
+                   "database.")
+            result = QtWidgets.QMessageBox.question(
+                self, "Confirmation", msg,
+                QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
+            if result == QtWidgets.QMessageBox.Cancel:
+                return False
+        sqlList = []
+        for rowidx in range(TOTAL_ROW):
+            sql = self.saveRowSql(rowidx)
+            if sql is not None:
+                sqlList.append(sql)
+        if len(sqlList) == 0:
+            self.parent.IDs = []
+            self.parent.IDsName = ''
+            self.parent.dataType = 'Unknown'
+            return True
+
+        ret = trunc_addDB('ChannelPrefer', sqlList)
+        if ret is not True:
+            displayTrackingInfo(self.parent, ret, "error")
+        self.parent.IDs = [
+            t.strip() for t in self.IDsWget.text().split(',')]
+        self.parent.IDsName = self.nameWget.text().strip()
+        self.parent.IDsDataType = self.dataTypeWget.currentText()
+        return True
+
+    def saveRowSql(self, rowidx):
+        current = 1 if self.IDsTableWidget.cellWidget(
+            rowidx, COL['sel']).isChecked() else 0
+        name = self.IDsTableWidget.cellWidget(
+            rowidx, COL['name']).text()
+        dataType = self.IDsTableWidget.cellWidget(
+            rowidx, COL['dataType']).currentText()
+        IDs = self.IDsTableWidget.cellWidget(
+            rowidx, COL['IDs']).text()
+        if IDs.strip() == '':
+            return
+        if name.strip() == '' and IDs.strip() != '':
+            msg = f"Please add Name for row {rowidx}."
+            QtWidgets.QMessageBox.information(self, "Missing info", msg)
+            return
+        return(f"INSERT INTO ChannelPrefer (name, IDs, dataType, current)"
+               f"VALUES ('{name}', '{IDs}', '{dataType}', {current})")
+
+    @QtCore.Slot()
+    def save_addToMainNClose(self):
+        if not self.save():
+            return
+        self.parent.currIDsNameLineEdit.setText(self.parent.IDsName)
+        self.parent.allChanCheckBox.setChecked(False)
+        self.close()
+
+    def getDataTypes(self):
+        dataTypeRows = executeDB(
+            'SELECT * FROM DataTypes ORDER BY dataType ASC')
+        return [d[0] for d in dataTypeRows]
+
+    def getDBChannels(self, dataType):
+        channelRows = executeDB(
+            f"SELECT channel FROM CHANNELS WHERE dataType='{dataType}' "
+            f" ORDER BY dataType ASC")
+        return [c[0] for c in channelRows]
+
+    def getIDsRows(self):
+        IDsRows = executeDB_dict(
+            "SELECT name, IDs, dataType, current FROM ChannelPrefer "
+            " ORDER BY name ASC")
+        return IDsRows
diff --git a/sohstationviewer/view/core/__init__.py b/sohstationviewer/view/core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/sohstationviewer/view/calendarwidget.py b/sohstationviewer/view/core/calendarwidget.py
similarity index 100%
rename from sohstationviewer/view/calendarwidget.py
rename to sohstationviewer/view/core/calendarwidget.py
diff --git a/sohstationviewer/view/core/dbgui_superclass.py b/sohstationviewer/view/core/dbgui_superclass.py
new file mode 100755
index 0000000000000000000000000000000000000000..f24870287d8dbe3c415f9133a922cb75cb44c675
--- /dev/null
+++ b/sohstationviewer/view/core/dbgui_superclass.py
@@ -0,0 +1,367 @@
+from PySide2 import QtWidgets, QtGui
+
+from sohstationviewer.database.proccessDB import executeDB
+
+
+def setWidgetColor(widget, changed=False, readOnly=False):
+    pallete = QtGui.QPalette()
+
+    if readOnly:
+        # grey text
+        pallete.setColor(QtGui.QPalette.Text, QtGui.QColor(100, 100, 100))
+        # light blue background
+        pallete.setColor(QtGui.QPalette.Base, QtGui.QColor(210, 240, 255))
+        widget.setReadOnly(True)
+        widget.setPalette(pallete)
+        return
+    else:
+        # white background
+        pallete.setColor(QtGui.QPalette.Base, QtGui.QColor(255, 255, 255))
+    if changed:
+        # red text
+        pallete.setColor(QtGui.QPalette.Text, QtGui.QColor(255, 0, 0))
+    else:
+        try:
+            if widget.isReadOnly():
+                # grey text
+                pallete.setColor(
+                    QtGui.QPalette.Text, QtGui.QColor(100, 100, 100))
+            else:
+                # black text
+                pallete.setColor(QtGui.QPalette.Text, QtGui.QColor(0, 0, 0))
+        except AttributeError:
+            pallete.setColor(QtGui.QPalette.Text, QtGui.QColor(0, 0, 0))
+    widget.setPalette(pallete)
+
+
+class Ui_DBInfoDialog(QtWidgets.QWidget):
+    def __init__(self, parent, columnHeaders, colName, tableName,
+                 resizeContentColumns=[], requestedColumns={},
+                 needDataTypeChoice=False, checkFK=True):
+        self.totalCol = len(columnHeaders)
+        self.columnHeaders = columnHeaders
+        self.resizeContentColumns = resizeContentColumns
+        self.requestedColumns = requestedColumns
+        self.needDataTypeChoice = needDataTypeChoice
+        self.colName = colName
+        self.tableName = tableName
+        self.checkFK = checkFK
+        super(Ui_DBInfoDialog, self).__init__()
+
+        self.setGeometry(100, 100, 900, 900)
+        mainLayout = QtWidgets.QVBoxLayout()
+        self.setLayout(mainLayout)
+
+        buttonLayout = self.createButtonsSection()
+        mainLayout.addLayout(buttonLayout)
+
+        self.createDataTableWidget()
+        mainLayout.addWidget(self.dataTableWidget, 1)
+        if self.tableName != '':
+            instruction = ("Background: LIGHT BLUE - Non Editable due to "
+                           "FK constrain; "
+                           "WHITE - Editable.    "
+                           "Text: BLACK - Saved; RED - Not saved")
+            # TODO: add question mark button to give instruction
+            # if self.tableName == 'parameters':
+            #     instruction += (
+            #         "\nValueColors is requested for multiColorDots plotType."
+            #         "Value from low to high"
+            #         "\nThe format for less than or equal value pair is: "
+            #         "value:color"
+            #         "\nThe format for greater than value pair is: "
+            #         "+value:color with "
+            #         "color=R,Y,G,M,C.\n Ex: 2.3:C|+4:M")
+            mainLayout.addWidget(QtWidgets.QLabel(instruction))
+
+    def addWidget(self, dataList, rowidx, colidx, foreignkey=False,
+                  choices=None, range=None, fieldName=''):
+        if dataList is None:
+            text = str(rowidx)      # row number
+        else:
+            text = (str(dataList[rowidx][colidx - 1])
+                    if rowidx < len(dataList) else '')
+
+        if dataList is None:
+            widget = QtWidgets.QPushButton(text)
+        elif choices is None and range is None:
+            widget = QtWidgets.QLineEdit(text)
+            if fieldName == 'convertFactor':
+                # precision=6
+                validator = QtGui.QDoubleValidator(0.0, 5.0, 6)
+                validator.setNotation(QtGui.QDoubleValidator.StandardNotation)
+                widget.setValidator(validator)
+                if text == '':
+                    widget.setText('1')
+        elif choices:
+            widget = QtWidgets.QComboBox()
+            widget.addItems(choices)
+            widget.setCurrentText(text)
+        elif range:
+            widget = QtWidgets.QSpinBox()
+            widget.setMinimum(range[0])
+            widget.setMaximum(range[1])
+            if text in ["", None, 'None']:
+                widget.setValue(range[0])
+            else:
+                widget.setValue(int(text))
+
+        if dataList is None:
+            setWidgetColor(widget)
+            widget.setFixedWidth(40)
+            widget.clicked.connect(
+                lambda: self.rowNumberClicked(widget))
+        elif foreignkey:
+            setWidgetColor(widget, readOnly=True)
+        else:
+            new = False if rowidx < len(dataList) else True
+            setWidgetColor(widget, readOnly=False, changed=new)
+            if choices is None and range is None:
+                widget.textChanged.connect(
+                    lambda changedtext:
+                    self.cellInputChange(changedtext, rowidx, colidx))
+            elif choices:
+                widget.currentTextChanged.connect(
+                    lambda changedtext:
+                    self.cellInputChange(changedtext, rowidx, colidx))
+            elif range:
+                widget.valueChanged.connect(
+                    lambda changedtext:
+                    self.cellInputChange(changedtext, rowidx, colidx))
+        self.dataTableWidget.setCellWidget(rowidx, colidx, widget)
+
+    def rowNumberClicked(self, widget):
+        self.dataTableWidget.selectRow(int(widget.text()))
+        self.dataTableWidget.repaint()
+
+    def createButtonsSection(self):
+        hLayout = QtWidgets.QHBoxLayout()
+
+        if self.needDataTypeChoice:
+            self.dataTypeCombobox = QtWidgets.QComboBox(self)
+            dataTypeRows = executeDB('SELECT * FROM DataTypes')
+            self.dataTypeCombobox.addItems([d[0] for d in dataTypeRows])
+            self.dataTypeCombobox.currentTextChanged.connect(
+                self.dataTypeChanged)
+            hLayout.addWidget(self.dataTypeCombobox)
+        if self.tableName != '':
+            self.addRowBtn = QtWidgets.QPushButton(self, text='ADD ROW')
+            self.addRowBtn.setFixedWidth(300)
+            self.addRowBtn.clicked.connect(self.addNewRow)
+            hLayout.addWidget(self.addRowBtn)
+
+            self.saveChangesBtn = QtWidgets.QPushButton(
+                self, text='SAVE CHANGES')
+            self.saveChangesBtn.setFixedWidth(300)
+            self.saveChangesBtn.clicked.connect(self.saveChanges)
+            hLayout.addWidget(self.saveChangesBtn)
+
+        self.closeBtn = QtWidgets.QPushButton(self, text='CLOSE')
+        self.closeBtn.setFixedWidth(300)
+        self.closeBtn.clicked.connect(self.close)
+        hLayout.addWidget(self.closeBtn)
+        return hLayout
+
+    def createDataTableWidget(self):
+        self.dataTableWidget = QtWidgets.QTableWidget(self)
+        self.dataTableWidget.verticalHeader().hide()
+        self.dataTableWidget.setColumnCount(self.totalCol)
+        self.dataTableWidget.setHorizontalHeaderLabels(self.columnHeaders)
+        header = self.dataTableWidget.horizontalHeader()
+        header.setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
+        for i in self.resizeContentColumns:
+            header.setSectionResizeMode(
+                i, QtWidgets.QHeaderView.ResizeToContents)
+
+        if self.needDataTypeChoice:
+            self.dataType = self.dataTypeCombobox.currentText()
+        self.updateDataTableWidgetItems()
+
+    def updateDataTableWidgetItems(self):
+        self.dataList = self.getDataList()
+        rowCount = len(self.dataList)
+        rowCount = 1 if rowCount == 0 else rowCount
+        self.dataTableWidget.setRowCount(rowCount)
+        for i in range(len(self.dataList)):
+            fk = self.checkDataForeignKey(self.dataList[i][0])
+            self.addRow(i, fk)
+        if len(self.dataList) == 0:
+            """
+            No Row, should leave 1 empty row
+            """
+            self.clearFirstRow()
+        self.update()
+
+    def addNewRow(self):
+        rowPosition = self.dataTableWidget.rowCount()
+        self.dataTableWidget.insertRow(rowPosition)
+        self.addRow(rowPosition)
+        self.dataTableWidget.scrollToBottom()
+        self.dataTableWidget.repaint()  # to show row's header
+        self.dataTableWidget.cellWidget(rowPosition, 1).setFocus()
+
+    def removeRow(self, removeRowidx):
+        self.dataTableWidget.removeRow(removeRowidx)
+        for i in range(removeRowidx, self.dataTableWidget.rowCount()):
+            cellWget = self.dataTableWidget.cellWidget(i, 0)
+            cellWget.setText(str(i))
+
+    def cellInputChange(self, changedText, rowidx, colidx):
+        """
+        If cell's value is changed, text's color will be red
+        otherwise, text's color will be black
+        """
+        changed = False
+        if rowidx < len(self.dataList):
+            if changedText != self.dataList[rowidx][colidx - 1]:
+                changed = True
+            cellWget = self.dataTableWidget.cellWidget(rowidx, colidx)
+            setWidgetColor(cellWget, changed=changed)
+
+    def checkDataForeignKey(self, val):
+        if not self.checkFK:
+            return False
+        sql = (f"SELECT {self.colName} FROM channels "
+               f"WHERE {self.colName}='{val}'")
+        paramRows = executeDB(sql)
+        if len(paramRows) > 0:
+            return True
+        else:
+            return False
+
+    def resetRowInputs(self, reset, widgetidx, listidx):
+        for colidx in range(1, self.totalCol):
+            cellWget = self.dataTableWidget.cellWidget(widgetidx, colidx)
+            readOnly = False
+            if hasattr(cellWget, 'isReadOnly'):
+                readOnly = cellWget.isReadOnly()
+            if not readOnly:
+                if reset == 1:
+                    orgVal = self.dataList[listidx][colidx - 1]
+                    if isinstance(cellWget, QtWidgets.QLineEdit):
+                        cellWget.setText(str(orgVal))
+                    elif isinstance(cellWget, QtWidgets.QComboBox):
+                        cellWget.setCurrentText(str(orgVal))
+                    elif isinstance(cellWget, QtWidgets.QSpinBox):
+                        cellWget.setValue(int(orgVal))
+                setWidgetColor(cellWget)
+
+    def addRow(self, rowidx, fk=False):
+        pass
+
+    def dataTypeChanged(self):
+        pass
+
+    def saveChanges(self):
+        try:
+            self.dataTableWidget.focusWidget().clearFocus()
+        except AttributeError:
+            pass
+        self.removeCount = 0
+        self.insertCount = 0
+        self.skipCount = 0
+        rowCount = self.dataTableWidget.rowCount()
+        for i in range(rowCount):
+            widgetidx = i - (rowCount - self.dataTableWidget.rowCount())
+            listidx = i - self.removeCount + self.insertCount - self.skipCount
+            rowInputs = self.getRowInputs(widgetidx)
+            reset = self.updateData(rowInputs, widgetidx, listidx)
+            if reset > -1:
+                self.resetRowInputs(reset, widgetidx, listidx)
+
+    def updateData(self, row, widgetidx, listidx, insertsql, updatesql):
+        """
+        update dataTableWidget and dataList according to the action to
+        add, remove, update
+        :param row: values of processed row in dataTableWidget
+        :param widgetidx: index of the process rows in dataTableWidget
+        :param listidx: index of the process rows in self.dataList
+        :param colName: key db column in the table
+        :param tableName: data table name
+        :param insertsql: query to insert a row to the table
+        :param updatesql: query to update a row in the table
+        :return -1 for doing nothing
+                0 no need to reset input values to org row
+                1 need to reset input values to org row
+        """
+        if listidx < len(self.dataList):
+            orgRow = self.dataList[listidx]
+            if row[0] == "":
+                msg = (f"The {self.colName} of '{orgRow}' at row {widgetidx} "
+                       f"has been changed to blank. Are you sure you want to "
+                       f"delete this row in database?")
+                result = QtWidgets.QMessageBox.question(
+                    self, "Delete %s?" % orgRow, msg,
+                    QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
+                )
+                if result == QtWidgets.QMessageBox.No:
+                    # reset the first widget value and call the function again
+                    # to check other fields
+                    cellWget = self.dataTableWidget.cellWidget(widgetidx, 1)
+                    cellWget.setText(orgRow[0])
+                    row[0] = orgRow[0]
+                    self.updateData(row, widgetidx, listidx)
+                else:
+                    sql = (f"DELETE FROM {self.tableName} "
+                           f"WHERE {self.colName}='{orgRow[0]}'")
+                    executeDB(sql)
+                    self.dataList.remove(orgRow)
+                    self.removeRow(widgetidx)
+                    self.removeCount += 1
+                    return -1
+
+            if row == orgRow:
+                return -1
+            if (row[0] in [p[0] for p in self.dataList] and
+                    self.dataList[listidx][0] != row[0]):
+                msg = (f"The {self.colName} of {orgRow} at row"
+                       f" {widgetidx} has been changed to '{row[0]}' "
+                       f"which already is in the database. "
+                       f"It will be changed back to {orgRow[0]}.")
+                QtWidgets.QMessageBox.information(self, "Error", msg)
+                # reset the first widget value and call the function again
+                # to check other fields
+                cellWget = self.dataTableWidget.cellWidget(widgetidx, 1)
+                cellWget.setText(orgRow[0])
+                row[0] = orgRow[0]
+                self.updateData(row, widgetidx, listidx)
+            else:
+                msg = (f"{orgRow} at row {widgetidx} has "
+                       f"been changed to {row}. Please confirm it.")
+                result = QtWidgets.QMessageBox.question(
+                    self, "Confirmation", msg,
+                    QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
+                )
+                if result == QtWidgets.QMessageBox.Cancel:
+                    return 1
+                else:
+                    executeDB(updatesql % orgRow[0])
+                    self.dataList[listidx] = row
+                    return 0
+
+        if row[0] == "":
+            msg = (f"Row {widgetidx} has blank {self.colName}. "
+                   f"It will be removed.")
+            QtWidgets.QMessageBox.information(self, "Error", msg)
+            self.removeRow(widgetidx)
+            return -1
+        blankRequestedColumns = [self.requestedColumns[i]
+                                 for i in self.requestedColumns.keys()
+                                 if row[i-1] == ""]
+        if blankRequestedColumns != []:
+            msg = (f"Row {widgetidx}: blank "
+                   f"{', '.join(blankRequestedColumns)} which require some "
+                   f"value(s).")
+            QtWidgets.QMessageBox.information(self, "Error", msg)
+            return -1
+
+        if row[0] in [p[0] for p in self.dataList]:
+            msg = (f"The {self.colName} '{row[0]}' is already in the database."
+                   f" Row {widgetidx} will be removed.")
+            QtWidgets.QMessageBox.information(self, "Error", msg)
+            self.removeRow(widgetidx)
+            return -1
+        executeDB(insertsql)
+        self.dataList.append(row)
+        self.insertCount += 1
+        return 0
diff --git a/sohstationviewer/view/filelist.py b/sohstationviewer/view/core/filelistwidget.py
similarity index 100%
rename from sohstationviewer/view/filelist.py
rename to sohstationviewer/view/core/filelistwidget.py
diff --git a/sohstationviewer/view/core/plottingWidget.py b/sohstationviewer/view/core/plottingWidget.py
new file mode 100755
index 0000000000000000000000000000000000000000..b95de5c7f7cb9e6c3100b73af4f4f4dff5dcac0d
--- /dev/null
+++ b/sohstationviewer/view/core/plottingWidget.py
@@ -0,0 +1,1029 @@
+import math
+
+from PySide2 import QtCore, QtWidgets
+from matplotlib.backends.backend_qt5agg import (
+    FigureCanvasQTAgg as Canvas)
+from matplotlib import pyplot as pl
+from matplotlib.patches import ConnectionPatch, Rectangle
+from matplotlib.ticker import AutoMinorLocator
+import numpy as np
+
+from sohstationviewer.controller.plottingData import (
+    getTitle, getGaps, getTimeTicks, getUnitBitweight, getMassposValueColors)
+from sohstationviewer.conf import constants
+from sohstationviewer.conf.colorSettings import Clr, set_colors
+from sohstationviewer.database import extractData
+from sohstationviewer.controller.util import displayTrackingInfo, getVal
+
+
+plotFunc = {
+    'linesDots': (
+        ("Lines, one color dots. "),
+        "plotLinesDots"),
+    'linesSRate': (
+        ("Lines, one color dots, bitweight info. "),
+        "plotLinesSRate"),
+    'linesMasspos': (
+        ("multi-line mass position, multi-color dots. "),
+        "plotLinesMasspos"),
+    # 'dotsMasspos': (
+    #     ("mass position, multi-color, single line. "),
+    #     "plotDotsMasspos"),
+    'dotForTime': (
+        "Dots according to timestamp. Color defined by valueColors. Ex: G",
+        "plotTimeDots"),
+    'multiColorDots': (
+        ("Multicolor dots with colors defined by valueColors. "
+         "Value from low to high. "
+         "Ex:*:W or -1:_|0:R|2.3:Y|+2.3:G. With colors: RYGMC: _: not plot"),
+        "plotMultiColorDots"
+    ),
+    'upDownDots': (
+        ("Show data with 2 different values: first down/ second up. "
+         "With colors defined by valueColors. Ex: 1:R|0:Y"),
+        'plotUpDownDots'
+    )
+}
+
+
+class PlottingWidget(QtWidgets.QScrollArea):
+    """
+    zorder:
+        axis spines: 0
+        center line: 1
+        lines: 2
+        gap, dots, : 3
+    """
+
+    def __init__(self, parent=None):
+        super().__init__()
+        self.parent = parent
+        self.plotNo = 0
+        self.infoWidget = None
+        self.widgt = QtWidgets.QWidget(parent)
+        self.axes = []
+        self.currplot_title = None
+        self.hidden_plots = {}
+        self.zoomMarker1Shown = False
+        self.axes = []
+
+        self.widthBase = 0.185
+        # width of plotting area
+        self.plottingW = self.widthBase
+        # X1: 0.19: Width = 20% of 50in (Figure's width)
+        # X2: 0.19*2: Width = 40% of 50in
+        # X4: 0.19*4: Width = 80% of 50 in
+        # height of plotting area
+        #     + changed when plots are added or removed
+        #     + changed when changing the V-magnify param
+        self.plottingH = 0.996
+        # this is the height of a standard plot
+        # plotH = 0.01        # 0.01: Height = 1% of 100in (Figure's height)
+        # left of plotting area: no change
+        self.plottingL = 0.03
+        # bottom of plot gap, where we start to draw data
+        # self.plotGapB = 0.990
+        # distance from left axis to start of label
+        self.labelPad = 100
+        self.fontSize = 7
+        """
+        Set Figure size 50in width, 100in height.
+        This is the maximum size of plotting container.
+        add_axes will draw proportion to this size.
+        The actual view for user based on size of self.widgt.
+        """
+        self.fig = pl.Figure(facecolor='white', figsize=(50, 100))
+        self.fig.canvas.mpl_connect('pick_event', self.__on_pick_on_artist)
+        self.fig.canvas.mpl_connect('button_press_event', self.__on_pick)
+
+        self.canvas = Canvas(self.fig)
+        self.canvas.setParent(self.widgt)
+        self.setWidget(self.widgt)
+        self.set_colors('B')
+
+    def contextMenuEvent(self, event):
+        if self.axes == []:
+            return
+        contextMenu = QtWidgets.QMenu(self)
+        removePlotAct = contextMenu.addAction(
+            "Remove %s" % self.currplot_title)
+        removePlotAct.setStatusTip("Remove the current Plot")
+        removePlotAct.triggered.connect(self.hide_currplot)
+
+        if self.hidden_plots != {}:
+            showAllPlotAct = contextMenu.addAction("Show All Plots")
+            showAllPlotAct.triggered.connect(self.show_all_hidden_plots)
+        showPlotActs = []
+        for i in sorted(self.hidden_plots.keys()):
+            showPlotActs.append(
+                contextMenu.addAction('Show hidden Plot %s' % i))
+            showPlotActs[-1].triggered.connect(
+                lambda *arg, index=i: self.show_hidden_plot(index))
+        contextMenu.exec_(self.mapToGlobal(event.pos()))
+
+    def __getTimestamp(self, event):
+        x, y = event.x, event.y                 # coordinate data
+        inv = self.axes[0].transData.inverted()
+        # convert to timestamp, bc event.xdata is None in the space bw axes
+        xdata = inv.transform((x, y))[0]
+        print('xdata of mouse: {:.2f}'.format(xdata))
+        return xdata
+
+    def __zoomBwMarkers(self, xdata):
+        if self.currMinX == xdata:
+            return
+        # self.fig.canvas.mpl_disconnect(self.follower)
+        self.__draw()
+        self.zoomMarker1Shown = False
+        [self.currMinX, self.currMaxX] = sorted(
+            [self.currMinX, xdata])
+        print("ZM2 self.currMinX:", self.currMinX)
+        print("ZM2 self.currMaxX:", self.currMaxX)
+        self.__set_lim()
+        self.zoomMarker1.set_visible(False)
+        self.zoomMarker2.set_visible(False)
+        self.__draw()
+
+    def __on_pick(self, event):
+        """
+        xmouse, ymouse = event.mouseevent.xdata, event.mouseevent.ydata
+        # x, y = artist.get_xdata(), artist.get_ydata()
+        # ind = event.ind
+        print('Artist picked:', artist)
+        # print(self.plots)
+        # print('Index:', self.plots.index(event.artist))
+        # print('{} vertices picked'.format(len(ind)))
+        # print('Pick between vertices {} and {}'.format(
+        min(ind), max(ind) + 1))
+        print('x, y of mouse: {:.2f},{:.2f}'.format(xmouse, ymouse))
+        # print('Data point:', x[ind[0]], y[ind[0]])
+                    print("mouseevent:", dir(event.mouseevent))
+            print("guievent:", dir(event.guiEvent))
+            print("modifiers:", event.guiEvent.modifiers())
+        """
+        # print(dir(event))
+        modifiers = event.guiEvent.modifiers()
+        xdata = self.__getTimestamp(event)
+        if modifiers == QtCore.Qt.ShiftModifier:
+            print("shift+click")
+            if not self.zoomMarker1Shown:
+                self.ruler.set_visible(False)
+                self.__set_ruler_visibled(self.zoomMarker1, xdata)
+                self.currMinX = xdata
+                print("ZM1 self.currMinX:", self.currMinX)
+                self.zoomMarker1Shown = True
+                self.__set_ruler_visibled(self.zoomMarker2, xdata)
+                self.__draw()
+            else:
+                self.__zoomBwMarkers(xdata)
+                # if self.currMinX == xdata:
+                #     return
+                # self.fig.canvas.mpl_disconnect(self.follower)
+                # self.__draw()
+                # self.zoomMarker1Shown = False
+                # [self.currMinX, self.currMaxX] = sorted(
+                #     [self.currMinX, xdata])
+                # print("ZM2 self.currMinX:", self.currMinX)
+                # print("ZM2 self.currMaxX:", self.currMaxX)
+                # self.__set_lim()
+                # self.zoomMarker1.set_visible(False)
+                # self.zoomMarker2.set_visible(False)
+                # self.__draw()
+        elif modifiers in [QtCore.Qt.ControlModifier,
+                           QtCore.Qt.MetaModifier]:
+            print("Ctrl+click")
+            self.zoomMarker1.set_visible(False)
+            self.zoomMarker1Shown = False
+            self.fig.canvas.mpl_disconnect(self.follower)
+            self.__set_ruler_visibled(self.ruler, xdata)
+        else:
+            print("click xmouse:", xdata)
+            if self.zoomMarker1Shown:
+                self.__zoomBwMarkers(xdata)
+
+    def __on_pick_on_artist(self, event):
+        print("============__on_pick ============")
+        """
+        xmouse, ymouse = event.mouseevent.xdata, event.mouseevent.ydata
+        # x, y = artist.get_xdata(), artist.get_ydata()
+        # ind = event.ind
+        print('Artist picked:', artist)
+        # print(self.plots)
+        # print('Index:', self.plots.index(event.artist))
+        # print('{} vertices picked'.format(len(ind)))
+        # print('Pick between vertices {} and {}'.format(
+        #         min(ind), max(ind) + 1))
+        print('x, y of mouse: {:.2f},{:.2f}'.format(xmouse, ymouse))
+        # print('Data point:', x[ind[0]], y[ind[0]])
+                    print("mouseevent:", dir(event.mouseevent))
+            print("guievent:", dir(event.guiEvent))
+            print("modifiers:", event.guiEvent.modifiers())
+        """
+        # print(dir(event))
+        artist = event.artist
+        # modifiers = event.guiEvent.modifiers()
+        print("artist:", artist)
+        if isinstance(artist, pl.Line2D):
+            x, y = artist.get_xdata(), artist.get_ydata()
+            ind = event.ind
+            print('Data point:', x[ind[0]], y[ind[0]])
+        if isinstance(artist, pl.Axes):
+            self.currplot = artist
+            self.currplot_index = self.axes.index(artist)
+            self.currplot_title = "Plot %s" % self.currplot_index
+
+    def __set_ruler_visibled(self, ruler, x):
+        ruler.set_visible(True)
+        ruler.xy1 = (x, 0)
+        ruler.xy2 = (x, self.bottom)
+        if ruler == self.zoomMarker2:
+            # make zoomMarker2 follow mouse.
+            # need to disconnect when state of rulers change
+            self.follower = self.fig.canvas.mpl_connect(
+                "motion_notify_event", self.__zoomMarker2_follow_mouse)
+        self.__draw()
+
+    def __zoomMarker2_follow_mouse(self, mouseevent):
+        xdata = self.__getTimestamp(mouseevent)
+        self.zoomMarker2.xy1 = (xdata, 0)
+        self.zoomMarker2.xy2 = (xdata, self.bottom)
+        self.__draw()
+
+    def keyPressEvent(self, event):
+        if event.key() == QtCore.Qt.Key_Escape:
+            print("ESC")
+            self.ruler.set_visible(False)
+            self.zoomMarker1.set_visible(False)
+            self.zoomMarker2.set_visible(False)
+            self.zoomMarker1Shown = False
+            self.__draw()
+        return super(PlottingWidget, self).keyPressEvent(event)
+
+    def __add_timestamp_bar(self, usedHeight, top=True):
+        """
+        set the axes to display timestampBar on top of the plotting area
+        setting axis off to not display at the begining
+        Color in qpeek is DClr[T0]
+        :return:
+        """
+        self.plottingH -= usedHeight
+        timestampBar = self.canvas.figure.add_axes(
+            [self.plottingL, self.plottingH, self.plottingW, 0.00005],
+        )
+        timestampBar.axis('off')
+        timestampBar.xaxis.set_minor_locator(AutoMinorLocator())
+        timestampBar.spines['bottom'].set_color(self.displayColor['TM'])
+        timestampBar.spines['top'].set_color(self.displayColor['TM'])
+
+        if top:
+            labelbottom = False
+        else:
+            labelbottom = True
+        timestampBar.tick_params(which='major', length=7, width=2,
+                                 direction='inout',
+                                 colors=self.displayColor['TM'],
+                                 labelbottom=labelbottom,
+                                 labeltop=not labelbottom)
+        timestampBar.tick_params(which='minor', length=4, width=1,
+                                 direction='inout',
+                                 colors=self.displayColor['TM'])
+        timestampBar.set_ylabel('Hours',
+                                fontweight='bold',
+                                fontsize=self.fontSize+2,
+                                rotation=0,
+                                labelpad=self.labelPad,
+                                ha='left',
+                                color=self.displayColor['TM'])
+
+        # self.__update_timestamp_bar(timestampBar)
+        return timestampBar
+
+    def __update_timestamp_bar(self, timestampBar):
+        times, majorTimes, majorTimeLabels = getTimeTicks(
+            self.currMinX, self.currMaxX, self.dateMode, self.timeTicksTotal)
+        timestampBar.axis('on')
+        timestampBar.set_yticks([])
+        timestampBar.set_xticks(times, minor=True)
+        timestampBar.set_xticks(majorTimes)
+        timestampBar.set_xticklabels(majorTimeLabels,
+                                     fontsize=self.fontSize+2)
+        timestampBar.set_xlim(self.currMinX, self.currMaxX)
+
+    def __create_axes(self, plotB, plotH, hasMinMaxLines=True):
+        ax = self.canvas.figure.add_axes(
+            [self.plottingL, plotB, self.plottingW, plotH],
+            picker=True
+        )
+
+        ax.spines['right'].set_visible(False)
+        ax.spines['left'].set_visible(False)
+        if hasMinMaxLines:
+            ax.spines['top'].set_zorder(0)
+            ax.spines['bottom'].set_zorder(0)
+            ax.spines['top'].set_color(self.displayColor['P0'])
+            ax.spines['bottom'].set_color(self.displayColor['P0'])
+
+        ax.set_yticks([])
+        ax.set_xticks([])
+        ax.tick_params(colors=self.displayColor['TX'],
+                       width=0,
+                       pad=-2,
+                       labelsize=self.fontSize)
+        ax.patch.set_alpha(0)
+        return ax
+
+    def setAxesInfo(self, ax, sampleNoList, sampleNoClrList=None,
+                    label=None, info='', y=None, chanDB=None, linkedAx=None):
+        if label is None:
+            label = chanDB['label']
+        titleVerAlignment = 'center'
+        # set info undertitle
+        if linkedAx is not None:
+            info = label
+        if info != '':
+            ax.text(
+                -0.15, 0.2,
+                info,
+                horizontalalignment='left',
+                verticalalignment='top',
+                rotation='horizontal',
+                transform=ax.transAxes,
+                color=self.displayColor['TX'],
+                size=self.fontSize
+            )
+            titleVerAlignment = 'top'
+        if linkedAx is None:
+            # set title on left side
+            ax.text(
+                -0.15, 0.6,
+                label,
+                horizontalalignment='left',
+                verticalalignment=titleVerAlignment,
+                rotation='horizontal',
+                transform=ax.transAxes,
+                color=self.displayColor['LB'],
+                size=self.fontSize + 2
+            )
+
+        # set samples' total on right side
+        if sampleNoClrList is None:
+            sampleNoClrList = len(sampleNoList) * ['W']
+        if len(sampleNoList) == 1:
+            ax.sampleLbl = ax.text(
+                1.005, 0.5,
+                sampleNoList[0],
+                horizontalalignment='left',
+                verticalalignment='center',
+                rotation='horizontal',
+                transform=ax.transAxes,
+                color=Clr[sampleNoClrList[0]],
+                size=self.fontSize
+            )
+        else:
+            # bottom
+            ax.sampleLbl = ax.text(
+                1.005, 0.25,
+                sampleNoList[0],
+                horizontalalignment='left',
+                verticalalignment='center',
+                rotation='horizontal',
+                transform=ax.transAxes,
+                color=Clr[sampleNoClrList[0]],
+                size=self.fontSize
+            )
+            # top
+            ax.sampleLbl = ax.text(
+                1.005, 0.75,
+                sampleNoList[1],
+                horizontalalignment='left',
+                verticalalignment='center',
+                rotation='horizontal',
+                transform=ax.transAxes,
+                color=Clr[sampleNoClrList[1]],
+                size=self.fontSize
+            )
+
+        if y is None:
+            # draw center line
+            ax.plot([self.currMinX, self.currMaxX],
+                    [0, 0],
+                    color=self.displayColor['P0'],
+                    linewidth=0.5,
+                    zorder=1
+                    )
+            ax.spines['top'].set_visible(False)
+            ax.spines['bottom'].set_visible(False)
+        else:
+            minY = min(y)
+            maxY = max(y)
+            ax.spines['top'].set_visible(True)
+            ax.spines['bottom'].set_visible(True)
+            ax.unit_bw = getUnitBitweight(chanDB, self.parent.bitweightOpt)
+            self.__setAxesYlim(ax, minY, maxY)
+
+    def __setAxesYlim(self, ax, minY, maxY):
+        minY = round(minY, 7)
+        maxY = round(maxY, 7)
+        if maxY > minY:
+            ax.set_yticks([minY, maxY])
+            ax.set_yticklabels(
+                [ax.unit_bw.format(minY), ax.unit_bw.format(maxY)])
+        if minY == maxY:
+            maxY += 1
+            ax.set_yticks([minY])
+            ax.set_yticklabels([ax.unit_bw.format(minY)])
+        ax.set_ylim(minY, maxY)
+
+    def addGapBar(self, gaps):
+        """
+        set the axes to display gapBar on top of the plotting area
+        setting axis off to not display at the begining
+        :return:
+        """
+        if self.parent.minGap is None:
+            return
+        self.gaps = getGaps(gaps, float(self.parent.minGap))
+        self.plottingH -= 0.003
+        self.gapBar = self.__create_axes(self.plottingH,
+                                         0.001,
+                                         hasMinMaxLines=False)
+        self.updateGapBar()
+
+    def updateGapBar(self):
+        gapLabel = "%sm" % self.parent.minGap
+        # TODO: calculate gap limit
+        # self.gaps = []
+        h = 0.001  # height of rectangle represent gap
+        self.setAxesInfo(self.gapBar, [len(self.gaps)],
+                         label=gapLabel)
+        # draw gaps
+        for i in range(len(self.gaps)):
+            x = self.gaps[i][0]
+            w = self.gaps[i][1] - self.gaps[i][
+                0]  # width of rectangle represent gap
+            self.gapBar.add_patch(Rectangle((x, - h / 2), w, h,
+                                            color='r',
+                                            picker=True,
+                                            lw=0.,
+                                            zorder=3))  # on top of center line
+
+    def __get_height(self, ratio):
+        plotH = 0.0012 * ratio  # ratio with figure height
+        bwPlotsDistance = 0.0015
+        self.plottingH -= plotH + bwPlotsDistance
+        self.plottingHPixel += 19 * ratio
+        return plotH
+
+    # -------------------- Different color dots ----------------------- #
+    def plotNone(self):
+        """
+        plot with nothing needed to show rulers
+        """
+        plotH = 0.00001
+        bwPlotsDistance = 0.0001
+        self.plottingH -= plotH + bwPlotsDistance
+        ax = self.__create_axes(self.plottingH, plotH, hasMinMaxLines=False)
+        ax.x = None
+        ax.plot([0], [0], linestyle="")
+        return ax
+
+    def plotMultiColorDots(self, cData, chanDB, chan, linkedAx):
+        """
+        plot scattered dots with colors defined by valueColors:
+          *:W or -1:_|0:R|2.3:Y|+2.3:G
+           with colors: RYGMC in dbSettings.py
+           _: not plot
+        :param data: data of the channel which is list of (time, value)
+        :param chanDB: info of channel from DB
+        :return:
+        """
+
+        plotH = self.__get_height(chanDB['height'])
+        if linkedAx is None:
+            ax = self.__create_axes(
+                self.plottingH, plotH, hasMinMaxLines=False)
+        else:
+            ax = linkedAx
+
+        x = []
+        prevVal = -constants.HIGHEST_INT
+
+        if chanDB['valueColors'] in [None, 'None', '']:
+            chanDB['valueColors'] = '*:W'
+        valueColors = chanDB['valueColors'].split('|')
+        for vc in valueColors:
+            v, c = vc.split(':')
+            val = getVal(v)
+            if c == '_':
+                prevVal = val
+                continue
+
+            if v.startswith('+'):
+                points = [cData['decTimes'][i]
+                          for i in range(len(cData['decData']))
+                          if cData['decData'][i] > val]
+            elif v == '*':
+                points = cData['decTimes']
+            else:
+                points = [cData['decTimes'][i]
+                          for i in range(len(cData['decData']))
+                          if prevVal < cData['decData'][i] <= val]
+            x += points
+
+            ax.plot(points, len(points) * [0], linestyle="",
+                    marker='s', markersize=0.5, zorder=3,
+                    color=Clr[c], picker=True, pickradius=3)
+            prevVal = val
+
+        totalSamples = len(x)
+
+        x = sorted(x)
+        self.setAxesInfo(ax, [totalSamples], chanDB=chanDB, linkedAx=linkedAx)
+        if linkedAx is None:
+            ax.x = x
+        else:
+            ax.linkedX = x
+        return ax
+
+    # def plotDotsMasspos(self, cData, chanDB, chan, linkedAx):
+    #     valueColors = getMassposValueColors(
+    #         self.parent.massPosVoltRangeOpt, chan, self.cMode, self.errors)
+    #     if valueColors is None:
+    #         return
+    #     chanDB['valueColors'] = valueColors
+    #     return self.plotMultiColorDots(cData, chanDB, chan, linkedAx)
+
+    # ---------------------------- up/down dots ---------------------------- #
+    def plotUpDownDots(self, cData, chanDB, chan, linkedAx):
+        """
+        data with 2 different values defined in valueColors
+        """
+        plotH = self.__get_height(chanDB['height'])
+        if linkedAx is None:
+            ax = self.__create_axes(
+                self.plottingH, plotH, hasMinMaxLines=False)
+        else:
+            ax = linkedAx
+
+        valCols = chanDB['valueColors'].split('|')
+        pointsList = []
+        colors = []
+        for vc in valCols:
+            v, c = vc.split(':')
+            val = getVal(v)
+
+            points = [cData['decTimes'][i]
+                      for i in range(len(cData['decData']))
+                      if cData['decData'][i] == val]
+            pointsList.append(points)
+            colors.append(c)
+
+        # down dots
+        ax.plot(pointsList[0], len(pointsList[0]) * [-0.5], linestyle="",
+                marker='s', markersize=2, zorder=3,
+                color=Clr[colors[0]], picker=True, pickradius=3)
+        # up dots
+        ax.plot(pointsList[1], len(pointsList[1]) * [0.5], linestyle="",
+                marker='s', markersize=2, zorder=3,
+                color=Clr[colors[1]], picker=True, pickradius=3)
+        x = pointsList[0] + pointsList[1]
+        x = sorted(x)
+        ax.set_ylim(-2, 2)
+        self.setAxesInfo(ax, [len(pointsList[0]), len(pointsList[1])],
+                         sampleNoClrList=colors,
+                         chanDB=chanDB,
+                         linkedAx=linkedAx)
+        if linkedAx is None:
+            ax.x = x
+        else:
+            ax.linkedX = x
+        return ax
+
+    # ----------------------- dots for times, ignore data------------------- #
+    def plotTimeDots(self, cData, chanDB, chan, linkedAx):
+        plotH = self.__get_height(chanDB['height'])
+        if linkedAx is None:
+            ax = self.__create_axes(self.plottingH, plotH)
+        else:
+            ax = linkedAx
+
+        color = 'W'
+        if chanDB['valueColors'] not in [None, 'None', '']:
+            color = chanDB['valueColors'].strip()
+        x = cData['decTimes']
+        self.setAxesInfo(ax, [len(x)], chanDB=chanDB, linkedAx=linkedAx)
+
+        ax.myPlot = ax.plot(x, [0]*len(x), marker='s', markersize=1.5,
+                            linestyle='', zorder=2,
+                            color=Clr[color], picker=True,
+                            pickradius=3)
+        if linkedAx is None:
+            ax.x = x
+        else:
+            ax.linkedX = x
+        return ax
+
+    # ----------------------- lines - one color dots ----------------------- #
+    def plotLinesDots(self, cData, chanDB, chan, linkedAx, info=''):
+        """ L:G|D:W """
+        plotH = self.__get_height(chanDB['height'])
+        if linkedAx is None:
+            ax = self.__create_axes(self.plottingH, plotH)
+        else:
+            ax = linkedAx
+
+        x, y = cData['decTimes'], cData['decData']
+        self.setAxesInfo(ax, [len(x)], chanDB=chanDB,
+                         info=info, y=y, linkedAx=linkedAx)
+        colors = {}
+        if chanDB['valueColors'] not in [None, 'None', '']:
+            colorParts = chanDB['valueColors'].split('|')
+            for cStr in colorParts:
+                obj, c = cStr.split(':')
+                colors[obj] = c
+
+        lColor = 'G'
+        hasDot = False
+        if 'L' in colors:
+            lColor = colors['L']
+        if 'D' in colors:
+            dColor = colors['D']
+            hasDot = True
+
+        if not hasDot:
+            ax.myPlot = ax.plot(x, y,
+                                linestyle='-', linewidth=0.7,
+                                color=Clr[lColor])
+        else:
+            ax.myPlot = ax.plot(x, y, marker='s', markersize=1.5,
+                                linestyle='-', linewidth=0.7, zorder=2,
+                                color=Clr[lColor],
+                                markerfacecolor=Clr[dColor],
+                                picker=True, pickradius=3)
+        if linkedAx is None:
+            ax.x = x
+            ax.y = y
+        else:
+            ax.linkedX = x
+            ax.linkedY = y
+        return ax
+
+    def plotLinesSRate(self, cData, chanDB, chan, linkedAx):
+        """
+        multi-line line seismic, one color, line only,
+            can apply bit weights in (get_unit_bitweight())
+        """
+        if cData['samplerate'] >= 1.0:
+            info = "%dsps" % cData['samplerate']
+        else:
+            info = "%gsps" % cData['samplerate']
+        return self.plotLinesDots(cData, chanDB, chan, linkedAx, info=info)
+
+    # ----------------------- lines - multi-color dots --------------------- #
+    def plotLinesMasspos(self, cData, chanDB, chan, linkedAx):
+
+        # if (chan.startswith("MP") and
+        #         chanDB['valueColors'] not in [None, 'None', '']):
+        #     _colors = chanDB['valueColors'].split("|")
+        #     _values = sorted(set(abs(cData['decData'])))
+        #
+        #     valueColors = [(_values[idx], _colors[idx])
+        #                    for idx in range(len(_values[:len(_colors)]))]
+        # else:
+        _values = sorted(set(abs(cData['decData'])))
+        print("_values:", _values)
+        valueColors = getMassposValueColors(
+            self.parent.massPosVoltRangeOpt, chan,
+            self.cMode, self.errors, retType='tupleList')
+
+        if valueColors is None:
+            return
+
+        plotH = self.__get_height(chanDB['height'])
+        ax = self.__create_axes(self.plottingH, plotH)
+
+        ax.x, ax.y = cData['times'], cData['data']
+        self.setAxesInfo(ax, [len(ax.x)], chanDB=chanDB, y=ax.y)
+        ax.myPlot = ax.plot(ax.x, ax.y,
+                            linestyle='-', linewidth=0.7,
+                            color=self.displayColor['PL'],
+                            zorder=2)[0]
+        colors = [None] * len(ax.y)
+        sizes = [0.5] * len(ax.y)
+        for i in range(len(ax.y)):
+            count = 0
+            prevV = 0
+            for v, c in valueColors:
+                if count < (len(valueColors) - 1):
+                    if prevV < abs(ax.y[i]) <= v:
+                        colors[i] = Clr[c]
+                        break
+                else:
+                    # if abs(ax.y[i]) > v:
+                    colors[i] = Clr[c]
+                    break
+                prevV = v
+                count += 1
+        ax.scatter(ax.x, ax.y, marker='s', c=colors, s=sizes, zorder=3)
+        return ax
+
+    # ---------------------------------------------------------#
+
+    def __add_ruler(self, color):
+        ruler = ConnectionPatch(
+            xyA=(0, 0),
+            xyB=(0, self.bottom),
+            coordsA="data",
+            coordsB="data",
+            axesA=self.timestampBarTop,
+            axesB=self.timestampBarBottom,
+            color=color,
+        )
+        ruler.set_visible(False)
+        self.timestampBarBottom.add_artist(ruler)
+        return ruler
+
+    def __set_lim(self, orgSize=False):
+        self.__update_timestamp_bar(self.timestampBarTop)
+        self.__update_timestamp_bar(self.timestampBarBottom)
+        if hasattr(self, 'gapBar'):
+            self.gapBar.set_xlim(self.currMinX, self.currMaxX)
+            if not orgSize:
+                newGaps = [g for g in self.gaps
+                           if (self.currMinX <= g[0] <= self.currMaxX
+                               or self.currMinX <= g[1] <= self.currMaxX)]
+
+                # reset total of samples on the right
+                self.gapBar.sampleLbl.set_text(len(newGaps))
+        for ax in self.axes:
+            ax.set_xlim(self.currMinX, self.currMaxX)
+            if ax.x is None:
+                # the plotNone bar is at the end, no need to process
+                break
+            if not orgSize:
+                # x, y
+                newX = [x for x in ax.x
+                        if x >= self.currMinX and x <= self.currMaxX]
+                # reset total of samples on the right
+                ax.sampleLbl.set_text(len(newX))
+                if len(newX) == 0:
+                    continue
+                if hasattr(ax, 'y'):
+                    # don't need to reset y range if ax.y not exist
+                    newMinX = min(newX)
+                    newMaxX = max(newX)
+                    try:
+                        newMinXIndex = ax.x.index(newMinX)
+                        newMaxXIndex = ax.x.index(newMaxX)
+                    except AttributeError:
+                        newMinXIndex = np.where(ax.x == newMinX)[0][0]
+                        newMaxXIndex = np.where(ax.x == newMaxX)[0][0]
+                    newY = ax.y[newMinXIndex:newMaxXIndex + 1]
+                    newMinY = min(newY)
+                    newMaxY = max(newY)
+                    self.__setAxesYlim(ax, newMinY, newMaxY)
+
+    def __set_title(self, title):
+        self.fig.text(-0.15, 100, title,
+                      verticalalignment='top',
+                      horizontalalignment='left',
+                      transform=self.timestampBarTop.transAxes,
+                      color=self.displayColor['TX'],
+                      size=self.fontSize)
+
+    def __draw(self):
+        try:
+            self.canvas.draw()
+            # a bug on mac:
+            # not showing updated info until clicking on another window
+            # fix by calling repaint()
+            self.widgt.repaint()
+        except TypeError:
+            pass
+
+    # ######## Functions for outside world #####
+    def init_size(self):
+        geo = self.maximumViewportSize()
+        if self.plotNo == 0:
+            # set view size fit with the scroll's view port size
+            self.widgt.setFixedWidth(geo.width())
+            self.widgt.setFixedHeight(geo.height())
+
+    def set_msg_widget(self, msgWidget):
+        self.msgWidget = msgWidget
+
+    def set_background_color(self, color='black'):
+        self.fig.patch.set_facecolor(color)
+        self.__draw()
+
+    def resetView(self):
+        """
+        reset all zooms back to the first plotting
+        """
+        if self.axes == []:
+            return
+        self.currMinX = self.minX
+        self.currMaxX = self.maxX
+        self.__set_lim()
+        self.__draw()
+
+    def clear(self):
+        if self.zoomMarker1.get_visible():
+            self.zoomMarker1.set_visible(False)
+        else:
+            self.zoomMarker1.set_visible(True)
+        # self.fig.clear()
+        # self.axes = []
+        self.__draw()
+
+    def decimateWConvertFactor(self, cData, convertFactor, maxDP=50000):
+        """
+        convertFactor = 150mV/count = 150V/1000count
+        => unit data * convertFactor= data *150/1000 V
+        convert data in numpy arrays to a reduced list
+        according to max number of datapoint needed
+        """
+        cData['data'] = np.multiply(cData['data'], [convertFactor])
+        ln = cData['times'].size
+        d = math.ceil(ln / maxDP)  # decimation factor
+
+        if d < 5:
+            cData['decTimes'] = cData['times']      # np array
+            cData['decData'] = cData['data']
+        else:
+            cData['decTimes'] = [cData['times'][i]
+                                 for i in range(ln) if i % d == 0]
+            cData['decData'] = [cData['data'][i]
+                                for i in range(ln) if i % d == 0]
+        return cData
+
+    def addPlots(self, setID, plottingData, reqInfoChans, timeTicksTotal):
+        """
+        :param setID: (netID, statID, locID)
+        :param plottingData: a ditionary including:
+            { gaps: [(t1,t2),(t1,t2),...]   (in epoch time)
+              channels:{cha: {netID, statID, locID, chanID, times, data,
+                              samplerate, startTmEpoch, endTmEpoch}       #
+              earliestUTC: the earliest time of all channels
+              latestUTC: the latest time of all channels
+        :timeTicksTotal: max number of tick to show on time bar
+        Data set: {channelname: [(x,y), (x,y)...]
+        """
+        # print('plottingData:', plottingData)
+        self.processingLog = []     # [(message, type)]
+        self.plottingData = plottingData
+        self.errors = []
+        if self.axes != []:
+            self.fig.clear()
+        self.dateMode = self.parent.dateFormat.upper()
+        self.timeTicksTotal = timeTicksTotal
+        self.minX = self.currMinX = plottingData['earliestUTC']
+        self.maxX = self.currMaxX = plottingData['latestUTC']
+        self.plotNo = len(plottingData['channels'])
+        title = getTitle(self, setID, plottingData, self.dateMode)
+
+        self.plottingHPixel = 200
+        # self.plottingH = self.plotGapB
+        self.axes = []
+
+        self.timestampBarTop = self.__add_timestamp_bar(0.003)
+        self.__set_title(title)
+        self.addGapBar(plottingData['gaps'])
+        notFoundChan = [c for c in reqInfoChans
+                        if c not in plottingData['channels'].keys()]
+        if len(notFoundChan) > 0:
+            msg = (f"The following channels is in Channel Preferences but "
+                   f"not in the given data: {notFoundChan}")
+            print(msg)
+            self.processingLog.append((msg, 'warning'))
+        print("all channels:", plottingData['channels'].keys())
+        for chan in plottingData['channels'].keys():
+            print(">>>>CHAN:", chan)
+            chanDB = extractData.getChanPlotInfo(chan, self.parent.dataType)
+            print("chanDB:", chanDB)
+            if chanDB['height'] == 0:
+                # not draw
+                continue
+            if chanDB['channel'] == 'DEFAULT':
+                msg = (f"Channel {chan}'s definition can't be found database.")
+                displayTrackingInfo(self.parent, msg, 'warning')
+
+            plotType = chanDB['plotType']
+            if chanDB['plotType'] == '':
+                continue
+
+            cData = self.decimateWConvertFactor(
+                plottingData['channels'][chan],
+                chanDB['convertFactor'],
+                50000)
+            linkedAx = None
+            if chanDB['linkedChan'] not in [None, 'None', '']:
+                try:
+                    linkedAx = plottingData['channels'][
+                        chanDB['linkedChan']]['ax']
+                except KeyError:
+                    pass
+            ax = getattr(self,
+                         plotFunc[plotType][1])(cData, chanDB, chan, linkedAx)
+            if ax is None:
+                continue
+            plottingData['channels'][chan]['ax'] = ax
+            ax.chan = chan
+            if linkedAx is None:
+                self.axes.append(ax)
+
+        self.axes.append(self.plotNone())
+        self.timestampBarBottom = self.__add_timestamp_bar(0.003, top=False)
+        self.__set_lim(orgSize=True)
+        self.bottom = self.axes[-1].get_ybound()[0]
+        self.ruler = self.__add_ruler(self.displayColor['TR'])
+        self.zoomMarker1 = self.__add_ruler(self.displayColor['ZM'])
+        self.zoomMarker2 = self.__add_ruler(self.displayColor['ZM'])
+        # Set view size fit with the given data
+        if self.widgt.geometry().height() < self.plottingHPixel:
+            self.widgt.setFixedHeight(self.plottingHPixel)
+
+        self.__draw()
+
+    def hide_plots(self, plot_indexes):
+        if self.axes == []:
+            return
+        plot_indexes = sorted(plot_indexes)
+        idx = 0
+        total_h = 0
+        for i in range(plot_indexes[0], len(self.axes)):
+            pos = self.axes[i].get_position()
+            pos.y0 += total_h
+            pos.y1 += total_h
+            if idx < len(plot_indexes) and i == plot_indexes[idx]:
+                h = pos.y1 - pos.y0
+                total_h += h
+                pos.y0 = pos.y1
+                idx += 1
+                self.hidden_plots[i] = h
+            self.axes[i].set_position(pos)
+
+        # currently consider every plot height are all 100px height
+        height = self.widgt.geometry().height() - 100 * len(plot_indexes)
+        self.widgt.setFixedHeight(height)
+        self.__draw()
+
+    def hide_currplot(self):
+        pos = self.currplot.get_position()
+        h = pos.y1 - pos.y0
+        pos.y0 = pos.y1
+        self.currplot.set_position(pos)
+        for i in range(self.currplot_index + 1, len(self.axes)):
+            pos = self.axes[i].get_position()
+            pos.y0 += h
+            pos.y1 += h
+            self.axes[i].set_position(pos)
+        # currently consider every plot height are all 100px height
+        height = self.widgt.geometry().height() - 100
+        self.widgt.setFixedHeight(height)
+        self.hidden_plots[self.currplot_index] = h
+        self.__draw()
+
+    def show_hidden_plot(self, index):
+        h = self.hidden_plots[index]
+        workplot = self.axes[index]
+        pos = workplot.get_position()
+        pos.y1 = pos.y0 + h
+        workplot.set_position(pos)
+        for i in range(index, len(self.axes)):
+            pos = self.axes[i].get_position()
+            pos.y0 -= h
+            pos.y1 -= h
+            self.axes[i].set_position(pos)
+        # currently consider every plot height are all 100px height
+        height = self.widgt.geometry().height() + 100
+        self.widgt.setFixedHeight(height)
+        del self.hidden_plots[index]
+        pos = workplot.get_position()
+        self.__draw()
+
+    def show_all_hidden_plots(self):
+        plot_indexes = sorted(self.hidden_plots.keys())
+        idx = 0
+        total_h = 0
+        for i in range(plot_indexes[0], len(self.axes)):
+            pos = self.axes[i].get_position()
+            if idx < len(plot_indexes) and i == plot_indexes[idx]:
+                h = self.hidden_plots[i]
+                total_h += h
+                pos.y1 = pos.y0 + h
+                idx += 1
+                del self.hidden_plots[i]
+            pos.y0 -= total_h
+            pos.y1 -= total_h
+            self.axes[i].set_position(pos)
+
+        # currently consider every plot height are all 100px height
+        height = self.widgt.geometry().height() + 100 * len(plot_indexes)
+        self.widgt.setFixedHeight(height)
+        self.__draw()
+
+    def set_colors(self, mode):
+        self.cMode = mode
+        self.displayColor = set_colors(mode)
+        self.fig.patch.set_facecolor(self.displayColor['MF'])
diff --git a/sohstationviewer/view/dataTypedialog.py b/sohstationviewer/view/dataTypedialog.py
new file mode 100755
index 0000000000000000000000000000000000000000..a546a2ddfb15fa2c90a001cbe229bb4e8d118d4a
--- /dev/null
+++ b/sohstationviewer/view/dataTypedialog.py
@@ -0,0 +1,34 @@
+"""
+datatypedialog.py
+GUI to add/edit/remove dataTypes
+NOTE: Cannot remove or change dataTypes that already have channels.
+"""
+
+from sohstationviewer.view.core.dbgui_superclass import Ui_DBInfoDialog
+from sohstationviewer.database.proccessDB import executeDB
+
+
+class DataTypeDialog(Ui_DBInfoDialog):
+    def __init__(self, parent):
+        super().__init__(parent, ['No.', 'DataType'], 'dataType', 'dataTypes',
+                         resizeContentColumns=[0])
+        self.setWindowTitle("Edit/Add/Delete DataTypes")
+
+    def addRow(self, rowidx, fk=False):
+        self.addWidget(None, rowidx, 0)       # No.
+        self.addWidget(self.dataList, rowidx, 1, foreignkey=fk)
+
+    def getDataList(self):
+        dataTypeRows = executeDB('SELECT * FROM DataTypes')
+        return [[d[0]] for d in dataTypeRows]
+
+    def getRowInputs(self, rowidx):
+        return [self.dataTableWidget.cellWidget(rowidx, 1).text().strip()]
+
+    def updateData(self, row, widgetidx, listidx):
+        insertsql = (f"INSERT INTO DataTypes VALUES('{row[0]}')")
+        updatesql = (f"UPDATE DataTypes SET dataType='{row[0]}' "
+                     f"WHERE dataType='%s'")
+
+        return super().updateData(
+            row, widgetidx, listidx, insertsql, updatesql)
diff --git a/sohstationviewer/view/mainwindow.py b/sohstationviewer/view/mainwindow.py
index 30b7c785670952fdd90c528ad15770ed6b72cd45..b7a90bbec32f2ba16f2ac2ae8cb1561edcf28b4b 100755
--- a/sohstationviewer/view/mainwindow.py
+++ b/sohstationviewer/view/mainwindow.py
@@ -1,21 +1,21 @@
 import pathlib
+import os
 
-from PySide2 import QtCore, QtGui, QtWidgets
+from PySide2 import QtCore, QtWidgets
 
 from sohstationviewer.view.ui.main_ui import Ui_MainWindow
 from sohstationviewer.view.calendardialog import CalendarDialog
-from sohstationviewer.view.calendarwidget import CalendarWidget
-from sohstationviewer.view.filelist import FileListItem
+from sohstationviewer.view.core.filelistwidget import FileListItem
+from sohstationviewer.controller.processing import loadData, detectDataType
+from sohstationviewer.view.dataTypedialog import DataTypeDialog
+from sohstationviewer.view.paramdialog import ParamDialog
+from sohstationviewer.view.channeldialog import ChannelDialog
+from sohstationviewer.view.plottypedialog import PlotTypeDialog
+from sohstationviewer.view.channelpreferdialog import ChannelPreferDialog
+from sohstationviewer.database.proccessDB import executeDB_dict
 
 
 class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
-    """
-    Implements the core logic for the Ui_MainWindow class
-    produced by Qt Designer. Any custom slots / signals
-    should be implemented in this class. Any modifications to
-    Ui_MainWindow *will* be lost if the UI is ever updated in
-    Qt Designer.
-    """
 
     currentDirectoryChanged = QtCore.Signal(str)
 
@@ -23,128 +23,78 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
         super().__init__(parent)
         self.setupUi(self)
 
-        cwd = str(pathlib.Path().resolve().parent)
-        self.setCurrentDirectory(cwd)
-
-        # File menu
-        self.deleteSetup.triggered.connect(self.deleteSetupFile)
-
-        # Commands menu
-        self.openGPSPlots.triggered.connect(self.openGpsPlot)
-        self.searchLog.triggered.connect(self.openLogSearch)
-        self.plotTimeRanges.triggered.connect(self.openPlotTimeRanges)
-        self.plotPositions.triggered.connect(self.openPlotPositions)
-
-        # Commands/Export submenu
-        self.exportDeploymentFile.triggered.connect(self.exportDepLine)
-        self.exportDeploymentBlock.triggered.connect(self.exportDepBlock)
-        self.exportTSPGeometry.triggered.connect(self.exportTspGeom)
-        self.exportShotInfo.triggered.connect(self.exportShotInf)
-
-        # Help menu
-        self.openCalendar.triggered.connect(self.openCalendarWidget)
-
-        # Options Menu
-        self.sortGroup = QtWidgets.QActionGroup(self)
-        self.sortGroup.addAction(self.sortFilesByType)
-        self.sortGroup.addAction(self.sortFilesAlphabetically)
-        self.sortFilesByType.setChecked(True)
-
-        self.colorGroup = QtWidgets.QActionGroup(self)
-        self.colorGroup.addAction(self.colorMPRegular)
-        self.colorGroup.addAction(self.colorMPTrillium)
-        self.colorMPRegular.setChecked(True)
-
-        self.dateGroup = QtWidgets.QActionGroup(self)
-        self.dateGroup.addAction(self.showYYYYDOYDates)
-        self.dateGroup.addAction(self.showYYYY_MM_DDDates)
-        self.dateGroup.addAction(self.showYYYYMMMDDDates)
-        # self.showYYYYDOYDates.setChecked(True)
-        self.showYYYY_MM_DDDates.setChecked(True)
-
-        # Connect slots to change the date format displayed
-        # by the QDateEdit widgets
-        # self.showYYYYDOYDates.triggered.connect(
-        # lambda: self.setDateFormat('yyyy:D'))
-        self.showYYYY_MM_DDDates.triggered.connect(
-            lambda: self.setDateFormat('yyyy-MM-dd'))
-        self.showYYYYMMMDDDates.triggered.connect(
-            lambda: self.setDateFormat('yyyyMMMdd'))
-
-        self.timeToDateEdit.setCalendarWidget(CalendarWidget(self))
-        self.timeToDateEdit.setDate(QtCore.QDate.currentDate())
-
-        self.timeFromDateEdit.setCalendarWidget(CalendarWidget(self))
-        self.timeFromDateEdit.setDate(QtCore.QDate.currentDate())
-
-        # self.showYYYYDOYDates.triggered.emit()
-        self.showYYYY_MM_DDDates.triggered.emit()
-
-        self.openFilesList.itemDoubleClicked.connect(
-            self.openFilesListItemDoubleClicked)
-
-        pal = self.openFilesList.palette()
-        pal.setColor(QtGui.QPalette.Highlight, QtGui.QColor(128, 255, 128))
-        pal.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(0, 0, 0))
-        self.openFilesList.setPalette(pal)
+        # Options
+        self.dateFormat = 'YYYY-MM-DD'
+        self.massPosVoltRangeOpt = 'regular'
+        self.bitweightOpt = ''
+        self.getChannelPrefer()
+        self.YYYY_MM_DDAction.triggered.emit()
 
-    @QtCore.Slot()
-    def deleteSetupFile(self):
-        print('Deleting setup file.')
-
-    @QtCore.Slot()
-    def openGpsPlot(self):
-        print('Opening GPS plot.')
-
-    @QtCore.Slot()
-    def openLogSearch(self):
-        print('Opening Log search.')
+    def resizeEvent(self, event):
+        self.plottingWidget.init_size()
 
     @QtCore.Slot()
-    def openPlotTimeRanges(self):
-        print('Opening Time Ranges Plot.')
+    def openDataType(self):
+        win = DataTypeDialog(self)
+        win.show()
 
     @QtCore.Slot()
-    def openPlotPositions(self):
-        print('Opening Positions Plot.')
+    def openParam(self):
+        win = ParamDialog(self)
+        win.show()
 
     @QtCore.Slot()
-    def exportDepLine(self):
-        print('Exporting Deployment File (Line).')
+    def openChannel(self):
+        win = ChannelDialog(self)
+        win.show()
 
     @QtCore.Slot()
-    def exportDepBlock(self):
-        print('Exporting Deployment File (Block).')
+    def openCalendarWidget(self):
+        calendar = CalendarDialog(self)
+        calendar.show()
 
     @QtCore.Slot()
-    def exportTspGeom(self):
-        print('Exporting TSP Shot File / Geometry.')
+    def openPlotType(self):
+        win = PlotTypeDialog(self)
+        win.show()
 
     @QtCore.Slot()
-    def exportShotInf(self):
-        print('Exporting Shot Info.')
+    def openChannelPreferences(self):
+        dirnames = [os.path.join(self.cwdLineEdit.text(), item.text())
+                    for item in self.openFilesList.selectedItems()]
+        if dirnames == []:
+            msg = "No directories has been selected."
+            QtWidgets.QMessageBox.warning(self, "Select directory", msg)
+            return
+        win = ChannelPreferDialog(self, dirnames)
+        win.show()
 
     @QtCore.Slot()
-    def readSelectedFile(self):
-        print('Reading currently selected file.')
-
-        # TODO: Launch a separate thread, so that
-        # the UI doesn't hang while data is being read.
-        # Otherwise, the user won't be able to cancel.
+    def allChanClicked(self):
+        if not self.allChanCheckBox.isChecked():
+            if self.IDs == []:
+                self.allChanCheckBox.setChecked(True)
+            else:
+                self.currIDsNameLineEdit.setText(self.IDsName)
+        else:
+            self.currIDsNameLineEdit.setText('')
 
     @QtCore.Slot()
-    def stopFileRead(self):
-        print('Abandoning file read.')
+    def replotLoadedData(self):
+        self.plottingWidget.resetView()
 
     @QtCore.Slot()
-    def writePSFile(self):
-        print('Writing PS file.')
+    def setDateFormat(self, displayFormat):
+        """
+        Sets the calendar format used by the QDateEdit text boxes.
+        :param displayFormat: str
+                A valid display format to be used for date conversion.
+        """
+        self.timeToDateEdit.setDisplayFormat(displayFormat)
+        self.timeFromDateEdit.setDisplayFormat(displayFormat)
+        self.dateFormat = displayFormat
 
     @QtCore.Slot()
-    def reloadFile(self):
-        print('Reloading last file.')
-
-    @QtCore.Slot(FileListItem)
     def openFilesListItemDoubleClicked(self, item):
         """
         Handles the double-click event emitted when a user double-clicks on an
@@ -161,31 +111,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
         # i.e., path.open(), or path.iterdir() ...
         # path = item.filePath
 
-    @QtCore.Slot(str)
-    def setDateFormat(self, displayFormat):
-        """
-        Sets the calendar format used by the QDateEdit
-        text boxes.
-
-        Parameters
-        ----------
-        displayFormat : str
-                A valid display format to be used for date conversion.
-        """
-        self.timeToDateEdit.setDisplayFormat(displayFormat)
-        self.timeFromDateEdit.setDisplayFormat(displayFormat)
-
-    def setCurrentDirectory(self, path=''):
-        # Remove entries when cwd changes
-        self.openFilesList.clear()
-        # Signal cwd changed, and gather list of files in new cwd
-        self.currentDirectoryChanged.emit(path)
-        for dent in pathlib.Path(path).iterdir():
-            if not dent.is_dir() or dent.name.startswith('.'):
-                continue
-
-            self.openFilesList.addItem(FileListItem(dent))
-
     @QtCore.Slot()
     def changeCurrentDirectory(self):
         """
@@ -201,11 +126,95 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
         self.setCurrentDirectory(new_path)
 
     @QtCore.Slot()
-    def openCalendarWidget(self):
+    def readSelectedFiles(self):
+        print('Reading currently selected file.')
+
+        # TODO: Launch a separate thread, so that
+        # the UI doesn't hang while data is being read.
+        # Otherwise, the user won't be able to cancel.
+
+        # reqInfoChans = [
+        #     'ACE', 'LOG', 'HH1', 'HH2', 'HHZ', 'LCE', 'LCQ',
+        #     'LH1', 'LH2', 'LHZ', 'VCO', 'VEA', 'VEC', 'VEP',
+        #     'VKI', 'VM1', 'VM2', 'VM3', 'VPB']
+        reqInfoChans = (self.IDs if not self.allChanCheckBox.isChecked()
+                        else [])
+
+        # TODO: Having a form for user to create the list of channels to draw
         """
-        Constructs a subclass of QCalendarDialog
-        which implements additional functionality to display
-        Julian dates alongside standard calendar format.
+        reqInfoChans: list of chans to read data from
+        It can be all chans in db or preference list of chans
+        For Reftek, the list of channels is fixed => may not need
         """
-        calendar = CalendarDialog(self)
-        calendar.show()
+        dirnames = [os.path.join(self.cwdLineEdit.text(), item.text())
+                    for item in self.openFilesList.selectedItems()]
+        if dirnames == []:
+            msg = "No directories has been selected."
+            QtWidgets.QMessageBox.warning(self, "Select directory", msg)
+            return
+        self.dataType = detectDataType(self, dirnames)
+        if self.dataType is None:
+            return
+
+        reqDSs = []
+        for idx, DSCheckbox in enumerate(self.dsCheckBoxes):
+            if DSCheckbox.isChecked():
+                reqDSs.append(idx + 1)
+
+        if (not self.allChanCheckBox.isChecked() and
+                self.dataType != self.IDsDataType):
+            msg = (f"DataType detected for the selected data set is "
+                   f"{self.dataType} which is different to IDs' "
+                   f"{self.IDsDataType}.\n"
+                   f"SOHStationViewer will read all data available.\n"
+                   f"Do you want to continue?")
+            result = QtWidgets.QMessageBox.question(
+                self, "Confirmation", msg,
+                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+            if result == QtWidgets.QMessageBox.No:
+                return
+            self.allChanCheckBox.setChecked(True)
+            self.currIDsNameLineEdit.setText('')
+            reqInfoChans = []
+
+        plottingDataSets = loadData(
+            self.dataType, self, dirnames, reqInfoChans, reqDSs)
+        if self.detectGapCheckBox.isChecked():
+            self.minGap = self.gapLenLineEdit.text()
+        else:
+            self.minGap = None
+        if len(plottingDataSets) == 1:
+            setID = list(plottingDataSets.keys())[0]
+            plottingData = plottingDataSets[setID]
+        else:
+            # TODO: create form with buttons of all sets for user to choose
+            # which one to plot
+            print("ask user with set of net,stat,loc they want to look at")
+        timeTickTotal = 5     # TODO: let user choose max ticks to be displayed
+        reqInfoChans = (reqInfoChans if reqInfoChans != []
+                        else plottingData['channels'].keys())
+        print("MAin Window: all channels:", plottingData['channels'].keys())
+        self.plottingWidget.addPlots(setID, plottingData,
+                                     reqInfoChans, timeTickTotal)
+
+    def setCurrentDirectory(self, path=''):
+        # Remove entries when cwd changes
+        self.openFilesList.clear()
+        # Signal cwd changed, and gather list of files in new cwd
+        self.currentDirectoryChanged.emit(path)
+        for dent in pathlib.Path(path).iterdir():
+            if not dent.is_dir() or dent.name.startswith('.'):
+                continue
+
+            self.openFilesList.addItem(FileListItem(dent))
+
+    def getChannelPrefer(self):
+        self.IDsName = ''
+        self.IDs = []
+        self.dataType = 'Unknown'
+        rows = executeDB_dict('SELECT name, IDs, dataType FROM ChannelPrefer '
+                              'WHERE current=1')
+        if len(rows) > 0:
+            self.IDsName = rows[0]['name']
+            self.IDs = [t.strip() for t in rows[0]['IDs'].split(',')]
+            self.IDsDataType = rows[0]['dataType']
diff --git a/sohstationviewer/view/paramdialog.py b/sohstationviewer/view/paramdialog.py
new file mode 100755
index 0000000000000000000000000000000000000000..9cdfcc2cf91ba7fa14f4c852194d23d23d6db72c
--- /dev/null
+++ b/sohstationviewer/view/paramdialog.py
@@ -0,0 +1,65 @@
+"""
+paramdialog.py
+GUI to add/dit/remove params
+NOTE: Cannot remove or change params that are already used for channels.
+"""
+from PySide2 import QtWidgets
+
+from sohstationviewer.view.core.dbgui_superclass import Ui_DBInfoDialog
+from sohstationviewer.view.core import plottingWidget
+from sohstationviewer.database.proccessDB import executeDB
+from sohstationviewer.conf.dbSettings import conf
+
+
+class ParamDialog(Ui_DBInfoDialog):
+    def __init__(self, parent):
+        super().__init__(
+            parent,
+            ['No.', 'Param', 'Plot Type', 'ValueColors', 'Height    '],
+            'param', 'parameters',
+            resizeContentColumns=[0, 3])
+        self.setWindowTitle("Edit/Add/Delete Parameters")
+
+    def addRow(self, rowidx, fk=False):
+        self.addWidget(None, rowidx, 0)       # No.
+        self.addWidget(self.dataList, rowidx, 1, foreignkey=fk)
+        self.addWidget(self.dataList, rowidx, 2,
+                       choices=[''] + sorted(plottingWidget.plotFunc.keys()))
+        self.addWidget(self.dataList, rowidx, 3)
+        self.addWidget(self.dataList, rowidx, 4,
+                       range=[0, 10])
+
+    def getDataList(self):
+        paramRows = executeDB('SELECT * FROM Parameters')
+        return [[d[0],
+                 '' if d[1] is None else d[1],
+                 d[2]]
+                for d in paramRows]
+
+    def getRowInputs(self, rowidx):
+        # check vallueColors string
+        valueColorsString = self.dataTableWidget.cellWidget(
+            rowidx, 3).currentText().strip()
+        valueColors = valueColorsString.split("|")
+        for vc in valueColors:
+            if not conf['valColRE'].match(vc):
+                msg = (f"The valueColor is requested for  '{vc}' at line "
+                       f"{rowidx}does not match the required format:"
+                       f"[+]value:color with color=R,Y,G,M,C."
+                       f"\n Ex: 2.3:C|+4:M")
+                QtWidgets.QMessageBox.information(self, "Error", msg)
+        return [
+            self.dataTableWidget.cellWidget(rowidx, 1).text().strip(),
+            self.dataTableWidget.cellWidget(rowidx, 2).currentText().strip(),
+            self.dataTableWidget.cellWidget(rowidx, 3).currentText().strip(),
+            int(self.dataTableWidget.cellWidget(rowidx, 4).text())
+        ]
+
+    def updateData(self, row, widgetidx, listidx):
+        insertsql = (f"INSERT INTO Parameters VALUES"
+                     f"('{row[0]}', '{row[1]}', {row[2]})")
+        updatesql = (f"UPDATE Parameters SET param='{row[0]}', "
+                     f"plotType='{row[1]}', height={row[2]} "
+                     f"WHERE param='%s'")
+        return super().updateData(
+            row, widgetidx, listidx, insertsql, updatesql)
diff --git a/sohstationviewer/view/plottypedialog.py b/sohstationviewer/view/plottypedialog.py
new file mode 100755
index 0000000000000000000000000000000000000000..4611d3eef0d7e87c0050a61f33f7dfecafd84709
--- /dev/null
+++ b/sohstationviewer/view/plottypedialog.py
@@ -0,0 +1,24 @@
+"""
+plottypedialog
+GUI to view the types of plotting and their descriptions
+NOTE: plottypes are defined in plottingWidget
+"""
+
+from sohstationviewer.view.core.dbgui_superclass import Ui_DBInfoDialog
+from sohstationviewer.view.core import plottingWidget
+
+
+class PlotTypeDialog(Ui_DBInfoDialog):
+    def __init__(self, parent):
+        super().__init__(
+            parent, ['No.', '       Plot Type        ', 'Description'],
+            '', '', resizeContentColumns=[0, 1], checkFK=False)
+        self.setWindowTitle("Plotting Types")
+
+    def addRow(self, rowidx, fk=False):
+        self.addWidget(None, rowidx, 0)       # No.
+        self.addWidget(self.dataList, rowidx, 1, foreignkey=True)
+        self.addWidget(self.dataList, rowidx, 2, foreignkey=True)
+
+    def getDataList(self):
+        return [[key, val[0]] for key, val in plottingWidget.plotFunc.items()]
diff --git a/sohstationviewer/view/ui/about_ui.py b/sohstationviewer/view/ui/about_ui_qtdesigner.py
similarity index 100%
rename from sohstationviewer/view/ui/about_ui.py
rename to sohstationviewer/view/ui/about_ui_qtdesigner.py
diff --git a/sohstationviewer/view/ui/calendar_ui.py b/sohstationviewer/view/ui/calendar_ui_qtdesigner.py
similarity index 78%
rename from sohstationviewer/view/ui/calendar_ui.py
rename to sohstationviewer/view/ui/calendar_ui_qtdesigner.py
index 0b272eefdeecaf28f1fc43c7fe325d647f9c0d41..34e8bfe7329fc58cfc6afa4bd9c9ef2b93d007ff 100644
--- a/sohstationviewer/view/ui/calendar_ui.py
+++ b/sohstationviewer/view/ui/calendar_ui_qtdesigner.py
@@ -8,7 +8,10 @@
 #
 # WARNING! All changes made in this file will be lost!
 
-from PySide2 import QtCore, QtGui, QtWidgets
+from PySide2 import QtCore, QtWidgets
+
+from sohstationviewer.view.core.calendarwidget import CalendarWidget
+
 
 class Ui_CalendarDialog(object):
     def setupUi(self, CalendarDialog):
@@ -29,11 +32,12 @@ class Ui_CalendarDialog(object):
         self.verticalLayout.addLayout(self.horizontalLayout)
 
         self.retranslateUi(CalendarDialog)
-        QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), CalendarDialog.accept)
-        QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), CalendarDialog.reject)
+        QtCore.QObject.connect(
+            self.buttonBox, QtCore.SIGNAL("accepted()"), CalendarDialog.accept)
+        QtCore.QObject.connect(
+            self.buttonBox, QtCore.SIGNAL("rejected()"), CalendarDialog.reject)
         QtCore.QMetaObject.connectSlotsByName(CalendarDialog)
 
     def retranslateUi(self, CalendarDialog):
-        CalendarDialog.setWindowTitle(QtWidgets.QApplication.translate("CalendarDialog", "Calendar", None, -1))
-
-from sohstationviewer.view.calendarwidget import CalendarWidget
+        CalendarDialog.setWindowTitle(QtWidgets.QApplication.translate(
+            "CalendarDialog", "Calendar", None, -1))
diff --git a/sohstationviewer/view/ui/main_ui.py b/sohstationviewer/view/ui/main_ui.py
old mode 100644
new mode 100755
index 53dac5eabc5eea9e30d13a17f9870570d5a11d90..aecc4a4d09575a99021c93b4aa6b7bf7a4e98b8c
--- a/sohstationviewer/view/ui/main_ui.py
+++ b/sohstationviewer/view/ui/main_ui.py
@@ -1,635 +1,458 @@
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file 'sohstationviewer/view/ui/main.ui',
-# licensing of 'sohstationviewer/view/ui/main.ui' applies.
-#
-# Created: Wed Jun  2 10:54:48 2021
-#      by: pyside2-uic  running on PySide2 5.13.2
-#
-# WARNING! All changes made in this file will be lost!
+# UI and connectSignals for MainWindow
 
 from PySide2 import QtCore, QtGui, QtWidgets
 
+from sohstationviewer.view.core.calendarwidget import CalendarWidget
+from sohstationviewer.view.core.plottingWidget import PlottingWidget
+
+
 class Ui_MainWindow(object):
     def setupUi(self, MainWindow):
-        MainWindow.setObjectName("MainWindow")
+        self.MainWindow = MainWindow
         MainWindow.resize(1798, 1110)
-        MainWindow.setUnifiedTitleAndToolBarOnMac(False)
-        self.centralwidget = QtWidgets.QWidget(MainWindow)
-        self.centralwidget.setObjectName("centralwidget")
-        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
-        self.gridLayout.setContentsMargins(2, 2, 2, 2)
-        self.gridLayout.setObjectName("gridLayout")
-        self.cwdPushButton = QtWidgets.QPushButton(self.centralwidget)
-        self.cwdPushButton.setObjectName("cwdPushButton")
-        self.gridLayout.addWidget(self.cwdPushButton, 0, 0, 1, 1)
-        self.cwdLineEdit = QtWidgets.QLineEdit(self.centralwidget)
-        self.cwdLineEdit.setObjectName("cwdLineEdit")
-        self.gridLayout.addWidget(self.cwdLineEdit, 0, 1, 1, 1)
-        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
-        self.gridLayout.addItem(spacerItem, 0, 2, 1, 1)
-        self.timeFromGrid = QtWidgets.QGridLayout()
-        self.timeFromGrid.setObjectName("timeFromGrid")
-        self.timeFromLabel = QtWidgets.QLabel(self.centralwidget)
-        self.timeFromLabel.setObjectName("timeFromLabel")
-        self.timeFromGrid.addWidget(self.timeFromLabel, 0, 0, 1, 1)
-        self.timeFromDateEdit = QtWidgets.QDateEdit(self.centralwidget)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.timeFromDateEdit.sizePolicy().hasHeightForWidth())
-        self.timeFromDateEdit.setSizePolicy(sizePolicy)
-        self.timeFromDateEdit.setObjectName("timeFromDateEdit")
+        MainWindow.setWindowTitle("SOH Station Viewer")
+        self.centralWidget = QtWidgets.QWidget(MainWindow)
+        MainWindow.setCentralWidget(self.centralWidget)
+
+        mainLayout = QtWidgets.QVBoxLayout()
+        mainLayout.setContentsMargins(5, 5, 5, 5)
+        mainLayout.setSpacing(0)
+        self.centralWidget.setLayout(mainLayout)
+        self.setFirstRow(mainLayout)
+        self.setSecondRow(mainLayout)
+        self.trackingInfoTextBrowser = QtWidgets.QTextBrowser(
+            self.centralWidget)
+        self.trackingInfoTextBrowser.setFixedHeight(60)
+        mainLayout.addWidget(self.trackingInfoTextBrowser)
+        self.createMenuBar(MainWindow)
+        self.connectSignals(MainWindow)
+
+    def setFirstRow(self, mainLayout):
+        hLayout = QtWidgets.QHBoxLayout()
+        hLayout.setContentsMargins(2, 2, 2, 2)
+        hLayout.setSpacing(8)
+        mainLayout.addLayout(hLayout)
+
+        self.cwdButton = QtWidgets.QPushButton(
+            "Main Data Directory", self.centralWidget)
+        hLayout.addWidget(self.cwdButton)
+
+        self.cwdLineEdit = QtWidgets.QLineEdit(
+            self.centralWidget)
+        hLayout.addWidget(self.cwdLineEdit, 1)
+
+        hLayout.addSpacing(40)
+
+        hLayout.addWidget(QtWidgets.QLabel('From'))
+        self.timeFromDateEdit = QtWidgets.QDateEdit(
+            self.centralWidget)
         self.timeFromDateEdit.setCalendarPopup(True)
         self.timeFromDateEdit.setDisplayFormat("yyyy-MM-dd")
-        self.timeFromGrid.addWidget(self.timeFromDateEdit, 0, 1, 1, 1)
-        self.gridLayout.addLayout(self.timeFromGrid, 0, 3, 1, 1)
-        self.timeToGrid = QtWidgets.QGridLayout()
-        self.timeToGrid.setObjectName("timeToGrid")
-        self.timeToDateEdit = QtWidgets.QDateEdit(self.centralwidget)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.timeToDateEdit.sizePolicy().hasHeightForWidth())
-        self.timeToDateEdit.setSizePolicy(sizePolicy)
-        self.timeToDateEdit.setObjectName("timeToDateEdit")
+        hLayout.addWidget(self.timeFromDateEdit)
+
+        hLayout.addWidget(QtWidgets.QLabel('To'))
+        self.timeToDateEdit = QtWidgets.QDateEdit(
+            self.centralWidget)
         self.timeToDateEdit.setCalendarPopup(True)
         self.timeToDateEdit.setDisplayFormat("yyyy-MM-dd")
-        self.timeToGrid.addWidget(self.timeToDateEdit, 0, 1, 1, 1)
-        self.timeToLabel = QtWidgets.QLabel(self.centralwidget)
-        self.timeToLabel.setObjectName("timeToLabel")
-        self.timeToGrid.addWidget(self.timeToLabel, 0, 0, 1, 1)
-        self.gridLayout.addLayout(self.timeToGrid, 0, 4, 1, 1)
-        self.verticalSplit = QtWidgets.QSplitter(self.centralwidget)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.verticalSplit.sizePolicy().hasHeightForWidth())
-        self.verticalSplit.setSizePolicy(sizePolicy)
-        self.verticalSplit.setOrientation(QtCore.Qt.Vertical)
-        self.verticalSplit.setHandleWidth(2)
-        self.verticalSplit.setChildrenCollapsible(False)
-        self.verticalSplit.setObjectName("verticalSplit")
-        self.mainWidget = QtWidgets.QWidget(self.verticalSplit)
-        self.mainWidget.setObjectName("mainWidget")
-        self.gridLayout_3 = QtWidgets.QGridLayout(self.mainWidget)
-        self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
-        self.gridLayout_3.setObjectName("gridLayout_3")
-        self.horizontalSplit = QtWidgets.QSplitter(self.mainWidget)
-        self.horizontalSplit.setOrientation(QtCore.Qt.Horizontal)
-        self.horizontalSplit.setHandleWidth(2)
-        self.horizontalSplit.setChildrenCollapsible(False)
-        self.horizontalSplit.setObjectName("horizontalSplit")
-        self.sideScrollArea = QtWidgets.QScrollArea(self.horizontalSplit)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.sideScrollArea.sizePolicy().hasHeightForWidth())
-        self.sideScrollArea.setSizePolicy(sizePolicy)
-        self.sideScrollArea.setMinimumSize(QtCore.QSize(0, 0))
-        self.sideScrollArea.setMaximumSize(QtCore.QSize(250, 16777215))
-        self.sideScrollArea.setWidgetResizable(True)
-        self.sideScrollArea.setObjectName("sideScrollArea")
-        self.sideScrollAreaContents = QtWidgets.QWidget()
-        self.sideScrollAreaContents.setGeometry(QtCore.QRect(0, 0, 264, 815))
-        self.sideScrollAreaContents.setObjectName("sideScrollAreaContents")
-        self.verticalLayout = QtWidgets.QVBoxLayout(self.sideScrollAreaContents)
-        self.verticalLayout.setSpacing(2)
-        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
-        self.verticalLayout.setObjectName("verticalLayout")
-        self.primaryGrid = QtWidgets.QGridLayout()
-        self.primaryGrid.setContentsMargins(0, 0, 0, 0)
-        self.primaryGrid.setObjectName("primaryGrid")
-        self.sep0 = QtWidgets.QFrame(self.sideScrollAreaContents)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.sep0.sizePolicy().hasHeightForWidth())
-        self.sep0.setSizePolicy(sizePolicy)
-        self.sep0.setFrameShape(QtWidgets.QFrame.HLine)
-        self.sep0.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.sep0.setObjectName("sep0")
-        self.primaryGrid.addWidget(self.sep0, 12, 1, 1, 1)
-        self.dsGrid = QtWidgets.QGridLayout()
-        self.dsGrid.setObjectName("dsGrid")
-        self.ds6CheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.ds6CheckBox.setObjectName("ds6CheckBox")
-        self.dsGrid.addWidget(self.ds6CheckBox, 2, 3, 1, 1)
-        self.ds3CheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.ds3CheckBox.setObjectName("ds3CheckBox")
-        self.dsGrid.addWidget(self.ds3CheckBox, 0, 3, 1, 1)
-        self.dsTPSCheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.dsTPSCheckBox.setObjectName("dsTPSCheckBox")
-        self.dsGrid.addWidget(self.dsTPSCheckBox, 3, 2, 1, 1)
-        self.dsLabel = QtWidgets.QLabel(self.sideScrollAreaContents)
-        self.dsLabel.setObjectName("dsLabel")
-        self.dsGrid.addWidget(self.dsLabel, 2, 0, 1, 1)
-        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
-        self.dsGrid.addItem(spacerItem1, 2, 4, 1, 1)
-        self.ds2CheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.ds2CheckBox.setObjectName("ds2CheckBox")
-        self.dsGrid.addWidget(self.ds2CheckBox, 0, 2, 1, 1)
-        self.ds1CheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.ds1CheckBox.setObjectName("ds1CheckBox")
-        self.dsGrid.addWidget(self.ds1CheckBox, 0, 1, 1, 1)
-        self.ds4CheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.ds4CheckBox.setObjectName("ds4CheckBox")
-        self.dsGrid.addWidget(self.ds4CheckBox, 2, 1, 1, 1)
-        self.ds5checkBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.ds5checkBox.setObjectName("ds5checkBox")
-        self.dsGrid.addWidget(self.ds5checkBox, 2, 2, 1, 1)
-        self.primaryGrid.addLayout(self.dsGrid, 11, 1, 1, 1)
-        self.sep2 = QtWidgets.QFrame(self.sideScrollAreaContents)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.sep2.sizePolicy().hasHeightForWidth())
-        self.sep2.setSizePolicy(sizePolicy)
-        self.sep2.setFrameShape(QtWidgets.QFrame.HLine)
-        self.sep2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.sep2.setObjectName("sep2")
-        self.primaryGrid.addWidget(self.sep2, 7, 1, 1, 1)
-        self.massPosGrid = QtWidgets.QGridLayout()
-        self.massPosGrid.setObjectName("massPosGrid")
-        self.massPosLabel = QtWidgets.QLabel(self.sideScrollAreaContents)
-        self.massPosLabel.setObjectName("massPosLabel")
-        self.massPosGrid.addWidget(self.massPosLabel, 0, 0, 1, 1)
-        self.hiChanCheckbox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.hiChanCheckbox.setObjectName("hiChanCheckbox")
-        self.massPosGrid.addWidget(self.hiChanCheckbox, 0, 2, 1, 1)
-        self.lowChanCheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.lowChanCheckBox.setObjectName("lowChanCheckBox")
-        self.massPosGrid.addWidget(self.lowChanCheckBox, 0, 1, 1, 1)
-        spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
-        self.massPosGrid.addItem(spacerItem2, 0, 3, 1, 1)
-        self.primaryGrid.addLayout(self.massPosGrid, 9, 1, 1, 1)
-        self.controlButtonGrid = QtWidgets.QGridLayout()
-        self.controlButtonGrid.setSpacing(2)
-        self.controlButtonGrid.setObjectName("controlButtonGrid")
-        self.writePushButton = QtWidgets.QPushButton(self.sideScrollAreaContents)
-        self.writePushButton.setObjectName("writePushButton")
-        self.controlButtonGrid.addWidget(self.writePushButton, 0, 3, 1, 1)
-        self.stopPushButton = QtWidgets.QPushButton(self.sideScrollAreaContents)
-        self.stopPushButton.setObjectName("stopPushButton")
-        self.controlButtonGrid.addWidget(self.stopPushButton, 0, 2, 1, 1)
-        self.readPushButton = QtWidgets.QPushButton(self.sideScrollAreaContents)
-        self.readPushButton.setObjectName("readPushButton")
-        self.controlButtonGrid.addWidget(self.readPushButton, 0, 1, 1, 1)
-        spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
-        self.controlButtonGrid.addItem(spacerItem3, 0, 0, 1, 1)
-        spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
-        self.controlButtonGrid.addItem(spacerItem4, 0, 4, 1, 1)
-        self.primaryGrid.addLayout(self.controlButtonGrid, 13, 1, 1, 1)
-        self.sep1 = QtWidgets.QFrame(self.sideScrollAreaContents)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.sep1.sizePolicy().hasHeightForWidth())
-        self.sep1.setSizePolicy(sizePolicy)
-        self.sep1.setFrameShape(QtWidgets.QFrame.HLine)
-        self.sep1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.sep1.setObjectName("sep1")
-        self.primaryGrid.addWidget(self.sep1, 10, 1, 1, 1)
-        self.sohCheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.sohCheckBox.setObjectName("sohCheckBox")
-        self.primaryGrid.addWidget(self.sohCheckBox, 8, 1, 1, 1)
-        self.listWidget_2 = QtWidgets.QListWidget(self.sideScrollAreaContents)
-        self.listWidget_2.setObjectName("listWidget_2")
-        self.primaryGrid.addWidget(self.listWidget_2, 15, 0, 1, 3)
-        self.mainOptionsGrid = QtWidgets.QGridLayout()
-        self.mainOptionsGrid.setHorizontalSpacing(1)
-        self.mainOptionsGrid.setVerticalSpacing(2)
-        self.mainOptionsGrid.setObjectName("mainOptionsGrid")
-        self.searchLineEdit = QtWidgets.QLineEdit(self.sideScrollAreaContents)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.searchLineEdit.sizePolicy().hasHeightForWidth())
-        self.searchLineEdit.setSizePolicy(sizePolicy)
-        self.searchLineEdit.setMinimumSize(QtCore.QSize(100, 0))
-        self.searchLineEdit.setMaximumSize(QtCore.QSize(150, 16777215))
-        self.searchLineEdit.setObjectName("searchLineEdit")
-        self.mainOptionsGrid.addWidget(self.searchLineEdit, 2, 0, 1, 1)
-        self.fileListGrid = QtWidgets.QGridLayout()
-        self.fileListGrid.setSpacing(2)
-        self.fileListGrid.setObjectName("fileListGrid")
-        self.fileListLogCheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.fileListLogCheckBox.setObjectName("fileListLogCheckBox")
-        self.fileListGrid.addWidget(self.fileListLogCheckBox, 0, 1, 1, 1)
-        self.fileListZipCheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.fileListZipCheckBox.setObjectName("fileListZipCheckBox")
-        self.fileListGrid.addWidget(self.fileListZipCheckBox, 1, 2, 1, 1)
-        self.fileListCfCheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.fileListCfCheckBox.setObjectName("fileListCfCheckBox")
-        self.fileListGrid.addWidget(self.fileListCfCheckBox, 1, 1, 1, 1)
-        self.fileListRefCheckBox = QtWidgets.QCheckBox(self.sideScrollAreaContents)
-        self.fileListRefCheckBox.setObjectName("fileListRefCheckBox")
-        self.fileListGrid.addWidget(self.fileListRefCheckBox, 0, 2, 1, 1)
-        self.label_4 = QtWidgets.QLabel(self.sideScrollAreaContents)
-        self.label_4.setObjectName("label_4")
-        self.fileListGrid.addWidget(self.label_4, 0, 0, 1, 1)
-        spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
-        self.fileListGrid.addItem(spacerItem5, 0, 5, 1, 1)
-        self.mainOptionsGrid.addLayout(self.fileListGrid, 3, 0, 1, 1)
-        self.backgroundGrid = QtWidgets.QGridLayout()
-        self.backgroundGrid.setSpacing(2)
-        self.backgroundGrid.setObjectName("backgroundGrid")
-        self.backgroundWhiteRadioButton = QtWidgets.QRadioButton(self.sideScrollAreaContents)
-        self.backgroundWhiteRadioButton.setChecked(True)
-        self.backgroundWhiteRadioButton.setObjectName("backgroundWhiteRadioButton")
-        self.buttonGroup = QtWidgets.QButtonGroup(MainWindow)
-        self.buttonGroup.setObjectName("buttonGroup")
-        self.buttonGroup.addButton(self.backgroundWhiteRadioButton)
-        self.backgroundGrid.addWidget(self.backgroundWhiteRadioButton, 0, 1, 1, 1)
-        self.backgroundBlackRadioButton = QtWidgets.QRadioButton(self.sideScrollAreaContents)
-        self.backgroundBlackRadioButton.setObjectName("backgroundBlackRadioButton")
-        self.buttonGroup.addButton(self.backgroundBlackRadioButton)
-        self.backgroundGrid.addWidget(self.backgroundBlackRadioButton, 0, 2, 1, 1)
-        self.backgroundLabel = QtWidgets.QLabel(self.sideScrollAreaContents)
-        self.backgroundLabel.setObjectName("backgroundLabel")
-        self.backgroundGrid.addWidget(self.backgroundLabel, 0, 0, 1, 1)
-        spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
-        self.backgroundGrid.addItem(spacerItem6, 0, 3, 1, 1)
-        self.mainOptionsGrid.addLayout(self.backgroundGrid, 4, 0, 1, 1)
-        self.plotControlGrid = QtWidgets.QGridLayout()
-        self.plotControlGrid.setSpacing(2)
-        self.plotControlGrid.setObjectName("plotControlGrid")
-        self.clearPushButton = QtWidgets.QPushButton(self.sideScrollAreaContents)
-        self.clearPushButton.setObjectName("clearPushButton")
-        self.plotControlGrid.addWidget(self.clearPushButton, 0, 0, 1, 1)
-        self.replotPushButton = QtWidgets.QPushButton(self.sideScrollAreaContents)
-        self.replotPushButton.setObjectName("replotPushButton")
-        self.plotControlGrid.addWidget(self.replotPushButton, 1, 0, 1, 1)
-        self.reloadPushButton = QtWidgets.QPushButton(self.sideScrollAreaContents)
-        self.reloadPushButton.setObjectName("reloadPushButton")
-        self.plotControlGrid.addWidget(self.reloadPushButton, 2, 0, 1, 1)
-        self.mainOptionsGrid.addLayout(self.plotControlGrid, 2, 1, 2, 1)
-        self.primaryGrid.addLayout(self.mainOptionsGrid, 4, 0, 1, 2)
-        self.openFilesList = QtWidgets.QListWidget(self.sideScrollAreaContents)
-        self.openFilesList.setObjectName("openFilesList")
-        self.primaryGrid.addWidget(self.openFilesList, 2, 0, 1, 2)
-        self.verticalLayout.addLayout(self.primaryGrid)
-        self.sideScrollArea.setWidget(self.sideScrollAreaContents)
-        self.mainScrollArea = QtWidgets.QScrollArea(self.horizontalSplit)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.mainScrollArea.sizePolicy().hasHeightForWidth())
-        self.mainScrollArea.setSizePolicy(sizePolicy)
-        self.mainScrollArea.setWidgetResizable(True)
-        self.mainScrollArea.setObjectName("mainScrollArea")
-        self.mainScrollAreaContents = QtWidgets.QWidget()
-        self.mainScrollAreaContents.setGeometry(QtCore.QRect(0, 0, 1540, 829))
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
-        sizePolicy.setHorizontalStretch(5)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.mainScrollAreaContents.sizePolicy().hasHeightForWidth())
-        self.mainScrollAreaContents.setSizePolicy(sizePolicy)
-        self.mainScrollAreaContents.setObjectName("mainScrollAreaContents")
-        self.gridLayout_2 = QtWidgets.QGridLayout(self.mainScrollAreaContents)
-        self.gridLayout_2.setSpacing(2)
-        self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
-        self.gridLayout_2.setObjectName("gridLayout_2")
-        # self.plottingWidget = PlottingWidget(self.mainScrollAreaContents)
-        # self.plottingWidget.setObjectName("plottingWidget")
-        # self.gridLayout_2.addWidget(self.plottingWidget, 0, 0, 1, 1)
-        self.mainScrollArea.setWidget(self.mainScrollAreaContents)
-        self.gridLayout_3.addWidget(self.horizontalSplit, 0, 0, 1, 1)
-        self.trackingInfoTextBrowser = QtWidgets.QTextBrowser(self.verticalSplit)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.trackingInfoTextBrowser.sizePolicy().hasHeightForWidth())
-        self.trackingInfoTextBrowser.setSizePolicy(sizePolicy)
-        self.trackingInfoTextBrowser.setMaximumSize(QtCore.QSize(16777215, 200))
-        self.trackingInfoTextBrowser.setObjectName("trackingInfoTextBrowser")
-        self.gridLayout.addWidget(self.verticalSplit, 1, 0, 1, 5)
-        MainWindow.setCentralWidget(self.centralwidget)
-        self.menubar = QtWidgets.QMenuBar(MainWindow)
-        self.menubar.setGeometry(QtCore.QRect(0, 0, 1798, 20))
-        self.menubar.setObjectName("menubar")
-        self.menuFile = QtWidgets.QMenu(self.menubar)
-        self.menuFile.setObjectName("menuFile")
-        self.menuCommand = QtWidgets.QMenu(self.menubar)
-        self.menuCommand.setObjectName("menuCommand")
-        self.menuExport_TT_Times_As = QtWidgets.QMenu(self.menuCommand)
-        self.menuExport_TT_Times_As.setObjectName("menuExport_TT_Times_As")
-        self.menuOptions = QtWidgets.QMenu(self.menubar)
-        self.menuOptions.setObjectName("menuOptions")
-        self.menuHelp = QtWidgets.QMenu(self.menubar)
-        self.menuHelp.setObjectName("menuHelp")
-        self.menuForm = QtWidgets.QMenu(self.menubar)
-        self.menuForm.setObjectName("menuForm")
-        self.menuPlots = QtWidgets.QMenu(self.menubar)
-        self.menuPlots.setObjectName("menuPlots")
-        MainWindow.setMenuBar(self.menubar)
-        self.statusbar = QtWidgets.QStatusBar(MainWindow)
-        self.statusbar.setObjectName("statusbar")
-        MainWindow.setStatusBar(self.statusbar)
-        self.quit = QtWidgets.QAction(MainWindow)
-        self.quit.setObjectName("quit")
-        self.deleteSetup = QtWidgets.QAction(MainWindow)
-        self.deleteSetup.setObjectName("deleteSetup")
-        self.openGPSPlots = QtWidgets.QAction(MainWindow)
-        self.openGPSPlots.setObjectName("openGPSPlots")
-        self.searchLog = QtWidgets.QAction(MainWindow)
-        self.searchLog.setObjectName("searchLog")
-        self.plotTimeRanges = QtWidgets.QAction(MainWindow)
-        self.plotTimeRanges.setObjectName("plotTimeRanges")
-        self.plotPositions = QtWidgets.QAction(MainWindow)
-        self.plotPositions.setObjectName("plotPositions")
-        self.exportDeploymentFile = QtWidgets.QAction(MainWindow)
-        self.exportDeploymentFile.setObjectName("exportDeploymentFile")
-        self.exportDeploymentBlock = QtWidgets.QAction(MainWindow)
-        self.exportDeploymentBlock.setObjectName("exportDeploymentBlock")
-        self.exportTSPGeometry = QtWidgets.QAction(MainWindow)
-        self.exportTSPGeometry.setObjectName("exportTSPGeometry")
-        self.exportShotInfo = QtWidgets.QAction(MainWindow)
-        self.exportShotInfo.setObjectName("exportShotInfo")
-        self.openCalendar = QtWidgets.QAction(MainWindow)
-        self.openCalendar.setObjectName("openCalendar")
-        self.plotDSPClkDifference = QtWidgets.QAction(MainWindow)
-        self.plotDSPClkDifference.setCheckable(True)
-        self.plotDSPClkDifference.setObjectName("plotDSPClkDifference")
-        self.plotPhaseError = QtWidgets.QAction(MainWindow)
-        self.plotPhaseError.setCheckable(True)
-        self.plotPhaseError.setObjectName("plotPhaseError")
-        self.plotJerk = QtWidgets.QAction(MainWindow)
-        self.plotJerk.setCheckable(True)
-        self.plotJerk.setObjectName("plotJerk")
-        self.plotFileErrors = QtWidgets.QAction(MainWindow)
-        self.plotFileErrors.setCheckable(True)
-        self.plotFileErrors.setObjectName("plotFileErrors")
-        self.plotGPSOnOffErr = QtWidgets.QAction(MainWindow)
-        self.plotGPSOnOffErr.setCheckable(True)
-        self.plotGPSOnOffErr.setObjectName("plotGPSOnOffErr")
-        self.plotGPSLkUnlk = QtWidgets.QAction(MainWindow)
-        self.plotGPSLkUnlk.setCheckable(True)
-        self.plotGPSLkUnlk.setObjectName("plotGPSLkUnlk")
-        self.plotTemperature = QtWidgets.QAction(MainWindow)
-        self.plotTemperature.setCheckable(True)
-        self.plotTemperature.setObjectName("plotTemperature")
-        self.plotVoltage = QtWidgets.QAction(MainWindow)
-        self.plotVoltage.setCheckable(True)
-        self.plotVoltage.setObjectName("plotVoltage")
-        self.plotBackupVoltage = QtWidgets.QAction(MainWindow)
-        self.plotBackupVoltage.setCheckable(True)
-        self.plotBackupVoltage.setObjectName("plotBackupVoltage")
-        self.plotDumpCall = QtWidgets.QAction(MainWindow)
-        self.plotDumpCall.setCheckable(True)
-        self.plotDumpCall.setObjectName("plotDumpCall")
-        self.plotAcquisitionOnOff = QtWidgets.QAction(MainWindow)
-        self.plotAcquisitionOnOff.setCheckable(True)
-        self.plotAcquisitionOnOff.setObjectName("plotAcquisitionOnOff")
-        self.plotResetPowerUp = QtWidgets.QAction(MainWindow)
-        self.plotResetPowerUp.setCheckable(True)
-        self.plotResetPowerUp.setObjectName("plotResetPowerUp")
-        self.plotErrorWarning = QtWidgets.QAction(MainWindow)
-        self.plotErrorWarning.setCheckable(True)
-        self.plotErrorWarning.setObjectName("plotErrorWarning")
-        self.plotDescrepancies = QtWidgets.QAction(MainWindow)
-        self.plotDescrepancies.setCheckable(True)
-        self.plotDescrepancies.setObjectName("plotDescrepancies")
-        self.plotSOHDataDefinitions = QtWidgets.QAction(MainWindow)
-        self.plotSOHDataDefinitions.setCheckable(True)
-        self.plotSOHDataDefinitions.setObjectName("plotSOHDataDefinitions")
-        self.plotNetworkUpDown = QtWidgets.QAction(MainWindow)
-        self.plotNetworkUpDown.setCheckable(True)
-        self.plotNetworkUpDown.setObjectName("plotNetworkUpDown")
-        self.plotEvents = QtWidgets.QAction(MainWindow)
-        self.plotEvents.setCheckable(True)
-        self.plotEvents.setObjectName("plotEvents")
-        self.plotDisk1Usage = QtWidgets.QAction(MainWindow)
-        self.plotDisk1Usage.setCheckable(True)
-        self.plotDisk1Usage.setObjectName("plotDisk1Usage")
-        self.plotDisk2Usage = QtWidgets.QAction(MainWindow)
-        self.plotDisk2Usage.setCheckable(True)
-        self.plotDisk2Usage.setObjectName("plotDisk2Usage")
-        self.plotMassPositions123 = QtWidgets.QAction(MainWindow)
-        self.plotMassPositions123.setCheckable(True)
-        self.plotMassPositions123.setObjectName("plotMassPositions123")
-        self.plotMassPositions456 = QtWidgets.QAction(MainWindow)
-        self.plotMassPositions456.setCheckable(True)
-        self.plotMassPositions456.setObjectName("plotMassPositions456")
-        self.plotAll = QtWidgets.QAction(MainWindow)
-        self.plotAll.setObjectName("plotAll")
-        self.plotTimingProblems = QtWidgets.QAction(MainWindow)
-        self.plotTimingProblems.setCheckable(True)
-        self.plotTimingProblems.setObjectName("plotTimingProblems")
-        self.filterNonSOHLines = QtWidgets.QAction(MainWindow)
-        self.filterNonSOHLines.setCheckable(True)
-        self.filterNonSOHLines.setObjectName("filterNonSOHLines")
-        self.sortFilesByType = QtWidgets.QAction(MainWindow)
-        self.sortFilesByType.setCheckable(True)
-        self.sortFilesByType.setObjectName("sortFilesByType")
-        self.sortFilesAlphabetically = QtWidgets.QAction(MainWindow)
-        self.sortFilesAlphabetically.setCheckable(True)
-        self.sortFilesAlphabetically.setObjectName("sortFilesAlphabetically")
-        self.calculateFileSizes = QtWidgets.QAction(MainWindow)
-        self.calculateFileSizes.setCheckable(True)
-        self.calculateFileSizes.setObjectName("calculateFileSizes")
-        self.warnIfBig = QtWidgets.QAction(MainWindow)
-        self.warnIfBig.setCheckable(True)
-        self.warnIfBig.setObjectName("warnIfBig")
-        self.addMassPositionToSOH = QtWidgets.QAction(MainWindow)
-        self.addMassPositionToSOH.setCheckable(True)
-        self.addMassPositionToSOH.setObjectName("addMassPositionToSOH")
-        self.colorMPRegular = QtWidgets.QAction(MainWindow)
-        self.colorMPRegular.setCheckable(True)
-        self.colorMPRegular.setObjectName("colorMPRegular")
-        self.colorMPTrillium = QtWidgets.QAction(MainWindow)
-        self.colorMPTrillium.setCheckable(True)
-        self.colorMPTrillium.setObjectName("colorMPTrillium")
-        self.addPositionsToET = QtWidgets.QAction(MainWindow)
-        self.addPositionsToET.setCheckable(True)
-        self.addPositionsToET.setObjectName("addPositionsToET")
-        self.readAntellopeLog = QtWidgets.QAction(MainWindow)
-        self.readAntellopeLog.setCheckable(True)
-        self.readAntellopeLog.setObjectName("readAntellopeLog")
-        self.showYYYYDOYDates = QtWidgets.QAction(MainWindow)
-        self.showYYYYDOYDates.setCheckable(True)
-        self.showYYYYDOYDates.setObjectName("showYYYYDOYDates")
-        self.showYYYY_MM_DDDates = QtWidgets.QAction(MainWindow)
-        self.showYYYY_MM_DDDates.setCheckable(True)
-        self.showYYYY_MM_DDDates.setObjectName("showYYYY_MM_DDDates")
-        self.showYYYYMMMDDDates = QtWidgets.QAction(MainWindow)
-        self.showYYYYMMMDDDates.setCheckable(True)
-        self.showYYYYMMMDDDates.setObjectName("showYYYYMMMDDDates")
-        self.setFontSizes = QtWidgets.QAction(MainWindow)
-        self.setFontSizes.setObjectName("setFontSizes")
-        self.openAbout = QtWidgets.QAction(MainWindow)
-        self.openAbout.setObjectName("openAbout")
-        self.menuFile.addAction(self.deleteSetup)
-        self.menuFile.addSeparator()
-        self.menuFile.addAction(self.quit)
-        self.menuExport_TT_Times_As.addAction(self.exportDeploymentFile)
-        self.menuExport_TT_Times_As.addAction(self.exportDeploymentBlock)
-        self.menuExport_TT_Times_As.addAction(self.exportTSPGeometry)
-        self.menuExport_TT_Times_As.addAction(self.exportShotInfo)
-        self.menuCommand.addAction(self.openGPSPlots)
-        self.menuCommand.addAction(self.searchLog)
-        self.menuCommand.addAction(self.plotTimeRanges)
-        self.menuCommand.addAction(self.plotPositions)
-        self.menuCommand.addSeparator()
-        self.menuCommand.addAction(self.menuExport_TT_Times_As.menuAction())
-        self.menuOptions.addAction(self.plotTimingProblems)
-        self.menuOptions.addAction(self.filterNonSOHLines)
-        self.menuOptions.addSeparator()
-        self.menuOptions.addAction(self.sortFilesByType)
-        self.menuOptions.addAction(self.sortFilesAlphabetically)
-        self.menuOptions.addAction(self.calculateFileSizes)
-        self.menuOptions.addAction(self.warnIfBig)
-        self.menuOptions.addSeparator()
-        self.menuOptions.addAction(self.addMassPositionToSOH)
-        self.menuOptions.addAction(self.colorMPRegular)
-        self.menuOptions.addAction(self.colorMPTrillium)
-        self.menuOptions.addSeparator()
-        self.menuOptions.addAction(self.addPositionsToET)
-        self.menuOptions.addSeparator()
-        self.menuOptions.addAction(self.readAntellopeLog)
-        self.menuOptions.addSeparator()
-        self.menuOptions.addAction(self.showYYYYDOYDates)
-        self.menuOptions.addAction(self.showYYYY_MM_DDDates)
-        self.menuOptions.addAction(self.showYYYYMMMDDDates)
-        self.menuOptions.addSeparator()
-        self.menuOptions.addAction(self.setFontSizes)
-        self.menuHelp.addAction(self.openCalendar)
-        self.menuHelp.addAction(self.openAbout)
-        self.menuPlots.addAction(self.plotDSPClkDifference)
-        self.menuPlots.addAction(self.plotPhaseError)
-        self.menuPlots.addAction(self.plotJerk)
-        self.menuPlots.addAction(self.plotFileErrors)
-        self.menuPlots.addAction(self.plotGPSOnOffErr)
-        self.menuPlots.addAction(self.plotGPSLkUnlk)
-        self.menuPlots.addAction(self.plotTemperature)
-        self.menuPlots.addAction(self.plotVoltage)
-        self.menuPlots.addAction(self.plotBackupVoltage)
-        self.menuPlots.addAction(self.plotDumpCall)
-        self.menuPlots.addAction(self.plotAcquisitionOnOff)
-        self.menuPlots.addAction(self.plotResetPowerUp)
-        self.menuPlots.addAction(self.plotErrorWarning)
-        self.menuPlots.addAction(self.plotDescrepancies)
-        self.menuPlots.addAction(self.plotSOHDataDefinitions)
-        self.menuPlots.addAction(self.plotNetworkUpDown)
-        self.menuPlots.addAction(self.plotEvents)
-        self.menuPlots.addAction(self.plotDisk1Usage)
-        self.menuPlots.addAction(self.plotDisk2Usage)
-        self.menuPlots.addAction(self.plotMassPositions123)
-        self.menuPlots.addAction(self.plotMassPositions456)
-        self.menuPlots.addSeparator()
-        self.menuPlots.addAction(self.plotAll)
-        self.menubar.addAction(self.menuFile.menuAction())
-        self.menubar.addAction(self.menuCommand.menuAction())
-        self.menubar.addAction(self.menuPlots.menuAction())
-        self.menubar.addAction(self.menuOptions.menuAction())
-        self.menubar.addAction(self.menuForm.menuAction())
-        self.menubar.addAction(self.menuHelp.menuAction())
-        self.timeFromLabel.setBuddy(self.timeFromDateEdit)
-        self.timeToLabel.setBuddy(self.timeToDateEdit)
-        self.dsLabel.setBuddy(self.ds1CheckBox)
-        self.massPosLabel.setBuddy(self.lowChanCheckBox)
-        self.label_4.setBuddy(self.fileListLogCheckBox)
-        self.backgroundLabel.setBuddy(self.backgroundWhiteRadioButton)
-
-        self.retranslateUi(MainWindow)
-        QtCore.QObject.connect(self.clearPushButton, QtCore.SIGNAL("clicked()"), self.searchLineEdit.clear)
-        QtCore.QObject.connect(self.quit, QtCore.SIGNAL("triggered(bool)"), MainWindow.close)
-        QtCore.QObject.connect(self.readPushButton, QtCore.SIGNAL("clicked()"), MainWindow.readSelectedFile)
-        QtCore.QObject.connect(self.stopPushButton, QtCore.SIGNAL("clicked()"), MainWindow.stopFileRead)
-        QtCore.QObject.connect(self.writePushButton, QtCore.SIGNAL("clicked()"), MainWindow.writePSFile)
-        # QtCore.QObject.connect(self.replotPushButton, QtCore.SIGNAL("clicked()"), self.plottingWidget.replotLoadedData)
-        QtCore.QObject.connect(self.reloadPushButton, QtCore.SIGNAL("clicked()"), MainWindow.reloadFile)
-        QtCore.QObject.connect(self.cwdPushButton, QtCore.SIGNAL("clicked()"), MainWindow.changeCurrentDirectory)
-        QtCore.QObject.connect(MainWindow, QtCore.SIGNAL("currentDirectoryChanged(QString)"), self.cwdLineEdit.setText)
-        QtCore.QMetaObject.connectSlotsByName(MainWindow)
-
-    def retranslateUi(self, MainWindow):
-        MainWindow.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "MainWindow", None, -1))
-        self.cwdPushButton.setText(QtWidgets.QApplication.translate("MainWindow", "Main Data Directory", None, -1))
-        self.timeFromLabel.setText(QtWidgets.QApplication.translate("MainWindow", "From", None, -1))
-        self.timeToLabel.setText(QtWidgets.QApplication.translate("MainWindow", "To", None, -1))
-        self.ds6CheckBox.setText(QtWidgets.QApplication.translate("MainWindow", "6", None, -1))
-        self.ds3CheckBox.setText(QtWidgets.QApplication.translate("MainWindow", "3", None, -1))
-        self.dsTPSCheckBox.setText(QtWidgets.QApplication.translate("MainWindow", "TPS", None, -1))
-        self.dsLabel.setText(QtWidgets.QApplication.translate("MainWindow", "DSs:", None, -1))
-        self.ds2CheckBox.setText(QtWidgets.QApplication.translate("MainWindow", "2", None, -1))
-        self.ds1CheckBox.setText(QtWidgets.QApplication.translate("MainWindow", "1", None, -1))
-        self.ds4CheckBox.setText(QtWidgets.QApplication.translate("MainWindow", "4", None, -1))
-        self.ds5checkBox.setText(QtWidgets.QApplication.translate("MainWindow", "5", None, -1))
-        self.massPosLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Mass Pos:", None, -1))
-        self.hiChanCheckbox.setText(QtWidgets.QApplication.translate("MainWindow", "456", None, -1))
-        self.lowChanCheckBox.setText(QtWidgets.QApplication.translate("MainWindow", "123", None, -1))
-        self.writePushButton.setText(QtWidgets.QApplication.translate("MainWindow", "Write .ps", None, -1))
-        self.stopPushButton.setText(QtWidgets.QApplication.translate("MainWindow", "Stop", None, -1))
-        self.readPushButton.setText(QtWidgets.QApplication.translate("MainWindow", "Read", None, -1))
-        self.sohCheckBox.setText(QtWidgets.QApplication.translate("MainWindow", "SOH Only", None, -1))
-        self.searchLineEdit.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search...", None, -1))
-        self.fileListLogCheckBox.setText(QtWidgets.QApplication.translate("MainWindow", ".log", None, -1))
-        self.fileListZipCheckBox.setText(QtWidgets.QApplication.translate("MainWindow", ".zip", None, -1))
-        self.fileListCfCheckBox.setText(QtWidgets.QApplication.translate("MainWindow", ".cf", None, -1))
-        self.fileListRefCheckBox.setText(QtWidgets.QApplication.translate("MainWindow", ".ref", None, -1))
-        self.label_4.setText(QtWidgets.QApplication.translate("MainWindow", "List:", None, -1))
-        self.backgroundWhiteRadioButton.setText(QtWidgets.QApplication.translate("MainWindow", "B", None, -1))
-        self.backgroundBlackRadioButton.setText(QtWidgets.QApplication.translate("MainWindow", "W", None, -1))
-        self.backgroundLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Background:", None, -1))
-        self.clearPushButton.setText(QtWidgets.QApplication.translate("MainWindow", "Clear", None, -1))
-        self.replotPushButton.setText(QtWidgets.QApplication.translate("MainWindow", "Replot", None, -1))
-        self.reloadPushButton.setText(QtWidgets.QApplication.translate("MainWindow", "Reload", None, -1))
-        self.menuFile.setTitle(QtWidgets.QApplication.translate("MainWindow", "&File", None, -1))
-        self.menuCommand.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Commands", None, -1))
-        self.menuExport_TT_Times_As.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Export TT Times As...", None, -1))
-        self.menuOptions.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Options", None, -1))
-        self.menuHelp.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Help", None, -1))
-        self.menuForm.setTitle(QtWidgets.QApplication.translate("MainWindow", "F&orms", None, -1))
-        self.menuPlots.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Plots", None, -1))
-        self.quit.setText(QtWidgets.QApplication.translate("MainWindow", "&Quit", None, -1))
-        self.deleteSetup.setText(QtWidgets.QApplication.translate("MainWindow", "&Delete Setup File", None, -1))
-        self.openGPSPlots.setText(QtWidgets.QApplication.translate("MainWindow", "&GPS Plotter", None, -1))
-        self.searchLog.setText(QtWidgets.QApplication.translate("MainWindow", "Log &Search", None, -1))
-        self.plotTimeRanges.setText(QtWidgets.QApplication.translate("MainWindow", "Plot &Time Ranges", None, -1))
-        self.plotPositions.setText(QtWidgets.QApplication.translate("MainWindow", "Plot &Positions", None, -1))
-        self.exportDeploymentFile.setText(QtWidgets.QApplication.translate("MainWindow", "Deployment File (&Line)", None, -1))
-        self.exportDeploymentBlock.setText(QtWidgets.QApplication.translate("MainWindow", "Deployment File (&Block)", None, -1))
-        self.exportTSPGeometry.setText(QtWidgets.QApplication.translate("MainWindow", "&TSP Shotfile / Geometry", None, -1))
-        self.exportShotInfo.setText(QtWidgets.QApplication.translate("MainWindow", "&Shot Info", None, -1))
-        self.openCalendar.setText(QtWidgets.QApplication.translate("MainWindow", "&Calendar", None, -1))
-        self.plotDSPClkDifference.setText(QtWidgets.QApplication.translate("MainWindow", "DSP-Clk Difference", None, -1))
-        self.plotPhaseError.setText(QtWidgets.QApplication.translate("MainWindow", "Phase Error", None, -1))
-        self.plotJerk.setText(QtWidgets.QApplication.translate("MainWindow", "Jerk/DSP Sets", None, -1))
-        self.plotFileErrors.setText(QtWidgets.QApplication.translate("MainWindow", ".err File Errors", None, -1))
-        self.plotGPSOnOffErr.setText(QtWidgets.QApplication.translate("MainWindow", "GPS On/Off/Err", None, -1))
-        self.plotGPSLkUnlk.setText(QtWidgets.QApplication.translate("MainWindow", "GPS Lk-Unlk", None, -1))
-        self.plotTemperature.setText(QtWidgets.QApplication.translate("MainWindow", "Temperature", None, -1))
-        self.plotVoltage.setText(QtWidgets.QApplication.translate("MainWindow", "Volts", None, -1))
-        self.plotBackupVoltage.setText(QtWidgets.QApplication.translate("MainWindow", "Backup Volts", None, -1))
-        self.plotDumpCall.setText(QtWidgets.QApplication.translate("MainWindow", "Dump Call", None, -1))
-        self.plotAcquisitionOnOff.setText(QtWidgets.QApplication.translate("MainWindow", "Acquisition On/Off", None, -1))
-        self.plotResetPowerUp.setText(QtWidgets.QApplication.translate("MainWindow", "Reset/Powerup", None, -1))
-        self.plotErrorWarning.setText(QtWidgets.QApplication.translate("MainWindow", "Error/Warning", None, -1))
-        self.plotDescrepancies.setText(QtWidgets.QApplication.translate("MainWindow", "Discrepancies", None, -1))
-        self.plotSOHDataDefinitions.setText(QtWidgets.QApplication.translate("MainWindow", "SOH/Data Definitions", None, -1))
-        self.plotNetworkUpDown.setText(QtWidgets.QApplication.translate("MainWindow", "Network Up/Down", None, -1))
-        self.plotEvents.setText(QtWidgets.QApplication.translate("MainWindow", "Events", None, -1))
-        self.plotDisk1Usage.setText(QtWidgets.QApplication.translate("MainWindow", "Disk 1 Usage", None, -1))
-        self.plotDisk2Usage.setText(QtWidgets.QApplication.translate("MainWindow", "Disk 2 Usage", None, -1))
-        self.plotMassPositions123.setText(QtWidgets.QApplication.translate("MainWindow", "Mass Positions 123", None, -1))
-        self.plotMassPositions456.setText(QtWidgets.QApplication.translate("MainWindow", "Mass Positions 456", None, -1))
-        self.plotAll.setText(QtWidgets.QApplication.translate("MainWindow", "All Plots", None, -1))
-        self.plotTimingProblems.setText(QtWidgets.QApplication.translate("MainWindow", "Plot Timing Problems", None, -1))
-        self.filterNonSOHLines.setText(QtWidgets.QApplication.translate("MainWindow", "Filter Non-SOH Lines", None, -1))
-        self.sortFilesByType.setText(QtWidgets.QApplication.translate("MainWindow", "Sort Files List By Type", None, -1))
-        self.sortFilesAlphabetically.setText(QtWidgets.QApplication.translate("MainWindow", "Sort Files List Alphabetically", None, -1))
-        self.calculateFileSizes.setText(QtWidgets.QApplication.translate("MainWindow", "Calculate File Sizes", None, -1))
-        self.warnIfBig.setText(QtWidgets.QApplication.translate("MainWindow", "Warn If Big", None, -1))
-        self.addMassPositionToSOH.setText(QtWidgets.QApplication.translate("MainWindow", "Add Mass Positions to SOH Messages", None, -1))
-        self.colorMPRegular.setText(QtWidgets.QApplication.translate("MainWindow", "MP Coloring (Regular)", None, -1))
-        self.colorMPTrillium.setText(QtWidgets.QApplication.translate("MainWindow", "MP Coloring (Trillium)", None, -1))
-        self.addPositionsToET.setText(QtWidgets.QApplication.translate("MainWindow", "Add Positions to ET Lines", None, -1))
-        self.readAntellopeLog.setText(QtWidgets.QApplication.translate("MainWindow", "Read Antelope-Produced Log File", None, -1))
-        self.showYYYYDOYDates.setText(QtWidgets.QApplication.translate("MainWindow", "Show YYYY:DOY Dates", None, -1))
-        self.showYYYY_MM_DDDates.setText(QtWidgets.QApplication.translate("MainWindow", "Show YYYY-MM-DD Dates", None, -1))
-        self.showYYYYMMMDDDates.setText(QtWidgets.QApplication.translate("MainWindow", "Show YYYYMMMDD Dates", None, -1))
-        self.setFontSizes.setText(QtWidgets.QApplication.translate("MainWindow", "Set Font Sizes", None, -1))
-        self.openAbout.setText(QtWidgets.QApplication.translate("MainWindow", "&About", None, -1))
-
-# from plottingwidget import PlottingWidget
+        hLayout.addWidget(self.timeToDateEdit)
+
+    def setSecondRow(self, mainLayout):
+        hLayout = QtWidgets.QHBoxLayout()
+        hLayout.setContentsMargins(0, 0, 0, 0)
+
+        mainLayout.addLayout(hLayout)
+
+        self.setControlColumn(hLayout)
+
+        self.plottingWidget = PlottingWidget(
+            self.MainWindow)
+        hLayout.addWidget(self.plottingWidget, 2)
+
+    def setControlColumn(self, parentLayout):
+        # TODO: search for sideScrollArea
+        leftWidget = QtWidgets.QWidget(self.centralWidget)
+        leftWidget.setFixedWidth(240)
+        leftWidget.setMinimumHeight(650)
+        parentLayout.addWidget(leftWidget)
+
+        leftLayout = QtWidgets.QVBoxLayout()
+        leftLayout.setContentsMargins(0, 0, 0, 0)
+        leftLayout.setSpacing(0)
+        leftWidget.setLayout(leftLayout)
+
+        self.openFilesList = QtWidgets.QListWidget(
+            self.centralWidget)
+        leftLayout.addWidget(self.openFilesList, 1)
+        pal = self.openFilesList.palette()
+        pal.setColor(QtGui.QPalette.Highlight, QtGui.QColor(128, 255, 128))
+        pal.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(0, 0, 0))
+        self.openFilesList.setPalette(pal)
+        # allow multiple-line selection
+        self.openFilesList.setSelectionMode(
+            QtWidgets.QAbstractItemView.ExtendedSelection)
+
+        self.insideDirCheckBox = QtWidgets.QCheckBox(
+            'Inside Directory', self.centralWidget)
+        leftLayout.addWidget(self.insideDirCheckBox)
+
+        searchGrid = QtWidgets.QGridLayout()
+        # searchGrid.setContentsMargins(0, 0, 0, 0)
+        leftLayout.addLayout(searchGrid)
+
+        self.searchLineEdit = QtWidgets.QLineEdit(self.centralWidget)
+        self.searchLineEdit.setPlaceholderText('Search...')
+        searchGrid.addWidget(self.searchLineEdit, 0, 0, 1, 3)
+
+        self.clearButton = QtWidgets.QPushButton('Clear', self.centralWidget)
+        self.clearButton.setFixedWidth(65)
+        searchGrid.addWidget(self.clearButton, 0, 3, 1, 1)
+
+        searchGrid.addWidget(QtWidgets.QLabel('List:'), 1, 0, 1, 1)
+        self.fileListLogCheckBox = QtWidgets.QCheckBox(
+            '.log', self.centralWidget)
+        searchGrid.addWidget(self.fileListLogCheckBox, 1, 1, 1, 1)
+        self.fileListZipCheckBox = QtWidgets.QCheckBox(
+            '.zip', self.centralWidget)
+        searchGrid.addWidget(self.fileListZipCheckBox, 1, 2, 1, 1)
+
+        self.replotButton = QtWidgets.QPushButton('RePlot', self.centralWidget)
+        self.replotButton.setFixedWidth(65)
+        searchGrid.addWidget(self.replotButton, 1, 3, 1, 1)
+
+        backgroundLayout = QtWidgets.QHBoxLayout()
+        # backgroundLayout.setContentsMargins(0, 0, 0, 0)
+        leftLayout.addLayout(backgroundLayout)
+        backgroundLayout.addWidget(QtWidgets.QLabel('Background:     '))
+        self.backgroundBlackRadioButton = QtWidgets.QRadioButton(
+            'B', self.centralWidget)
+        backgroundLayout.addWidget(self.backgroundBlackRadioButton)
+        self.backgroundWhiteRadioButton = QtWidgets.QRadioButton(
+            'W', self.centralWidget)
+        backgroundLayout.addWidget(self.backgroundWhiteRadioButton)
+
+        self.addSeperationLine(leftLayout)
+
+        TPS_gap_SOH_Layout = QtWidgets.QGridLayout()
+        TPS_gap_SOH_Layout.setContentsMargins(0, 0, 0, 10)
+        TPS_gap_SOH_Layout.setSpacing(5)
+        leftLayout.addLayout(TPS_gap_SOH_Layout)
+
+        self.showTPSCheckBox = QtWidgets.QCheckBox(
+            'TPS   Chans:', self.centralWidget)
+        TPS_gap_SOH_Layout.addWidget(self.showTPSCheckBox, 0, 0)
+        # TPS_gap_SOH_Layout.addWidget(
+        #     QtWidgets.QLabel('Chans:'), 0, 1, QtGui.Qt.AlignRight)
+        self.TPSChansLineEdit = QtWidgets.QLineEdit(self.centralWidget)
+        TPS_gap_SOH_Layout.addWidget(self.TPSChansLineEdit, 0, 1, 1, 3)
+
+        self.detectGapCheckBox = QtWidgets.QCheckBox(
+            'DetectGap   Len:', self.centralWidget)
+        TPS_gap_SOH_Layout.addWidget(self.detectGapCheckBox, 1, 0, 1, 2)
+
+        # TPS_gap_SOH_Layout.addWidget(
+        #     QtWidgets.QLabel('Len:'), 1, 2, QtGui.Qt.AlignRight)
+        self.gapLenLineEdit = QtWidgets.QLineEdit(self.centralWidget)
+        TPS_gap_SOH_Layout.addWidget(self.gapLenLineEdit, 1, 2, 1, 2)
+
+        self.sohCheckBox = QtWidgets.QCheckBox('SOH Only', self.centralWidget)
+        TPS_gap_SOH_Layout.addWidget(self.sohCheckBox, 2, 0)
+
+        massPosLayout = QtWidgets.QHBoxLayout()
+        # massPosLayout.setContentsMargins(0, 0, 0, 0)
+        leftLayout.addLayout(massPosLayout)
+        massPosLayout.addWidget(QtWidgets.QLabel('Mass Pos:'))
+        self.massPos123CheckBox = QtWidgets.QCheckBox(
+            '123', self.centralWidget)
+        massPosLayout.addWidget(self.massPos123CheckBox)
+        self.massPos456CheckBox = QtWidgets.QCheckBox(
+            '456', self.centralWidget)
+        massPosLayout.addWidget(self.massPos456CheckBox)
+
+        self.addSeperationLine(leftLayout)
+
+        dsGrid = QtWidgets.QGridLayout()
+        # dsGrid.setContentsMargins(0, 0, 0, 0)
+        leftLayout.addLayout(dsGrid)
+
+        dsGrid.addWidget(QtWidgets.QLabel('DSs:'), 0, 0, 2, 1,
+                         QtGui.Qt.AlignVCenter)
+        self.dsCheckBoxes = []
+        count = 0
+        for r in range(2):
+            for c in range(4):
+                count += 1
+                self.dsCheckBoxes.append(
+                    QtWidgets.QCheckBox('%s' % count, self.centralWidget))
+                dsGrid.addWidget(self.dsCheckBoxes[count - 1], r, c + 1)
+
+        self.addSeperationLine(leftLayout)
+
+        chanLayout = QtWidgets.QHBoxLayout()
+        chanLayout.setContentsMargins(0, 0, 0, 0)
+        chanLayout.setSpacing(0)
+        leftLayout.addLayout(chanLayout)
+
+        self.allChanCheckBox = QtWidgets.QCheckBox(
+            'AllChan  ', self.centralWidget)
+        self.allChanCheckBox.setChecked(True)
+        chanLayout.addWidget(self.allChanCheckBox)
+
+        self.preferChanButton = QtWidgets.QPushButton(
+            'Pref', self.centralWidget)
+        self.preferChanButton.setFixedWidth(47)
+        chanLayout.addWidget(self.preferChanButton)
+
+        chanLayout.addWidget(QtWidgets.QLabel('Cur'))
+        self.currIDsNameLineEdit = QtWidgets.QLineEdit(self.centralWidget)
+        chanLayout.addWidget(self.currIDsNameLineEdit)
+
+        submitLayout = QtWidgets.QHBoxLayout()
+        # submitLayout.setContentsMargins(5, 0, 0, 5)
+        submitLayout.setSpacing(5)
+        leftLayout.addLayout(submitLayout)
+        self.readButton = QtWidgets.QPushButton('Read', self.centralWidget)
+        submitLayout.addWidget(self.readButton)
+        self.stopButton = QtWidgets.QPushButton('Stop', self.centralWidget)
+        submitLayout.addWidget(self.stopButton)
+        self.writePSButton = QtWidgets.QPushButton(
+            'Write .ps', self.centralWidget)
+        submitLayout.addWidget(self.writePSButton)
+
+        self.infoListWidget = QtWidgets.QListWidget(self.centralWidget)
+        leftLayout.addWidget(self.infoListWidget, 1)
+
+    def createMenuBar(self, MainWindow):
+        mainMenu = MainWindow.menuBar()
+        fileMenu = mainMenu.addMenu("File")
+        commandMenu = mainMenu.addMenu("Commands")
+        optionMenu = mainMenu.addMenu("Options")
+        self.formMenu = mainMenu.addMenu("Forms")
+        databaseMenu = mainMenu.addMenu("Database")
+        helpMenu = mainMenu.addMenu("Help")
+
+        # exitAction = QtWidgets.QAction(QtGui.QIcon('exit.png'), "Exit", self)
+        # exitAction.setShortcut("Ctrl+X")
+        self.createFileMenu(MainWindow, fileMenu)
+        self.createCommandMenu(MainWindow, commandMenu)
+        self.createOptionMenu(MainWindow, optionMenu)
+        self.createDatabaseMenu(MainWindow, databaseMenu)
+        self.createHelpMenu(MainWindow, helpMenu)
+
+    def createFileMenu(self, MainWindow, menu):
+        self.exitAction = QtWidgets.QAction('Close', MainWindow)
+        menu.addAction(self.exitAction)
+
+    def createCommandMenu(self, MainWindow, menu):
+        self.GPSPlotterAction = QtWidgets.QAction(
+            'GPS Plotter', MainWindow)
+        menu.addAction(self.GPSPlotterAction)
+
+        self.logSearchAction = QtWidgets.QAction(
+            'Log Search', MainWindow)
+        menu.addAction(self.logSearchAction)
+
+        self.plotTimeRangesAction = QtWidgets.QAction(
+            'Plot Time Ranges', MainWindow)
+        menu.addAction(self.plotTimeRangesAction)
+
+        self.plotPositionAction = QtWidgets.QAction(
+            'Log Search', MainWindow)
+        menu.addAction(self.plotPositionAction)
+
+        exportTTTimesAsMenu = QtWidgets.QMenu(
+            'Export TT Times As:', MainWindow)
+        menu.addMenu(exportTTTimesAsMenu)
+
+        self.exportDeployFileLineAction = QtWidgets.QAction(
+            'Deployment File (Line)', MainWindow)
+        exportTTTimesAsMenu.addAction(self.exportDeployFileLineAction)
+
+        self.exportDeployFileBlockAction = QtWidgets.QAction(
+            'Deployment File (Block)', MainWindow)
+        exportTTTimesAsMenu.addAction(self.exportDeployFileBlockAction)
+
+        self.exportTPSShotFileAction = QtWidgets.QAction(
+            'TPS Shotfile/Geometry', MainWindow)
+        exportTTTimesAsMenu.addAction(self.exportTPSShotFileAction)
+
+        self.exportShotInfoAction = QtWidgets.QAction(
+            'Shot Info', MainWindow)
+        exportTTTimesAsMenu.addAction(self.exportShotInfoAction)
+
+    def createOptionMenu(self, MainWindow, menu):
+        self.plotTimingProblemsAction = QtWidgets.QAction(
+            'Plot Timing Problems', MainWindow)
+        self.plotTimingProblemsAction.setCheckable(True)
+        menu.addAction(self.plotTimingProblemsAction)
+
+        self.filterNonSOHAction = QtWidgets.QAction(
+            'Filter Out Non-SOH Lines', MainWindow)
+        self.filterNonSOHAction.setCheckable(True)
+        menu.addAction(self.filterNonSOHAction)
+
+        menu.addSeparator()
+
+        Q330GainMenu = QtWidgets.QMenu('Q330 Gain:', MainWindow)
+        menu.addMenu(Q330GainMenu)
+        self.Q330LowGainAction = QtWidgets.QAction('Low', MainWindow)
+        self.Q330LowGainAction.setCheckable(True)
+        Q330GainMenu.addAction(self.Q330LowGainAction)
+        self.Q330HighGainAction = QtWidgets.QAction('High', MainWindow)
+        self.Q330HighGainAction.setCheckable(True)
+        Q330GainMenu.addAction(self.Q330HighGainAction)
+
+        self.addMPToSOHAction = QtWidgets.QAction(
+            'Add Mass Positions to SOH Messages', MainWindow)
+        self.addMPToSOHAction.setCheckable(True)
+        menu.addAction(self.addMPToSOHAction)
+
+        MPColoringMenu = QtWidgets.QMenu('MP Coloring:', MainWindow)
+        menu.addMenu(MPColoringMenu)
+        self.MPRegularColorAction = QtWidgets.QAction(
+            '0.5, 2.0, 4.0, 7.0 (Regular)', MainWindow)
+        self.MPRegularColorAction.setCheckable(True)
+        MPColoringMenu.addAction(self.MPRegularColorAction)
+        self.MPTrilliumColorAction = QtWidgets.QAction(
+            '0.5, 1.8, 2.4, 3.5 (Trillium)', MainWindow)
+        self.MPTrilliumColorAction.setCheckable(True)
+        MPColoringMenu.addAction(self.MPTrilliumColorAction)
+
+        menu.addSeparator()
+
+        self.addPositionsToETAction = QtWidgets.QAction(
+            'Add Positions to ET Lines', MainWindow)
+        self.addPositionsToETAction.setCheckable(True)
+        menu.addAction(self.addPositionsToETAction)
+
+        self.readAntLogAction = QtWidgets.QAction(
+            'Read Antelope-Produced Log Files', MainWindow)
+        self.readAntLogAction.setCheckable(True)
+        menu.addAction(self.readAntLogAction)
+
+        menu.addSeparator()
+
+        self.calcFileSizesAction = QtWidgets.QAction(
+            'Calculate File Sizes', MainWindow)
+        menu.addAction(self.calcFileSizesAction)
+        self.warnIfBigAction = QtWidgets.QAction(
+            'Warn If Big', MainWindow)
+        menu.addAction(self.warnIfBigAction)
+
+        menu.addSeparator()
+
+        dateFormatMenu = QtWidgets.QMenu('Date Format:', MainWindow)
+        menu.addMenu(dateFormatMenu)
+        self.YYYYDOYAction = QtWidgets.QAction(
+            'YYYY:DOY', MainWindow)
+        self.YYYYDOYAction.setCheckable(True)
+        dateFormatMenu.addAction(self.YYYYDOYAction)
+        self.YYYY_MM_DDAction = QtWidgets.QAction(
+            'YYYY-MM-DD', MainWindow)
+        self.YYYY_MM_DDAction.setCheckable(True)
+        dateFormatMenu.addAction(self.YYYY_MM_DDAction)
+        self.YYYYMMMDDAction = QtWidgets.QAction(
+            'YYYYMMMDD', MainWindow)
+        self.YYYYMMMDDAction.setCheckable(True)
+        dateFormatMenu.addAction(self.YYYYMMMDDAction)
+
+        menu.addSeparator()
+
+        self.setFontSizeAction = QtWidgets.QAction(
+            'Set Font Sizes', MainWindow)
+        menu.addAction(self.setFontSizeAction)
+
+    def createDatabaseMenu(self, MainWindow, menu):
+        self.addEditDataTypeAction = QtWidgets.QAction(
+            'Add/Edit Data Types', MainWindow)
+        menu.addAction(self.addEditDataTypeAction)
+
+        self.addEditParamAction = QtWidgets.QAction(
+            'Add/Edit Parameters', MainWindow)
+        menu.addAction(self.addEditParamAction)
+
+        self.addEditChannelAction = QtWidgets.QAction(
+            'Add/Edit Channels', MainWindow)
+        menu.addAction(self.addEditChannelAction)
+
+        self.viewPlotTypeAction = QtWidgets.QAction(
+            'View Plot Types', MainWindow)
+        menu.addAction(self.viewPlotTypeAction)
+
+    def createHelpMenu(self, MainWindow, menu):
+        self.calendarAction = QtWidgets.QAction(
+            'Calendar', MainWindow)
+        menu.addAction(self.calendarAction)
+
+        self.aboutAction = QtWidgets.QAction(
+            'About', MainWindow)
+        menu.addAction(self.aboutAction)
+
+    def addSeperationLine(self, layout):
+        label = QtWidgets.QLabel()
+        label.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
+        label.setLineWidth(1)
+        layout.addWidget(label)
+
+    def resizeEvent(self, event):
+        self.plottingWidget.init_size()
+
+    def connectSignals(self, MainWindow):
+        self.connectMenuSignals(MainWindow)
+        self.connectWidgetSignals(MainWindow)
+
+    def connectMenuSignals(self, MainWindow):
+        # File
+        self.exitAction.triggered.connect(MainWindow.close)
+
+        # Commands
+
+        # Options
+        self.MPRegularColorAction.triggered.connect(
+            lambda: setattr(MainWindow, 'massPosVoltRangeOpt', 'regular'))
+        self.MPTrilliumColorAction.triggered.connect(
+            lambda: setattr(MainWindow, 'massPosVoltRangeOpt', 'trillium'))
+
+        self.YYYY_MM_DDAction.triggered.connect(
+            lambda: self.setDateFormat('yyyy-MM-dd'))
+        self.YYYYMMMDDAction.triggered.connect(
+            lambda: self.setDateFormat('yyyyMMMdd'))
+        self.YYYYDOYAction.triggered.connect(
+            lambda: self.setDateFormat('yyyyDOY'))
+
+        # Database
+        self.addEditDataTypeAction.triggered.connect(
+            MainWindow.openDataType)
+        self.addEditParamAction.triggered.connect(
+            MainWindow.openParam)
+        self.addEditChannelAction.triggered.connect(
+            MainWindow.openChannel)
+        self.viewPlotTypeAction.triggered.connect(
+            MainWindow.openPlotType)
+
+        # Form
+
+        # Help
+        self.calendarAction.triggered.connect(MainWindow.openCalendarWidget)
+
+    def connectWidgetSignals(self, MainWindow):
+        MainWindow.currentDirectoryChanged.connect(self.cwdLineEdit.setText)
+        # first Row
+        self.cwdButton.clicked.connect(MainWindow.changeCurrentDirectory)
+        self.timeToDateEdit.setCalendarWidget(CalendarWidget(MainWindow))
+        self.timeToDateEdit.setDate(QtCore.QDate.currentDate())
+
+        self.timeFromDateEdit.setCalendarWidget(CalendarWidget(MainWindow))
+        self.timeFromDateEdit.setDate(QtCore.QDate.currentDate())
+
+        # second Row
+        self.openFilesList.itemDoubleClicked.connect(
+            MainWindow.openFilesListItemDoubleClicked)
+
+        self.replotButton.clicked.connect(MainWindow.replotLoadedData)
+
+        self.allChanCheckBox.clicked.connect(
+            MainWindow.allChanClicked)
+        self.preferChanButton.clicked.connect(
+            MainWindow.openChannelPreferences)
+        self.readButton.clicked.connect(MainWindow.readSelectedFiles)