view client/client.py @ 3:38725950fe15

Make graphs moving. There are some weird issues with matplotlib rendering eating up all memory.
author Lewin Bormann <lbo@spheniscida.de>
date Thu, 28 Feb 2019 20:01:03 +0100
parents 61952d5a8718
children 7a722bb179ff
line wrap: on
line source

#!/usr/bin/env python3

import arguments
import os
import os.path as path
import sys
import zmq

import PyQt5.QtWidgets as wid
import PyQt5.QtCore as core

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

class Creds:
    user = ''
    password = ''
    addr = ''

class ClientConfigDialog(wid.QDialog):
    addr = None
    user = None
    password = None
    defaults = False

    def __init__(self, parent, defaults=False):
        super().__init__(parent)
        self.defaults = defaults

    def show_dialog(self):
        vbox = wid.QVBoxLayout(self)
        
        self.addr = wid.QLineEdit()
        self.addr.readOnly = False
        self.addr.setText(self.load_default('addr'))
        self.user = wid.QLineEdit()
        self.user.readOnly = False
        self.user.setText(self.load_default('user'))
        self.password = wid.QLineEdit()
        self.password.readOnly = False
        self.password.setText(self.load_default('password'))
        self.password.setEchoMode(wid.QLineEdit.Password)

        if self.defaults and self.addr.text() and self.user.text() and self.password.text():
            self.finished.emit(1)
            self.accepted.emit()
            return

        hbox_addr = wid.QHBoxLayout()
        hbox_addr.addWidget(wid.QLabel('Address of server: '))
        hbox_addr.addWidget(self.addr)

        hbox_user = wid.QHBoxLayout()
        hbox_user.addWidget(wid.QLabel('Username: '))
        hbox_user.addWidget(self.user)

        hbox_password = wid.QHBoxLayout()
        hbox_password.addWidget(wid.QLabel('Password: '))
        hbox_password.addWidget(self.password)

        ok = wid.QPushButton('OK')
        ok.clicked.connect(self.ok_clicked)
        ok.clicked.connect(super().done)
        vbox.addLayout(hbox_addr)
        vbox.addLayout(hbox_user)
        vbox.addLayout(hbox_password)
        vbox.addWidget(ok)

        self.setVisible(True)


    default_addr_file = '.config/stex/'

    def load_default(self, id):
        try:
            home = os.environ.get('HOME')
            if not home:
                home = path.join('/home/', os.environ.get('USER'))
            with open(path.join(home, self.default_addr_file, id), 'r') as f:
                return f.readline()
        except Exception as e:
            sys.stderr.write("Couldn't read file: {}\n".format(e))
            return ''
    
    def save_default(self, id, val):
        try:
            home = os.environ.get('HOME')
            if not home:
                home = path.join('/home/', os.environ.get('USER'))
            da = path.join(home, self.default_addr_file, id)
            os.makedirs(path.dirname(da), exist_ok=True)
            f = open(da, 'w' if os.access(da, os.F_OK) else 'x')
            f.write(val)
        except Exception as e:
            sys.stderr.write("Couldn't write file: {}\n".format(e))
            return

    def ok_clicked(self):
        self.save_default('addr', self.addr.text())
        self.save_default('user', self.user.text())
        self.save_default('password', self.password.text())

    def creds(self):
        creds = Creds()
        creds.user = self.user.text()
        creds.password = self.password.text()
        creds.addr = self.addr.text()
        return creds

class StockGraph(FigureCanvas):
    fig = None
    axes = None
    sym = ''

    MAX_LEN = 500
    XAXIS = [i for i in range(0, MAX_LEN)]
    history = [0 for i in range(0, MAX_LEN)]

    def __init__(self, sym, dim):
        self.sym = sym
        self.fig = Figure(figsize=(dim or (200,100)), dpi=90)
        self.axes = self.fig.add_subplot(111)

        super().__init__(self.fig)
        super().setSizePolicy(wid.QSizePolicy.Expanding, wid.QSizePolicy.Expanding)
        super().updateGeometry()

    def update_stock(self, value):
        self.history.append(value/100)
        self.history[0:] = self.history[1:]
        self.plot()

    def plot(self):
        self.axes.clear()
        self.axes.grid(True)
        self.axes.set_xlim(0, self.MAX_LEN)
        self.axes.set_title(self.sym)
        self.axes.plot(self.XAXIS, self.history)
        self.draw_idle()
        print('DEBUG: Would have drawn')

class StockWidget(wid.QWidget):
    graph = None

    def __init__(self, graph):
        super().__init__()
        self.graph = graph
        graph.show()
        #self.update(5000)

        mainvbox = wid.QVBoxLayout(self)
        mainvbox.addWidget(self.graph)
        mainvbox.addLayout(self.init_buttonbox())
        self.show()

    def init_buttonbox(self):
        buy = wid.QPushButton('  BUY   ')
        sell = wid.QPushButton('  SELL  ')
        self.current_state = wid.QLineEdit()
        self.current_state.setReadOnly(True)
        self.current_state.setAlignment(core.Qt.AlignCenter)

        hbox = wid.QHBoxLayout()
        hbox.addWidget(buy)
        hbox.addWidget(sell)
        hbox.addWidget(self.current_state)
        return hbox

    def update(self, val):
        print('DEBUG: Update {} with {}'.format(self.graph.sym, val))
        self.graph.update_stock(val)
        self.mainvbox.adjustSize()
        self.current_state.setText('{} pc / {} ø/pc / {} ø'.format('?', val/100, val/100))

class ClientSocket:
    zctx = None
    sock = None
    callback = None
    socknot = None

    def __init__(self, creds, callback):
        """callback is a function taking received data dicts."""
        self.callback = callback
        self.zctx = zmq.Context()
        self.sock = self.zctx.socket(zmq.SUB)
        self.sock.setsockopt(zmq.IPV6, 1)
        self.sock.subscribe('')
        self.sock.connect('tcp://{}'.format(creds.addr))
        self.sock.setsockopt(zmq.RCVTIMEO, 0)
        fd = self.sock.getsockopt(zmq.FD)
        self.socknot = core.QSocketNotifier(fd, core.QSocketNotifier.Read)
        self.socknot.activated.connect(self.on_activated)

    def on_activated(self, _sock):
        try:
            while True:
                msg = self.sock.recv_json()
                print('DEBUG: received something')
                if '_stockdata' not in msg:
                    return
                self.callback(msg)
        except Exception:
            return

class Client(arguments.BaseArguments, wid.QWidget):
    _doc = """
    Usage:
        stex [options]

    Options
        --defaults      Use cached defaults if available.
    """

    creds = Creds()

    def __init__(self):
        super(wid.QWidget, self).__init__()
        super(arguments.BaseArguments, self).__init__(doc=self._doc)

        ccd = ClientConfigDialog(self, defaults=self.defaults)
        ccd.accepted.connect(lambda: self.set_creds(ccd.creds()))
        ccd.accepted.connect(self.start_wait_window)
        ccd.show_dialog()

    def set_creds(self, creds):
        self.creds = creds

    def start_wait_window(self):
        self.sock = ClientSocket(self.creds, self.on_new_data)
        print('DEBUG: Started wait')

    stock_widgets = {}

    def on_new_data(self, stockdata):
        print('DEBUG: ' + str(stockdata))
        for sym, val in sorted(stockdata.items()):
            if sym in self.stock_widgets:
                self.stock_widgets[sym].update(val)
            elif sym != '_stockdata':
                sw = StockWidget(StockGraph(sym, None))
                self.stock_widgets[sym] = sw
                self.add_stock_widget(sw)

    main_window_active = False
    mainvbox = None
    hboxes = []
    widgets_per_hbox = 2

    def add_stock_widget(self, sw):
        if not self.main_window_active:
            self.start_main_window()
        if len(self.hboxes) == 0 or self.hboxes[-1].count() >= self.widgets_per_hbox:
            hbox = wid.QHBoxLayout()
            sw.show()
            hbox.addWidget(sw)
            self.hboxes.append(hbox)
            self.mainvbox.addLayout(hbox)
        else:
            self.hboxes[-1].addWidget(sw)
        return

    def start_main_window(self):
        print('DEBUG: started main window')
        self.mainvbox = wid.QVBoxLayout(self)
        self.main_window_active = True
        self.show()

def main():
    app = wid.QApplication(sys.argv)
    client = Client()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()