diff --git a/.gitignore b/.gitignore index ef3f3b3d..ceb23afb 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ cmd/miqt-rcc/miqt-rcc examples/helloworld/helloworld examples/helloworld6/helloworld6 examples/mdoutliner/mdoutliner +examples/mdoutliner/mdoutliner6 examples/windowsmanifest/windowsmanifest examples/uidesigner/uidesigner examples/libraries/extras-scintillaedit/extras-scintillaedit diff --git a/examples/mdoutliner6/README.md b/examples/mdoutliner6/README.md new file mode 100644 index 00000000..f00f0722 --- /dev/null +++ b/examples/mdoutliner6/README.md @@ -0,0 +1,8 @@ +# Markdown Outliner + +This is sample markdown content. + +## Features + +- You can use the outline on the left to jump to parts of the document +- Demonstrates use of the MIQT library diff --git a/examples/mdoutliner6/main.go b/examples/mdoutliner6/main.go new file mode 100644 index 00000000..ed0a3ecf --- /dev/null +++ b/examples/mdoutliner6/main.go @@ -0,0 +1,187 @@ +package main + +import ( + "embed" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + qt "github.com/mappu/miqt/qt6" +) + +//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.QWidget.AddActionWithText("New Tab") + newtab.SetShortcut(qt.NewQKeySequence2("Ctrl+N")) + newtab.SetIcon(qt.QIcon_FromTheme("document-new")) + newtab.OnTriggered(func() { + ret.createTabWithContents("New Document", "") + }) + + open := fileMenu.QWidget.AddActionWithText("Open...") + open.SetShortcut(qt.NewQKeySequence2("Ctrl+O")) + open.SetIcon(qt.QIcon_FromTheme("document-open")) + open.OnTriggered(ret.handleFileOpen) + + fileMenu.AddSeparator() + exit := fileMenu.QWidget.AddActionWithText("Exit") + exit.SetShortcut(qt.NewQKeySequence2("Ctrl+Q")) + exit.SetIcon(qt.QIcon_FromTheme("application-exit")) + exit.OnTriggered(func() { + os.Exit(0) + }) + + helpMenu := mnu.AddMenuWithTitle("&Help") + aboutQt := helpMenu.QWidget.AddActionWithText("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.NewQVariant4(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() +} diff --git a/examples/mdoutliner6/mdoutliner6.png b/examples/mdoutliner6/mdoutliner6.png new file mode 100644 index 00000000..7a35371b Binary files /dev/null and b/examples/mdoutliner6/mdoutliner6.png differ