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

BaseTrackTableModel: Fix column header mapping #13782

Open
wants to merge 19 commits into
base: 2.5
Choose a base branch
from

Conversation

daschuer
Copy link
Member

This finishes the work of @uklotzde in #13682 to fix the column header mapping in case using additional columns in his fork, not used in Mixxx.

I avoid a redundant initialization loop and in addition this improves the maintenance a lot by using a const kColumnPropertiesByEnum[] table to initialize thee header properties. This has the advantage that the compiler (tested with gcc) can check weather the table is complete.

If an entry is missing or not in consecutive order we will get:

src/library/columncache.cpp:168:92: sorry, unimplemented: non-trivial designated initializers not supported
  168 |         [ColumnCache::COLUMN_REKORDBOX_ANALYZE_PATH] = {REKORDBOX_ANALYZE_PATH, nullptr, 0}};
      |                                                                                            ^
src/library/columncache.cpp:169:50: error: static assertion failed
  169 | static_assert(std::size(kColumnPropertiesByEnum) == ColumnCache::NUM_COLUMNS);
      |               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~

Note: This requires a Transifex roundtrip to move the header translations to the new translation scope, to the new common place for all.

@JoergAtGithub
Copy link
Member

Note: This requires a Transifex roundtrip to move the header translations to the new translation scope, to the new common place for all.

We are past string freeze for 2.5 - Shouldn't this go into main instead?

@daschuer
Copy link
Member Author

We may decide. I have no particular demand. It is at 2.5 because @uklotzde has picked that target.
Since we just have moved strings, my hope is that Transifex fixes i automatically. If it fails a manual fix would be trivial.This should be done in main anyway to not loose any already translated strings.

DEBUG_ASSERT(it.key() < NUM_COLUMNS);
DEBUG_ASSERT(m_columnIndexByEnum[it.key()] == -1);
m_columnIndexByEnum[it.key()] = fieldIndex(it.value());
DEBUG_ASSERT(std::size(kColumnNameByEnum) == std::size(m_columnIndexByEnum));
Copy link
Member

Choose a reason for hiding this comment

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

could be a static assert.

Comment on lines 148 to 151
const QString& ColumnCache::columnName(Column column) const {
DEBUG_ASSERT(static_cast<std::size_t>(column) < std::size(kColumnNameByEnum));
return kColumnNameByEnum[column];
}
Copy link
Member

Choose a reason for hiding this comment

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

there may be some constexpr opportunity here by switching to a constexpr friendly type like QStringView and making kColumnNameByEnum constexpr accordingly by storing char16_ts instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

Creating a QString form a QStringView is a deep copy.
kColumnNameByEnum uses LIBRARYTABLE_ID and friends a QStringLiteral which is basically a constexpr, but does not have the keyword because of MSVC limitations.
So using a QStringView here for the sake of becoming a constexpr, will introduce more deep copies later where now implicit sharing is used.

Copy link
Member Author

Choose a reason for hiding this comment

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

With a pointer it works.

Comment on lines 46 to 48
index.sibling(index.row(),
fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ARTIST))
.data()
Copy link
Member

Choose a reason for hiding this comment

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

this deserves a utility function IMO.

Comment on lines 74 to 75
.toString()
.toFloat();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.toString()
.toFloat();
.toFloat();

Comment on lines 17 to 18
[ColumnCache::COLUMN_LIBRARYTABLE_ID] = LIBRARYTABLE_ID,
[ColumnCache::COLUMN_LIBRARYTABLE_ARTIST] = LIBRARYTABLE_ARTIST,
Copy link
Member

Choose a reason for hiding this comment

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

this is unfortunately not valid C++.

Copy link
Member Author

Choose a reason for hiding this comment

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

For my understanding this is the C++ 20 feature "designated initializers".

Copy link
Member Author

Choose a reason for hiding this comment

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

:-(

Error: /home/runner/work/mixxx/mixxx/src/library/columncache.cpp:27:9: error: array designators are a C99 extension [-Werror,-Wc99-designator]
        [ColumnCache::COLUMN_LIBRARYTABLE_ID] = {LIBRARYTABLE_ID,
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``

Copy link
Member

Choose a reason for hiding this comment

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

yup. Designated initializers are cool, but they deliberately work differently to allow for for example default arguments. I'm afraid there is no real way of achieving this trick in C++. You'll implicitly have to assume that the order of the list matches the enum order. The better strategy would instead simply be to not rely on the enum order at all though. Instead of having a bunch of globals arrays, consider having a single array of structs that bundles these together.

Copy link
Member Author

Choose a reason for hiding this comment

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

You'll implicitly have to assume that the order of the list matches the enum order.

A appreciate exactly this behavior a feature. This makes sure the list is complete and consistent.

I don't understand you proposal.

I would like to treat this as a compiler feature we can use like #pragma once, and just silence clazy.

What do you think?

Copy link
Member

@Swiftb0y Swiftb0y Oct 20, 2024

Choose a reason for hiding this comment

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

I would like to treat this as a compiler feature we can use like #pragma once, and just silence clazy.

I'm not sure its worth the trouble trying to opt into this extension and maintain it on every compiler. I'm also not sure what the semantics are when the enum suddenly gets different values (are entries in the array that are now empty unitialized, zero or default initialized?) I don't think we should mix C99 extensions with C++ code.

Have you instead considered putting this into a utility function with a switch-case? Performance is likely comparable and this time we don't have issues if the enum suddenly gets gaps and we can rely on -Wswitch for exhaustiveness (also we don't need NUM_COLUMNS anymore, which is always a hack).

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm also not sure what the semantics are when the enum suddenly gets different values

That is the great thing with this "feature". Compiling fails. So you cannot mess up the initalization you can do if this are single cals or an anonymous initalizer like in MSVC.

Copy link
Member Author

Choose a reason for hiding this comment

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

this time we don't have issues if the enum suddenly gets gaps

Than we have an initialized element. This can not happen currently.

Copy link
Member

Choose a reason for hiding this comment

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

Ok. I won't stress this for now then. Lets focus on the other stuff first.

return m_tableColumns.size() + sourceTableIndex - 1;
}
}
return tableIndex;
}

int BaseSqlTableModel::maxFieldIndex() const {
// Subtract one to remove the id column which is in both
return m_tableColumns.size() + (m_trackSource ? m_trackSource->maxFieldIndex() : 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

That doesn't look correct to me.

The brittle max index = size minus one semantics are error prone.

maxFieldIndex() should better become endFieldIndex() which would be more consistent with how ranges are represented in C++.

@daschuer
Copy link
Member Author

daschuer commented Nov 3, 2024

Done

@uklotzde
Copy link
Contributor

uklotzde commented Nov 3, 2024

No objections. I didn't review the additional changes in detail because they don't seem to affect my use case.

I'll update my fork after the changes have reached main.

Hint: This could have been achieved by merging smaller, independent PRs from different contributors. One at a time. Would cause less friction and frustration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants