diff --git a/src/widgets/stretchheaderview.cpp b/src/widgets/stretchheaderview.cpp index 226eac7e7..3c0e27f8c 100644 --- a/src/widgets/stretchheaderview.cpp +++ b/src/widgets/stretchheaderview.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2021, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,18 +35,20 @@ #include "stretchheaderview.h" -const int StretchHeaderView::kMinimumColumnWidth = 10; -const int StretchHeaderView::kMagicNumber = 0x502c950f; +namespace { +constexpr int kMagicNumber = 0x502C9510; +} StretchHeaderView::StretchHeaderView(const Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent), stretch_enabled_(false), in_mouse_move_event_(false) { - QObject::connect(this, &StretchHeaderView::sectionResized, this, &StretchHeaderView::SectionResized); - setMinimumSectionSize(kMinimumColumnWidth); - setTextElideMode(Qt::ElideRight); setDefaultSectionSize(100); + setMinimumSectionSize(30); + setTextElideMode(Qt::ElideRight); + + QObject::connect(this, &StretchHeaderView::sectionResized, this, &StretchHeaderView::SectionResized); } @@ -61,122 +63,13 @@ void StretchHeaderView::setModel(QAbstractItemModel *model) { } -void StretchHeaderView::NormaliseWidths(const QList §ions) { - - if (!stretch_enabled_) return; - - const ColumnWidthType total_sum = std::accumulate(column_widths_.begin(), column_widths_.end(), 0.0); - ColumnWidthType selected_sum = total_sum; - - if (!sections.isEmpty()) { - selected_sum = 0.0; - for (int i = 0; i < count(); ++i) - if (sections.contains(i)) { - selected_sum += column_widths_[i]; - } - } - - if (total_sum != 0.0 && !qFuzzyCompare(total_sum, 1.0)) { - const ColumnWidthType mult = (selected_sum + (1.0 - total_sum)) / selected_sum; - for (int i = 0; i < column_widths_.count(); ++i) { - if (sections.isEmpty() || sections.contains(i)) { - column_widths_[i] *= mult; - } - } - } -} - -void StretchHeaderView::UpdateWidths(const QList §ions) { - - if (!stretch_enabled_) return; - - for (int i = 0; i < column_widths_.count(); ++i) { - const ColumnWidthType w = column_widths_[i]; - - int pixels = static_cast(w * width()); - - if (!sections.isEmpty() && !sections.contains(i)) { - continue; - } - - if (pixels == 0 && !isSectionHidden(i)) { - hideSection(i); - } - else if (pixels != 0 && isSectionHidden(i)) { - showSection(i); - } - - if (pixels != 0) { - resizeSection(i, pixels); - } - } - -} - -void StretchHeaderView::HideSection(const int logical) { - - // Would this hide the last section? - bool all_hidden = true; - for (int i = 0; i < count(); ++i) { - if (i != logical && !isSectionHidden(i) && sectionSize(i) > 0) { - all_hidden = false; - break; - } - } - if (all_hidden) { - return; - } - - if (!stretch_enabled_) { - hideSection(logical); - return; - } - - column_widths_[logical] = 0.0; - NormaliseWidths(); - UpdateWidths(); - -} - -void StretchHeaderView::ShowSection(int logical) { - - if (!stretch_enabled_) { - showSection(logical); - return; - } - - // How many sections are visible already? - int visible_count = 0; - for (int i = 0; i < count(); ++i) { - if (!isSectionHidden(i)) { - ++visible_count; - } - } - - column_widths_[logical] = visible_count == 0 ? 1.0 : 1.0 / visible_count; - NormaliseWidths(); - UpdateWidths(); - -} - -void StretchHeaderView::SetSectionHidden(const int logical, const bool hidden) { - - if (hidden) { - HideSection(logical); - } - else { - ShowSection(logical); - } - -} - void StretchHeaderView::resizeEvent(QResizeEvent *e) { QHeaderView::resizeEvent(e); - if (!stretch_enabled_) return; - - UpdateWidths(); + if (stretch_enabled_) { + ResizeSections(); + } } @@ -188,31 +81,101 @@ void StretchHeaderView::mouseMoveEvent(QMouseEvent *e) { } -void StretchHeaderView::SectionResized(const int logical, const int, const int new_size) { +QByteArray StretchHeaderView::SaveState() const { - if (!stretch_enabled_) return; - - if (in_mouse_move_event_) { - // Update this section's proportional width - column_widths_[logical] = static_cast(new_size) / width(); - - // Find the visible sections to the right of the section that's being resized - int visual = visualIndex(logical); - QList logical_sections_to_resize; - for (int i = 0; i < count(); ++i) { - if (!isSectionHidden(i) && visualIndex(i) > visual) { - logical_sections_to_resize << i; - } - } - - // Resize just those columns - if (!logical_sections_to_resize.isEmpty()) { - in_mouse_move_event_ = false; - UpdateWidths(logical_sections_to_resize); - NormaliseWidths(logical_sections_to_resize); - in_mouse_move_event_ = true; + QList visual_indices; + QList column_pixel_widths; + QList columns_visible; + visual_indices.reserve(count()); + column_pixel_widths.reserve(count()); + for (int i = 0; i < count(); ++i) { + visual_indices << logicalIndex(i); + column_pixel_widths << sectionSize(i); + if (!isSectionHidden(i)) { + columns_visible << i; } } + + QByteArray state; + QDataStream s(&state, QIODevice::WriteOnly); + s.setVersion(QDataStream::Qt_5_12); + s << kMagicNumber; + s << stretch_enabled_; + s << visual_indices; + s << column_pixel_widths; + s << columns_visible; + s << column_widths_; + s << static_cast(sortIndicatorOrder()); + s << sortIndicatorSection(); + + return state; + +} + +bool StretchHeaderView::RestoreState(const QByteArray &state) { + + if (state.isEmpty()) return false; + + int magic_number = 0; + QList visual_indices; + QList column_pixel_widths; + QList columns_visible; + int sort_indicator_order = Qt::AscendingOrder; + int sort_indicator_section = 0; + + QDataStream s(state); + s.setVersion(QDataStream::Qt_5_12); + + s >> magic_number; + + if (magic_number != kMagicNumber || s.atEnd()) { + return false; + } + + s >> stretch_enabled_; + s >> visual_indices; + s >> column_pixel_widths; + s >> columns_visible; + s >> column_widths_; + s >> sort_indicator_order; + s >> sort_indicator_section; + + for (int i = 0; i < count(); ++i) { + if (i < visual_indices.count()) { + moveSection(visualIndex(visual_indices[i]), i); + } + if (i < column_pixel_widths.count()) { + resizeSection(i, column_pixel_widths[i]); + } + setSectionHidden(i, !columns_visible.contains(i)); + } + + // Have we added more columns since the last time? + while (column_widths_.count() < count()) { + column_widths_ << 0; + } + + setSortIndicator(sort_indicator_section, static_cast(sort_indicator_order)); + + if (stretch_enabled_) { + // In stretch mode, we've already set the proportional column widths so apply them now. + ResizeSections(); + } + + emit StretchEnabledChanged(stretch_enabled_); + + return true; + +} + +QByteArray StretchHeaderView::ResetState() { + + stretch_enabled_ = true; + + setSortIndicator(0, Qt::AscendingOrder); + + return QByteArray(); + } void StretchHeaderView::ToggleStretchEnabled() { @@ -232,22 +195,22 @@ void StretchHeaderView::SetStretchEnabled(const bool enabled) { // Stretch the columns to fill the widget NormaliseWidths(); - UpdateWidths(); + ResizeSections(); } emit StretchEnabledChanged(enabled); } -void StretchHeaderView::SetColumnWidth(const int logical, const ColumnWidthType width) { +void StretchHeaderView::SetColumnWidth(const int logical_index, const ColumnWidthType width) { + + column_widths_[logical_index] = width; if (!stretch_enabled_) return; - column_widths_[logical] = width; - QList other_columns; for (int i = 0; i < count(); ++i) { - if (!isSectionHidden(i) && i != logical) { + if (!isSectionHidden(i) && i != logical_index) { other_columns << i; } } @@ -256,125 +219,146 @@ void StretchHeaderView::SetColumnWidth(const int logical, const ColumnWidthType } -bool StretchHeaderView::RestoreState(const QByteArray &sdata) { +void StretchHeaderView::NormaliseWidths(const QList §ions) { - QDataStream s(sdata); - s.setVersion(QDataStream::Qt_5_6); + if (!stretch_enabled_) return; - int magic_number = 0; - s >> magic_number; + const ColumnWidthType total_sum = std::accumulate(column_widths_.begin(), column_widths_.end(), 0.0); + ColumnWidthType selected_sum = total_sum; - if (magic_number != kMagicNumber || s.atEnd()) { - return false; - } - - QList pixel_widths; - QList visual_indices; - int sort_indicator_order = Qt::AscendingOrder; - int sort_indicator_section = 0; - - s >> stretch_enabled_; - s >> pixel_widths; - s >> visual_indices; - s >> column_widths_; - s >> sort_indicator_order; - s >> sort_indicator_section; - - setSortIndicator(sort_indicator_section, static_cast(sort_indicator_order)); - - const qint64 persisted_column_count = qMin(qMin(visual_indices.count(), pixel_widths.count()), column_widths_.count()); - - // Set column visible state, visual indices and, if we're not in stretch mode, pixel widths. - for (int i = 0; i < count() && i < persisted_column_count; ++i) { - setSectionHidden(i, pixel_widths[i] <= kMinimumColumnWidth); - moveSection(visualIndex(visual_indices[i]), i); - - if (!stretch_enabled_) { - resizeSection(i, pixel_widths[i]); + if (!sections.isEmpty()) { + selected_sum = 0.0; + for (int i = 0; i < count(); ++i) { + if (sections.contains(i)) { + selected_sum += column_widths_[i]; + } } } - // Have we added more columns since the last time? - while (column_widths_.count() < count()) { - column_widths_ << 0; + if (total_sum != 0.0 && !qFuzzyCompare(total_sum, 1.0)) { + const ColumnWidthType mult = (selected_sum + (1.0 - total_sum)) / selected_sum; + for (int i = 0; i < column_widths_.count(); ++i) { + if (sections.isEmpty() || sections.contains(i)) { + column_widths_[i] *= mult; + } + } } +} + +void StretchHeaderView::ResizeSections(const QList §ions) { + + if (!stretch_enabled_) return; + + for (int i = 0; i < column_widths_.count(); ++i) { + if (isSectionHidden(i) || (!sections.isEmpty() && !sections.contains(i))) { + continue; + } + const int pixels = static_cast(column_widths_[i] * width()); + if (pixels != 0) { + resizeSection(i, pixels); + } + } + +} + +void StretchHeaderView::ShowSection(const int logical_index) { + + showSection(logical_index); + if (stretch_enabled_) { - // In stretch mode, we've already set the proportional column widths so apply them now. - UpdateWidths(); + + // How many sections are visible already? + int visible_count = 0; + for (int i = 0; i < count(); ++i) { + if (!isSectionHidden(i)) { + ++visible_count; + } + } + + column_widths_[logical_index] = visible_count == 0 ? 1.0 : 1.0 / visible_count; + + NormaliseWidths(); + ResizeSections(); + } - emit StretchEnabledChanged(stretch_enabled_); - - return true; - } -QByteArray StretchHeaderView::SaveState() const { - - QByteArray ret; - QDataStream s(&ret, QIODevice::WriteOnly); - - QList pixel_widths; - QList visual_indices; - - pixel_widths.reserve(count()); - visual_indices.reserve(count()); +void StretchHeaderView::HideSection(const int logical_index) { + // Would this hide the last section? + bool all_hidden = true; for (int i = 0; i < count(); ++i) { - pixel_widths << sectionSize(i); - visual_indices << logicalIndex(i); + if (i != logical_index && !isSectionHidden(i) && sectionSize(i) > 0) { + all_hidden = false; + break; + } + } + if (all_hidden) { + return; } - s.setVersion(QDataStream::Qt_5_6); + hideSection(logical_index); - s << kMagicNumber; - - s << stretch_enabled_; - s << pixel_widths; - s << visual_indices; - s << column_widths_; - s << static_cast(sortIndicatorOrder()); - s << sortIndicatorSection(); - - return ret; + if (stretch_enabled_) { + column_widths_[logical_index] = 0.0; + NormaliseWidths(); + ResizeSections(); + } } -QByteArray StretchHeaderView::ResetState() { +void StretchHeaderView::SetSectionHidden(const int logical_index, const bool hidden) { - QByteArray ret; - - QDataStream s(&ret, QIODevice::WriteOnly); - s.setVersion(QDataStream::Qt_5_6); - - s << kMagicNumber; - - stretch_enabled_ = true; - - column_widths_.resize(count()); - std::fill(column_widths_.begin(), column_widths_.end(), 1.0 / count()); - - QList visual_indices; - QList pixel_widths; - - visual_indices.reserve(count()); - pixel_widths.reserve(count()); - - for (int i = 0; i < count(); ++i) { - pixel_widths << 10; - visual_indices << count(); + if (hidden) { + HideSection(logical_index); + } + else { + ShowSection(logical_index); } - s << stretch_enabled_; - s << pixel_widths; - s << visual_indices; - s << column_widths_; - s << static_cast(Qt::AscendingOrder); - s << 0; +} - RestoreState(ret); +void StretchHeaderView::SectionResized(const int logical_index, const int old_size, const int new_size) { - return ret; + if (!stretch_enabled_) { + return; + } + + if (in_mouse_move_event_) { + bool resized = false; + if (new_size >= minimumSectionSize()) { + // Find the visible section to the right of the section that's being resized + const int visual_index = visualIndex(logical_index); + int right_section_logical_index = -1; + int right_section_visual_index = -1; + for (int i = 0; i <= count(); ++i) { + if (!isSectionHidden(i) && + visualIndex(i) > visual_index && + (right_section_visual_index == -1 || visualIndex(i) < right_section_visual_index)) { + right_section_logical_index = i; + right_section_visual_index = visualIndex(i); + } + } + if (right_section_logical_index != -1) { + const int right_section_size = sectionSize(right_section_logical_index) + (old_size - new_size); + if (right_section_size >= minimumSectionSize()) { + column_widths_[logical_index] = static_cast(new_size) / width(); + column_widths_[right_section_logical_index] = static_cast(right_section_size) / width(); + in_mouse_move_event_ = false; + NormaliseWidths(QList() << right_section_logical_index); + ResizeSections(QList() << right_section_logical_index); + in_mouse_move_event_ = true; + resized = true; + } + } + } + if (!resized) { + in_mouse_move_event_ = true; + resizeSection(logical_index, old_size); + in_mouse_move_event_ = false; + } + } } diff --git a/src/widgets/stretchheaderview.h b/src/widgets/stretchheaderview.h index 19b46b8c5..2806b09ca 100644 --- a/src/widgets/stretchheaderview.h +++ b/src/widgets/stretchheaderview.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2021, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,31 +42,28 @@ class StretchHeaderView : public QHeaderView { using ColumnWidthType = double; - static const int kMinimumColumnWidth; - static const int kMagicNumber; - void setModel(QAbstractItemModel *model) override; + bool is_stretch_enabled() const { return stretch_enabled_; } + // Serialises the proportional and actual column widths. // Use these instead of QHeaderView::restoreState and QHeaderView::saveState to persist the proportional values directly and avoid floating point errors over time. bool RestoreState(const QByteArray &sdata); QByteArray SaveState() const; QByteArray ResetState(); - // Hides a section and resizes all other sections to fill the gap. Does nothing if you try to hide the last section. - void HideSection(const int logical); - // Shows a section and resizes all other sections to make room. - void ShowSection(const int logical); + void ShowSection(const int logical_index); + + // Hides a section and resizes all other sections to fill the gap. Does nothing if you try to hide the last section. + void HideSection(const int logical_index); // Calls either HideSection or ShowSection. - void SetSectionHidden(const int logical, const bool hidden); + void SetSectionHidden(const int logical_index, const bool hidden); // Sets the width of the given column and resizes other columns appropriately. // width is the proportion of the entire width from 0.0 to 1.0. - void SetColumnWidth(const int logical, const ColumnWidthType width); - - bool is_stretch_enabled() const { return stretch_enabled_; } + void SetColumnWidth(const int logical_index, const ColumnWidthType width); public slots: // Changes the stretch mode. @@ -88,7 +85,7 @@ class StretchHeaderView : public QHeaderView { void NormaliseWidths(const QList §ions = QList()); // Resizes the actual columns to make them match the proportional values in column_widths_. - void UpdateWidths(const QList §ions = QList()); + void ResizeSections(const QList §ions = QList()); private slots: void SectionResized(const int logical, const int old_size, const int new_size);