<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 {
            name = btnPath.baseName();
        }
        auto btn = new PathButton(name.get(), displayName ? QString::fromUtf8(displayName.get()) : QString::fromUtf8(name.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>Corrected path button "hidden" names<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);
    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
<|endoftext|>


<commit_before>/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkPLYReader.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkPLYReader.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkPointData.h"
#include "vtkFloatArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPLY.h"
#include "vtkPolyData.h"

#include <ctype.h>
#include <stddef.h>

vtkStandardNewMacro(vtkPLYReader);

// Construct object with merging set to true.
vtkPLYReader::vtkPLYReader()
{
  this->FileName = NULL;

  this->SetNumberOfInputPorts(0);
}

vtkPLYReader::~vtkPLYReader()
{
  delete [] this->FileName;
}

typedef struct _plyVertex {
  float x[3];             // the usual 3-space position of a vertex
  unsigned char red;
  unsigned char green;
  unsigned char blue;
} plyVertex;

typedef struct _plyFace {
  unsigned char intensity; // optional face attributes
  unsigned char red;
  unsigned char green;
  unsigned char blue;
  unsigned char nverts;   // number of vertex indices in list
  int *verts;             // vertex index list
} plyFace;

int vtkPLYReader::RequestData(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **vtkNotUsed(inputVector),
  vtkInformationVector *outputVector)
{
  // get the info object
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // get the ouptut
  vtkPolyData *output = vtkPolyData::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  PlyProperty vertProps[] = {
    {"x", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,x)),
     0, 0, 0, 0},
    {"y", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,x)+sizeof(float)),
     0, 0, 0, 0},
    {"z", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,x)+sizeof(float)+sizeof(float)),
     0, 0, 0, 0},
    {"red", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyVertex,red)), 0, 0, 0, 0},
    {"green", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyVertex,green)), 0, 0, 0, 0},
    {"blue", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyVertex,blue)), 0, 0, 0, 0},
  };
  PlyProperty faceProps[] = {
    {"vertex_indices", PLY_INT, PLY_INT,
     static_cast<int>(offsetof(plyFace,verts)),
     1, PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyFace,nverts))},
    {"intensity", PLY_UCHAR, PLY_UCHAR,
     static_cast<int>(offsetof(plyFace,intensity)), 0, 0, 0, 0},
    {"red", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyFace,red)), 0, 0, 0, 0},
    {"green", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyFace,green)), 0, 0, 0, 0},
    {"blue", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyFace,blue)), 0, 0, 0, 0},
  };

  if (!this->FileName)
    {
    vtkErrorMacro(<<"A File Name must be specified.");
    return 0;
    }

  // open a PLY file for reading
  PlyFile *ply;
  int nelems, fileType, numElems, nprops;
  char **elist, *elemName;
  float version;

  if ( !(ply = vtkPLY::ply_open_for_reading(this->FileName, &nelems, &elist,
                                            &fileType, &version)) )
    {
    vtkWarningMacro(<<"Could not open PLY file");
    return 0;
    }

  // Check to make sure that we can read geometry
  PlyElement *elem;
  int index;
  if ( (elem = vtkPLY::find_element (ply, "vertex")) == NULL ||
       vtkPLY::find_property (elem, "x", &index) == NULL ||
       vtkPLY::find_property (elem, "y", &index) == NULL ||
       vtkPLY::find_property (elem, "z", &index) == NULL ||
       (elem = vtkPLY::find_element (ply, "face")) == NULL ||
       vtkPLY::find_property (elem, "vertex_indices", &index) == NULL )
    {
    vtkErrorMacro(<<"Cannot read geometry");
    vtkPLY::ply_close (ply);
    }

  // Check for optional attribute data. We can handle intensity; and the
  // triplet red, green, blue.
  bool intensityAvailable=false;
  vtkUnsignedCharArray *intensity=NULL;
  if ( (elem = vtkPLY::find_element (ply, "face")) != NULL &&
       vtkPLY::find_property (elem, "intensity", &index) != NULL )
    {
    intensity = vtkUnsignedCharArray::New();
    intensity->SetName("intensity");
    intensityAvailable = true;
    output->GetCellData()->AddArray(intensity);
    output->GetCellData()->SetActiveScalars("intensity");
    intensity->Delete();
    }

  bool RGBCellsAvailable=false;
  vtkUnsignedCharArray *RGBCells=NULL;
  if ( (elem = vtkPLY::find_element (ply, "face")) != NULL &&
       vtkPLY::find_property (elem, "red", &index) != NULL &&
       vtkPLY::find_property (elem, "green", &index) != NULL &&
       vtkPLY::find_property (elem, "blue", &index) != NULL )
    {
    RGBCells = vtkUnsignedCharArray::New();
    RGBCells->SetName("RGB");
    RGBCellsAvailable = true;
    output->GetCellData()->AddArray(RGBCells);
    output->GetCellData()->SetActiveScalars("RGB");
    RGBCells->Delete();
    }

  bool RGBPointsAvailable=false;
  vtkUnsignedCharArray *RGBPoints=NULL;
  if ( (elem = vtkPLY::find_element (ply, "vertex")) != NULL &&
       vtkPLY::find_property (elem, "red", &index) != NULL &&
       vtkPLY::find_property (elem, "green", &index) != NULL &&
       vtkPLY::find_property (elem, "blue", &index) != NULL )
    {
    RGBPoints = vtkUnsignedCharArray::New();
    RGBPointsAvailable = true;
    RGBPoints->SetName("RGB");
    output->GetPointData()->SetScalars(RGBPoints);
    RGBPoints->Delete();
    }

  // Okay, now we can grab the data
  int numPts=0, numPolys=0;
  for (int i = 0; i < nelems; i++)
    {
    //get the description of the first element */
    elemName = elist[i];
    vtkPLY::ply_get_element_description (ply, elemName, &numElems, &nprops);

    // if we're on vertex elements, read them in
    if ( elemName && !strcmp ("vertex", elemName) )
      {
      // Create a list of points
      numPts = numElems;
      vtkPoints *pts = vtkPoints::New();
      pts->SetDataTypeToFloat();
      pts->SetNumberOfPoints(numPts);

      // Setup to read the PLY elements
      vtkPLY::ply_get_property (ply, elemName, &vertProps[0]);
      vtkPLY::ply_get_property (ply, elemName, &vertProps[1]);
      vtkPLY::ply_get_property (ply, elemName, &vertProps[2]);

      if ( RGBPointsAvailable )
        {
        vtkPLY::ply_get_property (ply, elemName, &vertProps[3]);
        vtkPLY::ply_get_property (ply, elemName, &vertProps[4]);
        vtkPLY::ply_get_property (ply, elemName, &vertProps[5]);
        RGBPoints->SetNumberOfComponents(3);
        RGBPoints->SetNumberOfTuples(numPts);
        }
      plyVertex vertex;
      for (int j=0; j < numPts; j++)
        {
        vtkPLY::ply_get_element (ply, (void *) &vertex);
        pts->SetPoint (j, vertex.x);
        if ( RGBPointsAvailable )
          {
          RGBPoints->SetTuple3(j,vertex.red,vertex.green,vertex.blue);
          }
        }
      output->SetPoints(pts);
      pts->Delete();
      }//if vertex

    else if ( elemName && !strcmp ("face", elemName) )
      {
      // Create a polygonal array
      numPolys = numElems;
      vtkCellArray *polys = vtkCellArray::New();
      polys->Allocate(polys->EstimateSize(numPolys,3),numPolys/2);
      plyFace face;
      int verts[256];
      vtkIdType vtkVerts[256];

      // Get the face properties
      vtkPLY::ply_get_property (ply, elemName, &faceProps[0]);
      if ( intensityAvailable )
        {
        vtkPLY::ply_get_property (ply, elemName, &faceProps[1]);
        RGBCells->SetNumberOfComponents(1);
        RGBCells->SetNumberOfTuples(numPolys);
        }
      if ( RGBCellsAvailable )
        {
        vtkPLY::ply_get_property (ply, elemName, &faceProps[2]);
        vtkPLY::ply_get_property (ply, elemName, &faceProps[3]);
        vtkPLY::ply_get_property (ply, elemName, &faceProps[4]);
        RGBCells->SetNumberOfComponents(3);
        RGBCells->SetNumberOfTuples(numPolys);
        }

      // grab all the face elements
      for (int j=0; j < numPolys; j++)
        {
        //grab and element from the file
        face.verts = verts;
        vtkPLY::ply_get_element (ply, (void *) &face);
        for (int k=0; k<face.nverts; k++)
          {
          vtkVerts[k] = face.verts[k];
          }
        polys->InsertNextCell(face.nverts,vtkVerts);
        if ( intensityAvailable )
          {
          intensity->SetValue(j,face.intensity);
          }
        if ( RGBCellsAvailable )
          {
          RGBCells->SetValue(3*j,face.red);
          RGBCells->SetValue(3*j+1,face.green);
          RGBCells->SetValue(3*j+2,face.blue);
          }
        }
      output->SetPolys(polys);
      polys->Delete();
      }//if face

    free(elist[i]); //allocated by ply_open_for_reading
    elist[i] = NULL;

    }//for all elements of the PLY file
  free(elist); //allocated by ply_open_for_reading

  vtkDebugMacro( <<"Read: " << numPts << " points, "
                 << numPolys << " polygons");

  // close the PLY file
  vtkPLY::ply_close (ply);

  return 1;
}

int vtkPLYReader::CanReadFile(const char *filename)
{
  FILE *fd = fopen(filename, "rb");
  if (!fd) return 0;

  char line[4] = {};
  const char *result = fgets(line, sizeof(line), fd);
  fclose(fd);
  return (result && strncmp(result, "ply", 3) == 0);
}

void vtkPLYReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);

  os << indent << "File Name: "
     << (this->FileName ? this->FileName : "(none)") << "\n";

}
<commit_msg>Add support for texture coordinates and per vertices normals in PLY Reader<commit_after>/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkPLYReader.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkPLYReader.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkPointData.h"
#include "vtkFloatArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPLY.h"
#include "vtkPolyData.h"
#include <vtkSmartPointer.h>

#include <ctype.h>
#include <stddef.h>

vtkStandardNewMacro(vtkPLYReader);

// Construct object with merging set to true.
vtkPLYReader::vtkPLYReader()
{
  this->FileName = NULL;

  this->SetNumberOfInputPorts(0);
}

vtkPLYReader::~vtkPLYReader()
{
  delete [] this->FileName;
}

typedef struct _plyVertex {
  float x[3];             // the usual 3-space position of a vertex
  float tex[2];
  float normal[3];
  unsigned char red;
  unsigned char green;
  unsigned char blue;
} plyVertex;

typedef struct _plyFace {
  unsigned char intensity; // optional face attributes
  unsigned char red;
  unsigned char green;
  unsigned char blue;
  unsigned char nverts;   // number of vertex indices in list
  int *verts;             // vertex index list
} plyFace;

int vtkPLYReader::RequestData(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **vtkNotUsed(inputVector),
  vtkInformationVector *outputVector)
{
  // get the info object
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // get the ouptut
  vtkPolyData *output = vtkPolyData::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  PlyProperty vertProps[] = {
    {"x", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,x)),
     0, 0, 0, 0},
    {"y", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,x)+sizeof(float)),
     0, 0, 0, 0},
    {"z", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,x)+sizeof(float)+sizeof(float)),
     0, 0, 0, 0},
    {"u", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,tex)),
     0, 0, 0, 0},
    {"v", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,tex)+sizeof(float)),
     0, 0, 0, 0},
    {"nx", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,normal)),
     0, 0, 0, 0},
    {"ny", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,normal)+sizeof(float)),
     0, 0, 0, 0},
    {"nz", PLY_FLOAT, PLY_FLOAT, static_cast<int>(offsetof(plyVertex,normal)+2*sizeof(float)),
     0, 0, 0, 0},
    {"red", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyVertex,red)), 0, 0, 0, 0},
    {"green", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyVertex,green)), 0, 0, 0, 0},
    {"blue", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyVertex,blue)), 0, 0, 0, 0},
  };
  PlyProperty faceProps[] = {
    {"vertex_indices", PLY_INT, PLY_INT,
     static_cast<int>(offsetof(plyFace,verts)),
     1, PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyFace,nverts))},
    {"intensity", PLY_UCHAR, PLY_UCHAR,
     static_cast<int>(offsetof(plyFace,intensity)), 0, 0, 0, 0},
    {"red", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyFace,red)), 0, 0, 0, 0},
    {"green", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyFace,green)), 0, 0, 0, 0},
    {"blue", PLY_UCHAR, PLY_UCHAR, static_cast<int>(offsetof(plyFace,blue)), 0, 0, 0, 0},
  };

  if (!this->FileName)
    {
    vtkErrorMacro(<<"A File Name must be specified.");
    return 0;
    }

  // open a PLY file for reading
  PlyFile *ply;
  int nelems, fileType, numElems, nprops;
  char **elist, *elemName;
  float version;

  if ( !(ply = vtkPLY::ply_open_for_reading(this->FileName, &nelems, &elist,
                                            &fileType, &version)) )
    {
    vtkWarningMacro(<<"Could not open PLY file");
    return 0;
    }
  std::cout << "PLY file type = " << fileType << std::endl;

  // Check to make sure that we can read geometry
  PlyElement *elem;
  int index;
  if ( (elem = vtkPLY::find_element (ply, "vertex")) == NULL ||
       vtkPLY::find_property (elem, "x", &index) == NULL ||
       vtkPLY::find_property (elem, "y", &index) == NULL ||
       vtkPLY::find_property (elem, "z", &index) == NULL ||
       (elem = vtkPLY::find_element (ply, "face")) == NULL ||
       vtkPLY::find_property (elem, "vertex_indices", &index) == NULL )
    {
    vtkErrorMacro(<<"Cannot read geometry");
    vtkPLY::ply_close (ply);
    }

  // Check for optional attribute data. We can handle intensity; and the
  // triplet red, green, blue.
  bool intensityAvailable = false;
  vtkUnsignedCharArray *intensity = NULL;
  if ( (elem = vtkPLY::find_element (ply, "face")) != NULL &&
       vtkPLY::find_property (elem, "intensity", &index) != NULL )
    {
    intensity = vtkUnsignedCharArray::New();
    intensity->SetName("intensity");
    intensityAvailable = true;
    output->GetCellData()->AddArray(intensity);
    output->GetCellData()->SetActiveScalars("intensity");
    intensity->Delete();
    }

  bool RGBCellsAvailable = false;
  vtkSmartPointer<vtkUnsignedCharArray> RGBCells = NULL;
  if ( (elem = vtkPLY::find_element (ply, "face")) != NULL &&
       vtkPLY::find_property (elem, "red", &index) != NULL &&
       vtkPLY::find_property (elem, "green", &index) != NULL &&
       vtkPLY::find_property (elem, "blue", &index) != NULL )
    {
    RGBCells = vtkSmartPointer<vtkUnsignedCharArray>::New();
    RGBCells->SetName("RGB");
    RGBCellsAvailable = true;
    output->GetCellData()->AddArray(RGBCells);
    output->GetCellData()->SetActiveScalars("RGB");
    }

  bool RGBPointsAvailable = false;
  vtkSmartPointer<vtkUnsignedCharArray> RGBPoints = NULL;
  if ( (elem = vtkPLY::find_element (ply, "vertex")) != NULL &&
       vtkPLY::find_property (elem, "red", &index) != NULL &&
       vtkPLY::find_property (elem, "green", &index) != NULL &&
       vtkPLY::find_property (elem, "blue", &index) != NULL )
    {
    RGBPoints = vtkUnsignedCharArray::New();
    RGBPointsAvailable = true;
    RGBPoints->SetName("RGB");
    RGBPoints->SetNumberOfComponents(3);
    output->GetPointData()->SetScalars(RGBPoints);
    }

  bool NormalPointsAvailable=false;
  vtkSmartPointer<vtkFloatArray> Normals = NULL;
  if ( (elem = vtkPLY::find_element (ply, "vertex")) != NULL &&
       vtkPLY::find_property (elem, "nx", &index) != NULL &&
       vtkPLY::find_property (elem, "ny", &index) != NULL &&
       vtkPLY::find_property (elem, "nz", &index) != NULL )
    {
    Normals = vtkSmartPointer<vtkFloatArray>::New();
    NormalPointsAvailable = true;
    Normals->SetName("Normals");
    Normals->SetNumberOfComponents(3);
    output->GetPointData()->SetNormals(Normals);
    }

  bool TexCoordsPointsAvailable = false;
  vtkSmartPointer<vtkFloatArray> TexCoordsPoints = NULL;
  if ( (elem = vtkPLY::find_element (ply, "vertex")) != NULL &&
        vtkPLY::find_property (elem, "u", &index) != NULL &&
        vtkPLY::find_property (elem, "v", &index) != NULL)
    {
    TexCoordsPoints = vtkSmartPointer<vtkFloatArray>::New();
    TexCoordsPointsAvailable = true;
    TexCoordsPoints->SetName("TCoords");
    TexCoordsPoints->SetNumberOfComponents(2);
    output->GetPointData()->SetTCoords(TexCoordsPoints);
    }

  // Okay, now we can grab the data
  int numPts = 0, numPolys = 0;
  for (int i = 0; i < nelems; i++)
    {
    //get the description of the first element */
    elemName = elist[i];
    vtkPLY::ply_get_element_description (ply, elemName, &numElems, &nprops);

    // if we're on vertex elements, read them in
    if ( elemName && !strcmp ("vertex", elemName) )
      {
      // Create a list of points
      numPts = numElems;
      vtkPoints *pts = vtkPoints::New();
      pts->SetDataTypeToFloat();
      pts->SetNumberOfPoints(numPts);

      // Setup to read the PLY elements
      vtkPLY::ply_get_property (ply, elemName, &vertProps[0]);
      vtkPLY::ply_get_property (ply, elemName, &vertProps[1]);
      vtkPLY::ply_get_property (ply, elemName, &vertProps[2]);

      if ( TexCoordsPointsAvailable )
        {
        vtkPLY::ply_get_property (ply, elemName, &vertProps[3]);
        vtkPLY::ply_get_property (ply, elemName, &vertProps[4]);
        TexCoordsPoints->SetNumberOfTuples(numPts);
        }

      if ( NormalPointsAvailable )
        {
        vtkPLY::ply_get_property (ply, elemName, &vertProps[5]);
        vtkPLY::ply_get_property (ply, elemName, &vertProps[6]);
        vtkPLY::ply_get_property (ply, elemName, &vertProps[7]);
        Normals->SetNumberOfTuples(numPts);
        }

      if ( RGBPointsAvailable )
        {
        vtkPLY::ply_get_property (ply, elemName, &vertProps[8]);
        vtkPLY::ply_get_property (ply, elemName, &vertProps[9]);
        vtkPLY::ply_get_property (ply, elemName, &vertProps[10]);
        RGBPoints->SetNumberOfTuples(numPts);
        }

      plyVertex vertex;
      for (int j=0; j < numPts; j++)
        {
        vtkPLY::ply_get_element (ply, (void *) &vertex);
        pts->SetPoint (j, vertex.x);
        if ( TexCoordsPointsAvailable )
          {
          TexCoordsPoints->SetTuple2(j, vertex.tex[0], vertex.tex[1]);
          }
        if ( NormalPointsAvailable )
          {
          Normals->SetTuple3(j, vertex.normal[0], vertex.normal[1], vertex.normal[2]);
          }
        if ( RGBPointsAvailable )
          {
          RGBPoints->SetTuple3(j, vertex.red, vertex.green, vertex.blue);
          }
        }
      output->SetPoints(pts);
      pts->Delete();
      }//if vertex

    else if ( elemName && !strcmp ("face", elemName) )
      {
      // Create a polygonal array
      numPolys = numElems;
      vtkCellArray *polys = vtkCellArray::New();
      polys->Allocate(polys->EstimateSize(numPolys,3),numPolys/2);
      plyFace face;
      int verts[256];
      vtkIdType vtkVerts[256];

      // Get the face properties
      vtkPLY::ply_get_property (ply, elemName, &faceProps[0]);
      if ( intensityAvailable )
        {
        vtkPLY::ply_get_property (ply, elemName, &faceProps[1]);
        RGBCells->SetNumberOfComponents(1);
        RGBCells->SetNumberOfTuples(numPolys);
        }
      if ( RGBCellsAvailable )
        {
        vtkPLY::ply_get_property (ply, elemName, &faceProps[2]);
        vtkPLY::ply_get_property (ply, elemName, &faceProps[3]);
        vtkPLY::ply_get_property (ply, elemName, &faceProps[4]);
        RGBCells->SetNumberOfComponents(3);
        RGBCells->SetNumberOfTuples(numPolys);
        }

      // grab all the face elements
      for (int j=0; j < numPolys; j++)
        {
        //grab and element from the file
        face.verts = verts;
        vtkPLY::ply_get_element (ply, (void *) &face);
        for (int k=0; k < face.nverts; k++)
          {
          vtkVerts[k] = face.verts[k];
          }
        polys->InsertNextCell(face.nverts,vtkVerts);
        if ( intensityAvailable )
          {
          intensity->SetValue(j,face.intensity);
          }
        if ( RGBCellsAvailable )
          {
          RGBCells->SetValue(3*j,face.red);
          RGBCells->SetValue(3*j+1,face.green);
          RGBCells->SetValue(3*j+2,face.blue);
          }
        }
      output->SetPolys(polys);
      polys->Delete();
      }//if face

    free(elist[i]); //allocated by ply_open_for_reading
    elist[i] = NULL;

    }//for all elements of the PLY file
  free(elist); //allocated by ply_open_for_reading

  vtkDebugMacro( <<"Read: " << numPts << " points, "
                 << numPolys << " polygons");

  // close the PLY file
  vtkPLY::ply_close (ply);

  return 1;
}

int vtkPLYReader::CanReadFile(const char *filename)
{
  FILE *fd = fopen(filename, "rb");
  if (!fd) return 0;

  char line[4] = {};
  const char *result = fgets(line, sizeof(line), fd);
  fclose(fd);
  return (result && strncmp(result, "ply", 3) == 0);
}

void vtkPLYReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);

  os << indent << "File Name: "
     << (this->FileName ? this->FileName : "(none)") << "\n";
}
<|endoftext|>


<commit_before>/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkUGFacetReader.cxx
  Language:  C++
  Date:      $Date$
  Version:   $Revision$

Copyright (c) 1993-2001 Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 * Neither name of Ken Martin, Will Schroeder, or Bill Lorensen nor the names
   of any contributors may be used to endorse or promote products derived
   from this software without specific prior written permission.

 * Modified source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

=========================================================================*/
#include "vtkUGFacetReader.h"
#include "vtkByteSwap.h"
#include "vtkMergePoints.h"
#include "vtkObjectFactory.h"

//------------------------------------------------------------------------------
vtkUGFacetReader* vtkUGFacetReader::New()
{
  // First try to create the object from the vtkObjectFactory
  vtkObject* ret = vtkObjectFactory::CreateInstance("vtkUGFacetReader");
  if(ret)
    {
    return (vtkUGFacetReader*)ret;
    }
  // If the factory was unable to create the object, then create it here.
  return new vtkUGFacetReader;
}

// Construct object to extract all parts, and with point merging
// turned on.
vtkUGFacetReader::vtkUGFacetReader()
{
  this->FileName = NULL;
  this->PartColors = NULL;
  this->PartNumber = (-1); //extract all parts

  this->Merging = 1;
  this->Locator = NULL;
}

vtkUGFacetReader::~vtkUGFacetReader()
{
  if ( this->FileName )
    {
    delete [] this->FileName;
    }
  if ( this->PartColors )
    {
    this->PartColors->Delete();
    }
  if (this->Locator != NULL)
    {
    this->Locator->UnRegister(this);
    this->Locator = NULL;
    }
}

// Overload standard modified time function. If locator is modified,
// then this object is modified as well.
unsigned long vtkUGFacetReader::GetMTime()
{
  unsigned long mTime1=this->vtkPolyDataSource::GetMTime();
  unsigned long mTime2;

  if (this->Locator)
    {
    mTime2 = this->Locator->GetMTime();
    mTime1 = ( mTime1 > mTime2 ? mTime1 : mTime2 );
    }

  return mTime1;
}

void vtkUGFacetReader::Execute()
{
  FILE *fp;
  char header[36];
  struct {float  v1[3], v2[3], v3[3], n1[3], n2[3], n3[3];} facet;
  vtkIdType ptId[3];
  short ugiiColor, direction;
  int numberTris, numFacetSets, setNumber, facetNumber;
  vtkPoints *newPts, *mergedPts;
  vtkNormals *newNormals, *mergedNormals;
  vtkCellArray *newPolys, *mergedPolys;
  vtkPolyData *output = this->GetOutput();
  fpos_t pos;
  int triEstimate;

  vtkDebugMacro(<<"Reading UG facet file...");
  if ( this->FileName == NULL )
    {
    vtkErrorMacro(<<"No FileName specified...please specify one.");
    return;
    }

  // open the file
  if ( (fp = fopen(this->FileName, "rb")) == NULL)
    {
    vtkErrorMacro(<<"Cannot open file specified.");
    return;
    }

  // read the header stuff
  if ( fread (header, 1, 2, fp) <= 0 ||
  fread (&numFacetSets, 4, 1, fp) <= 0 ||
  fread (header, 1, 36, fp) <= 0 )
    {
    vtkErrorMacro(<<"File ended prematurely");
    return;
    }

  // swap bytes since this is a binary file format
  vtkByteSwap::Swap4BE(&numFacetSets);

  // Estimate how much space we need - find out the size of the
  // file and divide by 72 bytes per triangle
  fgetpos( fp, &pos );
  fseek( fp, 0L, SEEK_END );
  triEstimate = ftell( fp ) / 72;
  fsetpos( fp, &pos );

  // allocate memory
  if ( ! this->PartColors )
    {
    this->PartColors = vtkShortArray::New();
    this->PartColors->Allocate(100);
    }
  else
    {
    this->PartColors->Reset();
    }

  newPts = vtkPoints::New();
  newPts->Allocate(triEstimate,triEstimate);
  newNormals = vtkNormals::New();
  newNormals->Allocate(triEstimate,triEstimate);
  newPolys = vtkCellArray::New();
  newPolys->Allocate(newPolys->EstimateSize(triEstimate,3),triEstimate);

  // loop over all facet sets, extracting triangles
  for (setNumber=0; setNumber < numFacetSets; setNumber++)
    {

    if ( fread (&ugiiColor, 2, 1, fp) <= 0 ||
    fread (&direction, 2, 1, fp) <= 0 ||
    fread (&numberTris, 4, 1, fp) <= 0 )
      {
      vtkErrorMacro(<<"File ended prematurely");
      break;
      }

    // swap bytes if necc
    vtkByteSwap::Swap4BE(&numberTris);
    vtkByteSwap::Swap2BERange(&ugiiColor,1);
    vtkByteSwap::Swap2BERange(&direction,1);

    this->PartColors->InsertNextValue(ugiiColor);

    for (facetNumber=0; facetNumber < numberTris; facetNumber++)
      {
      if ( fread(&facet,72,1,fp) <= 0 )
        {
        vtkErrorMacro(<<"File ended prematurely");
        break;
        }

      // swap bytes if necc
      vtkByteSwap::Swap4BERange((float *)(&facet),18);

      if ( this->PartNumber == -1 || this->PartNumber == setNumber )
        {
        ptId[0] = newPts->InsertNextPoint(facet.v1);
        ptId[1] = newPts->InsertNextPoint(facet.v2);
        ptId[2] = newPts->InsertNextPoint(facet.v3);

        newNormals->InsertNormal(ptId[0],facet.n1);
        newNormals->InsertNormal(ptId[1],facet.n2);
        newNormals->InsertNormal(ptId[2],facet.n3);

        newPolys->InsertNextCell(3,ptId);
        }//if appropriate part
      }//for all facets in this set
    }//for this facet set

  // update output
  vtkDebugMacro(<<"Read "
                << newPts->GetNumberOfPoints() << " points, "
                << newPolys->GetNumberOfCells() << " triangles.");

  fclose(fp);

  //
  // Merge points/triangles if requested
  //
  if ( this->Merging )
    {
    int i;
    vtkIdType *pts, nodes[3], npts;
    float *x;

    mergedPts = vtkPoints::New();
    mergedPts->Allocate(newPts->GetNumberOfPoints()/3);
    mergedNormals = vtkNormals::New();
    mergedNormals->Allocate(newNormals->GetNumberOfNormals()/3);
    mergedPolys = vtkCellArray::New();
    mergedPolys->Allocate(newPolys->GetSize());

    if ( this->Locator == NULL )
      {
      this->CreateDefaultLocator();
      }
    this->Locator->InitPointInsertion (mergedPts, newPts->GetBounds());

    for (newPolys->InitTraversal(); newPolys->GetNextCell(npts,pts); )
      {
      for (i=0; i < 3; i++)
        {
        x = newPts->GetPoint(pts[i]);
        if ( this->Locator->InsertUniquePoint(x, nodes[i]) )
          {
          mergedNormals->InsertNormal(nodes[i],newNormals->GetNormal(pts[i]));
          }
        }

      if ( nodes[0] != nodes[1] && nodes[0] != nodes[2] &&
      nodes[1] != nodes[2] )
	{
        mergedPolys->InsertNextCell(3,nodes);
	}
      }

      newPts->Delete();
      newNormals->Delete();
      newPolys->Delete();

      vtkDebugMacro(<< "Merged to: "
                   << mergedPts->GetNumberOfPoints() << " points, "
                   << mergedPolys->GetNumberOfCells() << " triangles");
    }
  else
    {
    mergedPts = newPts;
    mergedNormals = newNormals;
    mergedPolys = newPolys;
    }
//
// Update ourselves
//
  output->SetPoints(mergedPts);
  mergedPts->Delete();

  output->GetPointData()->SetNormals(mergedNormals);
  mergedNormals->Delete();

  output->SetPolys(mergedPolys);
  mergedPolys->Delete();

  if (this->Locator)
    {
    this->Locator->Initialize(); //free storage
    }

  output->Squeeze();
}

int vtkUGFacetReader::GetNumberOfParts()
{
  char header[36];
  FILE *fp;
  int numberOfParts;

  if ( this->FileName == NULL )
    {
    vtkErrorMacro(<<"No FileName specified...please specify one.");
    return 0;
    }

  // open the file
  if ( (fp = fopen(this->FileName, "rb")) == NULL)
    {
    vtkErrorMacro(<<"Cannot open file specified.");
    return 0;
    }

  // read the header stuff
  if ( fread (header, 1, 2, fp) <= 0 ||
  fread (&numberOfParts, 4, 1, fp) <= 0 ||
  fread (header, 1, 36, fp) <= 0 )
    {
    vtkErrorMacro(<<"File ended prematurely");
    fclose(fp);
    return 0;
    }

  // swap bytes if necc
  vtkByteSwap::Swap4BE(&numberOfParts);

  fclose(fp);
  return numberOfParts;
}

// Retrieve color index for the parts in the file.
short vtkUGFacetReader::GetPartColorIndex(int partId)
{
  if ( this->PartColors == NULL )
    {
    this->Update();
    }

  if ( !this->PartColors ||
  partId < 0 || partId > this->PartColors->GetMaxId() )
    {
    return 0;
    }
  else
    {
    return this->PartColors->GetValue(partId);
    }
}

// Specify a spatial locator for merging points. By
// default an instance of vtkMergePoints is used.
void vtkUGFacetReader::SetLocator(vtkPointLocator *locator)
{
  if ( this->Locator == locator )
    {
    return;
    }
  if (this->Locator != NULL)
    {
    this->Locator->UnRegister(this);
    this->Locator = NULL;
    }
  if (locator != NULL)
    {
    locator->Register(this);
    }
  this->Locator = locator;
  this->Modified();
}

void vtkUGFacetReader::CreateDefaultLocator()
{
  if ( this->Locator == NULL )
    {
    this->Locator = vtkMergePoints::New();
    }
}

void vtkUGFacetReader::PrintSelf(ostream& os, vtkIndent indent)
{
  vtkPolyDataSource::PrintSelf(os,indent);

  os << indent << "File Name: "
     << (this->FileName ? this->FileName : "(none)") << "\n";

  os << indent << "Part Number: " << this->PartNumber << "\n";

  os << indent << "Merging: " << (this->Merging ? "On\n" : "Off\n");
  if ( this->Locator )
    {
    os << indent << "Locator: " << this->Locator << "\n";
    }
  else
    {
    os << indent << "Locator: (none)\n";
    }
}

<commit_msg>better checking<commit_after>/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkUGFacetReader.cxx
  Language:  C++
  Date:      $Date$
  Version:   $Revision$

Copyright (c) 1993-2001 Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 * Neither name of Ken Martin, Will Schroeder, or Bill Lorensen nor the names
   of any contributors may be used to endorse or promote products derived
   from this software without specific prior written permission.

 * Modified source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

=========================================================================*/
#include "vtkUGFacetReader.h"
#include "vtkByteSwap.h"
#include "vtkMergePoints.h"
#include "vtkObjectFactory.h"

//------------------------------------------------------------------------------
vtkUGFacetReader* vtkUGFacetReader::New()
{
  // First try to create the object from the vtkObjectFactory
  vtkObject* ret = vtkObjectFactory::CreateInstance("vtkUGFacetReader");
  if(ret)
    {
    return (vtkUGFacetReader*)ret;
    }
  // If the factory was unable to create the object, then create it here.
  return new vtkUGFacetReader;
}

// Construct object to extract all parts, and with point merging
// turned on.
vtkUGFacetReader::vtkUGFacetReader()
{
  this->FileName = NULL;
  this->PartColors = NULL;
  this->PartNumber = (-1); //extract all parts

  this->Merging = 1;
  this->Locator = NULL;
}

vtkUGFacetReader::~vtkUGFacetReader()
{
  if ( this->FileName )
    {
    delete [] this->FileName;
    }
  if ( this->PartColors )
    {
    this->PartColors->Delete();
    }
  if (this->Locator != NULL)
    {
    this->Locator->UnRegister(this);
    this->Locator = NULL;
    }
}

// Overload standard modified time function. If locator is modified,
// then this object is modified as well.
unsigned long vtkUGFacetReader::GetMTime()
{
  unsigned long mTime1=this->vtkPolyDataSource::GetMTime();
  unsigned long mTime2;

  if (this->Locator)
    {
    mTime2 = this->Locator->GetMTime();
    mTime1 = ( mTime1 > mTime2 ? mTime1 : mTime2 );
    }

  return mTime1;
}

void vtkUGFacetReader::Execute()
{
  FILE *fp;
  char header[36];
  struct {float  v1[3], v2[3], v3[3], n1[3], n2[3], n3[3];} facet;
  vtkIdType ptId[3];
  short ugiiColor, direction;
  int numberTris, numFacetSets, setNumber, facetNumber;
  vtkPoints *newPts, *mergedPts;
  vtkNormals *newNormals, *mergedNormals;
  vtkCellArray *newPolys, *mergedPolys;
  vtkPolyData *output = this->GetOutput();
  fpos_t pos;
  int triEstimate;

  vtkDebugMacro(<<"Reading UG facet file...");
  if ( this->FileName == NULL || strlen(this->FileName) == 0)
    {
    vtkErrorMacro(<<"No FileName specified...please specify one.");
    return;
    }

  // open the file
  if ( (fp = fopen(this->FileName, "rb")) == NULL)
    {
    vtkErrorMacro(<<"Cannot open file specified.");
    return;
    }

  // read the header stuff
  if ( fread (header, 1, 2, fp) <= 0 ||
  fread (&numFacetSets, 4, 1, fp) <= 0 ||
  fread (header, 1, 36, fp) <= 0 )
    {
    vtkErrorMacro(<<"File ended prematurely");
    return;
    }

  // swap bytes since this is a binary file format
  vtkByteSwap::Swap4BE(&numFacetSets);

  // Estimate how much space we need - find out the size of the
  // file and divide by 72 bytes per triangle
  fgetpos( fp, &pos );
  fseek( fp, 0L, SEEK_END );
  triEstimate = ftell( fp ) / 72;
  fsetpos( fp, &pos );

  // allocate memory
  if ( ! this->PartColors )
    {
    this->PartColors = vtkShortArray::New();
    this->PartColors->Allocate(100);
    }
  else
    {
    this->PartColors->Reset();
    }

  newPts = vtkPoints::New();
  newPts->Allocate(triEstimate,triEstimate);
  newNormals = vtkNormals::New();
  newNormals->Allocate(triEstimate,triEstimate);
  newPolys = vtkCellArray::New();
  newPolys->Allocate(newPolys->EstimateSize(triEstimate,3),triEstimate);

  // loop over all facet sets, extracting triangles
  for (setNumber=0; setNumber < numFacetSets; setNumber++)
    {

    if ( fread (&ugiiColor, 2, 1, fp) <= 0 ||
    fread (&direction, 2, 1, fp) <= 0 ||
    fread (&numberTris, 4, 1, fp) <= 0 )
      {
      vtkErrorMacro(<<"File ended prematurely");
      break;
      }

    // swap bytes if necc
    vtkByteSwap::Swap4BE(&numberTris);
    vtkByteSwap::Swap2BERange(&ugiiColor,1);
    vtkByteSwap::Swap2BERange(&direction,1);

    this->PartColors->InsertNextValue(ugiiColor);

    for (facetNumber=0; facetNumber < numberTris; facetNumber++)
      {
      if ( fread(&facet,72,1,fp) <= 0 )
        {
        vtkErrorMacro(<<"File ended prematurely");
        break;
        }

      // swap bytes if necc
      vtkByteSwap::Swap4BERange((float *)(&facet),18);

      if ( this->PartNumber == -1 || this->PartNumber == setNumber )
        {
        ptId[0] = newPts->InsertNextPoint(facet.v1);
        ptId[1] = newPts->InsertNextPoint(facet.v2);
        ptId[2] = newPts->InsertNextPoint(facet.v3);

        newNormals->InsertNormal(ptId[0],facet.n1);
        newNormals->InsertNormal(ptId[1],facet.n2);
        newNormals->InsertNormal(ptId[2],facet.n3);

        newPolys->InsertNextCell(3,ptId);
        }//if appropriate part
      }//for all facets in this set
    }//for this facet set

  // update output
  vtkDebugMacro(<<"Read "
                << newPts->GetNumberOfPoints() << " points, "
                << newPolys->GetNumberOfCells() << " triangles.");

  fclose(fp);

  //
  // Merge points/triangles if requested
  //
  if ( this->Merging )
    {
    int i;
    vtkIdType *pts, nodes[3], npts;
    float *x;

    mergedPts = vtkPoints::New();
    mergedPts->Allocate(newPts->GetNumberOfPoints()/3);
    mergedNormals = vtkNormals::New();
    mergedNormals->Allocate(newNormals->GetNumberOfNormals()/3);
    mergedPolys = vtkCellArray::New();
    mergedPolys->Allocate(newPolys->GetSize());

    if ( this->Locator == NULL )
      {
      this->CreateDefaultLocator();
      }
    this->Locator->InitPointInsertion (mergedPts, newPts->GetBounds());

    for (newPolys->InitTraversal(); newPolys->GetNextCell(npts,pts); )
      {
      for (i=0; i < 3; i++)
        {
        x = newPts->GetPoint(pts[i]);
        if ( this->Locator->InsertUniquePoint(x, nodes[i]) )
          {
          mergedNormals->InsertNormal(nodes[i],newNormals->GetNormal(pts[i]));
          }
        }

      if ( nodes[0] != nodes[1] && nodes[0] != nodes[2] &&
      nodes[1] != nodes[2] )
	{
        mergedPolys->InsertNextCell(3,nodes);
	}
      }

      newPts->Delete();
      newNormals->Delete();
      newPolys->Delete();

      vtkDebugMacro(<< "Merged to: "
                   << mergedPts->GetNumberOfPoints() << " points, "
                   << mergedPolys->GetNumberOfCells() << " triangles");
    }
  else
    {
    mergedPts = newPts;
    mergedNormals = newNormals;
    mergedPolys = newPolys;
    }
//
// Update ourselves
//
  output->SetPoints(mergedPts);
  mergedPts->Delete();

  output->GetPointData()->SetNormals(mergedNormals);
  mergedNormals->Delete();

  output->SetPolys(mergedPolys);
  mergedPolys->Delete();

  if (this->Locator)
    {
    this->Locator->Initialize(); //free storage
    }

  output->Squeeze();
}

int vtkUGFacetReader::GetNumberOfParts()
{
  char header[36];
  FILE *fp;
  int numberOfParts;

  if ( this->FileName == NULL || strlen(this->FileName) == 0)
    {
    vtkErrorMacro(<<"No FileName specified...please specify one.");
    return 0;
    }

  // open the file
  if ( (fp = fopen(this->FileName, "rb")) == NULL)
    {
    vtkErrorMacro(<<"Cannot open file specified.");
    return 0;
    }

  // read the header stuff
  if ( fread (header, 1, 2, fp) <= 0 ||
  fread (&numberOfParts, 4, 1, fp) <= 0 ||
  fread (header, 1, 36, fp) <= 0 )
    {
    vtkErrorMacro(<<"File ended prematurely");
    fclose(fp);
    return 0;
    }

  // swap bytes if necc
  vtkByteSwap::Swap4BE(&numberOfParts);

  fclose(fp);
  return numberOfParts;
}

// Retrieve color index for the parts in the file.
short vtkUGFacetReader::GetPartColorIndex(int partId)
{
  if ( this->PartColors == NULL )
    {
    this->Update();
    }

  if ( !this->PartColors ||
  partId < 0 || partId > this->PartColors->GetMaxId() )
    {
    return 0;
    }
  else
    {
    return this->PartColors->GetValue(partId);
    }
}

// Specify a spatial locator for merging points. By
// default an instance of vtkMergePoints is used.
void vtkUGFacetReader::SetLocator(vtkPointLocator *locator)
{
  if ( this->Locator == locator )
    {
    return;
    }
  if (this->Locator != NULL)
    {
    this->Locator->UnRegister(this);
    this->Locator = NULL;
    }
  if (locator != NULL)
    {
    locator->Register(this);
    }
  this->Locator = locator;
  this->Modified();
}

void vtkUGFacetReader::CreateDefaultLocator()
{
  if ( this->Locator == NULL )
    {
    this->Locator = vtkMergePoints::New();
    }
}

void vtkUGFacetReader::PrintSelf(ostream& os, vtkIndent indent)
{
  vtkPolyDataSource::PrintSelf(os,indent);

  os << indent << "File Name: "
     << (this->FileName ? this->FileName : "(none)") << "\n";

  os << indent << "Part Number: " << this->PartNumber << "\n";

  os << indent << "Merging: " << (this->Merging ? "On\n" : "Off\n");
  if ( this->Locator )
    {
    os << indent << "Locator: " << this->Locator << "\n";
    }
  else
    {
    os << indent << "Locator: (none)\n";
    }
}

<|endoftext|>


<commit_before>/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "src/trace_processor/importers/proto/android_camera_event_module.h"

#include <sstream>

#include "include/perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/protozero/field.h"
#include "protos/perfetto/trace/android/camera_event.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/slice_tables.h"
#include "src/trace_processor/tables/track_tables.h"
#include "src/trace_processor/timestamped_trace_piece.h"
#include "src/trace_processor/trace_sorter.h"

namespace perfetto {
namespace trace_processor {

using perfetto::protos::pbzero::TracePacket;

AndroidCameraEventModule::AndroidCameraEventModule(
    TraceProcessorContext* context)
    : context_(context) {
  RegisterForField(TracePacket::kAndroidCameraFrameEventFieldNumber, context);
}

AndroidCameraEventModule::~AndroidCameraEventModule() = default;

ModuleResult AndroidCameraEventModule::TokenizePacket(
    const protos::pbzero::TracePacket::Decoder& decoder,
    TraceBlobView* packet,
    int64_t /*packet_timestamp*/,
    PacketSequenceState* state,
    uint32_t field_id) {
  if (field_id != TracePacket::kAndroidCameraFrameEventFieldNumber) {
    return ModuleResult::Ignored();
  }
  const auto android_camera_frame_event =
      protos::pbzero::AndroidCameraFrameEvent::Decoder(
          decoder.android_camera_frame_event());
  context_->sorter->PushTracePacket(
      android_camera_frame_event.request_processing_started_ns(), state,
      std::move(*packet));
  return ModuleResult::Handled();
}

void AndroidCameraEventModule::ParsePacket(const TracePacket::Decoder& decoder,
                                           const TimestampedTracePiece& /*ttp*/,
                                           uint32_t field_id) {
  if (field_id != TracePacket::kAndroidCameraFrameEventFieldNumber) {
    return;
  }
  InsertCameraFrameSlice(decoder.android_camera_frame_event());
}

void AndroidCameraEventModule::InsertCameraFrameSlice(
    protozero::ConstBytes bytes) {
  const auto android_camera_frame_event =
      protos::pbzero::AndroidCameraFrameEvent::Decoder(bytes);
  StringId track_name = context_->storage->InternString(
      base::StackString<32>("Camera %d Frames",
                            android_camera_frame_event.camera_id())
          .string_view());
  StringId slice_name = context_->storage->InternString(
      base::StackString<32>("Frame %" PRId64,
                            android_camera_frame_event.frame_number())
          .string_view());
  int64_t ts = android_camera_frame_event.request_processing_started_ns();
  int64_t dur = android_camera_frame_event.responses_all_sent_ns() -
                android_camera_frame_event.request_processing_started_ns();
  auto track_set_id =
      context_->async_track_set_tracker->InternGlobalTrackSet(track_name);
  auto track_id =
      context_->async_track_set_tracker->Scoped(track_set_id, ts, dur);
  context_->slice_tracker->Scoped(ts, track_id, /*category=*/kNullStringId,
                                  slice_name, dur);
}

}  // namespace trace_processor
}  // namespace perfetto
<commit_msg>tp: fix compile in G3 am: 92a6e426c4<commit_after>/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "src/trace_processor/importers/proto/android_camera_event_module.h"

#include "perfetto/ext/base/string_utils.h"
#include "protos/perfetto/trace/android/camera_event.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/timestamped_trace_piece.h"
#include "src/trace_processor/trace_sorter.h"

namespace perfetto {
namespace trace_processor {

using perfetto::protos::pbzero::TracePacket;

AndroidCameraEventModule::AndroidCameraEventModule(
    TraceProcessorContext* context)
    : context_(context) {
  RegisterForField(TracePacket::kAndroidCameraFrameEventFieldNumber, context);
}

AndroidCameraEventModule::~AndroidCameraEventModule() = default;

ModuleResult AndroidCameraEventModule::TokenizePacket(
    const protos::pbzero::TracePacket::Decoder& decoder,
    TraceBlobView* packet,
    int64_t /*packet_timestamp*/,
    PacketSequenceState* state,
    uint32_t field_id) {
  if (field_id != TracePacket::kAndroidCameraFrameEventFieldNumber) {
    return ModuleResult::Ignored();
  }
  const auto android_camera_frame_event =
      protos::pbzero::AndroidCameraFrameEvent::Decoder(
          decoder.android_camera_frame_event());
  context_->sorter->PushTracePacket(
      android_camera_frame_event.request_processing_started_ns(), state,
      std::move(*packet));
  return ModuleResult::Handled();
}

void AndroidCameraEventModule::ParsePacket(const TracePacket::Decoder& decoder,
                                           const TimestampedTracePiece& /*ttp*/,
                                           uint32_t field_id) {
  if (field_id != TracePacket::kAndroidCameraFrameEventFieldNumber) {
    return;
  }
  InsertCameraFrameSlice(decoder.android_camera_frame_event());
}

void AndroidCameraEventModule::InsertCameraFrameSlice(
    protozero::ConstBytes bytes) {
  const auto android_camera_frame_event =
      protos::pbzero::AndroidCameraFrameEvent::Decoder(bytes);
  StringId track_name = context_->storage->InternString(
      base::StackString<32>("Camera %d Frames",
                            android_camera_frame_event.camera_id())
          .string_view());
  StringId slice_name = context_->storage->InternString(
      base::StackString<32>("Frame %" PRId64,
                            android_camera_frame_event.frame_number())
          .string_view());
  int64_t ts = android_camera_frame_event.request_processing_started_ns();
  int64_t dur = android_camera_frame_event.responses_all_sent_ns() -
                android_camera_frame_event.request_processing_started_ns();
  auto track_set_id =
      context_->async_track_set_tracker->InternGlobalTrackSet(track_name);
  auto track_id =
      context_->async_track_set_tracker->Scoped(track_set_id, ts, dur);
  context_->slice_tracker->Scoped(ts, track_id, /*category=*/kNullStringId,
                                  slice_name, dur);
}

}  // namespace trace_processor
}  // namespace perfetto
<|endoftext|>


<commit_before>#ifndef INCLUDE_AL_BUFFER_HPP
#define INCLUDE_AL_BUFFER_HPP

/*
 *  AlloSphere Research Group / Media Arts & Technology, UCSB, 2009
 */

/*
	Copyright (C) 2006-2008. The Regents of the University of California (REGENTS).
	All Rights Reserved.

	Permission to use, copy, modify, distribute, and distribute modified versions
	of this software and its documentation without fee and without a signed
	licensing agreement, is hereby granted, provided that the above copyright
	notice, the list of contributors, this paragraph and the following two paragraphs
	appear in all copies, modifications, and distributions.

	IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
	SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
	OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS
	BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

	REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
	THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
	PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
	HEREUNDER IS PROVIDED "AS IS". REGENTS HAS  NO OBLIGATION TO PROVIDE
	MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*/

#include <vector>

namespace al{

/// Buffer

///
///
template <class T, class Alloc=std::allocator<T> >
class Buffer : protected Alloc{
	typedef Alloc super;
public:

	/// @param[in] size			Initial size
	explicit Buffer(int size=0)
	:	mSize(size), mFill(0), mPos(size-1), mElems(size)
	{}

	/// @param[in] size			Initial size
	/// @param[in] capacity		Initial capacity
	Buffer(int size, int capacity)
	:	mSize(size), mFill(0), mPos(size-1), mElems(capacity)
	{}

	~Buffer(){}

	int capacity() const { return mElems.capacity(); }	///< Returns total capacity
	int fill() const { return mFill; }					///< Returns buffer fill amount
	int pos() const { return mPos; }					///< Returns write position
	int size() const { return mSize; }					///< Returns size
	T * elems(){ return &mElems[0]; }					///< Returns C-style pointer to elements

	/// Set element at absolute index
	T& operator[](int i){ return atAbs(i); }

	/// Get element at absolute index
	const T& operator[](int i) const { return atAbs(i); }

	/// Assign value to elements

	/// This function fills a Buffer with n copies of the given value. Note that
	/// the assignment completely changes the buffer and that the resulting size
	/// is the same as the number of elements assigned.  Old data may be lost.
	void assign(int n, const T& v){ mElems.assign(n,v); }

	/// Set element at absolute index
	T& atAbs(int i){ return mElems[i]; }

	/// Get element at absolute index
	const T& atAbs(int i) const { return mElems[i]; }

	/// Set element at relative index
	T& atRel(int i){ return mElems[wrapOnce(pos()-i, size())]; }

	/// Get element at relative index
	const T& atRel(int i) const { return mElems[wrapOnce(pos()-i, size())]; }

	/// Get last element
	const T& last() const { return mElems[mPos-1]; }

	/// Clear
	void clear(){ mSize=mFill=0; mPos=-1; }

	/// Resize buffer

	/// This will set both the size and capacity of the buffer to the requested
	/// size. If the number is smaller than the current size the buffer is
	/// truncated, otherwise the buffer is extended and new elements are
	/// default-constructed.
	void resize(int n){
		mElems.resize(n);
		setSize(n);
	}

	/// Set size of buffer

	/// If the requested size is larger than the current capacity, then the
	/// buffer will be resized.
	void size(int n){
		if(capacity() < n) resize(n);
		else setSize(n);
	}

	/// Appends element to end of buffer growing its size if necessary
	void append(const T& v, double growFactor=2){
		if(size() >= capacity()){	// double array size if too small
			mElems.resize((size() ? size() : 4)*growFactor);
		}
		super::construct(elems()+size(), v);
		mPos=mSize;
		++mSize;
	}
	/// synonym for append():
	void push_back(const T& v, double growFactor=2) { append(v, growFactor); }

	/// Write new element to ring buffer
	void write(const T& v){
		++mPos; if(pos() == size()){ mPos=0; }
		Alloc::construct(elems()+pos(), v);
		if(fill() < size()) ++mFill;
	}

private:
	int mSize;		// number of elements in array
	int	mFill;		// number of elements written to buffer (up to size())
	int mPos;		// circular buffer write position
	std::vector<T, Alloc> mElems;

	void setSize(int n){
		mSize=n;
		if(mFill>=n) mFill = n-1;
		if(mPos >=n) mPos  = n-1;
	}

	// Moves value one period closer to interval [0, max)
	static int wrapOnce(int v, int max){
		if(v <  0)   return v+max;
		if(v >= max) return v-max;
		return v;
	}
};

template <int N, class T>
struct Multi{
	typedef Multi M;
//	Multi(){}
//	Multi(const T& e ){ mem::set(elems, N, e); }
//	Multi(const T* es){ mem::copy(elems, es, N); }

	T elems[N];

	/// Set element at index with no bounds checking
	T& operator[](int i){ return elems[i];}

	/// Get element at index with no bounds checking
	const T& operator[](int i) const { return elems[i]; }

	#define DO for(int i=0; i<N; ++i)

	bool operator !=(const M& v){ DO{ if((*this)[i] == v[i]) return false; } return true; }
	bool operator !=(const T& v){ DO{ if((*this)[i] == v   ) return false; } return true; }
	M& operator   = (const M& v){ DO{ (*this)[i] = v[i]; } return *this; }
	M& operator   = (const T& v){ DO{ (*this)[i] = v;    } return *this; }
	bool operator ==(const M& v){ DO{ if((*this)[i] != v[i]) return false; } return true; }
	bool operator ==(const T& v){ DO{ if((*this)[i] != v   ) return false; } return true; }

	#undef DO

	/// Returns size of array
	static int size(){ return N; }

	/// Zeros all elements.
	void zero(){ memset(elems, 0, N * sizeof(T)); }
};

/// Fixed size shift buffer
template <int N, class T>
struct ShiftBuffer : public Multi<N,T>{

	typedef Multi<N,T> Base;
	using Base::elems;
	using Base::operator=;

	ShiftBuffer(const T& v=T()){ *this = v; }

	/// Push new element onto buffer. Newest element is at index 0.
	void operator()(const T& v){
		for(int i=N-1; i>0; --i) elems[i] = elems[i-1];
		elems[0]=v;
	}
};

} // al::

#endif
<commit_msg>fixed invalid memory bug in Buffer::append<commit_after>#ifndef INCLUDE_AL_BUFFER_HPP
#define INCLUDE_AL_BUFFER_HPP

/*
 *  AlloSphere Research Group / Media Arts & Technology, UCSB, 2009
 */

/*
	Copyright (C) 2006-2008. The Regents of the University of California (REGENTS).
	All Rights Reserved.

	Permission to use, copy, modify, distribute, and distribute modified versions
	of this software and its documentation without fee and without a signed
	licensing agreement, is hereby granted, provided that the above copyright
	notice, the list of contributors, this paragraph and the following two paragraphs
	appear in all copies, modifications, and distributions.

	IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
	SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
	OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS
	BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

	REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
	THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
	PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
	HEREUNDER IS PROVIDED "AS IS". REGENTS HAS  NO OBLIGATION TO PROVIDE
	MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*/

#include <vector>

namespace al{

/// Buffer

///
///
template <class T, class Alloc=std::allocator<T> >
class Buffer : protected Alloc{
	typedef Alloc super;
public:

	/// @param[in] size			Initial size
	explicit Buffer(int size=0)
	:	mSize(size), mFill(0), mPos(size-1), mElems(size)
	{}

	/// @param[in] size			Initial size
	/// @param[in] capacity		Initial capacity
	Buffer(int size, int capacity)
	:	mSize(size), mFill(0), mPos(size-1), mElems(capacity)
	{}

	~Buffer(){}

	int capacity() const { return mElems.capacity(); }	///< Returns total capacity
	int fill() const { return mFill; }					///< Returns buffer fill amount
	int pos() const { return mPos; }					///< Returns write position
	int size() const { return mSize; }					///< Returns size
	T * elems(){ return &mElems[0]; }					///< Returns C-style pointer to elements

	/// Set element at absolute index
	T& operator[](int i){ return atAbs(i); }

	/// Get element at absolute index
	const T& operator[](int i) const { return atAbs(i); }

	/// Assign value to elements

	/// This function fills a Buffer with n copies of the given value. Note that
	/// the assignment completely changes the buffer and that the resulting size
	/// is the same as the number of elements assigned.  Old data may be lost.
	void assign(int n, const T& v){ mElems.assign(n,v); }

	/// Set element at absolute index
	T& atAbs(int i){ return mElems[i]; }

	/// Get element at absolute index
	const T& atAbs(int i) const { return mElems[i]; }

	/// Set element at relative index
	T& atRel(int i){ return mElems[wrapOnce(pos()-i, size())]; }

	/// Get element at relative index
	const T& atRel(int i) const { return mElems[wrapOnce(pos()-i, size())]; }

	/// Get last element
	const T& last() const { return mElems[mPos-1]; }

	/// Clear
	void clear(){ mSize=mFill=0; mPos=-1; }

	/// Resize buffer

	/// This will set both the size and capacity of the buffer to the requested
	/// size. If the number is smaller than the current size the buffer is
	/// truncated, otherwise the buffer is extended and new elements are
	/// default-constructed.
	void resize(int n){
		mElems.resize(n);
		setSize(n);
	}

	/// Set size of buffer

	/// If the requested size is larger than the current capacity, then the
	/// buffer will be resized.
	void size(int n){
		if(capacity() < n) resize(n);
		else setSize(n);
	}

	/// Appends element to end of buffer growing its size if necessary
	void append(const T& v, double growFactor=2){
		if(size() >= capacity()){	// double array size if too small
			// Copy argument since it may be an element in current memory range
			// which may become invalid after the resize.
			const T vsafecopy = v;
			mElems.resize((size() ? size() : 4)*growFactor);
			super::construct(elems()+size(), vsafecopy);
		}
		else{
			super::construct(elems()+size(), v);
		}
		mPos=size();
		++mSize;
	}
	/// synonym for append():
	void push_back(const T& v, double growFactor=2) { append(v, growFactor); }

	/// Write new element to ring buffer
	void write(const T& v){
		++mPos; if(pos() == size()){ mPos=0; }
		Alloc::construct(elems()+pos(), v);
		if(fill() < size()) ++mFill;
	}

private:
	int mSize;		// number of elements in array
	int	mFill;		// number of elements written to buffer (up to size())
	int mPos;		// circular buffer write position
	std::vector<T, Alloc> mElems;

	void setSize(int n){
		mSize=n;
		if(mFill>=n) mFill = n-1;
		if(mPos >=n) mPos  = n-1;
	}

	// Moves value one period closer to interval [0, max)
	static int wrapOnce(int v, int max){
		if(v <  0)   return v+max;
		if(v >= max) return v-max;
		return v;
	}
};

template <int N, class T>
struct Multi{
	typedef Multi M;
//	Multi(){}
//	Multi(const T& e ){ mem::set(elems, N, e); }
//	Multi(const T* es){ mem::copy(elems, es, N); }

	T elems[N];

	/// Set element at index with no bounds checking
	T& operator[](int i){ return elems[i];}

	/// Get element at index with no bounds checking
	const T& operator[](int i) const { return elems[i]; }

	#define DO for(int i=0; i<N; ++i)

	bool operator !=(const M& v){ DO{ if((*this)[i] == v[i]) return false; } return true; }
	bool operator !=(const T& v){ DO{ if((*this)[i] == v   ) return false; } return true; }
	M& operator   = (const M& v){ DO{ (*this)[i] = v[i]; } return *this; }
	M& operator   = (const T& v){ DO{ (*this)[i] = v;    } return *this; }
	bool operator ==(const M& v){ DO{ if((*this)[i] != v[i]) return false; } return true; }
	bool operator ==(const T& v){ DO{ if((*this)[i] != v   ) return false; } return true; }

	#undef DO

	/// Returns size of array
	static int size(){ return N; }

	/// Zeros all elements.
	void zero(){ memset(elems, 0, N * sizeof(T)); }
};

/// Fixed size shift buffer
template <int N, class T>
struct ShiftBuffer : public Multi<N,T>{

	typedef Multi<N,T> Base;
	using Base::elems;
	using Base::operator=;

	ShiftBuffer(const T& v=T()){ *this = v; }

	/// Push new element onto buffer. Newest element is at index 0.
	void operator()(const T& v){
		for(int i=N-1; i>0; --i) elems[i] = elems[i-1];
		elems[0]=v;
	}
};

} // al::

#endif
<|endoftext|>