/* * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome * 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 * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Strawberry is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Strawberry. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "stretchheaderview.h" namespace { constexpr int kMagicNumber = 0x502C9510; } StretchHeaderView::StretchHeaderView(const Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent), stretch_enabled_(false), in_mouse_move_event_(false), forced_resize_logical_index_(-1) { setDefaultSectionSize(100); setMinimumSectionSize(30); setTextElideMode(Qt::ElideRight); QObject::connect(this, &StretchHeaderView::sectionResized, this, &StretchHeaderView::SectionResized); } void StretchHeaderView::setModel(QAbstractItemModel *model) { QHeaderView::setModel(model); if (stretch_enabled_) { column_widths_.resize(count()); std::fill(column_widths_.begin(), column_widths_.end(), 1.0 / count()); } } void StretchHeaderView::resizeEvent(QResizeEvent *e) { QHeaderView::resizeEvent(e); if (stretch_enabled_) { ResizeSections(); } } void StretchHeaderView::mouseMoveEvent(QMouseEvent *e) { in_mouse_move_event_ = true; QHeaderView::mouseMoveEvent(e); in_mouse_move_event_ = false; } QByteArray StretchHeaderView::SaveState() const { 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() && column_pixel_widths[i] > 0) { 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(); } Q_EMIT StretchEnabledChanged(stretch_enabled_); return true; } QByteArray StretchHeaderView::ResetState() { stretch_enabled_ = false; column_widths_.resize(count()); std::fill(column_widths_.begin(), column_widths_.end(), 1.0 / count()); setSortIndicator(-1, Qt::AscendingOrder); for (int i = 0; i < count(); ++i) { setSectionHidden(i, false); resizeSection(i, defaultSectionSize()); moveSection(visualIndex(i), i); } return SaveState(); } void StretchHeaderView::ToggleStretchEnabled() { SetStretchEnabled(!is_stretch_enabled()); } void StretchHeaderView::SetStretchEnabled(const bool enabled) { stretch_enabled_ = enabled; if (enabled) { // Initialize the list of widths from the current state of the widget column_widths_.resize(count()); for (int i = 0; i < count(); ++i) { column_widths_[i] = static_cast(sectionSize(i)) / width(); } // Stretch the columns to fill the widget NormaliseWidths(); ResizeSections(); } Q_EMIT StretchEnabledChanged(enabled); } void StretchHeaderView::SetColumnWidth(const int logical_index, const ColumnWidthType width) { column_widths_[logical_index] = width; if (!stretch_enabled_) return; QList other_columns; for (int i = 0; i < count(); ++i) { if (!isSectionHidden(i) && i != logical_index) { other_columns << i; } } NormaliseWidths(other_columns); } 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_.value(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::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_.value(i) * width()); if (pixels != 0) { resizeSection(i, pixels); } } } void StretchHeaderView::ShowSection(const int logical_index) { showSection(logical_index); if (stretch_enabled_) { // 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(); } else { if (sectionSize(logical_index) == 0) { resizeSection(logical_index, defaultSectionSize()); } } } void StretchHeaderView::HideSection(const int logical_index) { // Would this hide the last section? bool all_hidden = true; for (int i = 0; i < count(); ++i) { if (i != logical_index && !isSectionHidden(i) && sectionSize(i) > 0) { all_hidden = false; break; } } if (all_hidden) { return; } hideSection(logical_index); if (stretch_enabled_) { column_widths_[logical_index] = 0.0; NormaliseWidths(); ResizeSections(); } } void StretchHeaderView::SetSectionHidden(const int logical_index, const bool hidden) { if (hidden) { HideSection(logical_index); } else { ShowSection(logical_index); } } void StretchHeaderView::SectionResized(const int logical_index, const int old_size, const int new_size) { if (!stretch_enabled_) { return; } if (logical_index == forced_resize_logical_index_) { forced_resize_logical_index_ = -1; return; } if (!in_mouse_move_event_) { return; } 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) { forced_resize_logical_index_ = logical_index; in_mouse_move_event_ = false; resizeSection(logical_index, old_size); in_mouse_move_event_ = true; } }