fixed a bunch of stuff, added a notes programm, which lets you take notes, did some giant refactoring work and did some general designing

This commit is contained in:
2025-11-24 14:01:15 +01:00
parent 3cd75d97f6
commit 5fd77f4b39
12 changed files with 1768 additions and 866 deletions

811
src/NotesDialog.py Normal file
View File

@@ -0,0 +1,811 @@
import wx
import wx.richtext as rt
import json
from collections import defaultdict
import tempfile
import os
import time
import threading
from typing import Dict, Any, Optional
class NotesDialog(wx.Frame):
def __init__(self, parent, notes_data=None, pos=None):
# If no position specified, offset from parent
if pos is None and parent:
parent_pos = parent.GetPosition()
pos = (parent_pos.x + 50, parent_pos.y + 50)
super().__init__(parent, title="IRC Notes", size=(900, 650), pos=pos,
style=wx.DEFAULT_FRAME_STYLE)
self.parent = parent
self.notes_data = notes_data or defaultdict(dict)
self.current_note_key = None
self.updating_title = False
self.is_closing = False
self.content_changed = False
self.last_save_time = time.time()
self.auto_save_interval = 2 # seconds - reduced for immediate saving
self.SetBackgroundColour(wx.Colour(245, 245, 245))
# Set icon if parent has one
if parent:
self.SetIcon(parent.GetIcon())
self.create_controls()
self.load_notes_list()
# Bind close event to save before closing
self.Bind(wx.EVT_CLOSE, self.on_close)
# Auto-save timer (save every 2 seconds)
self.save_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_auto_save, self.save_timer)
self.save_timer.Start(self.auto_save_interval * 1000) # Convert to milliseconds
# Status update timer (update status more frequently)
self.status_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_status_update, self.status_timer)
self.status_timer.Start(3000) # 3 seconds
# Initialize status
self.update_status("Ready")
def create_controls(self):
# Create menu bar
self.create_menu_bar()
main_sizer = wx.BoxSizer(wx.VERTICAL)
# Main content area with splitter
splitter = wx.SplitterWindow(self, style=wx.SP_3D | wx.SP_LIVE_UPDATE)
# Left panel - notes list
left_panel = wx.Panel(splitter)
left_sizer = wx.BoxSizer(wx.VERTICAL)
notes_label = wx.StaticText(left_panel, label="Your Notes:")
font = notes_label.GetFont()
font.PointSize += 1
font = font.Bold()
notes_label.SetFont(font)
left_sizer.Add(notes_label, 0, wx.ALL, 8)
self.notes_list = wx.ListBox(left_panel, style=wx.LB_SINGLE)
self.notes_list.Bind(wx.EVT_LISTBOX, self.on_note_select)
self.notes_list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_note_double_click)
left_sizer.Add(self.notes_list, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 8)
# Note management buttons
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.new_btn = wx.Button(left_panel, label="New Note")
self.new_btn.Bind(wx.EVT_BUTTON, self.on_new_note)
btn_sizer.Add(self.new_btn, 1, wx.ALL, 3)
self.delete_btn = wx.Button(left_panel, label="Delete")
self.delete_btn.Bind(wx.EVT_BUTTON, self.on_delete_note)
btn_sizer.Add(self.delete_btn, 1, wx.ALL, 3)
left_sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 8)
left_panel.SetSizer(left_sizer)
# Right panel - editor
right_panel = wx.Panel(splitter)
right_sizer = wx.BoxSizer(wx.VERTICAL)
title_label = wx.StaticText(right_panel, label="Note Title:")
title_label.SetFont(font)
right_sizer.Add(title_label, 0, wx.ALL, 8)
self.title_ctrl = wx.TextCtrl(right_panel, style=wx.TE_PROCESS_ENTER)
self.title_ctrl.Bind(wx.EVT_TEXT, self.on_title_change)
self.title_ctrl.Bind(wx.EVT_KILL_FOCUS, self.on_title_lose_focus)
title_font = self.title_ctrl.GetFont()
title_font.PointSize += 2
self.title_ctrl.SetFont(title_font)
right_sizer.Add(self.title_ctrl, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 8)
content_label = wx.StaticText(right_panel, label="Content:")
content_label.SetFont(font)
right_sizer.Add(content_label, 0, wx.LEFT | wx.RIGHT, 8)
# Formatting toolbar
self.setup_editor_toolbar(right_panel)
right_sizer.Add(self.toolbar, 0, wx.EXPAND | wx.ALL, 8)
# Rich text editor - FIXED: Make sure editor attribute is created
self.editor = rt.RichTextCtrl(right_panel,
style=wx.VSCROLL | wx.HSCROLL | wx.BORDER_SIMPLE)
self.editor.Bind(wx.EVT_TEXT, self.on_content_change)
self.editor.Bind(wx.EVT_KILL_FOCUS, self.on_editor_lose_focus)
# Set default font
attr = rt.RichTextAttr()
attr.SetFontSize(11)
attr.SetFontFaceName("Segoe UI")
self.editor.SetBasicStyle(attr)
right_sizer.Add(self.editor, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 8)
right_panel.SetSizer(right_sizer)
# Split the window
splitter.SplitVertically(left_panel, right_panel, 250)
splitter.SetMinimumPaneSize(200)
main_sizer.Add(splitter, 1, wx.EXPAND | wx.ALL, 5)
# Status bar
self.status_bar = self.CreateStatusBar(2)
self.status_bar.SetStatusWidths([-1, 150])
self.update_status("Ready")
self.SetSizer(main_sizer)
self.enable_editor(False)
def create_menu_bar(self):
menubar = wx.MenuBar()
# File menu
file_menu = wx.Menu()
export_item = file_menu.Append(wx.ID_ANY, "&Export Notes...\tCtrl+E", "Export all notes to file")
import_item = file_menu.Append(wx.ID_ANY, "&Import Notes...\tCtrl+I", "Import notes from file")
file_menu.AppendSeparator()
export_text_item = file_menu.Append(wx.ID_ANY, "Export Current Note as &Text...\tCtrl+T", "Export current note as plain text")
file_menu.AppendSeparator()
exit_item = file_menu.Append(wx.ID_EXIT, "&Close", "Close notes window")
menubar.Append(file_menu, "&File")
# Notes menu
notes_menu = wx.Menu()
new_note_item = notes_menu.Append(wx.ID_NEW, "&New Note\tCtrl+N", "Create a new note")
delete_note_item = notes_menu.Append(wx.ID_DELETE, "&Delete Note\tDel", "Delete current note")
notes_menu.AppendSeparator()
rename_note_item = notes_menu.Append(wx.ID_ANY, "&Rename Note\tF2", "Rename current note")
menubar.Append(notes_menu, "&Notes")
# Edit menu
edit_menu = wx.Menu()
undo_item = edit_menu.Append(wx.ID_UNDO, "&Undo\tCtrl+Z", "Undo last action")
redo_item = edit_menu.Append(wx.ID_REDO, "&Redo\tCtrl+Y", "Redo last action")
edit_menu.AppendSeparator()
cut_item = edit_menu.Append(wx.ID_CUT, "Cu&t\tCtrl+X", "Cut selection")
copy_item = edit_menu.Append(wx.ID_COPY, "&Copy\tCtrl+C", "Copy selection")
paste_item = edit_menu.Append(wx.ID_PASTE, "&Paste\tCtrl+V", "Paste from clipboard")
edit_menu.AppendSeparator()
select_all_item = edit_menu.Append(wx.ID_SELECTALL, "Select &All\tCtrl+A", "Select all text")
menubar.Append(edit_menu, "&Edit")
# Format menu
format_menu = wx.Menu()
bold_item = format_menu.Append(wx.ID_ANY, "&Bold\tCtrl+B", "Bold text")
italic_item = format_menu.Append(wx.ID_ANY, "&Italic\tCtrl+I", "Italic text")
underline_item = format_menu.Append(wx.ID_ANY, "&Underline\tCtrl+U", "Underline text")
format_menu.AppendSeparator()
font_color_item = format_menu.Append(wx.ID_ANY, "&Text Color...", "Change text color")
bg_color_item = format_menu.Append(wx.ID_ANY, "&Highlight Color...", "Change highlight color")
format_menu.AppendSeparator()
clear_format_item = format_menu.Append(wx.ID_ANY, "&Clear Formatting\tCtrl+Space", "Clear text formatting")
menubar.Append(format_menu, "F&ormat")
self.SetMenuBar(menubar)
# Bind menu events
self.Bind(wx.EVT_MENU, self.on_save_to_file, export_item)
self.Bind(wx.EVT_MENU, self.on_load_from_file, import_item)
self.Bind(wx.EVT_MENU, self.on_export_text, export_text_item)
self.Bind(wx.EVT_MENU, self.on_close, exit_item)
self.Bind(wx.EVT_MENU, self.on_new_note, new_note_item)
self.Bind(wx.EVT_MENU, self.on_delete_note, delete_note_item)
self.Bind(wx.EVT_MENU, self.on_rename_note, rename_note_item)
self.Bind(wx.EVT_MENU, self.on_bold, bold_item)
self.Bind(wx.EVT_MENU, self.on_italic, italic_item)
self.Bind(wx.EVT_MENU, self.on_underline, underline_item)
self.Bind(wx.EVT_MENU, self.on_text_color, font_color_item)
self.Bind(wx.EVT_MENU, self.on_background_color, bg_color_item)
self.Bind(wx.EVT_MENU, self.on_clear_formatting, clear_format_item)
self.Bind(wx.EVT_MENU, self.on_undo, undo_item)
self.Bind(wx.EVT_MENU, self.on_redo, redo_item)
self.Bind(wx.EVT_MENU, self.on_cut, cut_item)
self.Bind(wx.EVT_MENU, self.on_copy, copy_item)
self.Bind(wx.EVT_MENU, self.on_paste, paste_item)
self.Bind(wx.EVT_MENU, self.on_select_all, select_all_item)
def setup_editor_toolbar(self, parent):
self.toolbar = wx.Panel(parent)
toolbar_sizer = wx.BoxSizer(wx.HORIZONTAL)
# Text formatting buttons
self.bold_btn = wx.Button(self.toolbar, label="B", size=(35, 30))
self.bold_btn.SetFont(self.bold_btn.GetFont().Bold())
self.bold_btn.Bind(wx.EVT_BUTTON, self.on_bold)
self.bold_btn.SetToolTip("Bold (Ctrl+B)")
toolbar_sizer.Add(self.bold_btn, 0, wx.ALL, 2)
self.italic_btn = wx.Button(self.toolbar, label="I", size=(35, 30))
font = self.italic_btn.GetFont()
font.MakeItalic()
self.italic_btn.SetFont(font)
self.italic_btn.Bind(wx.EVT_BUTTON, self.on_italic)
self.italic_btn.SetToolTip("Italic (Ctrl+I)")
toolbar_sizer.Add(self.italic_btn, 0, wx.ALL, 2)
self.underline_btn = wx.Button(self.toolbar, label="U", size=(35, 30))
self.underline_btn.Bind(wx.EVT_BUTTON, self.on_underline)
self.underline_btn.SetToolTip("Underline (Ctrl+U)")
toolbar_sizer.Add(self.underline_btn, 0, wx.ALL, 2)
toolbar_sizer.Add(wx.StaticLine(self.toolbar, style=wx.LI_VERTICAL), 0,
wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
# Font size
toolbar_sizer.Add(wx.StaticText(self.toolbar, label="Size:"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 2)
self.font_size_choice = wx.Choice(self.toolbar, choices=['8', '9', '10', '11', '12', '14', '16', '18', '20', '24'])
self.font_size_choice.SetSelection(3) # Default to 11
self.font_size_choice.Bind(wx.EVT_CHOICE, self.on_font_size)
self.font_size_choice.SetToolTip("Font Size")
toolbar_sizer.Add(self.font_size_choice, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 2)
toolbar_sizer.Add(wx.StaticLine(self.toolbar, style=wx.LI_VERTICAL), 0,
wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
# Alignment buttons
self.align_left_btn = wx.Button(self.toolbar, label="Left", size=(45, 30))
self.align_left_btn.Bind(wx.EVT_BUTTON, self.on_align_left)
self.align_left_btn.SetToolTip("Align Left")
toolbar_sizer.Add(self.align_left_btn, 0, wx.ALL, 2)
self.align_center_btn = wx.Button(self.toolbar, label="Center", size=(55, 30))
self.align_center_btn.Bind(wx.EVT_BUTTON, self.on_align_center)
self.align_center_btn.SetToolTip("Align Center")
toolbar_sizer.Add(self.align_center_btn, 0, wx.ALL, 2)
self.align_right_btn = wx.Button(self.toolbar, label="Right", size=(45, 30))
self.align_right_btn.Bind(wx.EVT_BUTTON, self.on_align_right)
self.align_right_btn.SetToolTip("Align Right")
toolbar_sizer.Add(self.align_right_btn, 0, wx.ALL, 2)
toolbar_sizer.Add(wx.StaticLine(self.toolbar, style=wx.LI_VERTICAL), 0,
wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
# Color buttons
self.text_color_btn = wx.Button(self.toolbar, label="Text Color", size=(80, 30))
self.text_color_btn.Bind(wx.EVT_BUTTON, self.on_text_color)
self.text_color_btn.SetToolTip("Text Color")
toolbar_sizer.Add(self.text_color_btn, 0, wx.ALL, 2)
self.bg_color_btn = wx.Button(self.toolbar, label="Highlight", size=(80, 30))
self.bg_color_btn.Bind(wx.EVT_BUTTON, self.on_background_color)
self.bg_color_btn.SetToolTip("Highlight Color")
toolbar_sizer.Add(self.bg_color_btn, 0, wx.ALL, 2)
toolbar_sizer.AddStretchSpacer()
# Clear formatting
self.clear_fmt_btn = wx.Button(self.toolbar, label="Clear Format", size=(100, 30))
self.clear_fmt_btn.Bind(wx.EVT_BUTTON, self.on_clear_formatting)
self.clear_fmt_btn.SetToolTip("Remove all formatting (Ctrl+Space)")
toolbar_sizer.Add(self.clear_fmt_btn, 0, wx.ALL, 2)
self.toolbar.SetSizer(toolbar_sizer)
self.toolbar.SetBackgroundColour(wx.Colour(230, 230, 230))
def enable_editor(self, enable):
self.title_ctrl.Enable(enable)
self.editor.Enable(enable)
self.delete_btn.Enable(enable)
# Enable/disable toolbar buttons
for child in self.toolbar.GetChildren():
if isinstance(child, wx.Button) or isinstance(child, wx.Choice):
child.Enable(enable)
def update_status(self, message: str):
"""Update status text with timestamp"""
timestamp = wx.DateTime.Now().FormatTime()
self.status_bar.SetStatusText(f"{timestamp} - {message}", 0)
# Update save status in second field
if self.current_note_key and self.content_changed:
time_since_change = int(time.time() - self.notes_data[self.current_note_key]['last_modified'])
self.status_bar.SetStatusText(f"Unsaved changes ({time_since_change}s)", 1)
elif self.current_note_key:
time_since_save = int(time.time() - self.last_save_time)
self.status_bar.SetStatusText(f"Saved {time_since_save}s ago", 1)
else:
self.status_bar.SetStatusText("", 1)
def load_notes_list(self):
self.notes_list.Clear()
sorted_keys = sorted(self.notes_data.keys(),
key=lambda k: self.notes_data[k].get('last_modified', 0),
reverse=True)
for key in sorted_keys:
display_title = self.notes_data[key].get('title', key)
self.notes_list.Append(display_title, key)
def save_current_note_formatting(self) -> bool:
"""Save the current note's formatting to XML"""
if not self.current_note_key or self.is_closing:
return False
try:
# Save plain text
self.notes_data[self.current_note_key]['content'] = self.editor.GetValue()
# Save XML with formatting using a temporary file
with tempfile.NamedTemporaryFile(mode='w+', suffix='.xml', delete=False, encoding='utf-8') as tmp:
tmp_path = tmp.name
# Save to temp file
handler = rt.RichTextXMLHandler()
buffer = self.editor.GetBuffer()
if buffer and handler.SaveFile(buffer, tmp_path):
# Read back the XML content
with open(tmp_path, 'r', encoding='utf-8') as f:
xml_content = f.read()
self.notes_data[self.current_note_key]['xml_content'] = xml_content
self.notes_data[self.current_note_key]['last_modified'] = time.time()
# Clean up temp file
try:
os.unlink(tmp_path)
except Exception:
pass
self.content_changed = False
self.last_save_time = time.time()
return True
except Exception as e:
print(f"Error saving note formatting: {e}")
return False
def auto_save_current_note(self):
"""Auto-save current note if changes were made"""
if self.current_note_key and self.content_changed:
if self.save_current_note_formatting():
return True
return False
def safe_load_xml_content(self, xml_content: str) -> bool:
"""Safely load XML content with error handling"""
try:
# Create a temporary file for XML loading
with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False, encoding='utf-8') as tmp:
tmp.write(xml_content)
tmp_path = tmp.name
# Clear current buffer first
self.editor.Clear()
# Load from temp file
handler = rt.RichTextXMLHandler()
success = handler.LoadFile(self.editor.GetBuffer(), tmp_path)
# Clean up temp file
try:
os.unlink(tmp_path)
except Exception:
pass
if success:
self.editor.Refresh()
return True
else:
# If XML load fails, fall back to plain text
return False
except Exception as e:
print(f"Error loading XML content: {e}")
return False
def on_note_select(self, event):
# Auto-save previous note before switching
if self.current_note_key:
self.auto_save_current_note()
selection = self.notes_list.GetSelection()
if selection != wx.NOT_FOUND:
key = self.notes_list.GetClientData(selection)
self.current_note_key = key
note_data = self.notes_data[key]
self.updating_title = True
self.title_ctrl.SetValue(note_data.get('title', ''))
self.updating_title = False
# Load content - check if it's XML format or plain text
content = note_data.get('content', '')
xml_content = note_data.get('xml_content', '')
# Clear any existing content first
self.editor.Clear()
if xml_content:
# Try to load rich text from XML
if not self.safe_load_xml_content(xml_content):
# Fallback to plain text if XML load fails
self.editor.SetValue(content)
elif content:
# Fallback to plain text
self.editor.SetValue(content)
else:
self.editor.Clear()
self.enable_editor(True)
self.editor.SetFocus()
self.content_changed = False
self.update_status(f"Loaded note: {note_data.get('title', 'Untitled')}")
def on_note_double_click(self, event):
"""Rename note on double click"""
self.on_rename_note(event)
def on_new_note(self, event):
note_id = f"note_{int(time.time() * 1000)}" # More precise timestamp
title = f"New Note {len(self.notes_data) + 1}"
self.notes_data[note_id] = {
'title': title,
'content': '',
'xml_content': '',
'created': time.time(),
'last_modified': time.time()
}
self.load_notes_list()
# Select and edit the new note
for i in range(self.notes_list.GetCount()):
if self.notes_list.GetClientData(i) == note_id:
self.notes_list.SetSelection(i)
self.on_note_select(event)
self.title_ctrl.SetFocus()
self.title_ctrl.SelectAll()
break
def on_delete_note(self, event):
if self.current_note_key:
note_title = self.notes_data[self.current_note_key].get('title', 'this note')
result = wx.MessageBox(
f"Are you sure you want to delete '{note_title}'?",
"Confirm Delete",
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION
)
if result == wx.YES:
del self.notes_data[self.current_note_key]
self.current_note_key = None
self.load_notes_list()
self.title_ctrl.SetValue('')
self.editor.Clear()
self.enable_editor(False)
self.update_status("Note deleted")
def on_rename_note(self, event):
"""Rename current note by focusing on title field"""
if self.current_note_key:
self.title_ctrl.SetFocus()
self.title_ctrl.SelectAll()
def on_title_change(self, event):
if self.current_note_key and not self.updating_title:
new_title = self.title_ctrl.GetValue().strip()
if new_title:
self.notes_data[self.current_note_key]['title'] = new_title
self.notes_data[self.current_note_key]['last_modified'] = time.time()
# Update the display in the list
for i in range(self.notes_list.GetCount()):
if self.notes_list.GetClientData(i) == self.current_note_key:
self.notes_list.SetString(i, new_title)
break
self.content_changed = True
# Auto-save immediately on title change
self.auto_save_current_note()
self.update_status("Title updated")
def on_title_lose_focus(self, event):
"""Auto-save when title loses focus"""
if self.content_changed:
self.auto_save_current_note()
event.Skip()
def on_content_change(self, event):
"""Mark content as changed for auto-save"""
if self.current_note_key:
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
# Trigger immediate auto-save on content change
wx.CallLater(1000, self.auto_save_current_note) # Save after 1 second delay
event.Skip()
def on_editor_lose_focus(self, event):
"""Auto-save when editor loses focus"""
if self.content_changed:
self.auto_save_current_note()
event.Skip()
def on_auto_save(self, event):
"""Auto-save current note on timer"""
if self.auto_save_current_note():
self.update_status(f"Auto-saved at {wx.DateTime.Now().FormatTime()}")
def on_status_update(self, event):
"""Update status bar"""
self.update_status("Ready")
# Text formatting methods
def on_bold(self, event):
self.editor.ApplyBoldToSelection()
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_italic(self, event):
self.editor.ApplyItalicToSelection()
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_underline(self, event):
self.editor.ApplyUnderlineToSelection()
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_font_size(self, event):
size = int(self.font_size_choice.GetStringSelection())
attr = rt.RichTextAttr()
attr.SetFontSize(size)
range_obj = self.editor.GetSelectionRange()
if range_obj.GetLength() > 0:
self.editor.SetStyle(range_obj, attr)
else:
# Apply to next typed text
self.editor.SetDefaultStyle(attr)
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_align_left(self, event):
attr = rt.RichTextAttr()
attr.SetAlignment(wx.TEXT_ALIGNMENT_LEFT)
# Get current paragraph range
pos = self.editor.GetInsertionPoint()
para = self.editor.GetBuffer().GetParagraphAtPosition(pos)
if para:
range_obj = para.GetRange()
self.editor.SetStyle(range_obj, attr)
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_align_center(self, event):
attr = rt.RichTextAttr()
attr.SetAlignment(wx.TEXT_ALIGNMENT_CENTRE)
pos = self.editor.GetInsertionPoint()
para = self.editor.GetBuffer().GetParagraphAtPosition(pos)
if para:
range_obj = para.GetRange()
self.editor.SetStyle(range_obj, attr)
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_align_right(self, event):
attr = rt.RichTextAttr()
attr.SetAlignment(wx.TEXT_ALIGNMENT_RIGHT)
pos = self.editor.GetInsertionPoint()
para = self.editor.GetBuffer().GetParagraphAtPosition(pos)
if para:
range_obj = para.GetRange()
self.editor.SetStyle(range_obj, attr)
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_text_color(self, event):
color_data = wx.ColourData()
color_data.SetChooseFull(True)
dlg = wx.ColourDialog(self, color_data)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour()
attr = rt.RichTextAttr()
attr.SetTextColour(color)
range_obj = self.editor.GetSelectionRange()
if range_obj.GetLength() > 0:
self.editor.SetStyle(range_obj, attr)
else:
self.editor.SetDefaultStyle(attr)
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
dlg.Destroy()
def on_background_color(self, event):
color_data = wx.ColourData()
color_data.SetChooseFull(True)
dlg = wx.ColourDialog(self, color_data)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour()
attr = rt.RichTextAttr()
attr.SetBackgroundColour(color)
range_obj = self.editor.GetSelectionRange()
if range_obj.GetLength() > 0:
self.editor.SetStyle(range_obj, attr)
else:
self.editor.SetDefaultStyle(attr)
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
dlg.Destroy()
def on_clear_formatting(self, event):
range_obj = self.editor.GetSelectionRange()
if range_obj.GetLength() > 0:
# Get the text
text = self.editor.GetRange(range_obj.GetStart(), range_obj.GetEnd())
# Delete the range and reinsert as plain text
self.editor.Delete(range_obj)
# Set basic style
attr = rt.RichTextAttr()
attr.SetFontSize(11)
attr.SetFontFaceName("Segoe UI")
attr.SetTextColour(wx.BLACK)
attr.SetBackgroundColour(wx.NullColour)
self.editor.BeginStyle(attr)
self.editor.WriteText(text)
self.editor.EndStyle()
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
# Menu command handlers
def on_undo(self, event):
if self.editor.CanUndo():
self.editor.Undo()
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_redo(self, event):
if self.editor.CanRedo():
self.editor.Redo()
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_cut(self, event):
self.editor.Cut()
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_copy(self, event):
self.editor.Copy()
def on_paste(self, event):
self.editor.Paste()
self.content_changed = True
self.notes_data[self.current_note_key]['last_modified'] = time.time()
def on_select_all(self, event):
self.editor.SelectAll()
def on_export_text(self, event):
if not self.current_note_key:
return
note_data = self.notes_data[self.current_note_key]
title = note_data.get('title', 'note')
with wx.FileDialog(self, "Export note as text",
defaultFile=f"{title}.txt",
wildcard="Text files (*.txt)|*.txt",
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as dlg:
if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetPath()
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(f"{title}\n")
f.write("=" * len(title) + "\n\n")
f.write(self.editor.GetValue())
wx.MessageBox("Note exported successfully!", "Success",
wx.OK | wx.ICON_INFORMATION)
self.update_status("Note exported to text file")
except Exception as e:
wx.MessageBox(f"Error exporting note: {e}", "Error",
wx.OK | wx.ICON_ERROR)
def on_save_to_file(self, event):
# Save current note first
if self.current_note_key:
self.auto_save_current_note()
with wx.FileDialog(self, "Export notes to file",
wildcard="JSON files (*.json)|*.json",
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as dlg:
if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetPath()
try:
# Convert defaultdict to regular dict for JSON serialization
notes_dict = {k: dict(v) for k, v in self.notes_data.items()}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(notes_dict, f, indent=2, ensure_ascii=False)
wx.MessageBox("Notes exported successfully!", "Success",
wx.OK | wx.ICON_INFORMATION)
self.update_status("All notes exported to file")
except Exception as e:
wx.MessageBox(f"Error saving file: {e}", "Error",
wx.OK | wx.ICON_ERROR)
def on_load_from_file(self, event):
with wx.FileDialog(self, "Import notes from file",
wildcard="JSON files (*.json)|*.json",
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as dlg:
if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetPath()
try:
with open(filename, 'r', encoding='utf-8') as f:
loaded_data = json.load(f)
# Auto-save current note before importing
if self.current_note_key:
self.auto_save_current_note()
# Merge with existing notes
result = wx.MessageBox(
"Replace existing notes or merge with them?\n\nYes = Replace, No = Merge",
"Import Options",
wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION
)
if result == wx.YES: # Replace
self.notes_data.clear()
self.notes_data.update(loaded_data)
self.update_status("All notes replaced with imported file")
elif result == wx.NO: # Merge
self.notes_data.update(loaded_data)
self.update_status("Imported notes merged with existing notes")
else: # Cancel
return
self.load_notes_list()
self.current_note_key = None
self.title_ctrl.SetValue('')
self.editor.Clear()
self.enable_editor(False)
wx.MessageBox("Notes imported successfully!", "Success",
wx.OK | wx.ICON_INFORMATION)
except Exception as e:
wx.MessageBox(f"Error loading file: {e}", "Error",
wx.OK | wx.ICON_ERROR)
def on_close(self, event):
"""Save everything before closing"""
self.is_closing = True
# Stop timers
self.save_timer.Stop()
self.status_timer.Stop()
# Save current note
if self.current_note_key:
self.auto_save_current_note()
self.update_status("Final auto-save completed")
# Notify parent if it has a callback
if self.parent and hasattr(self.parent, 'on_notes_closed'):
self.parent.on_notes_closed()
self.Destroy()
def get_notes_data(self):
"""Get all notes data"""
# Save current note before returning
if self.current_note_key:
self.auto_save_current_note()
return self.notes_data