first working version
This commit is contained in:
406
main.bas
Normal file
406
main.bas
Normal file
@@ -0,0 +1,406 @@
|
||||
' 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()
|
||||
Reference in New Issue
Block a user