Пример #1
0
 def construct(self, frame: ttk.Frame) -> None:
     self.data.setdefault("text", tk.StringVar(value="0.0"))
     self.label = ttk.Label(frame, text=self.name)
     self.entry = ValEntry(ValEntry.type_validator(float),
                           frame,
                           textvariable=self.data["text"])
     self.entry.bind("<Return>", lambda _: self._callback("enter"))
     self.label.pack(side="left")
     self.entry.pack(side="left")
Пример #2
0
class FBFloatEntry(FBWidget):
    def construct(self, frame: ttk.Frame) -> None:
        self.data.setdefault("text", tk.StringVar(value="0.0"))
        self.label = ttk.Label(frame, text=self.name)
        self.entry = ValEntry(ValEntry.type_validator(float),
                              frame,
                              textvariable=self.data["text"])
        self.entry.bind("<Return>", lambda _: self._callback("enter"))
        self.label.pack(side="left")
        self.entry.pack(side="left")

    def applyConfig(self):
        self.entry["width"] = int(self.config.setdefault("宽度", "20"))

    def rename(self, newName: str) -> None:
        self.label["text"] = newName
Пример #3
0
    def constructWithBorder(self, frame: ttk.Frame) -> None:
        self.data.setdefault("text", tk.StringVar(value="0.0"))

        topFrame = ttk.Frame(frame)
        self.label = ttk.Label(topFrame, text=self.name)
        self.entry = ValEntry(ValEntry.type_validator(float),
                              topFrame,
                              textvariable=self.data["text"])

        self.scale = ttk.Scale(frame,
                               orient="horizontal",
                               from_=0.0,
                               to=MAXVAL,
                               command=self._onScaleChange)

        rangeFrame = ttk.Frame(frame)
        self.lowLabel = ttk.Label(rangeFrame)
        self.highLabel = ttk.Label(rangeFrame)

        self.entry.bind("<Return>", lambda _: self._callback("enter"))
        self.entry.bindUpdate(self._calcScale)
        self.entry.bindUpdate(lambda _: self._callback("change"))
        self.scale.bind("<ButtonRelease-1>",
                        lambda _: self._callback("release"))

        rangeFrame.bind("<MouseWheel>", self._onMouseWheel)
        self.scale.bind("<MouseWheel>", self._onMouseWheel)

        topFrame.pack()
        self.label.pack(side="left")
        self.entry.pack(side="left")

        self.scale.pack(fill="x", expand=True)

        rangeFrame.pack(fill="x", expand=True)
        self.lowLabel.pack(side="left")
        self.highLabel.pack(side="right")
Пример #4
0
class FBScaleEntry(FBWidget):
    def constructWithBorder(self, frame: ttk.Frame) -> None:
        self.data.setdefault("text", tk.StringVar(value="0.0"))

        topFrame = ttk.Frame(frame)
        self.label = ttk.Label(topFrame, text=self.name)
        self.entry = ValEntry(ValEntry.type_validator(float),
                              topFrame,
                              textvariable=self.data["text"])

        self.scale = ttk.Scale(frame,
                               orient="horizontal",
                               from_=0.0,
                               to=MAXVAL,
                               command=self._onScaleChange)

        rangeFrame = ttk.Frame(frame)
        self.lowLabel = ttk.Label(rangeFrame)
        self.highLabel = ttk.Label(rangeFrame)

        self.entry.bind("<Return>", lambda _: self._callback("enter"))
        self.entry.bindUpdate(self._calcScale)
        self.entry.bindUpdate(lambda _: self._callback("change"))
        self.scale.bind("<ButtonRelease-1>",
                        lambda _: self._callback("release"))

        rangeFrame.bind("<MouseWheel>", self._onMouseWheel)
        self.scale.bind("<MouseWheel>", self._onMouseWheel)

        topFrame.pack()
        self.label.pack(side="left")
        self.entry.pack(side="left")

        self.scale.pack(fill="x", expand=True)

        rangeFrame.pack(fill="x", expand=True)
        self.lowLabel.pack(side="left")
        self.highLabel.pack(side="right")

    def _onMouseWheel(self, event):
        if self.editing():
            return
        if event.num == 5 or event.delta == -120:
            delta = -self.delta
        elif event.num == 4 or event.delta == 120:
            delta = self.delta
        else:
            return
        self.scale.set(max(0, min(MAXVAL, self.scale.get() + delta)))
        self._onScaleChange(self.scale.get())

    def _calcScale(self, *_):
        value = float(self.entry.get())
        self.scale.configure(value=max(
            0.0,
            min(MAXVAL, (value - self.low) / (self.high - self.low) * MAXVAL)))

    def _onScaleChange(self, scale):
        scale = round(float(scale))
        value = f"%.{self.displayPrecision}f" % (
            self.low + (self.high - self.low) * scale / MAXVAL)
        if value != self.entry.get():
            self.entry.set(value)
            self._callback("change")

    def applyConfig(self):
        self.entry["width"] = int(self.config.setdefault("宽度", "20"))
        self.low = float(self.config.setdefault("最小值", "0.0"))
        self.high = float(self.config.setdefault("最大值", "100.0"))
        self.displayPrecision = int(self.config.setdefault("显示精度", "2"))
        self.lowLabel["text"] = self.config["最小值"]
        self.highLabel["text"] = self.config["最大值"]
        self._calcScale()
        delta = float(self.config.setdefault("滚轮增量", "1.0"))
        self.delta = MAXVAL / (self.high - self.low) * delta

    def rename(self, newName: str) -> None:
        self.label["text"] = newName
Пример #5
0
    def __init__(self, isServer: bool = False):
        style = ttk.Style("cosmo")
        self._root = style.master
        self._root.title("FBImg")
        self._root.rowconfigure(0, weight=1)
        self._root.columnconfigure(0, weight=1)
        self._root.protocol("WM_DELETE_WINDOW", self.shutdown)

        self._imgCanvas = ttk.Canvas(self._root)
        self._imgCanvas.config(bg="light gray")
        self._imgCanvas.grid(row=0, column=0, sticky="nsew")
        self._imgCanvas_shape = (0, 0)

        opFrame = ttk.Frame(self._root)
        opFrame.grid(row=1, column=0, sticky="we")
        opFrame.columnconfigure(0, weight=1)

        val = lambda s: ValEntry.type_validator(int)(s) and int(s) > 0
        imgFrame = ttk.Frame(opFrame)
        imgFrame.grid(row=0, column=0, sticky="we")
        ttk.Label(imgFrame, text="宽度").pack(side="left", pady=5)
        self.wEntry = ValEntry(val, imgFrame, width=5)
        self.wEntry.pack(side="left", pady=5)
        ttk.Label(imgFrame, text="高度").pack(side="left", pady=5)
        self.hEntry = ValEntry(val, imgFrame, width=5)
        self.hEntry.pack(side="left", pady=5)
        ttk.Button(imgFrame, text="应用",
                   command=self._applySize).pack(side="left", padx=2, pady=5)

        self._pauseButton = ttk.Checkbutton(imgFrame,
                                            text="暂停",
                                            bootstyle=("success", "outline",
                                                       "toolbutton"))
        self._pauseButton.pack(side="left", padx=5, pady=5)

        ttk.Label(imgFrame, text="保存路径").pack(side="left", pady=5)
        self._dirEntry = ttk.Entry(imgFrame, width=30)
        self._dirEntry.pack(side="left", pady=5)

        ttk.Button(imgFrame, text="选择",
                   command=self._selectDir).pack(side="left", pady=5)

        self._saveButton = ttk.Button(imgFrame,
                                      text="保存",
                                      bootstyle=("success"),
                                      command=self.saveImg)
        self._saveButton.pack(side="left", padx=5, pady=5)
        self._recordButton = ttk.Checkbutton(imgFrame,
                                             text="录制",
                                             command=self._toggleRecord,
                                             bootstyle=("warning", "outline",
                                                        "toolbutton"))
        self._recordButton.pack(side="left", pady=5)
        self._cntLabel = ttk.Label(imgFrame)
        self._cntLabel.pack(side="left", pady=5)

        self._client = FBServer() if isServer else FBClient()
        self._recv = FBRawRecv()

        self._client.registerRecvCallback(self._recv.input)
        self._recv.registerRecvCallback(self.updateData)

        self._imgLock = threading.Lock()
        self._dirLock = threading.Lock()
        self.loadConfig()
Пример #6
0
class FBImgApp:
    def __init__(self, isServer: bool = False):
        style = ttk.Style("cosmo")
        self._root = style.master
        self._root.title("FBImg")
        self._root.rowconfigure(0, weight=1)
        self._root.columnconfigure(0, weight=1)
        self._root.protocol("WM_DELETE_WINDOW", self.shutdown)

        self._imgCanvas = ttk.Canvas(self._root)
        self._imgCanvas.config(bg="light gray")
        self._imgCanvas.grid(row=0, column=0, sticky="nsew")
        self._imgCanvas_shape = (0, 0)

        opFrame = ttk.Frame(self._root)
        opFrame.grid(row=1, column=0, sticky="we")
        opFrame.columnconfigure(0, weight=1)

        val = lambda s: ValEntry.type_validator(int)(s) and int(s) > 0
        imgFrame = ttk.Frame(opFrame)
        imgFrame.grid(row=0, column=0, sticky="we")
        ttk.Label(imgFrame, text="宽度").pack(side="left", pady=5)
        self.wEntry = ValEntry(val, imgFrame, width=5)
        self.wEntry.pack(side="left", pady=5)
        ttk.Label(imgFrame, text="高度").pack(side="left", pady=5)
        self.hEntry = ValEntry(val, imgFrame, width=5)
        self.hEntry.pack(side="left", pady=5)
        ttk.Button(imgFrame, text="应用",
                   command=self._applySize).pack(side="left", padx=2, pady=5)

        self._pauseButton = ttk.Checkbutton(imgFrame,
                                            text="暂停",
                                            bootstyle=("success", "outline",
                                                       "toolbutton"))
        self._pauseButton.pack(side="left", padx=5, pady=5)

        ttk.Label(imgFrame, text="保存路径").pack(side="left", pady=5)
        self._dirEntry = ttk.Entry(imgFrame, width=30)
        self._dirEntry.pack(side="left", pady=5)

        ttk.Button(imgFrame, text="选择",
                   command=self._selectDir).pack(side="left", pady=5)

        self._saveButton = ttk.Button(imgFrame,
                                      text="保存",
                                      bootstyle=("success"),
                                      command=self.saveImg)
        self._saveButton.pack(side="left", padx=5, pady=5)
        self._recordButton = ttk.Checkbutton(imgFrame,
                                             text="录制",
                                             command=self._toggleRecord,
                                             bootstyle=("warning", "outline",
                                                        "toolbutton"))
        self._recordButton.pack(side="left", pady=5)
        self._cntLabel = ttk.Label(imgFrame)
        self._cntLabel.pack(side="left", pady=5)

        self._client = FBServer() if isServer else FBClient()
        self._recv = FBRawRecv()

        self._client.registerRecvCallback(self._recv.input)
        self._recv.registerRecvCallback(self.updateData)

        self._imgLock = threading.Lock()
        self._dirLock = threading.Lock()
        self.loadConfig()

    def _toggleRecord(self, *_):
        if self._recordButton.instate(["selected"]):
            self._saveButton.config(state="disabled")
            self._recordButton.config(text="停止")
        else:
            self._saveButton.config(state="!disabled")
            self._recordButton.config(text="录制")

    def _updateDir(self):
        os.makedirs(self.save_dir, exist_ok=True)
        self._dirEntry.config(state="normal")
        self._dirEntry.delete(0, tk.END)
        self._dirEntry.insert(0, self.save_dir)
        self._dirEntry.config(state="readonly")

    def _selectDir(self, *_):
        with self._dirLock:
            cur = self.save_dir
        s = filedialog.askdirectory(master=self._root,
                                    initialdir=cur,
                                    title="选择保存路径",
                                    mustexist=False)
        if not s:
            return
        with self._dirLock:
            self.save_dir = s
        self._updateDir()

    def saveImg(self, *_):
        def save():
            with self._imgLock:
                img = self.img
            with self._dirLock:
                Dir = self.save_dir
            name = datetime.datetime.now().strftime(
                "%Y-%m-%d-%H-%M-%S-%f") + SAVE_EXT
            cv.imwrite(os.path.join(Dir, name), img)

        threading.Thread(target=save).start()

    def updateData(self, data: bytes) -> None:
        if self._pauseButton.instate(["selected"]):
            return
        with self._imgLock:
            w, h = self.w, self.h
        img = np.frombuffer(data, dtype=np.uint8).reshape((h, w))
        with self._imgLock:
            if w != self.w or h != self.h:
                return
            self.img = img
            self._updateFlag = True
        if self._recordButton.instate(["selected"]):
            self.saveImg()

    def _applySize(self):
        with self._imgLock:
            self.w, self.h = int(self.wEntry.get()), int(self.hEntry.get())
            self.img = np.zeros((self.h, self.w), dtype=np.uint8)
            self._updateFlag = True
        self._recv.setConfig(cnt=self.w * self.h)

    def loadConfig(self):
        os.makedirs(CFG_DIR, exist_ok=True)
        cfg = {}
        if os.path.exists(CFG_PATH):
            with open(CFG_PATH, "r") as f:
                cfg = json.load(f)
        self._root.geometry(cfg.get("geometry", "876x600+30+30"))
        self.w = cfg.get("w", 752)
        self.h = cfg.get("h", 480)
        self.wEntry.set(str(self.w))
        self.hEntry.set(str(self.h))
        self.save_dir = cfg.get("save_dir", SAVE_DIR)
        self._updateDir()
        self._applySize()

    def saveConfig(self):
        cfg = {
            "geometry": self._root.geometry(),
            "w": self.w,
            "h": self.h,
            "save_dir": self.save_dir
        }
        with open(CFG_PATH, "w") as f:
            json.dump(cfg, f)

    def create_image(self):
        canvas_w, canvas_h = self._imgCanvas.winfo_width(
        ), self._imgCanvas.winfo_height()
        with self._imgLock:
            if (canvas_w, canvas_h
                ) == self._imgCanvas_shape and not self._updateFlag:
                return
            img = self.img
            self._updateFlag = False
        self._imgCanvas_shape = (canvas_w, canvas_h)

        img_w, img_h = img.shape[1], img.shape[0]
        canvas_k, img_k = canvas_w / canvas_h, img_w / img_h
        w, h = (canvas_w,
                round(canvas_w /
                      img_k)) if canvas_k < img_k else (round(canvas_h *
                                                              img_k), canvas_h)
        resized_img = cv.resize(img, (max(1, w), max(1, h)),
                                interpolation=cv.INTER_NEAREST)
        cvt_img = resized_img
        self._imgTK = ImageTk.PhotoImage(master=self._imgCanvas,
                                         image=Image.fromarray(cvt_img))
        self._imgCanvas.create_image(canvas_w >> 1,
                                     canvas_h >> 1,
                                     image=self._imgTK)

    def _create_image_task(self):
        if self._running:
            self.create_image()
        if self._running:
            self._imgCanvas.after(50, self._create_image_task)

    def mainloop(self):
        self._running = True
        self._updateFlag = True
        self._create_image_task()
        self._client.start()
        self._recv.start()
        self._root.mainloop()

    def shutdown(self):
        self._running = False
        self._recv.shutdown()
        self._client.shutdown()
        self.saveConfig()
        self._root.destroy()
Пример #7
0
    def __init__(self,
                 master=None,
                 is_vertical=True,
                 queue_size: int = 10,
                 **kw):
        super().__init__(master, **kw)
        self._recv = FBFloatRecv(queue_size=queue_size)
        self.input = self._recv.input
        self.registerRecvCallback = self._recv.registerRecvCallback
        self.start = self._recv.start

        idLabel = ttk.Label(self, text="ID")
        self._idCbo = ttk.Combobox(self,
                                   width=4,
                                   state="readonly",
                                   values=["无"] + list(range(256)))
        self._idCbo.current(0)
        self._idCbo.bind("<<ComboboxSelected>>", self._applyID)

        cntLabel = ttk.Label(self, text="个数")
        self._cntEntry = ValEntry(lambda s: s.isdigit() and int(s) > 0,
                                  self,
                                  width=5)

        bitsLabel = ttk.Label(self, text="位数")
        self._bitsCbo = ttk.Combobox(self,
                                     width=4,
                                     state="readonly",
                                     values=[4, 8])
        self._bitsCbo.current(0)

        checkLabel = ttk.Label(self, text="校验")
        self._checkCbo = ttk.Combobox(self,
                                      width=4,
                                      state="readonly",
                                      values=["sum", "none"])
        self._checkCbo.current(0)

        extra = self.extraBody(self)

        self._cbs: callable = []
        applyButton = ttk.Button(self, text="应用", command=self._clickCB)

        if is_vertical:
            idLabel.grid(row=0, column=0, sticky="w")
            self._idCbo.grid(row=0, column=1, sticky="w")
            cntLabel.grid(row=1, column=0, sticky="w")
            self._cntEntry.grid(row=1, column=1, sticky="we")
            bitsLabel.grid(row=2, column=0, sticky="w")
            self._bitsCbo.grid(row=2, column=1, sticky="we")
            checkLabel.grid(row=3, column=0, sticky="w")
            self._checkCbo.grid(row=3, column=1, sticky="we")
            if extra is not None:
                extra.grid(row=4, column=0, columnspan=2, sticky="we")
            applyButton.grid(row=4 + (extra is not None),
                             column=0,
                             columnspan=2,
                             sticky="we")
        else:
            idLabel.pack(side="left")
            self._idCbo.pack(side="left", padx=2)
            cntLabel.pack(side="left")
            self._cntEntry.pack(side="left", padx=2)
            bitsLabel.pack(side="left")
            self._bitsCbo.pack(side="left", padx=2)
            checkLabel.pack(side="left")
            self._checkCbo.pack(side="left", padx=2)
            if extra is not None:
                extra.pack(side="left")
            applyButton.pack(side="left", padx=2)

        self.loadConfig()
Пример #8
0
class FBFloatRecvGUI(ttk.Frame):
    def __init__(self,
                 master=None,
                 is_vertical=True,
                 queue_size: int = 10,
                 **kw):
        super().__init__(master, **kw)
        self._recv = FBFloatRecv(queue_size=queue_size)
        self.input = self._recv.input
        self.registerRecvCallback = self._recv.registerRecvCallback
        self.start = self._recv.start

        idLabel = ttk.Label(self, text="ID")
        self._idCbo = ttk.Combobox(self,
                                   width=4,
                                   state="readonly",
                                   values=["无"] + list(range(256)))
        self._idCbo.current(0)
        self._idCbo.bind("<<ComboboxSelected>>", self._applyID)

        cntLabel = ttk.Label(self, text="个数")
        self._cntEntry = ValEntry(lambda s: s.isdigit() and int(s) > 0,
                                  self,
                                  width=5)

        bitsLabel = ttk.Label(self, text="位数")
        self._bitsCbo = ttk.Combobox(self,
                                     width=4,
                                     state="readonly",
                                     values=[4, 8])
        self._bitsCbo.current(0)

        checkLabel = ttk.Label(self, text="校验")
        self._checkCbo = ttk.Combobox(self,
                                      width=4,
                                      state="readonly",
                                      values=["sum", "none"])
        self._checkCbo.current(0)

        extra = self.extraBody(self)

        self._cbs: callable = []
        applyButton = ttk.Button(self, text="应用", command=self._clickCB)

        if is_vertical:
            idLabel.grid(row=0, column=0, sticky="w")
            self._idCbo.grid(row=0, column=1, sticky="w")
            cntLabel.grid(row=1, column=0, sticky="w")
            self._cntEntry.grid(row=1, column=1, sticky="we")
            bitsLabel.grid(row=2, column=0, sticky="w")
            self._bitsCbo.grid(row=2, column=1, sticky="we")
            checkLabel.grid(row=3, column=0, sticky="w")
            self._checkCbo.grid(row=3, column=1, sticky="we")
            if extra is not None:
                extra.grid(row=4, column=0, columnspan=2, sticky="we")
            applyButton.grid(row=4 + (extra is not None),
                             column=0,
                             columnspan=2,
                             sticky="we")
        else:
            idLabel.pack(side="left")
            self._idCbo.pack(side="left", padx=2)
            cntLabel.pack(side="left")
            self._cntEntry.pack(side="left", padx=2)
            bitsLabel.pack(side="left")
            self._bitsCbo.pack(side="left", padx=2)
            checkLabel.pack(side="left")
            self._checkCbo.pack(side="left", padx=2)
            if extra is not None:
                extra.pack(side="left")
            applyButton.pack(side="left", padx=2)

        self.loadConfig()

    def extraBody(self, master: ttk.Frame) -> ttk.Frame:
        return None

    def registerClickCallback(self, func: callable):
        self._cbs.append(func)

    def _clickCB(self):
        self._applyConfig()
        for cb in self._cbs:
            cb()

    def getID(self):
        return self._idCbo.current() - 1

    def getCnt(self):
        return int(self._cntEntry.get())

    def _applyConfig(self):
        id = self.getID()
        self.cfg["id"] = str(id)
        cfg = self.cfg["cfg"][str(id)]
        cnt = cfg["cnt"] = self.getCnt()
        bits = cfg["bits"] = int(self._bitsCbo.get())
        check = cfg["check"] = self._checkCbo.get()
        self._recv.setConfig(id=id,
                             cnt=cnt,
                             bits=bits,
                             checksum=check == "sum")

    @staticmethod
    def _getDefualtCfg():
        return {"cnt": 1, "bits": 4, "check": "sum"}

    def _applyID(self, *_):
        cfg = self.cfg["cfg"].setdefault(str(self.getID()),
                                         self._getDefualtCfg())
        self._cntEntry.set(str(cfg["cnt"]))
        self._bitsCbo.current(0 if cfg["bits"] == 4 else 1)
        self._checkCbo.current(0 if cfg["check"] == "sum" else 1)

    def loadConfig(self):
        os.makedirs(CFG_DIR, exist_ok=True)
        self.cfg = {}
        if os.path.exists(CFG_PATH):
            with open(CFG_PATH, "r") as f:
                self.cfg = json.load(f)
        id = self.cfg.setdefault("id", "-1")
        self._idCbo.current(int(id) + 1)
        self.cfg.setdefault("cfg", {})
        self._applyID()
        self._applyConfig()

    def saveConfig(self):
        res = {"id": self.cfg["id"], "cfg": {}}
        defaultCfg = self._getDefualtCfg()
        for id, cfg in self.cfg["cfg"].items():
            if cfg != defaultCfg:
                res["cfg"][id] = cfg

        with open(CFG_PATH, "w") as f:
            json.dump(res, f)

    def shutdown(self):
        self.saveConfig()
        self._recv.shutdown()