mirror of
https://github.com/gen2brain/cbconvert
synced 2026-06-30 09:11:54 +02:00
Add page spin to preview other pages
This commit is contained in:
+116
-48
@@ -7,9 +7,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/gen2brain/cbconvert"
|
||||
"github.com/gen2brain/iup-go/iup"
|
||||
)
|
||||
@@ -24,7 +25,7 @@ func selectRow(i int) {
|
||||
iup.GetHandle("Table").SetAttribute("FOCUSCELL", fmt.Sprintf("%d:1", i+1))
|
||||
}
|
||||
|
||||
// onSort re-syncs the files slice to the table's displayed order after a sort, so rows keep mapping to the right file.
|
||||
// onSort re-syncs the files slice to the table's displayed order after a sort.
|
||||
func onSort(ih iup.Ihandle, col int) int {
|
||||
n := len(files)
|
||||
if n < 2 {
|
||||
@@ -73,16 +74,49 @@ func onSort(ih iup.Ihandle, col int) int {
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
// appendFile adds a file as a new row to the table and the files slice.
|
||||
func appendFile(file cbconvert.File) {
|
||||
files = append(files, file)
|
||||
// addFiles appends files, natural-sorts the list, and rebuilds the table.
|
||||
func addFiles(fs []cbconvert.File) {
|
||||
if len(fs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
wasEmpty := len(files) == 0
|
||||
|
||||
var selPath string
|
||||
if index >= 0 && index < len(files) {
|
||||
selPath = files[index].Path
|
||||
}
|
||||
|
||||
files = append(files, fs...)
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
return sortorder.NaturalLess(files[i].Name, files[j].Name)
|
||||
})
|
||||
|
||||
t := iup.GetHandle("Table")
|
||||
lin := len(files)
|
||||
t.SetAttribute("NUMLIN", strconv.Itoa(lin))
|
||||
iup.SetAttributeId2(t, "", lin, 1, file.Name)
|
||||
iup.SetAttributeId2(t, "", lin, 2, cbconvert.FileType(file.Path))
|
||||
iup.SetAttributeId2(t, "", lin, 3, strconv.FormatFloat(float64(file.Stat.Size())/(1024*1024), 'f', 2, 64))
|
||||
t.SetAttribute("NUMLIN", strconv.Itoa(len(files)))
|
||||
for i, f := range files {
|
||||
lin := i + 1
|
||||
iup.SetAttributeId2(t, "", lin, 1, f.Name)
|
||||
iup.SetAttributeId2(t, "", lin, 2, cbconvert.FileType(f.Path))
|
||||
iup.SetAttributeId2(t, "", lin, 3, strconv.FormatFloat(float64(f.Stat.Size())/(1024*1024), 'f', 2, 64))
|
||||
}
|
||||
|
||||
if wasEmpty {
|
||||
selectRow(0)
|
||||
setActive()
|
||||
previewPost()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
index = -1
|
||||
for i, f := range files {
|
||||
if f.Path == selPath {
|
||||
selectRow(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
setActive()
|
||||
}
|
||||
|
||||
func previewPost() {
|
||||
@@ -90,30 +124,77 @@ func previewPost() {
|
||||
return
|
||||
}
|
||||
|
||||
width, height := previewSize()
|
||||
iup.GetHandle("Loading").SetAttributes("VISIBLE=YES, START=YES")
|
||||
if strings.ToLower(iup.GetGlobal("DRIVER")) == "motif" {
|
||||
iup.GetHandle("Preview").SetAttribute("IMAGE", "")
|
||||
file := files[index]
|
||||
|
||||
// On a new file, fetch the count first; the Page POSTMESSAGE handler clamps previewPage and renders.
|
||||
if file.Path != previewPath {
|
||||
previewPath = file.Path
|
||||
iup.GetHandle("Loading").SetAttributes("VISIBLE=YES, START=YES")
|
||||
go pageCountPost(file)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
previewRender()
|
||||
}
|
||||
|
||||
// previewRenderSize is the size the cover is rendered at.
|
||||
const previewRenderSize = 1200
|
||||
|
||||
// previewRender renders the current file at previewPage off the UI thread.
|
||||
func previewRender() {
|
||||
if index == -1 || len(files) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
file := files[index]
|
||||
|
||||
iup.GetHandle("Loading").SetAttributes("VISIBLE=YES, START=YES")
|
||||
|
||||
opts := options()
|
||||
page := previewPage
|
||||
|
||||
go func(opts cbconvert.Options) {
|
||||
conv := cbconvert.New(opts)
|
||||
|
||||
var s string
|
||||
file := files[index]
|
||||
|
||||
img, err := conv.Preview(file.Path, file.Stat, width, height)
|
||||
img, err := conv.PreviewPage(file.Path, file.Stat, page, previewRenderSize, previewRenderSize)
|
||||
if err != nil {
|
||||
s = err.Error()
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
iup.PostMessage(iup.GetHandle("Preview"), s, 0, img)
|
||||
iup.PostMessage(iup.GetHandle("Preview"), s, page, img)
|
||||
}(opts)
|
||||
}
|
||||
|
||||
// pageCountPost computes the file's page count off the UI thread and posts it, tagged with the path, to the Page spin.
|
||||
func pageCountPost(file cbconvert.File) {
|
||||
n, err := cbconvert.New(cbconvert.NewOptions()).PageCount(file.Path, file.Stat)
|
||||
if err != nil || n < 1 {
|
||||
n = 1
|
||||
}
|
||||
|
||||
iup.PostMessage(iup.GetHandle("Page"), file.Path, n, nil)
|
||||
}
|
||||
|
||||
// onPageChanged re-renders the preview for the spin's page; dedupes so spin and typing don't both fire.
|
||||
func onPageChanged() int {
|
||||
page := iup.GetHandle("Page").GetInt("VALUE") - 1
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
if page == previewPage {
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
previewPage = page
|
||||
previewRender()
|
||||
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
func onAddFiles(ih iup.Ihandle) int {
|
||||
args, err := fileDlg("Add Files", true, false, inputDirKey)
|
||||
if err != nil {
|
||||
@@ -134,21 +215,7 @@ func onAddFiles(ih iup.Ihandle) int {
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
wasEmpty := len(files) == 0
|
||||
|
||||
for _, file := range fs {
|
||||
appendFile(file)
|
||||
}
|
||||
|
||||
if wasEmpty && len(files) > 0 {
|
||||
selectRow(0)
|
||||
}
|
||||
|
||||
setActive()
|
||||
|
||||
if wasEmpty {
|
||||
previewPost()
|
||||
}
|
||||
addFiles(fs)
|
||||
}
|
||||
|
||||
return iup.DEFAULT
|
||||
@@ -174,21 +241,7 @@ func onAddDir(ih iup.Ihandle) int {
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
wasEmpty := len(files) == 0
|
||||
|
||||
for _, file := range fs {
|
||||
appendFile(file)
|
||||
}
|
||||
|
||||
if wasEmpty && len(files) > 0 {
|
||||
selectRow(0)
|
||||
}
|
||||
|
||||
setActive()
|
||||
|
||||
if wasEmpty {
|
||||
previewPost()
|
||||
}
|
||||
addFiles(fs)
|
||||
}
|
||||
|
||||
return iup.DEFAULT
|
||||
@@ -207,16 +260,31 @@ func onRemove(ih iup.Ihandle) int {
|
||||
}
|
||||
|
||||
setActive()
|
||||
previewPost()
|
||||
if len(files) == 0 {
|
||||
clearPreview()
|
||||
} else {
|
||||
previewPost()
|
||||
}
|
||||
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
// clearPreview resets the preview state and repaints the canvas to its empty state.
|
||||
func clearPreview() {
|
||||
hasCover = false
|
||||
previewPath = ""
|
||||
previewPage = 0
|
||||
|
||||
iup.GetHandle("PreviewInfo").SetAttribute("TITLE", "")
|
||||
iup.Update(iup.GetHandle("Preview"))
|
||||
}
|
||||
|
||||
func onRemoveAll(ih iup.Ihandle) int {
|
||||
index = -1
|
||||
files = make([]cbconvert.File, 0)
|
||||
|
||||
iup.GetHandle("Table").SetAttribute("NUMLIN", "0")
|
||||
clearPreview()
|
||||
setActive()
|
||||
|
||||
return iup.DEFAULT
|
||||
|
||||
@@ -33,6 +33,10 @@ var (
|
||||
|
||||
activeConv *cbconvert.Converter
|
||||
busy bool
|
||||
|
||||
previewPage int // 0-based page shown in the preview
|
||||
previewPath string // path of the file whose page range is loaded
|
||||
hasCover bool // whether a cover image is loaded for the preview canvas
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -78,7 +82,7 @@ func main() {
|
||||
img, _ := png.Decode(bytes.NewReader(appLogo))
|
||||
iup.ImageFromImage(img).SetHandle("logo")
|
||||
|
||||
dlg := iup.Dialog(layout()).SetAttributes(fmt.Sprintf(`TITLE="CBconvert %s", ICON=logo, SHRINK=YES`, appVersion)).SetHandle("dlg")
|
||||
dlg := iup.Dialog(layout()).SetAttributes(fmt.Sprintf(`TITLE="CBconvert %s", ICON=logo`, appVersion)).SetHandle("dlg")
|
||||
|
||||
dlg.SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
|
||||
sp := strings.Split(s, ": ")
|
||||
@@ -89,15 +93,6 @@ func main() {
|
||||
return iup.DEFAULT
|
||||
}))
|
||||
|
||||
dlg.SetCallback("RESIZE_CB", iup.ResizeFunc(func(ih iup.Ihandle, width, height int) int {
|
||||
iup.GetHandle("Preview").SetAttribute("IMAGE", "logo")
|
||||
iup.Refresh(ih)
|
||||
|
||||
previewPost()
|
||||
|
||||
return iup.DEFAULT
|
||||
}))
|
||||
|
||||
dlg.SetCallback("THEMECHANGED_CB", iup.ThemeChangedFunc(func(ih iup.Ihandle, darkMode int) int {
|
||||
t := iup.GetHandle("Table")
|
||||
tableRowColors(t, darkMode == 1)
|
||||
|
||||
@@ -10,6 +10,12 @@ func setActive() {
|
||||
opts := options()
|
||||
count := iup.GetHandle("Table").GetInt("NUMLIN")
|
||||
|
||||
if count > 0 && index != -1 {
|
||||
iup.GetHandle("PageBox").SetAttribute("VISIBLE", "YES")
|
||||
} else {
|
||||
iup.GetHandle("PageBox").SetAttribute("VISIBLE", "NO")
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
iup.GetHandle("Remove").SetAttribute("ACTIVE", "NO")
|
||||
iup.GetHandle("RemoveAll").SetAttribute("ACTIVE", "NO")
|
||||
|
||||
+105
-51
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image/gif"
|
||||
"math"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -58,7 +59,7 @@ func list() iup.Ihandle {
|
||||
"TITLE1": "Title",
|
||||
"TITLE2": "Type",
|
||||
"TITLE3": "Size (MiB)",
|
||||
"WIDTH1": "150",
|
||||
"WIDTH1": "300",
|
||||
"WIDTH2": "50",
|
||||
"WIDTH3": "100",
|
||||
"ALIGNMENT2": "ACENTER",
|
||||
@@ -102,21 +103,7 @@ func list() iup.Ihandle {
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
wasEmpty := len(files) == 0
|
||||
|
||||
for _, file := range fs {
|
||||
appendFile(file)
|
||||
}
|
||||
|
||||
if wasEmpty && len(files) > 0 {
|
||||
selectRow(0)
|
||||
}
|
||||
|
||||
setActive()
|
||||
|
||||
if wasEmpty {
|
||||
previewPost()
|
||||
}
|
||||
addFiles(fs)
|
||||
|
||||
return iup.DEFAULT
|
||||
}))
|
||||
@@ -124,49 +111,115 @@ func list() iup.Ihandle {
|
||||
return iup.Vbox(t)
|
||||
}
|
||||
|
||||
func previewSize() (int, int) {
|
||||
var width, height int
|
||||
sp := strings.Split(iup.GetHandle("Preview").GetAttribute("RASTERSIZE"), "x")
|
||||
if len(sp) == 2 {
|
||||
width, _ = strconv.Atoi(sp[0])
|
||||
height, _ = strconv.Atoi(sp[1])
|
||||
}
|
||||
|
||||
return width, height
|
||||
}
|
||||
|
||||
func preview() iup.Ihandle {
|
||||
return iup.Frame(
|
||||
iup.Vbox(
|
||||
iup.Label("").SetAttributes("EXPAND=YES, ALIGNMENT=ACENTER, MINSIZE=400x, IMAGE=cover").SetHandle("Preview").
|
||||
SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
|
||||
img := p.(cbconvert.Image)
|
||||
|
||||
iup.GetHandle("Loading").SetAttributes("VISIBLE=NO, STOP=YES")
|
||||
|
||||
if img.Image != nil && len(s) == 0 {
|
||||
iup.Destroy(iup.GetHandle("cover"))
|
||||
iup.ImageFromImage(img.Image).SetHandle("cover")
|
||||
|
||||
ih.SetAttribute("IMAGE", "cover")
|
||||
iup.GetHandle("PreviewInfo").SetAttribute("TITLE", fmt.Sprintf("%s (%dx%d)", img.SizeHuman, img.Width, img.Height))
|
||||
} else {
|
||||
ih.SetAttribute("IMAGE", "logo")
|
||||
iup.GetHandle("PreviewInfo").SetAttribute("TITLE", "")
|
||||
|
||||
sp := strings.Split(s, ": ")
|
||||
if len(sp) > 1 {
|
||||
iup.MessageError(ih, fmt.Sprintf("%s\n\n%s", sp[0], strings.Join(sp[1:], ": ")))
|
||||
}
|
||||
}
|
||||
|
||||
return iup.DEFAULT
|
||||
})),
|
||||
iup.Canvas().SetAttributes("EXPAND=YES, MINSIZE=400x, BORDER=NO").SetHandle("Preview").
|
||||
SetCallback("ACTION", iup.ActionFunc(drawPreview)).
|
||||
SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(previewMessage)),
|
||||
iup.Label("").SetAttributes("EXPAND=HORIZONTAL, ALIGNMENT=ACENTER").SetHandle("PreviewInfo"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// drawPreview draws the cover scaled to fit, else the logo centered.
|
||||
func drawPreview(ih iup.Ihandle) int {
|
||||
iup.DrawBegin(ih)
|
||||
defer iup.DrawEnd(ih)
|
||||
|
||||
cw, ch := iup.DrawGetSize(ih)
|
||||
iup.DrawParentBackground(ih)
|
||||
|
||||
name := "logo"
|
||||
if hasCover {
|
||||
name = "cover"
|
||||
}
|
||||
|
||||
iw, ihh, _ := iup.DrawGetImageInfo(name)
|
||||
if iw <= 0 || ihh <= 0 {
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
dw, dh := iw, ihh
|
||||
if hasCover {
|
||||
s := math.Min(float64(cw)/float64(iw), float64(ch)/float64(ihh))
|
||||
dw = int(float64(iw) * s)
|
||||
dh = int(float64(ihh) * s)
|
||||
}
|
||||
|
||||
iup.DrawImage(ih, name, (cw-dw)/2, (ch-dh)/2, dw, dh)
|
||||
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
// previewMessage receives a rendered cover from previewRender and triggers a canvas redraw.
|
||||
func previewMessage(ih iup.Ihandle, s string, i int, p any) int {
|
||||
if i != previewPage {
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
img := p.(cbconvert.Image)
|
||||
iup.GetHandle("Loading").SetAttributes("VISIBLE=NO, STOP=YES")
|
||||
|
||||
if img.Image != nil && len(s) == 0 {
|
||||
iup.Destroy(iup.GetHandle("cover"))
|
||||
iup.ImageFromImage(img.Image).SetHandle("cover")
|
||||
hasCover = true
|
||||
iup.GetHandle("PreviewInfo").SetAttribute("TITLE", fmt.Sprintf("%s (%dx%d)", img.SizeHuman, img.Width, img.Height))
|
||||
} else {
|
||||
hasCover = false
|
||||
iup.GetHandle("PreviewInfo").SetAttribute("TITLE", "")
|
||||
|
||||
sp := strings.Split(s, ": ")
|
||||
if len(sp) > 1 {
|
||||
iup.MessageError(ih, fmt.Sprintf("%s\n\n%s", sp[0], strings.Join(sp[1:], ": ")))
|
||||
}
|
||||
}
|
||||
|
||||
iup.Update(ih)
|
||||
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
// pageBox is the page-navigation spin shown in the status bar; hidden until a comic is selected.
|
||||
func pageBox() iup.Ihandle {
|
||||
return iup.Hbox(
|
||||
iup.Space().SetAttribute("SIZE", "5"),
|
||||
iup.Label("Page:"),
|
||||
iup.Space().SetAttribute("SIZE", "3"),
|
||||
iup.Text().SetAttributes(`SPIN=YES, SPINMIN=1, SPINMAX=1, VALUE=1, VISIBLECOLUMNS=3, MASK="/d*"`).SetHandle("Page").
|
||||
SetAttribute("TIP", "Preview a different page of the selected comic").
|
||||
SetCallback("SPIN_CB", iup.SpinFunc(func(ih iup.Ihandle, pos int) int {
|
||||
return onPageChanged()
|
||||
})).
|
||||
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||
return onPageChanged()
|
||||
})).
|
||||
SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
|
||||
if s != previewPath {
|
||||
return iup.DEFAULT
|
||||
}
|
||||
|
||||
ih.SetAttribute("SPINMAX", strconv.Itoa(i))
|
||||
iup.GetHandle("PageCount").SetAttribute("TITLE", fmt.Sprintf("/ %d", i))
|
||||
|
||||
if previewPage > i-1 {
|
||||
previewPage = i - 1
|
||||
}
|
||||
if previewPage < 0 {
|
||||
previewPage = 0
|
||||
}
|
||||
ih.SetAttribute("VALUE", strconv.Itoa(previewPage+1))
|
||||
|
||||
previewRender()
|
||||
|
||||
return iup.DEFAULT
|
||||
})),
|
||||
iup.Space().SetAttribute("SIZE", "3"),
|
||||
iup.Label("").SetHandle("PageCount"),
|
||||
).SetAttributes("ALIGNMENT=ACENTER, VISIBLE=NO").SetHandle("PageBox")
|
||||
}
|
||||
|
||||
func tabInput() iup.Ihandle {
|
||||
return iup.Hbox(
|
||||
iup.Vbox(
|
||||
@@ -586,6 +639,7 @@ func buttons() iup.Ihandle {
|
||||
func status() iup.Ihandle {
|
||||
return iup.Hbox(
|
||||
loading(),
|
||||
pageBox(),
|
||||
iup.Fill(),
|
||||
iup.Label("File 1 of 1").SetHandle("LabelStatus1").SetAttributes("VISIBLE=NO"),
|
||||
iup.Space().SetAttribute("SIZE", "5"),
|
||||
|
||||
Reference in New Issue
Block a user