From ba88df4c4e7ef0db866647fafda70c4125efbc73 Mon Sep 17 00:00:00 2001 From: David Michael Jackson Date: Wed, 29 May 2024 21:04:37 -0500 Subject: [PATCH] webster123-v1.0.2 Correction. Adds hooks after , topHtml, and , headTag Signed-off-by: David Michael Jackson --- webster123-v1.0.2.py | 743 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 743 insertions(+) create mode 100644 webster123-v1.0.2.py diff --git a/webster123-v1.0.2.py b/webster123-v1.0.2.py new file mode 100644 index 0000000..c4199e4 --- /dev/null +++ b/webster123-v1.0.2.py @@ -0,0 +1,743 @@ +import tkinter as tk +from tkinter import font, filedialog, messagebox, simpledialog, Toplevel, Text, Scrollbar, Menu +import pandas as pd +import os +import webbrowser + +global_df = None # Global variable to store the DataFrame +current_selection = None +clipboard_data = None +shift_start_row = None # Variable to store the starting row for shift-click selection + +# Define the column order +columns = [ + "siteId", "folderPath", "ourUrl", "ourTitle", "ourContent", "Extra1", "Extra2","topMenu", + "ourHeader", "ourFooter", "styleSheet", "scriptsUrl", "fileExtension", "ourMeta", "shareImageUrl", + "Website", "websiteUrl","Icon", "topHtml", "headTag", "ourShareButton", "useLinkBox", "directoryMode", "frontPage" +] +# Define initial values +initial_data = { + "ourUrl": ["publish-and-view-me"], + "folderPath": ["c:\\webster123"], + "fileExtension": ["html"], + "scriptsUrl": [""], + "ourTitle": [""], + "ourContent": [f"""

Congratulations!

You've published your first page.
Change it to your html to get started.
This is Webster123 v1.0.2
Visit Webster123.com for instructions.

"""], + "Extra1": [""], + "Extra2": [""], + "siteId": ["My Site"], + "topMenu": [""], + "ourHeader": [f""""""], + "ourFooter": [""], + "directoryMode": ["False"], + "shareImageUrl": [""], + "ourMeta": [""], + "Website": [""], + "websiteUrl": [""], + "styleSheet": [""], + "Icon": [""], + "topHtml": [""], + "headTag": [""], + "ourShareButton": [""], + "useLinkBox": ["False"], + "frontPage": ["False"] +} + +# Add 20 extra empty rows +for _ in range(20): + for key in initial_data.keys(): + initial_data[key].append("") + +column_config = { + "ourUrl": {"width": 220, "chars": 24, "instructions": "Words with a dash between them, no special characters"}, + "folderPath": {"width": 100, "chars": 12, "instructions": "The folder on your local where you wish to store the pages you create. Like C:\\webster123"}, + "fileExtension": {"width": 100, "chars": 12, "instructions": "html or php"}, + "ourTitle": {"width": 100, "chars": 12, "instructions": "The title of your web page."}, + "ourContent": {"width": 100, "chars": 12, "instructions": "Html content"}, + "Extra1": {"width": 100, "chars": 12, "instructions": "Extra Html content"}, + "Extra2": {"width": 100, "chars": 12, "instructions": "Extra Html content"}, + "siteId": {"width": 100, "chars": 12, "instructions": "Your site Id, Which site is this?"}, + "topMenu": {"width": 100, "chars": 12, "instructions": "Our menu entries are Anchor links stacked on top of each other."}, + "ourHeader": {"width": 100, "chars": 12, "instructions": "Html for the header of the website."}, + "ourFooter": {"width": 100, "chars": 12, "instructions": "Html for the Footer of our site."}, + "directoryMode": {"width": 100, "chars": 12, "instructions": "False and we produce a url like example.html. True and we create a folder example/ and put an index page in it.."}, + "shareImageUrl": {"width": 100, "chars": 12, "instructions": "The url of your share image"}, + "ourMeta": {"width": 100, "chars": 12, "instructions": "The meta Description of your page."}, + "Website": {"width": 100, "chars": 12, "instructions": "yoursite.com"}, + "websiteUrl": {"width": 100, "chars": 12, "instructions": "Website URL. Must have trailing slash '/', like https://yoursite.com/"}, + "styleSheet": {"width": 100, "chars": 12, "instructions": "The url of your stylesheet file. On your local drive it can look like file:///c:/Stylesheets/mystylesheet.css This way you can work with a stylesheet on your drive. When you publish the page on the internet, you can change it to something like https://mysite.com/mystylesheet.css"}, + "Icon": {"width": 100, "chars": 12, "instructions": "The website icon, usually 100x100px"}, + "topHtml": {"width": 100, "chars": 12, "instructions": "Inserted after "}, + "headTag": {"width": 100, "chars": 12, "instructions": "Inserted after "}, + "ourShareButton": {"width": 100, "chars": 12, "instructions": "AddtoAny Share Button. Leave blank to not use."}, + "useLinkBox": {"width": 100, "chars": 12, "instructions": "If True, a Link To This Page Box will be added"}, + "scriptsUrl": {"width": 100, "chars": 12, "instructions": "The url of your java script file. On your local drive it can look like file:///c:/Scriptsfolder/myscript.js This way you can work with a script on your drive. When you publish the page on the internet, you can change it to something like https://mysite.com/myscript.js"} + +} + +class SimpleTable(tk.Canvas): + def __init__(self, parent, rows=10, cols=5, font_size=10): + super().__init__(parent) + self.parent = parent + self.rows = rows + self.cols = cols + self.cells = {} + self.headers = [] + self.row_numbers = [] + self.header_height = 30 + self.row_number_width = 50 + self.font_size = font_size + self.font = font.Font(size=font_size) + self.cell_height = self.font.metrics("linespace") + 10 + + self.selection_rects = [] + self.start_row = None + self.edit_window = None # Track the edit window + self.clipboard_data = None # To store single cell value for pasting + + self.header_canvas = tk.Canvas(parent, height=self.header_height, bg='lightgrey') + self.row_number_canvas = tk.Canvas(parent, width=self.row_number_width, bg='lightgrey') + + self.header_canvas.grid(row=0, column=1, sticky='ew') + self.row_number_canvas.grid(row=1, column=0, sticky='ns') + + self.create_widgets() + self.bind_shortcuts() + + def create_widgets(self): + self.config(bg='white', highlightthickness=0) + self.bind("", self.on_table_click) + self.bind("", self.on_table_drag) + self.bind("", self.on_drag_end) + self.bind("", self.on_table_double_click) + self.bind("", self.on_table_right_click) + self.bind_all("", self.on_mouse_wheel) + self.row_number_canvas.bind("", self.on_row_number_right_click) + self.xview_moveto(0) + self.yview_moveto(0) + + def bind_shortcuts(self): + self.parent.bind("", self.copy_selection) + self.parent.bind("", self.cut_selection) + self.parent.bind("", self.paste_selection) + + def cut_selection(self): + self.copy_selection() + self.delete_selection(current_selection[0], current_selection[1]) + + def create_cell(self, row, col, value): + col_name = self.headers[col] + cell_config = column_config.get(col_name, {"width": 100, "chars": 12}) + cell_width = cell_config["width"] + x0 = self.row_number_width + sum(column_config.get(self.headers[c], {"width": 100})["width"] for c in range(col)) + y0 = row * self.cell_height + x1 = x0 + cell_width + y1 = y0 + self.cell_height + rect = self.create_rectangle(x0, y0, x1, y1, fill="white", outline="black") + + # Calculate the number of characters that fit in the cell width + char_width = self.font.measure("A") + max_chars = cell_width // char_width + display_value = str(value)[:max_chars] if value else "" + + text = self.create_text(x0 + 5, y0 + 5, anchor="nw", text=display_value, width=cell_width - 10, font=self.font, tag=f"cell_{row}_{col}") + self.cells[(row, col)] = (rect, text) + + def create_headers(self): + self.header_canvas.delete("all") + x_offset = 0 + for col, header in enumerate(self.headers): + cell_config = column_config.get(header, {"width": 100, "chars": 12}) + cell_width = cell_config["width"] + x0 = x_offset + y0 = 0 + x1 = x0 + cell_width + y1 = y0 + self.header_height + self.header_canvas.create_rectangle(x0, y0, x1, y1, fill="lightgrey", outline="black") + self.header_canvas.create_text(x0 + 5, y0 + 5, anchor="nw", text=header, font=self.font) + x_offset += cell_width + + def create_row_numbers(self): + self.row_number_canvas.delete("all") + for row in range(self.rows): + x0 = 0 + y0 = row * self.cell_height + x1 = x0 + self.row_number_width + y1 = y0 + self.cell_height + rect = self.row_number_canvas.create_rectangle(x0, y0, x1, y1, fill="lightgrey", outline="black") + text = self.row_number_canvas.create_text(x0 + 5, y0 + 5, anchor="nw", text=str(row + 1), font=self.font) + self.row_numbers.append((rect, text)) + self.row_number_canvas.tag_bind(rect, "", lambda event, r=row: self.on_row_number_click(event, r)) + self.row_number_canvas.tag_bind(text, "", lambda event, r=row: self.on_row_number_click(event, r)) + + def on_row_number_click(self, event, row): + global shift_start_row + if shift_start_row is None or not event.state & 0x1: # If shift key is not pressed + shift_start_row = row + self.clear_selection() + self.highlight_rows(shift_start_row, row) + global current_selection + current_selection = (min(shift_start_row, row), 0, max(shift_start_row, row), self.cols - 1) + + def highlight_rows(self, start_row, end_row): + for row in range(min(start_row, end_row), max(start_row, end_row) + 1): + for col in range(self.cols): + self.highlight_cell(row, col) + + def load_data(self, data, column_names): + self.rows = len(data) + self.cols = len(data[0]) if self.rows > 0 else 0 + self.headers = column_names + self.cells.clear() + self.delete("all") + self.populate_table() + for row, row_data in enumerate(data): + for col, value in enumerate(row_data): + self.create_cell(row, col, value) + self.update_idletasks() + self.config(scrollregion=self.bbox("all")) + self.create_headers() + self.create_row_numbers() + self.update_scroll_region() + + def populate_table(self): + for row in range(self.rows): + for col in range(self.cols): + self.create_cell(row, col, "") + self.create_headers() + self.create_row_numbers() + + def on_table_click(self, event): + global current_selection + x, y = self.canvasx(event.x), self.canvasy(event.y) + row, col = int(y // self.cell_height), self.get_col_at_x(x - self.row_number_width) + if row >= 0 and col >= 0: + self.clear_selection() + self.highlight_cell(row, col) + current_selection = (row, col, row, col) + + def get_col_at_x(self, x): + x_offset = 0 + for col, header in enumerate(self.headers): + cell_config = column_config.get(header, {"width": 100, "chars": 12}) + cell_width = cell_config["width"] + if x_offset <= x < x_offset + cell_width: + return col + x_offset += cell_width + return -1 + + def on_table_drag(self, event): + global current_selection + x, y = self.canvasx(event.x), self.canvasy(event.y) + row, col = int(y // self.cell_height), self.get_col_at_x(x - self.row_number_width) + if row >= 0 and col >= 0 and current_selection is not None: + self.clear_selection() + row1, col1, row2, col2 = current_selection + current_selection = (row1, col1, row, col) + self.highlight_rectangle(row1, col1, row, col) + + def on_drag_end(self, event): + self.start_row = None + + def on_table_double_click(self, event): + x, y = self.canvasx(event.x), self.canvasy(event.y) + row, col = int(y // self.cell_height), self.get_col_at_x(x - self.row_number_width) + if row >= 0 and col >= 0: + self.edit_cell(row, col) + + def on_table_right_click(self, event): + x, y = self.canvasx(event.x), self.canvasy(event.y) + row, col = int(y // self.cell_height), self.get_col_at_x(x - self.row_number_width) + if row >= 0 and col >= 0: + self.show_cell_context_menu(event, row, col) + + def on_row_number_right_click(self, event): + y = self.row_number_canvas.canvasy(event.y) + row = int(y // self.cell_height) + if row >= 0: + self.show_row_context_menu(event, row) + + def on_mouse_wheel(self, event): + if event.state & 0x1: # If Shift key is pressed + self.xview_scroll(-1 * int((event.delta / 120)), "units") + else: + self.yview_scroll(-1 * int((event.delta / 120)), "units") + self.update_scroll_region() + + def highlight_cell(self, row, col): + self.itemconfig(self.cells[(row, col)][0], fill="lightblue") + self.itemconfig(self.cells[(row, col)][1], fill="blue", font="Arial 10 bold") + + def clear_selection(self): + for rect in self.selection_rects: + self.delete(rect) + self.selection_rects.clear() + for (row, col), (rect, text) in self.cells.items(): + self.itemconfig(rect, fill="white") + self.itemconfig(text, fill="black", font="Arial 10") + + def highlight_rectangle(self, row1, col1, row2, col2): + for row in range(min(row1, row2), max(row1, row2) + 1): + for col in range(min(col1, col2), max(col1, col2) + 1): + self.highlight_cell(row, col) + cell_config = column_config.get(self.headers[col], {"width": 100, "chars": 12}) + cell_width = cell_config["width"] + x0 = self.row_number_width + sum(column_config.get(self.headers[c], {"width": 100})["width"] for c in range(col)) + y0 = row * self.cell_height + x1 = x0 + cell_width + y1 = y0 + self.cell_height + rect = self.create_rectangle(x0, y0, x1, y1, outline="blue", width=2) + self.selection_rects.append(rect) + + def show_cell_context_menu(self, event, row, col): + menu = Menu(self, tearoff=0) + menu.add_command(label="Copy Selection", command=self.copy_selection) + menu.add_command(label="Paste Selection", command=self.paste_selection) + menu.add_command(label="Delete Selection", command=lambda: self.delete_selection(row, col)) + menu.post(event.x_root, event.y_root) + + def show_row_context_menu(self, event, row): + menu = Menu(self, tearoff=0) + menu.add_command(label="Insert Row", command=lambda: self.insert_row(row)) + menu.add_command(label="Delete Row", command=lambda: self.delete_row(row)) + menu.add_command(label="Copy Selection", command=self.copy_selection) + menu.add_command(label="Publish Selected Rows", command=self.publish_selected_rows) + menu.add_command(label="View Selected Rows", command=self.view_selected_rows) + menu.post(event.x_root, event.y_root) + + def insert_row(self, row): + global global_df + new_row = [""] * self.cols + global_df = pd.concat([global_df.iloc[:row], pd.DataFrame([new_row], columns=global_df.columns), global_df.iloc[row:]]).reset_index(drop=True) + update_table() + + def delete_row(self, row): + global global_df + global_df = global_df.drop(global_df.index[row]).reset_index(drop=True) + update_table() + + def delete_selection(self, row, col): + global global_df, current_selection + if current_selection is not None: + row1, col1, row2, col2 = current_selection + global_df.iloc[min(row1, row2):max(row1, row2) + 1, min(col1, col2):max(col1, col2) + 1] = "" + update_table() + + def copy_selection(self): + global global_df, clipboard_data, current_selection + if current_selection is not None: + row1, col1, row2, col2 = current_selection + if row1 == row2 and col1 == col2: + # Copy a single cell + clipboard_data = global_df.iat[row1, col1] + else: + # Copy a range of cells + clipboard_data = global_df.iloc[min(row1, row2):max(row1, row2) + 1, min(col1, col2):max(col1, col2) + 1].copy() + + def paste_selection(self): + global global_df, clipboard_data, current_selection + if clipboard_data is not None and current_selection is not None: + row1, col1, row2, col2 = current_selection + if isinstance(clipboard_data, pd.DataFrame): + rows_to_paste = clipboard_data.shape[0] + cols_to_paste = clipboard_data.shape[1] + if row1 + rows_to_paste > self.rows or col1 + cols_to_paste > self.cols: + messagebox.showerror("Paste Error", "Not enough space to paste the selection") + return + for i in range(rows_to_paste): + for j in range(cols_to_paste): + global_df.iat[row1 + i, col1 + j] = clipboard_data.iat[i, j] + else: + for row in range(min(row1, row2), max(row1, row2) + 1): + for col in range(min(col1, col2), max(col1, col2) + 1): + global_df.iat[row, col] = clipboard_data + update_table() + + def edit_cell(self, row, col): + if self.edit_window is not None and self.edit_window.winfo_exists(): + self.edit_window.lift() # Bring the existing edit window to the front + return + if (row, col) not in self.cells: + return + full_text = global_df.iat[row, col] if not pd.isna(global_df.iat[row, col]) else "" # Handle NaN values + col_name = self.headers[col] + cell_config = column_config.get(col_name, {"width": 100, "chars": 12, "instructions": ""}) + instructions = cell_config.get("instructions", "") + + self.edit_window = tk.Toplevel(self) + self.edit_window.title(f"Edit Cell ({row}, {col})") + + if instructions: + instruction_label = tk.Label(self.edit_window, text=instructions, wraplength=400, justify="left") + instruction_label.pack(fill="x", padx=10, pady=10) + + text_frame = tk.Frame(self.edit_window, padx=10, pady=10) + text_frame.pack(fill="both", expand=True) + + text = Text(text_frame, wrap="word") + text.insert("1.0", full_text) + text.pack(fill="both", expand=True, side="left") + + scrollbar = Scrollbar(text_frame, command=text.yview) + scrollbar.pack(side="right", fill="y") + text.config(yscrollcommand=scrollbar.set) + + text.bind_all("", lambda event: text.yview_scroll(-1 * int((event.delta / 120)), "units")) + + button_frame = tk.Frame(self.edit_window) + button_frame.pack(fill="x", expand=False, pady=10) + + def save_edit(): + new_text = text.get("1.0", tk.END).strip() + self.itemconfig(self.cells[(row, col)][1], text=new_text[:cell_config["chars"]]) # Update with truncated text + global_df.iat[row, col] = new_text # Update the global DataFrame with full text + self.edit_window.destroy() + self.edit_window = None + self.update_scroll_region() # Update the scroll region after editing + + save_button = tk.Button(button_frame, text="Save", command=save_edit) + cancel_button = tk.Button(button_frame, text="Cancel", command=lambda: [self.edit_window.destroy(), setattr(self, 'edit_window', None)]) + save_button.pack(side="left", padx=20, pady=5) + cancel_button.pack(side="right", padx=20, pady=5) + + text.focus_set() + + def handle_return(event): + text.insert(tk.INSERT, "\n") + return "break" + + text.bind('', handle_return) + + # Update the scroll region after closing the edit window + self.edit_window.bind("", lambda event: [self.update_scroll_region(), setattr(self, 'edit_window', None)]) + + def update_scroll_region(self): + self.config(scrollregion=self.bbox("all")) + self.header_canvas.config(scrollregion=self.header_canvas.bbox("all")) + self.row_number_canvas.config(scrollregion=self.row_number_canvas.bbox("all")) + + if self.xview() is not None: + self.header_canvas.xview_moveto(self.xview()[0]) + if self.yview() is not None: + self.row_number_canvas.yview_moveto(self.yview()[0]) + + # Ensure headers and row numbers are always visible + self.tag_lower("header") + self.tag_lower("row_num") + self.parent.update() + + # Re-bind scroll events + self.bind_all("", self.on_mouse_wheel) + + def xview_handler(self, *args): + self.xview_moveto = super().xview_moveto + self.xview_scroll = super().xview_scroll + self.xview(*args) + self.header_canvas.xview(*args) + self.update_scroll_region() + + def yview_handler(self, *args): + self.yview_moveto = super().yview_moveto + self.yview_scroll = super().yview_scroll + self.yview(*args) + self.row_number_canvas.yview(*args) + self.update_scroll_region() + + def publish_selected_rows(self): + global current_selection + if current_selection is not None: + row1, col1, row2, col2 = current_selection + rows_to_publish = list(range(min(row1, row2) + 1, max(row1, row2) + 2)) + result = publish_rows_to_disk(",".join(map(str, rows_to_publish))) + messagebox.showinfo("Publish Result", result) + + def view_selected_rows(self): + global current_selection + if current_selection is not None: + row1, col1, row2, col2 = current_selection + rows_to_view = list(range(min(row1, row2) + 1, max(row1, row2) + 2)) + result = view_html_pages(",".join(map(str, rows_to_view))) + messagebox.showinfo("View Result", result) + +# Ensure directory creation +def ensure_dir(folder_path): + if not os.path.exists(folder_path): + os.makedirs(folder_path) + +# Write HTML to file +def write_html(content, filename): + with open(filename, 'w', encoding='utf-8') as file: + file.write(content) + +def parse_line_numbers(line_numbers): + result = [] + parts = line_numbers.split(',') + for part in parts: + if '-' in part: + start, end = map(int, part.split('-')) + result.extend(range(start - 1, end)) + else: + result.append(int(part) - 1) # Convert to zero-based index + return result + +# Generate HTML Content +def generate_html_content(row): + def check_nan(value): + return "" if pd.isna(value) else value + + full_content = f"{check_nan(row['ourContent'])}{check_nan(row.get('Extra1', ''))}{check_nan(row.get('Extra2', ''))}" + directory_mode = str(row.get('directoryMode', 'false')).strip().lower() == 'true' + front_page = str(row.get('frontPage', 'false')).strip().lower() == 'true' + canonical_url = row['websiteUrl'] if front_page and not directory_mode else f"{row['websiteUrl']}{row['ourUrl']}/" if directory_mode else f"{row['websiteUrl']}{row['ourUrl']}.{row.get('fileExtension', 'html')}" + use_link_box = str(row.get('useLinkBox', 'false')).strip().lower() == 'true' + + link_box_html = "" + if use_link_box: + link_box_html = f"""
The button below can help educators and others link to this page:

+
+ +
+ """ + + return f""" +{check_nan(row['topHtml'])} + + {check_nan(row['ourTitle'])} + + + + + + + + + + + + {check_nan(row['headTag'])} + + +
{check_nan(row['ourHeader'])}
+
{check_nan(row['topMenu'])}
+
+

{check_nan(row['ourTitle'])}

+ {full_content} + {link_box_html} + {check_nan(row['ourShareButton'])} +
+ + fastest websites possible + + +""" + + + +# Construct the file path for a given row +def construct_file_path(row): + folder_path = row['folderPath'] + directory_mode = str(row.get('directoryMode', 'false')).strip().lower() == 'true' + file_name = os.path.join(row['ourUrl'], "index") if directory_mode else row['ourUrl'] + file_extension = row.get('fileExtension', 'html') + return os.path.join(folder_path, f"{file_name}.{file_extension}") + +# Publish Rows to Disk +def publish_rows_to_disk(line_numbers): + global global_df + + if global_df is None: + messagebox.showwarning("No CSV", "No CSV loaded. Please load a CSV file first.") + return "No CSV loaded." + + try: + lines_to_process = parse_line_numbers(line_numbers) + output = [] + + for index in lines_to_process: + if index < len(global_df): + row = global_df.iloc[index] + folder_path = row['folderPath'] + + directory_mode = str(row.get('directoryMode', 'false')).strip().lower() == 'true' + file_name = os.path.join(row['ourUrl'], "index") if directory_mode else row['ourUrl'] + file_extension = row.get('fileExtension', 'html') + full_path = os.path.join(folder_path, f"{file_name}.{file_extension}") + + ensure_dir(os.path.dirname(full_path)) + html_content = generate_html_content(row) + write_html(html_content, full_path) + output.append(f"Processed row {index + 1} and saved to {full_path}") + + return "\n".join(output) + except Exception as e: + return f"Error processing data: {str(e)}" + +# View Html Pages +def view_html_pages(line_numbers=None): + global global_df + + if global_df is None: + messagebox.showwarning("No CSV", "No CSV loaded. Please load a CSV file first.") + return "No CSV loaded." + + try: + lines_to_open = parse_line_numbers(line_numbers) + files_to_open = [] + + for index in lines_to_open: + if index < len(global_df): + row = global_df.iloc[index] + file_path = construct_file_path(row) + files_to_open.append(file_path) + + if not files_to_open: + messagebox.showwarning("No Files", "No valid files to open.") + return "No valid files to open." + + for file in files_to_open: + webbrowser.open(f"file://{os.path.abspath(file)}", new=2) + return f"Opened {len(files_to_open)} files in the browser." + except Exception as e: + return f"Error opening files: {str(e)}" + +# Load CSV +def load_csv(): + global global_df + file_path = filedialog.askopenfilename(filetypes=[("CSV Files", "*.csv")]) + if not file_path: + return + try: + global_df = pd.read_csv(file_path) + + # Replace 0/1 with False/True for specific columns + bool_cols = ['directoryMode', 'useLinkBox', 'frontPage'] + for col in bool_cols: + if col in global_df.columns: + global_df[col] = global_df[col].replace({0: "False", 1: "True"}) + + # Ensure columns are in the predefined order + global_df = global_df.reindex(columns=columns) + + # Add extra empty rows if necessary to make sure there are at least 21 rows + if len(global_df) < 21: + extra_rows = 21 - len(global_df) + empty_rows = pd.DataFrame({col: [""] * extra_rows for col in columns}) + global_df = pd.concat([global_df, empty_rows], ignore_index=True) + + # Load the entire table to make all rows scrollable + update_table() + except Exception as e: + messagebox.showerror("Load Error", f"Failed to load CSV: {str(e)}") + +# Update Table +def update_table(rows_to_display=None): + global global_df + if global_df is not None: + if rows_to_display is not None: + data = global_df.head(rows_to_display).fillna("").values.tolist() + else: + data = global_df.fillna("").values.tolist() + column_names = global_df.columns.tolist() + table.load_data(data, column_names) + table.update_scroll_region() + + +# Save CSV +def save_csv(): + global global_df + if global_df is None: + messagebox.showwarning("No CSV", "No CSV loaded. Please load a CSV file first.") + return + file_path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV Files", "*.csv")]) + if not file_path: + return + try: + global_df.to_csv(file_path, index=False) + messagebox.showinfo("CSV Saved", f"CSV file saved successfully to {file_path}.") + except Exception as e: + messagebox.showerror("Save Error", f"Failed to save CSV: {str(e)}") + +# Update Table +def update_table(rows_to_display=None): + global global_df + if global_df is not None: + if rows_to_display is not None: + data = global_df.head(rows_to_display).fillna("").values.tolist() + else: + data = global_df.fillna("").values.tolist() + column_names = global_df.columns.tolist() + table.load_data(data, column_names) + table.update_scroll_region() + +# Process Button Action +def process_button_action(): + row_numbers = simpledialog.askstring("Rows to Process", "Enter rows to process (e.g., 0-2,4,6-8):") + if row_numbers: + result = publish_rows_to_disk(row_numbers) + messagebox.showinfo("Process Result", result) + +# Open Button Action +def open_button_action(): + row_numbers = simpledialog.askstring("Rows to Open", "Enter rows to open (e.g., 0-2,4,6-8):") + if row_numbers: + result = view_html_pages(row_numbers) + messagebox.showinfo("Open Result", result) + +# Main GUI Setup +root = tk.Tk() +root.title("Webster123pro") +root.geometry("1200x600") + +toolbar = tk.Frame(root) +toolbar.pack(side="top", fill="x", pady=10) + +load_button = tk.Button(toolbar, text="Load CSV", command=load_csv) +load_button.pack(side="left", padx=5, pady=5) + +save_button = tk.Button(toolbar, text="Save CSV", command=save_csv) +save_button.pack(side="left", padx=5, pady=5) + +frame = tk.Frame(root) +frame.pack(fill="both", expand=True, pady=10) + +x_scrollbar = tk.Scrollbar(frame, orient="horizontal") +x_scrollbar.grid(row=2, column=1, sticky='ew') + +y_scrollbar = tk.Scrollbar(frame, orient="vertical") +y_scrollbar.grid(row=1, column=2, sticky='ns') + +# Use the SimpleTable class to create a custom table +table = SimpleTable(frame) +table.grid(row=1, column=1, sticky='nsew') + +# Configure scrollbars +x_scrollbar.config(command=table.xview_handler) +y_scrollbar.config(command=table.yview_handler) +table.config(xscrollcommand=x_scrollbar.set, yscrollcommand=y_scrollbar.set) + +# Configure grid weights to make the table expandable +frame.grid_rowconfigure(1, weight=1) +frame.grid_columnconfigure(1, weight=1) + +# Initialize with initial data if no CSV is loaded +if global_df is None: + global_df = pd.DataFrame(initial_data, columns=columns) + update_table() + +root.mainloop() \ No newline at end of file