From 79217ef5dbedf43db108f29628b24b23fe1d981e Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 27 Sep 2024 16:35:12 -0400 Subject: [PATCH] multiple chromosome display in QtSLiM --- QtSLiM/QtSLiMChromosomeWidget.cpp | 751 +++++++++++++++++---- QtSLiM/QtSLiMChromosomeWidget.h | 97 ++- QtSLiM/QtSLiMChromosomeWidget_GL.cpp | 4 +- QtSLiM/QtSLiMChromosomeWidget_QT.cpp | 4 +- QtSLiM/QtSLiMGraphView.cpp | 7 +- QtSLiM/QtSLiMGraphView.h | 3 +- QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp | 52 -- QtSLiM/QtSLiMGraphView_1DPopulationSFS.h | 3 - QtSLiM/QtSLiMHaplotypeManager.cpp | 33 +- QtSLiM/QtSLiMHaplotypeManager.h | 12 +- QtSLiM/QtSLiMWindow.cpp | 328 +++------ QtSLiM/QtSLiMWindow.h | 22 +- QtSLiM/QtSLiMWindow.ui | 59 +- QtSLiM/QtSLiMWindow_glue.cpp | 14 +- VERSIONS | 3 + core/chromosome.cpp | 2 +- core/species_eidos.cpp | 2 +- 17 files changed, 903 insertions(+), 493 deletions(-) diff --git a/QtSLiM/QtSLiMChromosomeWidget.cpp b/QtSLiM/QtSLiMChromosomeWidget.cpp index da1adced..6855b730 100644 --- a/QtSLiM/QtSLiMChromosomeWidget.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include @@ -43,7 +45,425 @@ static const int selectionKnobSize = selectionKnobSizeExtension + selectionKnobS static const int spaceBetweenChromosomes = 5; -QtSLiMChromosomeWidget::QtSLiMChromosomeWidget(QWidget *p_parent, QtSLiMWindow *controller, Species *displaySpecies, Qt::WindowFlags f) +QtSLiMChromosomeWidgetController::QtSLiMChromosomeWidgetController(QtSLiMWindow *slimWindow, QWidget *displayWindow, Species *focalSpecies) : + QObject(displayWindow ? displayWindow : slimWindow), + slimWindow_(slimWindow), + displayWindow_(displayWindow) +{ + connect(slimWindow_, &QtSLiMWindow::controllerPartialUpdateAfterTick, this, &QtSLiMChromosomeWidgetController::updateFromController); + + // focalSpecies is used only in the displayWindow case, for showing the species badge in multispecies models + // otherwise, slimWindow will control the focal display species for our chromosome view as it requires + if (displayWindow) + { + if (!focalSpecies) + { + qDebug() << "no focal species for creating a chromosome display!"; + return; + } + + focalSpeciesName_ = focalSpecies->name_; + // focalSpeciesAvatar_ is set up in buildChromosomeDisplay(); + } +} + +void QtSLiMChromosomeWidgetController::updateFromController(void) +{ + if (displayWindow_) + { + Species *displaySpecies = focalDisplaySpecies(); + + if (displaySpecies) + { + Community *community = slimWindow_->community; + + if (needsRebuild_ && !invalidSimulation() && community->simulation_valid_ && (community->tick_ >= 1)) + { + // It's hard to tell, in general, whether we need a rebuild: if the number of + // chromosomes has changed, or the length of any chromosome, or the symbol of + // any chromosome, etc. There's no harm, so we just always rebuild at the + // first valid moment after recycling. + buildChromosomeDisplay(false); + needsRebuild_ = false; + } + } + else + { + // we've just recycled or become invalid; our next update should rebuild the display + needsRebuild_ = true; + } + } + + emit needsRedisplay(); +} + +Species *QtSLiMChromosomeWidgetController::focalDisplaySpecies(void) +{ + if (displayWindow_) + { + // with a chromosome display, we are not based on the current focal species of slimWindow_, so we + // need to look up the focal display species dynamically based on its name (which could fail) + if (focalSpeciesName_.length() == 0) + return nullptr; + + if (slimWindow_ && slimWindow_->community && (slimWindow_->community->Tick() >= 1)) + return slimWindow_->community->SpeciesWithName(focalSpeciesName_); + + return nullptr; + } + + // otherwise, our focal display species comes directly from slimWindow_ + return slimWindow_->focalDisplaySpecies(); +} + +void QtSLiMChromosomeWidgetController::buildChromosomeDisplay(bool resetWindowSize) +{ + // Remove any existing content from our display window and build new content + if (!displayWindow_) + return; + + // Assess the chromosomes to be displayed + Species *focalSpecies = focalDisplaySpecies(); + const std::vector &chromosomes = focalSpecies->Chromosomes(); + int chromosomeCount = (int)chromosomes.size(); + slim_position_t chromosomeMaxLength = 0; + + for (Chromosome *chromosome : chromosomes) + { + slim_position_t length = chromosome->last_position_ - chromosome->first_position_ + 1; + + chromosomeMaxLength = std::max(chromosomeMaxLength, length); + } + + // Deal with window sizing + const int margin = 5; + const int spacing = 5; + const int buttonRowHeight = margin + margin + 20; + + displayWindow_->setMinimumSize(500, margin + 20 * chromosomeCount + spacing * (chromosomeCount - 1) + buttonRowHeight); + displayWindow_->setMaximumSize(4096, margin + 200 * chromosomeCount + spacing * (chromosomeCount - 1) + buttonRowHeight); + if (resetWindowSize) + displayWindow_->resize(800, margin + 30 * chromosomeCount + spacing * (chromosomeCount - 1) + buttonRowHeight); + + // Find the top-level layout and remove all of its current children + QVBoxLayout *topLayout = qobject_cast(displayWindow_->layout()); + + QtSLiMClearLayout(topLayout, /* deleteWidgets */ true); + + // Add a chromosome view for each chromosome in the model, with a spacer next to it to give it the right length + std::vector labels; + bool firstRow = true; + + for (Chromosome *chromosome : chromosomes) + { + QHBoxLayout *rowLayout = new QHBoxLayout; + + rowLayout->setContentsMargins(margin, firstRow ? margin : spacing, margin, 0); + rowLayout->setSpacing(0); + topLayout->addLayout(rowLayout); + + QtSLiMChromosomeWidget *chromosomeWidget = new QtSLiMChromosomeWidget(nullptr); + + chromosomeWidget->setController(this); + chromosomeWidget->setFocalChromosome(chromosome); + chromosomeWidget->setDisplayedRange(QtSLiMRange(chromosome->first_position_, chromosome->last_position_ + 1)); + chromosomeWidget->setShowsTicks(false); + + slim_position_t length = chromosome->last_position_ - chromosome->first_position_ + 1; + double fractionOfMax = length / (double)chromosomeMaxLength; + int chromosomeStretch = (int)(round(fractionOfMax * 255)); // Qt requires a max value of 255 + + QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Expanding); + sizePolicy1.setHorizontalStretch(useScaledWidths_ ? chromosomeStretch : 0); + sizePolicy1.setVerticalStretch(0); + chromosomeWidget->setSizePolicy(sizePolicy1); + + QLabel *chromosomeLabel = new QLabel(); + chromosomeLabel->setText(QString::fromStdString(chromosome->symbol_)); + chromosomeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + + QSizePolicy sizePolicy2(QSizePolicy::Fixed, QSizePolicy::Expanding); + chromosomeLabel->setSizePolicy(sizePolicy2); + + rowLayout->addWidget(chromosomeLabel); + rowLayout->addSpacing(margin); + rowLayout->addWidget(chromosomeWidget); + + if (useScaledWidths_) + rowLayout->addStretch(255 - chromosomeStretch); // the remaining width after chromosomeStretch + + labels.push_back(chromosomeLabel); + + firstRow = false; + } + + // adjust all the labels to have the same width + int maxWidth = 0; + + for (QLabel *label : labels) + maxWidth = std::max(maxWidth, label->sizeHint().width()); + + for (QLabel *label : labels) + label->setMinimumWidth(maxWidth); + + // Add a horizontal layout at the bottom, for the action button + QHBoxLayout *buttonLayout = nullptr; + + { + buttonLayout = new QHBoxLayout; + + buttonLayout->setContentsMargins(margin, margin, margin, margin); + buttonLayout->setSpacing(5); + topLayout->addLayout(buttonLayout); + + // set up the species badge; note that unlike QtSLiMGraphView, we set it up here immediately, + // since we are guaranteed to already have a valid species object, and then we don't update it + focalSpeciesAvatar_ = focalSpecies->avatar_; + + if (focalSpeciesAvatar_.length() && (focalSpecies->community_.all_species_.size() > 1)) + { + QLabel *speciesLabel = new QLabel(); + speciesLabel->setText(QString::fromStdString(focalSpeciesAvatar_)); + buttonLayout->addWidget(speciesLabel); + } + + QSpacerItem *rightSpacer = new QSpacerItem(16, 5, QSizePolicy::Expanding, QSizePolicy::Minimum); + buttonLayout->addItem(rightSpacer); + + // this code is based on the creation of executeScriptButton in ui_QtSLiMEidosConsole.h + QtSLiMPushButton *actionButton = new QtSLiMPushButton(displayWindow_); + actionButton->setObjectName(QString::fromUtf8("actionButton")); + actionButton->setMinimumSize(QSize(20, 20)); + actionButton->setMaximumSize(QSize(20, 20)); + actionButton->setFocusPolicy(Qt::NoFocus); + QIcon icon4; + icon4.addFile(QtSLiMImagePath("action", false), QSize(), QIcon::Normal, QIcon::Off); + icon4.addFile(QtSLiMImagePath("action", true), QSize(), QIcon::Normal, QIcon::On); + actionButton->setIcon(icon4); + actionButton->setIconSize(QSize(20, 20)); + actionButton->qtslimSetBaseName("action"); + actionButton->setCheckable(true); + actionButton->setFlat(true); +#if QT_CONFIG(tooltip) + actionButton->setToolTip("

configure chromosome display

"); +#endif // QT_CONFIG(tooltip) + buttonLayout->addWidget(actionButton); + + connect(actionButton, &QPushButton::pressed, this, [actionButton, this]() { actionButton->qtslimSetHighlight(true); actionButtonRunMenu(actionButton); }); + connect(actionButton, &QPushButton::released, this, [actionButton]() { actionButton->qtslimSetHighlight(false); }); + + // note that this action button has no enable/disable code anywhere, since it is happy to respond at all times + } +} + +void QtSLiMChromosomeWidgetController::runChromosomeContextMenuAtPoint(QPoint p_globalPoint) +{ + if (!slimWindow_) + return; + + Community *community = slimWindow_->community; + + if (!invalidSimulation() && community && community->simulation_valid_) + { + QMenu contextMenu("chromosome_menu", slimWindow_); // slimWindow_ is the parent in the sense that the menu is freed if slimWindow_ is freed + QAction *scaledWidths = nullptr; + QAction *unscaledWidths = nullptr; + Species *focalSpecies = focalDisplaySpecies(); + + if (displayWindow_ && focalSpecies && (focalSpecies->Chromosomes().size() > 1)) + { + // only in multichromosome models, offer to scale the widths of the displayed chromosomes + // according to their length or not, as the user prefers + scaledWidths = contextMenu.addAction("Use Scaled Widths"); + scaledWidths->setCheckable(true); + scaledWidths->setChecked(useScaledWidths_); + + unscaledWidths = contextMenu.addAction("Use Full Widths"); + unscaledWidths->setCheckable(true); + unscaledWidths->setChecked(!useScaledWidths_); + + contextMenu.addSeparator(); + } + + QAction *displayMutations = contextMenu.addAction("Display Mutations"); + displayMutations->setCheckable(true); + displayMutations->setChecked(shouldDrawMutations_); + + QAction *displaySubstitutions = contextMenu.addAction("Display Substitutions"); + displaySubstitutions->setCheckable(true); + displaySubstitutions->setChecked(shouldDrawFixedSubstitutions_); + + QAction *displayGenomicElements = contextMenu.addAction("Display Genomic Elements"); + displayGenomicElements->setCheckable(true); + displayGenomicElements->setChecked(shouldDrawGenomicElements_); + + QAction *displayRateMaps = contextMenu.addAction("Display Rate Maps"); + displayRateMaps->setCheckable(true); + displayRateMaps->setChecked(shouldDrawRateMaps_); + + contextMenu.addSeparator(); + + QAction *displayFrequencies = contextMenu.addAction("Display Frequencies"); + displayFrequencies->setCheckable(true); + displayFrequencies->setChecked(!displayHaplotypes_); + + QAction *displayHaplotypes = contextMenu.addAction("Display Haplotypes"); + displayHaplotypes->setCheckable(true); + displayHaplotypes->setChecked(displayHaplotypes_); + + QActionGroup *displayGroup = new QActionGroup(this); // On Linux this provides a radio-button-group appearance + displayGroup->addAction(displayFrequencies); + displayGroup->addAction(displayHaplotypes); + + QAction *displayAllMutations = nullptr; + QAction *selectNonneutralMutations = nullptr; + + // mutation type checkmark items + { + const std::map &muttypes = community->AllMutationTypes(); + + if (muttypes.size() > 0) + { + contextMenu.addSeparator(); + + displayAllMutations = contextMenu.addAction("Display All Mutations"); + displayAllMutations->setCheckable(true); + displayAllMutations->setChecked(displayMuttypes_.size() == 0); + + // Make a sorted list of all mutation types we know – those that exist, and those that used to exist that we are displaying + std::vector all_muttypes; + + for (auto muttype_iter : muttypes) + { + MutationType *muttype = muttype_iter.second; + slim_objectid_t muttype_id = muttype->mutation_type_id_; + + all_muttypes.emplace_back(muttype_id); + } + + all_muttypes.insert(all_muttypes.end(), displayMuttypes_.begin(), displayMuttypes_.end()); + + // Avoid building a huge menu, which will hang the app + if (all_muttypes.size() <= 500) + { + std::sort(all_muttypes.begin(), all_muttypes.end()); + all_muttypes.resize(static_cast(std::distance(all_muttypes.begin(), std::unique(all_muttypes.begin(), all_muttypes.end())))); + + // Then add menu items for each of those muttypes + for (slim_objectid_t muttype_id : all_muttypes) + { + QString menuItemTitle = QString("Display m%1").arg(muttype_id); + MutationType *muttype = community->MutationTypeWithID(muttype_id); // try to look up the mutation type; can fail if it doesn't exists now + + if (muttype && (community->all_species_.size() > 1)) + menuItemTitle.append(" ").append(QString::fromStdString(muttype->species_.avatar_)); + + QAction *mutationAction = contextMenu.addAction(menuItemTitle); + + mutationAction->setData(muttype_id); + mutationAction->setCheckable(true); + + if (std::find(displayMuttypes_.begin(), displayMuttypes_.end(), muttype_id) != displayMuttypes_.end()) + mutationAction->setChecked(true); + } + } + + contextMenu.addSeparator(); + + selectNonneutralMutations = contextMenu.addAction("Select Non-Neutral MutationTypes"); + } + } + + // Run the context menu synchronously + QAction *action = contextMenu.exec(p_globalPoint); + + // Act upon the chosen action; we just do it right here instead of dealing with slots + if (action) + { + if (action == scaledWidths) + { + if (!useScaledWidths_) + { + useScaledWidths_ = true; + buildChromosomeDisplay(false); + } + } + else if (action == unscaledWidths) + { + if (useScaledWidths_) + { + useScaledWidths_ = false; + buildChromosomeDisplay(false); + } + } + else if (action == displayMutations) + shouldDrawMutations_ = !shouldDrawMutations_; + else if (action == displaySubstitutions) + shouldDrawFixedSubstitutions_ = !shouldDrawFixedSubstitutions_; + else if (action == displayGenomicElements) + shouldDrawGenomicElements_ = !shouldDrawGenomicElements_; + else if (action == displayRateMaps) + shouldDrawRateMaps_ = !shouldDrawRateMaps_; + else if (action == displayFrequencies) + displayHaplotypes_ = false; + else if (action == displayHaplotypes) + displayHaplotypes_ = true; + else + { + const std::map &muttypes = community->AllMutationTypes(); + + if (action == displayAllMutations) + displayMuttypes_.clear(); + else if (action == selectNonneutralMutations) + { + // - (IBAction)filterNonNeutral:(id)sender + displayMuttypes_.clear(); + + for (auto muttype_iter : muttypes) + { + MutationType *muttype = muttype_iter.second; + slim_objectid_t muttype_id = muttype->mutation_type_id_; + + if ((muttype->dfe_type_ != DFEType::kFixed) || (muttype->dfe_parameters_[0] != 0.0)) + displayMuttypes_.emplace_back(muttype_id); + } + } + else + { + // - (IBAction)filterMutations:(id)sender + slim_objectid_t muttype_id = action->data().toInt(); + auto present_iter = std::find(displayMuttypes_.begin(), displayMuttypes_.end(), muttype_id); + + if (present_iter == displayMuttypes_.end()) + { + // this mut-type is not being displayed, so add it to our display list + displayMuttypes_.emplace_back(muttype_id); + } + else + { + // this mut-type is being displayed, so remove it from our display list + displayMuttypes_.erase(present_iter); + } + } + } + + emit needsRedisplay(); + } + } +} + +void QtSLiMChromosomeWidgetController::actionButtonRunMenu(QtSLiMPushButton *p_actionButton) +{ + QPoint mousePos = QCursor::pos(); + + runChromosomeContextMenuAtPoint(mousePos); + + // This is not called by Qt, for some reason (nested tracking loops?), so we call it explicitly + p_actionButton->qtslimSetHighlight(false); +} + + +QtSLiMChromosomeWidget::QtSLiMChromosomeWidget(QWidget *p_parent, QtSLiMChromosomeWidgetController *controller, Species *displaySpecies, Qt::WindowFlags f) #ifndef SLIM_NO_OPENGL : QOpenGLWidget(p_parent, f) #else @@ -62,7 +482,7 @@ QtSLiMChromosomeWidget::QtSLiMChromosomeWidget(QWidget *p_parent, QtSLiMWindow * QtSLiMChromosomeWidget::~QtSLiMChromosomeWidget() { - setReferenceChromosomeView(nullptr); + setDependentChromosomeView(nullptr); if (haplotype_mgr_) { @@ -79,29 +499,58 @@ QtSLiMChromosomeWidget::~QtSLiMChromosomeWidget() controller_ = nullptr; } -void QtSLiMChromosomeWidget::setController(QtSLiMWindow *controller) +void QtSLiMChromosomeWidget::setController(QtSLiMChromosomeWidgetController *controller) { - controller_ = controller; + if (controller != controller_) + { + if (controller_) + disconnect(controller_, &QtSLiMChromosomeWidgetController::needsRedisplay, this, nullptr); + + controller_ = controller; + connect(controller, &QtSLiMChromosomeWidgetController::needsRedisplay, this, &QtSLiMChromosomeWidget::updateAfterTick); + } +} + +Chromosome *QtSLiMChromosomeWidget::resetToDefaultChromosome(void) +{ + Species *focalSpecies = focalDisplaySpecies(); + Chromosome *chromosome = nullptr; + + if (focalSpecies) + { + const std::vector &chromosomes = focalSpecies->Chromosomes(); + + if (chromosomes.size() > 0) + chromosome = chromosomes[0]; // start on the first chromosome + } + + setFocalChromosome(chromosome); + + // ... and reset to the default selection + setSelectedRange(QtSLiMRange(0, 0)); + + updateDependentView(); + + return chromosome; } void QtSLiMChromosomeWidget::setFocalDisplaySpecies(Species *displaySpecies) { // We can have no focal species (when coming out of the nib, in particular); in that case we display empty state - if (displaySpecies) + if (displaySpecies && (displaySpecies->name_ != focalSpeciesName_)) { + // we've switched species, so we should remember the new one focalSpeciesName_ = displaySpecies->name_; - const std::vector &chromosomes = displaySpecies->Chromosomes(); + // ... and reset to the default chromosome + resetToDefaultChromosome(); - if (chromosomes.size() > 0) - setFocalChromosome(chromosomes[0]); // start on the first chromosome - else - setFocalChromosome(nullptr); + update(); + updateDependentView(); } else { - focalSpeciesName_ = ""; - setFocalChromosome(nullptr); + // if displaySpecies is nullptr or unchanged, we just stick with our last remembered species } } @@ -112,21 +561,30 @@ Species *QtSLiMChromosomeWidget::focalDisplaySpecies(void) if (focalSpeciesName_.length() == 0) return nullptr; - if (controller_ && controller_->community && (controller_->community->Tick() >= 1)) - return controller_->community->SpeciesWithName(focalSpeciesName_); + if (controller_ && controller_->community() && (controller_->community()->Tick() >= 1)) + return controller_->community()->SpeciesWithName(focalSpeciesName_); return nullptr; } void QtSLiMChromosomeWidget::setFocalChromosome(Chromosome *chromosome) { - if (chromosome) + if (chromosome && (chromosome->Symbol() != focalChromosomeSymbol_)) + { + // we've switched chromosomes, so remember the new one focalChromosomeSymbol_ = chromosome->Symbol(); - else - focalChromosomeSymbol_ = ""; + + // ... and reset to the default selection + setSelectedRange(QtSLiMRange(0, 0)); + + // ... and if our new chromosome belongs to a different species, remember that + if (chromosome->species_.name_ != focalSpeciesName_) + focalSpeciesName_ = chromosome->species_.name_; + + update(); + updateDependentView(); + } - hasSelection_ = false; - emit selectedRangeChanged(); } Chromosome *QtSLiMChromosomeWidget::focalChromosome(void) @@ -137,12 +595,45 @@ Chromosome *QtSLiMChromosomeWidget::focalChromosome(void) { Chromosome *chromosome = focalSpecies->ChromosomeFromSymbol(focalChromosomeSymbol_); + if (isOverview_ && !chromosome) + { + // force a reset to the default chromosome for the focal species + chromosome = resetToDefaultChromosome(); + } + return chromosome; } return nullptr; } +void QtSLiMChromosomeWidget::setDependentChromosomeView(QtSLiMChromosomeWidget *p_dependent_widget) +{ + if (dependentChromosomeView_ != p_dependent_widget) + { + dependentChromosomeView_ = p_dependent_widget; + isOverview_ = (dependentChromosomeView_ ? true : false); + showsTicks_ = !isOverview_; + + updateDependentView(); + } +} + +void QtSLiMChromosomeWidget::updateDependentView(void) +{ + if (dependentChromosomeView_) + { + Chromosome *chromosome = focalChromosome(); + + dependentChromosomeView_->setFocalChromosome(chromosome); + + if (chromosome) + dependentChromosomeView_->setDisplayedRange(getSelectedRange(chromosome)); + + dependentChromosomeView_->stateChanged(); + } +} + void QtSLiMChromosomeWidget::stateChanged(void) { // when the model state changes, we toss our cached haplotype manager to generate a new plot @@ -152,17 +643,16 @@ void QtSLiMChromosomeWidget::stateChanged(void) haplotype_mgr_ = nullptr; } - if (referenceChromosomeView_) - { - Chromosome *newFocalChromosome = referenceChromosomeView_->focalChromosome(); - - if (newFocalChromosome != focalChromosome()) - setFocalChromosome(newFocalChromosome); - } - update(); } +void QtSLiMChromosomeWidget::updateAfterTick(void) +{ + // overview chromosomes don't need to update all the time, since their display doesn't change + if (!isOverview_) + stateChanged(); +} + #ifndef SLIM_NO_OPENGL void QtSLiMChromosomeWidget::initializeGL() { @@ -217,9 +707,16 @@ QRect QtSLiMChromosomeWidget::getContentRect(void) { QRect bounds = rect(); - // Two things are going on here. The width gets inset by two pixels on each side because our frame is outset that much from our apparent frame, to - // make room for the selection knobs to spill over a bit. The height gets adjusted because our "content rect" does not include our ticks. - return QRect(bounds.left(), bounds.top(), bounds.width(), bounds.height() - (isOverview_ ? (selectionKnobSize+1) : heightForTicks)); + // The height gets adjusted because our "content rect" does not include the space for selection knobs below + // (for the overview) or for tick marks and labels (for the zoomed view). Note that SLiMguiLegacy has a two- + // pixel margin on the left and right of the chromosome view, to avoid clipping the selection knobs, but that + // is a bit harder to do in Qt since the UI layout is trickier, so we just let the knobs clip; it's fine. + int bottomMargin = (isOverview_ ? (selectionKnobSize+1) : heightForTicks); + + if (!isOverview_ && !showsTicks_) + bottomMargin = 0; + + return QRect(bounds.left(), bounds.top(), bounds.width(), bounds.height() - bottomMargin); } QRect QtSLiMChromosomeWidget::getInteriorRect(void) @@ -227,36 +724,22 @@ QRect QtSLiMChromosomeWidget::getInteriorRect(void) return getContentRect().marginsRemoved(QMargins(1, 1, 1, 1)); } -void QtSLiMChromosomeWidget::setReferenceChromosomeView(QtSLiMChromosomeWidget *p_ref_widget) -{ - if (referenceChromosomeView_ != p_ref_widget) - { - if (referenceChromosomeView_) - disconnect(referenceChromosomeView_); - - referenceChromosomeView_ = p_ref_widget; - isOverview_ = true; - - if (referenceChromosomeView_) - { - isOverview_ = false; - connect(referenceChromosomeView_, &QtSLiMChromosomeWidget::selectedRangeChanged, this, [this]() { stateChanged(); }); - } - } -} - QtSLiMRange QtSLiMChromosomeWidget::getSelectedRange(Chromosome *chromosome) { - if (hasSelection_) + if (hasSelection_ && chromosome && (chromosome == focalChromosome())) { return QtSLiMRange(selectionFirstBase_, selectionLastBase_ - selectionFirstBase_ + 1); // number of bases encompassed; a selection from x to x encompasses 1 base } - else + else if (chromosome) { slim_position_t chromosomeLastPosition = chromosome->last_position_; return QtSLiMRange(0, chromosomeLastPosition + 1); // chromosomeLastPosition + 1 bases are encompassed } + else + { + return QtSLiMRange(0, 0); + } } void QtSLiMChromosomeWidget::setSelectedRange(QtSLiMRange p_selectionRange) @@ -289,8 +772,9 @@ void QtSLiMChromosomeWidget::setSelectedRange(QtSLiMRange p_selectionRange) // Our selection changed, so update and post a change notification update(); - - emit selectedRangeChanged(); + + if (isOverview_ && dependentChromosomeView_) + updateDependentView(); } void QtSLiMChromosomeWidget::restoreLastSelection(void) @@ -305,17 +789,14 @@ void QtSLiMChromosomeWidget::restoreLastSelection(void) { hasSelection_ = false; } - else - { - // We want to always post the notification, to make sure updating happens correctly; - // this ensures that correct ticks marks get drawn after a recycle, etc. - //return; - } // Our selection changed, so update and post a change notification update(); - - emit selectedRangeChanged(); + + // We want to always post the notification, to make sure updating happens correctly; + // this ensures that correct ticks marks get drawn after a recycle, etc. + if (isOverview_ && dependentChromosomeView_) + updateDependentView(); } QtSLiMRange QtSLiMChromosomeWidget::getDisplayedRange(Chromosome *chromosome) @@ -328,9 +809,22 @@ QtSLiMRange QtSLiMChromosomeWidget::getDisplayedRange(Chromosome *chromosome) } else { - QtSLiMChromosomeWidget *reference = referenceChromosomeView_; - - return reference->getSelectedRange(chromosome); + return displayedRange_; + } +} + +void QtSLiMChromosomeWidget::setDisplayedRange(QtSLiMRange p_displayedRange) +{ + displayedRange_ = p_displayedRange; + update(); +} + +void QtSLiMChromosomeWidget::setShowsTicks(bool p_showTicks) +{ + if (p_showTicks != showsTicks_) + { + showsTicks_ = p_showTicks; + update(); } } @@ -355,7 +849,7 @@ void QtSLiMChromosomeWidget::paintEvent(QPaintEvent * /* p_paint_event */) // if the simulation is at tick 0, it is not ready if (ready) - if (controller_->community->Tick() == 0) + if (controller_->community()->Tick() == 0) ready = false; if (ready) @@ -370,8 +864,9 @@ void QtSLiMChromosomeWidget::paintEvent(QPaintEvent * /* p_paint_event */) QtSLiMRange displayedRange = getDisplayedRange(chromosome); // draw ticks at bottom of content rect - drawTicksInContentRect(contentRect, displaySpecies, displayedRange, painter); - + if (showsTicks_) + drawTicksInContentRect(contentRect, displaySpecies, displayedRange, painter); + // do the core drawing, with or without OpenGL according to user preference #ifndef SLIM_NO_OPENGL if (QtSLiMPreferencesNotifier::instance().useOpenGLPref()) @@ -393,10 +888,10 @@ void QtSLiMChromosomeWidget::paintEvent(QPaintEvent * /* p_paint_event */) else { // erase the content area itself - painter.fillRect(interiorRect, QtSLiMColorWithWhite(inDarkMode ? 0.118 : 0.88, 1.0)); + painter.fillRect(interiorRect, QtSLiMColorWithWhite(inDarkMode ? 0.118 : 0.9, 1.0)); // frame - QtSLiMFrameRect(contentRect, QtSLiMColorWithWhite(inDarkMode ? 0.067 : 0.6, 1.0), painter); + QtSLiMFrameRect(contentRect, QtSLiMColorWithWhite(inDarkMode ? 0.067 : 0.77, 1.0), painter); } } @@ -490,17 +985,22 @@ void QtSLiMChromosomeWidget::drawOverview(Species *displaySpecies, QPainter &pai if (chrom == focalChrom) { - // overlay the selection last, since it bridges over the frame if (hasSelection_) { + // overlay the selection last, since it bridges over the frame QtSLiMFrameRect(chromContentRect, QtSLiMColorWithWhite(inDarkMode ? 0.067 : 0.6, 1.0), painter); overlaySelection(chromInteriorRect, displayedRange, painter); } else if (chromosomes.size() > 1) { + // highlight the selected chromosome, if we have more than one chromosome painter.fillRect(chromInteriorRect, QtSLiMColorWithWhite(0.0, 0.30)); QtSLiMFrameRect(chromContentRect, QtSLiMColorWithWhite(inDarkMode ? 1.0 : 0.0, 1.0), painter); } + else + { + QtSLiMFrameRect(chromContentRect, QtSLiMColorWithWhite(inDarkMode ? 0.067 : 0.6, 1.0), painter); + } } else { @@ -690,10 +1190,11 @@ void QtSLiMChromosomeWidget::overlaySelection(QRect interiorRect, QtSLiMRange di } } -Chromosome *QtSLiMChromosomeWidget::_setFocalChromosomeForTracking(QMouseEvent *p_event) +Chromosome *QtSLiMChromosomeWidget::_findFocalChromosomeForTracking(QMouseEvent *p_event) { // this hit-tracks the same layout that drawOverview() displays QPoint curPoint = p_event->pos(); + QRect overallRect = rect(); QRect contentRect = getContentRect(); Species *displaySpecies = focalDisplaySpecies(); const std::vector &chromosomes = displaySpecies->Chromosomes(); @@ -711,24 +1212,22 @@ Chromosome *QtSLiMChromosomeWidget::_setFocalChromosomeForTracking(QMouseEvent * int64_t remainingLength = totalLength; int leftPosition = contentRect.left(); + // note that we hit-test against the overall frames of the chromosomes (including the margin + // at the bottom for selection knobs), but set contentRectForTrackedChromosome_ based on the + // content rect for the chromosome (excluding that margin); see mousePressEvent() for why. for (Chromosome *chrom : chromosomes) { double scale = (double)availableWidth / remainingLength; slim_position_t chromLength = (chrom->last_position_ - chrom->first_position_ + 1); int width = (int)round(chromLength * scale); int paddedWidth = 2 + width; - QRect chromContentRect(leftPosition, contentRect.top(), paddedWidth, contentRect.height()); + QRect chromOverallFrame(leftPosition, overallRect.top(), paddedWidth, overallRect.height()); - if (chromContentRect.contains(curPoint)) + if (chromOverallFrame.contains(curPoint)) { - if (chrom != focalChromosome()) - { - setFocalChromosome(chrom); - update(); - } + QRect chromContentRect(leftPosition, contentRect.top(), paddedWidth, contentRect.height()); contentRectForTrackedChromosome_ = chromContentRect; - return chrom; } @@ -747,46 +1246,26 @@ void QtSLiMChromosomeWidget::mousePressEvent(QMouseEvent *p_event) // if the simulation is at tick 0, it is not ready if (ready) - if (controller_->community->Tick() == 0) + if (controller_->community()->Tick() == 0) ready = false; if (ready) { - // first, switch to the chromosome that was clicked in - Chromosome *focalChrom = _setFocalChromosomeForTracking(p_event); + // find which chromosome was clicked in; this sets contentRectForTrackedChromosome_ to the content rect of that chromosome + // note that it hit-tests aginst the overall chromosome view, including the selection knob margin, though + Chromosome *hitChromosome = _findFocalChromosomeForTracking(p_event); - if (!focalChrom) + // if the click was not in a chromosome (like in the gap between them), just return with no effect + if (!hitChromosome) return; - QRect contentRect = contentRectForTrackedChromosome_; - QRect interiorRect = contentRect.marginsRemoved(QMargins(1, 1, 1, 1)); - QtSLiMRange displayedRange = getDisplayedRange(focalChrom); + QRect contentRect = contentRectForTrackedChromosome_; + QRect interiorRect = contentRect.marginsRemoved(QMargins(1, 1, 1, 1)); + QtSLiMRange displayedRange = getDisplayedRange(hitChromosome); QPoint curPoint = p_event->pos(); - - // Option-clicks just set the selection to the clicked genomic element, no questions asked - /*if (p_event->modifiers() & Qt::AltModifier) - { - if (contentRect.contains(curPoint)) - { - slim_position_t clickedBase = baseForPosition(curPoint.x(), interiorRect, displayedRange); - QtSLiMRange selectionRange = QtSLiMRange(0, 0); - - for (GenomicElement *genomicElement : chromosome.GenomicElements()) - { - slim_position_t startPosition = genomicElement->start_position_; - slim_position_t endPosition = genomicElement->end_position_; - - if ((clickedBase >= startPosition) && (clickedBase <= endPosition)) - selectionRange = QtSLiMRange(startPosition, endPosition - startPosition + 1); - } - - setSelectedRange(selectionRange); - return; - } - }*/ - - // first check for a hit in one of our selection handles - /*if (hasSelection_) + + // check for a hit in one of our selection handles + if (hasSelection_ && (hitChromosome == focalChromosome())) { QRect selectionRect = rectEncompassingBaseToBase(selectionFirstBase_, selectionLastBase_, interiorRect, displayedRange); int leftEdge = selectionRect.left(); @@ -816,9 +1295,43 @@ void QtSLiMChromosomeWidget::mousePressEvent(QMouseEvent *p_event) mouseMoveEvent(p_event); // the click may not be aligned exactly on the center of the bar, so clicking might shift it a bit; do that now return; } - }*/ - - if (contentRect.contains(curPoint)) + } + + // _findFocalChromosomeForTracking() will return a hit anywhere in the overall chromosome view, so that we can test for hits + // in the selection knobs above; but from this point forward, we only want to handle hits that are actually in the content area + if (!contentRect.contains(curPoint)) + return; + + // given that it wasn't a hit in a selection handle, we now switch to the chromosome that was clicked in; + // other kinds of clicks change the focal chromosome to the one hit by the click + if (hitChromosome != focalChromosome()) + { + setFocalChromosome(hitChromosome); + update(); + } + + // option-clicks just set the selection to the clicked genomic element, no questions asked + // tracking does not continue beyond this step, since we don't set isTracking_ = true + if (p_event->modifiers() & Qt::AltModifier) + { + slim_position_t clickedBase = baseForPosition(curPoint.x(), interiorRect, displayedRange); + QtSLiMRange selectionRange = QtSLiMRange(0, 0); + GenomicElement *genomicElement = hitChromosome->ElementForPosition(clickedBase); + + if (genomicElement) + { + slim_position_t startPosition = genomicElement->start_position_; + slim_position_t endPosition = genomicElement->end_position_; + selectionRange = QtSLiMRange(startPosition, endPosition - startPosition + 1); + } + + mouseInsideCounter_++; // prevent a flip to displaying chromosome numbers + + setSelectedRange(selectionRange); + return; + } + + // otherwise we have an ordinary click, selecting a chromosome and perhaps dragging out a selection { isTracking_ = true; trackingStartBase_ = baseForPosition(curPoint.x(), interiorRect, displayedRange); @@ -834,15 +1347,12 @@ void QtSLiMChromosomeWidget::mousePressEvent(QMouseEvent *p_event) savedHasSelection_ = hasSelection_; update(); - emit selectedRangeChanged(); + updateDependentView(); } } } } -// - (void)setUpMarker:(SLiMSelectionMarker **)marker atBase:(slim_position_t)selectionBase isLeft:(BOOL)isLeftMarker -// FIXME at present QtSLiM doesn't have the selection markers during tracking that SLiMgui has... - void QtSLiMChromosomeWidget::_mouseTrackEvent(QMouseEvent *p_event) { QRect contentRect = contentRectForTrackedChromosome_; @@ -880,8 +1390,6 @@ void QtSLiMChromosomeWidget::_mouseTrackEvent(QMouseEvent *p_event) // Save the selection for restoring across recycles, etc. savedHasSelection_ = hasSelection_; - - //[self removeSelectionMarkers]; } else { @@ -894,15 +1402,12 @@ void QtSLiMChromosomeWidget::_mouseTrackEvent(QMouseEvent *p_event) savedSelectionFirstBase_ = selectionFirstBase_; savedSelectionLastBase_ = selectionLastBase_; savedHasSelection_ = hasSelection_; - - //[self setUpMarker:&startMarker atBase:selectionFirstBase isLeft:YES]; - //[self setUpMarker:&endMarker atBase:selectionLastBase isLeft:NO]; } if (selectionChanged) { update(); - emit selectedRangeChanged(); + updateDependentView(); } } } diff --git a/QtSLiM/QtSLiMChromosomeWidget.h b/QtSLiM/QtSLiMChromosomeWidget.h index b8fce2f9..23a48f8e 100644 --- a/QtSLiM/QtSLiMChromosomeWidget.h +++ b/QtSLiM/QtSLiMChromosomeWidget.h @@ -24,6 +24,7 @@ #define GL_SILENCE_DEPRECATION #include +#include #ifndef SLIM_NO_OPENGL #include @@ -39,16 +40,63 @@ class QtSLiMWindow; class QtSLiMHaplotypeManager; class QPainter; class QContextMenuEvent; +class QButton; struct QtSLiMRange { int64_t location, length; + explicit QtSLiMRange() : location(0), length(0) {} explicit QtSLiMRange(int64_t p_location, int64_t p_length) : location(p_location), length(p_length) {} }; +// This is a little controller class that governs a chromosome view or views, and an associated action button +// It is used by QtSLiMWindow for the main chromosome views (overview and zoomed), and by the chromosome display +class QtSLiMChromosomeWidgetController : public QObject +{ + Q_OBJECT + + QtSLiMWindow *slimWindow_ = nullptr; + + // state used only in the chromosome display case + QPointer displayWindow_ = nullptr; + std::string focalSpeciesName_; // we keep the name of our focal species, since a pointer would be unsafe + std::string focalSpeciesAvatar_; // cached so we can display it even when the simulation is invalid + bool needsRebuild_ = false; // true immediately after recycling + +public: + bool useScaledWidths_ = true; // used only by the chromosome display + bool shouldDrawMutations_ = true; + bool shouldDrawFixedSubstitutions_ = false; + bool shouldDrawGenomicElements_ = false; + bool shouldDrawRateMaps_ = false; + + bool displayHaplotypes_ = false; // if false, displaying frequencies; if true, displaying haplotypes + std::vector displayMuttypes_; // if empty, display all mutation types; otherwise, display only the muttypes chosen + + QtSLiMChromosomeWidgetController(QtSLiMWindow *slimWindow, QWidget *displayWindow, Species *focalSpecies); + + void buildChromosomeDisplay(bool resetWindowSize); + void updateFromController(void); + + void runChromosomeContextMenuAtPoint(QPoint p_globalPoint); + void actionButtonRunMenu(QtSLiMPushButton *p_actionButton); + + // forwards from slimWindow_; this is everything QtSLiMChromosomeWidget needs from the outside world + bool invalidSimulation(void) { return slimWindow_->invalidSimulation(); } + Community *community(void) { return slimWindow_->community; } + Species *focalDisplaySpecies(void); + void colorForGenomicElementType(GenomicElementType *elementType, slim_objectid_t elementTypeID, float *p_red, float *p_green, float *p_blue, float *p_alpha) + { slimWindow_->colorForGenomicElementType(elementType, elementTypeID, p_red, p_green, p_blue, p_alpha); } + QtSLiMWindow *slimWindow(void) { return slimWindow_; } + +signals: + void needsRedisplay(void); +}; + + // This is a fast macro for when all we need is the offset of a base from the left edge of interiorRect; interiorRect.origin.x is not added here! // This is based on the same math as rectEncompassingBase:toBase:interiorRect:displayedRange:, and must be kept in synch with that method. #define LEFT_OFFSET_OF_BASE(startBase, interiorRect, displayedRange) (static_cast(floor(((startBase - static_cast(displayedRange.location)) / static_cast(displayedRange.length)) * interiorRect.width()))) @@ -60,16 +108,21 @@ class QtSLiMChromosomeWidget : public QOpenGLWidget, protected QOpenGLFunctions class QtSLiMChromosomeWidget : public QWidget #endif { - Q_OBJECT + Q_OBJECT - QtSLiMWindow *controller_ = nullptr; + QtSLiMChromosomeWidgetController *controller_ = nullptr; std::string focalSpeciesName_; // we keep the name of our focal species, since a pointer would be unsafe - std::string focalChromosomeSymbol_; // we keep the name of our focal species, since a pointer would be unsafe + std::string focalChromosomeSymbol_; // we keep the symbol of our focal chromosome, since a pointer would be unsafe - bool isOverview_ = true; - QtSLiMChromosomeWidget *referenceChromosomeView_ = nullptr; + bool isOverview_ = false; + QtSLiMChromosomeWidget *dependentChromosomeView_ = nullptr; - // Selection + bool showsTicks_ = true; + + // Displayed range (only in a regular chromosome view) + QtSLiMRange displayedRange_; + + // Selection (only in the overview) bool hasSelection_ = false; slim_position_t selectionFirstBase_ = 0, selectionLastBase_ = 0; @@ -82,7 +135,7 @@ class QtSLiMChromosomeWidget : public QWidget int mouseInsideCounter_ = 0; bool showChromosomeNumbers_ = false; // set true after a delay when the mouse is inside - // Tracking + // Tracking (only in the overview) bool isTracking_ = false; QRect contentRectForTrackedChromosome_; slim_position_t trackingStartBase_ = 0, trackingLastBase_ = 0; @@ -94,28 +147,32 @@ class QtSLiMChromosomeWidget : public QWidget QtSLiMHaplotypeManager *haplotype_mgr_ = nullptr; // the haplotype manager constructed for the current display; cached public: - explicit QtSLiMChromosomeWidget(QWidget *p_parent = nullptr, QtSLiMWindow *controller = nullptr, Species *displaySpecies = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + explicit QtSLiMChromosomeWidget(QWidget *p_parent = nullptr, QtSLiMChromosomeWidgetController *controller = nullptr, Species *displaySpecies = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); virtual ~QtSLiMChromosomeWidget() override; - void setController(QtSLiMWindow *controller); + void setController(QtSLiMChromosomeWidgetController *controller); + Chromosome *resetToDefaultChromosome(void); void setFocalDisplaySpecies(Species *displaySpecies); Species *focalDisplaySpecies(void); void setFocalChromosome(Chromosome *chromosome); Chromosome *focalChromosome(void); - void setReferenceChromosomeView(QtSLiMChromosomeWidget *p_ref_widget); + void setDependentChromosomeView(QtSLiMChromosomeWidget *p_dependent_widget); bool hasSelection(void) { return hasSelection_; } QtSLiMRange getSelectedRange(Chromosome *chromosome); void setSelectedRange(QtSLiMRange p_selectionRange); void restoreLastSelection(void); + void updateDependentView(void); QtSLiMRange getDisplayedRange(Chromosome *chromosome); + void setDisplayedRange(QtSLiMRange p_displayedRange); - void stateChanged(void); // update when the SLiM model state changes; tosses any cached display info + bool showsTicks(void) { return showsTicks_; } + void setShowsTicks(bool p_showTicks); -signals: - void selectedRangeChanged(void); + void stateChanged(void); // update when the SLiM model state changes; tosses any cached display info + void updateAfterTick(void); protected: #ifndef SLIM_NO_OPENGL @@ -158,7 +215,7 @@ class QtSLiMChromosomeWidget : public QWidget void qtDrawMutationIntervals(QRect &interiorRect, Species *displaySpecies, QtSLiMRange displayedRange, QPainter &painter); void qtDrawRateMaps(QRect &interiorRect, Species *displaySpecies, QtSLiMRange displayedRange, QPainter &painter); - Chromosome *_setFocalChromosomeForTracking(QMouseEvent *p_event); + Chromosome *_findFocalChromosomeForTracking(QMouseEvent *p_event); virtual void mousePressEvent(QMouseEvent *p_event) override; void _mouseTrackEvent(QMouseEvent *p_event); virtual void mouseMoveEvent(QMouseEvent *p_event) override; @@ -175,12 +232,12 @@ class QtSLiMChromosomeWidget : public QWidget // Our configuration is kept by the controller, since it is shared by all chromosome views for multispecies models // However, "overview" chromosome views are always configured the same, hard-coded here - inline bool shouldDrawMutations(void) const { return isOverview_ ? false : controller_->chromosome_shouldDrawMutations_; } - inline bool shouldDrawFixedSubstitutions(void) const { return isOverview_ ? false : controller_->chromosome_shouldDrawFixedSubstitutions_; } - inline bool shouldDrawGenomicElements(void) const { return isOverview_ ? true : controller_->chromosome_shouldDrawGenomicElements_; } - inline bool shouldDrawRateMaps(void) const { return isOverview_ ? false : controller_->chromosome_shouldDrawRateMaps_; } - inline bool displayHaplotypes(void) const { return isOverview_ ? false : controller_->chromosome_display_haplotypes_; } - inline std::vector &displayMuttypes(void) const { return controller_->chromosome_display_muttypes_; } + inline bool shouldDrawMutations(void) const { return isOverview_ ? false : controller_->shouldDrawMutations_; } + inline bool shouldDrawFixedSubstitutions(void) const { return isOverview_ ? false : controller_->shouldDrawFixedSubstitutions_; } + inline bool shouldDrawGenomicElements(void) const { return isOverview_ ? true : controller_->shouldDrawGenomicElements_; } + inline bool shouldDrawRateMaps(void) const { return isOverview_ ? false : controller_->shouldDrawRateMaps_; } + inline bool displayHaplotypes(void) const { return isOverview_ ? false : controller_->displayHaplotypes_; } + inline std::vector &displayMuttypes(void) const { return controller_->displayMuttypes_; } }; #endif // QTSLIMCHROMOSOMEWIDGET_H diff --git a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp index 8bf7b75c..27ab6fbf 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp @@ -42,7 +42,7 @@ void QtSLiMChromosomeWidget::glDrawRect(Species *displaySpecies) // if the simulation is at tick 0, it is not ready if (ready) - if (controller_->community->Tick() == 0) + if (controller_->community()->Tick() == 0) ready = false; if (ready) @@ -91,7 +91,7 @@ void QtSLiMChromosomeWidget::glDrawRect(Species *displaySpecies) if (!haplotype_mgr_) { size_t interiorHeight = static_cast(interiorRect.height()); // one sample per available pixel line, for simplicity and speed; 47, in the current UI layout - haplotype_mgr_ = new QtSLiMHaplotypeManager(nullptr, QtSLiMHaplotypeManager::ClusterNearestNeighbor, QtSLiMHaplotypeManager::ClusterNoOptimization, controller_, displaySpecies, interiorHeight, false); + haplotype_mgr_ = new QtSLiMHaplotypeManager(nullptr, QtSLiMHaplotypeManager::ClusterNearestNeighbor, QtSLiMHaplotypeManager::ClusterNoOptimization, controller_, displaySpecies, displayedRange, interiorHeight, false); } if (haplotype_mgr_) diff --git a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp index 84672d53..3263b425 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp @@ -40,7 +40,7 @@ void QtSLiMChromosomeWidget::qtDrawRect(Species *displaySpecies, QPainter &paint // if the simulation is at tick 0, it is not ready if (ready) - if (controller_->community->Tick() == 0) + if (controller_->community()->Tick() == 0) ready = false; if (ready) @@ -88,7 +88,7 @@ void QtSLiMChromosomeWidget::qtDrawRect(Species *displaySpecies, QPainter &paint if (!haplotype_mgr_) { size_t interiorHeight = static_cast(interiorRect.height()); // one sample per available pixel line, for simplicity and speed; 47, in the current UI layout - haplotype_mgr_ = new QtSLiMHaplotypeManager(nullptr, QtSLiMHaplotypeManager::ClusterNearestNeighbor, QtSLiMHaplotypeManager::ClusterNoOptimization, controller_, displaySpecies, interiorHeight, false); + haplotype_mgr_ = new QtSLiMHaplotypeManager(nullptr, QtSLiMHaplotypeManager::ClusterNearestNeighbor, QtSLiMHaplotypeManager::ClusterNoOptimization, controller_, displaySpecies, displayedRange, interiorHeight, false); } if (haplotype_mgr_) diff --git a/QtSLiM/QtSLiMGraphView.cpp b/QtSLiM/QtSLiMGraphView.cpp index 7869aee6..59686a5c 100644 --- a/QtSLiM/QtSLiMGraphView.cpp +++ b/QtSLiM/QtSLiMGraphView.cpp @@ -71,8 +71,7 @@ QtSLiMGraphView::QtSLiMGraphView(QWidget *p_parent, QtSLiMWindow *controller) : controller_ = controller; setFocalDisplaySpecies(controller_->focalDisplaySpecies()); - connect(controller, &QtSLiMWindow::controllerUpdatedAfterTick, this, &QtSLiMGraphView::updateAfterTick); - connect(controller, &QtSLiMWindow::controllerChromosomeSelectionChanged, this, &QtSLiMGraphView::controllerChromosomeSelectionChanged); + connect(controller, &QtSLiMWindow::controllerFullUpdateAfterTick, this, &QtSLiMGraphView::updateAfterTick); connect(controller, &QtSLiMWindow::controllerTickFinished, this, &QtSLiMGraphView::controllerTickFinished); connect(controller, &QtSLiMWindow::controllerRecycled, this, &QtSLiMGraphView::controllerRecycled); @@ -1279,10 +1278,6 @@ void QtSLiMGraphView::controllerRecycled(void) if (action) action->setEnabled(!controller_->invalidSimulation() && !missingFocalDisplaySpecies()); } -void QtSLiMGraphView::controllerChromosomeSelectionChanged(void) -{ -} - void QtSLiMGraphView::controllerTickFinished(void) { } diff --git a/QtSLiM/QtSLiMGraphView.h b/QtSLiM/QtSLiMGraphView.h index 3316dcda..519381f7 100644 --- a/QtSLiM/QtSLiMGraphView.h +++ b/QtSLiM/QtSLiMGraphView.h @@ -109,9 +109,9 @@ public slots: virtual void invalidateDrawingCache(void); // subclasses must call this themselves in their destructor - super cannot do it! virtual void graphWindowResized(void); virtual void controllerRecycled(void); // subclasses must call super: QtSLiMGraphView::controllerRecycled() - virtual void controllerChromosomeSelectionChanged(void); virtual void controllerTickFinished(void); virtual void updateAfterTick(void); // subclasses must call super: QtSLiMGraphView::updateAfterTick() + virtual void updateSpeciesBadge(void); void actionButtonRunMenu(QtSLiMPushButton *actionButton); protected: @@ -122,7 +122,6 @@ public slots: void setFocalDisplaySpecies(Species *species); Species *focalDisplaySpecies(void); bool missingFocalDisplaySpecies(void); // true if the graph has a focal display species but can't find it - void updateSpeciesBadge(void); // Base graphing functionality QRect interiorRectForBounds(QRect bounds); diff --git a/QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp b/QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp index 791c90bf..060f47dd 100644 --- a/QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp +++ b/QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp @@ -19,8 +19,6 @@ #include "QtSLiMGraphView_1DPopulationSFS.h" -#include - #include "QtSLiMWindow.h" @@ -84,10 +82,6 @@ double *QtSLiMGraphView_1DPopulationSFS::populationSFS(int mutationTypeCount) // get the selected chromosome range Species *graphSpecies = focalDisplaySpecies(); - bool hasSelection; - slim_position_t selectionFirstBase; - slim_position_t selectionLastBase; - controller_->chromosomeSelection(graphSpecies, &hasSelection, &selectionFirstBase, &selectionLastBase); // tally into our bins Population &pop = graphSpecies->population_; @@ -104,15 +98,6 @@ double *QtSLiMGraphView_1DPopulationSFS::populationSFS(int mutationTypeCount) { const Mutation *mutation = mut_block_ptr + registry[registry_index]; - // if the user has selected a subrange of the chromosome, we will work from that - if (hasSelection) - { - slim_position_t mutationPosition = mutation->position_; - - if ((mutationPosition < selectionFirstBase) || (mutationPosition > selectionLastBase)) - continue; - } - slim_refcount_t mutationRefCount = *(refcount_block_ptr + mutation->BlockIndex()); double mutationFrequency = mutationRefCount / totalHaplosomeCount; int mutationBin = static_cast(floor(mutationFrequency * binCount)); @@ -157,29 +142,6 @@ void QtSLiMGraphView_1DPopulationSFS::drawGraph(QPainter &painter, QRect interio // plot our histogram bars drawGroupedBarplot(painter, interiorRect, spectrum, mutationTypeCount, binCount, 0.0, (1.0 / binCount)); - - // if we have a limited selection range, overdraw a note about that - bool hasSelection; - slim_position_t selectionFirstBase; - slim_position_t selectionLastBase; - controller_->chromosomeSelection(graphSpecies, &hasSelection, &selectionFirstBase, &selectionLastBase); - - if (hasSelection) - { - painter.setFont(QtSLiMGraphView::fontForTickLabels()); - painter.setBrush(Qt::darkGray); - - QString labelText = QString("%1 – %2").arg(selectionFirstBase).arg(selectionLastBase); - QRect labelBoundingRect = painter.boundingRect(QRect(), Qt::TextDontClip | Qt::TextSingleLine, labelText); - double labelX = interiorRect.x() + (interiorRect.width() - labelBoundingRect.width()) / 2.0; - double labelY = interiorRect.y() + interiorRect.height() - (labelBoundingRect.height() + 4); - - labelY = painter.transform().map(QPointF(labelX, labelY)).y(); - - painter.setWorldMatrixEnabled(false); - painter.drawText(QPointF(labelX, labelY), labelText); - painter.setWorldMatrixEnabled(true); - } } QtSLiMLegendSpec QtSLiMGraphView_1DPopulationSFS::legendKey(void) @@ -187,12 +149,6 @@ QtSLiMLegendSpec QtSLiMGraphView_1DPopulationSFS::legendKey(void) return mutationTypeLegendKey(); // we use the prefab mutation type legend } -void QtSLiMGraphView_1DPopulationSFS::controllerChromosomeSelectionChanged(void) -{ - invalidateCachedData(); - update(); -} - bool QtSLiMGraphView_1DPopulationSFS::providesStringForData(void) { return true; @@ -202,14 +158,6 @@ void QtSLiMGraphView_1DPopulationSFS::appendStringForData(QString &string) { // get the selected chromosome range Species *graphSpecies = focalDisplaySpecies(); - bool hasSelection; - slim_position_t selectionFirstBase; - slim_position_t selectionLastBase; - controller_->chromosomeSelection(graphSpecies, &hasSelection, &selectionFirstBase, &selectionLastBase); - - if (hasSelection) - string.append(QString("# Selected chromosome range: %1 – %2\n").arg(selectionFirstBase).arg(selectionLastBase)); - int binCount = histogramBinCount_; int mutationTypeCount = static_cast(graphSpecies->mutation_types_.size()); double *plotData = populationSFS(mutationTypeCount); diff --git a/QtSLiM/QtSLiMGraphView_1DPopulationSFS.h b/QtSLiM/QtSLiMGraphView_1DPopulationSFS.h index 861e8cc8..9f6cb050 100644 --- a/QtSLiM/QtSLiMGraphView_1DPopulationSFS.h +++ b/QtSLiM/QtSLiMGraphView_1DPopulationSFS.h @@ -40,9 +40,6 @@ class QtSLiMGraphView_1DPopulationSFS : public QtSLiMGraphView virtual bool providesStringForData(void) override; virtual void appendStringForData(QString &string) override; -public slots: - virtual void controllerChromosomeSelectionChanged(void) override; - private: double *populationSFS(int mutationTypeCount); }; diff --git a/QtSLiM/QtSLiMHaplotypeManager.cpp b/QtSLiM/QtSLiMHaplotypeManager.cpp index 38f975dd..60d16258 100644 --- a/QtSLiM/QtSLiMHaplotypeManager.cpp +++ b/QtSLiM/QtSLiMHaplotypeManager.cpp @@ -18,7 +18,7 @@ // You should have received a copy of the GNU General Public License along with SLiM. If not, see . #include "QtSLiMHaplotypeManager.h" -#include "QtSLiMWindow.h" +#include "QtSLiMChromosomeWidget.h" #include "QtSLiMHaplotypeOptions.h" #include "QtSLiMHaplotypeProgress.h" #include "QtSLiMPreferences.h" @@ -49,10 +49,10 @@ // This class method runs a plot options dialog, and then produces a haplotype plot with a progress panel as it is being constructed -void QtSLiMHaplotypeManager::CreateHaplotypePlot(QtSLiMWindow *controller) +void QtSLiMHaplotypeManager::CreateHaplotypePlot(QtSLiMChromosomeWidgetController *controller) { Species *displaySpecies = controller->focalDisplaySpecies(); - QtSLiMHaplotypeOptions optionsPanel(controller); + QtSLiMHaplotypeOptions optionsPanel(controller->slimWindow()); int result = optionsPanel.exec(); @@ -63,13 +63,14 @@ void QtSLiMHaplotypeManager::CreateHaplotypePlot(QtSLiMWindow *controller) QtSLiMHaplotypeManager::ClusteringOptimization clusteringOptimization = optionsPanel.clusteringOptimization(); // First generate the haplotype plot data, with a progress panel + // FIXME MULTICHROM this code path should work with all of the chromosomes, probably... QtSLiMHaplotypeManager *haplotypeManager = new QtSLiMHaplotypeManager(nullptr, clusteringMethod, clusteringOptimization, - controller, displaySpecies, haplosomeSampleSize, true); + controller, displaySpecies, QtSLiMRange(0,0), haplosomeSampleSize, true); if (haplotypeManager->valid_) { // Make a new window to show the graph - QWidget *window = new QWidget(controller, Qt::Window | Qt::Tool); // the graph window has us as a parent, but is still a standalone window + QWidget *window = new QWidget(controller->slimWindow(), Qt::Window | Qt::Tool); // the graph window has us as a parent, but is still a standalone window window->setWindowTitle(QString("Haplotype snapshot (%1)").arg(haplotypeManager->titleString)); window->setMinimumSize(400, 200); @@ -101,7 +102,7 @@ void QtSLiMHaplotypeManager::CreateHaplotypePlot(QtSLiMWindow *controller) buttonLayout->setSpacing(5); topLayout->addLayout(buttonLayout); - if (controller->community->all_species_.size() > 1) + if (controller->community()->all_species_.size() > 1) { // make our species avatar badge QLabel *speciesLabel = new QLabel(); @@ -150,13 +151,14 @@ void QtSLiMHaplotypeManager::CreateHaplotypePlot(QtSLiMWindow *controller) } QtSLiMHaplotypeManager::QtSLiMHaplotypeManager(QObject *p_parent, ClusteringMethod clusteringMethod, ClusteringOptimization optimizationMethod, - QtSLiMWindow *controller, Species *displaySpecies, size_t sampleSize, bool showProgress) : + QtSLiMChromosomeWidgetController *controller, Species *displaySpecies, QtSLiMRange displayedRange, + size_t sampleSize, bool showProgress) : QObject(p_parent) { controller_ = controller; focalSpeciesName_ = displaySpecies->name_; - Community *community = controller_->community; + Community *community = controller_->community(); Species *graphSpecies = focalDisplaySpecies(); Population &population = graphSpecies->population_; @@ -174,12 +176,13 @@ QtSLiMHaplotypeManager::QtSLiMHaplotypeManager(QObject *p_parent, ClusteringMeth for (auto subpop_pair : population.subpops_) selected_subpops.emplace_back(subpop_pair.second); - // Figure out whether we're analyzing / displaying a subrange; gross that we go right into the ChromosomeView, I know... - - controller_->chromosomeSelection(graphSpecies, &usingSubrange, &subrangeFirstBase, &subrangeLastBase); + // Figure out whether we're analyzing / displaying a subrange + usingSubrange = (displayedRange.length == 0) ? false : true; + subrangeFirstBase = displayedRange.location; + subrangeLastBase = displayedRange.location + displayedRange.length - 1; // Also dig to find out whether we're displaying all mutation types or just a subset; if a subset, each MutationType has a display flag - displayingMuttypeSubset = (controller_->chromosomeDisplayMuttypes().size() != 0); + displayingMuttypeSubset = (controller_->displayMuttypes_.size() != 0); // Set our window title from the controller's state QString title; @@ -245,7 +248,7 @@ QtSLiMHaplotypeManager::QtSLiMHaplotypeManager(QObject *p_parent, ClusteringMeth { int progressSteps = (clusterOptimization == QtSLiMHaplotypeManager::ClusterOptimizeWith2opt) ? 3 : 2; - progressPanel_ = new QtSLiMHaplotypeProgress(controller_); + progressPanel_ = new QtSLiMHaplotypeProgress(controller_->slimWindow()); progressPanel_->runProgressWithHaplosomeCount(haplosomes.size(), progressSteps); } @@ -286,8 +289,8 @@ Species *QtSLiMHaplotypeManager::focalDisplaySpecies(void) { // We look up our focal species object by name every time, since keeping a pointer to it would be unsafe // Before initialize() is done species have not been created, so we return nullptr in that case - if (controller_ && controller_->community && (controller_->community->Tick() >= 1)) - return controller_->community->SpeciesWithName(focalSpeciesName_); + if (controller_ && controller_->community() && (controller_->community()->Tick() >= 1)) + return controller_->community()->SpeciesWithName(focalSpeciesName_); return nullptr; } diff --git a/QtSLiM/QtSLiMHaplotypeManager.h b/QtSLiM/QtSLiMHaplotypeManager.h index e651c7bb..f673787f 100644 --- a/QtSLiM/QtSLiMHaplotypeManager.h +++ b/QtSLiM/QtSLiMHaplotypeManager.h @@ -38,7 +38,10 @@ #include "slim_globals.h" #include "mutation.h" -class QtSLiMWindow; +#include "QtSLiMChromosomeWidget.h" + + +class QtSLiMChromosomeWidgetController; class Species; class Haplosome; class QtSLiMHaplotypeProgress; @@ -60,11 +63,12 @@ class QtSLiMHaplotypeManager : public QObject }; // This class method runs a plot options dialog, and then produces a haplotype plot with a progress panel as it is being constructed - static void CreateHaplotypePlot(QtSLiMWindow *controller); + static void CreateHaplotypePlot(QtSLiMChromosomeWidgetController *controller); // Constructing a QtSLiMHaplotypeManager directly is also allowing, if you don't want options or progress QtSLiMHaplotypeManager(QObject *p_parent, ClusteringMethod clusteringMethod, ClusteringOptimization optimizationMethod, - QtSLiMWindow *controller, Species *displaySpecies, size_t sampleSize, bool showProgress); + QtSLiMChromosomeWidgetController *controller, Species *displaySpecies, QtSLiMRange displayedRange, + size_t sampleSize, bool showProgress); ~QtSLiMHaplotypeManager(void); #ifndef SLIM_NO_OPENGL @@ -78,7 +82,7 @@ class QtSLiMHaplotypeManager : public QObject bool valid_ = true; // set to false if the user cancels the progress panel private: - QtSLiMWindow *controller_ = nullptr; + QtSLiMChromosomeWidgetController *controller_ = nullptr; std::string focalSpeciesName_; // we keep the name of our focal species, since a pointer would be unsafe Species *focalDisplaySpecies(void); diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index 9e757ff6..179037b1 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -593,15 +593,15 @@ void QtSLiMWindow::interpolateSplitters(void) void QtSLiMWindow::addChromosomeWidgets(QVBoxLayout *chromosomeLayout, QtSLiMChromosomeWidget *overviewWidget, QtSLiMChromosomeWidget *zoomedWidget) { - overviewWidget->setController(this); - overviewWidget->setReferenceChromosomeView(nullptr); + if (!chromosomeConfig) + chromosomeConfig = new QtSLiMChromosomeWidgetController(this, nullptr, nullptr); + + overviewWidget->setController(chromosomeConfig); + overviewWidget->setDependentChromosomeView(zoomedWidget); - zoomedWidget->setController(this); - zoomedWidget->setReferenceChromosomeView(overviewWidget); + zoomedWidget->setController(chromosomeConfig); + zoomedWidget->setDependentChromosomeView(nullptr); - // Forward notification of changes to the selection in the chromosome view - connect(overviewWidget, &QtSLiMChromosomeWidget::selectedRangeChanged, this, [this]() { emit controllerChromosomeSelectionChanged(); }); - // Add these widgets to our vectors of chromosome widgets chromosomeWidgetLayouts.push_back(chromosomeLayout); chromosomeOverviewWidgets.push_back(overviewWidget); @@ -1577,44 +1577,6 @@ std::vector QtSLiMWindow::selectedSubpopulations(void) return selectedSubpops; // note these are sorted by species, not by id, unlike listedSubpopulations() } -void QtSLiMWindow::chromosomeSelection(Species *species, bool *p_hasSelection, slim_position_t *p_selectionFirstBase, slim_position_t *p_selectionLastBase) -{ - // FIXME MULTICHROM not clear what happens to this in the multichrom world... - // First we need to look up the chromosome view for the requested species - for (QtSLiMChromosomeWidget *chromosomeWidget : chromosomeOverviewWidgets) - { - Species *widgetSpecies = chromosomeWidget->focalDisplaySpecies(); - - if (widgetSpecies == species) - { - if (p_hasSelection) - *p_hasSelection = chromosomeWidget->hasSelection(); - - QtSLiMRange selRange = chromosomeWidget->getSelectedRange(&species->TheChromosome()); - - if (p_selectionFirstBase) - *p_selectionFirstBase = selRange.location; - if (p_selectionLastBase) - *p_selectionLastBase = selRange.location + selRange.length - 1; - - return; - } - } - - // We drop through to here if the species can't be found, which should not happen - if (p_hasSelection) - *p_hasSelection = false; - if (p_selectionFirstBase) - *p_selectionFirstBase = 0; - if (p_selectionLastBase) - *p_selectionLastBase = species->TheChromosome().last_position_; -} - -const std::vector &QtSLiMWindow::chromosomeDisplayMuttypes(void) -{ - return chromosome_display_muttypes_; -} - void QtSLiMWindow::setInvalidSimulation(bool p_invalid) { if (invalidSimulation_ != p_invalid) @@ -2305,6 +2267,7 @@ void QtSLiMWindow::selectedSpeciesChanged(void) // do a full update to show the state for the new species updateAfterTickFull(true); + updateUIEnabling(); } QtSLiMGraphView *QtSLiMWindow::graphViewForGraphWindow(QWidget *p_window) @@ -2616,15 +2579,7 @@ void QtSLiMWindow::removeExtraChromosomeViews(void) ui->chromosomeLayout->removeItem(widgetLayout); - // remove all items under widgetLayout - QLayoutItem *child; - - while ((child = widgetLayout->takeAt(0)) != nullptr) - { - delete child->widget(); // delete the widget - delete child; // delete the layout item - } - + QtSLiMClearLayout(widgetLayout, /* deleteWidgets */ true); delete widgetLayout; ui->chromosomeLayout->update(); @@ -2634,7 +2589,7 @@ void QtSLiMWindow::removeExtraChromosomeViews(void) chromosomeZoomedWidgets.pop_back(); } - // Sometimes the call above to "delete child->widget()" hangs for up to a second. This appears to be due to + // Sometimes the call above to QtSLiMClearLayout hangs for up to a second. This appears to be due to // disposing of the OpenGL context used for the widget, and might be an AMD Radeon issue. Here's a backtrace // I managed to get from sample. The only thing I can think of to do about this would be to keep the view // around and reuse it, to avoid having to dispose of its context. But this may be specific to my hardware; @@ -2700,13 +2655,13 @@ void QtSLiMWindow::updateChromosomeViewSetup(void) sizePolicy1.setVerticalStretch(0); QVBoxLayout *chromosomeWidgetLayout = new QVBoxLayout(); - chromosomeWidgetLayout->setSpacing(15); + chromosomeWidgetLayout->setSpacing(4); overviewWidget = new QtSLiMChromosomeWidget(ui->centralWidget); sizePolicy1.setHeightForWidth(overviewWidget->sizePolicy().hasHeightForWidth()); overviewWidget->setSizePolicy(sizePolicy1); - overviewWidget->setMinimumSize(QSize(0, 30)); - overviewWidget->setMaximumSize(QSize(16777215, 30)); + overviewWidget->setMinimumSize(QSize(0, 20)); + overviewWidget->setMaximumSize(QSize(16777215, 20)); chromosomeWidgetLayout->addWidget(overviewWidget); zoomedWidget = new QtSLiMChromosomeWidget(ui->centralWidget); @@ -2827,9 +2782,6 @@ void QtSLiMWindow::updateAfterTickFull(bool fullUpdate) // Now update our other UI, some of which depends upon the state of subpopTableView ui->individualsWidget->update(); - for (QtSLiMChromosomeWidget *zoomedWidget : chromosomeZoomedWidgets) - zoomedWidget->stateChanged(); - if (fullUpdate) updateTickCounter(); @@ -2941,8 +2893,14 @@ void QtSLiMWindow::updateAfterTickFull(bool fullUpdate) } // Update graph windows as well; this will usually trigger an update() but may do other updating work as well - if (fullUpdate) - emit controllerUpdatedAfterTick(); + // BCH 9/26/2024: This mechanism is now used to update chromosome views as well. We now have two signals, + // one for partial updates and one for full updates; a given receiver should connect to just one of these signals. + // The partial update signal is always emitted; the full update signal is emitted only for full updates, + // which are less frequent. + if (fullUpdate) + emit controllerFullUpdateAfterTick(); + + emit controllerPartialUpdateAfterTick(); } void QtSLiMWindow::updatePlayButtonIcon(bool pressed) @@ -3008,7 +2966,11 @@ void QtSLiMWindow::updateUIEnabling(void) ui->browserButton->setEnabled(true); ui->jumpToPopupButton->setEnabled(true); + Species *species = focalDisplaySpecies(); + bool canMakeChromosomeDisplay = !invalidSimulation_ && (species != nullptr) && species->HasGenetics() && (species->Chromosomes().size() > 0); + ui->chromosomeActionButton->setEnabled(!invalidSimulation_); + ui->chromosomeDisplayButton->setEnabled(canMakeChromosomeDisplay); ui->clearOutputButton->setEnabled(!invalidSimulation_); ui->dumpPopulationButton->setEnabled(!invalidSimulation_); ui->debugOutputButton->setEnabled(true); @@ -5158,6 +5120,18 @@ void QtSLiMWindow::showDrawerClicked(void) tablesDrawerController->activateWindow(); } +void QtSLiMWindow::chromosomeDisplayClicked(void) +{ + QWidget *chromosomeDisplay = newChromosomeDisplay(); + + if (chromosomeDisplay) + { + chromosomeDisplay->show(); + chromosomeDisplay->raise(); + chromosomeDisplay->activateWindow(); + } +} + void QtSLiMWindow::showConsoleClicked(void) { isTransient = false; // Since the user has taken an interest in the window, clear the document's transient status @@ -5203,174 +5177,6 @@ void QtSLiMWindow::debugOutputClicked(void) debugOutputWindow_->activateWindow(); } -void QtSLiMWindow::runChromosomeContextMenuAtPoint(QPoint p_globalPoint) -{ - if (!invalidSimulation() && community && community->simulation_valid_) - { - QMenu contextMenu("chromosome_menu", this); - - QAction *displayMutations = contextMenu.addAction("Display Mutations"); - displayMutations->setCheckable(true); - displayMutations->setChecked(chromosome_shouldDrawMutations_); - - QAction *displaySubstitutions = contextMenu.addAction("Display Substitutions"); - displaySubstitutions->setCheckable(true); - displaySubstitutions->setChecked(chromosome_shouldDrawFixedSubstitutions_); - - QAction *displayGenomicElements = contextMenu.addAction("Display Genomic Elements"); - displayGenomicElements->setCheckable(true); - displayGenomicElements->setChecked(chromosome_shouldDrawGenomicElements_); - - QAction *displayRateMaps = contextMenu.addAction("Display Rate Maps"); - displayRateMaps->setCheckable(true); - displayRateMaps->setChecked(chromosome_shouldDrawRateMaps_); - - contextMenu.addSeparator(); - - QAction *displayFrequencies = contextMenu.addAction("Display Frequencies"); - displayFrequencies->setCheckable(true); - displayFrequencies->setChecked(!chromosome_display_haplotypes_); - - QAction *displayHaplotypes = contextMenu.addAction("Display Haplotypes"); - displayHaplotypes->setCheckable(true); - displayHaplotypes->setChecked(chromosome_display_haplotypes_); - - QActionGroup *displayGroup = new QActionGroup(this); // On Linux this provides a radio-button-group appearance - displayGroup->addAction(displayFrequencies); - displayGroup->addAction(displayHaplotypes); - - QAction *displayAllMutations = nullptr; - QAction *selectNonneutralMutations = nullptr; - - // mutation type checkmark items - { - const std::map &muttypes = community->AllMutationTypes(); - - if (muttypes.size() > 0) - { - contextMenu.addSeparator(); - - displayAllMutations = contextMenu.addAction("Display All Mutations"); - displayAllMutations->setCheckable(true); - displayAllMutations->setChecked(chromosome_display_muttypes_.size() == 0); - - // Make a sorted list of all mutation types we know – those that exist, and those that used to exist that we are displaying - std::vector all_muttypes; - - for (auto muttype_iter : muttypes) - { - MutationType *muttype = muttype_iter.second; - slim_objectid_t muttype_id = muttype->mutation_type_id_; - - all_muttypes.emplace_back(muttype_id); - } - - all_muttypes.insert(all_muttypes.end(), chromosome_display_muttypes_.begin(), chromosome_display_muttypes_.end()); - - // Avoid building a huge menu, which will hang the app - if (all_muttypes.size() <= 500) - { - std::sort(all_muttypes.begin(), all_muttypes.end()); - all_muttypes.resize(static_cast(std::distance(all_muttypes.begin(), std::unique(all_muttypes.begin(), all_muttypes.end())))); - - // Then add menu items for each of those muttypes - for (slim_objectid_t muttype_id : all_muttypes) - { - QString menuItemTitle = QString("Display m%1").arg(muttype_id); - MutationType *muttype = community->MutationTypeWithID(muttype_id); // try to look up the mutation type; can fail if it doesn't exists now - - if (muttype && (community->all_species_.size() > 1)) - menuItemTitle.append(" ").append(QString::fromStdString(muttype->species_.avatar_)); - - QAction *mutationAction = contextMenu.addAction(menuItemTitle); - - mutationAction->setData(muttype_id); - mutationAction->setCheckable(true); - - if (std::find(chromosome_display_muttypes_.begin(), chromosome_display_muttypes_.end(), muttype_id) != chromosome_display_muttypes_.end()) - mutationAction->setChecked(true); - } - } - - contextMenu.addSeparator(); - - selectNonneutralMutations = contextMenu.addAction("Select Non-Neutral MutationTypes"); - } - } - - // Run the context menu synchronously - QAction *action = contextMenu.exec(p_globalPoint); - - // Act upon the chosen action; we just do it right here instead of dealing with slots - if (action) - { - if (action == displayMutations) - chromosome_shouldDrawMutations_ = !chromosome_shouldDrawMutations_; - else if (action == displaySubstitutions) - chromosome_shouldDrawFixedSubstitutions_ = !chromosome_shouldDrawFixedSubstitutions_; - else if (action == displayGenomicElements) - chromosome_shouldDrawGenomicElements_ = !chromosome_shouldDrawGenomicElements_; - else if (action == displayRateMaps) - chromosome_shouldDrawRateMaps_ = !chromosome_shouldDrawRateMaps_; - else if (action == displayFrequencies) - chromosome_display_haplotypes_ = false; - else if (action == displayHaplotypes) - chromosome_display_haplotypes_ = true; - else - { - const std::map &muttypes = community->AllMutationTypes(); - - if (action == displayAllMutations) - chromosome_display_muttypes_.clear(); - else if (action == selectNonneutralMutations) - { - // - (IBAction)filterNonNeutral:(id)sender - chromosome_display_muttypes_.clear(); - - for (auto muttype_iter : muttypes) - { - MutationType *muttype = muttype_iter.second; - slim_objectid_t muttype_id = muttype->mutation_type_id_; - - if ((muttype->dfe_type_ != DFEType::kFixed) || (muttype->dfe_parameters_[0] != 0.0)) - chromosome_display_muttypes_.emplace_back(muttype_id); - } - } - else - { - // - (IBAction)filterMutations:(id)sender - slim_objectid_t muttype_id = action->data().toInt(); - auto present_iter = std::find(chromosome_display_muttypes_.begin(), chromosome_display_muttypes_.end(), muttype_id); - - if (present_iter == chromosome_display_muttypes_.end()) - { - // this mut-type is not being displayed, so add it to our display list - chromosome_display_muttypes_.emplace_back(muttype_id); - } - else - { - // this mut-type is being displayed, so remove it from our display list - chromosome_display_muttypes_.erase(present_iter); - } - } - } - - for (auto *widget : chromosomeZoomedWidgets) - widget->update(); - } - } -} - -void QtSLiMWindow::chromosomeActionRunMenu(void) -{ - QPoint mousePos = QCursor::pos(); - - runChromosomeContextMenuAtPoint(mousePos); - - // This is not called by Qt, for some reason (nested tracking loops?), so we call it explicitly - chromosomeActionReleased(); -} - #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) // In some versions of Qt5, such as 5.9.5, QChar::FormFeed did not yet exist #define Eidos_FormFeed 0x0C @@ -5931,7 +5737,7 @@ void QtSLiMWindow::displayGraphClicked(void) { isTransient = false; // Since the user has taken an interest in the window, clear the document's transient status - QtSLiMHaplotypeManager::CreateHaplotypePlot(this); + QtSLiMHaplotypeManager::CreateHaplotypePlot(chromosomeConfig); } else { @@ -6345,6 +6151,8 @@ QWidget *QtSLiMWindow::graphWindowWithView(QtSLiMGraphView *graphView, double wi graph_window->setAttribute(Qt::WA_DeleteOnClose, true); + graphView->updateSpeciesBadge(); + return graph_window; } @@ -6433,7 +6241,7 @@ void QtSLiMWindow::graphPopupButtonRunMenu(void) { isTransient = false; // Since the user has taken an interest in the window, clear the document's transient status - QtSLiMHaplotypeManager::CreateHaplotypePlot(this); + QtSLiMHaplotypeManager::CreateHaplotypePlot(chromosomeConfig); } else { @@ -6501,6 +6309,60 @@ void QtSLiMWindow::graphPopupButtonRunMenu(void) graphPopupButtonReleased(); } +QWidget *QtSLiMWindow::newChromosomeDisplay(void) +{ + isTransient = false; // Since the user has taken an interest in the window, clear the document's transient status + + // Create a chromosome display window for the focal species; if the window is on the "all" tab + // in a multispecies model, just return (we shouldn't be called in that case anyway). + // FIXME there is a flaw in the whole design here -- none of this updates dynamically if the + // user changes the number of chromosomes, the chromosome symbols, etc. The user will need + // to close the display window and open a new one, if that happens; not the end of the world. + // Fixing this would require a smarter controller object that rebuilds the whole content of + // the display after a recycle-and-step to reflect whatever the initialize() process built. + Species *species = focalDisplaySpecies(); + + if (!species) + return nullptr; + + // Make a new window to show the chromosome display + QWidget *display_window = new QWidget(this, Qt::Window | Qt::Tool); // the display window has us as a parent, but is still a standalone window + + display_window->setWindowTitle("Chromosome Display"); +#ifdef __APPLE__ + // set the window icon only on macOS; on Linux it changes the app icon as a side effect + display_window->setWindowIcon(QIcon()); +#endif + + // Make a new layout for chromosome views and build the display inside it + QVBoxLayout *topLayout = new QVBoxLayout; + + display_window->setLayout(topLayout); + topLayout->setContentsMargins(0, 0, 0, 0); + topLayout->setSpacing(0); + + QtSLiMChromosomeWidgetController *displayController = new QtSLiMChromosomeWidgetController(this, display_window, species); + + displayController->buildChromosomeDisplay(/* resetWindowSize */ true); + + // force geometry calculation, which is lazy + display_window->setAttribute(Qt::WA_DontShowOnScreen, true); + display_window->show(); + display_window->hide(); + display_window->setAttribute(Qt::WA_DontShowOnScreen, false); + + // Position the window nicely + display_window->move(this->frameGeometry().topLeft() + QPoint(50, 50)); + + // make window actions for all global menu items + // we do NOT need to do this, because we use Qt::Tool; Qt will use our parent window's shortcuts + //qtSLiMAppDelegate->addActionsForGlobalMenuItems(display_window); + + display_window->setAttribute(Qt::WA_DeleteOnClose, true); + + return display_window; +} + void QtSLiMWindow::changeDirectoryClicked(void) { isTransient = false; // Since the user has taken an interest in the window, clear the document's transient status diff --git a/QtSLiM/QtSLiMWindow.h b/QtSLiM/QtSLiMWindow.h index 7e3ab80c..770d8f14 100644 --- a/QtSLiM/QtSLiMWindow.h +++ b/QtSLiM/QtSLiMWindow.h @@ -54,6 +54,7 @@ class QtSLiMScriptTextEdit; class QtSLiMTextEdit; class QtSLiMDebugOutputWindow; class QtSLiMChromosomeWidget; +class QtSLiMChromosomeWidgetController; class LogFile; @@ -131,15 +132,9 @@ class QtSLiMWindow : public QMainWindow bool reloadingSubpopTableview = false; bool reloadingSpeciesBar = false; - // chromosome view configuration, kept by us because it applies to all chromosome views in multispecies models - bool chromosome_shouldDrawMutations_ = true; - bool chromosome_shouldDrawFixedSubstitutions_ = false; - bool chromosome_shouldDrawGenomicElements_ = false; - bool chromosome_shouldDrawRateMaps_ = false; + // chromosome view configuration, applied to all chromosome views in multispecies models + QtSLiMChromosomeWidgetController *chromosomeConfig = nullptr; - bool chromosome_display_haplotypes_ = false; // if false, displaying frequencies; if true, displaying haplotypes - std::vector chromosome_display_muttypes_; // if empty, display all mutation types; otherwise, display only the muttypes chosen - public: typedef enum { WF = 0, @@ -165,8 +160,6 @@ class QtSLiMWindow : public QMainWindow std::vector listedSubpopulations(void); std::vector selectedSubpopulations(void); - void chromosomeSelection(Species *species, bool *p_hasSelection, slim_position_t *p_selectionFirstBase, slim_position_t *p_selectionLastBase); - const std::vector &chromosomeDisplayMuttypes(void); inline bool invalidSimulation(void) { return invalidSimulation_; } void setInvalidSimulation(bool p_invalid); @@ -250,8 +243,8 @@ class QtSLiMWindow : public QMainWindow void playStateChanged(void); void controllerChangeCountChanged(int changeCount); - void controllerUpdatedAfterTick(void); - void controllerChromosomeSelectionChanged(void); + void controllerPartialUpdateAfterTick(void); + void controllerFullUpdateAfterTick(void); void controllerTickFinished(void); void controllerRecycled(void); @@ -264,7 +257,7 @@ public slots: void playSpeedChanged(void); void showDrawerClicked(void); - void chromosomeActionRunMenu(void); + void chromosomeDisplayClicked(void); void showConsoleClicked(void); void showBrowserClicked(void); void jumpToPopupButtonRunMenu(void); @@ -307,6 +300,8 @@ private slots: void toggleDrawerReleased(void); void chromosomeActionPressed(void); void chromosomeActionReleased(void); + void chromosomeDisplayPressed(void); + void chromosomeDisplayReleased(void); void clearDebugPressed(void); void clearDebugReleased(void); @@ -346,6 +341,7 @@ private slots: void positionNewSubsidiaryWindow(QWidget *window); QWidget *graphWindowWithView(QtSLiMGraphView *graphView, double windowWidth=300, double windowHeight=300); QtSLiMGraphView *graphViewForGraphWindow(QWidget *window); + QWidget *newChromosomeDisplay(void); // used to suppress saving of resize/position info until we are fully constructed bool donePositioning_ = false; diff --git a/QtSLiM/QtSLiMWindow.ui b/QtSLiM/QtSLiMWindow.ui index 89ba9dba..83796cca 100644 --- a/QtSLiM/QtSLiMWindow.ui +++ b/QtSLiM/QtSLiMWindow.ui @@ -609,7 +609,7 @@ QSlider::handle:horizontal:disabled { 16777215 - 30 + 20 @@ -641,7 +641,7 @@ QSlider::handle:horizontal:disabled { - 0 + 5 @@ -689,23 +689,52 @@ QSlider::handle:horizontal:disabled { - - - Qt::Vertical + + + + 0 + 0 + - - QSizePolicy::Expanding + + + 20 + 20 + - + 20 - 0 + 20 - + + Qt::NoFocus + + + <html><head/><body><p>chromosome view actions</p></body></html> + + + + :/buttons/action.png + :/buttons/action_H.png:/buttons/action.png + + + + 20 + 20 + + + + true + + + true + + - + 0 @@ -728,12 +757,12 @@ QSlider::handle:horizontal:disabled { Qt::NoFocus - <html><head/><body><p>chromosome view actions</p></body></html> + <html><head/><body><p>make a new chromosome display window</p></body></html> - :/buttons/action.png - :/buttons/action_H.png:/buttons/action.png + :/buttons/chrom_display.png + :/buttons/chrom_display_H.png:/buttons/chrom_display.png @@ -760,7 +789,7 @@ QSlider::handle:horizontal:disabled { 20 - 45 + 14 diff --git a/QtSLiM/QtSLiMWindow_glue.cpp b/QtSLiM/QtSLiMWindow_glue.cpp index 9d6101bb..81d2970a 100644 --- a/QtSLiM/QtSLiMWindow_glue.cpp +++ b/QtSLiM/QtSLiMWindow_glue.cpp @@ -42,6 +42,7 @@ void QtSLiMWindow::glueUI(void) connect(ui->toggleDrawerButton, &QPushButton::clicked, this, &QtSLiMWindow::showDrawerClicked); //connect(ui->chromosomeActionButton, &QPushButton::clicked, this, &QtSLiMWindow::chromosomeActionClicked); // this button runs when it is pressed + connect(ui->chromosomeDisplayButton, &QPushButton::clicked, this, &QtSLiMWindow::chromosomeDisplayClicked); connect(ui->clearDebugButton, &QPushButton::clicked, ui->scriptTextEdit, &QtSLiMScriptTextEdit::clearDebugPoints); connect(ui->checkScriptButton, &QPushButton::clicked, ui->scriptTextEdit, &QtSLiMTextEdit::checkScript); @@ -64,6 +65,7 @@ void QtSLiMWindow::glueUI(void) ui->recycleButton->qtslimSetBaseName("recycle"); ui->toggleDrawerButton->qtslimSetBaseName("open_type_drawer"); ui->chromosomeActionButton->qtslimSetBaseName("action"); + ui->chromosomeDisplayButton->qtslimSetBaseName("chrom_display"); ui->clearDebugButton->qtslimSetBaseName("clear_debug"); ui->checkScriptButton->qtslimSetBaseName("check"); ui->prettyprintButton->qtslimSetBaseName("prettyprint"); @@ -96,6 +98,8 @@ void QtSLiMWindow::glueUI(void) connect(ui->toggleDrawerButton, &QPushButton::released, this, &QtSLiMWindow::toggleDrawerReleased); connect(ui->chromosomeActionButton, &QPushButton::pressed, this, &QtSLiMWindow::chromosomeActionPressed); connect(ui->chromosomeActionButton, &QPushButton::released, this, &QtSLiMWindow::chromosomeActionReleased); + connect(ui->chromosomeDisplayButton, &QPushButton::pressed, this, &QtSLiMWindow::chromosomeDisplayPressed); + connect(ui->chromosomeDisplayButton, &QPushButton::released, this, &QtSLiMWindow::chromosomeDisplayReleased); connect(ui->clearDebugButton, &QPushButton::pressed, this, &QtSLiMWindow::clearDebugPressed); connect(ui->clearDebugButton, &QPushButton::released, this, &QtSLiMWindow::clearDebugReleased); connect(ui->checkScriptButton, &QPushButton::pressed, this, &QtSLiMWindow::checkScriptPressed); @@ -276,12 +280,20 @@ void QtSLiMWindow::toggleDrawerReleased(void) void QtSLiMWindow::chromosomeActionPressed(void) { ui->chromosomeActionButton->qtslimSetHighlight(true); - chromosomeActionRunMenu(); // this button runs its menu when it is pressed, so make that call here + chromosomeConfig->actionButtonRunMenu(ui->chromosomeActionButton); } void QtSLiMWindow::chromosomeActionReleased(void) { ui->chromosomeActionButton->qtslimSetHighlight(false); } +void QtSLiMWindow::chromosomeDisplayPressed(void) +{ + ui->chromosomeDisplayButton->qtslimSetHighlight(true); +} +void QtSLiMWindow::chromosomeDisplayReleased(void) +{ + ui->chromosomeDisplayButton->qtslimSetHighlight(false); +} void QtSLiMWindow::clearDebugPressed(void) { ui->clearDebugButton->qtslimSetHighlight(true); diff --git a/VERSIONS b/VERSIONS index 91d87727..e7184a4a 100644 --- a/VERSIONS +++ b/VERSIONS @@ -63,6 +63,9 @@ development head (in the master branch): policy change: the old "haplosome type" (A/X/Y) in the genome metadata is now a chromosome type (different) in the haplosome metadata policy change: similarly, haplosome type A/X/Y in SLiM output (e.g., outputFull()) is now a chromosome type (different) policy change: initialization order is a bit stricter; initializeSex() with "X" or "Y" must come earlier, before calls that would define an implicit "A" chromosome + add multichromosome display to SLiMgui + add a "chromosome display" window where you can view all the chromosomes simultaneously + policy change: the 1D SFS graph and haplotype plot no longer depend upon the current chromosome range selection; that was weird, and doesn't work well in multichrom version 4.3 (Eidos version 3.3): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 299cd1ce..0c6aec2b 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -65,7 +65,6 @@ Chromosome::Chromosome(Species &p_species, ChromosomeType p_type, int64_t p_id, name_(), index_(p_index), type_(p_type), - preferred_mutrun_count_(p_preferred_mutcount), exp_neg_overall_mutation_rate_H_(0.0), exp_neg_overall_mutation_rate_M_(0.0), exp_neg_overall_mutation_rate_F_(0.0), exp_neg_overall_recombination_rate_H_(0.0), exp_neg_overall_recombination_rate_M_(0.0), exp_neg_overall_recombination_rate_F_(0.0), @@ -76,6 +75,7 @@ Chromosome::Chromosome(Species &p_species, ChromosomeType p_type, int64_t p_id, probability_both_0_F_(0.0), probability_both_0_OR_mut_0_break_non0_F_(0.0), probability_both_0_OR_mut_0_break_non0_OR_mut_non0_break_0_F_(0.0), #endif + preferred_mutrun_count_(p_preferred_mutcount), x_experiments_enabled_(false), community_(p_species.community_), species_(p_species), diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index c4821861..a4344e89 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -304,7 +304,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeChromosome(const std::st } // Set up the new chromosome object - Chromosome *chromosome = new Chromosome(*this, chromosome_type, id, symbol, /* p_index */ num_chromosome_inits_, (int)mutrun_count); + Chromosome *chromosome = new Chromosome(*this, chromosome_type, id, symbol, /* p_index */ (uint8_t)num_chromosome_inits_, (int)mutrun_count); chromosome->SetName(name); chromosome->first_position_ = start;