commit 1b280bfafce57a0a91a005494090d302c64ab886 Author: kicer Date: Fri Jul 26 21:14:07 2019 +0800 Init scomm project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/scomm.json b/scomm.json new file mode 100755 index 0000000..9281145 --- /dev/null +++ b/scomm.json @@ -0,0 +1,223 @@ +{ + "Button":[ + { + "name":"btn-scan", + "text":"串口设置", + "column": 1, + "weight": 0, + "row": 1 + }, + { + "name":"btn-onoff", + "text":"打开串口", + "column": 5, + "row": 1 + }, + { + "name":"btn-clear", + "text":"清除窗口", + "column": 1, + "row": 3 + }, + { + "name":"btn-sendfile", + "text":"发送文件", + "column": 1, + "row": 4 + }, + { + "name":"btn-pack01", + "text":"pack-01", + "column": 2, + "row": 5 + }, + { + "name":"btn-pack02", + "text":"pack-02", + "column": 3, + "row": 5 + }, + { + "name":"btn-pack03", + "text":"pack-03", + "column": 4, + "row": 5 + }, + { + "name":"btn-pack04", + "text":"pack-04", + "column": 5, + "row": 5 + }, + { + "name":"btn-pack05", + "text":"pack-05", + "column": 6, + "row": 5 + }, + { + "name":"btn-send", + "text":"发送", + "column": 10, + "row": 6 + } + ], + "Label":[ + { + "name":"empty-01", + "text":" ", + "column": 9, + "colweight": 1, + "row": 1 + }, + { + "name":"label-sscript", + "text":"组帧脚本:", + "column": 1, + "row": 5 + } + ], + "Checkbutton":[ + { + "name":"ckbtn-rhex", + "text":"HEX显示", + "sticky": "w", + "column": 2, + "row": 3 + }, + { + "name":"ckbtn-shex", + "text":"HEX发送", + "sticky": "w", + "column": 2, + "row": 4 + }, + { + "name":"ckbtn-0d", + "text":"追加\\r", + "sticky": "w", + "column": 3, + "row": 4 + }, + { + "name":"ckbtn-0a", + "text":"追加\\n", + "sticky": "w", + "column": 4, + "row": 4 + } + ], + "Entry": [ + { + "name":"entry-com", + "bg":"gold", + "width":20, + "column": 2, + "columnspan": 2, + "row": 1 + }, + { + "name":"entry-baud", + "bg":"gold", + "width":10, + "column": 4, + "row": 1 + }, + { + "name":"entry-sendText", + "bg":"gold", + "column": 1, + "columnspan": 9, + "row": 6 + } + ], + "Text":{ + "name":"text-recv", + "bg":"#cccccc", + "column": 1, + "columnspan": 9, + "rowweight": 1, + "row": 2 + }, + "Frame":{ + "name":"frame-opBtns", + "row":2, + "column": 10, + "Label":[ + { + "name":"label-rscript", + "text":"解帧脚本:", + "column": 2, + "row": 1 + }, + { + "name":"label-data", + "text":"预置数据:", + "column": 2, + "row": 7 + } + ], + "Button":[ + { + "name":"btn-unpack01", + "text":"unpack-01", + "column": 2, + "row": 2 + }, + { + "name":"btn-unpack02", + "text":"unpack-02", + "column": 2, + "row": 3 + }, + { + "name":"btn-unpack03", + "text":"unpack-03", + "column": 2, + "row": 4 + }, + { + "name":"btn-unpack04", + "text":"unpack-04", + "column": 2, + "row": 5 + }, + { + "name":"btn-unpack05", + "text":"unpack-05", + "column": 2, + "row": 6 + }, + { + "name":"btn-data01", + "text":"data-01", + "column": 2, + "row": 8 + }, + { + "name":"btn-data02", + "text":"data-02", + "column": 2, + "row": 9 + }, + { + "name":"btn-data03", + "text":"data-03", + "column": 2, + "row": 10 + }, + { + "name":"btn-data04", + "text":"data-04", + "column": 2, + "row": 11 + }, + { + "name":"btn-data05", + "text":"data-05", + "column": 2, + "row": 12 + } + ] + } +} diff --git a/scomm.py b/scomm.py new file mode 100755 index 0000000..e6fc189 --- /dev/null +++ b/scomm.py @@ -0,0 +1,8 @@ +#! /usr/bin/env python3 + +import tkgen.gengui + +if __name__ == '__main__': + root = tkgen.gengui.TkJson('scomm.json', title='scomm串口调试助手') + #root = tkgen.gengui.TkJson('rsa_ui.json', title='scomm串口调试助手') + root.mainloop() diff --git a/tkgen/gengui.py b/tkgen/gengui.py new file mode 100755 index 0000000..f5bf6b2 --- /dev/null +++ b/tkgen/gengui.py @@ -0,0 +1,468 @@ +# +# Copyright (c) 2011. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA +# +""" +Module for easy creation of Tkinter GUIs. + +Created on Apr 21, 2011 + +@author: tmetsch +""" + +try: + # python 3 + import tkinter +except ImportError: + # python 2 + import Tkinter as tkinter + +try: + # python 3 + from tkinter import ttk +except ImportError: + # python 2 + import ttk + +import json +import os +import traceback +import yaml + + +def _contains_dict(items): + """ + Checks if a set of items contains a dictionary. + + items -- The set of items to test. + """ + result = False + for item in items.keys(): + if isinstance(items[item], dict): + result = True + break + return result + + +def _contains_list(items): + """ + Checks if a set of items contains a list. + + items -- The set of items to test. + """ + result = False + for item in items.keys(): + if isinstance(items[item], list) and item is not item.lower(): + # the .lower check ensures that I can have attribute lists + result = True + break + return result + + +class TkJson(tkinter.Tk): + """ + Simple class which wraps Tk and uses some JSON to contruct a GUI. + """ + + menu = None + widgets = {} + + def __init__(self, filename, title='Tk', prefer_tk=True, file_type='json'): + """ + Initialize a Tk root and created the UI from a JSON file. + + Returns the Tk root. + """ + # Needs to be done this way - because base class do not derive from + # object :-( + tkinter.Tk.__init__(self) + self.prefer_tk = prefer_tk + self.title(title) + + if file_type == 'json': + user_interface = json.load(open(filename)) if os.path.isfile( + filename) else json.loads(filename) + elif file_type == 'yaml': + user_interface = yaml.load(open(filename)) if os.path.isfile( + filename) else yaml.loads(filename) + else: + raise ValueError('Only json or yaml file definitions are' + 'supported.') + + self.create_widgets(self, user_interface) + + def create_widgets(self, parent, items): + """ + Creates a set of Tk widgets. + """ + for name in items.keys(): + current = items[name] + if isinstance(current, dict) and not _contains_list( + current) and not _contains_dict(current): + self._create_widget(name, parent, current) + + elif isinstance(current, dict) and _contains_list(current): + widget = self._create_widget(name, parent, current) + if not widget: + break + + self.create_widgets(widget, current) + elif isinstance(current, dict) and _contains_dict(current): + widget = self._create_widget(name, parent, current) + if not widget: + break + + self.create_widgets(widget, current) + elif isinstance(current, list): + for item in current: + self.create_widgets(parent, {name: item}) + + def _create_widget(self, name, parent, desc): + """ + Tries to resolve the widget from Tk and create it. + + Returns the newly created widget. + + name -- Name of the widget. + parent -- The parent widget. + desc -- Dictionary containing the description for this widget. + """ + position, weight, padding, opt = self._get_options(desc) + + try: + widget_factory = getattr( + tkinter, name) if self.prefer_tk else getattr(ttk, name) + except AttributeError: + try: + widget_factory = getattr( + ttk, name) if self.prefer_tk else getattr(tkinter, name) + except AttributeError: + traceback.print_exc() + raise AttributeError( + 'Neither Tkinter nor ttk have a widget named: ', name) + + while True: + try: + widget = widget_factory(parent, **opt) + break + except Exception as exp: + if len(opt) == 0: + break + del opt[str(exp).split()[2][2:-1]] + # widget = widget_factory(parent,**opt) + + # todo: tkinter Label for debug + #tkinter.Label(text=position[0],relief=tkinter.SUNKEN).grid(row=position[0],column=0,sticky='news') + #tkinter.Label(text=position[1],relief=tkinter.SUNKEN).grid(row=0,column=position[1],sticky='news') + widget.grid(row=position[0], + column=position[1], + columnspan=weight[0], + rowspan=weight[1], + sticky=padding[2], + padx=padding[0], + pady=padding[1]) + + # propaget size settings when needed. + if 'width' in opt or 'height' in opt: + widget.grid_propagate(0) + + # set parent weight of the cells + if weight[2] > 0: + parent.rowconfigure(position[0], weight=weight[2]) + if weight[3] > 0: + parent.columnconfigure(position[1], weight=weight[3]) + + self.widgets[widget._name] = widget + + return widget + + def _get_options(self, dictionary): + """ + Extracts the needed options from a dictionary. + + dictionary -- Dictionary with all the options in it. + """ + options = {} + + row = 0 + column = 0 + + colspan = 1 + rowspan = 1 + rowweight = 0 + colweight = 0 + + padx = 2 + pady = 2 + + sticky = 'news' + + if 'row' in dictionary: + row = dictionary['row'] + dictionary.pop('row') + if 'column' in dictionary: + column = dictionary['column'] + dictionary.pop('column') + + if 'columnspan' in dictionary: + colspan = dictionary['columnspan'] + dictionary.pop('columnspan') + if 'rowspan' in dictionary: + rowspan = dictionary['rowspan'] + dictionary.pop('rowspan') + if 'rowweight' in dictionary: + rowweight = dictionary['rowweight'] + dictionary.pop('rowweight') + if 'colweight' in dictionary: + colweight = dictionary['colweight'] + dictionary.pop('colweight') + if 'weight' in dictionary: + colweight = dictionary['weight'] + rowweight = dictionary['weight'] + dictionary.pop('weight') + + if 'padx' in dictionary: + padx = dictionary['padx'] + dictionary.pop('padx') + if 'pady' in dictionary: + pady = dictionary['pady'] + dictionary.pop('pady') + if 'sticky' in dictionary: + sticky = dictionary['sticky'] + dictionary.pop('sticky') + + for key in dictionary.keys(): + if not isinstance(dictionary[key], + dict) and not isinstance(dictionary[key], list): + options[str(key)] = str(dictionary[key]) + elif isinstance(dictionary[key], list) and key == key.lower(): + # so we have an attribute list... + options[str(key)] = dictionary[key] + + return [row, column], [colspan, rowspan, rowweight, colweight], \ + [padx, pady, sticky], options + + ## + # Rest is public use :-) + ## + + def button(self, name, cmd, focus=False): + """ + Associate a Tk widget with a function. + + name -- Name of the widget. + cmd -- The command to trigger. + focus -- indicates wether this item has the focus. + """ + item = self.get(name) + item.config(command=cmd) + + if focus: + item.focus_set() + + def checkbox(self, name, focus=False): + """ + Associates a IntVar with a checkbox. + + name -- Name of the Checkbox. + focus -- indicates wether this item has the focus. + """ + var = tkinter.IntVar() + item = self.get(name) + item.config(variable=var) + + if focus: + item.focus_set() + + return var + + def entry(self, name, key=None, cmd=None, focus=False): + """ + Returns the text of a TK widget. + + name -- Name of the Tk widget. + key -- Needed if a key should be bound to this instance. + cmd -- If key is defined cmd needs to be defined. + focus -- Indicates wether this entry should take focus. + """ + var = tkinter.StringVar() + + item = self.get(name) + item.config(textvariable=var) + + if focus: + item.focus_set() + + if key is not None and cmd is not None: + item.bind(key, cmd) + + return var + + def label(self, name): + """ + Associate a StringVar with a label. + + name -- Name of the Label. + """ + var = tkinter.StringVar() + item = self.get(name) + item.config(textvariable=var) + return var + + def get(self, name): + """ + Find a Tk widget by name and return it. + """ + if name in self.widgets.keys(): + return self.widgets[name] + else: + raise KeyError('Widget with the name ` ' + name + ' ` not found.') + + def xscroll(self, widget_name, scrollbar_name): + """ + Add a horizontal scrollbar to a widget. + + widget_name -- name of the widget. + scollbar_name -- name of the scrollbar. + """ + widget = self.get(widget_name) + scrollbar = self.get(scrollbar_name) + + widget.config(xscrollcommand=scrollbar.set) + scrollbar.config(command=widget.xview) + + def yscroll(self, widget_name, scrollbar_name): + """ + Add a vertical scrollbar to a widget. + + widget_name -- name of the widget. + scollbar_name -- name of the scrollbar. + """ + widget = self.get(widget_name) + scrollbar = self.get(scrollbar_name) + + widget.config(yscrollcommand=scrollbar.set) + scrollbar.config(command=widget.yview) + + def create_menu(self, commands, name=None, parent=None, popup=False): + """ + Creates a menu(entry) if non is available. Returns the created menu so + you can define submenus. + + commands -- dict with 'label':'command' structure. + name -- Needs to be provided if it is a dropdown or submenu. + parent -- Needs to be provided if it is a submenu. + popup -- indicates if it is an popup menu or not (Default: False) + """ + if self.menu is None and popup is False: + # If no menu exists create one and add it to the Tk root. + self.menu = tkinter.Menu(self, tearoff=0) + self.config(menu=self.menu) + + if name is None and parent is None and popup is False \ + and len(commands.keys()) > 0: + # Just create a Menu entry. + for key in commands: + self.menu.add_command(label=key, command=commands[key]) + return self.menu + elif name is not None and popup is False and len(commands.keys()) > 0: + if parent is None: + # Create a top-level drop down menu. + tmp_menu = tkinter.Menu(self.menu, tearoff=0) + self.menu.add_cascade(label=name, menu=tmp_menu) + else: + # Create a submenu. + tmp_menu = tkinter.Menu(parent, tearoff=0) + parent.add_cascade(label=name, menu=tmp_menu) + + for key in commands: + tmp_menu.add_command(label=key, command=commands[key]) + + return tmp_menu + elif popup is True and len(commands.keys()) > 0: + tmp_menu = tkinter.Menu(self, tearoff=0) + for key in commands: + tmp_menu.add_command(label=key, command=commands[key]) + + return tmp_menu + else: + raise AttributeError('Invalid parameters provided') + + # Move? + + def create_from_file(self, parent, filename): + """ + Create a set of widgets and add them to the given parent. + + parent -- The parent of the to be created widgets. + filename -- The JSON definition file. + """ + ui_file = open(filename) + definition = json.load(ui_file) + self.create_widgets(parent, definition) + + def notebook(self, parent, filename, name='Tab'): + """ + Add a tab to a tkk notebook widget. + + parent -- The parent notebook widget instance. + filename -- The file which describes the content of the tab. + name -- The name of the tab. + """ + frame = tkinter.Frame() + self.create_from_file(frame, filename) + parent.add(frame, text=name) + + def toplevel(self, filename, title='Dialog'): + """ + Open a Toplevel widget. + + parent -- The parent notebook widget instance. + title -- The title for the dialog. + """ + dialog = tkinter.Toplevel() + dialog.title(title) + self.create_from_file(dialog, filename) + dialog.grid() + return dialog + + def treeview(self, treeview, name, values, parent='', index=0): + """ + Adds an item to a treeview. + + treeview -- The treeview to add the items to. + name -- The name of the value. + values -- The values itself. + parent -- Default will create root items, if specified create a leaf. + index -- If index < current # of items - insert at the top. + """ + return treeview.insert(parent, index, text=name, values=values) + + +class TkYaml(TkJson): + """ + Wrapper class for parsing yaml files. + """ + + def __init__(self, filename, title='Tk', prefer_tk=True): + """ + Initialize a Tk root and created the UI from a YAML file. + + Returns the Tk root. + """ + super(TkYaml, self).__init__(filename, title, prefer_tk, + file_type='yaml')