From f21c6054c138332ae5b81f3d29cd88b3cf691bf0 Mon Sep 17 00:00:00 2001
From: destinyk <destiny.kuehn@student.nmt.edu>
Date: Fri, 1 Dec 2023 16:53:19 -0700
Subject: [PATCH] add scan traces to thread with prog window

---
 ckMseed/ckMseed.py | 347 +++++++++++++++++++++++++++++++--------------
 1 file changed, 240 insertions(+), 107 deletions(-)

diff --git a/ckMseed/ckMseed.py b/ckMseed/ckMseed.py
index c95fbae..dc4090d 100644
--- a/ckMseed/ckMseed.py
+++ b/ckMseed/ckMseed.py
@@ -40,7 +40,17 @@ import time
 from getopt import getopt
 from glob import glob
 from operator import mod
-from PySide2 import QtCore, QtGui, QtWidgets
+from PySide2.QtWidgets import (QApplication, QWidget, QTabWidget,
+                               QGroupBox, QVBoxLayout, QHBoxLayout,
+                               QCheckBox, QPushButton, QLineEdit,
+                               QLabel, QRadioButton, QTextEdit,
+                               QComboBox, QFileDialog, QMessageBox,
+                               QProgressDialog, QTreeWidget,
+                               QTreeWidgetItem
+                               )
+from PySide2.QtCore import (Qt, QObject, QThread, Signal,
+                            QEventLoop, QTimer)
+#from PySide2 import QtCore, QtGui, QtWidgets
 
 from ckMseed.LibTrace import *
 
@@ -103,13 +113,13 @@ def main():
                 print(VERSION)
                 sys.exit(0)
     print("\n", os.path.basename(sys.argv[0]), VERSION)
-    app = QtWidgets.QApplication(sys.argv)
+    app = QApplication(sys.argv)
     window = MainWindow(batchfile=batchfile, dataloc=dataloc, runflag=runflag)
     window.show()
     sys.exit(app.exec_())
 
 
-class MainWindow(QtWidgets.QWidget):
+class MainWindow(QWidget):
 
     def __init__(self, batchfile, dataloc, runflag):
         super().__init__()
@@ -128,26 +138,26 @@ class MainWindow(QtWidgets.QWidget):
         self.setWindowTitle("ckMseed " + VERSION)
 
         # window layout
-        self.window_layout = QtWidgets.QVBoxLayout()
+        self.window_layout = QVBoxLayout()
         self.setLayout(self.window_layout)
 
         # tab init
-        self.tabwidget = QtWidgets.QTabWidget()
-        self.scan = QtWidgets.QWidget()
+        self.tabwidget = QTabWidget()
+        self.scan = QWidget()
         self.tabwidget.addTab(self.scan, "Scan")
 
         # groupbox for popup help and save/exit buttons
-        self.mainbox = QtWidgets.QGroupBox()
-        self.mainbox_layout = QtWidgets.QHBoxLayout(self.mainbox)
+        self.mainbox = QGroupBox()
+        self.mainbox_layout = QHBoxLayout(self.mainbox)
 
         # PopUp Help Check box
-        self.pop_up_help = QtWidgets.QCheckBox("PopUp Help")
+        self.pop_up_help = QCheckBox("PopUp Help")
         self.pop_up_help.setChecked(False)
         self.pop_up_help.setToolTip("Toggles 'PopUp Help' on and off")
         self.pop_up_help.toggled.connect(self.click_pop_up_help)
 
         # Save Scan button
-        self.save_scan_btn = QtWidgets.QPushButton("Save Scan")
+        self.save_scan_btn = QPushButton("Save Scan")
         self.save_scan_btn.setStyleSheet("QPushButton::hover"
                                          "{"
                                          "background-color:green;"
@@ -155,7 +165,7 @@ class MainWindow(QtWidgets.QWidget):
         self.save_scan_btn.clicked.connect(self.clicked_save_scan)
 
         # Exit button
-        self.exit_btn = QtWidgets.QPushButton("Exit")
+        self.exit_btn = QPushButton("Exit")
         self.exit_btn.setStyleSheet("QPushButton::hover"
                                     "{"
                                     "background-color:rgb(165, 29, 45);"
@@ -169,7 +179,7 @@ class MainWindow(QtWidgets.QWidget):
         self.mainbox_layout.addWidget(self.exit_btn)
 
         # info bar init
-        self.infobar = QtWidgets.QLineEdit()
+        self.infobar = QLineEdit()
         self.infobar.setStyleSheet("background-color:yellow")
         self.infobar.setReadOnly(True)
 
@@ -190,31 +200,31 @@ class MainWindow(QtWidgets.QWidget):
         """
 
         # init tab layout
-        self.scan_layout = QtWidgets.QVBoxLayout(self.scan)
+        self.scan_layout = QVBoxLayout(self.scan)
 
         # init groupboxes for data fields
-        self.datadir_box = QtWidgets.QGroupBox()
+        self.datadir_box = QGroupBox()
         self.datadir_box.setCheckable(False)
 
-        self.stations_box = QtWidgets.QGroupBox()
+        self.stations_box = QGroupBox()
         self.stations_box.setCheckable(False)
 
-        self.radio_box = QtWidgets.QGroupBox()
+        self.radio_box = QGroupBox()
         self.radio_box.setCheckable(False)
 
-        self.scantext_box = QtWidgets.QGroupBox()
+        self.scantext_box = QGroupBox()
         self.scantext_box.setCheckable(False)
 
         # layouts for groupboxes
-        hbox_datadir = QtWidgets.QHBoxLayout(self.datadir_box)
-        hbox_stations = QtWidgets.QHBoxLayout(self.stations_box)
-        hbox_rbuttons = QtWidgets.QHBoxLayout(self.radio_box)
-        vbox_scantext = QtWidgets.QVBoxLayout(self.scantext_box)
+        hbox_datadir = QHBoxLayout(self.datadir_box)
+        hbox_stations = QHBoxLayout(self.stations_box)
+        hbox_rbuttons = QHBoxLayout(self.radio_box)
+        vbox_scantext = QVBoxLayout(self.scantext_box)
 
         # data dir widgets
-        self.dd_label = QtWidgets.QLabel("Data Directories:")
-        self.dd_text = QtWidgets.QLineEdit()
-        self.scan_traces_btn = QtWidgets.QPushButton("Scan Traces")
+        self.dd_label = QLabel("Data Directories:")
+        self.dd_text = QLineEdit()
+        self.scan_traces_btn = QPushButton("Scan Traces")
         self.scan_traces_btn.setStyleSheet("""
                                            QPushButton{
                                            background-color:lightblue;
@@ -225,13 +235,13 @@ class MainWindow(QtWidgets.QWidget):
                                            }
                                            """)
         self.scan_traces_btn.clicked.connect(self.click_scan_traces)
-        self.find_btn = QtWidgets.QPushButton("Find")
+        self.find_btn = QPushButton("Find")
         self.find_btn.setStyleSheet("QPushButton::hover"
                                     "{"
                                     "background-color:green;"
                                     "}")
         self.find_btn.clicked.connect(self.file_dialogue)
-        self.clear_btn = QtWidgets.QPushButton("Clear")
+        self.clear_btn = QPushButton("Clear")
         self.clear_btn.setStyleSheet("QPushButton::hover"
                                      "{"
                                      "background-color:yellow;"
@@ -246,9 +256,9 @@ class MainWindow(QtWidgets.QWidget):
         hbox_datadir.addWidget(self.clear_btn)
 
         # stations widgets
-        self.stations_label = QtWidgets.QLabel("Scan only stations (colon separated list):")
-        self.stations_text = QtWidgets.QLineEdit()
-        self.stations_clear_btn = QtWidgets.QPushButton("Clear")
+        self.stations_label = QLabel("Scan only stations (colon separated list):")
+        self.stations_text = QLineEdit()
+        self.stations_clear_btn = QPushButton("Clear")
         self.stations_clear_btn.setStyleSheet("QPushButton::hover"
                                               "{"
                                               "background-color:yellow;"
@@ -259,14 +269,14 @@ class MainWindow(QtWidgets.QWidget):
         hbox_stations.addWidget(self.stations_clear_btn)
 
         # rbuttons widgets
-        self.scan_type_label = QtWidgets.QLabel("Scan Type:")
-        self.simple_rbtn = QtWidgets.QRadioButton("Simple")
+        self.scan_type_label = QLabel("Scan Type:")
+        self.simple_rbtn = QRadioButton("Simple")
         self.simple_rbtn.setChecked(True)
         self.simple_rbtn.clicked.connect(self.click_scan_type)
-        self.verbose_rbtn = QtWidgets.QRadioButton("Verbose")
+        self.verbose_rbtn = QRadioButton("Verbose")
         self.verbose_rbtn.setChecked(False)
         self.verbose_rbtn.clicked.connect(self.click_scan_type)
-        self.vv_rbtn = QtWidgets.QRadioButton("Very Verbose")
+        self.vv_rbtn = QRadioButton("Very Verbose")
         self.vv_rbtn.setChecked(False)
         self.vv_rbtn.clicked.connect(self.click_scan_type)
 
@@ -280,11 +290,11 @@ class MainWindow(QtWidgets.QWidget):
         # scan text widgets
 
         # Stat:Chan:Loc:Net:Sps Found label
-        self.scantext_label = QtWidgets.QLabel("Stat:Chan:Loc:Net:Sps Found")
-        self.scantext_label.setAlignment(QtGui.Qt.AlignCenter)
+        self.scantext_label = QLabel("Stat:Chan:Loc:Net:Sps Found")
+        self.scantext_label.setAlignment(Qt.AlignCenter)
 
         # Stat:Chan:Loc:Net:Sps Found text box
-        self.scantext = QtWidgets.QTextEdit()
+        self.scantext = QTextEdit()
         self.scantext.setAcceptRichText(False)
 
         # add widgets to layout
@@ -311,29 +321,29 @@ class MainWindow(QtWidgets.QWidget):
         Build widgets in Big Endian tab
         """
 
-        self.big_endian = QtWidgets.QWidget()
+        self.big_endian = QWidget()
         self.tabwidget.addTab(self.big_endian, "Big Endian")
-        self.little_endian = QtWidgets.QWidget()
+        self.little_endian = QWidget()
         self.tabwidget.addTab(self.little_endian, "Little Endian")
-        self.errors = QtWidgets.QWidget()
+        self.errors = QWidget()
         self.tabwidget.addTab(self.errors, "Errors")
 
         # layouts
-        self.big_endian_layout = QtWidgets.QVBoxLayout(self.big_endian)
-        self.big_traces_layout = QtWidgets.QHBoxLayout()
+        self.big_endian_layout = QVBoxLayout(self.big_endian)
+        self.big_traces_layout = QHBoxLayout()
 
         # widgets
-        self.display_traces_matching = QtWidgets.QLabel("Display Traces Matching:")
+        self.display_traces_matching = QLabel("Display Traces Matching:")
 
-        self.big_e_dropmenu = QtWidgets.QComboBox()
+        self.big_e_dropmenu = QComboBox()
         self.big_e_dropmenu.activated.connect(self.big_e_dropmenu_selections)
 
-        self.big_e_text = QtWidgets.QTextEdit()
+        self.big_e_text = QTextEdit()
         self.big_e_text.setAcceptRichText(False)
 
         self.big_traces_layout.addWidget(self.display_traces_matching)
         self.big_traces_layout.addWidget(self.big_e_dropmenu)
-        self.big_traces_layout.setAlignment(QtGui.Qt.AlignCenter)
+        self.big_traces_layout.setAlignment(Qt.AlignCenter)
 
         self.big_endian_layout.addLayout(self.big_traces_layout)
         self.big_endian_layout.addWidget(self.big_e_text)
@@ -344,23 +354,23 @@ class MainWindow(QtWidgets.QWidget):
         """
 
         # layouts
-        self.little_endian_layout = QtWidgets.QVBoxLayout(self.little_endian)
-        self.little_traces_layout = QtWidgets.QHBoxLayout()
+        self.little_endian_layout = QVBoxLayout(self.little_endian)
+        self.little_traces_layout = QHBoxLayout()
 
         # 'Display Traces Matching' label
-        self.display_traces_matching2 = QtWidgets.QLabel("Display Traces Matching:")
+        self.display_traces_matching2 = QLabel("Display Traces Matching:")
 
         # 'Display Traces Matching' drop menu
-        self.little_e_dropmenu = QtWidgets.QComboBox()
+        self.little_e_dropmenu = QComboBox()
         self.little_e_dropmenu.activated.connect(self.little_e_dropmenu_selections)
 
         # Little Endian Textbox
-        self.little_e_text = QtWidgets.QTextEdit()
+        self.little_e_text = QTextEdit()
         self.little_e_text.setAcceptRichText(False)
 
         self.little_traces_layout.addWidget(self.display_traces_matching2)
         self.little_traces_layout.addWidget(self.little_e_dropmenu)
-        self.little_traces_layout.setAlignment(QtGui.Qt.AlignCenter)
+        self.little_traces_layout.setAlignment(Qt.AlignCenter)
 
         self.little_endian_layout.addLayout(self.little_traces_layout)
         self.little_endian_layout.addWidget(self.little_e_text)
@@ -370,41 +380,41 @@ class MainWindow(QtWidgets.QWidget):
         Build widgets in Errors tab
         """
 
-        self.errors_layout = QtWidgets.QVBoxLayout(self.errors)
+        self.errors_layout = QVBoxLayout(self.errors)
 
-        self.error_rbtns_box = QtWidgets.QGroupBox()
-        self.error_rbtns_layout = QtWidgets.QHBoxLayout(self.error_rbtns_box)
+        self.error_rbtns_box = QGroupBox()
+        self.error_rbtns_layout = QHBoxLayout(self.error_rbtns_box)
 
         # Display label
-        self.display_label = QtWidgets.QLabel("Display:")
+        self.display_label = QLabel("Display:")
         self.display_label.setToolTip("Filter Error Messages")
 
         # All button
-        self.all_rbtn = QtWidgets.QRadioButton("All")
+        self.all_rbtn = QRadioButton("All")
         self.all_rbtn.setChecked(True)
         self.all_rbtn.clicked.connect(self.click_errors_display)
         self.all_rbtn.clicked.connect(self.display_error)
 
         # Read/Write button
-        self.rw_rbtn = QtWidgets.QRadioButton("Read/Write")
+        self.rw_rbtn = QRadioButton("Read/Write")
         self.rw_rbtn.setChecked(False)
         self.rw_rbtn.clicked.connect(self.click_errors_display)
         self.rw_rbtn.clicked.connect(self.display_error)
 
         # Size button
-        self.size_rbtn = QtWidgets.QRadioButton("Size")
+        self.size_rbtn = QRadioButton("Size")
         self.size_rbtn.setChecked(False)
         self.size_rbtn.clicked.connect(self.click_errors_display)
         self.size_rbtn.clicked.connect(self.display_error)
 
         # Endian button
-        self.endian_rbtn = QtWidgets.QRadioButton("Endian")
+        self.endian_rbtn = QRadioButton("Endian")
         self.endian_rbtn.setChecked(False)
         self.endian_rbtn.clicked.connect(self.click_errors_display)
         self.endian_rbtn.clicked.connect(self.display_error)
 
         # Non-Unique button
-        self.non_u_rbtn = QtWidgets.QRadioButton("Non-Unique")
+        self.non_u_rbtn = QRadioButton("Non-Unique")
         self.non_u_rbtn.setChecked(False)
         self.non_u_rbtn.clicked.connect(self.click_errors_display)
         self.non_u_rbtn.clicked.connect(self.display_error)
@@ -419,7 +429,7 @@ class MainWindow(QtWidgets.QWidget):
         self.error_rbtns_layout.addStretch(1)
 
         # Errors Textbox
-        self.error_text = QtWidgets.QTextEdit()
+        self.error_text = QTextEdit()
         self.error_text.setAcceptRichText(False)
 
         self.errors_layout.addWidget(self.error_rbtns_box)
@@ -573,7 +583,7 @@ class MainWindow(QtWidgets.QWidget):
         """
         File Dialogue for choosing directories
         """
-        self.search = QtWidgets.QFileDialog.getExistingDirectory()
+        self.search = QFileDialog.getExistingDirectory()
 
         directories = self.dd_text.text()
 
@@ -595,8 +605,8 @@ class MainWindow(QtWidgets.QWidget):
 
         # if data directory is empty, an error message will pop up
         if directory == "":
-            error_msg = QtWidgets.QMessageBox()
-            error_msg.setIcon(QtWidgets.QMessageBox.Critical)
+            error_msg = QMessageBox()
+            error_msg.setIcon(QMessageBox.Critical)
             error_msg.setText("Error")
             error_msg.setInformativeText("No directories listed.")
             error_msg.setWindowTitle("Error")
@@ -638,6 +648,12 @@ class MainWindow(QtWidgets.QWidget):
                 self.stat_sel_list.append(statsel)
 
         self.launch_find_trace()
+    
+    def after_find_trace(self):
+        """
+        Update widgets after find trace
+        thread finishes
+        """
 
         numfiles = self.num_big_files + self.num_little_files
         numerrors = self.num_errors
@@ -648,7 +664,7 @@ class MainWindow(QtWidgets.QWidget):
         self.write_little_endian("All")
         self.display_error()
 
-        QtWidgets.QApplication.beep()
+        QApplication.beep()
 
     def reset_data(self):
         """
@@ -806,8 +822,12 @@ class MainWindow(QtWidgets.QWidget):
 
         # if not BATCHMODE:
         #    self.CancelTL(self.run_scan, "Scan Traces")
-
-        self.find_trace(self.data_dir_list)
+        # if not BATCHMODE:
+        self.begin_thread("Scan Traces",
+                        lambda: self.find_trace(self.data_dir_list),
+                        self.after_find_trace)
+        # else:
+        #     self.launchfindTrace()
 
         # if self.run_scan and not BATCHMODE:
         #    self.KillCancelTL(self.run_scan)
@@ -850,12 +870,17 @@ class MainWindow(QtWidgets.QWidget):
                     _file = next(listfiles)
                 except StopIteration:
                     break
-
+                # handle cancel request
+                if not self.run_scan:
+                    break
                 # keep user posted of progress
                 if mod(cnt, 10):
                     pass
                 else:
-                    self.wait("Examining File: ", cnt)
+                    if not BATCHMODE:
+                        self.func_worker.update.emit(cnt)
+                    else: 
+                        self.wait("Examining File: ", cnt)
                 # build up list of mseed files w/ full pathnames
                 fullname = os.path.join(directory, _file)
                 if os.path.isfile(fullname):
@@ -973,9 +998,9 @@ class MainWindow(QtWidgets.QWidget):
         Add data found in file scan to Scan tab
         """
 
-        yellow = QtGui.QColor(255, 255, 0)
-        white = QtGui.QColor(255, 255, 255)
-        light_blue = QtGui.QColor(153, 204, 255)
+        yellow = Qt.yellow
+        white = Qt.white
+        light_blue = Qt.cyan
 
         scan_header = ("Stat:Chan:Loc:Net:Sps \t Big \t Little \t Start Time \t\t End Time")
         self.scantext.setTextBackgroundColor(yellow)
@@ -1031,12 +1056,13 @@ class MainWindow(QtWidgets.QWidget):
         Add data found in file scan to Big Endian tab
         """
 
-        yellow = QtGui.QColor(255, 255, 0)
-        white = QtGui.QColor(255, 255, 255)
-        Green = QtGui.QColor(0, 100, 0)
-        Blue = QtGui.QColor(0, 0, 153)
-        light_blue = QtGui.QColor(153, 204, 255)
-        Black = QtGui.QColor(0, 0, 0)
+        yellow = Qt.yellow
+        white = Qt.white
+        light_blue = Qt.cyan
+        Green = Qt.darkGreen
+        Blue = Qt.blue
+        light_blue = Qt.cyan
+        Black = Qt.black
 
         tab_header = ("Trace Name \t\t\t          \t\t\t            \t\t\t\n"
                       "Start Time \t\t\t End Time \t\t\t Blockettes \t\t\t")
@@ -1119,7 +1145,9 @@ class MainWindow(QtWidgets.QWidget):
             else:
                 text = "Displaying " + str(numtrace) + " trace."
             self.infobar.setText(text)
-            self.infobar.setAlignment(QtGui.Qt.AlignCenter)
+            self.infobar.setStyleSheet(
+                "background-color:green;")
+            self.infobar.setAlignment(Qt.AlignCenter)
         return
 
     def write_little_endian(self, key):
@@ -1127,12 +1155,13 @@ class MainWindow(QtWidgets.QWidget):
         Add data found in file scan to Little Endian tab
         """
 
-        yellow = QtGui.QColor(255, 255, 0)
-        white = QtGui.QColor(255, 255, 255)
-        Green = QtGui.QColor(0, 100, 0)
-        Blue = QtGui.QColor(0, 0, 153)
-        light_blue = QtGui.QColor(153, 204, 255)
-        Black = QtGui.QColor(0, 0, 0)
+        yellow = Qt.yellow
+        white = Qt.white
+        light_blue = Qt.cyan
+        Green = Qt.darkGreen
+        Blue = Qt.blue
+        light_blue = Qt.cyan
+        Black = Qt.black
 
         tab_header = ("Trace Name \t\t\t          \t\t\t            \t\t\t\n"
                       "Start Time \t\t\t End Time \t\t\t Blockettes \t\t\t")
@@ -1213,7 +1242,9 @@ class MainWindow(QtWidgets.QWidget):
                     numtrace += 1
             text = "Displaying " + str(numtrace) + " traces"
             self.infobar.setText(text)
-            self.infobar.setAlignment(QtGui.Qt.AlignCenter)
+            self.infobar.setStyleSheet(
+                "background-color:green;")
+            self.infobar.setAlignment(Qt.AlignCenter)
         return
 
     def display_error(self):
@@ -1249,7 +1280,7 @@ class MainWindow(QtWidgets.QWidget):
         text = (str(numfiles) + " unique mseed files found. *** " +
                 str(numerrors) + " scan errors.")
         self.infobar.setText(text)
-        self.infobar.setAlignment(QtGui.Qt.AlignCenter)
+        self.infobar.setAlignment(Qt.AlignCenter)
 
         # tab info
         self.tabwidget.setTabText(1, "Big Endian (" + str(self.num_big_files) + ")")
@@ -1276,10 +1307,10 @@ class MainWindow(QtWidgets.QWidget):
                     print(str)
         else:
             if bg != "yellow" and bg != "lightblue":
-                QtWidgets.QApplication.beep()
+                QApplication.beep()
             text = str
             self.infobar.setText(text)
-            self.infobar.setAlignment(QtGui.Qt.AlignCenter)
+            self.infobar.setAlignment(Qt.AlignCenter)
             self.infobar.setStyleSheet("background-color:" + bg)
 
     def big_e_dropmenu_selections(self, event):
@@ -1326,16 +1357,16 @@ class MainWindow(QtWidgets.QWidget):
         """
 
         # Window settings
-        self.save_window = QtWidgets.QWidget()
+        self.save_window = QWidget()
         self.save_window.setFixedSize(400, 200)
         self.save_window.setWindowTitle("Save Scan File as:")
 
         # Save file name
-        self.save_file_name = QtWidgets.QTextEdit()
+        self.save_file_name = QTextEdit()
         self.save_file_name.setText(save_file)
 
         # Initialize buttons
-        self.save_btn = QtWidgets.QPushButton("Save")
+        self.save_btn = QPushButton("Save")
         self.save_btn.setStyleSheet("""
                                     QPushButton{
                                     background-color:rgb(98, 160, 234);;
@@ -1348,7 +1379,7 @@ class MainWindow(QtWidgets.QWidget):
         self.save_btn.clicked.connect(lambda: self.write_file())
         self.save_btn.clicked.connect(lambda: self.save_window.close())
 
-        self.cancel_btn = QtWidgets.QPushButton("Cancel")
+        self.cancel_btn = QPushButton("Cancel")
         self.cancel_btn.setStyleSheet("QPushButton::hover"
                                       "{"
                                       "background-color:red;"
@@ -1356,33 +1387,33 @@ class MainWindow(QtWidgets.QWidget):
         self.cancel_btn.clicked.connect(lambda: self.close_save_window())
 
         # Initialize checkboxes
-        self.save_all = QtWidgets.QCheckBox()
+        self.save_all = QCheckBox()
         self.save_all.setText("All")
         self.save_all.toggled.connect(self.save_all_toggled)
-        self.save_info = QtWidgets.QCheckBox()
+        self.save_info = QCheckBox()
         self.save_info.setText("Info")
-        self.save_errors = QtWidgets.QCheckBox()
+        self.save_errors = QCheckBox()
         self.save_errors.setText("Error")
-        self.save_error_traces = QtWidgets.QCheckBox()
+        self.save_error_traces = QCheckBox()
         self.save_error_traces.setText("Error Traces")
 
         # Initialize layouts
-        file_layout = QtWidgets.QVBoxLayout()
+        file_layout = QVBoxLayout()
         file_layout.addWidget(self.save_file_name)
 
-        check_box_layout = QtWidgets.QHBoxLayout()
+        check_box_layout = QHBoxLayout()
         check_box_layout.addWidget(self.save_all)
         check_box_layout.addWidget(self.save_info)
         check_box_layout.addWidget(self.save_errors)
         check_box_layout.addWidget(self.save_error_traces)
-        check_box_layout.setAlignment(QtCore.Qt.AlignCenter)
+        check_box_layout.setAlignment(Qt.AlignCenter)
 
-        buttons_layout = QtWidgets.QVBoxLayout()
+        buttons_layout = QVBoxLayout()
         buttons_layout.addWidget(self.save_btn)
         buttons_layout.addWidget(self.cancel_btn)
-        buttons_layout.setAlignment(QtCore.Qt.AlignBottom)
+        buttons_layout.setAlignment(Qt.AlignBottom)
 
-        total_layout = QtWidgets.QVBoxLayout()
+        total_layout = QVBoxLayout()
         total_layout.addLayout(file_layout)
         total_layout.addLayout(check_box_layout)
         total_layout.addLayout(buttons_layout)
@@ -1420,13 +1451,13 @@ class MainWindow(QtWidgets.QWidget):
         """
 
         self.result = ""
-        self.warning = QtWidgets.QMessageBox()
-        self.warning.setIcon(QtWidgets.QMessageBox.Warning)
+        self.warning = QMessageBox()
+        self.warning.setIcon(QMessageBox.Warning)
         self.warning.setWindowTitle("Warning")
         self.warning.setText("The file exists!")
 
-        overwrite = self.warning.addButton("Overwrite", QtWidgets.QMessageBox.ActionRole)
-        cancel = self.warning.addButton("Cancel", QtWidgets.QMessageBox.ActionRole)
+        overwrite = self.warning.addButton("Overwrite", QMessageBox.ActionRole)
+        cancel = self.warning.addButton("Cancel", QMessageBox.ActionRole)
 
         self.warning.exec_()
 
@@ -1536,6 +1567,108 @@ class MainWindow(QtWidgets.QWidget):
                 return
 
         self.save_window.close()
+    
+    def begin_thread(self, title, func, after_func):
+        """
+        Create thread to run function
+        """
+
+        # init thread and worker
+        self.thread = QThread(self)
+        self.func_worker = Worker(func)
+
+        # set signals/slots
+        self.func_worker.update.connect(self.update_progress_window)
+        self.thread.started.connect(self.func_worker.run)
+
+        # function to run after thread finishes
+        if after_func:
+            self.func_worker.finished.connect(after_func)
+
+        # delete worker and thread when done
+        self.func_worker.finished.connect(self.thread.quit)
+        self.func_worker.finished.connect(self.func_worker.deleteLater)
+        self.thread.finished.connect(self.thread.deleteLater)
+
+        # init vars related to progress window
+        if not BATCHMODE:
+            # get file num for progress window
+            numfiles = 0
+            text = ""
+
+            if title == "Scan Traces":
+                for dir in self.data_dir_list:
+                    numfiles += (
+                        sum([len(files) for r, d, files in os.walk(dir)]))
+                text = title + " is active. Please wait."
+
+            # launch progress window
+            self.build_progress_window(title, text, numfiles)
+            self.func_worker.finished.connect(
+                lambda: self.progress_window.done(0))
+
+            # give progress window a small
+            # amount of time to finish
+            # loading before
+            # starting thread
+            loop = QEventLoop(self)
+            QTimer.singleShot(100, loop.quit)
+            loop.exec_()
+
+        self.thread.start()
+    
+    def build_progress_window(self, title, text, max):
+        """
+        Create progress window to update user
+        on status of current process
+        """
+
+        self.progress_window = QProgressDialog(
+            labelText=text, minimum=0,
+            maximum=max, parent=self)
+
+        cancel_b = QPushButton("Cancel")
+        cancel_b.setStyleSheet(
+            "QPushButton::hover{background-color:red;}")
+        cancel_b.clicked.connect(
+            lambda: self.stop_thread(title))
+        self.progress_window.setCancelButton(cancel_b)
+        self.progress_window.open()
+    
+    def update_progress_window(self, val):
+        """
+        Update progress window progress bar
+        """
+
+        self.progress_window.setValue(val)
+    
+    def stop_thread(self, title):
+        """
+        Stop the currently running thread
+        """
+
+        if title == "Scan Traces":
+            self.run_scan = 0
+
+
+class Worker(QObject):
+    """
+    Thread worker
+    """
+    # progress_window progressbar counter
+    update = Signal(int)
+    # progress_window progressbar max
+    new_max = Signal(int)
+    finished = Signal()
+
+    def __init__(self, func):
+        super().__init__()
+        self.func = func
+
+    def run(self):
+        self.func()
+        self.finished.emit()
+
 
 
 if __name__ == "__main__":
-- 
GitLab