<commit_before>/*
 * Copyright (C) 2016  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "pathbar.h"
#include "pathbar_p.h"
#include <QToolButton>
#include <QScrollArea>
#include <QScrollBar>
#include <QHBoxLayout>
#include <QResizeEvent>
#include <QContextMenuEvent>
#include <QMenu>
#include <QClipboard>
#include <QApplication>
#include <QTimer>
#include <QDebug>
#include "pathedit.h"

namespace Fm {

PathBar::PathBar(QWidget* parent):
    QWidget(parent),
    tempPathEdit_(nullptr),
    toggledBtn_(nullptr) {

    QHBoxLayout* topLayout = new QHBoxLayout(this);
    topLayout->setContentsMargins(0, 0, 0, 0);
    topLayout->setSpacing(0);
    bool rtl(layoutDirection() == Qt::RightToLeft);

    // the arrow button used to scroll to start of the path
    scrollToStart_ = new QToolButton(this);
    scrollToStart_->setArrowType(rtl ? Qt::RightArrow : Qt::LeftArrow);
    scrollToStart_->setAutoRepeat(true);
    scrollToStart_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
    connect(scrollToStart_, &QToolButton::clicked, this, &PathBar::onScrollButtonClicked);
    topLayout->addWidget(scrollToStart_);

    // there might be too many buttons when the path is long, so make it scrollable.
    scrollArea_ = new QScrollArea(this);
    scrollArea_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    scrollArea_->setFrameShape(QFrame::NoFrame);
    scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
    scrollArea_->verticalScrollBar()->setDisabled(true);
    connect(scrollArea_->horizontalScrollBar(), &QAbstractSlider::valueChanged, this, &PathBar::setArrowEnabledState);
    topLayout->addWidget(scrollArea_, 1); // stretch factor=1, make it expandable

    // the arrow button used to scroll to end of the path
    scrollToEnd_ = new QToolButton(this);
    scrollToEnd_->setArrowType(rtl ? Qt::LeftArrow : Qt::RightArrow);
    scrollToEnd_->setAutoRepeat(true);
    scrollToEnd_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
    connect(scrollToEnd_, &QToolButton::clicked, this, &PathBar::onScrollButtonClicked);
    topLayout->addWidget(scrollToEnd_);

    // container widget of the path buttons
    buttonsWidget_ = new QWidget(this);
    buttonsWidget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);

    buttonsLayout_ = new QHBoxLayout(buttonsWidget_);
    buttonsLayout_->setContentsMargins(0, 0, 0, 0);
    buttonsLayout_->setSpacing(0);
    buttonsLayout_->setSizeConstraint(QLayout::SetFixedSize); // required when added to scroll area according to QScrollArea doc.
    scrollArea_->setWidget(buttonsWidget_); // make the buttons widget scrollable if the path is too long
}

void PathBar::resizeEvent(QResizeEvent* event) {
    QWidget::resizeEvent(event);
    updateScrollButtonVisibility();
    QTimer::singleShot(0, this, SLOT(ensureToggledVisible()));
}

void PathBar::wheelEvent(QWheelEvent* event) {
    QWidget::wheelEvent(event);
    QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction;
    int vDelta = event->angleDelta().y();
    if(vDelta > 0) {
        if(scrollToStart_->isEnabled()) {
            action = QAbstractSlider::SliderSingleStepSub;
        }
    }
    else if(vDelta < 0) {
        if(scrollToEnd_->isEnabled()) {
            action = QAbstractSlider::SliderSingleStepAdd;
        }
    }
    scrollArea_->horizontalScrollBar()->triggerAction(action);
}

void PathBar::mousePressEvent(QMouseEvent* event) {
    QWidget::mousePressEvent(event);
    if(event->button() == Qt::LeftButton) {
        openEditor();
    }
    else if(event->button() == Qt::MiddleButton) {
        PathButton* btn = qobject_cast<PathButton*>(childAt(event->x(), event->y()));
        if(btn != nullptr) {
            scrollArea_->ensureWidgetVisible(btn,
                                             1); // a harmless compensation for a miscalculation in Qt
            Q_EMIT middleClickChdir(pathForButton(btn));
        }
    }
}

void PathBar::contextMenuEvent(QContextMenuEvent* event) {
    QMenu* menu = new QMenu(this);
    connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater);

    QAction* action = menu->addAction(tr("&Edit Path"));
    connect(action, &QAction::triggered, this, &PathBar::openEditor);

    action = menu->addAction(tr("&Copy Path"));
    connect(action, &QAction::triggered, this, &PathBar::copyPath);

    menu->popup(mapToGlobal(event->pos()));
}

void PathBar::updateScrollButtonVisibility() {
    // Wait for the horizontal scrollbar to be completely shaped.
    // Without this, the enabled state of arrow buttons might be
    // wrong when the pathbar is created for the first time.
    QTimer::singleShot(0, this, SLOT(setScrollButtonVisibility()));
}

void PathBar::setScrollButtonVisibility() {
    bool showScrollers;
    if(tempPathEdit_ != nullptr) {
        showScrollers = false;
    }
    else {
        showScrollers = (buttonsLayout_->sizeHint().width() > width());
    }
    scrollToStart_->setVisible(showScrollers);
    scrollToEnd_->setVisible(showScrollers);
    if(showScrollers) {
        QScrollBar* sb = scrollArea_->horizontalScrollBar();
        int value = sb->value();
        scrollToStart_->setEnabled(value != sb->minimum());
        scrollToEnd_->setEnabled(value != sb->maximum());
    }
}

Fm::FilePath PathBar::pathForButton(PathButton* btn) {
    std::string fullPath;
    int buttonCount = buttonsLayout_->count() - 1; // the last item is a spacer
    for(int i = 0; i < buttonCount; ++i) {
        if(!fullPath.empty() && fullPath.back() != '/') {
            fullPath += '/';
        }
        PathButton* elem = static_cast<PathButton*>(buttonsLayout_->itemAt(i)->widget());
        fullPath += elem->name();
        if(elem == btn)
            break;
    }
    return Fm::FilePath::fromPathStr(fullPath.c_str());
}

void PathBar::onButtonToggled(bool checked) {
    if(checked) {
        PathButton* btn = static_cast<PathButton*>(sender());
        toggledBtn_ = btn;
        currentPath_ = pathForButton(btn);
        Q_EMIT chdir(currentPath_);

        // since scrolling to the toggled buton will happen correctly only when the
        // layout is updated and because the update is disabled on creating buttons
        // in setPath(), the update status can be used as a sign to know when to wait
        if(updatesEnabled()) {
            scrollArea_->ensureWidgetVisible(btn, 1);
        }
        else {
            QTimer::singleShot(0, this, SLOT(ensureToggledVisible()));
        }
    }
}

void PathBar::ensureToggledVisible() {
    if(toggledBtn_ != nullptr && tempPathEdit_ == nullptr) {
        scrollArea_->ensureWidgetVisible(toggledBtn_, 1);
    }
}

void PathBar::onScrollButtonClicked() {
    QToolButton* btn = static_cast<QToolButton*>(sender());
    QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction;
    if(btn == scrollToEnd_) {
        action = QAbstractSlider::SliderSingleStepAdd;
    }
    else if(btn == scrollToStart_) {
        action = QAbstractSlider::SliderSingleStepSub;
    }
    scrollArea_->horizontalScrollBar()->triggerAction(action);
}

void PathBar::setPath(Fm::FilePath path) {
    if(currentPath_ == path) { // same path, do nothing
        return;
    }

    auto oldPath = std::move(currentPath_);
    currentPath_ = std::move(path);
    // check if we already have a button for this path
    int buttonCount = buttonsLayout_->count() - 1; // the last item is a spacer
    if(oldPath && currentPath_.isPrefixOf(oldPath)) {
        for(int i = buttonCount - 1; i >= 0; --i) {
            auto btn = static_cast<PathButton*>(buttonsLayout_->itemAt(i)->widget());
            if(pathForButton(btn) == currentPath_) {
                btn->setChecked(true); // toggle the button
                /* we don't need to emit chdir signal here since later
                 * toggled signal will be triggered on the button, which
                 * in turns emit chdir. */
                return;
            }
        }
    }

    /* FIXME: if the new path is the subdir of our full path, actually
     *        we can append several new buttons rather than re-create
     *        all of the buttons. This can reduce flickers. */

    setUpdatesEnabled(false);
    toggledBtn_ = nullptr;
    // we do not have the path in the buttons list
    // destroy existing path element buttons and the spacer
    QLayoutItem* item;
    while((item = buttonsLayout_->takeAt(0)) != nullptr) {
        delete item->widget();
        delete item;
    }

    // create new buttons for the new path
    auto btnPath = currentPath_;
    while(btnPath) {
        Fm::CStrPtr name;
        Fm::CStrPtr displayName;
        auto parent = btnPath.parent();
        // FIXME: some buggy uri types, such as menu://, fail to return NULL when there is no parent path.
        // Instead, the path itself is returned. So we check if the parent path is the same as current path.
        auto isRoot = !parent.isValid() || parent == btnPath;
        if(isRoot) {
            displayName = btnPath.displayName();
            name = btnPath.toString();
        }
        else {
            displayName = btnPath.baseName();
            // "name" is used for making the path from its components in PathBar::pathForButton().
            // So, it should be extracted from g_file_get_parse_name().
            // (In places like trash:///, FilePath::baseName() cannot be used to make a full path.)
            name = CStrPtr{g_path_get_basename(btnPath.displayName().get())};
        }
        auto btn = new PathButton(name.get(), QString::fromUtf8(displayName.get()), isRoot, buttonsWidget_);
        btn->show();
        connect(btn, &QAbstractButton::toggled, this, &PathBar::onButtonToggled);
        buttonsLayout_->insertWidget(0, btn);
        if(isRoot) { // this is the root element of the path
            break;
        }
        btnPath = parent;
    }
    buttonsLayout_->addStretch(1); // add a spacer at the tail of the buttons

    // we don't want to scroll vertically. make the scroll area fit the height of the buttons
    // FIXME: this is a little bit hackish :-(
    scrollArea_->setFixedHeight(buttonsLayout_->sizeHint().height());
    updateScrollButtonVisibility();

    // to guarantee that the button will be scrolled to correctly,
    // it should be toggled only after the layout update starts above
    buttonCount = buttonsLayout_->count() - 1;
    if(buttonCount > 0) {
        PathButton* lastBtn = static_cast<PathButton*>(buttonsLayout_->itemAt(buttonCount - 1)->widget());
        // we don't have to emit the chdir signal since the "onButtonToggled()" slot will be triggered by this.
        lastBtn->setChecked(true);
    }

    setUpdatesEnabled(true);
}

void PathBar::openEditor() {
    if(tempPathEdit_ == nullptr) {
        tempPathEdit_ = new PathEdit(this);
        delete layout()->replaceWidget(scrollArea_, tempPathEdit_, Qt::FindDirectChildrenOnly);
        scrollArea_->hide();
        scrollToStart_->setVisible(false);
        scrollToEnd_->setVisible(false);
        tempPathEdit_->setText(QString::fromUtf8(currentPath_.toString().get()));

        connect(tempPathEdit_, &PathEdit::returnPressed, this, &PathBar::onReturnPressed);
        connect(tempPathEdit_, &PathEdit::editingFinished, this, &PathBar::closeEditor);
    }
    tempPathEdit_->selectAll();
    QApplication::clipboard()->setText(tempPathEdit_->text(), QClipboard::Selection);
    QTimer::singleShot(0, tempPathEdit_, SLOT(setFocus()));
}

void PathBar::closeEditor() {
    if(tempPathEdit_ == nullptr) {
        return;
    }
    // If a menu has popped up synchronously (with QMenu::exec), the path buttons may be drawn
    // but the path-edit may not disappear until the menu is closed. So, we hide it here.
    // However, since hiding the path-edit makes it lose focus and emit editingFinished(),
    // we should first disconnect from it to avoid recursive calling of the current function.
    tempPathEdit_->disconnect();
    tempPathEdit_->setVisible(false);
    delete layout()->replaceWidget(tempPathEdit_, scrollArea_, Qt::FindDirectChildrenOnly);
    scrollArea_->show();
    if(buttonsLayout_->sizeHint().width() > width()) {
        scrollToStart_->setVisible(true);
        scrollToEnd_->setVisible(true);
    }

    tempPathEdit_->deleteLater();
    tempPathEdit_ = nullptr;
    updateScrollButtonVisibility();

    Q_EMIT editingFinished();
}

void PathBar::copyPath() {
    QApplication::clipboard()->setText(QString::fromUtf8(currentPath_.toString().get()));
}

void PathBar::onReturnPressed() {
    QByteArray pathStr = tempPathEdit_->text().toLocal8Bit();
    setPath(Fm::FilePath::fromPathStr(pathStr.constData()));
}

void PathBar::setArrowEnabledState(int value) {
    if(buttonsLayout_->sizeHint().width() > width()) {
        QScrollBar* sb = scrollArea_->horizontalScrollBar();
        scrollToStart_->setEnabled(value != sb->minimum());
        scrollToEnd_->setEnabled(value != sb->maximum());
    }
}

} // namespace Fm
<commit_msg>Guarantee horizontal alignment for pathbar scroll buttons (#570)<commit_after>/*
 * Copyright (C) 2016  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "pathbar.h"
#include "pathbar_p.h"
#include <QToolButton>
#include <QScrollArea>
#include <QScrollBar>
#include <QHBoxLayout>
#include <QResizeEvent>
#include <QContextMenuEvent>
#include <QMenu>
#include <QClipboard>
#include <QApplication>
#include <QTimer>
#include <QDebug>
#include "pathedit.h"

namespace Fm {

PathBar::PathBar(QWidget* parent):
    QWidget(parent),
    tempPathEdit_(nullptr),
    toggledBtn_(nullptr) {

    QHBoxLayout* topLayout = new QHBoxLayout(this);
    topLayout->setContentsMargins(0, 0, 0, 0);
    topLayout->setSpacing(0);
    bool rtl(layoutDirection() == Qt::RightToLeft);

    // the arrow button used to scroll to start of the path
    scrollToStart_ = new QToolButton(this);
    scrollToStart_->setArrowType(rtl ? Qt::RightArrow : Qt::LeftArrow);
    scrollToStart_->setAutoRepeat(true);
    scrollToStart_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
    connect(scrollToStart_, &QToolButton::clicked, this, &PathBar::onScrollButtonClicked);
    topLayout->addWidget(scrollToStart_);

    // there might be too many buttons when the path is long, so make it scrollable.
    scrollArea_ = new QScrollArea(this);
    scrollArea_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    scrollArea_->setFrameShape(QFrame::NoFrame);
    scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
    scrollArea_->verticalScrollBar()->setDisabled(true);
    connect(scrollArea_->horizontalScrollBar(), &QAbstractSlider::valueChanged, this, &PathBar::setArrowEnabledState);
    topLayout->addWidget(scrollArea_, 1); // stretch factor=1, make it expandable

    // the arrow button used to scroll to end of the path
    scrollToEnd_ = new QToolButton(this);
    scrollToEnd_->setArrowType(rtl ? Qt::LeftArrow : Qt::RightArrow);
    scrollToEnd_->setAutoRepeat(true);
    scrollToEnd_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
    connect(scrollToEnd_, &QToolButton::clicked, this, &PathBar::onScrollButtonClicked);
    topLayout->addWidget(scrollToEnd_);

    // container widget of the path buttons
    buttonsWidget_ = new QWidget(this);
    buttonsWidget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);

    buttonsLayout_ = new QHBoxLayout(buttonsWidget_);
    buttonsLayout_->setContentsMargins(0, 0, 0, 0);
    buttonsLayout_->setSpacing(0);
    buttonsLayout_->setSizeConstraint(QLayout::SetFixedSize); // required when added to scroll area according to QScrollArea doc.
    scrollArea_->setWidget(buttonsWidget_); // make the buttons widget scrollable if the path is too long
}

void PathBar::resizeEvent(QResizeEvent* event) {
    QWidget::resizeEvent(event);
    if(event->oldSize().width() != event->size().width()) {
        updateScrollButtonVisibility();
        QTimer::singleShot(0, this, SLOT(ensureToggledVisible()));
    }
}

void PathBar::wheelEvent(QWheelEvent* event) {
    QWidget::wheelEvent(event);
    QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction;
    int vDelta = event->angleDelta().y();
    if(vDelta > 0) {
        if(scrollToStart_->isEnabled()) {
            action = QAbstractSlider::SliderSingleStepSub;
        }
    }
    else if(vDelta < 0) {
        if(scrollToEnd_->isEnabled()) {
            action = QAbstractSlider::SliderSingleStepAdd;
        }
    }
    scrollArea_->horizontalScrollBar()->triggerAction(action);
}

void PathBar::mousePressEvent(QMouseEvent* event) {
    QWidget::mousePressEvent(event);
    if(event->button() == Qt::LeftButton) {
        openEditor();
    }
    else if(event->button() == Qt::MiddleButton) {
        PathButton* btn = qobject_cast<PathButton*>(childAt(event->x(), event->y()));
        if(btn != nullptr) {
            scrollArea_->ensureWidgetVisible(btn,
                                             1); // a harmless compensation for a miscalculation in Qt
            Q_EMIT middleClickChdir(pathForButton(btn));
        }
    }
}

void PathBar::contextMenuEvent(QContextMenuEvent* event) {
    QMenu* menu = new QMenu(this);
    connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater);

    QAction* action = menu->addAction(tr("&Edit Path"));
    connect(action, &QAction::triggered, this, &PathBar::openEditor);

    action = menu->addAction(tr("&Copy Path"));
    connect(action, &QAction::triggered, this, &PathBar::copyPath);

    menu->popup(mapToGlobal(event->pos()));
}

void PathBar::updateScrollButtonVisibility() {
    // Wait for the horizontal scrollbar to be completely shaped.
    // Without this, the enabled state of arrow buttons might be
    // wrong when the pathbar is created for the first time.
    QTimer::singleShot(0, this, SLOT(setScrollButtonVisibility()));
}

void PathBar::setScrollButtonVisibility() {
    bool showScrollers;
    if(tempPathEdit_ != nullptr) {
        showScrollers = false;
    }
    else {
        showScrollers = (buttonsLayout_->sizeHint().width() > width());
    }
    scrollToStart_->setVisible(showScrollers);
    scrollToEnd_->setVisible(showScrollers);
    if(showScrollers) {
        QScrollBar* sb = scrollArea_->horizontalScrollBar();
        int value = sb->value();
        scrollToStart_->setEnabled(value != sb->minimum());
        scrollToEnd_->setEnabled(value != sb->maximum());
        // align scroll buttons horizontally
        scrollToStart_->setMaximumHeight(qMax(buttonsWidget_->height(), scrollToStart_->minimumSizeHint().height()));
        scrollToEnd_->setMaximumHeight(qMax(buttonsWidget_->height(), scrollToEnd_->minimumSizeHint().height()));
    }
}

Fm::FilePath PathBar::pathForButton(PathButton* btn) {
    std::string fullPath;
    int buttonCount = buttonsLayout_->count() - 1; // the last item is a spacer
    for(int i = 0; i < buttonCount; ++i) {
        if(!fullPath.empty() && fullPath.back() != '/') {
            fullPath += '/';
        }
        PathButton* elem = static_cast<PathButton*>(buttonsLayout_->itemAt(i)->widget());
        fullPath += elem->name();
        if(elem == btn)
            break;
    }
    return Fm::FilePath::fromPathStr(fullPath.c_str());
}

void PathBar::onButtonToggled(bool checked) {
    if(checked) {
        PathButton* btn = static_cast<PathButton*>(sender());
        toggledBtn_ = btn;
        currentPath_ = pathForButton(btn);
        Q_EMIT chdir(currentPath_);

        // since scrolling to the toggled buton will happen correctly only when the
        // layout is updated and because the update is disabled on creating buttons
        // in setPath(), the update status can be used as a sign to know when to wait
        if(updatesEnabled()) {
            scrollArea_->ensureWidgetVisible(btn, 1);
        }
        else {
            QTimer::singleShot(0, this, SLOT(ensureToggledVisible()));
        }
    }
}

void PathBar::ensureToggledVisible() {
    if(toggledBtn_ != nullptr && tempPathEdit_ == nullptr) {
        scrollArea_->ensureWidgetVisible(toggledBtn_, 1);
    }
}

void PathBar::onScrollButtonClicked() {
    QToolButton* btn = static_cast<QToolButton*>(sender());
    QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction;
    if(btn == scrollToEnd_) {
        action = QAbstractSlider::SliderSingleStepAdd;
    }
    else if(btn == scrollToStart_) {
        action = QAbstractSlider::SliderSingleStepSub;
    }
    scrollArea_->horizontalScrollBar()->triggerAction(action);
}

void PathBar::setPath(Fm::FilePath path) {
    if(currentPath_ == path) { // same path, do nothing
        return;
    }

    auto oldPath = std::move(currentPath_);
    currentPath_ = std::move(path);
    // check if we already have a button for this path
    int buttonCount = buttonsLayout_->count() - 1; // the last item is a spacer
    if(oldPath && currentPath_.isPrefixOf(oldPath)) {
        for(int i = buttonCount - 1; i >= 0; --i) {
            auto btn = static_cast<PathButton*>(buttonsLayout_->itemAt(i)->widget());
            if(pathForButton(btn) == currentPath_) {
                btn->setChecked(true); // toggle the button
                /* we don't need to emit chdir signal here since later
                 * toggled signal will be triggered on the button, which
                 * in turns emit chdir. */
                return;
            }
        }
    }

    /* FIXME: if the new path is the subdir of our full path, actually
     *        we can append several new buttons rather than re-create
     *        all of the buttons. This can reduce flickers. */

    setUpdatesEnabled(false);
    toggledBtn_ = nullptr;
    // we do not have the path in the buttons list
    // destroy existing path element buttons and the spacer
    QLayoutItem* item;
    while((item = buttonsLayout_->takeAt(0)) != nullptr) {
        delete item->widget();
        delete item;
    }

    // create new buttons for the new path
    auto btnPath = currentPath_;
    while(btnPath) {
        Fm::CStrPtr name;
        Fm::CStrPtr displayName;
        auto parent = btnPath.parent();
        // FIXME: some buggy uri types, such as menu://, fail to return NULL when there is no parent path.
        // Instead, the path itself is returned. So we check if the parent path is the same as current path.
        auto isRoot = !parent.isValid() || parent == btnPath;
        if(isRoot) {
            displayName = btnPath.displayName();
            name = btnPath.toString();
        }
        else {
            displayName = btnPath.baseName();
            // "name" is used for making the path from its components in PathBar::pathForButton().
            // So, it should be extracted from g_file_get_parse_name().
            // (In places like trash:///, FilePath::baseName() cannot be used to make a full path.)
            name = CStrPtr{g_path_get_basename(btnPath.displayName().get())};
        }
        auto btn = new PathButton(name.get(), QString::fromUtf8(displayName.get()), isRoot, buttonsWidget_);
        btn->show();
        connect(btn, &QAbstractButton::toggled, this, &PathBar::onButtonToggled);
        buttonsLayout_->insertWidget(0, btn);
        if(isRoot) { // this is the root element of the path
            break;
        }
        btnPath = parent;
    }
    buttonsLayout_->addStretch(1); // add a spacer at the tail of the buttons

    // we don't want to scroll vertically. make the scroll area fit the height of the buttons
    // FIXME: this is a little bit hackish :-(
    scrollArea_->setFixedHeight(buttonsLayout_->sizeHint().height());
    updateScrollButtonVisibility();

    // to guarantee that the button will be scrolled to correctly,
    // it should be toggled only after the layout update starts above
    buttonCount = buttonsLayout_->count() - 1;
    if(buttonCount > 0) {
        PathButton* lastBtn = static_cast<PathButton*>(buttonsLayout_->itemAt(buttonCount - 1)->widget());
        // we don't have to emit the chdir signal since the "onButtonToggled()" slot will be triggered by this.
        lastBtn->setChecked(true);
    }

    setUpdatesEnabled(true);
}

void PathBar::openEditor() {
    if(tempPathEdit_ == nullptr) {
        tempPathEdit_ = new PathEdit(this);
        delete layout()->replaceWidget(scrollArea_, tempPathEdit_, Qt::FindDirectChildrenOnly);
        scrollArea_->hide();
        scrollToStart_->setVisible(false);
        scrollToEnd_->setVisible(false);
        tempPathEdit_->setText(QString::fromUtf8(currentPath_.toString().get()));

        connect(tempPathEdit_, &PathEdit::returnPressed, this, &PathBar::onReturnPressed);
        connect(tempPathEdit_, &PathEdit::editingFinished, this, &PathBar::closeEditor);
    }
    tempPathEdit_->selectAll();
    QApplication::clipboard()->setText(tempPathEdit_->text(), QClipboard::Selection);
    QTimer::singleShot(0, tempPathEdit_, SLOT(setFocus()));
}

void PathBar::closeEditor() {
    if(tempPathEdit_ == nullptr) {
        return;
    }
    // If a menu has popped up synchronously (with QMenu::exec), the path buttons may be drawn
    // but the path-edit may not disappear until the menu is closed. So, we hide it here.
    // However, since hiding the path-edit makes it lose focus and emit editingFinished(),
    // we should first disconnect from it to avoid recursive calling of the current function.
    tempPathEdit_->disconnect();
    tempPathEdit_->setVisible(false);
    delete layout()->replaceWidget(tempPathEdit_, scrollArea_, Qt::FindDirectChildrenOnly);
    scrollArea_->show();
    if(buttonsLayout_->sizeHint().width() > width()) {
        scrollToStart_->setVisible(true);
        scrollToEnd_->setVisible(true);
    }

    tempPathEdit_->deleteLater();
    tempPathEdit_ = nullptr;
    updateScrollButtonVisibility();

    Q_EMIT editingFinished();
}

void PathBar::copyPath() {
    QApplication::clipboard()->setText(QString::fromUtf8(currentPath_.toString().get()));
}

void PathBar::onReturnPressed() {
    QByteArray pathStr = tempPathEdit_->text().toLocal8Bit();
    setPath(Fm::FilePath::fromPathStr(pathStr.constData()));
}

void PathBar::setArrowEnabledState(int value) {
    if(buttonsLayout_->sizeHint().width() > width()) {
        QScrollBar* sb = scrollArea_->horizontalScrollBar();
        scrollToStart_->setEnabled(value != sb->minimum());
        scrollToEnd_->setEnabled(value != sb->maximum());
    }
}

} // namespace Fm
<|endoftext|>


<commit_before>#pragma once

// Mantella
#include <mantella_bits/optimisationAlgorithm/trajectoryBasedAlgorithm.hpp>
#include <mantella_bits/helper/random.hpp>

namespace mant {
  template <class DistanceFunction>
  class HookeJeevesAlgorithm : public TrajectoryBasedAlgorithm<double, DistanceFunction> {
    public:
      explicit HookeJeevesAlgorithm(
          const std::shared_ptr<OptimisationProblem<double>> optimisationProblem) noexcept;

      HookeJeevesAlgorithm(const HookeJeevesAlgorithm&) = delete;
      HookeJeevesAlgorithm& operator=(const HookeJeevesAlgorithm&) = delete;

      void setInitialStepSize(
          const arma::Col<double>& initialStepSize);

      void setStepSizeDecrease(
          const arma::Col<double>& stepSizeDecrease);

      std::string to_string() const noexcept override;

    protected:
      arma::Col<double> initialStepSize_;
      arma::Col<double> stepSizeDecrease_;

      void optimiseImplementation() noexcept override;
  };

  template <class DistanceFunction>
  HookeJeevesAlgorithm<DistanceFunction>::HookeJeevesAlgorithm(
      const std::shared_ptr<OptimisationProblem<double>> optimisationProblem) noexcept
    : TrajectoryBasedAlgorithm<double, DistanceFunction>(optimisationProblem) {
    setInitialStepSize(this->optimisationProblem_->getUpperBounds() - this->optimisationProblem_->getLowerBounds());
    setStepSizeDecrease(arma::ones<arma::Col<double>>(optimisationProblem->getNumberOfDimensions()) * 0.5);
  }

  template <class DistanceFunction>
  void HookeJeevesAlgorithm<DistanceFunction>::optimiseImplementation() noexcept {
    ++this->numberOfIterations_;

    this->bestParameter_ = this->initialParameter_;
    this->bestSoftConstraintsValue_ = this->optimisationProblem_->getSoftConstraintsValue(this->initialParameter_);
    this->bestObjectiveValue_ = this->optimisationProblem_->getObjectiveValue(this->initialParameter_);

    bool reduceStepSize = false;
    arma::Col<double> stepSize = initialStepSize_;

    while(!this->isFinished() && !this->isTerminated()) {
      if (reduceStepSize) {
        stepSize %= stepSizeDecrease_;
      }

      reduceStepSize = true;

      arma::Col<double> candidateParameter = this->bestParameter_;
      for (std::size_t n = 0; n < this->optimisationProblem_->getNumberOfDimensions(); ++n) {
        candidateParameter.at(n) += stepSize.at(n);

        if(this->optimisationProblem_->getUpperBounds().at(n) < candidateParameter.at(n)) {
          candidateParameter.at(n) = this->optimisationProblem_->getUpperBounds().at(n);
        }

        ++this->numberOfIterations_;
        double candidateSoftConstraintsValue = this->optimisationProblem_->getSoftConstraintsValue(candidateParameter);
        double candidateObjectiveValue = this->optimisationProblem_->getObjectiveValue(candidateParameter);

        if(candidateSoftConstraintsValue < this->bestSoftConstraintsValue_ || (candidateSoftConstraintsValue == this->bestSoftConstraintsValue_ && candidateObjectiveValue < this->bestObjectiveValue_)) {
          reduceStepSize = false;

          this->bestParameter_ = candidateParameter;
          this->bestSoftConstraintsValue_ = candidateSoftConstraintsValue;
          this->bestObjectiveValue_ = candidateObjectiveValue;
        }

        if (this->isFinished() || this->isTerminated()) {
          break;
        }

        candidateParameter.at(n) = this->bestParameter_.at(n) - stepSize.at(n);

        if(this->optimisationProblem_->getLowerBounds().at(n) > candidateParameter.at(n)) {
          candidateParameter.at(n) = this->optimisationProblem_->getLowerBounds().at(n);
        }

        ++this->numberOfIterations_;
        candidateSoftConstraintsValue = this->optimisationProblem_->getSoftConstraintsValue(candidateParameter);
        candidateObjectiveValue = this->optimisationProblem_->getObjectiveValue(candidateParameter);

        if(candidateSoftConstraintsValue < this->bestSoftConstraintsValue_ || (candidateSoftConstraintsValue == this->bestSoftConstraintsValue_ && candidateObjectiveValue < this->bestObjectiveValue_)) {
          reduceStepSize = false;

          this->bestParameter_ = candidateParameter;
          this->bestSoftConstraintsValue_ = candidateSoftConstraintsValue;
          this->bestObjectiveValue_ = candidateObjectiveValue;
        }

        if (this->isFinished() || this->isTerminated()) {
          break;
        }

        candidateParameter.at(n) = this->bestParameter_.at(n);
      }
    }
  }

  template <class DistanceFunction>
  void HookeJeevesAlgorithm<DistanceFunction>::setInitialStepSize(
      const arma::Col<double>& initialStepSize) {
    if(initialStepSize.n_rows != this->optimisationProblem_->getNumberOfDimensions()) {
      throw std::logic_error("The number of dimensions of the initial step size (" + std::to_string(initialStepSize.n_elem) + ") must match the number of dimensions of the optimisation problem (" + std::to_string(this->optimisationProblem_->getNumberOfDimensions()) + ").");
    } else if (arma::any(initialStepSize <= 0)) {
      throw std::logic_error("The initial step size must be strict greater than 0.");
    }

    initialStepSize_ = initialStepSize;
  }

  template <class DistanceFunction>
  void HookeJeevesAlgorithm<DistanceFunction>::setStepSizeDecrease(
      const arma::Col<double>& stepSizeDecrease) {
    if(stepSizeDecrease.n_rows != this->optimisationProblem_->getNumberOfDimensions()) {
      throw std::logic_error("The number of dimensions of the step size decrease (" + std::to_string(stepSizeDecrease.n_elem) + ") must match the number of dimensions of the optimisation problem (" + std::to_string(this->optimisationProblem_->getNumberOfDimensions()) + ").");
    } else if(arma::any(stepSizeDecrease <= 0) || arma::any(stepSizeDecrease >= 1)) {
      throw std::logic_error("The step size decrease must be strict greater than 0 and strict lower than 1.");
    }

    stepSizeDecrease_ = stepSizeDecrease;
  }

  template <class DistanceFunction>
  std::string HookeJeevesAlgorithm<DistanceFunction>::to_string() const noexcept {
    return "HookeJeevesAlgorithm";
  }
}
<commit_msg>devel: Readded another todo entry<commit_after>#pragma once

// Mantella
#include <mantella_bits/optimisationAlgorithm/trajectoryBasedAlgorithm.hpp>
#include <mantella_bits/helper/random.hpp>

// TODO Add restarting
namespace mant {
  template <class DistanceFunction>
  class HookeJeevesAlgorithm : public TrajectoryBasedAlgorithm<double, DistanceFunction> {
    public:
      explicit HookeJeevesAlgorithm(
          const std::shared_ptr<OptimisationProblem<double>> optimisationProblem) noexcept;

      HookeJeevesAlgorithm(const HookeJeevesAlgorithm&) = delete;
      HookeJeevesAlgorithm& operator=(const HookeJeevesAlgorithm&) = delete;

      void setInitialStepSize(
          const arma::Col<double>& initialStepSize);

      void setStepSizeDecrease(
          const arma::Col<double>& stepSizeDecrease);

      std::string to_string() const noexcept override;

    protected:
      arma::Col<double> initialStepSize_;
      arma::Col<double> stepSizeDecrease_;

      void optimiseImplementation() noexcept override;
  };

  template <class DistanceFunction>
  HookeJeevesAlgorithm<DistanceFunction>::HookeJeevesAlgorithm(
      const std::shared_ptr<OptimisationProblem<double>> optimisationProblem) noexcept
    : TrajectoryBasedAlgorithm<double, DistanceFunction>(optimisationProblem) {
    setInitialStepSize(this->optimisationProblem_->getUpperBounds() - this->optimisationProblem_->getLowerBounds());
    setStepSizeDecrease(arma::ones<arma::Col<double>>(optimisationProblem->getNumberOfDimensions()) * 0.5);
  }

  template <class DistanceFunction>
  void HookeJeevesAlgorithm<DistanceFunction>::optimiseImplementation() noexcept {
    ++this->numberOfIterations_;

    this->bestParameter_ = this->initialParameter_;
    this->bestSoftConstraintsValue_ = this->optimisationProblem_->getSoftConstraintsValue(this->initialParameter_);
    this->bestObjectiveValue_ = this->optimisationProblem_->getObjectiveValue(this->initialParameter_);

    bool reduceStepSize = false;
    arma::Col<double> stepSize = initialStepSize_;

    while(!this->isFinished() && !this->isTerminated()) {
      if (reduceStepSize) {
        stepSize %= stepSizeDecrease_;
      }

      reduceStepSize = true;

      arma::Col<double> candidateParameter = this->bestParameter_;
      for (std::size_t n = 0; n < this->optimisationProblem_->getNumberOfDimensions(); ++n) {
        candidateParameter.at(n) += stepSize.at(n);

        if(this->optimisationProblem_->getUpperBounds().at(n) < candidateParameter.at(n)) {
          candidateParameter.at(n) = this->optimisationProblem_->getUpperBounds().at(n);
        }

        ++this->numberOfIterations_;
        double candidateSoftConstraintsValue = this->optimisationProblem_->getSoftConstraintsValue(candidateParameter);
        double candidateObjectiveValue = this->optimisationProblem_->getObjectiveValue(candidateParameter);

        if(candidateSoftConstraintsValue < this->bestSoftConstraintsValue_ || (candidateSoftConstraintsValue == this->bestSoftConstraintsValue_ && candidateObjectiveValue < this->bestObjectiveValue_)) {
          reduceStepSize = false;

          this->bestParameter_ = candidateParameter;
          this->bestSoftConstraintsValue_ = candidateSoftConstraintsValue;
          this->bestObjectiveValue_ = candidateObjectiveValue;
        }

        if (this->isFinished() || this->isTerminated()) {
          break;
        }

        candidateParameter.at(n) = this->bestParameter_.at(n) - stepSize.at(n);

        if(this->optimisationProblem_->getLowerBounds().at(n) > candidateParameter.at(n)) {
          candidateParameter.at(n) = this->optimisationProblem_->getLowerBounds().at(n);
        }

        ++this->numberOfIterations_;
        candidateSoftConstraintsValue = this->optimisationProblem_->getSoftConstraintsValue(candidateParameter);
        candidateObjectiveValue = this->optimisationProblem_->getObjectiveValue(candidateParameter);

        if(candidateSoftConstraintsValue < this->bestSoftConstraintsValue_ || (candidateSoftConstraintsValue == this->bestSoftConstraintsValue_ && candidateObjectiveValue < this->bestObjectiveValue_)) {
          reduceStepSize = false;

          this->bestParameter_ = candidateParameter;
          this->bestSoftConstraintsValue_ = candidateSoftConstraintsValue;
          this->bestObjectiveValue_ = candidateObjectiveValue;
        }

        if (this->isFinished() || this->isTerminated()) {
          break;
        }

        candidateParameter.at(n) = this->bestParameter_.at(n);
      }
    }
  }

  template <class DistanceFunction>
  void HookeJeevesAlgorithm<DistanceFunction>::setInitialStepSize(
      const arma::Col<double>& initialStepSize) {
    if(initialStepSize.n_rows != this->optimisationProblem_->getNumberOfDimensions()) {
      throw std::logic_error("The number of dimensions of the initial step size (" + std::to_string(initialStepSize.n_elem) + ") must match the number of dimensions of the optimisation problem (" + std::to_string(this->optimisationProblem_->getNumberOfDimensions()) + ").");
    } else if (arma::any(initialStepSize <= 0)) {
      throw std::logic_error("The initial step size must be strict greater than 0.");
    }

    initialStepSize_ = initialStepSize;
  }

  template <class DistanceFunction>
  void HookeJeevesAlgorithm<DistanceFunction>::setStepSizeDecrease(
      const arma::Col<double>& stepSizeDecrease) {
    if(stepSizeDecrease.n_rows != this->optimisationProblem_->getNumberOfDimensions()) {
      throw std::logic_error("The number of dimensions of the step size decrease (" + std::to_string(stepSizeDecrease.n_elem) + ") must match the number of dimensions of the optimisation problem (" + std::to_string(this->optimisationProblem_->getNumberOfDimensions()) + ").");
    } else if(arma::any(stepSizeDecrease <= 0) || arma::any(stepSizeDecrease >= 1)) {
      throw std::logic_error("The step size decrease must be strict greater than 0 and strict lower than 1.");
    }

    stepSizeDecrease_ = stepSizeDecrease;
  }

  template <class DistanceFunction>
  std::string HookeJeevesAlgorithm<DistanceFunction>::to_string() const noexcept {
    return "HookeJeevesAlgorithm";
  }
}
<|endoftext|>


<commit_before>#include "profiler.h"

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "display.h"

#ifdef __APPLE__
// See comment in Accessors class
pthread_key_t Accessors::key_;
#else
__thread JNIEnv * Accessors::env_;
#endif

ASGCTType Asgct::asgct_;

TraceData Profiler::traces_[kMaxStackTraces];

JVMPI_CallFrame Profiler::frame_buffer_[kMaxStackTraces][kMaxFramesToCapture];

int Profiler::failures_[kNumCallTraceErrors + 1];

namespace {

// Helper class to store and reset errno when in a signal handler.
class ErrnoRaii {
 public:
  ErrnoRaii() { stored_errno_ = errno; }
  ~ErrnoRaii() { errno = stored_errno_; }

 private:
  int stored_errno_;

  DISALLOW_COPY_AND_ASSIGN(ErrnoRaii);
};
}  // namespace

static uint64_t CalculateHash(JVMPI_CallTrace *trace, int skip) {
  // Make hash-value
  uint64_t h = 0;
  for (int i = skip; i < trace->num_frames; i++) {
    h += reinterpret_cast<uintptr_t>(trace->frames[i].method_id);
    h += h << 10;
    h ^= h >> 6;
    h += static_cast<uintptr_t>(trace->frames[i].lineno);
    h += h << 10;
    h ^= h >> 6;
  }
  h += h << 3;
  h ^= h >> 11;
  return h;
}

void Profiler::Handle(int signum, siginfo_t *info, void *context) {
  IMPLICITLY_USE(signum);
  IMPLICITLY_USE(info);
  ErrnoRaii err_storage;  // stores and resets errno

  JNIEnv *env = Accessors::CurrentJniEnv();
  if (env == NULL) {
    // native / JIT / GC thread, which isn't attached to the JVM.
    failures_[0]++;
    return;
  }

  JVMPI_CallTrace trace;
  JVMPI_CallFrame frames[kMaxFramesToCapture];
  // memset is not async-safe.
  for (int i = 0; i < kMaxFramesToCapture; ++i) {
    frames[i].lineno = 0;
    frames[i].method_id = 0;
  }

  trace.frames = frames;
  trace.env_id = env;

  ASGCTType asgct = Asgct::GetAsgct();
  (*asgct)(&trace, kMaxFramesToCapture, context);

  if (trace.num_frames < 0) {
    int idx = -trace.num_frames;
    if (idx > kNumCallTraceErrors) {
      return;
    }
    failures_[idx]++;
  }

  uint64_t hash_val = CalculateHash(&trace, 0);
  uint64_t idx = hash_val % kMaxStackTraces;

  uint64_t i = idx;
  do {
    intptr_t *count = &(traces_[i].count);
    if (*count == 0 && (NoBarrier_CompareAndSwap(count, 0, 1) == 0)) {
      // memcpy is not async safe
      JVMPI_CallFrame *fb = frame_buffer_[i];
      for (int frame_num = 0; frame_num < trace.num_frames; ++frame_num) {
        fb[frame_num].lineno = trace.frames[frame_num].lineno;
        fb[frame_num].method_id = trace.frames[frame_num].method_id;
      }

      traces_[i].trace.frames = fb;
      traces_[i].trace.num_frames = trace.num_frames;
      return;
    }

    if ((traces_[i].trace.num_frames == trace.num_frames) &&
        (memcmp(traces_[i].trace.frames, trace.frames,
                sizeof(JVMPI_CallFrame) * kMaxFramesToCapture) == 0)) {
      NoBarrier_AtomicIncrement(&(traces_[i].count), 1);
      return;
    }

    i = (i + 1) % kMaxStackTraces;
  } while (i != idx);
}

// This method schedules the SIGPROF timer to go off every sec
// seconds, usec microseconds.
bool SignalHandler::SetSigprofInterval(int sec, int usec) {
  static struct itimerval timer;
  timer.it_interval.tv_sec = sec;
  timer.it_interval.tv_usec = usec;
  timer.it_value = timer.it_interval;
  if (setitimer(ITIMER_PROF, &timer, 0) == -1) {
    fprintf(stderr, "Scheduling profiler interval failed with error %d\n",
            errno);
    return false;
  }
  return true;
}

struct sigaction SignalHandler::SetAction(void (*action)(int, siginfo_t *,
                                                         void *)) {
  struct sigaction sa;
  sa.sa_handler = NULL;
  sa.sa_sigaction = action;
  sa.sa_flags = SA_RESTART | SA_SIGINFO;

  sigemptyset(&sa.sa_mask);

  struct sigaction old_handler;
  if (sigaction(SIGPROF, &sa, &old_handler) != 0) {
    fprintf(stderr, "Scheduling profiler action failed with error %d\n", errno);
    return old_handler;
  }

  return old_handler;
}

bool Profiler::Start() {
  int usec_wait = 1000000 / kNumInterrupts;

  memset(traces_, 0, sizeof(traces_));
  memset(frame_buffer_, 0, sizeof(frame_buffer_));
  memset(failures_, 0, sizeof(failures_));

  // old_action_ is stored, but never used.  This is in case of future
  // refactorings that need it.
  old_action_ = handler_.SetAction(&Profiler::Handle);
  return handler_.SetSigprofInterval(0, usec_wait);
}

void Profiler::Stop() {
  handler_.SetSigprofInterval(0, 0);
  // Breaks encapsulation, but whatever.
  signal(SIGPROF, SIG_IGN);
}

static int CompareTraceData(const void *v1, const void *v2) {
  const TraceData *tr1 = reinterpret_cast<const TraceData *>(v1);
  const TraceData *tr2 = reinterpret_cast<const TraceData *>(v2);
  return tr1->count > tr2->count ? 1 : (tr1->count < tr2->count ? -1 : 0);
}

void Profiler::DumpToFile(FILE *file) {
  qsort(traces_, kMaxStackTraces, sizeof(TraceData), &CompareTraceData);

  StackTracesPrinter printer(file, jvmti_);

  printer.PrintStackTraces(traces_, kMaxStackTraces);
  printer.PrintLeafHistogram(traces_, kMaxStackTraces);

  fprintf(file, "Failures:\n"
                "Instances    Reason\n"
                "%-12d Non Java thread (GC/JIT/pure native)\n"
                "%-12d Stack walking disabled\n"
                "%-12d Java thread doing GC work\n"
                "%-12d In native code, unknown frame.\n"
                "%-12d In native code, non-walkable frame (you are likely to"
                " get this for native code).\n"
                "%-12d In Java code, unknown frame.\n"
                "%-12d In Java code, non-walkable frame (for example,"
                " if the frame is being constructed).\n"
                "%-12d Unknown thread state.\n"
                "%-12d Thread exiting.\n"
                "%-12d Thread in deoptimization"
                " (for dynamic recompilation).\n"
                "%-12d Thread in a safepoint"
                " (such as a stop-the-world GC).\n",
          failures_[-kNativeStackTrace], failures_[-kNoClassLoad],
          failures_[-kGcTraceError], failures_[-kUnknownNotJava],
          failures_[-kNotWalkableFrameNotJava], failures_[-kUnknownJava],
          failures_[-kNotWalkableFrameJava], failures_[-kUnknownState],
          failures_[-kTicksThreadExit], failures_[-kDeoptHandler],
          failures_[-kSafepoint]);
}
<commit_msg>Zero-initialization of frame data did not work with padded structs.  Now it does.<commit_after>#include "profiler.h"

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "display.h"

#ifdef __APPLE__
// See comment in Accessors class
pthread_key_t Accessors::key_;
#else
__thread JNIEnv * Accessors::env_;
#endif

ASGCTType Asgct::asgct_;

TraceData Profiler::traces_[kMaxStackTraces];

JVMPI_CallFrame Profiler::frame_buffer_[kMaxStackTraces][kMaxFramesToCapture];

int Profiler::failures_[kNumCallTraceErrors + 1];

namespace {

// Helper class to store and reset errno when in a signal handler.
class ErrnoRaii {
 public:
  ErrnoRaii() { stored_errno_ = errno; }
  ~ErrnoRaii() { errno = stored_errno_; }

 private:
  int stored_errno_;

  DISALLOW_COPY_AND_ASSIGN(ErrnoRaii);
};
}  // namespace

static uint64_t CalculateHash(JVMPI_CallTrace *trace, int skip) {
  // Make hash-value
  uint64_t h = 0;
  for (int i = skip; i < trace->num_frames; i++) {
    h += reinterpret_cast<uintptr_t>(trace->frames[i].method_id);
    h += h << 10;
    h ^= h >> 6;
    h += static_cast<uintptr_t>(trace->frames[i].lineno);
    h += h << 10;
    h ^= h >> 6;
  }
  h += h << 3;
  h ^= h >> 11;
  return h;
}

void Profiler::Handle(int signum, siginfo_t *info, void *context) {
  IMPLICITLY_USE(signum);
  IMPLICITLY_USE(info);
  ErrnoRaii err_storage;  // stores and resets errno

  JNIEnv *env = Accessors::CurrentJniEnv();
  if (env == NULL) {
    // native / JIT / GC thread, which isn't attached to the JVM.
    failures_[0]++;
    return;
  }

  JVMPI_CallTrace trace;
  JVMPI_CallFrame frames[kMaxFramesToCapture];
  // We have to set every byte to 0 instead of just initializing the
  // individual fields, because the structs might be padded, and we
  // use memcmp on it later.  We can't use memset, because it isn't
  // async-safe.
  char *base = reinterpret_cast<char *>(frames);
  for (char *p = base; p < base + sizeof(JVMPI_CallFrame) * kMaxFramesToCapture;
       p++) {
    *p = 0;
  }

  trace.frames = frames;
  trace.env_id = env;

  ASGCTType asgct = Asgct::GetAsgct();
  (*asgct)(&trace, kMaxFramesToCapture, context);

  if (trace.num_frames < 0) {
    int idx = -trace.num_frames;
    if (idx > kNumCallTraceErrors) {
      return;
    }
    failures_[idx]++;
  }

  uint64_t hash_val = CalculateHash(&trace, 0);
  uint64_t idx = hash_val % kMaxStackTraces;

  uint64_t i = idx;

  do {
    intptr_t *count = &(traces_[i].count);
    if (*count == 0 && (NoBarrier_CompareAndSwap(count, 0, 1) == 0)) {
      // memcpy is not async safe
      JVMPI_CallFrame *fb = frame_buffer_[i];
      for (int frame_num = 0; frame_num < trace.num_frames; ++frame_num) {
        base = reinterpret_cast<char *>(&(fb[frame_num]));
        // Make sure the padding is all set to 0.
        for (char *p = base; p < base + sizeof(JVMPI_CallFrame); p++) {
          *p = 0;
        }
        fb[frame_num].lineno = trace.frames[frame_num].lineno;
        fb[frame_num].method_id = trace.frames[frame_num].method_id;
      }

      traces_[i].trace.frames = fb;
      traces_[i].trace.num_frames = trace.num_frames;
      return;
    }

    if ((traces_[i].trace.num_frames == trace.num_frames) &&
        (memcmp(traces_[i].trace.frames, trace.frames,
                sizeof(JVMPI_CallFrame) * kMaxFramesToCapture) == 0)) {
      NoBarrier_AtomicIncrement(&(traces_[i].count), 1);
      return;
    }

    i = (i + 1) % kMaxStackTraces;
  } while (i != idx);
}

// This method schedules the SIGPROF timer to go off every sec
// seconds, usec microseconds.
bool SignalHandler::SetSigprofInterval(int sec, int usec) {
  static struct itimerval timer;
  timer.it_interval.tv_sec = sec;
  timer.it_interval.tv_usec = usec;
  timer.it_value = timer.it_interval;
  if (setitimer(ITIMER_PROF, &timer, 0) == -1) {
    fprintf(stderr, "Scheduling profiler interval failed with error %d\n",
            errno);
    return false;
  }
  return true;
}

struct sigaction SignalHandler::SetAction(void (*action)(int, siginfo_t *,
                                                         void *)) {
  struct sigaction sa;
  sa.sa_handler = NULL;
  sa.sa_sigaction = action;
  sa.sa_flags = SA_RESTART | SA_SIGINFO;

  sigemptyset(&sa.sa_mask);

  struct sigaction old_handler;
  if (sigaction(SIGPROF, &sa, &old_handler) != 0) {
    fprintf(stderr, "Scheduling profiler action failed with error %d\n", errno);
    return old_handler;
  }

  return old_handler;
}

bool Profiler::Start() {
  int usec_wait = 1000000 / kNumInterrupts;

  memset(traces_, 0, sizeof(traces_));
  memset(frame_buffer_, 0, sizeof(frame_buffer_));
  memset(failures_, 0, sizeof(failures_));

  // old_action_ is stored, but never used.  This is in case of future
  // refactorings that need it.
  old_action_ = handler_.SetAction(&Profiler::Handle);
  return handler_.SetSigprofInterval(0, usec_wait);
}

void Profiler::Stop() {
  handler_.SetSigprofInterval(0, 0);
  // Breaks encapsulation, but whatever.
  signal(SIGPROF, SIG_IGN);
}

static int CompareTraceData(const void *v1, const void *v2) {
  const TraceData *tr1 = reinterpret_cast<const TraceData *>(v1);
  const TraceData *tr2 = reinterpret_cast<const TraceData *>(v2);
  return tr1->count > tr2->count ? 1 : (tr1->count < tr2->count ? -1 : 0);
}

void Profiler::DumpToFile(FILE *file) {
  qsort(traces_, kMaxStackTraces, sizeof(TraceData), &CompareTraceData);

  StackTracesPrinter printer(file, jvmti_);

  printer.PrintStackTraces(traces_, kMaxStackTraces);
  printer.PrintLeafHistogram(traces_, kMaxStackTraces);

  fprintf(file, "Failures:\n"
                "Instances    Reason\n"
                "%-12d Non Java thread (GC/JIT/pure native)\n"
                "%-12d Stack walking disabled\n"
                "%-12d Java thread doing GC work\n"
                "%-12d In native code, unknown frame.\n"
                "%-12d In native code, non-walkable frame (you are likely to"
                " get this for native code).\n"
                "%-12d In Java code, unknown frame.\n"
                "%-12d In Java code, non-walkable frame (for example,"
                " if the frame is being constructed).\n"
                "%-12d Unknown thread state.\n"
                "%-12d Thread exiting.\n"
                "%-12d Thread in deoptimization"
                " (for dynamic recompilation).\n"
                "%-12d Thread in a safepoint"
                " (such as a stop-the-world GC).\n",
          failures_[-kNativeStackTrace], failures_[-kNoClassLoad],
          failures_[-kGcTraceError], failures_[-kUnknownNotJava],
          failures_[-kNotWalkableFrameNotJava], failures_[-kUnknownJava],
          failures_[-kNotWalkableFrameJava], failures_[-kUnknownState],
          failures_[-kTicksThreadExit], failures_[-kDeoptHandler],
          failures_[-kSafepoint]);
}
<|endoftext|>


<commit_before>#ifndef SFML_INPUT_HANDLER_HPP
#define SFML_INPUT_HANDLER_HPP
#include <unordered_map>
#include <SFML/Window.hpp>
#include "hash_pair.hpp"

class InputHandler
{
public:
	struct MousePosition
	{
		MousePosition():
		x(0),y(0){}
		int x;
		int y;
	};
	InputHandler()
	{
		auto p = sf::Mouse::getPosition();
		p_current_mouse_info.x = p.x;
		p_current_mouse_info.y = p.y;
		p_previous_mouse_info = p_current_mouse_info;
	}
	~InputHandler(){}

	void update()
	{
		#if !(INPUT_DISABLE_KEYBOARD)
		keyUpdate();
		#endif
		#if !(INPUT_DISABLE_MOUSE)
		mouseUpdate();
		#endif
		#if !(INPUT_DISABLE_JOYSTICK)
		joystickUpdate();
		#endif
	}

	void handleEvent(const sf::Event& event)
	{
		#if !(INPUT_DISABLE_KEYBOARD)
		if (keyEvent(event)) return;
		#endif
		#if !(INPUT_DISABLE_MOUSE)
		if (mouseEvent(event)) return;
		#endif
		#if !(INPUT_DISABLE_JOYSTICK)
		if (joystickEvent(event)) return;
		#endif
	}

#if !(INPUT_DISABLE_KEYBOARD)
	bool isKeyPressed(sf::Keyboard::Key key)
	{
		return p_key_pressed[key];
	}

	bool isKeyDown(sf::Keyboard::Key key)
	{
		return p_key_down[key];
	}

	bool isKeyReleased(sf::Keyboard::Key key)
	{
		return p_key_released[key];
	}
#endif

#if !(INPUT_DISABLE_MOUSE)
	bool mouseMoved()
	{
		return p_mouse_move_event;
	}

	const MousePosition& getMouseCurrentPosition()
	{
		return p_current_mouse_info;
	}

	const MousePosition& getMousePreviousPosition()
	{
		return p_previous_mouse_info;
	}

	bool isMouseButtonPressed(sf::Mouse::Button button)
	{
		return p_mouse_button_pressed[button];
	}

	bool isMouseButtonDown(sf::Mouse::Button button)
	{
		return p_mouse_button_down[button];
	}

	bool isMouseButtonReleased(sf::Mouse::Button button)
	{
		return p_mouse_button_released[button];
	}

	bool mouseWheelScrolled()
	{
		return p_mouse_wheel_scrolled;
	}

	float getMouseWheelScrollDelta()
	{
		return p_mouse_scroll_delta;
	}
#endif

#if !(INPUT_DISABLE_JOYSTICK)
	bool isJoystickConnected(unsigned int joystickId)
	{
		return p_joystick_connected[joystickId];
	}

	bool isJoystickActive(unsigned int joystickId)
	{
		return p_joystick_active[joystickId];
	}

	bool isJoystickDisconnected(unsigned int joystickId)
	{
		return p_joystick_disconnected[joystickId];
	}

	bool isJoystickButtonPressed(unsigned int joystickId, unsigned int button)
	{
		return p_joystick_button_pressed[std::pair<unsigned int, unsigned int>(joystickId, button)];
	}

	bool isJoystickButtonDown(unsigned int joystickId, unsigned int button)
	{
		return p_joystick_button_down[std::pair<unsigned int, unsigned int>(joystickId, button)];
	}

	bool isJoystickButtonReleased(unsigned int joystickId, unsigned int button)
	{
		return p_joystick_button_released[std::pair<unsigned int, unsigned int>(joystickId, button)];
	}

	float getJoystickAxisPosition(unsigned int joystickId, sf::Joystick::Axis axis)
	{
		return p_joystick_axis_position[std::pair<unsigned int, unsigned int>(joystickId, axis)];
	}
#endif

private:
#if !(INPUT_DISABLE_KEYBOARD)
	void keyUpdate()
	{
		for (auto it = p_key_pressed.begin(); it != p_key_pressed.end(); ++it)
		{
			if (it->second)
			{
				p_key_down[it->first] = true;
				it->second = false;
			}
		}

		for (auto it = p_key_released.begin(); it != p_key_released.end(); ++it)
		{
			if (it->second)
				it->second = false;
		}
	}

	bool keyEvent(const sf::Event& event)
	{
		if (event.type == sf::Event::KeyPressed)
		{
			p_key_pressed[event.key.code] = not p_key_down[event.key.code];
			p_key_released[event.key.code] = false;
			return true;
		}
		else if (event.type == sf::Event::KeyReleased)
		{
			p_key_pressed[event.key.code] = false;
			p_key_down[event.key.code] = false;
			p_key_released[event.key.code] = true;
			return true;
		}
		return false;
	}

	std::unordered_map<unsigned int, bool> p_key_pressed;
	std::unordered_map<unsigned int, bool> p_key_down;
	std::unordered_map<unsigned int, bool> p_key_released;
#endif

#if !(INPUT_DISABLE_MOUSE)
	void mouseUpdate()
	{
		p_mouse_move_event = false;

		for (auto it = p_mouse_button_pressed.begin(); it != p_mouse_button_pressed.end(); ++it)
		{
			if (it->second)
			{
				p_mouse_button_down[it->first] = true;
				it->second = false;
			}
		}

		for (auto it = p_mouse_button_released.begin(); it != p_mouse_button_released.end(); ++it)
		{
			if (it->second)
				it->second = false;
		}

		if (p_mouse_wheel_scrolled)
		{
			p_mouse_wheel_scrolled = false;
			p_mouse_scroll_delta = 0.0f;
		}
	}

	bool mouseEvent(const sf::Event& event)
	{
		if (event.type == sf::Event::MouseButtonPressed)
		{
			p_mouse_button_pressed[event.mouseButton.button] = not p_mouse_button_down[event.mouseButton.button];
			p_mouse_button_released[event.mouseButton.button] = false;
			return true;
		}
		else if (event.type == sf::Event::MouseButtonReleased)
		{
			p_mouse_button_pressed[event.mouseButton.button] = false;
			p_mouse_button_down[event.mouseButton.button] = false;
			p_mouse_button_released[event.mouseButton.button] = true;
			return true;
		}
		else if (event.type == sf::Event::MouseMoved)
		{
			p_previous_mouse_info = p_current_mouse_info;
			MousePosition mouse;
			mouse.x = event.mouseMove.x;
			mouse.y = event.mouseMove.y;
			p_current_mouse_info = mouse;
			p_mouse_move_event = true;
			return true;
		}
		else if (event.type == sf::Event::MouseWheelMoved)
		{
			p_mouse_wheel_scrolled = true;
			p_mouse_scroll_delta = event.mouseWheel.delta;
			return true;
		}
		return false;
	}

	bool p_mouse_move_event;
	bool p_mouse_wheel_scrolled;
	float p_mouse_scroll_delta;
	MousePosition p_current_mouse_info;
	MousePosition p_previous_mouse_info;

	std::unordered_map<unsigned int, bool> p_mouse_button_pressed;
	std::unordered_map<unsigned int, bool> p_mouse_button_down;
	std::unordered_map<unsigned int, bool> p_mouse_button_released;
#endif

#if !(INPUT_DISABLE_JOYSTICK)
	void joystickUpdate()
	{
		for (auto it = p_joystick_button_pressed.begin(); it != p_joystick_button_pressed.end(); ++it)
		{
			if (it->second)
			{
				p_joystick_button_down[it->first] = true;
				it->second = false;
			}
		}

		for (auto it = p_joystick_button_released.begin(); it != p_joystick_button_released.end(); ++it)
		{
			if (it->second)
				it->second = false;
		}

		for (auto it = p_joystick_connected.begin(); it != p_joystick_connected.end(); ++it)
		{
			if (it->second)
			{
				p_joystick_active[it->first] = true;
				it->second = false;
			}
		}

		for (auto it = p_joystick_disconnected.begin(); it != p_joystick_disconnected.end(); ++it)
		{
			if (it->second)
				it->second = false;
		}
	}

	bool joystickEvent(const sf::Event& event)
	{
		if (event.type == sf::Event::JoystickConnected)
		{
			p_joystick_connected[event.joystickConnect.joystickId] = not p_joystick_active[event.joystickConnect.joystickId];
			p_joystick_disconnected[event.joystickConnect.joystickId] = false;
			return true;
		}
		else if (event.type == sf::Event::JoystickDisconnected)
		{
			p_joystick_connected[event.joystickConnect.joystickId] = false;
			p_joystick_active[event.joystickConnect.joystickId] = false;
			p_joystick_disconnected[event.joystickConnect.joystickId] = true;
			return true;
		}
		else if (event.type == sf::Event::JoystickButtonPressed)
		{
			std::pair<unsigned int, unsigned int> k(event.joystickButton.joystickId, event.joystickButton.button);
			p_joystick_button_pressed[k] = not p_joystick_button_down[k];
			p_joystick_button_released[k] = false;
			return true;
		}
		else if (event.type == sf::Event::JoystickButtonReleased)
		{
			std::pair<unsigned int, unsigned int> k(event.joystickButton.joystickId, event.joystickButton.button);
			p_joystick_button_pressed[k] = false;
			p_joystick_button_down[k] = false;
			p_joystick_button_released[k] = true;
			return true;
		}
		else if (event.type == sf::Event::JoystickMoved)
		{
			std::pair<unsigned int, unsigned int> k(event.joystickMove.joystickId, event.joystickMove.axis);
			p_joystick_axis_position[k] = event.joystickMove.position;
			return true;
		}
		return false;
	}

	std::unordered_map<unsigned int, bool> p_joystick_connected;
	std::unordered_map<unsigned int, bool> p_joystick_active;
	std::unordered_map<unsigned int, bool> p_joystick_disconnected;
	std::unordered_map<std::pair<unsigned int, unsigned int>, bool> p_joystick_button_pressed;
	std::unordered_map<std::pair<unsigned int, unsigned int>, bool> p_joystick_button_down;
	std::unordered_map<std::pair<unsigned int, unsigned int>, bool> p_joystick_button_released;
	std::unordered_map<std::pair<unsigned int, unsigned int>, float> p_joystick_axis_position;
#endif

};
#endif
<commit_msg>Added missing ifdef in constructor causing errors when compiling with INPUT_DISABLE_MOUSE<commit_after>#ifndef SFML_INPUT_HANDLER_HPP
#define SFML_INPUT_HANDLER_HPP
#include <unordered_map>
#include <SFML/Window.hpp>
#include "hash_pair.hpp"

class InputHandler
{
public:
	struct MousePosition
	{
		MousePosition():
		x(0),y(0){}
		int x;
		int y;
	};
	InputHandler()
	{
		#if !(INPUT_DISABLE_MOUSE)
		auto p = sf::Mouse::getPosition();
		p_current_mouse_info.x = p.x;
		p_current_mouse_info.y = p.y;
		p_previous_mouse_info = p_current_mouse_info;
		#endif
	}
	~InputHandler(){}

	void update()
	{
		#if !(INPUT_DISABLE_KEYBOARD)
		keyUpdate();
		#endif
		#if !(INPUT_DISABLE_MOUSE)
		mouseUpdate();
		#endif
		#if !(INPUT_DISABLE_JOYSTICK)
		joystickUpdate();
		#endif
	}

	void handleEvent(const sf::Event& event)
	{
		#if !(INPUT_DISABLE_KEYBOARD)
		if (keyEvent(event)) return;
		#endif
		#if !(INPUT_DISABLE_MOUSE)
		if (mouseEvent(event)) return;
		#endif
		#if !(INPUT_DISABLE_JOYSTICK)
		if (joystickEvent(event)) return;
		#endif
	}

#if !(INPUT_DISABLE_KEYBOARD)
	bool isKeyPressed(sf::Keyboard::Key key)
	{
		return p_key_pressed[key];
	}

	bool isKeyDown(sf::Keyboard::Key key)
	{
		return p_key_down[key];
	}

	bool isKeyReleased(sf::Keyboard::Key key)
	{
		return p_key_released[key];
	}
#endif

#if !(INPUT_DISABLE_MOUSE)
	bool mouseMoved()
	{
		return p_mouse_move_event;
	}

	const MousePosition& getMouseCurrentPosition()
	{
		return p_current_mouse_info;
	}

	const MousePosition& getMousePreviousPosition()
	{
		return p_previous_mouse_info;
	}

	bool isMouseButtonPressed(sf::Mouse::Button button)
	{
		return p_mouse_button_pressed[button];
	}

	bool isMouseButtonDown(sf::Mouse::Button button)
	{
		return p_mouse_button_down[button];
	}

	bool isMouseButtonReleased(sf::Mouse::Button button)
	{
		return p_mouse_button_released[button];
	}

	bool mouseWheelScrolled()
	{
		return p_mouse_wheel_scrolled;
	}

	float getMouseWheelScrollDelta()
	{
		return p_mouse_scroll_delta;
	}
#endif

#if !(INPUT_DISABLE_JOYSTICK)
	bool isJoystickConnected(unsigned int joystickId)
	{
		return p_joystick_connected[joystickId];
	}

	bool isJoystickActive(unsigned int joystickId)
	{
		return p_joystick_active[joystickId];
	}

	bool isJoystickDisconnected(unsigned int joystickId)
	{
		return p_joystick_disconnected[joystickId];
	}

	bool isJoystickButtonPressed(unsigned int joystickId, unsigned int button)
	{
		return p_joystick_button_pressed[std::pair<unsigned int, unsigned int>(joystickId, button)];
	}

	bool isJoystickButtonDown(unsigned int joystickId, unsigned int button)
	{
		return p_joystick_button_down[std::pair<unsigned int, unsigned int>(joystickId, button)];
	}

	bool isJoystickButtonReleased(unsigned int joystickId, unsigned int button)
	{
		return p_joystick_button_released[std::pair<unsigned int, unsigned int>(joystickId, button)];
	}

	float getJoystickAxisPosition(unsigned int joystickId, sf::Joystick::Axis axis)
	{
		return p_joystick_axis_position[std::pair<unsigned int, unsigned int>(joystickId, axis)];
	}
#endif

private:
#if !(INPUT_DISABLE_KEYBOARD)
	void keyUpdate()
	{
		for (auto it = p_key_pressed.begin(); it != p_key_pressed.end(); ++it)
		{
			if (it->second)
			{
				p_key_down[it->first] = true;
				it->second = false;
			}
		}

		for (auto it = p_key_released.begin(); it != p_key_released.end(); ++it)
		{
			if (it->second)
				it->second = false;
		}
	}

	bool keyEvent(const sf::Event& event)
	{
		if (event.type == sf::Event::KeyPressed)
		{
			p_key_pressed[event.key.code] = not p_key_down[event.key.code];
			p_key_released[event.key.code] = false;
			return true;
		}
		else if (event.type == sf::Event::KeyReleased)
		{
			p_key_pressed[event.key.code] = false;
			p_key_down[event.key.code] = false;
			p_key_released[event.key.code] = true;
			return true;
		}
		return false;
	}

	std::unordered_map<unsigned int, bool> p_key_pressed;
	std::unordered_map<unsigned int, bool> p_key_down;
	std::unordered_map<unsigned int, bool> p_key_released;
#endif

#if !(INPUT_DISABLE_MOUSE)
	void mouseUpdate()
	{
		p_mouse_move_event = false;

		for (auto it = p_mouse_button_pressed.begin(); it != p_mouse_button_pressed.end(); ++it)
		{
			if (it->second)
			{
				p_mouse_button_down[it->first] = true;
				it->second = false;
			}
		}

		for (auto it = p_mouse_button_released.begin(); it != p_mouse_button_released.end(); ++it)
		{
			if (it->second)
				it->second = false;
		}

		if (p_mouse_wheel_scrolled)
		{
			p_mouse_wheel_scrolled = false;
			p_mouse_scroll_delta = 0.0f;
		}
	}

	bool mouseEvent(const sf::Event& event)
	{
		if (event.type == sf::Event::MouseButtonPressed)
		{
			p_mouse_button_pressed[event.mouseButton.button] = not p_mouse_button_down[event.mouseButton.button];
			p_mouse_button_released[event.mouseButton.button] = false;
			return true;
		}
		else if (event.type == sf::Event::MouseButtonReleased)
		{
			p_mouse_button_pressed[event.mouseButton.button] = false;
			p_mouse_button_down[event.mouseButton.button] = false;
			p_mouse_button_released[event.mouseButton.button] = true;
			return true;
		}
		else if (event.type == sf::Event::MouseMoved)
		{
			p_previous_mouse_info = p_current_mouse_info;
			MousePosition mouse;
			mouse.x = event.mouseMove.x;
			mouse.y = event.mouseMove.y;
			p_current_mouse_info = mouse;
			p_mouse_move_event = true;
			return true;
		}
		else if (event.type == sf::Event::MouseWheelMoved)
		{
			p_mouse_wheel_scrolled = true;
			p_mouse_scroll_delta = event.mouseWheel.delta;
			return true;
		}
		return false;
	}

	bool p_mouse_move_event;
	bool p_mouse_wheel_scrolled;
	float p_mouse_scroll_delta;
	MousePosition p_current_mouse_info;
	MousePosition p_previous_mouse_info;

	std::unordered_map<unsigned int, bool> p_mouse_button_pressed;
	std::unordered_map<unsigned int, bool> p_mouse_button_down;
	std::unordered_map<unsigned int, bool> p_mouse_button_released;
#endif

#if !(INPUT_DISABLE_JOYSTICK)
	void joystickUpdate()
	{
		for (auto it = p_joystick_button_pressed.begin(); it != p_joystick_button_pressed.end(); ++it)
		{
			if (it->second)
			{
				p_joystick_button_down[it->first] = true;
				it->second = false;
			}
		}

		for (auto it = p_joystick_button_released.begin(); it != p_joystick_button_released.end(); ++it)
		{
			if (it->second)
				it->second = false;
		}

		for (auto it = p_joystick_connected.begin(); it != p_joystick_connected.end(); ++it)
		{
			if (it->second)
			{
				p_joystick_active[it->first] = true;
				it->second = false;
			}
		}

		for (auto it = p_joystick_disconnected.begin(); it != p_joystick_disconnected.end(); ++it)
		{
			if (it->second)
				it->second = false;
		}
	}

	bool joystickEvent(const sf::Event& event)
	{
		if (event.type == sf::Event::JoystickConnected)
		{
			p_joystick_connected[event.joystickConnect.joystickId] = not p_joystick_active[event.joystickConnect.joystickId];
			p_joystick_disconnected[event.joystickConnect.joystickId] = false;
			return true;
		}
		else if (event.type == sf::Event::JoystickDisconnected)
		{
			p_joystick_connected[event.joystickConnect.joystickId] = false;
			p_joystick_active[event.joystickConnect.joystickId] = false;
			p_joystick_disconnected[event.joystickConnect.joystickId] = true;
			return true;
		}
		else if (event.type == sf::Event::JoystickButtonPressed)
		{
			std::pair<unsigned int, unsigned int> k(event.joystickButton.joystickId, event.joystickButton.button);
			p_joystick_button_pressed[k] = not p_joystick_button_down[k];
			p_joystick_button_released[k] = false;
			return true;
		}
		else if (event.type == sf::Event::JoystickButtonReleased)
		{
			std::pair<unsigned int, unsigned int> k(event.joystickButton.joystickId, event.joystickButton.button);
			p_joystick_button_pressed[k] = false;
			p_joystick_button_down[k] = false;
			p_joystick_button_released[k] = true;
			return true;
		}
		else if (event.type == sf::Event::JoystickMoved)
		{
			std::pair<unsigned int, unsigned int> k(event.joystickMove.joystickId, event.joystickMove.axis);
			p_joystick_axis_position[k] = event.joystickMove.position;
			return true;
		}
		return false;
	}

	std::unordered_map<unsigned int, bool> p_joystick_connected;
	std::unordered_map<unsigned int, bool> p_joystick_active;
	std::unordered_map<unsigned int, bool> p_joystick_disconnected;
	std::unordered_map<std::pair<unsigned int, unsigned int>, bool> p_joystick_button_pressed;
	std::unordered_map<std::pair<unsigned int, unsigned int>, bool> p_joystick_button_down;
	std::unordered_map<std::pair<unsigned int, unsigned int>, bool> p_joystick_button_released;
	std::unordered_map<std::pair<unsigned int, unsigned int>, float> p_joystick_axis_position;
#endif
};
#endif
<|endoftext|>


<commit_before>// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "lib/extras/packed_image_convert.h"

#include <cstdint>

#include "jxl/color_encoding.h"
#include "jxl/types.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/color_management.h"
#include "lib/jxl/dec_external_image.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_external_image.h"
#include "lib/jxl/enc_image_bundle.h"

namespace jxl {
namespace extras {

Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
                                          ThreadPool* pool, CodecInOut* io) {
  const bool has_alpha = ppf.info.alpha_bits != 0;
  JXL_ASSERT(!ppf.frames.empty());
  if (has_alpha) {
    JXL_ASSERT(ppf.info.alpha_bits == ppf.info.bits_per_sample);
    JXL_ASSERT(ppf.info.alpha_exponent_bits ==
               ppf.info.exponent_bits_per_sample);
  }

  const bool is_gray = ppf.info.num_color_channels == 1;
  JXL_ASSERT(ppf.info.num_color_channels == 1 ||
             ppf.info.num_color_channels == 3);

  // Convert the image metadata
  io->SetSize(ppf.info.xsize, ppf.info.ysize);
  io->metadata.m.bit_depth.bits_per_sample = ppf.info.bits_per_sample;
  io->metadata.m.bit_depth.exponent_bits_per_sample =
      ppf.info.exponent_bits_per_sample;
  io->metadata.m.bit_depth.floating_point_sample =
      ppf.info.exponent_bits_per_sample != 0;
  io->metadata.m.modular_16_bit_buffer_sufficient =
      ppf.info.exponent_bits_per_sample == 0 && ppf.info.bits_per_sample <= 12;

  io->metadata.m.SetAlphaBits(ppf.info.alpha_bits,
                              ppf.info.alpha_premultiplied);

  io->metadata.m.xyb_encoded = !ppf.info.uses_original_profile;
  JXL_ASSERT(ppf.info.orientation > 0 && ppf.info.orientation <= 8);
  io->metadata.m.orientation = ppf.info.orientation;

  // Convert animation metadata
  JXL_ASSERT(ppf.frames.size() == 1 || ppf.info.have_animation);
  io->metadata.m.have_animation = ppf.info.have_animation;
  io->metadata.m.animation.tps_numerator = ppf.info.animation.tps_numerator;
  io->metadata.m.animation.tps_denominator = ppf.info.animation.tps_denominator;
  io->metadata.m.animation.num_loops = ppf.info.animation.num_loops;

  // Convert the color encoding.
  if (!ppf.icc.empty()) {
    PaddedBytes icc;
    icc.append(ppf.icc);
    if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
      fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB");
      io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray);
    }
  } else {
    JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding(
        ppf.color_encoding, &io->metadata.m.color_encoding));
  }

  // Convert the extra blobs
  io->blobs.exif.clear();
  io->blobs.exif.append(ppf.metadata.exif);
  io->blobs.iptc.clear();
  io->blobs.iptc.append(ppf.metadata.iptc);
  io->blobs.jumbf.clear();
  io->blobs.jumbf.append(ppf.metadata.jumbf);
  io->blobs.xmp.clear();
  io->blobs.xmp.append(ppf.metadata.xmp);

  // Convert the pixels
  io->dec_pixels = 0;
  io->frames.clear();
  for (const auto& frame : ppf.frames) {
    JXL_ASSERT(frame.color.pixels() != nullptr);
    size_t frame_bits_per_sample =
        frame.color.BitsPerChannel(frame.color.format.data_type);
    JXL_ASSERT(frame_bits_per_sample != 0);
    // It is ok for the frame.color.format.num_channels to not match the
    // number of channels on the image.
    JXL_ASSERT(1 <= frame.color.format.num_channels &&
               frame.color.format.num_channels <= 4);

    const Span<const uint8_t> span(
        static_cast<const uint8_t*>(frame.color.pixels()),
        frame.color.pixels_size);
    Rect frame_rect =
        Rect(frame.x0, frame.y0, frame.color.xsize, frame.color.ysize);
    JXL_ASSERT(frame_rect.IsInside(Rect(0, 0, ppf.info.xsize, ppf.info.ysize)));

    ImageBundle bundle(&io->metadata.m);
    if (ppf.info.have_animation) {
      bundle.duration = frame.frame_info.duration;
      bundle.blend = frame.blend;
      bundle.use_for_next_frame = frame.use_for_next_frame;
    }
    bundle.name = frame.name;  // frame.frame_info.name_length is ignored here.
    bundle.origin.x0 = frame.x0;
    bundle.origin.y0 = frame.y0;

    JXL_ASSERT(io->metadata.m.color_encoding.IsGray() ==
               (frame.color.format.num_channels <= 2));

    const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 ||
                          frame.color.format.data_type == JXL_TYPE_FLOAT;
    JXL_RETURN_IF_ERROR(ConvertFromExternal(
        span, frame.color.xsize, frame.color.ysize,
        io->metadata.m.color_encoding,
        /*has_alpha=*/frame.color.format.num_channels == 2 ||
            frame.color.format.num_channels == 4,
        /*alpha_is_premultiplied=*/ppf.info.alpha_premultiplied,
        frame_bits_per_sample, frame.color.format.endianness,
        /*flipped_y=*/frame.color.flipped_y, pool, &bundle,
        /*float_in=*/float_in, /*align=*/0));

    // TODO(deymo): Convert the extra channels. FIXME!
    JXL_CHECK(frame.extra_channels.empty());

    io->frames.push_back(std::move(bundle));
    io->dec_pixels += frame.color.xsize * frame.color.ysize;
  }

  if (ppf.info.exponent_bits_per_sample == 0) {
    // uint case.
    io->metadata.m.bit_depth.bits_per_sample = io->Main().DetectRealBitdepth();
  }
  if (ppf.info.intensity_target != 0) {
    io->metadata.m.SetIntensityTarget(ppf.info.intensity_target);
  } else {
    SetIntensityTarget(io);
  }
  io->CheckMetadata();
  return true;
}

// Allows converting from internal CodecInOut to external PackedPixelFile
Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
                                          const JxlPixelFormat& pixel_format,
                                          const ColorEncoding& c_desired,
                                          ThreadPool* pool,
                                          PackedPixelFile* ppf) {
  const bool has_alpha = io.metadata.m.HasAlpha();
  bool alpha_premultiplied = false;
  JXL_ASSERT(!io.frames.empty());

  if (has_alpha) {
    JXL_ASSERT(io.metadata.m.GetAlphaBits() ==
               io.metadata.m.bit_depth.bits_per_sample);
    const auto* alpha_channel = io.metadata.m.Find(ExtraChannel::kAlpha);
    JXL_ASSERT(alpha_channel->bit_depth.exponent_bits_per_sample ==
               io.metadata.m.bit_depth.exponent_bits_per_sample);
    alpha_premultiplied = alpha_channel->alpha_associated;
  }

  // Convert the image metadata
  ppf->info.xsize = io.metadata.size.xsize();
  ppf->info.ysize = io.metadata.size.ysize();
  ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels();
  ppf->info.bits_per_sample = io.metadata.m.bit_depth.bits_per_sample;
  ppf->info.exponent_bits_per_sample =
      io.metadata.m.bit_depth.exponent_bits_per_sample;

  ppf->info.alpha_bits = io.metadata.m.GetAlphaBits();
  ppf->info.alpha_premultiplied = alpha_premultiplied;

  ppf->info.uses_original_profile = !io.metadata.m.xyb_encoded;
  JXL_ASSERT(0 < io.metadata.m.orientation && io.metadata.m.orientation <= 8);
  ppf->info.orientation =
      static_cast<JxlOrientation>(io.metadata.m.orientation);
  ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels();

  // Convert animation metadata
  JXL_ASSERT(io.frames.size() == 1 || io.metadata.m.have_animation);
  ppf->info.have_animation = io.metadata.m.have_animation;
  ppf->info.animation.tps_numerator = io.metadata.m.animation.tps_numerator;
  ppf->info.animation.tps_denominator = io.metadata.m.animation.tps_denominator;
  ppf->info.animation.num_loops = io.metadata.m.animation.num_loops;

  // Convert the color encoding
  ppf->icc.assign(c_desired.ICC().begin(), c_desired.ICC().end());
  if (ppf->icc.empty()) {
    ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding);
  }

  // Convert the extra blobs
  ppf->metadata.exif.assign(io.blobs.exif.begin(), io.blobs.exif.end());
  ppf->metadata.iptc.assign(io.blobs.iptc.begin(), io.blobs.iptc.end());
  ppf->metadata.jumbf.assign(io.blobs.jumbf.begin(), io.blobs.jumbf.end());
  ppf->metadata.xmp.assign(io.blobs.xmp.begin(), io.blobs.xmp.end());
  const bool float_out = pixel_format.data_type == JXL_TYPE_FLOAT ||
                         pixel_format.data_type == JXL_TYPE_FLOAT16;
  // Convert the pixels
  ppf->frames.clear();
  for (const auto& frame : io.frames) {
    size_t frame_bits_per_sample = frame.metadata()->bit_depth.bits_per_sample;
    JXL_ASSERT(frame_bits_per_sample != 0);
    // It is ok for the frame.color().kNumPlanes to not match the
    // number of channels on the image.
    const uint32_t num_channels =
        frame.metadata()->color_encoding.Channels() + has_alpha;
    JxlPixelFormat format{/*num_channels=*/num_channels,
                          /*data_type=*/pixel_format.data_type,
                          /*endianness=*/pixel_format.endianness,
                          /*align=*/pixel_format.align};

    PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(),
                             format);
    const size_t bits_per_sample =
        packed_frame.color.BitsPerChannel(pixel_format.data_type);
    packed_frame.name = frame.name;
    packed_frame.frame_info.name_length = frame.name.size();
    // Color transform
    ImageBundle ib = frame.Copy();
    const ImageBundle* to_color_transform = &ib;
    ImageMetadata metadata = io.metadata.m;
    ImageBundle store(&metadata);
    const ImageBundle* transformed;
    // TODO(firsching): handle the transform here.
    JXL_RETURN_IF_ERROR(TransformIfNeeded(*to_color_transform, c_desired,
                                          GetJxlCms(), pool, &store,
                                          &transformed));
    size_t stride = ib.oriented_xsize() *
                    (c_desired.Channels() * ppf->info.bits_per_sample) /
                    kBitsPerByte;
    PaddedBytes pixels(stride * ib.oriented_ysize());

    JXL_RETURN_IF_ERROR(ConvertToExternal(
        *transformed, bits_per_sample, float_out, format.num_channels,
        format.endianness,
        /* stride_out=*/packed_frame.color.stride, pool,
        packed_frame.color.pixels(), packed_frame.color.pixels_size,
        /*out_callback=*/nullptr, /*out_opaque=*/nullptr,
        frame.metadata()->GetOrientation()));

    // TODO(firsching): Convert the extra channels, beside one potential alpha
    // channel. FIXME!
    JXL_CHECK(frame.extra_channels().size() <= has_alpha);
    ppf->frames.push_back(std::move(packed_frame));
  }

  return true;
}
}  // namespace extras
}  // namespace jxl
<commit_msg>Add support for extra channels to PPF -> codecio conversion.<commit_after>// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "lib/extras/packed_image_convert.h"

#include <cstdint>

#include "jxl/color_encoding.h"
#include "jxl/types.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/color_management.h"
#include "lib/jxl/dec_external_image.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_external_image.h"
#include "lib/jxl/enc_image_bundle.h"

namespace jxl {
namespace extras {

Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
                                          ThreadPool* pool, CodecInOut* io) {
  const bool has_alpha = ppf.info.alpha_bits != 0;
  JXL_ASSERT(!ppf.frames.empty());
  if (has_alpha) {
    JXL_ASSERT(ppf.info.alpha_bits == ppf.info.bits_per_sample);
    JXL_ASSERT(ppf.info.alpha_exponent_bits ==
               ppf.info.exponent_bits_per_sample);
  }

  const bool is_gray = ppf.info.num_color_channels == 1;
  JXL_ASSERT(ppf.info.num_color_channels == 1 ||
             ppf.info.num_color_channels == 3);

  // Convert the image metadata
  io->SetSize(ppf.info.xsize, ppf.info.ysize);
  io->metadata.m.bit_depth.bits_per_sample = ppf.info.bits_per_sample;
  io->metadata.m.bit_depth.exponent_bits_per_sample =
      ppf.info.exponent_bits_per_sample;
  io->metadata.m.bit_depth.floating_point_sample =
      ppf.info.exponent_bits_per_sample != 0;
  io->metadata.m.modular_16_bit_buffer_sufficient =
      ppf.info.exponent_bits_per_sample == 0 && ppf.info.bits_per_sample <= 12;

  io->metadata.m.SetAlphaBits(ppf.info.alpha_bits,
                              ppf.info.alpha_premultiplied);

  io->metadata.m.xyb_encoded = !ppf.info.uses_original_profile;
  JXL_ASSERT(ppf.info.orientation > 0 && ppf.info.orientation <= 8);
  io->metadata.m.orientation = ppf.info.orientation;

  // Convert animation metadata
  JXL_ASSERT(ppf.frames.size() == 1 || ppf.info.have_animation);
  io->metadata.m.have_animation = ppf.info.have_animation;
  io->metadata.m.animation.tps_numerator = ppf.info.animation.tps_numerator;
  io->metadata.m.animation.tps_denominator = ppf.info.animation.tps_denominator;
  io->metadata.m.animation.num_loops = ppf.info.animation.num_loops;

  // Convert the color encoding.
  if (!ppf.icc.empty()) {
    PaddedBytes icc;
    icc.append(ppf.icc);
    if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
      fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB");
      io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray);
    }
  } else {
    JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding(
        ppf.color_encoding, &io->metadata.m.color_encoding));
  }

  // Convert the extra blobs
  io->blobs.exif.clear();
  io->blobs.exif.append(ppf.metadata.exif);
  io->blobs.iptc.clear();
  io->blobs.iptc.append(ppf.metadata.iptc);
  io->blobs.jumbf.clear();
  io->blobs.jumbf.append(ppf.metadata.jumbf);
  io->blobs.xmp.clear();
  io->blobs.xmp.append(ppf.metadata.xmp);

  // Append all other extra channels.
  for (const PackedPixelFile::PackedExtraChannel& info :
       ppf.extra_channels_info) {
    ExtraChannelInfo out;
    out.type = static_cast<jxl::ExtraChannel>(info.ec_info.type);
    out.bit_depth.bits_per_sample = info.ec_info.bits_per_sample;
    out.bit_depth.exponent_bits_per_sample =
        info.ec_info.exponent_bits_per_sample;
    out.bit_depth.floating_point_sample =
        info.ec_info.exponent_bits_per_sample != 0;
    out.dim_shift = info.ec_info.dim_shift;
    out.name = info.name;
    out.alpha_associated = (info.ec_info.alpha_premultiplied != 0);
    out.spot_color[0] = info.ec_info.spot_color[0];
    out.spot_color[1] = info.ec_info.spot_color[1];
    out.spot_color[2] = info.ec_info.spot_color[2];
    out.spot_color[3] = info.ec_info.spot_color[3];
    io->metadata.m.extra_channel_info.push_back(std::move(out));
  }

  // Convert the pixels
  io->dec_pixels = 0;
  io->frames.clear();
  for (const auto& frame : ppf.frames) {
    JXL_ASSERT(frame.color.pixels() != nullptr);
    size_t frame_bits_per_sample =
        frame.color.BitsPerChannel(frame.color.format.data_type);
    JXL_ASSERT(frame_bits_per_sample != 0);
    // It is ok for the frame.color.format.num_channels to not match the
    // number of channels on the image.
    JXL_ASSERT(1 <= frame.color.format.num_channels &&
               frame.color.format.num_channels <= 4);

    const Span<const uint8_t> span(
        static_cast<const uint8_t*>(frame.color.pixels()),
        frame.color.pixels_size);
    Rect frame_rect =
        Rect(frame.x0, frame.y0, frame.color.xsize, frame.color.ysize);
    JXL_ASSERT(frame_rect.IsInside(Rect(0, 0, ppf.info.xsize, ppf.info.ysize)));

    ImageBundle bundle(&io->metadata.m);
    if (ppf.info.have_animation) {
      bundle.duration = frame.frame_info.duration;
      bundle.blend = frame.blend;
      bundle.use_for_next_frame = frame.use_for_next_frame;
    }
    bundle.name = frame.name;  // frame.frame_info.name_length is ignored here.
    bundle.origin.x0 = frame.x0;
    bundle.origin.y0 = frame.y0;

    JXL_ASSERT(io->metadata.m.color_encoding.IsGray() ==
               (frame.color.format.num_channels <= 2));

    const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 ||
                          frame.color.format.data_type == JXL_TYPE_FLOAT;
    JXL_RETURN_IF_ERROR(ConvertFromExternal(
        span, frame.color.xsize, frame.color.ysize,
        io->metadata.m.color_encoding,
        /*has_alpha=*/frame.color.format.num_channels == 2 ||
            frame.color.format.num_channels == 4,
        /*alpha_is_premultiplied=*/ppf.info.alpha_premultiplied,
        frame_bits_per_sample, frame.color.format.endianness,
        /*flipped_y=*/frame.color.flipped_y, pool, &bundle,
        /*float_in=*/float_in, /*align=*/0));

    for (const auto& ppf_ec : frame.extra_channels) {
      bundle.extra_channels().emplace_back(ppf_ec.xsize, ppf_ec.ysize);
      JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize,
                               ppf_ec.pixels(), ppf_ec.pixels_size, pool,
                               &bundle.extra_channels().back()));
    }

    io->frames.push_back(std::move(bundle));
    io->dec_pixels += frame.color.xsize * frame.color.ysize;
  }

  if (ppf.info.exponent_bits_per_sample == 0) {
    // uint case.
    io->metadata.m.bit_depth.bits_per_sample = io->Main().DetectRealBitdepth();
  }
  if (ppf.info.intensity_target != 0) {
    io->metadata.m.SetIntensityTarget(ppf.info.intensity_target);
  } else {
    SetIntensityTarget(io);
  }
  io->CheckMetadata();
  return true;
}

// Allows converting from internal CodecInOut to external PackedPixelFile
Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
                                          const JxlPixelFormat& pixel_format,
                                          const ColorEncoding& c_desired,
                                          ThreadPool* pool,
                                          PackedPixelFile* ppf) {
  const bool has_alpha = io.metadata.m.HasAlpha();
  bool alpha_premultiplied = false;
  JXL_ASSERT(!io.frames.empty());

  if (has_alpha) {
    JXL_ASSERT(io.metadata.m.GetAlphaBits() ==
               io.metadata.m.bit_depth.bits_per_sample);
    const auto* alpha_channel = io.metadata.m.Find(ExtraChannel::kAlpha);
    JXL_ASSERT(alpha_channel->bit_depth.exponent_bits_per_sample ==
               io.metadata.m.bit_depth.exponent_bits_per_sample);
    alpha_premultiplied = alpha_channel->alpha_associated;
  }

  // Convert the image metadata
  ppf->info.xsize = io.metadata.size.xsize();
  ppf->info.ysize = io.metadata.size.ysize();
  ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels();
  ppf->info.bits_per_sample = io.metadata.m.bit_depth.bits_per_sample;
  ppf->info.exponent_bits_per_sample =
      io.metadata.m.bit_depth.exponent_bits_per_sample;

  ppf->info.alpha_bits = io.metadata.m.GetAlphaBits();
  ppf->info.alpha_premultiplied = alpha_premultiplied;

  ppf->info.uses_original_profile = !io.metadata.m.xyb_encoded;
  JXL_ASSERT(0 < io.metadata.m.orientation && io.metadata.m.orientation <= 8);
  ppf->info.orientation =
      static_cast<JxlOrientation>(io.metadata.m.orientation);
  ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels();

  // Convert animation metadata
  JXL_ASSERT(io.frames.size() == 1 || io.metadata.m.have_animation);
  ppf->info.have_animation = io.metadata.m.have_animation;
  ppf->info.animation.tps_numerator = io.metadata.m.animation.tps_numerator;
  ppf->info.animation.tps_denominator = io.metadata.m.animation.tps_denominator;
  ppf->info.animation.num_loops = io.metadata.m.animation.num_loops;

  // Convert the color encoding
  ppf->icc.assign(c_desired.ICC().begin(), c_desired.ICC().end());
  if (ppf->icc.empty()) {
    ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding);
  }

  // Convert the extra blobs
  ppf->metadata.exif.assign(io.blobs.exif.begin(), io.blobs.exif.end());
  ppf->metadata.iptc.assign(io.blobs.iptc.begin(), io.blobs.iptc.end());
  ppf->metadata.jumbf.assign(io.blobs.jumbf.begin(), io.blobs.jumbf.end());
  ppf->metadata.xmp.assign(io.blobs.xmp.begin(), io.blobs.xmp.end());
  const bool float_out = pixel_format.data_type == JXL_TYPE_FLOAT ||
                         pixel_format.data_type == JXL_TYPE_FLOAT16;
  // Convert the pixels
  ppf->frames.clear();
  for (const auto& frame : io.frames) {
    size_t frame_bits_per_sample = frame.metadata()->bit_depth.bits_per_sample;
    JXL_ASSERT(frame_bits_per_sample != 0);
    // It is ok for the frame.color().kNumPlanes to not match the
    // number of channels on the image.
    const uint32_t num_channels =
        frame.metadata()->color_encoding.Channels() + has_alpha;
    JxlPixelFormat format{/*num_channels=*/num_channels,
                          /*data_type=*/pixel_format.data_type,
                          /*endianness=*/pixel_format.endianness,
                          /*align=*/pixel_format.align};

    PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(),
                             format);
    const size_t bits_per_sample =
        packed_frame.color.BitsPerChannel(pixel_format.data_type);
    packed_frame.name = frame.name;
    packed_frame.frame_info.name_length = frame.name.size();
    // Color transform
    ImageBundle ib = frame.Copy();
    const ImageBundle* to_color_transform = &ib;
    ImageMetadata metadata = io.metadata.m;
    ImageBundle store(&metadata);
    const ImageBundle* transformed;
    // TODO(firsching): handle the transform here.
    JXL_RETURN_IF_ERROR(TransformIfNeeded(*to_color_transform, c_desired,
                                          GetJxlCms(), pool, &store,
                                          &transformed));
    size_t stride = ib.oriented_xsize() *
                    (c_desired.Channels() * ppf->info.bits_per_sample) /
                    kBitsPerByte;
    PaddedBytes pixels(stride * ib.oriented_ysize());

    JXL_RETURN_IF_ERROR(ConvertToExternal(
        *transformed, bits_per_sample, float_out, format.num_channels,
        format.endianness,
        /* stride_out=*/packed_frame.color.stride, pool,
        packed_frame.color.pixels(), packed_frame.color.pixels_size,
        /*out_callback=*/nullptr, /*out_opaque=*/nullptr,
        frame.metadata()->GetOrientation()));

    // TODO(firsching): Convert the extra channels, beside one potential alpha
    // channel. FIXME!
    JXL_CHECK(frame.extra_channels().size() <= has_alpha);
    ppf->frames.push_back(std::move(packed_frame));
  }

  return true;
}
}  // namespace extras
}  // namespace jxl
<|endoftext|>