Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add an dialog to enter Cabrillo data before exporting #4

Merged
merged 4 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 31 additions & 24 deletions core/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,23 @@ type Controller struct {
callHistoryFinder *callhistory.Finder
hamDXMap *hamdxmap.HamDXMap

VFO *vfo.VFO
Logbook *logbook.Logbook
QSOList *logbook.QSOList
Entry *entry.Controller
Workmode *workmode.Controller
Radio *radio.Controller
Keyer *keyer.Keyer
Callinfo *callinfo.Callinfo
Score *score.Counter
Rate *rate.Counter
ServiceStatus *ServiceStatus
NewContest *newcontest.Controller
Settings *settings.Settings
Bandmap *bandmap.Bandmap
Clusters *cluster.Clusters
Parrot *parrot.Parrot
VFO *vfo.VFO
Logbook *logbook.Logbook
QSOList *logbook.QSOList
Entry *entry.Controller
Workmode *workmode.Controller
Radio *radio.Controller
Keyer *keyer.Keyer
Callinfo *callinfo.Callinfo
Score *score.Counter
Rate *rate.Counter
ServiceStatus *ServiceStatus
NewContestController *newcontest.Controller
ExportCabrilloController *cabrillo.Controller
Settings *settings.Settings
Bandmap *bandmap.Bandmap
Clusters *cluster.Clusters
Parrot *parrot.Parrot
}

// View defines the visual functionality of the main application window.
Expand Down Expand Up @@ -146,7 +147,8 @@ func (c *Controller) Startup() {
c.configuration.Contest(),
)
c.callHistoryFinder.Notify(c.Settings)
c.NewContest = newcontest.NewController(c.Settings, c.configuration.LogDirectory())
c.NewContestController = newcontest.NewController(c.Settings, c.configuration.LogDirectory())
c.ExportCabrilloController = cabrillo.NewController()

c.bandplan = bandplan.IARURegion1 // TODO: make the bandplan configurable
c.dxccFinder = dxcc.New()
Expand Down Expand Up @@ -396,7 +398,7 @@ func proposeFilename(contestName, callsign string) string {

func (c *Controller) New() {
var err error
newContest, ok := c.NewContest.Run()
newContest, ok := c.NewContestController.Run()
if !ok {
return
}
Expand Down Expand Up @@ -538,6 +540,12 @@ func (c *Controller) SaveAs() {
}

func (c *Controller) ExportCabrillo() {
var err error
export, openCabrilloFile, ok := c.ExportCabrilloController.Run(c.Settings, c.Score.Result(), c.QSOList.All())
if !ok {
return
}

proposedName := c.proposeFilename() + ".cabrillo"
filename, ok, err := c.view.SelectSaveFile("Export Cabrillo File", c.configuration.LogDirectory(), proposedName, "*.cabrillo")
if !ok {
Expand All @@ -555,16 +563,15 @@ func (c *Controller) ExportCabrillo() {
}
defer file.Close()

err = cabrillo.Export(
file,
c.Settings,
c.Score.Result(),
c.QSOList.All()...)
err = cabrillo.Export(file, export)
if err != nil {
c.view.ShowErrorDialog("Cannot export Cabrillo to %s: %v", filename, err)
return
}
c.openWithExternalApplication(filename)

if openCabrilloFile {
c.openWithExternalApplication(filename)
}
}

func (c *Controller) ExportADIF() {
Expand Down
204 changes: 195 additions & 9 deletions core/export/cabrillo/cabrillo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,209 @@ package cabrillo
import (
"fmt"
"io"
"log"
"strings"
"text/template"
"time"

"github.com/ftl/cabrillo"
"github.com/ftl/conval"
"github.com/ftl/hamradio/callsign"
"github.com/ftl/hellocontest/core"
)

// Export writes the given QSOs to the given writer in the Cabrillo format.
// The header is very limited and needs to be completed manually after the log was written.
func Export(w io.Writer, settings core.Settings, claimedScore int, qsos ...core.QSO) error {
type View interface {
Show() bool

SetCategoryAssisted(bool)
SetCategoryBand(string)
SetCategoryMode(string)
SetCategoryOperator(string)
SetCategoryPower(string)
SetName(string)
SetEmail(string)
SetOpenAfterExport(bool)
}

type Controller struct {
view View

definition *conval.Definition
category cabrillo.Category
name string
email string
openAfterExport bool
}

func NewController() *Controller {
result := &Controller{
openAfterExport: false,
}

return result
}

func (c *Controller) SetView(view View) {
if view == nil {
panic("cabrillo.Controller.SetView must not be called with nil")
}
if c.view != nil {
panic("cabrillo.Controller.SetView was already called")
}

c.view = view
}

func (c *Controller) Run(settings core.Settings, claimedScore int, qsos []core.QSO) (*cabrillo.Log, bool, bool) {
c.definition = settings.Contest().Definition

c.updateCategorySettings()
c.view.SetName(c.name)
c.view.SetEmail(c.email)
c.view.SetOpenAfterExport(c.openAfterExport)
accepted := c.view.Show()
if !accepted {
return nil, false, false
}

export := createCabrilloLog(settings, claimedScore, qsos)
export.Category = c.category
export.Name = c.name
export.Email = c.email

return export, c.openAfterExport, true
}

func (c *Controller) Categories() []string {
if c.definition == nil {
return nil
}
result := make([]string, len(c.definition.Categories))
for i, category := range c.definition.Categories {
result[i] = category.Name
}
return result
}

func (c *Controller) SetCategory(name string) {
category, found := c.findCategory(name)
if !found {
log.Printf("no category with name %q found", name)
return
}

c.category.Assisted = convalToCabrilloAssisted(category)
c.category.Band = convalToCabrilloBand(category, c.definition.Bands)
c.category.Mode = convalToCabrilloMode(category, c.definition.Modes)
c.category.Operator = convalToCabrilloOperator(category)
c.category.Power = convalToCabrilloPower(category)
c.updateCategorySettings()
}

func (c *Controller) updateCategorySettings() {
log.Printf("new category settings: %+v", c.category)
c.view.SetCategoryAssisted(c.category.Assisted == cabrillo.Assisted)
c.view.SetCategoryBand(string(c.category.Band))
c.view.SetCategoryMode(string(c.category.Mode))
c.view.SetCategoryOperator(string(c.category.Operator))
c.view.SetCategoryPower(string(c.category.Power))
}

func (c *Controller) findCategory(name string) (conval.Category, bool) {
if c.definition == nil {
return conval.Category{}, false
}
for _, category := range c.definition.Categories {
if category.Name == name {
return category, true
}
}
return conval.Category{}, false
}

func (c *Controller) SetCategoryAssisted(assisted bool) {
if assisted {
c.category.Assisted = cabrillo.Assisted
} else {
c.category.Assisted = cabrillo.NonAssisted
}
}

func (c *Controller) CategoryBands() []string {
if c.definition.Bands == nil {
return []string{
"ALL", "160M", "80M", "40M", "20M", "15M", "10M", "6M", "4M", "2M", "222", "432",
"902", "1.2G", "2.3G", "3.4G", "5.7G", "10G", "24G", "47G", "75G", "122G", "134G",
"241G", "LIGHT", "VHF-3-BAND", "VHF-FM-ONLY",
}
}
result := make([]string, len(c.definition.Bands)+1)
result[0] = "ALL"
for i, band := range c.definition.Bands {
result[i+1] = string(convertBand(band))
}
return result
}

func (c *Controller) SetCategoryBand(band string) {
c.category.Band = cabrillo.CategoryBand(strings.ToUpper(band))
}

func (c *Controller) CategoryModes() []string {
if c.definition.Modes == nil {
return []string{"CW", "PH", "RY", "DG", "FM", "MIXED"}
}
result := make([]string, len(c.definition.Modes))
for i, mode := range c.definition.Modes {
result[i] = string(convertMode(mode))
}
if len(result) > 1 {
result = append(result, "MIXED")
}
return result
}

func (c *Controller) SetCategoryMode(mode string) {
c.category.Mode = cabrillo.CategoryMode(strings.ToUpper(mode))
}

func (c *Controller) CategoryOperators() []string {
return []string{"SINGLE-OP", "MULTI-OP", "CHECKLOG"}
}

func (c *Controller) SetCategoryOperator(operator string) {
c.category.Operator = cabrillo.CategoryOperator(strings.ToUpper(operator))
}

func (c *Controller) CategoryPowers() []string {
return []string{"QRP", "LOW", "HIGH"}
}

func (c *Controller) SetCategoryPower(power string) {
c.category.Power = cabrillo.CategoryPower(strings.ToUpper(power))
}

func (c *Controller) SetName(name string) {
c.name = name
}

func (c *Controller) SetEmail(email string) {
c.email = email
}

func (c *Controller) SetOpenAfterExport(open bool) {
c.openAfterExport = open
}

func createCabrilloLog(settings core.Settings, claimedScore int, qsos []core.QSO) *cabrillo.Log {
export := cabrillo.NewLog()
export.Callsign = settings.Station().Callsign
export.CreatedBy = "Hello Contest"
export.Contest = cabrillo.ContestIdentifier(settings.Contest().Name)
export.Contest = cabrillo.ContestIdentifier(settings.Contest().Definition.Identifier)
export.Operators = []callsign.Callsign{settings.Station().Operator}
export.GridLocator = settings.Station().Locator
export.ClaimedScore = claimedScore
export.Certificate = true

qsoData := make([]cabrillo.QSO, 0, len(qsos))
ignoredQSOs := make([]cabrillo.QSO, 0, len(qsos))
Expand All @@ -36,11 +220,13 @@ func Export(w io.Writer, settings core.Settings, claimedScore int, qsos ...core.
export.QSOData = qsoData
export.IgnoredQSOs = ignoredQSOs

return cabrillo.WriteWithTags(w, export, false, false, cabrillo.CreatedByTag, cabrillo.ContestTag,
cabrillo.CallsignTag, cabrillo.OperatorsTag, cabrillo.GridLocatorTag, cabrillo.ClaimedScoreTag,
cabrillo.Tag("SPECIFIC"), cabrillo.CategoryAssistedTag, cabrillo.CategoryBandTag, cabrillo.CategoryModeTag,
cabrillo.CategoryOperatorTag, cabrillo.CategoryPowerTag, cabrillo.ClubTag, cabrillo.NameTag,
cabrillo.EmailTag)
return export
}

// Export writes the given QSOs to the given writer in the Cabrillo format.
// The header is very limited and needs to be completed manually after the log was written.
func Export(w io.Writer, export *cabrillo.Log) error {
return cabrillo.Write(w, export, false)
}

var qrg = map[core.Band]string{
Expand Down
Loading