示例#1
0
class Sidebar(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle = "Longway Sidebar"
        self.setGeometry(1920 - 180, 0, 180, 1080 - 50)

        self.layout = QVBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.setStyleSheet(
            'background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop: 0 #4d77b3, stop: 0.015 #67a6d0, stop: 0.03 #5578ac, stop: 1 #3364af)')

        self.gadgets = []

        self.splitter = QSplitter(Qt.Vertical)
        self.splitter.setChildrenCollapsible(False)
        self.splitter.setMinimumHeight(20)

        self.clickable = QPushButton()
        self.clickable.setStyleSheet('''
                                    QPushButton {
                                        background-color: #00ffffff;
                                    }
                                    QPushButton:pressed {
                                        background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #111166, stop: 1 #222277);     
                                    }
                                    QPushButton:hover {
                                        background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #4685d9, stop: 1 #89c8f1);
                                    }; height: 24px; border: none; color:white; font-weight: bold;''')
        self.clickable.setText('Add Gadget')

        self.clickable.clicked.connect(self.add_gadget)
        for i in range(0):
            r = randint(0, 1)
            if r == 0:
                self.gadgets.append(GadgetText())
            elif r == 1:
                self.gadgets.append(GadgetWeb())
            # self.gadgets[-1].setFrameShape(QFrame.StyledPanel)
            self.splitter.addWidget(self.gadgets[-1])

        self.layout.addWidget(self.splitter)
        self.layout.addWidget(self.clickable)
        self.setLayout(self.layout)

        self.setWindowFlags(Qt.FramelessWindowHint)
        self.show()

    def add_gadget(self):
        self.gadgets.append(GadgetText())
        self.splitter.addWidget(self.gadgets[-1])
示例#2
0
class KUZA(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.video_url_input = QLineEdit()
        self.video_url_input.setPlaceholderText('유튜브 URL을 입력하세요.')

        self.target_lang = QComboBox()
        self.target_lang.addItem('영어')
        self.target_lang.addItem('중국어(간체-중국)')
        self.target_lang.addItem('일본어')
        self.target_lang.addItem('이탈리아어')
        self.target_lang.addItem('독일어')
        self.target_lang.addItem('러시아어')
        self.target_lang.addItem('스페인어')
        self.target_lang.addItem('프랑스어')
        self.target_lang.setToolTip('영상의 언어를 선택하세요.')

        self.manual_lang = QLineEdit()
        self.manual_lang.setPlaceholderText('영상 언어를 GCP의 STT에 맞게 입력(선택)')

        self.submit_btn = QPushButton()
        self.submit_btn.setText('시작')
        self.submit_btn.clicked.connect(self.submit)

        self.json_upload_btn = QPushButton("JSON 파일 열기")
        self.json_upload_btn.clicked.connect(self.upload_GCP_key)

        self.search_bar_groupbox = QGroupBox()
        search_grid = QGridLayout()
        search_grid.addWidget(self.video_url_input, 0, 0)
        search_grid.addWidget(self.target_lang, 0, 1)
        search_grid.addWidget(self.manual_lang, 0, 2)
        search_grid.addWidget(self.submit_btn, 0, 3)
        search_grid.addWidget(self.json_upload_btn, 0, 4)
        self.search_bar_groupbox.setLayout(search_grid)
        self.search_bar_groupbox.setMaximumHeight(50)
        self.search_bar_groupbox.setMinimumWidth(850)

        self.webview = QWebEngineView()
        self.webview.load(QUrl('about:blank'))
        self.webview_groupbox = QGroupBox()
        webview_grid = QGridLayout()
        webview_grid.addWidget(self.webview)
        self.webview_groupbox.setLayout(webview_grid)
        self.webview.show()

        self.caption_text_groupbox = QGroupBox()
        caption_grid = QGridLayout()
        self.caption_text_display = QTextEdit()
        caption_grid.addWidget(self.caption_text_display)
        self.caption_text_groupbox.setLayout(caption_grid)

        self.split_video_caption = QSplitter(Qt.Horizontal)
        self.split_video_caption.addWidget(self.webview_groupbox)
        self.split_video_caption.addWidget(self.caption_text_groupbox)
        self.split_video_caption.setMinimumHeight(700)

        grid = QGridLayout()
        grid.addWidget(self.search_bar_groupbox, 0, 0)
        grid.addWidget(self.split_video_caption, 1, 0)

        self.setLayout(grid)

        self.setWindowTitle('K-UZA')
        self.showMaximized()

    def submit(self):
        url = self.video_url_input.text().strip()
        if not 'watch?v=' in url:
            caption = '정상적인 URL을 입력해주세요.'
        else:
            youtube_id_idx = url.find('watch?v=') + len('watch?v=')
            youtube_id = url[youtube_id_idx:]
            self.webview.load(
                QUrl('https://www.youtube.com/embed/' + youtube_id))
            man_lang = self.manual_lang.text()
            if man_lang:
                caption = run_backend(url, man_lang)
            else:
                lang = self.target_lang.currentText()
                if lang:
                    lang_GCP = {
                        '영어': 'en-US',
                        '중국어(간체-중국)': 'zh',
                        '프랑스어': 'fr-FR',
                        '러시아어': 'ru-RU',
                        '이탈리아어': 'it-IT',
                        '스페인어': 'es-ES',
                        '일본어': 'ja-JP',
                        '독일어': 'de-DE'
                    }
                caption = run_backend(url, lang_GCP[lang])
        self.caption_text_display.setText(caption)

    def upload_GCP_key(self):
        json_path = QFileDialog.getOpenFileName(self, 'Open File', './')
        if not json_path[0]:
            return
        with open(json_path[0], 'r') as json:
            GCP_KEY = json.read()
        with open('./GCP-API-KEY.json', 'w') as key_file:
            key_file.write(GCP_KEY)
示例#3
0
class Sidebar(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle = "Longway Sidebar"
        self.setGeometry(1920 - 180, 0, 180, 1080 - 32)

        self.color_gradient_left = QColor(100, 100, 100, 50)
        self.color_gradient_right = QColor(0, 0, 0, 255)

        self.layout = QVBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)

        # self.layout.addWidget(QWidget())
        self.setLayout(self.layout)

        # Splitter for storing tiles
        self.tiles = []

        self.splitter = QSplitter(Qt.Vertical)
        self.splitter.setChildrenCollapsible(False)
        self.splitter.setMinimumHeight(20)
        self.splitter.setStyleSheet('''
                                    QSplitter::handle:vertical {
                                        height: 1px;
                                        background: black
                                    }''')
        self.layout.addWidget(self.splitter)

        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)

        # Starting the show!
        self.addTile(TileSlideshow())
        self.addTile(TileTest())
        self.addTile(Tile())
        self.addTile(Tile())
        self.addTile(QWidget())
        self.show()

    def paintEvent(self, event):
        # Initializing QPainter
        qp = QPainter()
        qp.begin(self)
        qp.setRenderHint(QPainter.Antialiasing)
        sidebar_rect = self.geometry()
        # Gradient
        gradient = QLinearGradient(0, 0, sidebar_rect.width(), 0)
        gradient.setColorAt(0.0, self.color_gradient_left)
        gradient.setColorAt(1.0, self.color_gradient_right)

        qp.setBrush(QBrush(gradient))
        # qp.setPen(Qt.white)
        qp.drawRect(0, 0, sidebar_rect.width(), sidebar_rect.height())

        # Glass highlight
        qp.setBrush(QBrush(Qt.white))
        qp.setPen(QPen(QBrush(Qt.white), 0.01))
        qp.setOpacity(0.1)

        qppath = QPainterPath()
        qppath.moveTo(sidebar_rect.width() * 0.2, 0)
        qppath.quadTo(sidebar_rect.width() * 0.3, sidebar_rect.height()
                      * 0.5, sidebar_rect.width() * 0.2, sidebar_rect.height() - 1)
        qppath.lineTo(0, sidebar_rect.height())
        qppath.lineTo(0, 0)
        qp.setClipPath(qppath)
        qp.drawRect(1, 1, sidebar_rect.width() - 1, sidebar.height() - 1)

        # Left border highlight
        qp.setOpacity(1.0)
        gradient = QLinearGradient(0, 0, 2, 0)
        gradient.setColorAt(0.0, QColor(255, 255, 255, 80))
        gradient.setColorAt(1.0, QColor(0, 0, 0, 0))

        qp.setBrush(QBrush(gradient))
        # qp.setPen(Qt.transparent)
        qp.drawRect(0, 0, 8, sidebar_rect.height())

        qp.end()

    def addTile(self, tile):
        self.tiles.append(tile)
        self.splitter.addWidget(self.tiles[-1])
示例#4
0
class App(QWidget):
    def __init__(self):
        super(App, self).__init__()
        self.title = 'IotHoneyPot'
        self.left = 0
        self.top = 0
        desktop = QDesktopWidget()
        self.width = desktop.geometry().width()
        self.height = desktop.geometry().height()
        print "width : %d height %d" % (self.width, self.height)
        self.initUI()
        self.showMaximized()

    def initUI(self):
        self.setWindowTitle(self.title)
        #self.setGeometry(self.left, self.top, self.width, self.height)
        # Add box layout, add table to box layout and add box layout to widget
        self.mainLayout = QVBoxLayout()

        #creating the topBar
        self.topBar = QGroupBox()
        self.horizontalLayout = QHBoxLayout()
        startButton = QPushButton("START")
        stopButton = QPushButton("STOP")
        startButton.setFixedSize(100, 28)
        stopButton.setFixedSize(100, 28)
        self.horizontalLayout.addWidget(startButton, 0, Qt.AlignRight)
        self.horizontalLayout.addWidget(stopButton)
        self.topBar.setLayout(self.horizontalLayout)
        startButton.clicked.connect(self.Start)
        stopButton.clicked.connect(self.Stop)
        #topBar ends here

        # create centralWidet
        self.createCentralWidget()

        self.mainLayout.addWidget(self.topBar)
        self.mainLayout.addWidget(self.centralWidget)
        self.setLayout(self.mainLayout)

        #self.updatePorts("23")
        #self.updateAttackerIPs("192.168.8.17")

        # Show widget
        self.show()

    def Start(self):
        #self.my=thread.start_new_thread(startmiddleware,("192.168.43.152","23",self))
        self.my = MyThread("192.168.1.3", "23", self)
        self.my.start()

    def Stop(self):
        self.my.stop()

    def updatePorts(self, port):
        item = QStandardItem(port)
        item.setEditable(False)
        self.ports.appendRow(item)

    def updateAttackerIPs(self, ip):
        item = QStandardItem(ip)
        item.setEditable(False)
        self.attackerIP.appendRow(item)

    def updateUIlogs(self, date, time, attackerip, attackerport, direction,
                     localip, localport, protocol, byte, country, payload):
        global i
        self.tableWidget.insertRow(i + 1)
        self.tableWidget.setItem(i, 0, QTableWidgetItem(date))
        self.tableWidget.setItem(i, 1, QTableWidgetItem(time))
        self.tableWidget.setItem(i, 2, QTableWidgetItem(attackerip))
        self.tableWidget.setItem(i, 3, QTableWidgetItem(attackerport))
        self.tableWidget.setItem(i, 4, QTableWidgetItem(direction))
        self.tableWidget.setItem(i, 5, QTableWidgetItem(localip))
        self.tableWidget.setItem(i, 6, QTableWidgetItem(localport))
        self.tableWidget.setItem(i, 7, QTableWidgetItem(protocol))
        self.tableWidget.setItem(i, 8, QTableWidgetItem(byte))
        self.tableWidget.setItem(i, 9, QTableWidgetItem(country))
        self.tableWidget.setItem(i, 10, QTableWidgetItem(payload))
        i = (i + 1) % 1500
        self.tableWidget.show()

    def createCentralWidget(self):
        # Create table
        self.centralWidget = QSplitter(Qt.Horizontal)
        self.treeView = QTreeView()
        self.treeView.setHeaderHidden(True)
        standardModel = QStandardItemModel()
        rootNode = standardModel.invisibleRootItem()
        self.ports = QStandardItem("Local Ports")
        self.attackerIP = QStandardItem("Attacker IPs")
        self.ports.setEditable(False)
        self.attackerIP.setEditable(False)

        rootNode.appendRow(self.ports)
        rootNode.appendRow(self.attackerIP)

        self.treeView.setModel(standardModel)

        self.tableWidget = QTableWidget(self)
        self.tableWidget.setRowCount(1500)
        self.tableWidget.setColumnCount(11)
        self.tableWidget.verticalHeader().setVisible(False)
        self.tableWidget.setHorizontalHeaderLabels([
            'Date', 'Time', 'RemoteIP', 'Remote Port', 'Direction', 'Local IP',
            'Local Port', 'Protocol', 'Bytes', 'Country', 'Packet Detail'
        ])
        self.tableWidget.setColumnWidth(0, 100)
        self.tableWidget.setColumnWidth(2, self.width * 0.1)
        self.tableWidget.setColumnWidth(3, self.width * 0.07)
        self.tableWidget.setColumnWidth(4, self.width * 0.05)
        self.tableWidget.setColumnWidth(5, self.width * 0.1)
        self.tableWidget.setColumnWidth(6, self.width * 0.06)
        self.tableWidget.setColumnWidth(7, self.width * 0.05)
        self.tableWidget.setColumnWidth(8, self.width * 0.04)
        self.tableWidget.setColumnWidth(10, self.width * 0.095)
        #        self.tableWidget.move(30,10)

        # table selection change
        self.tableWidget.doubleClicked.connect(self.on_click)
        self.treeView.setMinimumWidth(self.width * 0.15)
        self.tableWidget.setMinimumWidth(self.width * 0.85)
        self.centralWidget.addWidget(self.treeView)
        self.centralWidget.addWidget(self.tableWidget)
        self.centralWidget.setMinimumHeight(self.height * 0.95)

    @pyqtSlot()
    def on_click(self):
        print("\n")
        for currentQTableWidgetItem in self.tableWidget.selectedItems():
            print(currentQTableWidgetItem.row(),
                  currentQTableWidgetItem.column(),
                  currentQTableWidgetItem.text())
示例#5
0
class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = 'kfit'
        self.left = 400
        self.top = 150
        self.width = 1200
        self.height = 800
        self.file_name = ''
        self.xcol_idx = 0
        self.ycol_idx = 1
        self.ngau = 0
        self.nlor = 0
        self.nvoi = 0
        self.nlin = 1
        self.model = None
        self.result = None
        self.curves_df = None
        self.params_df = None
        self.edit_mode = False
        # empty Parameters to hold parameter guesses/constraints
        self.params = Parameters()
        self.guesses = {'value': {}, 'min': {}, 'max': {}}
        self.usr_vals = {'value': {}, 'min': {}, 'max': {}}
        self.usr_entry_widgets = {}
        self.cid = None

        # file import settings
        self.sep = ','
        self.header = 'infer'
        self.index_col = None
        self.skiprows = None
        self.dtype = None
        self.encoding = None

        # keyboard shortcuts
        self.fit_shortcut = QShortcut(QKeySequence('Ctrl+F'), self)
        self.fit_shortcut.activated.connect(self.fit)
        self.reset_shortcut = QShortcut(QKeySequence('Ctrl+R'), self)
        self.reset_shortcut.activated.connect(self.hard_reset)
        self.save_fit = QShortcut(QKeySequence('Ctrl+S'), self)
        self.save_fit.activated.connect(self.export_results)
        self.add_gau = QShortcut(QKeySequence('G'), self)
        self.add_gau.activated.connect(lambda: self.increment('gau', True))
        self.add_gau.activated.connect(self.init_param_widgets)
        self.sub_gau = QShortcut(QKeySequence('Shift+G'), self)
        self.sub_gau.activated.connect(lambda: self.increment('gau', False))
        self.sub_gau.activated.connect(self.init_param_widgets)
        self.add_lor = QShortcut(QKeySequence('L'), self)
        self.add_lor.activated.connect(lambda: self.increment('lor', True))
        self.add_lor.activated.connect(self.init_param_widgets)
        self.sub_lor = QShortcut(QKeySequence('Shift+L'), self)
        self.sub_lor.activated.connect(lambda: self.increment('lor', False))
        self.sub_lor.activated.connect(self.init_param_widgets)
        self.add_voi = QShortcut(QKeySequence('V'), self)
        self.add_voi.activated.connect(lambda: self.increment('voi', True))
        self.add_voi.activated.connect(self.init_param_widgets)
        self.sub_voi = QShortcut(QKeySequence('Shift+V'), self)
        self.sub_voi.activated.connect(lambda: self.increment('voi', False))
        self.sub_voi.activated.connect(self.init_param_widgets)
        self.add_lin = QShortcut(QKeySequence('N'), self)
        self.add_lin.activated.connect(lambda: self.increment('lin', True))
        self.add_lin.activated.connect(self.init_param_widgets)
        self.sub_lin = QShortcut(QKeySequence('Shift+N'), self)
        self.sub_lin.activated.connect(lambda: self.increment('lin', False))
        self.sub_lin.activated.connect(self.init_param_widgets)

        # temporary data
        x = np.linspace(0, 10, 500)
        y = models.gauss(x, 0.5, 4, 0.4) + \
            models.gauss(x, 0.8, 5, 0.2) + \
            models.gauss(x, 0.4, 6, 0.3) + 0.2

        # set data
        self.data = pd.DataFrame([x, y]).T
        self.data.columns = ['x', 'y']
        self.x = self.data['x']
        self.y = self.data['y']
        self.xmin = self.data['x'].min()
        self.xmax = self.data['x'].max()

        self.initUI()

    def initUI(self):
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.setWindowTitle(self.title)
        self.setWindowIcon(QIcon('../images/K.png'))

        # set up the status bar
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage('Welcome to kfit!', msg_length)
        self.status_bar.setStyleSheet('background-color: white')

        # Create the Main Widget and Layout
        self.main_layout = QVBoxLayout()
        self.main_widget = QSplitter()
        self.main_widget.setOrientation(Qt.Vertical)
        self.setCentralWidget(self.main_widget)

        # create "top bar" widget
        self.topbar_layout = QHBoxLayout()
        self.topbar_widget = QWidget()
        # fit button
        self.fit_button = QPushButton('Fit', self)
        self.fit_button.setMaximumWidth(100)
        self.fit_button.clicked.connect(self.fit)
        self.fit_button.installEventFilter(self)
        # import button
        self.import_button = QPushButton('Import', self)
        self.import_button.setMaximumWidth(100)
        self.import_button.clicked.connect(self.get_data)
        self.import_button.installEventFilter(self)
        # import settings button
        self.import_settings_button = QPushButton('', self)
        self.import_settings_button.setIcon(
            QIcon.fromTheme('stock_properties'))
        self.import_settings_button.setMaximumWidth(40)
        self.import_settings_button.clicked.connect(
            self.import_settings_dialog)
        self.import_settings_button.installEventFilter(self)
        # reset fit button
        self.reset_button = QPushButton('', self)
        self.reset_button.setIcon(QIcon.fromTheme('view-refresh'))
        self.reset_button.setMaximumWidth(40)
        self.reset_button.clicked.connect(self.hard_reset)
        self.reset_button.installEventFilter(self)
        # save results button
        self.save_button = QPushButton('', self)
        self.save_button.setIcon(QIcon.fromTheme('filesave'))
        self.save_button.setMaximumWidth(40)
        self.save_button.clicked.connect(self.export_results)
        self.save_button.installEventFilter(self)
        # progress bar
        self.progress_bar = QProgressBar()
        # self.progressBar.setMaximumWidth(150)
        self.progress_bar.hide()
        # get column header for x
        self.xlabel = QLabel(self)
        self.xlabel.setText('ColumnIndex(X):')
        self.xlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        # self.xlabel.setMaximumWidth(250)
        self.xline_entry = QLineEdit(self)
        self.xline_entry.setText('0')
        self.xline_entry.setAlignment(Qt.AlignCenter)
        # self.xline_entry.setMaximumWidth(50)
        self.xline_entry.returnPressed.connect(self.column_index_set)
        # get column header for y
        self.ylabel = QLabel(self)
        self.ylabel.setText('ColumnIndex(Y):')
        self.ylabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        # self.ylabel.setMaximumWidth(100)
        self.yline_entry = QLineEdit(self)
        self.yline_entry.setText('1')
        self.yline_entry.setAlignment(Qt.AlignCenter)
        # self.yline_entry.setMaximumWidth(50)
        self.yline_entry.returnPressed.connect(self.column_index_set)
        # add topbar widgets to layout
        self.topbar_layout.addSpacing(600)
        self.topbar_layout.addWidget(self.xlabel)
        self.topbar_layout.addWidget(self.xline_entry)
        self.topbar_layout.addWidget(self.ylabel)
        self.topbar_layout.addWidget(self.yline_entry)
        self.topbar_layout.addWidget(self.fit_button)
        self.topbar_layout.addWidget(self.import_button)
        self.topbar_layout.addWidget(self.import_settings_button)
        self.topbar_layout.addWidget(self.reset_button)
        self.topbar_layout.addWidget(self.save_button)
        self.topbar_layout.addWidget(self.progress_bar)
        self.topbar_layout.setAlignment(Qt.AlignRight)
        self.topbar_widget.setLayout(self.topbar_layout)
        self.topbar_widget.setMaximumHeight(75)

        # create tabs widget
        self.tabs = QTabWidget(self)
        self.tab1 = QWidget(self)
        self.tab2 = QTableView(self)
        self.tab3 = QWidget(self)
        self.tabs.addTab(self.tab1, 'Graph')
        self.tabs.addTab(self.tab2, 'Data')
        self.tabs.addTab(self.tab3, 'Output')
        self.tabs.setMinimumHeight(300)

        # create params widget
        self.params_widget = QSplitter()
        self.gau_layout = QVBoxLayout()
        self.gau_layout.setAlignment(Qt.AlignTop)
        self.gau_widget = QWidget()
        self.gau_widget.setLayout(self.gau_layout)
        self.gau_widget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.gau_scroll = QScrollArea()
        self.gau_scroll.setWidget(self.gau_widget)
        self.gau_scroll.setWidgetResizable(True)
        self.lor_widget = QWidget()
        self.lor_layout = QVBoxLayout()
        self.lor_layout.setAlignment(Qt.AlignTop)
        self.lor_widget.setLayout(self.lor_layout)
        self.lor_widget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.lor_scroll = QScrollArea()
        self.lor_scroll.setWidget(self.lor_widget)
        self.lor_scroll.setWidgetResizable(True)
        self.voi_widget = QWidget()
        self.voi_layout = QVBoxLayout()
        self.voi_layout.setAlignment(Qt.AlignTop)
        self.voi_widget.setLayout(self.voi_layout)
        self.voi_widget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.voi_scroll = QScrollArea()
        self.voi_scroll.setWidget(self.voi_widget)
        self.voi_scroll.setWidgetResizable(True)
        self.lin_widget = QWidget()
        self.lin_layout = QVBoxLayout()
        self.lin_layout.setAlignment(Qt.AlignTop)
        self.lin_widget.setLayout(self.lin_layout)
        self.lin_widget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.lin_scroll = QScrollArea()
        self.lin_scroll.setWidget(self.lin_widget)
        self.lin_scroll.setWidgetResizable(True)
        self.params_widget.addWidget(self.gau_scroll)
        self.params_widget.addWidget(self.lor_scroll)
        self.params_widget.addWidget(self.voi_scroll)
        self.params_widget.addWidget(self.lin_scroll)
        self.params_widget.setMinimumHeight(180)

        # add everything to main widget
        self.main_widget.addWidget(self.topbar_widget)
        self.main_widget.addWidget(self.tabs)
        self.main_widget.addWidget(self.params_widget)

        # Tab 1 - Graph / Model
        # Graph
        plt.style.use('fivethirtyeight')
        self.tab1.figure = Figure(figsize=(8, 6), dpi=60)
        self.tab1.canvas = FigureCanvas(self.tab1.figure)
        self.tab1.toolbar = NavigationToolbar(self.tab1.canvas, self)
        # tristate checkbox for edit mode
        self.emode_box = QCheckBox()
        self.emode_box.setTristate(True)
        self.emode_box.setIcon(QIcon.fromTheme('stock_edit'))
        self.emode_box.stateChanged.connect(self.toggle_edit_mode)
        self.emode_box.installEventFilter(self)
        # tweaking the toolbar layout
        self.tab1.toolbar.setIconSize(QSize(18, 18))
        spacer = QWidget()
        spacer.setFixedWidth(20)
        self.tab1.toolbar.addWidget(spacer)
        self.tab1.toolbar.addWidget(self.emode_box)
        self.tab1.toolbar.locLabel.setAlignment(Qt.AlignRight | Qt.AlignCenter)
        graph_layout = QVBoxLayout()
        graph_layout.addWidget(self.tab1.toolbar)
        graph_layout.addWidget(self.tab1.canvas)

        # The "Set Model" layout
        model_layout = QGridLayout()
        widget_setgau = QWidget()
        layout_setgau = QHBoxLayout()
        layout_setgau.setSizeConstraint(QLayout.SetFixedSize)
        widget_setgau.setLayout(layout_setgau)
        widget_setlor = QWidget()
        layout_setlor = QHBoxLayout()
        layout_setlor.setSizeConstraint(QLayout.SetFixedSize)
        widget_setlor.setLayout(layout_setlor)
        widget_setvoi = QWidget()
        layout_setvoi = QHBoxLayout()
        layout_setvoi.setSizeConstraint(QLayout.SetFixedSize)
        widget_setvoi.setLayout(layout_setvoi)
        widget_setlin = QWidget()
        layout_setlin = QHBoxLayout()
        layout_setlin.setSizeConstraint(QLayout.SetFixedSize)
        widget_setlin.setLayout(layout_setlin)
        model_layout.addWidget(widget_setgau, 0, 0)
        model_layout.addWidget(widget_setlor, 0, 1)
        model_layout.addWidget(widget_setvoi, 0, 2)
        model_layout.addWidget(widget_setlin, 0, 3)

        # specify number of gaussian curves
        gauss_label = QLabel(self)
        gauss_label.setText('Gaussians')
        gauss_label.setAlignment(Qt.AlignVCenter)
        gauss_button_add = QPushButton('', self)
        gauss_button_add.setIcon(QIcon.fromTheme('list-add'))
        gauss_button_add.clicked.connect(lambda: self.increment('gau', True))
        gauss_button_add.clicked.connect(self.init_param_widgets)
        gauss_button_sub = QPushButton('', self)
        gauss_button_sub.setIcon(QIcon.fromTheme('list-remove'))
        gauss_button_sub.clicked.connect(lambda: self.increment('gau', False))
        gauss_button_sub.clicked.connect(self.init_param_widgets)
        layout_setgau.addWidget(gauss_label)
        layout_setgau.addWidget(gauss_button_add)
        layout_setgau.addWidget(gauss_button_sub)
        # specify number of lorentzian curves
        lorentz_label = QLabel(self)
        lorentz_label.setText('Lorentzians')
        lorentz_label.setAlignment(Qt.AlignVCenter)
        lorentz_button_add = QPushButton('', self)
        lorentz_button_add.setIcon(QIcon.fromTheme('list-add'))
        lorentz_button_add.clicked.connect(lambda: self.increment('lor', True))
        lorentz_button_add.clicked.connect(self.init_param_widgets)
        lorentz_button_sub = QPushButton('', self)
        lorentz_button_sub.setIcon(QIcon.fromTheme('list-remove'))
        lorentz_button_sub.clicked.connect(
            lambda: self.increment('lor', False))
        lorentz_button_sub.clicked.connect(self.init_param_widgets)
        layout_setlor.addWidget(lorentz_label)
        layout_setlor.addWidget(lorentz_button_add)
        layout_setlor.addWidget(lorentz_button_sub)
        # specify number of voigt curves
        voigt_label = QLabel(self)
        voigt_label.setText('Pseudo-Voigts')
        voigt_label.setAlignment(Qt.AlignVCenter)
        voigt_button_add = QPushButton('', self)
        voigt_button_add.setIcon(QIcon.fromTheme('list-add'))
        voigt_button_add.clicked.connect(lambda: self.increment('voi', True))
        voigt_button_add.clicked.connect(self.init_param_widgets)
        voigt_button_sub = QPushButton('', self)
        voigt_button_sub.setIcon(QIcon.fromTheme('list-remove'))
        voigt_button_sub.clicked.connect(lambda: self.increment('voi', False))
        voigt_button_sub.clicked.connect(self.init_param_widgets)
        layout_setvoi.addWidget(voigt_label)
        layout_setvoi.addWidget(voigt_button_add)
        layout_setvoi.addWidget(voigt_button_sub)
        # specify number of lines
        line_label = QLabel(self)
        line_label.setText('Lines:')
        line_label.setAlignment(Qt.AlignVCenter)
        line_button_add = QPushButton('', self)
        line_button_add.setIcon(QIcon.fromTheme('list-add'))
        line_button_add.clicked.connect(lambda: self.increment('lin', True))
        line_button_add.clicked.connect(self.init_param_widgets)
        line_button_sub = QPushButton('', self)
        line_button_sub.setIcon(QIcon.fromTheme('list-remove'))
        line_button_sub.clicked.connect(lambda: self.increment('lin', False))
        line_button_sub.clicked.connect(self.init_param_widgets)
        layout_setlin.addWidget(line_label)
        layout_setlin.addWidget(line_button_add)
        layout_setlin.addWidget(line_button_sub)

        graph_layout.addLayout(model_layout)
        self.tab1.setLayout(graph_layout)
        self.plot()

        # Tab 2 - Data Table
        self.table_model = PandasModel(self.data)
        self.tab2.setModel(self.table_model)
        self.tab2.resizeColumnsToContents()

        # Tab 3 - Output
        self.tab3_widget = QPlainTextEdit()
        tab3_layout = QVBoxLayout()
        tab3_layout.addWidget(self.tab3_widget)
        self.tab3.setLayout(tab3_layout)

        self.init_param_widgets()
        self.show()

    def export_results(self):
        self.process_results()
        # open file dialog
        exp_file_name, _ = QFileDialog.getSaveFileName(
            self,
            'QFileDialog.getSaveFileName()',
            'fit_results.csv',
            'CSV files (*.csv)',
        )
        if exp_file_name:
            self.process_results()
            self.curves_df.to_csv(exp_file_name)
            # NOTE: if user chooses a file extension other than .csv, or does
            # not use a file extension, this should still work, but I haven't
            # tested too rigorously yet
            self.params_df.to_csv('{}.params.csv'.format(
                exp_file_name[:exp_file_name.find('.csv')]))
            self.status_bar.showMessage(
                'Exported fit results to: ' + exp_file_name, 2 * msg_length)
        else:
            self.status_bar.showMessage('Export canceled.', 2 * msg_length)
            return

    def process_results(self):
        if self.result is not None:
            self.params_df = pd.DataFrame.from_dict(self.result.best_values,
                                                    orient='index')
            self.params_df.index.name = 'parameter'
            self.params_df.columns = ['value']
            curves_dict = {
                'data': self.y,
                'total_fit': self.result.best_fit,
            }
            components = self.result.eval_components()
            for i, comp in enumerate(components):
                curves_dict[comp[:comp.find('_')]] = components[comp]
            self.curves_df = pd.DataFrame.from_dict(curves_dict)
            self.curves_df.index = self.x
            self.curves_df.index.name = self.data.columns[self.xcol_idx]
        else:
            self.status_bar.showMessage('No fit results to export!',
                                        msg_length)

    def eventFilter(self, object, event):
        if event.type() == QEvent.Enter:
            if object is self.reset_button:
                self.status_bar.showMessage("Reset fit")
            if object is self.import_settings_button:
                self.status_bar.showMessage("File import settings")
            if object is self.import_button:
                self.status_bar.showMessage("Import .csv file")
            if object is self.fit_button:
                self.status_bar.showMessage("Fit data")
            if object is self.emode_box:
                self.status_bar.showMessage("Toggle edit mode")
            if object is self.save_button:
                self.status_bar.showMessage("Export fit results")
            return True
        elif event.type() == QEvent.Leave:
            self.status_bar.showMessage(None)
        return False

    def init_model(self):
        # increment() ensures nlin >= 1
        self.model = models.line_mod(self.nlin)
        if self.ngau != 0:
            self.model += models.gauss_mod(self.ngau)
        if self.nlor != 0:
            self.model += models.lor_mod(self.nlor)
        if self.nvoi != 0:
            self.model += models.voigt_mod(self.nvoi)
        self.status_bar.showMessage(
            "Model updated: " +
            str([self.ngau, self.nlor, self.nvoi, self.nlin]), msg_length)

    def init_param_widgets(self):
        self.init_model()
        self.usr_entry_widgets = {'value': {}, 'min': {}, 'max': {}}
        labels = {}
        rnd = 3  # decimals to round to in placeholder text
        self.clear_fit_layouts()

        for param_name in self.model.param_names:
            # set param label text
            labels[param_name] = QLabel()
            labels[param_name].setText(param_name)

            # make qlineedit widgets
            for key in self.usr_entry_widgets:
                self.usr_entry_widgets[key][param_name] = QLineEdit()
                if param_name in self.usr_vals[key]:
                    self.usr_entry_widgets[key][param_name]\
                        .setPlaceholderText(
                            str(round(self.usr_vals[key][param_name], rnd))
                        )
                else:
                    self.usr_entry_widgets[key][param_name]\
                        .setPlaceholderText(key)
                # set up connections
                # connect() expects a callable func, hence the lambda
                self.usr_entry_widgets[key][param_name].returnPressed.connect(
                    lambda: self.update_usr_vals(self.usr_entry_widgets))

            # add widgets to respective layouts
            sublayout1 = QVBoxLayout()
            sublayout2 = QHBoxLayout()
            sublayout1.addWidget(labels[param_name])
            for key in self.usr_entry_widgets:
                sublayout2.addWidget(self.usr_entry_widgets[key][param_name])
            if param_name.find('gau') != -1:
                self.gau_layout.addLayout(sublayout1)
                self.gau_layout.addLayout(sublayout2)
            if param_name.find('lor') != -1:
                self.lor_layout.addLayout(sublayout1)
                self.lor_layout.addLayout(sublayout2)
            if param_name.find('voi') != -1:
                self.voi_layout.addLayout(sublayout1)
                self.voi_layout.addLayout(sublayout2)
            if param_name.find('lin') != -1:
                self.lin_layout.addLayout(sublayout1)
                self.lin_layout.addLayout(sublayout2)

        # Resize all of the LineEntry widgets
        for key in self.usr_entry_widgets:
            for param, widget in self.usr_entry_widgets[key].items():
                widget.setMaximumWidth(150)

        if self.result is not None:
            self.set_params()
            self.update_param_widgets()

    def update_usr_vals(self, entry):
        # get text input from each usr_entry_widget
        for val_type, param_dict in self.usr_entry_widgets.items():
            for param, param_widget in param_dict.items():
                try:
                    self.usr_vals[val_type][param] = \
                        float(param_widget.text())
                except Exception:
                    pass

    def update_param_widgets(self):
        rnd = 3
        # the 'value' placeholder text is the result for that param
        # taken from self.result
        # the 'min' and 'max' text is from either the self.guesses
        # or from self.usr_vals
        for param in self.params:
            if param in self.result.best_values:
                self.usr_entry_widgets['value'][param].setPlaceholderText(
                    str(round(self.result.best_values[param], rnd)))
                self.usr_entry_widgets['min'][param].setPlaceholderText(
                    str(round(self.params[param].min, rnd)))
                self.usr_entry_widgets['max'][param].setPlaceholderText(
                    str(round(self.params[param].max, rnd)))

    def guess_params(self):
        for comp in self.model.components:
            if comp.prefix.find('gau') != -1 or \
                    comp.prefix.find('lor') != -1 or \
                    comp.prefix.find('voi') != -1:

                # need to define explicitly to make proper guesses
                c = comp.prefix + 'center'
                a = comp.prefix + 'amplitude'
                s = comp.prefix + 'sigma'
                f = comp.prefix + 'fraction'

                self.guesses['value'][c] = \
                    self.data.iloc[:, self.xcol_idx].mean()
                self.guesses['value'][a] = \
                    self.data.iloc[:, self.ycol_idx].mean()
                self.guesses['value'][s] = \
                    self.data.iloc[:, self.xcol_idx].std()
                self.guesses['min'][c] = None
                self.guesses['min'][a] = 0
                self.guesses['min'][s] = 0
                self.guesses['max'][c] = None
                self.guesses['max'][a] = None
                self.guesses['max'][s] = None

                if comp.prefix.find('voi') != -1:
                    self.guesses['value'][f] = 0.5
                    self.guesses['min'][f] = 0
                    self.guesses['max'][f] = 1
            else:
                slope = comp.prefix + 'slope'
                intc = comp.prefix + 'intercept'
                for p in [slope, intc]:
                    self.guesses['value'][p] = \
                        self.data.iloc[:, self.ycol_idx].mean()
                    self.guesses['min'][p] = None
                    self.guesses['max'][p] = None

    def set_params(self):
        self.params = Parameters()
        self.guess_params()
        self.update_usr_vals(self.usr_entry_widgets)
        vals = {}

        # fill params with any user-entered values
        # fill in blanks with guesses
        for param_name in self.model.param_names:
            for val_type in ['value', 'min', 'max']:
                if param_name in self.usr_vals[val_type]:
                    vals[val_type] = self.usr_vals[val_type][param_name]
                    # print('param: ' + param_name + ', type: ' +\
                    #         val_type + ', set_by: user')
                else:
                    vals[val_type] = self.guesses[val_type][param_name]
                    # print('param: ' + param_name + ', type: ' +\
                    #         val_type + ', set_by: guess')
            self.params.add(name=param_name,
                            value=vals['value'],
                            vary=True,
                            min=vals['min'],
                            max=vals['max'])

    def set_xy_range(self):
        self.x = self.data.iloc[:, self.xcol_idx]
        self.y = self.data.iloc[:, self.ycol_idx]
        self.xmin, self.xmax = self.ax.get_xlim()
        range_bool = (self.x >= self.xmin) & (self.x <= self.xmax)
        self.x = self.x[range_bool].values
        self.y = self.y[range_bool].values

    def reset_xy_range(self):
        self.xmin = np.min(self.x)
        self.xmax = np.max(self.x)

    def fit(self):
        self.emode_box.setCheckState(0)
        self.toggle_edit_mode()
        self.set_xy_range()
        self.set_params()
        self.result = self.model.fit(data=self.y,
                                     params=self.params,
                                     x=self.x,
                                     method='least_squares')
        self.tab3_widget.clear()
        self.tab3_widget.insertPlainText(self.result.fit_report())
        self.plot()
        # overwrite widgets to clear input (not ideal method..)
        self.init_param_widgets()
        # update widgets with new placeholder text
        self.update_param_widgets()

    def column_index_set(self):
        # make sure user enters index that can be converted to int
        try:
            idx_x = int(self.xline_entry.text())
        except ValueError:
            self.status_bar.showMessage(idx_type_error_msg, msg_length)
            self.xline_entry.setText(None)
            return
        try:
            idx_y = int(self.yline_entry.text())
        except ValueError:
            self.status_bar.showMessage(idx_type_error_msg, msg_length)
            self.yline_entry.setText(None)
            return
        self.xcol_idx = idx_x
        self.ycol_idx = idx_y
        self.result = None
        # make sure user enters an index that's in the data range
        try:
            self.x = self.data.iloc[:, self.xcol_idx]
        except IndexError:
            self.status_bar.showMessage(idx_range_error_msg, msg_length)
            self.xline_entry.setText(None)
            return
        try:
            self.y = self.data.iloc[:, self.ycol_idx]
        except IndexError:
            self.status_bar.showMessage(idx_range_error_msg, msg_length)
            self.yline_entry.setText(None)
            return
        self.xmin = np.min(self.x)
        self.xmax = np.max(self.x)
        self.plot()
        self.status_bar.showMessage(
            'ColumnIndex(X) = ' + str(idx_x) + ', ' + 'ColumnIndex(Y) = ' +
            str(idx_y), msg_length)

    def toggle_edit_mode(self):
        # first toggle off the zoom or pan button
        # so they don't interfere with edit_mode cursor style
        if self.tab1.toolbar._active == 'ZOOM':
            self.tab1.toolbar.zoom()
        if self.tab1.toolbar._active == 'PAN':
            self.tab1.toolbar.pan()
        states = {
            0: 'Edit mode off',
            1: 'Edit mode on | copy x-value',
            2: 'Edit mode on | copy y-value',
        }
        self.status_bar.showMessage(states[self.emode_box.checkState()],
                                    msg_length)
        if self.emode_box.checkState() == 0:
            self.mpl_cursor = None
            self.tab1.canvas.mpl_disconnect(self.cid)
        if self.emode_box.checkState() == 1:
            self.mpl_cursor = Cursor(self.ax, lw=1, c='red', linestyle='--')
            self.cid = self.tab1.canvas.mpl_connect('button_press_event',
                                                    self.get_coord_click)
        if self.emode_box.checkState() == 2:
            self.cid = self.tab1.canvas.mpl_connect('button_press_event',
                                                    self.get_coord_click)

    def get_coord_click(self, event):
        self.x_edit, self.y_edit = round(event.xdata, 3), round(event.ydata, 3)
        if self.emode_box.checkState() == 1:
            pyperclip.copy(self.x_edit)
            self.status_bar.showMessage(
                'Copied X=' + str(self.x_edit) + ' to clipboard!', msg_length)
        if self.emode_box.checkState() == 2:
            pyperclip.copy(self.y_edit)
            self.status_bar.showMessage(
                'Copied Y=' + str(self.y_edit) + ' to clipboard!', msg_length)

    def close_app(self):
        sys.exit()

    def get_data(self):
        self.emode_box.setCheckState(0)
        self.toggle_edit_mode()
        # reset column indices
        self.xcol_idx = 0
        self.ycol_idx = 1
        # open file dialog
        self.file_name, _ = QFileDialog.getOpenFileName(
            self, 'Open File', '', 'CSV files (*.csv);; All Files (*)')
        if self.file_name:
            # this message isn't showing up...
            # TODO: needs to be threaded
            self.status_bar.showMessage('Importing: ' + self.file_name,
                                        msg_length)
            try:
                df = tools.to_df(self.file_name,
                                 sep=self.sep,
                                 header=self.header,
                                 index_col=self.index_col,
                                 skiprows=self.skiprows,
                                 dtype=self.dtype,
                                 encoding=self.encoding)
                df.iloc[:, self.xcol_idx]
                df.iloc[:, self.ycol_idx]
            except Exception:
                self.status_bar.showMessage(file_import_error_msg,
                                            2 * msg_length)
                return
        else:
            self.status_bar.showMessage('Import canceled.', msg_length)
            return

        self.data = df
        self.table_model = PandasModel(self.data)
        self.tab2.setModel(self.table_model)
        self.tab2.resizeColumnsToContents()
        # clear any previous fit result
        self.result = None
        # reset x, y, and xlim
        self.x = self.data.iloc[:, self.xcol_idx].values
        self.y = self.data.iloc[:, self.ycol_idx].values
        self.xmin = self.data.iloc[:, self.xcol_idx].min()
        self.xmax = self.data.iloc[:, self.xcol_idx].max()
        self.plot()
        self.status_bar.showMessage('Import finished.', msg_length)

    def import_settings_dialog(self):
        self.dialog_window = QDialog()
        self.dialog_window.setWindowTitle('File Import Settings')
        toplevel = QVBoxLayout()
        dialog_layout = QHBoxLayout()
        label_layout = QVBoxLayout()
        entry_layout = QVBoxLayout()
        button_layout = QVBoxLayout()
        label1 = QLabel(self.dialog_window)
        label1.setText('sep')
        label2 = QLabel(self.dialog_window)
        label2.setText('header')
        label3 = QLabel(self.dialog_window)
        label3.setText('skiprows')
        label4 = QLabel(self.dialog_window)
        label4.setText('dtype')
        label5 = QLabel(self.dialog_window)
        label5.setText('encoding')
        for lbl in [label1, label2, label3, label4, label5]:
            label_layout.addWidget(lbl)
        self.sep_edit = QLineEdit(self.dialog_window)
        self.head_edit = QLineEdit(self.dialog_window)
        self.skipr_edit = QLineEdit(self.dialog_window)
        self.dtype_edit = QLineEdit(self.dialog_window)
        self.enc_edit = QLineEdit(self.dialog_window)
        self.sep_edit.setText(self.sep)
        self.head_edit.setText(self.header)
        # if value is None, show text as 'None'
        if self.skiprows is not None:
            self.skipr_edit.setText(self.skiprows)
        else:
            self.skipr_edit.setText('None')
        if self.dtype is not None:
            self.dtype_edit.setText(self.dtype)
        else:
            self.dtype_edit.setText('None')
        if self.encoding is not None:
            self.enc_edit.setText(self.encoding)
        else:
            self.enc_edit.setText('None')

        # add widgets to layout
        for ewidget in [
                self.sep_edit, self.head_edit, self.skipr_edit,
                self.dtype_edit, self.enc_edit
        ]:
            ewidget.setAlignment(Qt.AlignCenter)
            entry_layout.addWidget(ewidget)

        button1 = QPushButton('Set', self.dialog_window)
        button2 = QPushButton('Set', self.dialog_window)
        button3 = QPushButton('Set', self.dialog_window)
        button4 = QPushButton('Set', self.dialog_window)
        button5 = QPushButton('Set', self.dialog_window)
        for btn in [button1, button2, button3, button4, button5]:
            btn.clicked.connect(self.set_import_settings)
            button_layout.addWidget(btn)

        reflabel = QLabel(self.dialog_window)
        reflabel.setText(
            "for help, refer to <a href='https://pandas.pydata.org/" +
            "pandas-docs/stable/reference/api/" +
            "pandas.read_csv.html'>pandas.read_csv()</a>")
        reflabel.setOpenExternalLinks(True)
        reflabel.setAlignment(Qt.AlignCenter)
        for lo in [label_layout, entry_layout, button_layout]:
            dialog_layout.addLayout(lo)
        toplevel.addLayout(dialog_layout)
        toplevel.addSpacing(25)
        toplevel.addWidget(reflabel)
        self.dialog_window.setLayout(toplevel)
        self.dialog_window.setWindowModality(Qt.ApplicationModal)
        self.dialog_window.exec_()

    def set_import_settings(self):
        self.sep = self.sep_edit.text()
        self.header = self.head_edit.text()
        # convert 'None' entries to None
        if self.skipr_edit.text() == 'None':
            self.skiprows = None
        else:
            self.skiprows = self.skipr_edit.text()
        if self.dtype_edit.text() == 'None':
            self.dtype = None
        else:
            self.dtype = self.dtype_edit.text()
        if self.enc_edit.text() == 'None':
            self.encoding = None
        else:
            self.encoding = self.enc_edit.text()

    def plot(self):
        self.tab1.figure.clear()
        self.ax = self.tab1.figure.add_subplot(111, label=self.file_name)
        self.ax.scatter(self.x,
                        self.y,
                        s=100,
                        c='None',
                        edgecolors='black',
                        linewidth=1,
                        label='data')
        if self.result is not None:
            yfit = self.result.best_fit
            self.ax.plot(self.x, yfit, c='r', linewidth=2.5)
            cmap = cm.get_cmap('gnuplot')
            components = self.result.eval_components()
            for i, comp in enumerate(components):
                self.ax.plot(self.x,
                             components[comp],
                             linewidth=2.5,
                             linestyle='--',
                             c=cmap(i / len(components)),
                             label=comp[:comp.find('_')])
        self.ax.set_xlabel(self.data.columns[self.xcol_idx], labelpad=15)
        self.ax.set_ylabel(self.data.columns[self.ycol_idx], labelpad=15)
        self.ax.set_xlim([self.xmin, self.xmax])
        self.ax.legend(loc='upper right')
        self.tab1.figure.subplots_adjust(bottom=0.15, left=0.06, right=0.94)
        self.tab1.canvas.draw()

    def clear_layout(self, layout):
        if layout:
            while layout.count():
                item = layout.takeAt(0)
                widget = item.widget()
                if widget:
                    widget.deleteLater()
                else:
                    self.clear_layout(item.layout())
                layout.removeItem(item)

    def clear_fit_layouts(self):
        for layout in [
                self.gau_layout, self.lor_layout, self.voi_layout,
                self.lin_layout
        ]:
            self.clear_layout(layout)

    def hard_reset(self):
        self.clear_fit_layouts()
        self.ngau = 0
        self.nlor = 0
        self.nvoi = 0
        self.nlin = 1
        self.init_model()
        self.params = Parameters()
        self.result = None
        self.params_df = None
        self.curves_df = None
        self.guesses = {'value': {}, 'min': {}, 'max': {}}
        self.usr_vals = {'value': {}, 'min': {}, 'max': {}}
        self.init_param_widgets()
        self.plot()

    def increment(self, val, add):
        if add:
            if val == 'gau':
                self.ngau += 1
            if val == 'lor':
                self.nlor += 1
            if val == 'voi':
                self.nvoi += 1
            if val == 'lin':
                self.nlin += 1
        if not add:
            if val == 'gau':
                self.ngau -= 1
            if val == 'lor':
                self.nlor -= 1
            if val == 'voi':
                self.nvoi -= 1
            if val == 'lin':
                self.nlin -= 1

        # make sure value doesn't go below zero
        if self.ngau < 0:
            self.ngau = 0
        if self.nlor < 0:
            self.nlor = 0
        if self.nvoi < 0:
            self.nvoi = 0
        if self.nlin < 1:
            self.nlin = 1
示例#6
0
class Visualization:
    def __init__(self, world):
        """
        Main Interface between the OpenGL stuff and the simulator.
        Initializes the camera, and the opengl-widget.
        controlls the speed of the simulation.
        :param world: the world class
        """
        self._world = world
        self._last_light_rotation = 0
        self._rounds_per_second = 10
        self._reset_flag = False
        self._running = False
        self._app = None
        self._viewer = None
        self._gui = None
        self._splitter = None
        self._recording = False
        self._animation = world.config_data.animation
        self._auto_animation = world.config_data.auto_animation
        self._manual_animation_speed = world.config_data.manual_animation_speed
        self.light_rotation = False
        self.grid_size = world.grid.size

        # create the QApplication
        self._app = QApplication([])

        # create camera for the visualization
        # if grid is 2D, set to orthographic projection (it is better for 2D)
        if self._world.grid.get_dimension_count() == 2:
            self._camera = Camera(
                self._world.config_data.window_size_x,
                self._world.config_data.window_size_y,
                self._world.config_data.look_at, self._world.config_data.phi,
                self._world.config_data.theta, self._world.config_data.radius,
                self._world.config_data.fov,
                self._world.config_data.cursor_offset,
                self._world.config_data.render_distance, "ortho",
                self._world.grid.get_scaling())
        else:
            self._camera = Camera(
                self._world.config_data.window_size_x,
                self._world.config_data.window_size_y,
                self._world.config_data.look_at, self._world.config_data.phi,
                self._world.config_data.theta, self._world.config_data.radius,
                self._world.config_data.fov,
                self._world.config_data.cursor_offset,
                self._world.config_data.render_distance, "perspective",
                self._world.grid.get_scaling())

        # create the opengl widget
        self._viewer = OGLWidget(self._world, self._camera)
        self._viewer.glInit()
        self.recorder = Recorder(self._world, self._viewer)

        # create and show the main Window
        self._splitter = QSplitter()
        self._splitter.closeEvent = close
        self._splitter.setMinimumWidth(self._world.config_data.window_size_x)
        self._splitter.setMinimumHeight(self._world.config_data.window_size_y)
        self._splitter.setWindowTitle("Simulator")

        self._splitter.show()

        # create gui
        # creating the gui has to happen after showing the window, so the gui can access
        # opengl variables and programs during creation
        self._gui_module = importlib.import_module('components.gui.' +
                                                   self._world.config_data.gui)

        # the key press handler
        def key_press_event(event):
            self._gui_module.key_handler(event.key(), self._world, self)

        # loading key handler from gui module
        if "key_handler" in dir(self._gui_module):
            self._viewer.keyPressEventHandler = key_press_event
            self._splitter.keyPressEvent = key_press_event
        else:
            show_msg("No key_handler(key, vis) function found in gui module!",
                     Level.WARNING, self._splitter)

        # loading gui from gui module
        if "create_gui" in dir(self._gui_module):
            self._gui = self._gui_module.create_gui(self._world, self)
            if self._gui is not None and issubclass(self._gui.__class__,
                                                    QWidget):
                self._splitter.addWidget(self._gui)
                self._splitter.keyPressEvent = self._viewer.keyPressEvent
                self._splitter.keyReleaseEvent = self._viewer.keyReleaseEvent
                self._splitter.addWidget(self._viewer)
                self._splitter.setSizes([
                    self._world.config_data.window_size_x * 0.25,
                    self._world.config_data.window_size_x * 0.75
                ])
            else:
                # noinspection PyUnresolvedReferences
                show_msg(
                    "The create_gui(world, vis) function in gui module didn't return a QWidget."
                    + "Expected a QWidget or a subclass, but got %s." %
                    self._gui.__class__.__name__, Level.WARNING,
                    self._splitter)
                self._splitter.addWidget(self._viewer)

        else:
            show_msg(
                "No create_gui(world, vis) function found in gui module. GUI not created",
                Level.INFO, self._splitter)
            self._splitter.addWidget(self._viewer)

        # waiting for the simulation window to be fully active
        while not self._splitter.windowHandle().isExposed():
            self._process_events()
        # first update and draw call.
        self._viewer.update_scene()

    def is_recording(self):
        return self._recording

    def _process_events(self):
        self._app.processEvents()
        if self._reset_flag:
            raise ResetException()

    def reset(self):
        """
        stops the simulation.
        deletes all data in the visualization.
        resets the camera
        :return:
        """
        self._reset_flag = False
        self._running = False
        self._viewer.agent_offset_data = {}
        self._viewer.agent_update_flag = True
        self._viewer.item_offset_data = {}
        self._viewer.item_update_flag = True
        self._viewer.location_offset_data = {}
        self._viewer.location_update_flag = True
        self._viewer.update_data()
        self._camera.reset()
        self._viewer.update_scene()

    def wait_for_thread(self, thread: Thread, window_message, window_title):
        """
        executes a thread and shows a loading window till the thread stops.
        blocks the gui, while thread runs.
        :param thread: the thread
        :param window_message: the displayed message
        :param window_title: the title of the loading window
        :return:
        """

        loading_window = LoadingWindow(window_message, window_title)
        if self._gui is not None and issubclass(self._gui.__class__, QWidget):
            self._gui.setDisabled(True)
        thread.start()
        while thread.is_alive():
            try:
                self._process_events()
            except VisualizationError as ve:
                show_msg(ve, Level.CRITICAL, self._splitter)
                exit(1)
        thread.join()
        loading_window.close()
        self._gui.setDisabled(False)

    def rotate_light(self):
        """
        rotates the light direction at a steady degrees/second velocity independent of the CPU-clock or framerate.
        :return:
        """
        # rotation of light only in 3d
        if self._world.grid.get_dimension_count() > 2:
            # 20° per second rotation
            if self._last_light_rotation == 0:
                self._last_light_rotation = time.perf_counter()
            else:
                angle = (time.perf_counter() - self._last_light_rotation) * 20
                self._last_light_rotation = time.perf_counter()
                self._viewer.rotate_light(angle)
                self._viewer.glDraw()

    def start_stop(self):
        """
        starts and pauses the simulation
        :return:
        """
        self._running = not self._running

    def _wait_while_not_running(self):
        """
        helper function.
        waits until the running flag is set.
        :return:
        """
        sleep_time = 1.0 / 120.0
        self._process_events()
        if self.light_rotation:
            self.rotate_light()
        while not self._running:
            # sleeping for 1/120 secs, for responsive GUI
            time.sleep(sleep_time)
            if self.light_rotation:
                self.rotate_light()
            self._process_events()

    def animate(self, round_start_time, speed):
        """
        loop for animating the movement of agents and carried items
        :param round_start_time: the start of the round
        :param speed: speed of the animation in 1/steps. less or equal zero = automatic mode
        """
        if speed < 0:
            # draw at location according to the passed time and the rps
            half_round_time = (1.0 / self._rounds_per_second) / 2.0
            now = time.perf_counter()
            while (now - round_start_time) < half_round_time:
                self._viewer.set_animation_percentage(
                    min(1, (now - round_start_time) / half_round_time))
                self._process_events()
                self._viewer.glDraw()
                now = time.perf_counter()
        else:
            # draw at location according to the selected animation speed
            for i in range(1, max(1, speed)):
                self._viewer.set_animation_percentage(float(i / max(1, speed)))
                self._process_events()
                self._viewer.glDraw()

        self._viewer.set_animation_percentage(1)
        self._viewer.glDraw()

        # reset the previous position after animation.
        # not reseting it causes a visual bug if the matter didn't move.
        for agent in self._viewer.agent_offset_data:
            current_data = self._viewer.agent_offset_data[agent]
            self._viewer.agent_offset_data[agent] = (current_data[0],
                                                     current_data[1],
                                                     agent.coordinates,
                                                     current_data[3])
        for item in self._viewer.item_offset_data:
            current_data = self._viewer.item_offset_data[item]
            self._viewer.item_offset_data[item] = (current_data[0],
                                                   current_data[1],
                                                   item.coordinates,
                                                   current_data[3])
        self._viewer.agent_update_flag = True
        self._viewer.item_update_flag = True

    def run(self, round_start_timestamp):
        """
        main function for running the simulation with the visualization.
        At this time, its just error handling here.. the simulation and drawing stuff starts in the run_iteration method
        :param round_start_timestamp: timestamp of the start of the round.
        :return:
        """
        # TODO: MARK - print stuff here to find exact seg fault location
        try:
            self._run_iteration(round_start_timestamp)
        except VisualizationError as ve:
            if ve.level == Level.INFO:
                show_msg(ve.msg, ve.level, self.get_main_window())
            if ve.level == Level.CRITICAL:
                show_msg(ve.msg, ve.level, self.get_main_window())
                exit(1)
            if ve.level == Level.WARNING:
                try:
                    self._run_iteration(round_start_timestamp)
                    show_msg(ve.msg, ve.level, self.get_main_window())
                except VisualizationError as ve:
                    show_msg(ve.msg, ve.level, self.get_main_window())
                    if ve.level != Level.INFO:
                        exit(1)

    def _run_iteration(self, round_start_timestamp):
        """
        Controls the "waiting time", so the rounds_per_second value is being kept at the specified value.
        :param round_start_timestamp: timestamp of the start of the round.
        :return:
        """
        # update and draw/animate scene
        self._viewer.update_data()
        if self._animation:
            self.animate(
                round_start_timestamp,
                -1 if self._auto_animation else self._manual_animation_speed)
        else:
            self._viewer.glDraw()

        # waiting until simulation starts
        self._wait_while_not_running()

        # record round
        if self._recording:
            self.recorder.record_round()
            self._splitter.setWindowTitle("Simulator, recorded: %d rounds" %
                                          len(self.recorder.records))

        # waiting until enough time passed to do the next simulation round.
        time_elapsed = time.perf_counter() - round_start_timestamp
        # sleeping time - max 1/120 s for a responsive GUI
        sleep_time = min(1.0 / 120, (1.0 / self._rounds_per_second) / 10.0)
        max_wait_time = 1 / self._rounds_per_second
        while time_elapsed < max_wait_time:
            time.sleep(sleep_time)
            # check if still running... if not wait (important for low rounds_per_second values)
            self._wait_while_not_running()
            time_elapsed = time.perf_counter() - round_start_timestamp

    def remove_agent(self, agent):
        """
        removes an agent from the visualization.
        it wont be deleted immediately! not until the next round.
        if you want an immediate deletion of the agent, then call this function, then, update_data and after that
        glDraw of the OpenGLWidget.

        :param agent: the agent (not the id, the instance) to be deleted
        :return:
        """
        self._viewer.agent_update_flag = True
        if agent in self._viewer.agent_offset_data:
            del self._viewer.agent_offset_data[agent]

    def agent_changed(self, agent):
        """
        updates the offset, color and carry data of the agent in the visualization.
        it wont be an immediate update. it will update in the beginning of the next "run" call / after current round.
        :param agent: the agent that has changed (the instance)
        :return:
        """
        self._viewer.agent_update_flag = True
        prev_pos = agent.coordinates
        if agent in self._viewer.agent_offset_data:
            prev_pos = self._viewer.agent_offset_data[agent][0]
        self._viewer.agent_offset_data[agent] = (agent.coordinates,
                                                 agent.color, prev_pos, 1.0 if
                                                 agent.is_carried() else 0.0)

    def remove_item(self, item):
        """
        removes an item from the visualization.
        :param item: the item (not the id, the instance) to be deleted
        :return:
        """
        self._viewer.item_update_flag = True
        if item in self._viewer.item_offset_data:
            del self._viewer.item_offset_data[item]

    def item_changed(self, item):
        """
        updates the offset, color and carry data of the item in the visualization.
        :param item: the item ( not the id, the instance) to be deleted
        :return:
        """
        self._viewer.item_update_flag = True
        prev_pos = item.coordinates
        if item in self._viewer.item_offset_data:
            prev_pos = self._viewer.item_offset_data[item][0]

        self._viewer.item_offset_data[item] = (item.coordinates, item.color,
                                               prev_pos, 1.0
                                               if item.is_carried() else 0.0)

    def remove_location(self, location):
        """
        removes a location from the visualization.
        :param location: the location (not the id, the instance) to be deleted
        :return:
        """
        self._viewer.location_update_flag = True
        if location in self._viewer.location_offset_data:
            del self._viewer.location_offset_data[location]

    def location_changed(self, location):
        """
        updates the offset and color data of the location in the visualization.
        :param location: the location ( not the id, the instance) to be deleted
        :return:
        """
        self._viewer.location_update_flag = True
        self._viewer.location_offset_data[location] = (location.coordinates,
                                                       location.color)

    def update_visualization_data(self):
        self._viewer.update_data()

    # setters and getters for various variables in the visualization

    def set_rounds_per_second(self, rounds_per_second):
        self._rounds_per_second = rounds_per_second

    def get_rounds_per_second(self):
        return self._rounds_per_second

    def reset_camera_position(self):
        self._camera.reset()
        self._viewer.update_scene()

    def set_field_of_view(self, fov: float):
        self._camera.set_fov(fov)
        self._viewer.update_programs_projection_matrix()
        self._viewer.update_cursor_data()
        self._viewer.glDraw()

    def get_field_of_view(self):
        return self._camera.get_fov()

    def set_drag_sensitivity(self, s: float):
        self._viewer.drag_sensitivity = s

    def get_drag_sensitivity(self):
        return self._viewer.drag_sensitivity

    def set_zoom_sensitivity(self, s: float):
        self._viewer.zoom_sensitivity = s

    def get_zoom_sensitivity(self):
        return self._viewer.zoom_sensitivity

    def set_rotation_sensitivity(self, s: float):
        self._viewer.rotation_sensitivity = s

    def get_rotation_sensitivity(self):
        return self._viewer.rotation_sensitivity

    def get_projection_type(self):
        return self._camera.get_projection_type()

    def set_projection_type(self, projection_type):
        self._camera.set_projection_type(projection_type)
        self._viewer.update_programs_projection_matrix()
        self._viewer.glDraw()

    def get_background_color(self):
        return self._viewer.background

    def set_background_color(self, color):
        self._viewer.set_background_color(color)

    def get_grid_line_color(self):
        return self._viewer.programs["grid"].get_line_color()

    def set_grid_line_color(self, color):
        self._viewer.programs["grid"].set_line_color(color)

    def get_grid_border_color(self):
        return self._viewer.programs["grid"].get_border_color()

    def set_grid_border_color(self, color):
        self._viewer.programs["grid"].set_border_color(color)

    def get_grid_line_width(self):
        return self._viewer.programs["grid"].width

    def set_grid_line_width(self, width):
        self._viewer.programs["grid"].set_width(width)
        self._viewer.glDraw()

    def get_grid_line_scaling(self):
        return self._viewer.programs["grid"].get_line_scaling()

    def set_grid_line_scaling(self, scaling):
        self._viewer.programs["grid"].set_line_scaling(scaling)
        self._viewer.glDraw()

    def get_grid_coordinates_color(self):
        return self._viewer.programs["grid"].get_model_color()

    def set_grid_coordinates_color(self, color):
        self._viewer.programs["grid"].set_model_color(color)
        self._viewer.glDraw()

    def get_grid_coordinates_scaling(self):
        return self._viewer.programs["grid"].get_model_scaling()

    def set_grid_coordinates_scaling(self, scaling):
        self._viewer.programs["grid"].set_model_scaling(scaling)
        self._viewer.glDraw()

    def get_render_distance(self):
        return self._camera.get_render_distance()

    def set_render_distance(self, render_distance):
        self._camera.set_render_distance(render_distance)
        self._viewer.update_programs_projection_matrix()
        self._viewer.glDraw()

    def get_show_lines(self):
        return self._viewer.programs["grid"].show_lines

    def set_show_lines(self, show_lines: bool):
        self._viewer.programs["grid"].show_lines = show_lines
        self._viewer.glDraw()

    def get_show_border(self):
        return self._viewer.programs["grid"].show_border

    def set_show_border(self, show_border: bool):
        self._viewer.programs["grid"].show_border = show_border
        self._viewer.glDraw()

    def get_show_coordinates(self):
        return self._viewer.programs["grid"].show_coordinates

    def set_show_coordinates(self, show_coordinates: bool):
        self._viewer.programs["grid"].show_coordinates = show_coordinates
        self._viewer.glDraw()

    def get_show_center(self):
        return self._viewer.show_center

    def set_show_center(self, show_center: bool):
        self._viewer.show_center = show_center
        self._viewer.glDraw()

    def get_show_focus(self):
        return self._viewer.show_focus

    def set_show_focus(self, show_focus: bool):
        self._viewer.show_focus = show_focus
        self._viewer.glDraw()

    def take_screenshot(self, quick):
        self._viewer.take_screenshot(quick)

    def recalculate_grid(self, size):
        self.grid_size = size
        self._viewer.programs["grid"].update_offsets(
            self._world.grid.get_box(size))
        self._viewer.glDraw()

    def get_agent_scaling(self):
        return self._viewer.programs["agent"].get_model_scaling()

    def set_agent_scaling(self, scaling):
        self._viewer.programs["agent"].set_model_scaling(scaling)
        self._viewer.glDraw()

    def get_item_scaling(self):
        return self._viewer.programs["item"].get_model_scaling()

    def set_item_scaling(self, scaling):
        self._viewer.programs["item"].set_model_scaling(scaling)
        self._viewer.glDraw()

    def get_location_scaling(self):
        return self._viewer.programs["location"].get_model_scaling()

    def set_location_scaling(self, scaling):
        self._viewer.programs["location"].set_model_scaling(scaling)
        self._viewer.glDraw()

    def set_on_cursor_click_matter_type(self, matter_type):
        if matter_type == MatterType.ITEM or matter_type == MatterType.AGENT or matter_type == MatterType.LOCATION:
            self._viewer.cursor_type = matter_type
            self._viewer.update_cursor_data()

    def is_running(self):
        return self._running

    def get_added_matter_color(self):
        return self._viewer.added_matter_color

    def set_added_matter_color(self, color):
        self._viewer.added_matter_color = color

    def start_recording(self):
        self.recorder.record_round()
        self._splitter.setWindowTitle("Simulator, recorded: %d rounds" %
                                      len(self.recorder.records))
        self._recording = True

    def get_viewer_res(self):
        return self._viewer.width(), self._viewer.height()

    def stop_recording(self):
        self._recording = False

    def export_recording(self):
        if len(self.recorder.records) == 0:
            show_msg("No rounds recorded. Nothing to export.", Level.INFO,
                     self._splitter)
            return
        if self._running:
            self.start_stop()
        self._viewer.set_show_info_frame(False)
        self._viewer.set_enable_cursor(False)
        if "set_disable_sim" in dir(self._gui_module):
            self._gui_module.set_disable_sim(True)
        else:
            show_msg(
                "No 'set_disable_sim(disable_flag)' function in gui module found."
                "\nRunning simulation within recording mode may result in undefined behavior!",
                Level.WARNING, self._splitter)

        self.recorder.show(self.do_export)

        # loop
        while self.recorder.is_open():
            self._process_events()

        # go back to the main window
        if "set_disable_sim" in dir(self._gui_module):
            self._gui_module.set_disable_sim(False)

        self._viewer.agent_update_flag = True
        self._viewer.item_update_flag = True
        self._viewer.location_update_flag = True
        self._viewer.update_data()
        self._viewer.set_show_info_frame(True)
        self._viewer.set_enable_cursor(True)

    def do_export(self, rps, width, height, codec, first_frame_idx,
                  last_frame_idx, animation):

        if not os.path.exists("outputs/videos") or not os.path.isdir(
                "outputs/videos"):
            os.mkdir("outputs/videos")
        directory = "."
        if os.path.exists("outputs/videos") and os.path.isdir(
                "outputs/videos"):
            directory = "outputs/videos"
        path = TopQFileDialog(self._splitter).getSaveFileName(
            options=(TopQFileDialog.Options()),
            filter="*.mp4;;*.avi;;*.mkv",
            directory=directory)
        if path[0] == '':
            return

        if path[0].endswith("mp4") or path[0].endswith(
                ".avi") or path[0].endswith(".mkv"):
            fullpath = path[0]
        else:
            fullpath = path[0] + path[1].replace('*', '')

        if animation:
            animation_steps = int(30 / rps)
            if animation_steps < 1:
                animation_steps = 1
        else:
            animation_steps = 1

        writer = cv2.VideoWriter(fullpath, cv2.VideoWriter_fourcc(*codec),
                                 rps * animation_steps, (width, height))
        self._viewer.setDisabled(True)
        # creating and opening loading window
        lw = LoadingWindow("", "Exporting Video...")
        lw.show()
        out_of = (last_frame_idx - first_frame_idx + 1) * animation_steps
        for i in range(first_frame_idx - 1, last_frame_idx):
            # render and write frame
            self._viewer.inject_record_data(self.recorder.records[i])
            # animate
            for j in range(1, animation_steps + 1):
                # process events so the gui thread does respond to interactions..
                self._process_events()
                # update loading windows text and progress bar
                processing = (i - first_frame_idx + 1) * animation_steps + j
                lw.set_message("Please wait!\nExporting frame %d/%d..." %
                               (processing, out_of))
                lw.set_progress(processing, out_of)
                self._viewer.set_animation_percentage(j / animation_steps)
                self._viewer.glDraw()
                img = self._viewer.get_frame_cv(width, height)
                writer.write(img)
        self._viewer.inject_record_data(self.recorder.records[last_frame_idx -
                                                              1])
        writer.release()
        lw.close()
        self._viewer.setDisabled(False)
        self._viewer.resizeGL(self._viewer.width(), self._viewer.height())
        self._viewer.update_scene()
        show_msg("Video exported successfully!", Level.INFO, self._splitter)

    def delete_recording(self):
        self.recorder = Recorder(self._world, self._viewer)
        self._splitter.setWindowTitle("Simulator")

    def set_antialiasing(self, value):
        if value <= 0:
            self._viewer.disable_aa()
        else:
            self._viewer.enable_aa(value)

    def take_vector_screenshot(self):
        if self._world.grid.__class__.__name__ == "TriangularGrid":
            if not os.path.exists("outputs/screenshots") or not os.path.isdir(
                    "outputs/screenshots"):
                os.mkdir("outputs/screenshots")
            directory = "."
            if os.path.exists("outputs/screenshots") and os.path.isdir(
                    "outputs/screenshots"):
                directory = "outputs/screenshots"

            path = TopQFileDialog(self._splitter).getSaveFileName(
                options=(TopQFileDialog.Options()),
                filter="*.svg",
                directory=directory)
            if path[0] == '':
                return

            if path[0].endswith(".svg"):
                create_svg(self._world, path[0])
            else:
                create_svg(self._world, path[0] + ".svg")
        else:
            show_msg(
                "Not implemented yet.\nWorks only with Triangular Grid for now!\nSorry!",
                Level.WARNING, self._splitter)

    def set_animation(self, animation):
        if not animation:
            self._viewer.set_animation_percentage(1)
        self._animation = animation

    def get_animation(self):
        return self._animation

    def set_auto_animation(self, auto_animation):
        self._auto_animation = auto_animation

    def get_auto_animation(self):
        return self._auto_animation

    def set_manual_animation_speed(self, manual_animation_speed):
        self._manual_animation_speed = manual_animation_speed

    def get_manual_animation_speed(self):
        return self._manual_animation_speed

    def get_main_window(self):
        return self._splitter

    def set_reset_flag(self):
        self._reset_flag = True