Skip to content

Conversation

@Sunnyl75
Copy link

@Sunnyl75 Sunnyl75 commented Oct 22, 2025

Updates Token Manager to Qt 6. Sets checkboxes default to unchecked and tokenSizeComboBox default to 32 px. Built and validated in alt CI; identical commits here.

Summary by Sourcery

Introduce full token management and rendering for characters in both group and map views, including custom overrides, ghost tokens for removed NPCs, and new user preferences defaulting to 32px icons.

New Features:

  • Add TokenManager to load, cache, and upload custom character tokens from a tokens directory
  • Allow setting or clearing individual character icons via context menu in the group widget
  • Render tokens in the group list and map canvas with support for ghost tokens of removed NPCs

Enhancements:

  • Refactor GroupModel to use helper functions for display and tooltip roles and extract insertion logic
  • Introduce GhostRegistry to track removed NPC positions and render ghost icons on the map
  • Extend CGroupChar and CharacterBatch to pass display names for token lookup and rendering

Build:

  • Include tokenmanager.cpp and tokenmanager.h in the build sources

Documentation:

  • Update README to point to the wiki build instructions

Sunnyl75 and others added 30 commits September 18, 2025 08:13
…hing

- Introduced a new CHARACTER_TOKEN column to the Group Manager table.
- Integrated TokenManager for resolving and loading character token images.
- Implemented fallback logic and QPixmapCache in TokenManager to reduce redundant image loading.
- Added GroupDelegate logic to render icons, with default size set to 32x32 pixels.
- Suppressed repeated debug output now that caching is effective.
- Layout and header adjusted to support the new Icon column without disrupting existing columns.

Foundation laid for future enhancements, including token toggle and dynamic resizing.
* Uploads token PNGs once and registers them in the live OpenGL renderer
* Player token drawn immediately; mounts/NPCs hidden in player???s room
* Prevents duplicate overlays and black fallback
… icon; paint background first; move header resize after model set; recalc token column/rows on icon-size change
…zeHint matches scaled icon; revert all-columns auto-resize; keep per-column header setup after model set
…icon; sizeHint matches scaled icon; revert all-columns auto-resize; keep per-column header setup after model set"

This reverts commit bf166d2.
…o scaled icon; paint background first; move header resize after model set; recalc token column/rows on icon-size change"

This reverts commit a1bde36.
@sourcery-ai
Copy link

sourcery-ai bot commented Oct 22, 2025

Reviewer's Guide

This PR adds a new TokenManager to handle custom character icons, integrates token rendering into the group list and map (including NPC ghost tokens), introduces new group preferences (show/hide tokens, map tokens, NPC ghosts, and configurable token size with defaults unchecked and 32px), refactors GroupModel character insertion/removal to use a helper and ghost registry, and updates build and docs accordingly.

Sequence diagram for updating character token and map rendering

sequenceDiagram
    actor User
    participant GroupWidget
    participant TokenManager
    participant MapCanvas
    User->>GroupWidget: Set icon for character
    GroupWidget->>TokenManager: getToken(key)
    GroupWidget->>GroupWidget: slot_updateLabels()
    GroupWidget->>MapCanvas: sig_characterUpdated(character)
    MapCanvas->>TokenManager: getToken(key)
    MapCanvas->>MapCanvas: slot_requestUpdate()
    MapCanvas->>MapCanvas: paintCharacters() (uses token icon)
Loading

Entity relationship diagram for new group manager settings

erDiagram
    GROUP_MANAGER_SETTINGS {
        bool showTokens
        bool showMapTokens
        int tokenIconSize
        bool showNpcGhosts
        QMap tokenOverrides
    }
    TOKEN_OVERRIDES {
        QString characterName
        QString tokenKey
    }
    GROUP_MANAGER_SETTINGS ||--o{ TOKEN_OVERRIDES : contains
Loading

Class diagram for new and updated token management classes

classDiagram
class TokenManager {
  +QPixmap getToken(key: QString)
  +static QString overrideFor(displayName: QString)
  +const QMap<QString, QString> &availableFiles()
  +MMTextureId textureIdFor(key: QString)
  +MMTextureId uploadNow(key: QString, px: QPixmap)
  +void rememberUpload(key: QString, id: MMTextureId, tex: SharedMMTexture)
  +SharedMMTexture textureById(id: MMTextureId) const
  -void scanDirectories()
  -QMap<QString, QString> m_availableFiles
  -QFileSystemWatcher m_watcher
  -QMap<QString, QString> m_tokenPathCache
  -QPixmap m_fallbackPixmap
  -QHash<QString, MMTextureId> m_textureCache
  -QVector<SharedMMTexture> m_ownedTextures
}
class CGroupChar {
  -QString m_characterToken
  +QString getDisplayName() const
  +void setCharacterToken(tokenPath: QString)
  +const QString &getCharacterToken() const
  +inline bool isMount() const
}
class GhostInfo {
  +QString tokenKey
}
class GhostRegistry {
}

TokenManager <.. CGroupChar : uses
CGroupChar <.. GhostRegistry : used for ghost tokens
GhostRegistry <.. GhostInfo : contains
Loading

File-Level Changes

Change Details Files
Add TokenManager for loading, caching and uploading token pixmaps and textures
  • Implement TokenManager singleton with directory scanning, override lookup and QFileSystemWatcher
  • Add pixmap fetch, normalization of keys, and fallback handling
  • Expose textureIdFor, uploadNow, rememberUpload, textureById APIs
  • Register tokenmanager.cpp/h in CMakeLists
src/group/tokenmanager.cpp
src/group/tokenmanager.h
CMakeLists.txt
Integrate tokens into group list UI with context menu actions
  • Pass TokenManager to GroupModel and extract decoration logic into makeDisplayRole/makeTooltipRole
  • Use tm->getToken to display icon column and adjust dataForCharacter implementation
  • Add Set & Use Default icon actions to GroupWidget context menu
  • Adjust table icon size and row height based on tokenIconSize config
src/group/groupwidget.cpp
src/group/groupwidget.h
Render map tokens and NPC ghost tokens in CharacterBatch
  • Extend drawCharacter and drawBox to accept dispName and queue token quads
  • Upload and bind textures for tokens under colored overlay
  • Introduce GhostRegistry and use g_ghosts to render ghost tokens on map
  • Clear and manage m_charTokenQuads and m_charTokenKeys buffers
src/display/Characters.cpp
src/display/Characters.h
src/display/GhostRegistry.h
Add and wire new group preferences for token visibility, map tokens, NPC ghosts, and token size
  • Add showTokens, showMapTokens, showNpcGhosts, tokenIconSize, tokenOverrides to Configuration::GroupManagerSettings
  • Read/write these new keys in configuration.cpp
  • Add checkboxes and tokenSizeComboBox in GroupPage with default unchecked and 32px selection
  • Emit sig_groupSettingsChanged on state changes
src/preferences/grouppage.cpp
src/preferences/grouppage.h
src/configuration/configuration.cpp
src/configuration/configuration.h
Refactor GroupModel character insertion/removal and introduce ghost handling helper
  • Extract insertNewCharactersInto helper to simplify NPC sorting logic
  • Replace manual insertion in setCharacters with helper call
  • On removeCharacterById, store a ghost entry for NPCs when showNpcGhosts is enabled
src/group/groupwidget.cpp
Miscellaneous build updates and documentation tweaks
  • Include display/GhostRegistry.h in main target sources
  • Update README link for build instructions
  • Trigger CI via README change
README.md
CMakeLists.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/group/groupwidget.cpp:850` </location>
<code_context>
+        emit sig_characterUpdated(selectedCharacter);
     });

+    connect(m_table, &QAbstractItemView::clicked, this, &GroupWidget::showContextMenu);
+
     connect(m_group, &Mmapper2Group::sig_characterAdded, this, &GroupWidget::slot_onCharacterAdded);
</code_context>

<issue_to_address>
**question:** Context menu is now shown on click, which may interfere with row selection.

Directly connecting showContextMenu to the clicked signal may disrupt standard row selection or double-click behavior. Please review if this impacts usability and consider limiting context menu activation to right-click or a specific action.
</issue_to_address>

### Comment 2
<location> `src/group/groupwidget.cpp:760` </location>
<code_context>
+    const int row = std::max(icon, m_table->fontMetrics().height() + 4);

+    m_table->verticalHeader()->setDefaultSectionSize(row);
+    m_table->setIconSize(QSize(icon, icon));
     m_center = new QAction(QIcon(":/icons/roomfind.png"), tr("&Center"), this);
     connect(m_center, &QAction::triggered, this, [this]() {
</code_context>

<issue_to_address>
**suggestion:** Icon size and row height are now dynamically set, but may not handle DPI scaling.

Please verify that the sizing logic works correctly on high-DPI displays and with system scaling enabled across platforms.

Suggested implementation:

```cpp
    // Minimize row height, handle DPI scaling
    const int icon = getConfig().groupManager.tokenIconSize;
    qreal scale = m_table->devicePixelRatioF();
    const int scaledIcon = std::round(icon * scale);
    const int scaledRow = std::max(scaledIcon, std::round((m_table->fontMetrics().height() + 4) * scale));

    m_table->verticalHeader()->setDefaultSectionSize(scaledRow);
    m_table->setIconSize(QSize(scaledIcon, scaledIcon));

```

If you want to support per-screen DPI scaling (multi-monitor setups), consider using `QScreen *screen = m_table->screen();` and `screen->devicePixelRatio()` instead of `m_table->devicePixelRatioF()`. Also, ensure that your icons are provided at multiple resolutions (e.g., SVG or @2x PNGs) for best appearance.
</issue_to_address>

### Comment 3
<location> `src/display/Characters.cpp:356-359` </location>
<code_context>
     }

+    // ── draw map-tokens underneath the coloured overlay ──
+    for (size_t q = 0; q < m_charTokenKeys.size(); ++q) {
+        const size_t base = q * 4;
+        if (base + 3 >= m_charTokenQuads.size())
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Token quad rendering assumes exactly four vertices per token.

If quad generation logic changes, mismatched vertex counts could cause rendering issues. Adding assertions or error handling would help detect these problems early.

```suggestion
    for (size_t q = 0; q < m_charTokenKeys.size(); ++q) {
        const size_t base = q * 4;
        // Ensure each token quad has exactly four vertices
        Q_ASSERT_X(base + 3 < m_charTokenQuads.size(), "Token quad rendering", "Each token quad must have exactly four vertices.");
        if (base + 3 >= m_charTokenQuads.size()) {
            qWarning("Token quad rendering: Mismatched vertex count for token quad %zu (expected 4 vertices per token)", q);
            break;
        }
```
</issue_to_address>

### Comment 4
<location> `src/group/CGroupChar.h:125` </location>
<code_context>
 #undef X_DECL_GETTERS_AND_SETTERS

+    /* ---------- temporary helper until GMCP flags real mounts ---------- */
+    inline bool isMount() const
+    {
+        return isNpc(); // treat every NPC as a “mount” for now
</code_context>

<issue_to_address>
**nitpick:** isMount() currently treats all NPCs as mounts, which may be misleading.

Since isMount() currently returns true for all NPCs, please add a comment or TODO indicating this is a temporary solution and will be updated when GMCP support is available.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

emit sig_characterUpdated(selectedCharacter);
});

connect(m_table, &QAbstractItemView::clicked, this, &GroupWidget::showContextMenu);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Context menu is now shown on click, which may interfere with row selection.

Directly connecting showContextMenu to the clicked signal may disrupt standard row selection or double-click behavior. Please review if this impacts usability and consider limiting context menu activation to right-click or a specific action.

const int row = std::max(icon, m_table->fontMetrics().height() + 4);

m_table->verticalHeader()->setDefaultSectionSize(row);
m_table->setIconSize(QSize(icon, icon));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Icon size and row height are now dynamically set, but may not handle DPI scaling.

Please verify that the sizing logic works correctly on high-DPI displays and with system scaling enabled across platforms.

Suggested implementation:

    // Minimize row height, handle DPI scaling
    const int icon = getConfig().groupManager.tokenIconSize;
    qreal scale = m_table->devicePixelRatioF();
    const int scaledIcon = std::round(icon * scale);
    const int scaledRow = std::max(scaledIcon, std::round((m_table->fontMetrics().height() + 4) * scale));

    m_table->verticalHeader()->setDefaultSectionSize(scaledRow);
    m_table->setIconSize(QSize(scaledIcon, scaledIcon));

If you want to support per-screen DPI scaling (multi-monitor setups), consider using QScreen *screen = m_table->screen(); and screen->devicePixelRatio() instead of m_table->devicePixelRatioF(). Also, ensure that your icons are provided at multiple resolutions (e.g., SVG or @2x PNGs) for best appearance.

Comment on lines +356 to +359
for (size_t q = 0; q < m_charTokenKeys.size(); ++q) {
const size_t base = q * 4;
if (base + 3 >= m_charTokenQuads.size())
break;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Token quad rendering assumes exactly four vertices per token.

If quad generation logic changes, mismatched vertex counts could cause rendering issues. Adding assertions or error handling would help detect these problems early.

Suggested change
for (size_t q = 0; q < m_charTokenKeys.size(); ++q) {
const size_t base = q * 4;
if (base + 3 >= m_charTokenQuads.size())
break;
for (size_t q = 0; q < m_charTokenKeys.size(); ++q) {
const size_t base = q * 4;
// Ensure each token quad has exactly four vertices
Q_ASSERT_X(base + 3 < m_charTokenQuads.size(), "Token quad rendering", "Each token quad must have exactly four vertices.");
if (base + 3 >= m_charTokenQuads.size()) {
qWarning("Token quad rendering: Mismatched vertex count for token quad %zu (expected 4 vertices per token)", q);
break;
}

#undef X_DECL_GETTERS_AND_SETTERS

/* ---------- temporary helper until GMCP flags real mounts ---------- */
inline bool isMount() const
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: isMount() currently treats all NPCs as mounts, which may be misleading.

Since isMount() currently returns true for all NPCs, please add a comment or TODO indicating this is a temporary solution and will be updated when GMCP support is available.

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.

2 participants