miqt/examples/mdoutliner/main.go

190 lines
4.0 KiB
Go
Raw Permalink Normal View History

package main
import (
"embed"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/mappu/miqt/qt"
)
//go:embed README.md
var embedContent embed.FS
const (
lineNumberRole = int(qt.UserRole + 1)
)
type AppWindow struct {
w *qt.QMainWindow
cw *qt.QWidget
tabs *qt.QTabWidget
}
type AppTab struct {
tab *qt.QWidget
outline *qt.QListWidget
textArea *qt.QTextEdit
}
func NewAppTab() *AppTab {
var ret AppTab
tab := qt.NewQWidget2()
ret.tab = tab
layout := qt.NewQHBoxLayout(tab)
panes := qt.NewQSplitter2()
layout.AddWidget(panes.QWidget)
ret.outline = qt.NewQListWidget(tab)
panes.AddWidget(ret.outline.QWidget)
ret.outline.OnCurrentItemChanged(ret.handleJumpToBookmark)
ret.textArea = qt.NewQTextEdit(tab)
ret.textArea.OnTextChanged(ret.handleTextChanged)
panes.AddWidget(ret.textArea.QWidget)
panes.SetSizes([]int{250, 550})
return &ret
}
func NewAppWindow() *AppWindow {
ret := AppWindow{}
ret.w = qt.NewQMainWindow2()
ret.w.SetWindowTitle("Markdown Outliner")
// Menu
mnu := qt.NewQMenuBar2()
fileMenu := mnu.AddMenuWithTitle("&File")
newtab := fileMenu.AddAction("New Tab")
newtab.SetShortcut(qt.NewQKeySequence2("Ctrl+N"))
newtab.SetIcon(qt.QIcon_FromTheme("document-new"))
newtab.OnTriggered(func() {
ret.createTabWithContents("New Document", "")
})
open := fileMenu.AddAction("Open...")
open.SetShortcut(qt.NewQKeySequence2("Ctrl+O"))
open.SetIcon(qt.QIcon_FromTheme("document-open"))
open.OnTriggered(ret.handleFileOpen)
fileMenu.AddSeparator()
exit := fileMenu.AddAction("Exit")
exit.SetIcon(qt.QIcon_FromTheme("application-exit"))
exit.OnTriggered(func() {
os.Exit(0)
})
helpMenu := mnu.AddMenuWithTitle("&Help")
aboutQt := helpMenu.AddAction("About Qt")
aboutQt.SetIcon(qt.QIcon_FromTheme("help-about"))
aboutQt.SetShortcut(qt.NewQKeySequence2("F1"))
aboutQt.OnTriggered(func() {
qt.QApplication_AboutQt()
})
ret.w.SetMenuBar(mnu)
// Main widgets
ret.tabs = qt.NewQTabWidget(ret.w.QWidget)
ret.tabs.SetTabsClosable(true)
ret.tabs.OnTabCloseRequested(func(index int) {
ret.handleTabClose(index)
})
ret.w.SetCentralWidget(ret.tabs.QWidget)
// Add initial tab
sampleContent, err := embedContent.ReadFile("README.md")
if err != nil {
panic(err)
}
ret.createTabWithContents("README.md", string(sampleContent))
return &ret
}
func (a *AppWindow) handleTabClose(tabIndex int) {
a.tabs.RemoveTab(tabIndex)
}
func (a *AppWindow) handleFileOpen() {
fname := qt.QFileDialog_GetOpenFileName4(a.w.QWidget, "Open markdown file...", "", "Markdown files (*.md *.txt);;All Files (*)")
if fname == "" {
return
}
contents, err := ioutil.ReadFile(fname)
if err != nil {
qt.QMessageBox_Warning(a.w.QWidget, "Failed to open file", fmt.Sprintf("Opening file %q: %v", fname, err))
return
}
a.createTabWithContents(filepath.Base(fname), string(contents))
}
func (a *AppWindow) createTabWithContents(tabTitle, tabContent string) {
tab := NewAppTab()
tab.textArea.SetText(tabContent)
tabIdx := a.tabs.AddTab2(tab.tab, qt.QIcon_FromTheme("text-markdown"), tabTitle)
a.tabs.SetCurrentIndex(tabIdx)
}
func (t *AppTab) handleTextChanged() {
content := t.textArea.ToPlainText()
t.updateOutlineForContent(content)
}
func (t *AppTab) updateOutlineForContent(content string) {
t.outline.Clear()
lines := strings.Split(content, "\n")
for lineNumber, line := range lines {
if strings.HasPrefix(line, `#`) {
bookmark := qt.NewQListWidgetItem7(line, t.outline)
bookmark.SetToolTip(fmt.Sprintf("Line %d", lineNumber+1))
bookmark.SetData(lineNumberRole, qt.NewQVariant7(lineNumber))
}
}
}
func (t *AppTab) handleJumpToBookmark(current *qt.QListWidgetItem, previous *qt.QListWidgetItem) {
itm := t.outline.CurrentItem()
if itm == nil {
return
}
lineNumber := itm.Data(lineNumberRole).ToInt()
targetBlock := t.textArea.Document().FindBlockByLineNumber(lineNumber)
t.textArea.SetTextCursor(qt.NewQTextCursor4(targetBlock))
t.textArea.SetFocus()
}
func main() {
qt.NewQApplication(os.Args)
app := NewAppWindow()
app.w.Show()
qt.QApplication_Exec()
}