Compare commits
3 Commits
67e87e9731
...
fa3bc3cafe
| Author | SHA1 | Date | |
|---|---|---|---|
| fa3bc3cafe | |||
| a5ef038823 | |||
| 58b32a6cb3 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
metar
|
||||
metar.rcc
|
||||
61
main.qml
61
main.qml
@@ -23,7 +23,68 @@ Kirigami.ApplicationWindow {
|
||||
onTriggered: addAirport.open()
|
||||
}
|
||||
}
|
||||
Kirigami.CardsListView {
|
||||
anchors.fill: parent
|
||||
id: cardLayout
|
||||
model: logic.metarList
|
||||
delegate: metarListDelegate
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: metarListDelegate
|
||||
Kirigami.AbstractCard {
|
||||
contentItem: Item {
|
||||
implicitWidth: delegateLayout.implicitWidth
|
||||
implicitHeight: delegateLayout.implicitHeight
|
||||
ColumnLayout {
|
||||
id: delegateLayout
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
text: stationId
|
||||
}
|
||||
Kirigami.Heading {
|
||||
text: flightCategory
|
||||
|
||||
// Dynamically set the color based on the flight category
|
||||
Component.onCompleted: {
|
||||
color = Qt.binding(function() {
|
||||
if (flightCategory == "VFR") {
|
||||
return "green";
|
||||
} else if (flightCategory == "MVFR") {
|
||||
return "blue";
|
||||
} else if (flightCategory == "IFR") {
|
||||
return "red";
|
||||
} else if (flightCategory == "LIFR") {
|
||||
return "fuchsia";
|
||||
} else {
|
||||
return "white";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: "edit-delete"
|
||||
onClicked: logic.deleteAirport(stationId)
|
||||
}
|
||||
}
|
||||
Controls.Label {
|
||||
text: rawMetar
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: addAirport
|
||||
|
||||
|
||||
136
metar.nim
136
metar.nim
@@ -1,4 +1,5 @@
|
||||
import std/[httpclient, times, xmlparser, xmltree]
|
||||
import std/[httpclient, os, sequtils, sugar, strutils, xmlparser, xmltree]
|
||||
import Tables
|
||||
import strformat
|
||||
import NimQml
|
||||
import macros
|
||||
@@ -10,7 +11,11 @@ proc getMetar(client: HttpClient, code: string): XMLNode =
|
||||
parseXml(metarData)
|
||||
|
||||
proc childString(node: XMLnode, name: string): string =
|
||||
node.child(name).innerText
|
||||
let node = node.child(name)
|
||||
if node != nil:
|
||||
result = node.innerText
|
||||
else:
|
||||
result = ""
|
||||
|
||||
type SkyCondition = object
|
||||
skyCover: string
|
||||
@@ -29,7 +34,7 @@ type MetarData = object
|
||||
flightCategory: string
|
||||
skyConditions: seq[SkyCondition]
|
||||
|
||||
proc fromXml*(xmlData: XMLNode): MetarData =
|
||||
proc newMetarData*(xmlData: XMLNode): MetarData =
|
||||
let metar = xmlData.child("data").child("METAR")
|
||||
|
||||
result = MetarData(
|
||||
@@ -39,7 +44,7 @@ proc fromXml*(xmlData: XMLNode): MetarData =
|
||||
temperature: metar.childString("temp_c"),
|
||||
dewPoint: metar.childString("dewpoint_c"),
|
||||
windHeading: metar.childString("wind_dir_degrees"),
|
||||
visibility: metar.childString("wisibility_statute_mi"),
|
||||
visibility: metar.childString("visibility_statute_mi"),
|
||||
altimiter: metar.childString("altim_in_hg"),
|
||||
flightCategory: metar.childString("flight_category")
|
||||
)
|
||||
@@ -50,31 +55,129 @@ proc fromXml*(xmlData: XMLNode): MetarData =
|
||||
skyCover: skyConditionXml.attr("sky_cover"),
|
||||
cloudBaseAgl: skyconditionXml.attr("cloud_base_ft_agl")))
|
||||
|
||||
type AirportRoles {.pure.} = enum
|
||||
RawMetar = UserRole + 1
|
||||
FlightCategory = UserRole + 2
|
||||
StationId = UserRole + 3
|
||||
|
||||
proc readConfig(): seq[string] =
|
||||
let metarConfigDir = getConfigDir() & "/metarweather"
|
||||
let metarConf = metarConfigDir & "/metarweather.conf"
|
||||
|
||||
if not dirExists(metarConfigDir):
|
||||
createDir(metarConfigDir)
|
||||
|
||||
if not fileExists(metarConf):
|
||||
return @[]
|
||||
|
||||
let config = readFile(metarConf)
|
||||
|
||||
if config.isEmptyOrWhiteSpace:
|
||||
return @[]
|
||||
|
||||
return config.strip().split(",")
|
||||
|
||||
proc writeConfig(airports: seq[string]) =
|
||||
let config = airports.join(",")
|
||||
|
||||
let metarConfigDir = getConfigDir() & "/metarweather"
|
||||
let metarConf = metarConfigDir & "/metarweather.conf"
|
||||
|
||||
writeFile(metarConf, config)
|
||||
|
||||
|
||||
proc getMetars(airports: seq[string]): seq[MetarData] =
|
||||
let client = newHttpClient()
|
||||
airports.map(airportCode => client.getMetar(airportCode)).map(metarXml => newMetarData(metarXml))
|
||||
|
||||
|
||||
QtObject:
|
||||
type MetarList* = ref object of QAbstractListModel
|
||||
airports*: seq[MetarData]
|
||||
|
||||
proc delete(self: MetarList) =
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: MetarList) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newMetarList*(airports: seq[MetarData]): MetarList =
|
||||
new(result, delete)
|
||||
result.airports = airports
|
||||
result.setup
|
||||
|
||||
method rowCount(self: MetarList, index: QModelIndex = nil): int =
|
||||
self.airports.len
|
||||
|
||||
method data(self: MetarList, index: QModelIndex, role: int): QVariant =
|
||||
if not index.isValid:
|
||||
return
|
||||
if index.row < 0 or index.row >= self.airports.len:
|
||||
return
|
||||
let airport = self.airports[index.row]
|
||||
let airportRole = role.AirportRoles
|
||||
case airportRole:
|
||||
of AirportRoles.RawMetar: result = newQVariant(airport.rawText)
|
||||
of AirportRoles.FlightCategory: result = newQVariant(airport.flightCategory)
|
||||
of AirportRoles.StationId: result = newQVariant(airport.stationId)
|
||||
|
||||
method roleNames(self: MetarList): Table[int, string] =
|
||||
{ AirportRoles.RawMetar.int:"rawMetar",
|
||||
AirportRoles.FlightCategory.int:"flightCategory",
|
||||
AirportRoles.StationId.int:"stationId"}.toTable
|
||||
|
||||
QtObject:
|
||||
type ApplicationLogic* = ref object of QObject
|
||||
app: QApplication
|
||||
metarList: MetarList
|
||||
|
||||
proc delete*(self: ApplicationLogic) =
|
||||
self.QObject.delete
|
||||
self.metarList.delete
|
||||
|
||||
proc setup(self: ApplicationLogic) =
|
||||
self.QObject.setup
|
||||
|
||||
proc getMetarList(self: ApplicationLogic): QVariant {.slot.} =
|
||||
return newQVariant(self.metarList)
|
||||
|
||||
proc metarListChanged(self: ApplicationLogic, metarList: QVariant) {.signal.}
|
||||
|
||||
proc newApplicationLogic*(app: QApplication): ApplicationLogic =
|
||||
new(result)
|
||||
proc newApplicationLogic*(app: QApplication, airports: seq[MetarData]): ApplicationLogic =
|
||||
new(result, delete)
|
||||
result.app = app
|
||||
result.metarList = newMetarList(airports)
|
||||
result.setup()
|
||||
|
||||
## Real methods below here
|
||||
|
||||
proc refresh(self: ApplicationLogic) {.slot.} =
|
||||
echo "Refresh called"
|
||||
let airports = self.metarList.airports.map(metar => metar.stationId)
|
||||
|
||||
self.metarList.delete
|
||||
self.metarList = newMetarList(airports.getMetars())
|
||||
self.metarListChanged(newQVariant(self.metarList))
|
||||
|
||||
proc addAirport(self: ApplicationLogic, code: string) {.slot.} =
|
||||
echo &"Add airport called {code}"
|
||||
let airports = self.metarList.airports.map(metar => metar.stationId) & code
|
||||
|
||||
writeConfig(airports)
|
||||
|
||||
self.metarList.delete
|
||||
self.metarList = newMetarList(airports.getMetars())
|
||||
self.metarListChanged(newQVariant(self.metarList))
|
||||
|
||||
proc deleteAirport(self: ApplicationLogic, code: string) {.slot.} =
|
||||
echo &"Delete {code}"
|
||||
let airports = self.metarList.airports.map(metar => metar.stationId)
|
||||
.filter(stationId => stationId != code)
|
||||
|
||||
writeConfig(airports)
|
||||
|
||||
self.metarList.delete
|
||||
self.metarList = newMetarList(airports.getMetars())
|
||||
self.metarListChanged(newQVariant(self.metarList))
|
||||
|
||||
QtProperty[QVariant] metarList:
|
||||
read = getMetarList
|
||||
notify = metarListChanged
|
||||
|
||||
proc mainProc() =
|
||||
let app = newQApplication()
|
||||
@@ -83,11 +186,18 @@ proc mainProc() =
|
||||
let engine = newQQmlApplicationEngine()
|
||||
defer: engine.delete()
|
||||
|
||||
let logic = newApplicationLogic(app)
|
||||
let airports = readConfig()
|
||||
let metarData = getMetars(airports)
|
||||
|
||||
let logic = newApplicationLogic(app, metarData)
|
||||
let logicVariant = newQVariant(logic)
|
||||
|
||||
engine.setRootContextProperty("logic", logicVariant)
|
||||
engine.load("main.qml")
|
||||
|
||||
let appDirPath = app.applicationDirPath & "/" & "metar.rcc"
|
||||
QResource.registerResource(appDirPath)
|
||||
engine.load(newQUrl("qrc:///main.qml"))
|
||||
|
||||
app.exec()
|
||||
|
||||
when isMainModule:
|
||||
|
||||
18
metar.nimble
Normal file
18
metar.nimble
Normal file
@@ -0,0 +1,18 @@
|
||||
# Package
|
||||
|
||||
version = "0.1.0"
|
||||
author = "Zoe Moore"
|
||||
description = "Aviation metar weather checking app"
|
||||
license = "GPL-3.0-or-later"
|
||||
bin = @["metar"]
|
||||
|
||||
|
||||
# Dependencies
|
||||
|
||||
requires @["nim >= 1.4.8", "nimqml >= 0.9.0"]
|
||||
|
||||
task build, "Compile the binary":
|
||||
exec ("nim -d:ssl -d:release c metar")
|
||||
|
||||
before build:
|
||||
exec ("rcc --binary resources.qrc -o metar.rcc")
|
||||
6
resources.qrc
Normal file
6
resources.qrc
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource>
|
||||
<file>main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
Reference in New Issue
Block a user