import { DesignData, WidgetData } from './types'; import { getVariableName, generateVariableNames, getWidgetTypeForGeneration, indentText, } from './utils'; import { getWidgetParameters, getPlaceParameters, generateWidgetContent, } from './widgetHelpers'; import { generateEventHandlers, getWidgetEventBindings } from './eventHelpers'; export class CodeGenerator { private indentLevel = 0; private designData: DesignData | null = null; public generateTkinterCode(designData: DesignData): string { this.designData = designData; const className = designData.form.className || 'Application'; console.log( '[Generator] Start, widgets:', designData.widgets.length, 'events:', designData.events?.length || 0 ); const lines: string[] = []; const nameMap = generateVariableNames(designData.widgets); lines.push('import tkinter as tk'); lines.push('from tkinter import ttk'); lines.push(''); lines.push(`class ${className}:`); this.indentLevel = 1; lines.push(this.indent('def __init__(self):')); this.indentLevel = 2; lines.push(this.indent('self.root = tk.Tk()')); lines.push(this.indent(`self.root.title("${designData.form.title}")`)); lines.push( this.indent( `self.root.geometry("${designData.form.size.width}x${designData.form.size.height}")` ) ); lines.push(this.indent('self.create_widgets()')); lines.push(''); this.indentLevel = 1; lines.push(this.indent('def create_widgets(self):')); this.indentLevel = 2; designData.widgets.forEach((widget) => { console.log('[Generator] Widget:', widget.id, widget.type); lines.push(...this.generateWidgetCode(widget, nameMap)); }); this.indentLevel = 1; lines.push(this.indent('def run(self):')); this.indentLevel = 2; lines.push(this.indent('self.root.mainloop()')); lines.push(''); const hasEvents = designData.events && designData.events.length > 0; if (hasEvents) { console.log('[Generator] Generating event handlers'); lines.push( ...generateEventHandlers( designData, (text) => indentText(1, text), (text) => indentText(2, text) ) ); } this.indentLevel = 0; lines.push('if __name__ == "__main__":'); this.indentLevel = 1; lines.push(this.indent('try:')); this.indentLevel = 2; lines.push(this.indent(`app = ${className}()`)); lines.push(this.indent('app.run()')); this.indentLevel = 1; lines.push(this.indent('except Exception as e:')); this.indentLevel = 2; lines.push(this.indent('import traceback')); lines.push(this.indent('traceback.print_exc()')); lines.push(this.indent('input("Press Enter to exit...")')); return lines.join('\n'); } public generateCreateWidgetsBody(designData: DesignData): string { this.designData = designData; const lines: string[] = []; const nameMap = generateVariableNames(designData.widgets); this.indentLevel = 2; // Inside class -> inside create_widgets designData.widgets.forEach((widget) => { lines.push(...this.generateWidgetCode(widget, nameMap)); }); return lines.join('\n'); } public generateEventHandler(event: any): string { // Simple helper to generate a single event handler method // Used when injecting new methods const lines: string[] = []; // Use existing signature if available (to preserve user arguments) // Note: signature does not include 'def ' prefix in our current extraction logic? // Let's check base.py: sig_raw = self.source_code[def_start_idx : start_idx].strip() // It includes 'def name(...)'. // So we just use it directly. if (event.signature) { lines.push(` ${event.signature}:`); } else { lines.push(` def ${event.name}(self, event=None):`); } if (event.code) { const codeContent = typeof event.code === 'string' ? event.code : String(event.code || ''); const body = codeContent .split('\n') .map((l: string) => ' ' + l) .join('\n'); lines.push(body); } else { lines.push(' pass'); } return lines.join('\n'); } private generateWidgetCode( widget: WidgetData, nameMap: Map ): string[] { const lines: string[] = []; const varName = getVariableName(widget, nameMap); const widgetType = getWidgetTypeForGeneration(widget.type); lines.push( this.indent( `self.${varName} = ${widgetType}(self.root${getWidgetParameters(widget)})` ) ); const placeParams = getPlaceParameters(widget); lines.push(this.indent(`self.${varName}.place(${placeParams})`)); const contentLines = generateWidgetContent(widget, varName); if (contentLines.length > 0) { contentLines.forEach((l) => lines.push(this.indent(l))); } lines.push( ...getWidgetEventBindings( this.designData, widget, varName, (text) => this.indent(text) ) ); return lines; } private indent(text: string): string { return indentText(this.indentLevel, text); } }