Files
FREEnote/main.bas

407 lines
12 KiB
QBasic
Raw Permalink Normal View History

2026-02-24 11:40:42 -05:00
' main.bas
#inclib "gtk-3"
#define __USE_GTK3__
#include once "gtk/gtk.bi"
#include once "doc.bi"
' ---------- Globals ----------
dim shared gWin as GtkWidget ptr
dim shared gView as GtkWidget ptr
dim shared gBuf as GtkTextBuffer ptr
dim shared gStatus as GtkWidget ptr
dim shared gStatusId as guint
dim shared gDoc as DocState
const APP_NAME = "FREEnote"
const APP_VERSION = "0.1"
' ---------- Helpers ----------
function AppTitle() as string
dim appBase as string = APP_NAME
dim docLabel as string
if gDoc.path <> "" then
docLabel = gDoc.path
else
docLabel = "Untitled"
end if
if gDoc.modified <> 0 then
return appBase & " - " & docLabel & " *"
else
return appBase & " - " & docLabel
end if
end function
declare function OnDeleteEvent cdecl (byval widget as GtkWidget ptr, byval event as GdkEvent ptr, byval userData as gpointer) as gboolean
declare function SaveCurrent() as integer
sub UpdateTitle()
gtk_window_set_title(GTK_WINDOW(gWin), AppTitle())
end sub
sub UpdateStatus()
' Line/Col from insert mark
dim insMark as GtkTextMark ptr = gtk_text_buffer_get_insert(gBuf)
dim it as GtkTextIter
gtk_text_buffer_get_iter_at_mark(gBuf, @it, insMark)
dim ln as integer = gtk_text_iter_get_line(@it) + 1
dim col as integer = gtk_text_iter_get_line_offset(@it) + 1
dim msg as string = "Ln " & str(ln) & ", Col " & str(col) & _
iif(gDoc.modified<>0, " (Modified)", " (Saved)")
gtk_statusbar_pop(GTK_STATUSBAR(gStatus), gStatusId)
gtk_statusbar_push(GTK_STATUSBAR(gStatus), gStatusId, msg)
end sub
sub SetBufferText(byref s as string)
gtk_text_buffer_set_text(gBuf, s, len(s))
' Clear modified flag after programmatic set
gtk_text_buffer_set_modified(gBuf, FALSE)
Doc_SetModified(gDoc, 0)
UpdateTitle()
UpdateStatus()
end sub
function GetBufferText() as string
dim a as GtkTextIter, b as GtkTextIter
gtk_text_buffer_get_bounds(gBuf, @a, @b)
dim p as zstring ptr = gtk_text_buffer_get_text(gBuf, @a, @b, TRUE)
dim textOut as string = *p
g_free(p)
return textOut
end function
' returns: 0 cancel, 1 discard, 2 save
function ConfirmDiscardChanges() as integer
if gDoc.modified = 0 then return 1
dim dlg as GtkWidget ptr
dlg = gtk_message_dialog_new( _
GTK_WINDOW(gWin), _
GTK_DIALOG_MODAL, _
GTK_MESSAGE_WARNING, _
GTK_BUTTONS_NONE, _
"This document has unsaved changes." _
)
gtk_dialog_add_button(GTK_DIALOG(dlg), "Cancel", GTK_RESPONSE_CANCEL)
gtk_dialog_add_button(GTK_DIALOG(dlg), "Discard", GTK_RESPONSE_REJECT)
gtk_dialog_add_button(GTK_DIALOG(dlg), "Save", GTK_RESPONSE_ACCEPT)
gtk_message_dialog_format_secondary_text( _
GTK_MESSAGE_DIALOG(dlg), _
"Do you want to save your changes before continuing?" _
)
dim resp as integer = gtk_dialog_run(GTK_DIALOG(dlg))
gtk_widget_destroy(dlg)
select case resp
case GTK_RESPONSE_ACCEPT
return 2
case GTK_RESPONSE_REJECT
return 1
case else
return 0
end select
end function
function WriteFile(byref path as string, byref contents as string) as integer
dim f as integer = freefile()
if open(path for output as #f) <> 0 then return 0
print #f, contents;
close #f
return 1
end function
function ReadFile(byref path as string, byref outText as string) as integer
dim f as integer = freefile()
if open(path for input as #f) <> 0 then return 0
outText = ""
while not eof(f)
dim oneLine as string
line input #f, oneLine
if outText <> "" then outText &= chr(10)
outText &= oneLine
wend
close #f
return 1
end function
function OnDeleteEvent cdecl (byval widget as GtkWidget ptr, byval event as GdkEvent ptr, byval userData as gpointer) as gboolean
dim r as integer = ConfirmDiscardChanges()
if r = 0 then return TRUE
if r = 2 then
if SaveCurrent() = 0 then return TRUE
end if
return FALSE
end function
function ChooseOpenPath() as string
dim dlg as GtkWidget ptr
dlg = gtk_file_chooser_dialog_new( _
"Open File", _
GTK_WINDOW(gWin), _
GTK_FILE_CHOOSER_ACTION_OPEN, _
"_Cancel", GTK_RESPONSE_CANCEL, _
"_Open", GTK_RESPONSE_ACCEPT, _
NULL _
)
dim result as string = ""
if gtk_dialog_run(GTK_DIALOG(dlg)) = GTK_RESPONSE_ACCEPT then
dim p as zstring ptr = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg))
if p <> 0 then
result = *p
g_free(p)
end if
end if
gtk_widget_destroy(dlg)
return result
end function
function ChooseSavePath(byref suggested as string) as string
dim dlg as GtkWidget ptr
dlg = gtk_file_chooser_dialog_new( _
"Save File", _
GTK_WINDOW(gWin), _
GTK_FILE_CHOOSER_ACTION_SAVE, _
"_Cancel", GTK_RESPONSE_CANCEL, _
"_Save", GTK_RESPONSE_ACCEPT, _
NULL _
)
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE)
if suggested <> "" then
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dlg), suggested)
end if
dim result as string = ""
if gtk_dialog_run(GTK_DIALOG(dlg)) = GTK_RESPONSE_ACCEPT then
dim p as zstring ptr = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg))
if p <> 0 then
result = *p
g_free(p)
end if
end if
gtk_widget_destroy(dlg)
return result
end function
function SaveToPath(byref path as string) as integer
dim txt as string = GetBufferText()
if WriteFile(path, txt) = 0 then
dim errDlg as GtkWidget ptr
errDlg = gtk_message_dialog_new( _
GTK_WINDOW(gWin), GTK_DIALOG_MODAL, _
GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _
"Could not save file." _
)
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(errDlg), path)
gtk_dialog_run(GTK_DIALOG(errDlg))
gtk_widget_destroy(errDlg)
return 0
end if
Doc_SetPath(gDoc, path)
gtk_text_buffer_set_modified(gBuf, FALSE)
Doc_SetModified(gDoc, 0)
UpdateTitle()
UpdateStatus()
return 1
end function
function SaveCurrent() as integer
if gDoc.path = "" then
dim chosen as string = ChooseSavePath("")
if chosen = "" then return 0
return SaveToPath(chosen)
else
return SaveToPath(gDoc.path)
end if
end function
' ---------- Callbacks ----------
sub OnBufferModifiedChanged cdecl (byval buf as GtkTextBuffer ptr, byval userData as gpointer)
dim m as gboolean = gtk_text_buffer_get_modified(buf)
Doc_SetModified(gDoc, iif(m, 1, 0))
UpdateTitle()
UpdateStatus()
end sub
sub OnMarkSet cdecl (byval buf as GtkTextBuffer ptr, byval iter as GtkTextIter ptr, byval mark as GtkTextMark ptr, byval userData as gpointer)
UpdateStatus()
end sub
sub OnNew cdecl (byval widget as GtkWidget ptr, byval userData as gpointer)
dim r as integer = ConfirmDiscardChanges()
if r = 0 then exit sub
if r = 2 then
if SaveCurrent() = 0 then exit sub
end if
Doc_SetPath(gDoc, "")
SetBufferText("")
end sub
sub OnOpen cdecl (byval widget as GtkWidget ptr, byval userData as gpointer)
dim r as integer = ConfirmDiscardChanges()
if r = 0 then exit sub
if r = 2 then
if SaveCurrent() = 0 then exit sub
end if
dim path as string = ChooseOpenPath()
if path = "" then exit sub
dim txt as string
if ReadFile(path, txt) = 0 then
dim errDlg as GtkWidget ptr
errDlg = gtk_message_dialog_new( _
GTK_WINDOW(gWin), GTK_DIALOG_MODAL, _
GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _
"Could not open file." _
)
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(errDlg), path)
gtk_dialog_run(GTK_DIALOG(errDlg))
gtk_widget_destroy(errDlg)
exit sub
end if
Doc_SetPath(gDoc, path)
SetBufferText(txt)
end sub
sub OnSave cdecl (byval widget as GtkWidget ptr, byval userData as gpointer)
SaveCurrent()
end sub
sub OnSaveAs cdecl (byval widget as GtkWidget ptr, byval userData as gpointer)
dim suggested as string = gDoc.path
dim chosen as string = ChooseSavePath(suggested)
if chosen = "" then exit sub
SaveToPath(chosen)
end sub
sub OnExit cdecl (byval widget as GtkWidget ptr, byval userData as gpointer)
gtk_widget_destroy(gWin)
end sub
sub OnAbout cdecl (byval widget as GtkWidget ptr, byval userData as gpointer)
dim dlg as GtkWidget ptr
dlg = gtk_message_dialog_new( _
GTK_WINDOW(gWin), _
GTK_DIALOG_MODAL, _
GTK_MESSAGE_INFO, _
GTK_BUTTONS_OK, _
APP_NAME & " " & APP_VERSION _
)
gtk_message_dialog_format_secondary_text( _
GTK_MESSAGE_DIALOG(dlg), _
"A lightweight text editor written in FreeBASIC + GTK3." & chr(10) & _
"Created by John Paul Wohlscheid and ChatGPT." _
)
gtk_dialog_run(GTK_DIALOG(dlg))
gtk_widget_destroy(dlg)
end sub
' ---------- UI Construction ----------
function MakeMenuBar() as GtkWidget ptr
dim menubar as GtkWidget ptr = gtk_menu_bar_new()
' File menu
dim fileItem as GtkWidget ptr = gtk_menu_item_new_with_label("File")
dim fileMenu as GtkWidget ptr = gtk_menu_new()
dim miNew as GtkWidget ptr = gtk_menu_item_new_with_label("New")
dim miOpen as GtkWidget ptr = gtk_menu_item_new_with_label("Open")
dim miSave as GtkWidget ptr = gtk_menu_item_new_with_label("Save")
dim miSaveA as GtkWidget ptr = gtk_menu_item_new_with_label("Save As")
dim sep1 as GtkWidget ptr = gtk_separator_menu_item_new()
dim miExit as GtkWidget ptr = gtk_menu_item_new_with_label("Exit")
gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), miNew)
gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), miOpen)
gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), miSave)
gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), miSaveA)
gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), sep1)
gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), miExit)
gtk_menu_item_set_submenu(GTK_MENU_ITEM(fileItem), fileMenu)
gtk_menu_shell_append(GTK_MENU_SHELL(menubar), fileItem)
g_signal_connect(miNew, "activate", G_CALLBACK(@OnNew), NULL)
g_signal_connect(miOpen, "activate", G_CALLBACK(@OnOpen), NULL)
g_signal_connect(miSave, "activate", G_CALLBACK(@OnSave), NULL)
g_signal_connect(miSaveA,"activate", G_CALLBACK(@OnSaveAs),NULL)
g_signal_connect(miExit, "activate", G_CALLBACK(@OnExit), NULL)
' Help menu
dim helpItem as GtkWidget ptr = gtk_menu_item_new_with_label("Help")
dim helpMenu as GtkWidget ptr = gtk_menu_new()
dim miAbout as GtkWidget ptr = gtk_menu_item_new_with_label("About")
gtk_menu_shell_append(GTK_MENU_SHELL(helpMenu), miAbout)
gtk_menu_item_set_submenu(GTK_MENU_ITEM(helpItem), helpMenu)
gtk_menu_shell_append(GTK_MENU_SHELL(menubar), helpItem)
g_signal_connect(miAbout, "activate", G_CALLBACK(@OnAbout), NULL)
return menubar
end function
' ---------- Main ----------
gtk_init(NULL, NULL)
Doc_Init(gDoc)
gWin = gtk_window_new(GTK_WINDOW_TOPLEVEL)
gtk_window_set_default_size(GTK_WINDOW(gWin), 900, 600)
UpdateTitle()
g_signal_connect(gWin, "delete-event", G_CALLBACK(@OnDeleteEvent), NULL)
dim vbox as GtkWidget ptr = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0)
gtk_container_add(GTK_CONTAINER(gWin), vbox)
dim menubar as GtkWidget ptr = MakeMenuBar()
gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0)
' Scrolled editor
dim scroller as GtkWidget ptr = gtk_scrolled_window_new(NULL, NULL)
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC)
gtk_box_pack_start(GTK_BOX(vbox), scroller, TRUE, TRUE, 0)
gView = gtk_text_view_new()
gtk_container_add(GTK_CONTAINER(scroller), gView)
gBuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gView))
' Statusbar
gStatus = gtk_statusbar_new()
gStatusId = gtk_statusbar_get_context_id(GTK_STATUSBAR(gStatus), "cursor")
gtk_box_pack_end(GTK_BOX(vbox), gStatus, FALSE, FALSE, 0)
' Buffer signals for modified + cursor status
g_signal_connect(gBuf, "modified-changed", G_CALLBACK(@OnBufferModifiedChanged), NULL)
g_signal_connect(gBuf, "mark-set", G_CALLBACK(@OnMarkSet), NULL)
UpdateStatus()
gtk_widget_show_all(gWin)
gtk_main()