<commit_before>/*
  Q Light Controller - Fixture Definition Editor
  qlcfixtureeditor.cpp

  Copyright (C) <NAME>

  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.txt

  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 <QTreeWidgetItem>
#include <QInputDialog>
#include <QHeaderView>
#include <QTreeWidget>
#include <QMessageBox>
#include <QToolButton>
#include <QFileDialog>
#include <QCloseEvent>
#include <QSettings>
#include <QComboBox>
#include <QLineEdit>
#include <QSpinBox>
#include <QTabWidget>
#include <QIcon>
#include <QMenu>
#include <QList>
#include <QUrl>

#include "qlcfixturedefcache.h"
#include "qlcfixturemode.h"
#include "qlcfixturedef.h"
#include "qlccapability.h"
#include "qlcphysical.h"
#include "qlcchannel.h"
#include "qlcconfig.h"
#include "qlcfile.h"

#ifdef Q_WS_X11
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#endif

#include "fixtureeditor.h"
#include "editphysical.h"
#include "editchannel.h"
#include "editmode.h"
#include "util.h"
#include "app.h"

extern App *_app;
extern int errno;

#define PROP_PTR Qt::UserRole

#define CH_COL_NAME 0
#define CH_COL_GRP  1

#define MODE_COL_NAME 0
#define MODE_COL_CHS  1
#define MODE_COL_HEAD 2

#define SETTINGS_GEOMETRY "fixtureeditor/geometry"

QLCFixtureEditor::QLCFixtureEditor(QWidget *parent, QLCFixtureDef *fixtureDef,
                                   const QString& fileName)
    : QWidget(parent)
    , m_fixtureDef(fixtureDef)
    , m_fileName(fileName)
    , m_modified(false)
{
    setupUi(this);
    init();
    setCaption();

    setModified(false);

    /* Connect to be able to enable/disable clipboard actions */
    connect(_app, SIGNAL(clipboardChanged()), this, SLOT(slotClipboardChanged()));

    /* Initial update to clipboard actions */
    slotClipboardChanged();

    QSettings settings;
    QVariant var = settings.value(SETTINGS_GEOMETRY);
    if (var.isValid() == true)
        parentWidget()->restoreGeometry(var.toByteArray());
}

QLCFixtureEditor::~QLCFixtureEditor()
{
    QSettings settings;
    settings.setValue(SETTINGS_GEOMETRY, parentWidget()->saveGeometry());

    delete m_fixtureDef;
    m_fixtureDef = NULL;
}

void QLCFixtureEditor::init()
{
    /* General page */
    m_manufacturerEdit->setText(m_fixtureDef->manufacturer());
    m_manufacturerEdit->setValidator(CAPS_VALIDATOR(this));
    connect(m_manufacturerEdit, SIGNAL(textEdited(const QString&)),
            this, SLOT(slotManufacturerTextEdited(const QString&)));

    m_modelEdit->setText(m_fixtureDef->model());
    m_modelEdit->setValidator(CAPS_VALIDATOR(this));
    connect(m_modelEdit, SIGNAL(textEdited(const QString&)),
            this, SLOT(slotModelTextEdited(const QString&)));

    m_typeCombo->setCurrentIndex(m_typeCombo->findText(m_fixtureDef->typeToString(m_fixtureDef->type())));
    connect(m_typeCombo, SIGNAL(activated(const QString&)),
            this, SLOT(slotTypeActivated(const QString&)));

    // Display author name or suggest current user name if there isn't one.
    // When the def already has an author, disable the field to prevent modification.
    m_authorEdit->setText(m_fixtureDef->author());
    if (m_authorEdit->text().length() > 0)
    {
        // Temporarily allow editing author name since most definitions contain wrong name:
        // m_authorEdit->setEnabled(false);
    }
    else
    {
        m_authorEdit->setText(QLCFile::currentUserName());
    }
    connect(m_authorEdit, SIGNAL(textEdited(const QString&)),
            this, SLOT(slotAuthorTextEdited(const QString&)));

    /* Channel page */
    connect(m_addChannelButton, SIGNAL(clicked()), this, SLOT(slotAddChannel()));
    connect(m_removeChannelButton, SIGNAL(clicked()), this, SLOT(slotRemoveChannel()));
    connect(m_editChannelButton, SIGNAL(clicked()), this, SLOT(slotEditChannel()));
    connect(m_copyChannelButton, SIGNAL(clicked()), this, SLOT(slotCopyChannel()));
    connect(m_pasteChannelButton, SIGNAL(clicked()), this, SLOT(slotPasteChannel()));
    connect(m_expandChannelsButton, SIGNAL(clicked()), this, SLOT(slotExpandChannels()));

    connect(m_channelList, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
            this, SLOT(slotChannelListSelectionChanged(QTreeWidgetItem*)));
    connect(m_channelList, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotChannelListContextMenuRequested()));
    connect(m_channelList, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
            this, SLOT(slotEditChannel()));
    connect(m_channelList, SIGNAL(expanded(QModelIndex)),
            this, SLOT(slotChannelItemExpanded()));

    m_channelList->setContextMenuPolicy(Qt::CustomContextMenu);
    m_channelList->setIconSize(QSize(24, 24));
    refreshChannelList();

    /* Mode page */
    connect(m_addModeButton, SIGNAL(clicked()), this, SLOT(slotAddMode()));
    connect(m_removeModeButton, SIGNAL(clicked()), this, SLOT(slotRemoveMode()));
    connect(m_editModeButton, SIGNAL(clicked()), this, SLOT(slotEditMode()));
    connect(m_cloneModeButton, SIGNAL(clicked()), this, SLOT(slotCloneMode()));
    connect(m_expandModesButton, SIGNAL(clicked()), this, SLOT(slotExpandModes()));

    connect(m_modeList, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
            this, SLOT(slotModeListSelectionChanged(QTreeWidgetItem*)));
    connect(m_modeList, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotModeListContextMenuRequested()));
    connect(m_modeList, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
            this, SLOT(slotEditMode()));
    connect(m_modeList, SIGNAL(expanded(QModelIndex)),
            this, SLOT(slotModeItemExpanded()));

    m_modeList->setContextMenuPolicy(Qt::CustomContextMenu);
    refreshModeList();

    /* Aliases page */
    connect(m_addAliasButton, SIGNAL(clicked()), this, SLOT(slotAddAliasClicked()));
    connect(m_removeAliasButton, SIGNAL(clicked()), this, SLOT(slotRemoveAliasClicked()));
    refreshAliasList();
    refreshAliasModes();
    refreshAliasModeChannels();
    refreshAliasAllChannels();
    refreshAliasTree();

    connect(m_aliasCapCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(refreshAliasModes()));
    connect(m_modesCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(refreshAliasModeChannels()));

    /* Physical page */
    m_phyEdit = new EditPhysical(m_fixtureDef->physical(), this);
    m_phyEdit->show();
    physicalLayout->addWidget(m_phyEdit);

    connect(m_phyEdit, SIGNAL(copyToClipboard(QLCPhysical)),
            this, SLOT(slotCopyPhysicalClipboard(QLCPhysical)));
    connect(m_phyEdit, SIGNAL(requestPasteFromClipboard()),
            this, SLOT(slotPastePhysicalInfo()));
}

void QLCFixtureEditor::closeEvent(QCloseEvent *e)
{
    if (m_modified)
    {
        int r = QMessageBox::information(this, tr("Close"),
                                     tr("Do you want to save changes to fixture\n\""
                                        "%1\"\nbefore closing?").arg(m_fixtureDef->name()),
                                     QMessageBox::Yes,
                                     QMessageBox::No,
                                     QMessageBox::Cancel);

        if (r == QMessageBox::Yes)
            if (save())
                e->accept();
            else
                e->ignore();
        else if (r == QMessageBox::No)
            e->accept();
        else
            e->ignore();
    }
    else
    {
        e->accept();
    }
}

bool QLCFixtureEditor::checkManufacturerModel()
{
    /* Check that the fixture has a manufacturer and a model for
       unique identification */
    if (m_fixtureDef->manufacturer().length() == 0)
    {
        QMessageBox::warning(this,
                             tr("Missing important information"),
                             tr("Missing manufacturer name.\n"
                                "Unable to save fixture."));
        m_tab->setCurrentIndex(0);
        m_manufacturerEdit->setFocus();
        return false;
    }
    else if (m_fixtureDef->model().length() == 0)
    {
        QMessageBox::warning(this,
                             tr("Missing important information"),
                             tr("Missing fixture model name.\n"
                                "Unable to save fixture."));
        m_tab->setCurrentIndex(0);
        m_modelEdit->setFocus();
        return false;
    }

    return true;
}

bool QLCFixtureEditor::save()
{
    if (checkManufacturerModel() == false)
        return false;

    if (m_fileName.simplified().isEmpty() == true)
    {
        return saveAs();
    }
    else
    {
        m_fixtureDef->setPhysical(m_phyEdit->physical());
        QFile::FileError error = m_fixtureDef->saveXML(m_fileName);
        if (error == QFile::NoError)
        {
            setModified(false);
            return true;
        }
        else
        {
            QMessageBox::critical(this, tr("Fixture saving failed"),
                                  tr("Unable to save fixture definition:\n%1")
                                  .arg(QLCFile::errorString(error)));
            return false;
        }
    }
}

bool QLCFixtureEditor::saveAs()
{
    /* Bail out if there is no manufacturer or model */
    if (checkManufacturerModel() == false)
        return false;

    /* Create a file save dialog */
    QFileDialog dialog(this);
    dialog.setWindowTitle(tr("Save fixture definition"));
    dialog.setAcceptMode(QFileDialog::AcceptSave);
    dialog.setNameFilter(KQXFFilter);

    QString path;
    QDir dir = QLCFixtureDefCache::userDefinitionDirectory();
    if (m_fileName.isEmpty() == true)
    {
        /* Construct a new path for the (yet) unnamed file */
        QString man = m_fixtureDef->manufacturer().replace(" ", "-");
        QString mod = m_fixtureDef->model().replace(" ", "-");
        path = QString("%1-%2%3").arg(man).arg(mod).arg(KExtFixture);
        dialog.setDirectory(dir);
        dialog.selectFile(path);
    }
    else
    {
        /* The fixture already has a file name. Use that then. */
        dialog.setDirectory(QDir(m_fileName));
        dialog.selectFile(m_fileName);
    }

    /* Execute the dialog */
    if (dialog.exec() != QDialog::Accepted)
        return false;

    path = dialog.selectedFiles().first();
    if (path.length() != 0)
    {
        if (path.right(strlen(KExtFixture)) != QString(KExtFixture))
            path += QString(KExtFixture);

        m_fixtureDef->setPhysical(m_phyEdit->physical());
        QFile::FileError error = m_fixtureDef->saveXML(path);
        if (error == QFile::NoError)
        {
            m_fileName = path;
            setCaption();
            setModified(false);
            return true;
        }
        else
        {
            QMessageBox::critical(this, tr("Fixture saving failed"),
                                  tr("Unable to save fixture definition:\n%1")
                                  .arg(QLCFile::errorString(error)));
            return false;
        }
    }
    else
    {
        return false;
    }
}

void QLCFixtureEditor::setFileName(QString path)
{
    m_fileName = path;
}

QString QLCFixtureEditor::fileName() const
{
    return m_fileName;
}

bool QLCFixtureEditor::modified() const
{
    return m_modified;
}

void QLCFixtureEditor::setCaption()
{
    QString caption;
    QString fileName;

    fileName = m_fileName;
    if (fileName.isEmpty())
        fileName = tr("New Fixture");

    /* If the document is modified, append an asterisk after the
       filename. Otherwise the caption is just the current filename */
    caption = QDir::toNativeSeparators(fileName);
    if (m_modified == true)
        caption += QString(" *");

    parentWidget()->setWindowTitle(caption);
}

void QLCFixtureEditor::setModified(bool modified)
{
    m_modified = modified;
    setCaption();
}

/*****************************************************************************
 * General tab functions
 *****************************************************************************/

void QLCFixtureEditor::slotManufacturerTextEdited(const QString &text)
{
    m_fixtureDef->setManufacturer(text);
    setModified();
}

void QLCFixtureEditor::slotModelTextEdited(const QString &text)
{
    m_fixtureDef->setModel(text);
    setModified();
}

void QLCFixtureEditor::slotAuthorTextEdited(const QString &text)
{
    m_fixtureDef->setAuthor(text);
    setModified();
}

void QLCFixtureEditor::slotTypeActivated(const QString &text)
{
    m_fixtureDef->setType(m_fixtureDef->stringToType(text));
    setModified();
}

/*****************************************************************************
 * Channels tab functions
 *****************************************************************************/

void QLCFixtureEditor::slotChannelListSelectionChanged(QTreeWidgetItem *item)
{
    if (item == NULL)
    {
        m_removeChannelButton->setEnabled(false);
        m_editChannelButton->setEnabled(false);
        m_copyChannelButton->setEnabled(false);
    }
    else
    {
        m_removeChannelButton->setEnabled(true);
        m_editChannelButton->setEnabled(true);
        m_copyChannelButton->setEnabled(true);
    }
}

void QLCFixtureEditor::slotAddChannel()
{
    EditChannel ec(this);

    bool ok = false;
    while (ok == false)
    {
        if (ec.exec() == QDialog::Accepted)
        {
            if (m_fixtureDef->channel(ec.channel()->name()) != NULL)
            {
                QMessageBox::warning(this,
                                     tr("Channel already exists"),
                                     tr("A channel by the name \"%1\" already exists!")
                                     .arg(ec.channel()->name()));
                ok = false;
            }
            else if (ec.channel()->name().length() == 0)
            {
                QMessageBox::warning(this,
                                     tr("Channel has no name"),
                                     tr("You must give the channel a descriptive name!"));
                ok = false;
            }
            else
            {
                /* Create a new channel and item for it */
                QTreeWidgetItem *item;
                QLCChannel *ch;

                ch = ec.channel()->createCopy();
                item = new QTreeWidgetItem(m_channelList);

                m_fixtureDef->addChannel(ch);
                updateChannelItem(ch, item);
                m_channelList->setCurrentItem(item);
                m_channelList->resizeColumnToContents(CH_COL_NAME);

                setModified();
                refreshAliasList();
                refreshAliasAllChannels();
                ok = true;
            }
        }
        else
        {
            /* Cancel pressed */
            ok = true;
        }
    }
}

void QLCFixtureEditor::slotRemoveChannel()
{
    QLCChannel *channel = currentChannel();
    Q_ASSERT(channel != NULL);

    if (QMessageBox::question(this, "Remove Channel",
                              tr("Are you sure you wish to remove channel: %1?")
                              .arg(channel->name()),
                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
    {
        QTreeWidgetItem *item;
        QTreeWidgetItem *next;

        item = m_channelList->currentItem();
        if (m_channelList->itemBelow(item) != NULL)
            next = m_channelList->itemBelow(item);
        else if (m_channelList->itemAbove(item) != NULL)
            next = m_channelList->itemAbove(item);
        else
            next = NULL;

        // Remove the selected channel from the fixture (also deleted)
        m_fixtureDef->removeChannel(currentChannel());
        delete item;

        m_channelList->setCurrentItem(next);
        setModified();

        // Remove channel from modes and set nullptr for acts on channels

        for (int i = 0; i < m_modeList->topLevelItemCount(); ++i)
        {
            QTreeWidgetItem *item = m_modeList->topLevelItem(i);

            QLCFixtureMode *mode = qobject_cast<QLCFixtureMode*>(item->data(MODE_COL_NAME, PROP_PTR).toULongLong());
            mode->actsOnChannelsList().remove(channel);

            QLCChannel *mainChannel = mode->actsOnChannelsList().value(channel);
            mode->actsOnChannelsList()[mainChannel] = nullptr;
        }

        refreshModeList();
    }
}

void QLCFixtureEditor::slotEditChannel()
{
    QLCChannel *real = NULL;

    // Initialize the dialog with the selected logical channel or
    // bail out if there is no current selection
    real = currentChannel();
    if (real == NULL)
        return;

    EditChannel ec(this, real);
    if (ec.exec() == QDialog::Accepted)<commit_msg>Do not use C++11 statements<commit_after>/*
  Q Light Controller - Fixture Definition Editor
  qlcfixtureeditor.cpp

  Copyright (C) <NAME>

  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.txt

  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 <QTreeWidgetItem>
#include <QInputDialog>
#include <QHeaderView>
#include <QTreeWidget>
#include <QMessageBox>
#include <QToolButton>
#include <QFileDialog>
#include <QCloseEvent>
#include <QSettings>
#include <QComboBox>
#include <QLineEdit>
#include <QSpinBox>
#include <QTabWidget>
#include <QIcon>
#include <QMenu>
#include <QList>
#include <QUrl>

#include "qlcfixturedefcache.h"
#include "qlcfixturemode.h"
#include "qlcfixturedef.h"
#include "qlccapability.h"
#include "qlcphysical.h"
#include "qlcchannel.h"
#include "qlcconfig.h"
#include "qlcfile.h"

#ifdef Q_WS_X11
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#endif

#include "fixtureeditor.h"
#include "editphysical.h"
#include "editchannel.h"
#include "editmode.h"
#include "util.h"
#include "app.h"

extern App *_app;
extern int errno;

#define PROP_PTR Qt::UserRole

#define CH_COL_NAME 0
#define CH_COL_GRP  1

#define MODE_COL_NAME 0
#define MODE_COL_CHS  1
#define MODE_COL_HEAD 2

#define SETTINGS_GEOMETRY "fixtureeditor/geometry"

QLCFixtureEditor::QLCFixtureEditor(QWidget *parent, QLCFixtureDef *fixtureDef,
                                   const QString& fileName)
    : QWidget(parent)
    , m_fixtureDef(fixtureDef)
    , m_fileName(fileName)
    , m_modified(false)
{
    setupUi(this);
    init();
    setCaption();

    setModified(false);

    /* Connect to be able to enable/disable clipboard actions */
    connect(_app, SIGNAL(clipboardChanged()), this, SLOT(slotClipboardChanged()));

    /* Initial update to clipboard actions */
    slotClipboardChanged();

    QSettings settings;
    QVariant var = settings.value(SETTINGS_GEOMETRY);
    if (var.isValid() == true)
        parentWidget()->restoreGeometry(var.toByteArray());
}

QLCFixtureEditor::~QLCFixtureEditor()
{
    QSettings settings;
    settings.setValue(SETTINGS_GEOMETRY, parentWidget()->saveGeometry());

    delete m_fixtureDef;
    m_fixtureDef = NULL;
}

void QLCFixtureEditor::init()
{
    /* General page */
    m_manufacturerEdit->setText(m_fixtureDef->manufacturer());
    m_manufacturerEdit->setValidator(CAPS_VALIDATOR(this));
    connect(m_manufacturerEdit, SIGNAL(textEdited(const QString&)),
            this, SLOT(slotManufacturerTextEdited(const QString&)));

    m_modelEdit->setText(m_fixtureDef->model());
    m_modelEdit->setValidator(CAPS_VALIDATOR(this));
    connect(m_modelEdit, SIGNAL(textEdited(const QString&)),
            this, SLOT(slotModelTextEdited(const QString&)));

    m_typeCombo->setCurrentIndex(m_typeCombo->findText(m_fixtureDef->typeToString(m_fixtureDef->type())));
    connect(m_typeCombo, SIGNAL(activated(const QString&)),
            this, SLOT(slotTypeActivated(const QString&)));

    // Display author name or suggest current user name if there isn't one.
    // When the def already has an author, disable the field to prevent modification.
    m_authorEdit->setText(m_fixtureDef->author());
    if (m_authorEdit->text().length() > 0)
    {
        // Temporarily allow editing author name since most definitions contain wrong name:
        // m_authorEdit->setEnabled(false);
    }
    else
    {
        m_authorEdit->setText(QLCFile::currentUserName());
    }
    connect(m_authorEdit, SIGNAL(textEdited(const QString&)),
            this, SLOT(slotAuthorTextEdited(const QString&)));

    /* Channel page */
    connect(m_addChannelButton, SIGNAL(clicked()), this, SLOT(slotAddChannel()));
    connect(m_removeChannelButton, SIGNAL(clicked()), this, SLOT(slotRemoveChannel()));
    connect(m_editChannelButton, SIGNAL(clicked()), this, SLOT(slotEditChannel()));
    connect(m_copyChannelButton, SIGNAL(clicked()), this, SLOT(slotCopyChannel()));
    connect(m_pasteChannelButton, SIGNAL(clicked()), this, SLOT(slotPasteChannel()));
    connect(m_expandChannelsButton, SIGNAL(clicked()), this, SLOT(slotExpandChannels()));

    connect(m_channelList, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
            this, SLOT(slotChannelListSelectionChanged(QTreeWidgetItem*)));
    connect(m_channelList, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotChannelListContextMenuRequested()));
    connect(m_channelList, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
            this, SLOT(slotEditChannel()));
    connect(m_channelList, SIGNAL(expanded(QModelIndex)),
            this, SLOT(slotChannelItemExpanded()));

    m_channelList->setContextMenuPolicy(Qt::CustomContextMenu);
    m_channelList->setIconSize(QSize(24, 24));
    refreshChannelList();

    /* Mode page */
    connect(m_addModeButton, SIGNAL(clicked()), this, SLOT(slotAddMode()));
    connect(m_removeModeButton, SIGNAL(clicked()), this, SLOT(slotRemoveMode()));
    connect(m_editModeButton, SIGNAL(clicked()), this, SLOT(slotEditMode()));
    connect(m_cloneModeButton, SIGNAL(clicked()), this, SLOT(slotCloneMode()));
    connect(m_expandModesButton, SIGNAL(clicked()), this, SLOT(slotExpandModes()));

    connect(m_modeList, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
            this, SLOT(slotModeListSelectionChanged(QTreeWidgetItem*)));
    connect(m_modeList, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotModeListContextMenuRequested()));
    connect(m_modeList, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
            this, SLOT(slotEditMode()));
    connect(m_modeList, SIGNAL(expanded(QModelIndex)),
            this, SLOT(slotModeItemExpanded()));

    m_modeList->setContextMenuPolicy(Qt::CustomContextMenu);
    refreshModeList();

    /* Aliases page */
    connect(m_addAliasButton, SIGNAL(clicked()), this, SLOT(slotAddAliasClicked()));
    connect(m_removeAliasButton, SIGNAL(clicked()), this, SLOT(slotRemoveAliasClicked()));
    refreshAliasList();
    refreshAliasModes();
    refreshAliasModeChannels();
    refreshAliasAllChannels();
    refreshAliasTree();

    connect(m_aliasCapCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(refreshAliasModes()));
    connect(m_modesCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(refreshAliasModeChannels()));

    /* Physical page */
    m_phyEdit = new EditPhysical(m_fixtureDef->physical(), this);
    m_phyEdit->show();
    physicalLayout->addWidget(m_phyEdit);

    connect(m_phyEdit, SIGNAL(copyToClipboard(QLCPhysical)),
            this, SLOT(slotCopyPhysicalClipboard(QLCPhysical)));
    connect(m_phyEdit, SIGNAL(requestPasteFromClipboard()),
            this, SLOT(slotPastePhysicalInfo()));
}

void QLCFixtureEditor::closeEvent(QCloseEvent *e)
{
    if (m_modified)
    {
        int r = QMessageBox::information(this, tr("Close"),
                                     tr("Do you want to save changes to fixture\n\""
                                        "%1\"\nbefore closing?").arg(m_fixtureDef->name()),
                                     QMessageBox::Yes,
                                     QMessageBox::No,
                                     QMessageBox::Cancel);

        if (r == QMessageBox::Yes)
            if (save())
                e->accept();
            else
                e->ignore();
        else if (r == QMessageBox::No)
            e->accept();
        else
            e->ignore();
    }
    else
    {
        e->accept();
    }
}

bool QLCFixtureEditor::checkManufacturerModel()
{
    /* Check that the fixture has a manufacturer and a model for
       unique identification */
    if (m_fixtureDef->manufacturer().length() == 0)
    {
        QMessageBox::warning(this,
                             tr("Missing important information"),
                             tr("Missing manufacturer name.\n"
                                "Unable to save fixture."));
        m_tab->setCurrentIndex(0);
        m_manufacturerEdit->setFocus();
        return false;
    }
    else if (m_fixtureDef->model().length() == 0)
    {
        QMessageBox::warning(this,
                             tr("Missing important information"),
                             tr("Missing fixture model name.\n"
                                "Unable to save fixture."));
        m_tab->setCurrentIndex(0);
        m_modelEdit->setFocus();
        return false;
    }

    return true;
}

bool QLCFixtureEditor::save()
{
    if (checkManufacturerModel() == false)
        return false;

    if (m_fileName.simplified().isEmpty() == true)
    {
        return saveAs();
    }
    else
    {
        m_fixtureDef->setPhysical(m_phyEdit->physical());
        QFile::FileError error = m_fixtureDef->saveXML(m_fileName);
        if (error == QFile::NoError)
        {
            setModified(false);
            return true;
        }
        else
        {
            QMessageBox::critical(this, tr("Fixture saving failed"),
                                  tr("Unable to save fixture definition:\n%1")
                                  .arg(QLCFile::errorString(error)));
            return false;
        }
    }
}

bool QLCFixtureEditor::saveAs()
{
    /* Bail out if there is no manufacturer or model */
    if (checkManufacturerModel() == false)
        return false;

    /* Create a file save dialog */
    QFileDialog dialog(this);
    dialog.setWindowTitle(tr("Save fixture definition"));
    dialog.setAcceptMode(QFileDialog::AcceptSave);
    dialog.setNameFilter(KQXFFilter);

    QString path;
    QDir dir = QLCFixtureDefCache::userDefinitionDirectory();
    if (m_fileName.isEmpty() == true)
    {
        /* Construct a new path for the (yet) unnamed file */
        QString man = m_fixtureDef->manufacturer().replace(" ", "-");
        QString mod = m_fixtureDef->model().replace(" ", "-");
        path = QString("%1-%2%3").arg(man).arg(mod).arg(KExtFixture);
        dialog.setDirectory(dir);
        dialog.selectFile(path);
    }
    else
    {
        /* The fixture already has a file name. Use that then. */
        dialog.setDirectory(QDir(m_fileName));
        dialog.selectFile(m_fileName);
    }

    /* Execute the dialog */
    if (dialog.exec() != QDialog::Accepted)
        return false;

    path = dialog.selectedFiles().first();
    if (path.length() != 0)
    {
        if (path.right(strlen(KExtFixture)) != QString(KExtFixture))
            path += QString(KExtFixture);

        m_fixtureDef->setPhysical(m_phyEdit->physical());
        QFile::FileError error = m_fixtureDef->saveXML(path);
        if (error == QFile::NoError)
        {
            m_fileName = path;
            setCaption();
            setModified(false);
            return true;
        }
        else
        {
            QMessageBox::critical(this, tr("Fixture saving failed"),
                                  tr("Unable to save fixture definition:\n%1")
                                  .arg(QLCFile::errorString(error)));
            return false;
        }
    }
    else
    {
        return false;
    }
}

void QLCFixtureEditor::setFileName(QString path)
{
    m_fileName = path;
}

QString QLCFixtureEditor::fileName() const
{
    return m_fileName;
}

bool QLCFixtureEditor::modified() const
{
    return m_modified;
}

void QLCFixtureEditor::setCaption()
{
    QString caption;
    QString fileName;

    fileName = m_fileName;
    if (fileName.isEmpty())
        fileName = tr("New Fixture");

    /* If the document is modified, append an asterisk after the
       filename. Otherwise the caption is just the current filename */
    caption = QDir::toNativeSeparators(fileName);
    if (m_modified == true)
        caption += QString(" *");

    parentWidget()->setWindowTitle(caption);
}

void QLCFixtureEditor::setModified(bool modified)
{
    m_modified = modified;
    setCaption();
}

/*****************************************************************************
 * General tab functions
 *****************************************************************************/

void QLCFixtureEditor::slotManufacturerTextEdited(const QString &text)
{
    m_fixtureDef->setManufacturer(text);
    setModified();
}

void QLCFixtureEditor::slotModelTextEdited(const QString &text)
{
    m_fixtureDef->setModel(text);
    setModified();
}

void QLCFixtureEditor::slotAuthorTextEdited(const QString &text)
{
    m_fixtureDef->setAuthor(text);
    setModified();
}

void QLCFixtureEditor::slotTypeActivated(const QString &text)
{
    m_fixtureDef->setType(m_fixtureDef->stringToType(text));
    setModified();
}

/*****************************************************************************
 * Channels tab functions
 *****************************************************************************/

void QLCFixtureEditor::slotChannelListSelectionChanged(QTreeWidgetItem *item)
{
    if (item == NULL)
    {
        m_removeChannelButton->setEnabled(false);
        m_editChannelButton->setEnabled(false);
        m_copyChannelButton->setEnabled(false);
    }
    else
    {
        m_removeChannelButton->setEnabled(true);
        m_editChannelButton->setEnabled(true);
        m_copyChannelButton->setEnabled(true);
    }
}

void QLCFixtureEditor::slotAddChannel()
{
    EditChannel ec(this);

    bool ok = false;
    while (ok == false)
    {
        if (ec.exec() == QDialog::Accepted)
        {
            if (m_fixtureDef->channel(ec.channel()->name()) != NULL)
            {
                QMessageBox::warning(this,
                                     tr("Channel already exists"),
                                     tr("A channel by the name \"%1\" already exists!")
                                     .arg(ec.channel()->name()));
                ok = false;
            }
            else if (ec.channel()->name().length() == 0)
            {
                QMessageBox::warning(this,
                                     tr("Channel has no name"),
                                     tr("You must give the channel a descriptive name!"));
                ok = false;
            }
            else
            {
                /* Create a new channel and item for it */
                QTreeWidgetItem *item;
                QLCChannel *ch;

                ch = ec.channel()->createCopy();
                item = new QTreeWidgetItem(m_channelList);

                m_fixtureDef->addChannel(ch);
                updateChannelItem(ch, item);
                m_channelList->setCurrentItem(item);
                m_channelList->resizeColumnToContents(CH_COL_NAME);

                setModified();
                refreshAliasList();
                refreshAliasAllChannels();
                ok = true;
            }
        }
        else
        {
            /* Cancel pressed */
            ok = true;
        }
    }
}

void QLCFixtureEditor::slotRemoveChannel()
{
    QLCChannel *channel = currentChannel();
    Q_ASSERT(channel != NULL);

    if (QMessageBox::question(this, "Remove Channel",
                              tr("Are you sure you wish to remove channel: %1?")
                              .arg(channel->name()),
                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
    {
        QTreeWidgetItem *item;
        QTreeWidgetItem *next;

        item = m_channelList->currentItem();
        if (m_channelList->itemBelow(item) != NULL)
            next = m_channelList->itemBelow(item);
        else if (m_channelList->itemAbove(item) != NULL)
            next = m_channelList->itemAbove(item);
        else
            next = NULL;

        // Remove the selected channel from the fixture (also deleted)
        m_fixtureDef->removeChannel(currentChannel());
        delete item;

        m_channelList->setCurrentItem(next);
        setModified();

        // Remove channel from modes and set nullptr for acts on channels
        for (int i = 0; i < m_modeList->topLevelItemCount(); ++i)
        {
            QTreeWidgetItem *item = m_modeList->topLevelItem(i);

            QLCFixtureMode *mode = qobject_cast<QLCFixtureMode*>(item->data(MODE_COL_NAME, PROP_PTR).toULongLong());
            mode->actsOnChannelsList().remove(channel);

            QLCChannel *mainChannel = mode->actsOnChannelsList().value(channel);
            mode->actsOnChannelsList()[mainChannel] = NULL;
        }

        refreshModeList();
    }
}

void QLCFixtureEditor::slotEditChannel()
{
    QLCChannel *real = NULL;

    // Initialize the dialog with the selected logical channel or
    // bail out if there is no current selection
    real = currentChannel();
    if (real == NULL)
        return;

    EditChannel ec(this, real);
    if (ec.exec() == QDialog::Accepted)
{'fraction_non_alphanumeric': 0.13581400782756003, 'fraction_numerical': 0.0018692680647234066, 'mean_word_length': 2.7427853082641014, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 203, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '221845', 'n_tokens_mistral': 10065, 'n_tokens_neox': 9761, 'n_words': 2001}