From 5be078f8464762f740c04839738c3d39e725e1bb Mon Sep 17 00:00:00 2001 From: paul Date: Thu, 26 Sep 2024 23:16:43 +0530 Subject: [PATCH] working on code generation --- src/canvas/canvas.js | 2 +- src/canvas/widgets/base.js | 6 +- src/frameworks/tkinter/engine/code.js | 160 ++++++++++++------ .../tkinter/engine/componentCodes/entry.py | 27 +++ .../tkinter/widgets/ checkButton.js | 44 ++++- src/frameworks/tkinter/widgets/base.js | 8 +- src/frameworks/tkinter/widgets/button.js | 16 +- src/frameworks/tkinter/widgets/frame.js | 12 +- src/frameworks/tkinter/widgets/input.js | 12 ++ src/frameworks/tkinter/widgets/label.js | 28 +-- src/frameworks/tkinter/widgets/spinBox.js | 18 ++ 11 files changed, 256 insertions(+), 77 deletions(-) create mode 100644 src/frameworks/tkinter/engine/componentCodes/entry.py diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js index 1250f48..b6b01fc 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -767,7 +767,7 @@ class Canvas extends React.Component { // Find the dragged widget object let dragWidgetObj = this.findWidgetFromListById(dragElementID) - console.log("Drag widget obj: ", dragWidgetObj, dropWidgetObj) + // console.log("Drag widget obj: ", dragWidgetObj, dropWidgetObj) if (dropWidgetObj && dragWidgetObj) { const dragWidget = this.widgetRefs[dragWidgetObj.id] diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index 7f4f16f..18003fd 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -195,8 +195,6 @@ class Widget extends React.Component { componentDidMount() { - // FIXME: initial layout is not set properly - if (this.state.attrs.layout){ this.setLayout(this.state.attrs.layout.value) // console.log("prior layout: ", this.state.attrs.layout.value) @@ -534,7 +532,7 @@ class Widget extends React.Component { } setWidgetName(name) { - + console.log("named: ", name) this.updateState({ widgetName: name.length > 0 ? name : this.state.widgetName }) @@ -601,7 +599,7 @@ class Widget extends React.Component { }) this.setAttrValue("layout", value) - this.props.onLayoutUpdate({parentId: this.__id, parentLayout: layout})// inform children about the layout update + this.props.onLayoutUpdate({parentId: this.__id, parentLayout: value})// inform children about the layout update } diff --git a/src/frameworks/tkinter/engine/code.js b/src/frameworks/tkinter/engine/code.js index 8f1aef8..4f11f6f 100644 --- a/src/frameworks/tkinter/engine/code.js +++ b/src/frameworks/tkinter/engine/code.js @@ -2,6 +2,78 @@ import { createFilesAndDownload } from "../../../codeEngine/utils" import MainWindow from "../widgets/mainWindow" import { message } from "antd" +import TopLevel from "../widgets/toplevel" + + +// FIXME: if the toplevel comes first, before the MainWindow in widgetlist the root may become null +// Recursive function to generate the code list, imports, requirements, and track mainVariable +function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariable = null, mainVariable = "") { + let variableNames = [] // {widgetId: "", name: "", count: 1} + let imports = new Set([]) + let requirements = new Set([]) + + let code = [] + + for (let widget of widgetList) { + // console.log("Key: ", widget) + const widgetRef = widgetRefs[widget.id].current + + let varName = widgetRef.getVariableName() + + imports.add(widgetRef.getImports()) + requirements.add(widgetRef.getRequirements()) + + // Set main variable if the widget is MainWindow + if (widget.widgetType === MainWindow) { + mainVariable = varName + } + + if (!variableNames.find((val) => val.variable === varName)) { + variableNames.push({ widgetId: widgetRef.id, name: varName, count: 1 }) + } else { + // Avoid duplicate names + const existingVariable = variableNames.find((val) => val.variable === varName) + const existingCount = existingVariable.count + existingVariable.count += 1 + varName = `${existingVariable.name}${existingCount}` + variableNames.push({ widgetId: widgetRef.id, name: varName, count: 1 }) + } + + const currentParentVariable = variableNames.find(val => val.widgetId === widget.id)?.name || parentVariable + + let widgetCode = widgetRef.generateCode(varName, currentParentVariable) + + if (!(widgetCode instanceof Array)) { + throw new Error("Generate code function should return array, each new line should be a new item") + } + + // Add \n after every line + widgetCode = widgetCode.flatMap((item, index) => index < widgetCode.length - 1 ? [item, "\n"] : [item]) + + code.push(...widgetCode) + code.push("\n\n") + + // Recursively handle child widgets, which are full widget objects + if (widget.children && widget.children.length > 0) { + const childResult = generateTkinterCodeList(widget.children, widgetRefs, varName, mainVariable) + + // Merge child imports, requirements, and code + imports = new Set([...imports, ...childResult.imports]) + requirements = new Set([...requirements, ...childResult.requirements]) + code.push(...childResult.code) + + // Track the main variable returned by the child + mainVariable = childResult.mainVariable || mainVariable + } + } + + return { + imports: Array.from(imports), // Convert Set to Array + code: code, + requirements: Array.from(requirements), // Convert Set to Array + mainVariable + } +} async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[]){ @@ -10,7 +82,10 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[]){ let mainWindowCount = 0 - for (let widget of widgetList){ + // only MainWindow and/or the TopLevel should be on the canvas + const filteredWidgetList = widgetList.filter(widget => widget.widgetType === MainWindow || widget.widgetType === TopLevel) + + for (let widget of filteredWidgetList){ if (widget.widgetType === MainWindow){ mainWindowCount += 1 } @@ -27,64 +102,47 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[]){ return } - let variableNames = [] // {widgetId: "", name: "", count: 1} + // let code = [`# This code is generated by PyUIbuilder: https://github.com/paulledemon \n`] + // code.push("\n", "import tkinter as tk", "\n\n") - let imports = new Set([]) - let requirements = new Set([]) + // widget - {id, widgetType: widgetComponentType, children: [], parent: "", initialData: {}} + + const generatedObject = generateTkinterCodeList(filteredWidgetList, widgetRefs, "", "") - let code = [`# This code is generated by PyUIbuilder: https://github.com/paulledemon \n`] - code.push("\n", "import tkinter as tk", "\n\n") + const {code: codeLines, imports, requirements, mainVariable} = generatedObject + const code = [ + "# This code is generated by PyUIbuilder: https://github.com/paulledemon", + "\n\n", + ...imports, + "\n\n", + ...codeLines, + "\n", + `${mainVariable}.mainloop()`, + ] - let mainVariable = "" // the main window variable - - for (let widget of widgetList){ - console.log("Key: ", widget) - const widgetRef = widgetRefs[widget.id].current - - let varName = widgetRef.getVariableName() - - imports.add(widgetRef.getImports()) - requirements.add(widgetRef.getRequirements()) - - if (widget.widgetType === MainWindow){ - mainVariable = varName - } - - if (!variableNames.find((val) => val.variable === varName)){ - variableNames.push({widgetId: widgetRef.id, name: varName, count: 1}) - }else{ - // Avoid duplicate names - const existingVariable = variableNames.find((val) => val.variable === varName) - const existingCount = existingVariable.count - existingVariable.count += 1 - varName = `${existingVariable.name}${existingCount}` - variableNames.push({widgetId: widgetRef.id, name: varName, count: 1}) - } - - const parentVariable = variableNames.find(val => val.widgetId === widget.id)?.name || null - - console.log("widget ref: ", widgetRef) - let widgetCode = widgetRef.generateCode(varName, parentVariable) - - if (!widgetCode instanceof Array){ - throw new Error("Generate code function should return array, each new line should be a new item") - } - // add \n after every line - widgetCode = widgetCode.flatMap((item, index) => index < code.length - 1 ? [item, "\n"] : [item]) - - code.push(...widgetCode) - code.push("\n") - - } - - code.push(`\n${mainVariable}.mainloop()`) // start the main loop for tkinter - + // TODO: create requirements.txt file console.log("Code: ", code.join("")) message.info("starting zipping files, download will start in a few seconds") - // createFilesAndDownload([], projectName).then(() => { + const createFileList = [ + { + fileData: code.join(""), + fileName: "main.py", + folder: "" + } + ] + + if (requirements.length > 0){ + createFileList.push({ + fileData: requirements.join("\n"), + fileName: "requirements.txt", + folder: "" + }) + } + + // createFilesAndDownload(createFileList, projectName).then(() => { // message.success("Download complete") // }).catch(() => { // message.error("Error while downloading") diff --git a/src/frameworks/tkinter/engine/componentCodes/entry.py b/src/frameworks/tkinter/engine/componentCodes/entry.py new file mode 100644 index 0000000..0d76a9e --- /dev/null +++ b/src/frameworks/tkinter/engine/componentCodes/entry.py @@ -0,0 +1,27 @@ +import tkinter as tk + +class EntryWithPlaceholder(tk.Entry): + def __init__(self, master=None, placeholder="placeholder", *args, **kwargs): + super().__init__(master, *args, **kwargs) + + self.placeholder = placeholder + self.placeholder_color = color + self.default_fg_color = self['fg'] + + self.bind("", self.foc_in) + self.bind("", self.foc_out) + + self.put_placeholder() + + def put_placeholder(self): + self.insert(0, self.placeholder) + self['fg'] = self.placeholder_color + + def foc_in(self, *args): + if self['fg'] == self.placeholder_color: + self.delete('0', 'end') + self['fg'] = self.default_fg_color + + def foc_out(self, *args): + if not self.get(): + self.put_placeholder() \ No newline at end of file diff --git a/src/frameworks/tkinter/widgets/ checkButton.js b/src/frameworks/tkinter/widgets/ checkButton.js index 66c9e19..758b34e 100644 --- a/src/frameworks/tkinter/widgets/ checkButton.js +++ b/src/frameworks/tkinter/widgets/ checkButton.js @@ -64,6 +64,26 @@ export class CheckBox extends TkinterBase{ this.setWidgetInnerStyle("backgroundColor", "#fff0") } + generateCode(variableName, parent){ + + const labelText = this.getAttrValue("checkLabel") + const bg = this.getAttrValue("styling.backgroundColor") + const fg = this.getAttrValue("styling.foregroundColor") + + const code = [ + `${variableName} = tk.Checkbutton(master=${parent}, text="${labelText}")`, + `${variableName}.config(bg="${bg}", fg="${fg}")`, + ] + + if (this.getAttrValue("defaultChecked")){ + code.push(`${variableName}.select()`) + } + + code.push(`${variableName}.${this.getLayoutCode()}`) + + return code + } + getToolbarAttrs(){ const toolBarAttrs = super.getToolbarAttrs() @@ -109,7 +129,7 @@ export class CheckBox extends TkinterBase{ export class RadioButton extends Widget{ static widgetType = "radio_button" - // FIXME: the radio buttons are not visible because of the default height provided + constructor(props) { super(props) @@ -162,6 +182,28 @@ export class RadioButton extends Widget{ this.setWidgetInnerStyle("backgroundColor", "#fff0") } + generateCode(variableName, parent){ + + const bg = this.getAttrValue("styling.backgroundColor") + const fg = this.getAttrValue("styling.foregroundColor") + + const radios = this.getAttrValue("radios") + // TODO: from here + const code = [ + `${variableName}_var = tk.IntVar()`, + `${variableName} = tk.Radiobutton(master=${parent}, text="")`, + `${variableName}.config(bg="${bg}", fg="${fg}")`, + ] + + if (this.getAttrValue("defaultChecked")){ + code.push(`${variableName}.select()`) + } + + code.push(`${variableName}.${this.getLayoutCode()}`) + + return code + } + getToolbarAttrs(){ const toolBarAttrs = super.getToolbarAttrs() diff --git a/src/frameworks/tkinter/widgets/base.js b/src/frameworks/tkinter/widgets/base.js index fbc526f..95b0121 100644 --- a/src/frameworks/tkinter/widgets/base.js +++ b/src/frameworks/tkinter/widgets/base.js @@ -2,7 +2,8 @@ import { Layouts, PosType } from "../../../canvas/constants/layouts" import Tools from "../../../canvas/constants/tools" import Widget from "../../../canvas/widgets/base" - +// TODO: add full width and full height in base widget +// TODO: the pack should configure width and height of widgets class TkinterBase extends Widget { @@ -20,7 +21,7 @@ class TkinterBase extends Widget { let layoutManager = `pack()` if (parentLayout === Layouts.FLEX){ - layoutManager = `pack(${direction === "horizontal"? "tk.LEFT" : "tk.TOP"})` + layoutManager = `pack(side=${direction === "row" ? "tk.LEFT" : "tk.TOP"})` }else if (parentLayout === Layouts.GRID){ const row = this.getAttrValue("gridManager.row") const col = this.getAttrValue("gridManager.col") @@ -34,7 +35,6 @@ class TkinterBase extends Widget { } setParentLayout(parentLayout){ - console.log("parent layout: ", parentLayout) const {layout, direction, gap} = parentLayout @@ -195,7 +195,7 @@ class TkinterBase extends Widget { } this.setState(newData, () => { - let layoutAttrs = this.setParentLayout(parentLayout).attrs || {} + let layoutAttrs = this.setParentLayout(parentLayout) || {} // UPdates attrs let newAttrs = { ...this.state.attrs, ...layoutAttrs } diff --git a/src/frameworks/tkinter/widgets/button.js b/src/frameworks/tkinter/widgets/button.js index 0811e2c..775a8d1 100644 --- a/src/frameworks/tkinter/widgets/button.js +++ b/src/frameworks/tkinter/widgets/button.js @@ -43,12 +43,25 @@ class Button extends TkinterBase{ } } + componentDidMount(){ super.componentDidMount() this.setWidgetName("button") this.setAttrValue("styling.backgroundColor", "#E4E2E2") } + generateCode(variableName, parent){ + + const labelText = this.getAttrValue("buttonLabel") + const bg = this.getAttrValue("styling.backgroundColor") + const fg = this.getAttrValue("styling.foregroundColor") + return [ + `${variableName} = tk.Button(master=${parent}, text="${labelText}")`, + `${variableName}.config(bg="${bg}", fg="${fg}")`, + `${variableName}.${this.getLayoutCode()}` + ] + } + getToolbarAttrs(){ const toolBarAttrs = super.getToolbarAttrs() @@ -69,7 +82,8 @@ class Button extends TkinterBase{ return (
-
+
{/* {this.props.children} */}
{this.getAttrValue("buttonLabel")} diff --git a/src/frameworks/tkinter/widgets/frame.js b/src/frameworks/tkinter/widgets/frame.js index ac13418..8cc7614 100644 --- a/src/frameworks/tkinter/widgets/frame.js +++ b/src/frameworks/tkinter/widgets/frame.js @@ -12,11 +12,17 @@ class Frame extends TkinterBase{ this.droppableTags = { exclude: ["image", "video", "media", "toplevel", "main_window"] } + } - this.state = { - ...this.state, + generateCode(variableName, parent){ - } + const bg = this.getAttrValue("styling.backgroundColor") + + return [ + `${variableName} = tk.Frame(master=${parent})`, + `${variableName}.config(bg="${bg}")`, + `${variableName}.${this.getLayoutCode()}` + ] } componentDidMount(){ diff --git a/src/frameworks/tkinter/widgets/input.js b/src/frameworks/tkinter/widgets/input.js index 0530225..4221817 100644 --- a/src/frameworks/tkinter/widgets/input.js +++ b/src/frameworks/tkinter/widgets/input.js @@ -50,6 +50,18 @@ export class Input extends TkinterBase{ this.setWidgetName("Entry") } + generateCode(variableName, parent){ + + const placeHolderText = this.getAttrValue("labelWidget") + const bg = this.getAttrValue("styling.backgroundColor") + const fg = this.getAttrValue("styling.foregroundColor") + return [ + `${variableName} = tk.Entry(master=${parent}, text="${placeHolderText}")`, + `${variableName}.config(bg="${bg}", fg="${fg}")`, + `${variableName}.${this.getLayoutCode()}` + ] + } + getToolbarAttrs(){ const toolBarAttrs = super.getToolbarAttrs() diff --git a/src/frameworks/tkinter/widgets/label.js b/src/frameworks/tkinter/widgets/label.js index da66a54..eb5f098 100644 --- a/src/frameworks/tkinter/widgets/label.js +++ b/src/frameworks/tkinter/widgets/label.js @@ -46,22 +46,26 @@ class Label extends TkinterBase{ } } - generateCode(parent){ - - const label = this.getAttrValue("labelWidget") - - return (` - ${this.getWidgetName()} = tk.Label(master=${parent}, text="${label}") - ${this.getWidgetName()}.${this.getLayoutCode()} - `) - } - componentDidMount(){ super.componentDidMount() - this.setAttrValue("styling.backgroundColor", "#fff0") - this.setWidgetInnerStyle("backgroundColor", "#fff0") + this.setAttrValue("styling.backgroundColor", "#E4E2E2") + this.setWidgetName("label") } + + generateCode(variableName, parent){ + + const labelText = this.getAttrValue("labelWidget") + const bg = this.getAttrValue("styling.backgroundColor") + const fg = this.getAttrValue("styling.foregroundColor") + return [ + `${variableName} = tk.Label(master=${parent}, text="${labelText}")`, + `${variableName}.config(bg="${bg}", fg="${fg}")`, + `${variableName}.${this.getLayoutCode()}` + ] + } + + getToolbarAttrs(){ const toolBarAttrs = super.getToolbarAttrs() diff --git a/src/frameworks/tkinter/widgets/spinBox.js b/src/frameworks/tkinter/widgets/spinBox.js index 107589e..ea92c61 100644 --- a/src/frameworks/tkinter/widgets/spinBox.js +++ b/src/frameworks/tkinter/widgets/spinBox.js @@ -8,6 +8,7 @@ import TkinterBase from "./base" class SpinBox extends TkinterBase{ static widgetType = "spin_box" + constructor(props) { super(props) @@ -75,6 +76,23 @@ class SpinBox extends TkinterBase{ this.setWidgetName("SpinBox") } + generateCode(variableName, parent){ + + const min = this.getAttrValue("spinProps.min") + const max = this.getAttrValue("spinProps.max") + const step = this.getAttrValue("spinProps.step") + const defaultValue = this.getAttrValue("spinProps.defaultValue") + + const bg = this.getAttrValue("styling.backgroundColor") + const fg = this.getAttrValue("styling.foregroundColor") + return [ + `${variableName} = tk.Spinbox(master=${parent})`, + `${variableName}.config(bg="${bg}", fg="${fg}", from_=${min}, to=${max}, + increment=${step})`, + `${variableName}.${this.getLayoutCode()}` + ] + } + getToolbarAttrs(){ const toolBarAttrs = super.getToolbarAttrs()