mirror of
https://github.com/gen2brain/cbconvert
synced 2026-06-30 17:21:54 +02:00
514 lines
10 KiB
Go
514 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"sort"
|
|
"strconv"
|
|
|
|
"github.com/fvbommel/sortorder"
|
|
|
|
"github.com/gen2brain/cbconvert"
|
|
"github.com/gen2brain/cbconvert/cmd/cbconvert-gui/i18n"
|
|
"github.com/gen2brain/iup-go/iup"
|
|
)
|
|
|
|
// selectRow focuses and selects the given 0-based row in the table.
|
|
func selectRow(i int) {
|
|
if i < 0 || i >= len(files) {
|
|
return
|
|
}
|
|
|
|
index = i
|
|
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.
|
|
func onSort(ih iup.Ihandle, col int) int {
|
|
n := len(files)
|
|
if n < 2 {
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
rowKey := func(name, size string) string {
|
|
return name + "\x00" + size
|
|
}
|
|
|
|
buckets := make(map[string][]int, n)
|
|
for i, f := range files {
|
|
size := strconv.FormatFloat(float64(f.Stat.Size())/(1024*1024), 'f', 2, 64)
|
|
k := rowKey(f.Name, size)
|
|
buckets[k] = append(buckets[k], i)
|
|
}
|
|
|
|
var selPath string
|
|
if index >= 0 && index < len(files) {
|
|
selPath = files[index].Path
|
|
}
|
|
|
|
reordered := make([]cbconvert.File, 0, n)
|
|
for lin := 1; lin <= n; lin++ {
|
|
k := rowKey(iup.GetAttributeId2(ih, "", lin, 1), iup.GetAttributeId2(ih, "", lin, 3))
|
|
idxs := buckets[k]
|
|
if len(idxs) == 0 {
|
|
return iup.DEFAULT
|
|
}
|
|
reordered = append(reordered, files[idxs[0]])
|
|
buckets[k] = idxs[1:]
|
|
}
|
|
|
|
files = reordered
|
|
|
|
index = -1
|
|
if selPath != "" {
|
|
for i, f := range files {
|
|
if f.Path == selPath {
|
|
selectRow(i)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
// 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")
|
|
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() {
|
|
if index == -1 || len(files) == 0 {
|
|
return
|
|
}
|
|
|
|
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
|
|
|
|
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, 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(i18n.Lng(i18n.DlgAddFiles), true, false, inputDirKey)
|
|
if err != nil {
|
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
|
fmt.Println(err)
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
conv := cbconvert.New(options())
|
|
|
|
fs, err := conv.Files(args)
|
|
if err != nil {
|
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
|
fmt.Println(err)
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
addFiles(fs)
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
func onAddDir(ih iup.Ihandle) int {
|
|
args, err := fileDlg(i18n.Lng(i18n.DlgAddDir), false, true, inputDirKey)
|
|
if err != nil {
|
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
|
fmt.Println(err)
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
conv := cbconvert.New(options())
|
|
|
|
fs, err := conv.Files(args)
|
|
if err != nil {
|
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
|
fmt.Println(err)
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
addFiles(fs)
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
func onRemove(ih iup.Ihandle) int {
|
|
if index < 0 || index >= len(files) {
|
|
return iup.IGNORE
|
|
}
|
|
|
|
iup.GetHandle("Table").SetAttribute("DELLIN", strconv.Itoa(index+1))
|
|
files = slices.Delete(files, index, index+1)
|
|
|
|
if index >= len(files) {
|
|
index = len(files) - 1
|
|
}
|
|
|
|
setActive()
|
|
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() {
|
|
iup.Destroy(iup.GetHandle("cover"))
|
|
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
|
|
}
|
|
|
|
func onThumbnail(ih iup.Ihandle) int {
|
|
conv := cbconvert.New(options())
|
|
conv.Nfiles = len(files)
|
|
activeConv = conv
|
|
setBusy(true)
|
|
|
|
conv.OnProgress = func() {
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "progress2", 0, conv)
|
|
}
|
|
|
|
var canceled = false
|
|
conv.OnCancel = func() {
|
|
canceled = true
|
|
}
|
|
|
|
iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
|
|
if c == iup.K_ESC {
|
|
conv.Cancel()
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
}))
|
|
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "start", 0, conv)
|
|
|
|
go func(c *cbconvert.Converter) {
|
|
for _, file := range files {
|
|
if canceled {
|
|
break
|
|
}
|
|
|
|
if err := c.Thumbnail(file); err != nil {
|
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
|
fmt.Println(err)
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
|
|
}(conv)
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
func onCover(ih iup.Ihandle) int {
|
|
conv := cbconvert.New(options())
|
|
conv.Nfiles = len(files)
|
|
activeConv = conv
|
|
setBusy(true)
|
|
|
|
conv.OnProgress = func() {
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "progress2", 0, conv)
|
|
}
|
|
|
|
var canceled = false
|
|
conv.OnCancel = func() {
|
|
canceled = true
|
|
}
|
|
|
|
iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
|
|
if c == iup.K_ESC {
|
|
conv.Cancel()
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
}))
|
|
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "start", 0, conv)
|
|
|
|
go func(c *cbconvert.Converter) {
|
|
for _, file := range files {
|
|
if canceled {
|
|
break
|
|
}
|
|
|
|
if err := c.Cover(file); err != nil {
|
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
|
fmt.Println(err)
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
|
|
}(conv)
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
func onConvert(ih iup.Ihandle) int {
|
|
if busy {
|
|
if activeConv != nil {
|
|
activeConv.Cancel()
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
conv := cbconvert.New(options())
|
|
conv.Nfiles = len(files)
|
|
activeConv = conv
|
|
setBusy(true)
|
|
|
|
conv.OnStart = func() {
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "convert", 0, conv)
|
|
}
|
|
|
|
conv.OnProgress = func() {
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "progress", 0, conv)
|
|
}
|
|
|
|
iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
|
|
if c == iup.K_ESC {
|
|
conv.Cancel()
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
})).SetCallback("CLOSE_CB", iup.CloseFunc(func(ih iup.Ihandle) int {
|
|
if err := os.RemoveAll(conv.Workdir); err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
}))
|
|
|
|
convertErr := func(err error) {
|
|
if errors.Is(err, context.Canceled) {
|
|
if err := os.RemoveAll(conv.Workdir); err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
|
fmt.Println(err)
|
|
|
|
if err := os.RemoveAll(conv.Workdir); err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
go func(c *cbconvert.Converter) {
|
|
if c.Opts.Combine {
|
|
if err := c.Combine(files); err != nil {
|
|
convertErr(err)
|
|
}
|
|
} else {
|
|
for _, file := range files {
|
|
if err := c.Convert(file); err != nil {
|
|
convertErr(err)
|
|
if errors.Is(err, context.Canceled) {
|
|
break
|
|
}
|
|
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
|
|
}(conv)
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
func onOutputDirectory(ih iup.Ihandle) int {
|
|
args, err := fileDlg(i18n.Lng(i18n.DlgOutputDir), false, true, outputDirKey)
|
|
if err != nil {
|
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
|
fmt.Println(err)
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
if len(args) == 1 {
|
|
iup.GetHandle("OutDir").SetAttribute("VALUE", args[0])
|
|
}
|
|
|
|
setActive()
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
func onOutputFile(ih iup.Ihandle) int {
|
|
name := saveDlg(i18n.Lng(i18n.DlgOutputFile), outputDirKey)
|
|
if name != "" {
|
|
iup.GetHandle("OutFile").SetAttribute("VALUE", filepath.Base(name))
|
|
iup.GetHandle("OutDir").SetAttribute("VALUE", filepath.Dir(name))
|
|
setActive()
|
|
}
|
|
|
|
return iup.DEFAULT
|
|
}
|
|
|
|
func onFilterChanged(ih iup.Ihandle) int {
|
|
switch ih.GetInt("VALUE") {
|
|
case 1:
|
|
ih.SetAttribute("TIP", i18n.Lng(i18n.FilterNearest))
|
|
case 2:
|
|
ih.SetAttribute("TIP", i18n.Lng(i18n.FilterBox))
|
|
case 3:
|
|
ih.SetAttribute("TIP", i18n.Lng(i18n.FilterLinear))
|
|
case 4:
|
|
ih.SetAttribute("TIP", i18n.Lng(i18n.FilterMitchell))
|
|
case 5:
|
|
ih.SetAttribute("TIP", i18n.Lng(i18n.FilterCatmull))
|
|
case 6:
|
|
ih.SetAttribute("TIP", i18n.Lng(i18n.FilterGaussian))
|
|
case 7:
|
|
ih.SetAttribute("TIP", i18n.Lng(i18n.FilterLanczos))
|
|
}
|
|
|
|
previewPost()
|
|
|
|
return iup.DEFAULT
|
|
}
|