From 28410fc2c299bfdbbbbaa59e1e2cb71dfc0f3518 Mon Sep 17 00:00:00 2001 From: Jon Booth Date: Wed, 4 Oct 2023 20:59:46 +1300 Subject: [PATCH] Move more of the flight log code into the base module. Now only one tab for the flight log with an options panel on the side to filter and save --- data/libs/FlightLog.lua | 256 +++++++++++- data/libs/utils.lua | 40 ++ data/pigui/modules/info-view/06-flightlog.lua | 363 ++++++------------ 3 files changed, 390 insertions(+), 269 deletions(-) diff --git a/data/libs/FlightLog.lua b/data/libs/FlightLog.lua index fe2253f2406..0ee2e910c61 100644 --- a/data/libs/FlightLog.lua +++ b/data/libs/FlightLog.lua @@ -15,6 +15,15 @@ local Serializer = require 'Serializer' local utils = require 'utils' +local Character = require 'Character' + + +-- required for formatting / localisation +local ui = require 'pigui' +local Lang = require 'Lang' +local l = Lang.GetResource("ui-core") +-- end of formating / localisation stuff + -- default values (private) local FlightLogSystemQueueLength = 1000 local FlightLogStationQueueLength = 1000 @@ -25,20 +34,95 @@ local FlightLogStation = {} local FlightLogCustom = {} -- a generic log entry: -LogEntry = utils.class("FlightLog.LogEntry") +LogEntry = utils.class("FlightLog.LogEntry.Base") + +function LogEntry:GetType() + local full_type = self.Class().meta.class + return string.sub( full_type, #"FlightLog.LogEntry."+1, #full_type ) +end + +-- return true if this has a modifiable entry field +function LogEntry:HasEntry() + return true +end -function LogEntry:Constructor( type, index, sort_date, entry ) + +-- return true if this has a Delete() method +function LogEntry:SupportsDelete() + return false +end + +function LogEntry:Constructor( index, sort_date, entry ) -- the index of the entry from the list which it came self.index = index -- a date that can be used to sort entries on TODO: remove this self.sort_date = sort_date -- the entry text associated with this log entry self.entry = entry - -- the type this logentry is - self.type = type + self.type = self:GetType() +end + +-- return the name for this log entry type +function LogEntry:GetLocalizedName() + return "Error" +end + +-- return an array of key value pairs, the key being localized and the +-- value being formatted appropriately. +function LogEntry:GetDataPairs() + return { "ERROR", "This should never be seen" } +end + +function LogEntry:UpdateEntry( entry ) + self.entry = entry +end + +-- convenience helper function +-- Sometimes date is empty, e.g. departure date prior to departure +-- TODO: maybe not return this at all then! +function LogEntry.formatDate(date) + return date and Format.Date(date) or nil +end + +-- Based on flight state, compose a reasonable string for location +function LogEntry.composeLocationString(location) + return string.interp(l["FLIGHTLOG_"..location[1]], + { primary_info = location[2], + secondary_info = location[3] or "", + tertiary_info = location[4] or "",}) +end + +CurrentStatusLogEntry = utils.class("FilghtLog.LogEntry.CurrentStatus", LogEntry ) + +function CurrentStatusLogEntry:HasEntry() + return false +end + + +function CurrentStatusLogEntry:Constructor() + LogEntry.Constructor( self, 0, Game.time, "" ) +end + +function CurrentStatusLogEntry:GetLocalizedName() + return l.PERSONAL_INFORMATION; end -SystemLogEntry = utils.class("FlightLog.SystemLogEntry", LogEntry) +-- return an array of key value pairs, the key being localized and the +-- value being formatted appropriately. +function CurrentStatusLogEntry:GetDataPairs() + + local player = Character.persistent.player + + return { + { l.NAME_PERSON, player.name }, + -- TODO: localize + { "Title", player.title }, + { l.RATING, l[player:GetCombatRating()] }, + { l.KILLS, string.format('%d',player.killcount) } + } +end + +SystemLogEntry = utils.class("FlightLog.LogEntry.System", LogEntry) function SystemLogEntry:Constructor( index, systemp, arrtime, deptime, entry ) @@ -48,7 +132,7 @@ function SystemLogEntry:Constructor( index, systemp, arrtime, deptime, entry ) sort_date = arrtime end - LogEntry.Constructor( self, "system", index, sort_date, entry ) + LogEntry.Constructor( self, index, sort_date, entry ) self.systemp = systemp self.arrtime = arrtime @@ -56,16 +140,31 @@ function SystemLogEntry:Constructor( index, systemp, arrtime, deptime, entry ) end +function SystemLogEntry:GetLocalizedName() + return l.LOG_SYSTEM; +end + +-- return an array of key value pairs, the key being localized and the +-- value being formatted appropriately. +function SystemLogEntry:GetDataPairs() + return { + { l.ARRIVAL_DATE, self.formatDate(self.arrtime) }, + { l.DEPARTURE_DATE, self.formatDate(self.deptime) }, + { l.IN_SYSTEM, ui.Format.SystemPath(self.systemp) }, + { l.ALLEGIANCE, self.systemp:GetStarSystem().faction.name } + } +end + function SystemLogEntry:UpdateEntry( entry ) + LogEntry.UpdateEntry( self, entry ) FlightLogSystem[self.index][4] = entry - self.entry = entry end -- a custom log entry: -CustomLogEntry = utils.class("FlightLog.CustomLogEntry", LogEntry) +CustomLogEntry = utils.class("FlightLog.LogEntry.Custom", LogEntry) function CustomLogEntry:Constructor( index, systemp, time, money, location, entry ) - LogEntry.Constructor( self, "custom", index, time, entry ) + LogEntry.Constructor( self, index, time, entry ) self.systemp = systemp self.time = time @@ -73,26 +172,79 @@ function CustomLogEntry:Constructor( index, systemp, time, money, location, entr self.location = location end +function CustomLogEntry:GetLocalizedName() + return l.LOG_CUSTOM; +end + +-- return an array of key value pairs, the key being localized and the +-- value being formatted appropriately. +function CustomLogEntry:GetDataPairs() + return { + { l.DATE, self.formatDate(self.time) }, + { l.LOCATION, self.composeLocationString(self.location) }, + { l.IN_SYSTEM, ui.Format.SystemPath(self.systemp) }, + { l.ALLEGIANCE, self.systemp:GetStarSystem().faction.name }, + { l.CASH, Format.Money(self.money) } + } +end + + function CustomLogEntry:UpdateEntry( entry ) + LogEntry.UpdateEntry( self, entry ) FlightLogCustom[self.index][5] = entry - self.entry = entry +end + +-- return true if this has a Delete() method +function CustomLogEntry:SupportsDelete() + return true +end + +function CustomLogEntry:Delete() + table.remove(FlightLogCustom, index) + + -- TODO: now all the indexes are wrong. + -- we should do away with them all! end -- a station log entry: -StationLogEntry = utils.class("FlightLog.StationLogEntry", LogEntry) +StationLogEntry = utils.class("FlightLog.LogEntry.Station", LogEntry) function StationLogEntry:Constructor( index, systemp, deptime, money, entry ) - LogEntry.Constructor( self, "station", index, deptime, entry ) + LogEntry.Constructor( self, index, deptime, entry ) self.systemp = systemp self.deptime = deptime self.money = money end +function StationLogEntry:GetLocalizedName() + return l.LOG_STATION; +end + +-- return an array of key value pairs, the key being localized and the +-- value being formatted appropriately. +function StationLogEntry:GetDataPairs() + + local station_type = "FLIGHTLOG_" .. self.systemp:GetSystemBody().type + + + return { + { l.DATE, self.formatDate(self.deptime) }, + { l.STATION, string.interp(l[station_type], + { primary_info = self.systemp:GetSystemBody().name, + secondary_info = self.systemp:GetSystemBody().parent.name }) }, +-- { l.LOCATION, self.systemp:GetSystemBody().parent.name }, + { l.IN_SYSTEM, ui.Format.SystemPath(self.systemp) }, + { l.ALLEGIANCE, self.systemp:GetStarSystem().faction.name }, + { l.CASH, Format.Money(self.money) }, + } +end + + function StationLogEntry:UpdateEntry( entry ) + LogEntry.UpdateEntry( self, entry ) FlightLogStation[self.index][4] = entry - self.entry = entry end local FlightLog @@ -101,7 +253,6 @@ FlightLog = { -- -- Group: Methods -- - -- -- Method: GetSystemPaths -- @@ -432,6 +583,83 @@ FlightLog = { } +-- +-- Method: GetLogEntries +-- +-- Parameters: +-- +-- types - An array of the types we want to fetch, nil if all of them +-- maximum - the maximum number of results to return +-- Return: +-- +-- iterator - A function which will generate the entries from the +-- log, returning one each time it is called until it +-- runs out, after which it returns nil. Each entry is +-- a child class of LogEntry +-- +-- Example: +-- +-- > for entry in FlightLog.GetLogEntries( { "Custom", "System", "Station" ) do +-- > print( entry.GetType(), entry.entry ) +-- > end +function FlightLog:GetLogEntries(types, maximum) + + -- TODO: actually just store a list of all of them as they are at startup + local type_set = utils.set.New(types) + + local all_entries = {} + + if nil == types or type_set:contains( 'Custom' ) then + for entry in self.GetCustomEntry() do + table.insert( all_entries, entry ) + end + end + + if nil == types or type_set:contains( 'Station' ) then + for entry in self.GetStationPaths() do + table.insert( all_entries, entry ) + end + end + + if nil == types or type_set:contains( 'System' ) then + for entry in self.GetSystemPaths() do + table.insert( all_entries, entry ) + end + end + + + + local function sortf( a, b ) + return a.sort_date > b.sort_date + end + + + + table.sort( all_entries, sortf ) + + -- note regardless of sort order, current status always comes first. + local currentStatus = nil + if nil == types or type_set:contains( "CurrentStatus" ) then + currentStatus = CurrentStatusLogEntry.New() + end + + local counter = 0 + maximum = maximum or #all_entries + return function () + if currentStatus then + t = currentStatus + currentStatus = nil + return t + end + if counter < maximum then + counter = counter + 1 + return all_entries[counter] + end + return nil + end +end + + -- LOGGING -- onLeaveSystem diff --git a/data/libs/utils.lua b/data/libs/utils.lua index 63d0eb6f27b..7d2015bbfd9 100644 --- a/data/libs/utils.lua +++ b/data/libs/utils.lua @@ -780,4 +780,44 @@ utils.getFromIntervals = function(array, value) return array[utils.getIndexFromIntervals(array, value)][1] end +-- a simple set class +utils.set = utils.class( "utils.set" ) + +-- Constructor +-- Build a set +-- Parameters: +-- +-- array - an array, every element of which will be added to the set. If not provided the set starts empty +-- number - some value in intervals +function utils.set:Constructor( array ) + self.set = {} + if nil ~= array then + for _, value in pairs( array ) do + self.set[value] = true + end + end +end + +function utils.set:add( value ) + self.set[value] = true +end + +function utils.set:remove( value ) + self.set[value] = nil +end + +function utils.set:contains( value ) + return self.set[value] ~= nil +end + +function utils.set:to_array() + local array = {} + for v, _ in pairs( self.set ) do + array[#array+1] = v + end + return array +end + return utils + + diff --git a/data/pigui/modules/info-view/06-flightlog.lua b/data/pigui/modules/info-view/06-flightlog.lua index 56b26b86d1c..fb185d99199 100644 --- a/data/pigui/modules/info-view/06-flightlog.lua +++ b/data/pigui/modules/info-view/06-flightlog.lua @@ -21,98 +21,32 @@ local l = Lang.GetResource("ui-core") local iconSize = ui.rescaleUI(Vector2(28, 28)) local buttonSpaceSize = iconSize --- Sometimes date is empty, e.g. departure date prior to departure -local function formatDate(date) - return date and Format.Date(date) or nil -end - --- Based on flight state, compose a reasonable string for location -local function composeLocationString(location) - return string.interp(l["FLIGHTLOG_"..location[1]], - { primary_info = location[2], - secondary_info = location[3] or "", - tertiary_info = location[4] or "",}) -end - - -function CustomEntryIterator() - local generator = FlightLog.GetCustomEntry() - - local function my_generator() - local entry = generator() - if entry ~= nil then - - function entry:write_header( formatter ) - formatter:write( l.LOG_CUSTOM ):write(":"):newline() - end - function entry:write_details( formatter ) - formatter:headerText(l.DATE, formatDate(self.time)) - formatter:headerText(l.LOCATION, composeLocationString(self.location)) - formatter:headerText(l.IN_SYSTEM, ui.Format.SystemPath(self.systemp)) - formatter:headerText(l.ALLEGIANCE, self.systemp:GetStarSystem().faction.name) - formatter:headerText(l.CASH, Format.Money(self.money)) - end +local include_player_info = true +local include_custom_log = true +local include_station_log = true +local include_system_log = true +local export_html = true - return entry - end - return nil - end +local function getIncludedSet() + o = {} + if include_player_info then o[#o+1] = "CurrentStatus" end + if include_custom_log then o[#o+1] = "Custom" end + if include_station_log then o[#o+1] = "Station" end + if include_system_log then o[#o+1] = "System" end - return my_generator + return o; end -function StationPathIterator() - local generator = FlightLog.GetStationPaths() - - local function my_generator() - local entry = generator() - if entry ~= nil then - function entry:write_header( formatter ) - formatter:write( l.LOG_STATION ):write(":"):newline() - end - function entry:write_details( formatter ) - local station_type = "FLIGHTLOG_" .. self.systemp:GetSystemBody().type - formatter:headerText(l.DATE, formatDate(self.deptime)) - formatter:headerText(l.STATION, string.interp(l[station_type], - { primary_info = self.systemp:GetSystemBody().name, - secondary_info = self.systemp:GetSystemBody().parent.name })) - -- formatter:headerText(l.LOCATION, self.systemp:GetSystemBody().parent.name) - formatter:headerText(l.IN_SYSTEM, ui.Format.SystemPath(self.systemp)) - formatter:headerText(l.ALLEGIANCE, self.systemp:GetStarSystem().faction.name) - formatter:headerText(l.CASH, Format.Money(self.money)) - end - return entry - end - return nil +function writeLogEntry( entry, formatter, write_header ) + if write_header then + formatter:write( entry:GetLocalizedName() ):newline() end - return my_generator -end - -function SystemPathIterator() - local generator = FlightLog.GetSystemPaths() - - local function my_generator() - local entry = generator() - if entry ~= nil then - function entry:write_header( formatter ) - formatter:write( l.LOG_SYSTEM ):write(":"):newline() - end - - function entry:write_details( formatter ) - formatter:headerText(l.ARRIVAL_DATE, formatDate(self.arrtime)) - formatter:headerText(l.DEPARTURE_DATE, formatDate(self.deptime)) - formatter:headerText(l.IN_SYSTEM, ui.Format.SystemPath(self.systemp)) - formatter:headerText(l.ALLEGIANCE, self.systemp:GetStarSystem().faction.name) - end - return entry - end - return nil + for _, pair in pairs( entry:GetDataPairs() ) do + formatter:headerText( pair[1], pair[2] ) end - - return my_generator end local ui_formatter = {} @@ -129,6 +63,17 @@ function ui_formatter:headerText(title, text, wrap) end end +function ui_formatter:write( string ) + ui.text( string ) + ui.sameLine() + return self +end + +function ui_formatter:newline() + ui.text( "" ) + return self +end + local text_formatter = {} function text_formatter:open( file ) @@ -201,10 +146,7 @@ function html_formatter:separator() self.file:write( "\n
\n" ) end - -entering_text_custom = false -entering_text_system = false -entering_text_station = false +entering_text = false -- Display Entry text, and Edit button, to update flightlog function inputText(entry, counter, entering_text, str, clicked) @@ -228,100 +170,56 @@ function inputText(entry, counter, entering_text, str, clicked) return entering_text end -local function renderCustomLog( formatter ) - local counter = 0 - local was_clicked = false - for entry in CustomEntryIterator() do - counter = counter + 1 - - entry:write_details( formatter ) +local function renderLog( formatter ) - ::input:: - entering_text_custom = inputText(entry, counter, - entering_text_custom, "custom", was_clicked) - ui.nextColumn() - - was_clicked = false - if ui.iconButton(icons.pencil, buttonSpaceSize, l.EDIT .. "##custom"..counter) then - was_clicked = true - -- If edit field was clicked, we want to edit _this_ iteration's field, - -- not next record's. Quick, behind you, velociraptor! - goto input - end - - if ui.iconButton(icons.trashcan, buttonSpaceSize, l.REMOVE .. "##custom" .. counter) then - FlightLog.DeleteCustomEntry(counter) - -- if we were already in edit mode, reset it, or else it carries over to next iteration - entering_text_custom = false - end - - ui.nextColumn() - ui.separator() - ui.spacing() + ui.spacing() + -- input field for custom log: + ui_formatter:headerText(l.LOG_NEW, "") + ui.sameLine() + local text, changed = ui.inputText("##inputfield", "", {"EnterReturnsTrue"}) + if changed then + FlightLog.MakeCustomEntry(text) end -end + ui.separator() --- See comments on previous function -local function renderStationLog( formatter ) local counter = 0 local was_clicked = false - for entry in StationPathIterator() do - counter = counter + 1 - - entry:write_details( formatter ) - - ::input:: - entering_text_station = inputText(entry, counter, - entering_text_station, "station", was_clicked) - ui.nextColumn() - - was_clicked = false - if ui.iconButton(icons.pencil, buttonSpaceSize, l.EDIT .. "##station"..counter) then - was_clicked = true - goto input + for entry in FlightLog:GetLogEntries(getIncludedSet()) do + counter = counter + 1 + + writeLogEntry( entry, formatter, true ) + + if entry:HasEntry() then + ::input:: + entering_text = inputText(entry, counter, + entering_text, "custom", was_clicked) + ui.nextColumn() + + was_clicked = false + if ui.iconButton(icons.pencil, buttonSpaceSize, l.EDIT .. "##custom"..counter) then + was_clicked = true + -- If edit field was clicked, we want to edit _this_ iteration's field, + -- not next record's. Quick, behind you, velociraptor! + goto input + end + else + ui.nextColumn() end - ui.nextColumn() - ui.separator() - end -end - --- See comments on previous function -local function renderSystemLog( formatter ) - local counter = 0 - local was_clicked = false - for entry in SystemPathIterator() do - counter = counter + 1 - - entry:write_details( formatter ) - - ::input:: - entering_text_system = inputText(entry, counter, - entering_text_system, "sys", was_clicked) - ui.nextColumn() - - was_clicked = false - if ui.iconButton(icons.pencil, buttonSpaceSize, l.EDIT .. "##system"..counter) then - was_clicked = true - goto input + if entry:SupportsDelete() then + if ui.iconButton(icons.trashcan, buttonSpaceSize, l.REMOVE .. "##custom" .. counter) then + entry:Delete() + -- if we were already in edit mode, reset it, or else it carries over to next iteration + entering_text = false + end end ui.nextColumn() ui.separator() + ui.spacing() end end -local function displayLog(logFn) - ui.spacing() - -- reserve a narrow right column for edit / remove icon - local width = ui.getContentRegion().x - ui.columns(2, "##flightLogColumns", false) - ui.setColumnWidth(0, width - iconSize.x) - - logFn(ui_formatter) - ui.columns(1) -end - local function checkbox(label, checked, tooltip) -- local color = colors.buttonBlue -- local changed, ret @@ -338,12 +236,6 @@ local function checkbox(label, checked, tooltip) return changed, ret end -local export_player_info = true -local export_custom_log = true -local export_station_log = true -local export_system_log = true -local export_html = true - local function exportLogs() -- TODO localize? local foldername = FileSystem.MakeUserDataDirectory( "player_logs" ) @@ -366,45 +258,10 @@ local function exportLogs() formatter:open( io.open( log_filename, "w" ) ) - if export_player_info then - formatter:headerText( l.NAME_PERSON, player.name ) - -- TODO: localize - formatter:headerText( "Title", player.title ) - formatter:headerText( l.RATING, l[player:GetCombatRating()] ) - formatter:headerText( l.KILLS, string.format('%d',player.killcount) ) - formatter:separator() - formatter:newline() - end - - all_entries = {} - - if export_custom_log then - for entry in CustomEntryIterator() do - table.insert( all_entries, entry ) - end - end - - if export_station_log then - for entry in StationPathIterator() do - table.insert( all_entries, entry ) - end - end - - if export_system_log then - for entry in SystemPathIterator() do - table.insert( all_entries, entry ) - end - end - - local function sortf( a, b ) - return a.sort_date < b.sort_date - end + for entry in FlightLog:GetLogEntries(getIncludedSet()) do - table.sort( all_entries, sortf ) + writeLogEntry(entry, formatter, true) - for i, entry in ipairs(all_entries) do - entry:write_header(formatter) - entry:write_details(formatter) if #entry.entry > 0 then formatter:headerText(l.ENTRY, entry.entry, true) end @@ -415,19 +272,20 @@ local function exportLogs() end -local function displayExportOptions() +local function displayFilterOptions() ui.spacing() local c local flight_log = true; - c,export_player_info = checkbox(l.PERSONAL_INFORMATION, export_player_info) - c,export_custom_log = checkbox(l.LOG_CUSTOM, export_custom_log) - c,export_station_log = checkbox(l.LOG_STATION, export_station_log) - c,export_system_log = checkbox(l.LOG_SYSTEM, export_system_log) + c,include_player_info = checkbox(l.PERSONAL_INFORMATION, include_player_info) + c,include_custom_log = checkbox(l.LOG_CUSTOM, include_custom_log) + c,include_station_log = checkbox(l.LOG_STATION, include_station_log) + c,include_system_log = checkbox(l.LOG_SYSTEM, include_system_log) + ui.spacing() + ui.separator() + ui.spacing() c,export_html = checkbox("HTML", export_html) - - ui.separator() ui.spacing() if ui.button(l.SAVE) then @@ -438,44 +296,37 @@ end local function drawFlightHistory() - ui.tabBarFont("#flightlog", { - - { name = l.LOG_CUSTOM, - draw = function() - ui.spacing() - -- input field for custom log: - ui_formatter:headerText(l.LOG_NEW, "") - ui.sameLine() - local text, changed = ui.inputText("##inputfield", "", {"EnterReturnsTrue"}) - if changed then - FlightLog.MakeCustomEntry(text) - end - ui.separator() - displayLog(renderCustomLog) - end }, - - { name = l.LOG_STATION, - draw = function() - displayLog(renderStationLog) - end }, - - { name = l.LOG_SYSTEM, - draw = function() - displayLog(renderSystemLog) - end }, - - -- TODO localize - { name = "Export", - draw = function() - displayExportOptions() - end } - - }, pionillium.heading) + + ui.spacing() + -- reserve a narrow right column for edit / remove icon + local width = ui.getContentRegion().x + + + ui.columns(2, "##flightLogColumns", false) + ui.setColumnWidth(0, width - (iconSize.x*3)/2) + ui.setColumnWidth(1, (iconSize.x*3)/2) + + renderLog(ui_formatter) end -local function drawLog () - ui.withFont(pionillium.body, function() - drawFlightHistory() +local function drawScreen() + + local width = ui.getContentRegion().x + + ui.columns(2, "flightLogTop", false) + + ui.setColumnWidth(0, (width*3)/4) + + ui.child( "FlightLogList", function() + ui.withFont(pionillium.body, function() + drawFlightHistory() + end) + end) + ui.nextColumn() + ui.child( "FlightLogConfig", function() + ui.withFont(pionillium.body, function() + displayFilterOptions() + end) end) end @@ -484,7 +335,9 @@ InfoView:registerView({ name = l.FLIGHT_LOG, icon = ui.theme.icons.bookmark, showView = true, - draw = drawLog, + draw = drawScreen, refresh = function() end, - debugReload = function() package.reimport() end + debugReload = function() + package.reimport() + end })