Skip to content

feat(gui): show the server host country as a flag icon - #691

Merged
got3nks merged 1 commit into
amule-org:masterfrom
got3nks:feat/server-list-virtual-flags
Jul 29, 2026
Merged

feat(gui): show the server host country as a flag icon#691
got3nks merged 1 commit into
amule-org:masterfrom
got3nks:feat/server-list-virtual-flags

Conversation

@got3nks

@got3nks got3nks commented Jul 29, 2026

Copy link
Copy Markdown

Closes #686.

Since #440 the core resolves each ed2k server's host country and hands it to the GUI, but the two lists rendered the same piece of data two different ways: the peer list drew a flag bitmap, while the server list prefixed the Server Name column with the ISO code (de - ServerName, or ? - when unresolved). The prefix ate horizontal space in a column that is often too narrow already, and broke up the name itself.

The server list now draws the same flag the peer list does, with the same rule for the unresolved case: no icon rather than a placeholder.

Finishing the virtual-list migration

Getting an icon into that column meant finishing a migration that was started when the virtual lists landed and deliberately left out — the server list was the last report-mode CMuleListCtrl of the pair, and only a virtual list can answer OnGetItemColumnImage per row.

CServerListCtrl now derives from CMuleVirtualListCtrl and renders its cells on demand from the model via GetItemColumnText. That also buys what the other virtual lists already have: O(1) row lookup instead of the linear FindItem that RefreshServer, RemoveServer and HighlightServer each ran; a std::sort over the model instead of moving native rows; and one coalesced re-sort per update batch instead of an inline SortList per refreshed server. Ping, Users and Files are declared live-sort columns, so sorting by one of them now follows the data — which matters most right after a server-list refresh, when UDP replies arrive in bursts.

Three things had to be rebuilt against the model rather than the native items:

  • The connected server's bold font moves from a per-item wxListItem to OnGetItemAttr; HighlightServer just moves m_connected and repaints the two affected rows.
  • FitColumnsToContent measures the model itself. wxLIST_AUTOSIZE is a no-op on a virtual control — it has no native items to measure and hands back a default width — so only the header half can still be left to wxLIST_AUTOSIZE_USEHEADER.
  • RemoveAllServers collects its victims before deleting any, instead of deleting while walking row indices. The confirmations it raises run a nested event loop, and a row index does not survive that; a server pointer does.

The flags live in a small image list owned by this one control, seeded with the header sort arrows so those keep their indices, rather than in the list every CMuleListCtrl shares. Each flag is padded onto a transparent 16×16 cell — wxImageList has one fixed size and the bundled flags are 16×11, so adding them as-is would stretch them vertically.

One gate for both lists

The rule deciding which country code to show was duplicated between the two lists, and the server list's copy carried an extra thePrefs::IsGeoIPEnabled() check the peer list deliberately does not have. It is redundant on both paths: the core only emits its country tag when its resolver is on, so a tag received over EC already implies it, and the monolithic local lookup is guarded by the resolver's own IsEnabled(). On amulegui that pref is merely a mirror of the core's, refreshed at prefs-sync time, so consulting it could only ever disagree with the tag we were handed. Both lists now share GetDisplayCountryCode().

That helper is a header rather than a .cpp on purpose: the answer depends on ENABLE_IP2COUNTRY / CLIENT_GUI, and those are per-target compile definitions. CountryFlags.cpp — the obvious neighbour — is built into muleappgui, one static library linked into both binaries with neither define set, where it could only ever produce the amulegui answer.

Drive-by

Two CMuleListCtrl methods assumed native items and were dead against a virtual model: GetSelectedItems returned nothing, and DeleteAllItems left the model holding pointers to objects the caller was about to free. Both are now handled in CMuleVirtualListCtrl, which is where the other virtual lists get them from too.

No new translatable strings, so no catalog changes.

Testing

Built and run on macOS (monolithic aMule and amulegui). Verified in the running app: flags render undistorted for resolved servers and are absent for unresolved ones, header sort arrows still work, the connected server renders bold and clears on disconnect, columns fit their content on load, and server removal (including the static-server confirmation) behaves as before.

Object-level check that both build variants compile the intended branch: amule.dir/ServerListCtrl.cpp.o references CIP2Country::GetCountryCode/IsEnabled, while amulegui.dir/ServerListCtrl.cpp.o references only CCountryFlags::GetFlag — amulegui stays link-clean of the resolver.

Since amule-project#440 the core resolves each ed2k server's host country and hands it
to the GUI, but the two lists rendered the same piece of data two
different ways: the peer list drew a flag bitmap, while the server list
prefixed the Server Name column with the ISO code ("de - ServerName", or
"? - " when unresolved). The prefix ate horizontal space in a column that
is often too narrow already and broke up the name itself.

The server list now draws the same flag the peer list does, with the same
rule for the unresolved case: no icon rather than a placeholder.

Getting an icon into that column meant finishing a migration that was
started when the virtual lists landed and deliberately left out: the
server list was the last report-mode CMuleListCtrl of the pair, and only a
virtual list can answer OnGetItemColumnImage per row. CServerListCtrl now
derives from CMuleVirtualListCtrl and renders its cells on demand from the
model via GetItemColumnText, which also buys what the other virtual lists
already have -- O(1) row lookup instead of the linear FindItem that
RefreshServer, RemoveServer and HighlightServer each ran, a std::sort over
the model instead of moving native rows, and one coalesced re-sort per
update batch instead of an inline SortList per refreshed server. Ping,
Users and Files are declared live-sort columns, so sorting by one of them
now follows the data.

Three things had to be rebuilt against the model rather than the native
items:

  - The connected server's bold font moves from a per-item wxListItem to
    OnGetItemAttr; HighlightServer just moves m_connected and repaints.
  - FitColumnsToContent measures the model itself. wxLIST_AUTOSIZE is a
    no-op on a virtual control -- it has no native items to measure and
    returns a default width -- so only the header half can still be left
    to wxLIST_AUTOSIZE_USEHEADER.
  - RemoveAllServers collects its victims before deleting any, instead of
    deleting while walking row indices. The confirmations it raises run a
    nested event loop, and a row index does not survive that; a server
    pointer does.

The flags go in a small image list owned by this one control, seeded with
the header sort arrows so they keep their indices, rather than in the list
every CMuleListCtrl shares. Each flag is padded onto a transparent 16x16
cell -- wxImageList has one fixed size, and the bundled flags are 16x11,
so adding them as-is would stretch them.

The gate deciding which country code to show was duplicated between the
two lists, and the server list's copy had an extra
thePrefs::IsGeoIPEnabled() check that the peer list deliberately does not
have. It is redundant on both paths: the core only emits its country tag
when its resolver is on, so a tag received over EC already implies it, and
the monolithic local lookup is guarded by the resolver's own IsEnabled().
On amulegui that pref is merely a mirror of the core's, refreshed at
prefs-sync time, so consulting it could only ever disagree with the tag we
were handed. Both lists now share GetDisplayCountryCode().

That helper is a header, not a .cpp, because the answer depends on
ENABLE_IP2COUNTRY / CLIENT_GUI and those are per-target compile
definitions. CountryFlags.cpp -- the obvious neighbour -- is built into
muleappgui, one static library linked into both binaries with neither
define set, where it could only ever produce the amulegui answer.

Two base-class methods assumed native items and were dead against a
virtual model: GetSelectedItems returned nothing, and DeleteAllItems left
the model holding pointers to objects the caller was about to free. Both
are now handled in CMuleVirtualListCtrl.

No new translatable strings.
@got3nks
got3nks force-pushed the feat/server-list-virtual-flags branch from 0f5f000 to 0b95edb Compare July 29, 2026 10:02
@got3nks
got3nks merged commit 804fe29 into amule-org:master Jul 29, 2026
13 checks passed
@got3nks
got3nks deleted the feat/server-list-virtual-flags branch July 29, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Server list shows the country as an ISO code instead of a flag icon (follow-up to #440)

1 participant