text: /*
    Q Light Controller
    sceneeditor.cpp
  
    Copyright (c) Heikki Junnila, Stefan Krumm
  
    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 <QColorDialog>
  #include <QTreeWidget>
  #include <QMessageBox>
  #include <QToolButton>
  #include <QScrollArea>
  #include <QTabWidget>
  #include <QComboBox>
  #include <QSettings>
  #include <QLineEdit>
  #include <QToolBar>
  #include <QLayout>
  #include <qmath.h>
  #include <QLabel>
  #include <QDebug>
  
  #include "genericdmxsource.h"
  #include "fixtureselection.h"
  #include "speeddialwidget.h"
  #include "functionmanager.h"
  #include "fixtureconsole.h"
  #include "groupsconsole.h"
  #include "qlcfixturedef.h"
  #include "channelsgroup.h"
  #include "qlcclipboard.h"
  #include "positiontool.h"
  #include "sceneeditor.h"
  #include "mastertimer.h"
  #include "qlcchannel.h"
  #include "chaserstep.h"
  #include "fixture.h"
  #include "chaser.h"
  #include "scene.h"
  #include "doc.h"
  
  #define KColumnName         0
  #define KColumnManufacturer 1
  #define KColumnModel        2
  #define KColumnID           3
  
  #define KTabGeneral         0
  
  #define SETTINGS_CHASER "sceneeditor/chaser"
  
  #define UI_STATE_TAB_INDEX "tabIndex"
  #define UI_STATE_TAB_MODE  "tabMode"
  #define UI_STATE_SHOW_DIAL "showDial"
  #define UI_STATE_TABBED_FIXTURES  0
  #define UI_STATE_ALL_FIXTURES     1
  
  SceneEditor::SceneEditor(QWidget* parent, Scene* scene, Doc* doc, bool applyValues)
      : QWidget(parent)
      , m_doc(doc)
      , m_scene(scene)
      , m_source(NULL)
      , m_initFinished(false)
      , m_speedDials(NULL)
      , m_channelGroupsTab(-1)
      , m_currentTab(KTabGeneral)
      , m_fixtureFirstTabIndex(1)
      , m_copyFromSelection(false)
  {
      qDebug() << Q_FUNC_INFO;
  
      Q_ASSERT(doc != NULL);
      Q_ASSERT(scene != NULL);
  
      setupUi(this);
  
      init(applyValues);
  
      // Start new (==empty) scenes from the first tab and ones with something in them
      // on the first fixture page.
      if (m_tab->count() == 0)
          slotTabChanged(KTabGeneral);
      else
      {
          QVariant tabIndex = scene->uiStateValue(UI_STATE_TAB_INDEX);
          if (tabIndex.isValid())
              m_tab->setCurrentIndex(tabIndex.toInt());
          else
              m_tab->setCurrentIndex(0);
      }
  
      QVariant showDial = scene->uiStateValue(UI_STATE_SHOW_DIAL);
      if (showDial.isNull() == false && showDial.toBool() == true)
          m_speedDialAction->setChecked(true);
  
      connect(m_doc, SIGNAL(fixtureRemoved(quint32)), this, SLOT(slotFixtureRemoved(quint32)));
  
      m_initFinished = true;
  
      // Set focus to the editor
      m_nameEdit->setFocus();
  }
  
  SceneEditor::~SceneEditor()
  {
      qDebug() << Q_FUNC_INFO;
  
      delete m_source;
  
      QSettings settings;
      quint32 id = m_chaserCombo->itemData(m_chaserCombo->currentIndex()).toUInt();
      settings.setValue(SETTINGS_CHASER, id);
  }
  
  void SceneEditor::slotFunctionManagerActive(bool active)
  {
      qDebug() << Q_FUNC_INFO;
  
      if (active == true)
      {
          if (m_speedDialAction->isChecked() && m_speedDials == NULL)
              createSpeedDials();
      }
      else
      {
          if (m_speedDials != NULL)
              m_speedDials->deleteLater();
          m_speedDials = NULL;
      }
  }
  
  void SceneEditor::slotSetSceneValues(QList <SceneValue>&sceneValues)
  {
      QListIterator <SceneValue> it(sceneValues);
  
      while (it.hasNext() == true)
      {
          SceneValue sv(it.next());
  
          Fixture *fixture = m_doc->fixture(sv.fxi);
          Q_ASSERT(fixture != NULL);
  
          FixtureConsole *fc = fixtureConsole(fixture);
          if (fc != NULL)
              fc->setSceneValue(sv);
      }
  }
  
  void SceneEditor::slotFixtureRemoved(quint32 id)
  {
      removeFixtureTab(id);
      removeFixtureItem(id);
  
      QListIterator <SceneValue> it(m_scene->values());
  
      while (it.hasNext() == true)
      {
          SceneValue sv(it.next());
          if (sv.fxi == id)
              m_scene->unsetValue(id, sv.channel);
      }
      m_scene->removeFixture(id);
  }
  
  void SceneEditor::init(bool applyValues)
  {
      QVariant tabMode = m_scene->uiStateValue(UI_STATE_TAB_MODE);
  
      this->layout()->setContentsMargins(8, 3, 8, 3);
  
      /* Actions */
      m_enableCurrentAction = new QAction(QIcon(":/check.png"),
                                          tr("Enable all channels in current fixture"), this);
      m_disableCurrentAction = new QAction(QIcon(":/uncheck.png"),
                                           tr("Disable all channels in current fixture"), this);
      m_copyAction = new QAction(QIcon(":/editcopy.png"),
                                 tr("Copy current values to clipboard"), this);
      m_pasteAction = new QAction(QIcon(":/editpaste.png"),
                                  tr("Paste clipboard values to current fixture"), this);
      m_copyToAllAction = new QAction(QIcon(":/editcopyall.png"),
                                      tr("Copy current values to all fixtures"), this);
      m_colorToolAction = new QAction(QIcon(":/color.png"),
                                      tr("Color tool for CMY/RGB-capable fixtures"), this);
      m_positionToolAction = new QAction(QIcon(":/xypad.png"),
                                      tr("Position tool for moving heads/scanners"), this);
      m_tabViewAction = new QAction(QIcon(":/tabview.png"),
                                      tr("Switch between tab view and all channels view"), this);
      m_blindAction = new QAction(QIcon(":/blind.png"),
                                  tr("Toggle blind mode"), this);
      m_speedDialAction = new QAction(QIcon(":/speed.png"),
                                      tr("Show/Hide speed dial window"), this);
      m_recordAction = new QAction(QIcon(":/record.png"),
                                   tr("Clone this scene and append as a new step to the selected chaser"), this);
  
      m_nextTabAction = new QAction(QIcon(":/forward.png"), tr("Go to next fixture tab"), this);
      m_nextTabAction->setShortcut(QKeySequence("Alt+Right"));
      connect(m_nextTabAction, SIGNAL(triggered(bool)),
              this, SLOT(slotGoToNextTab()));
      m_prevTabAction = new QAction(QIcon(":/back.png"), tr("Go to previous fixture tab"), this);
      m_prevTabAction->setShortcut(QKeySequence("Alt+Left"));
      connect(m_prevTabAction, SIGNAL(triggered(bool)),
              this, SLOT(slotGoToPreviousTab()));
  
      // Speed Dial initial state
      m_speedDialAction->setCheckable(true);
  
      // Blind initial state
      m_blindAction->setCheckable(true);
  
      m_tabViewAction->setCheckable(true);
      if (tabMode.isNull() || tabMode.toInt() == UI_STATE_TABBED_FIXTURES)
          m_tabViewAction->setChecked(true);
  
      // Chaser combo init
      quint32 selectId = Function::invalidId();
      QSettings settings;
      QVariant var = settings.value(SETTINGS_CHASER);
      if (var.isValid() == true)
          selectId = var.toUInt();
      m_chaserCombo = new QComboBox(this);
      m_chaserCombo->setMaximumWidth(250);
      m_chaserCombo->addItem(tr("None"), Function::invalidId());
      slotChaserComboActivated(0);
      foreach (Function *function, m_doc->functionsByType(Function::ChaserType))
      {
          m_chaserCombo->addItem(function->name(), function->id());
          if (function->id() == selectId)
          {
              int index = m_chaserCombo->count() - 1;
              m_chaserCombo->setCurrentIndex(index);
              slotChaserComboActivated(index);
          }
      }
      QLabel *nameLabel = new QLabel(tr("Scene name:"));
      m_nameEdit = new QLineEdit();
  
      // Connections
      connect(m_enableCurrentAction, SIGNAL(triggered(bool)),
              this, SLOT(slotEnableCurrent()));
      connect(m_disableCurrentAction, SIGNAL(triggered(bool)),
              this, SLOT(slotDisableCurrent()));
      connect(m_copyAction, SIGNAL(triggered(bool)),
              this, SLOT(slotCopy()));
      connect(m_pasteAction, SIGNAL(triggered(bool)),
              this, SLOT(slotPaste()));
      connect(m_copyToAllAction, SIGNAL(triggered(bool)),
              this, SLOT(slotCopyToAll()));
      connect(m_colorToolAction, SIGNAL(triggered(bool)),
              this, SLOT(slotColorTool()));
      connect(m_positionToolAction, SIGNAL(triggered(bool)),
              this, SLOT(slotPositionTool()));
      connect(m_speedDialAction, SIGNAL(toggled(bool)),
              this, SLOT(slotSpeedDialToggle(bool)));
      connect(m_tabViewAction, SIGNAL(toggled(bool)),
              this, SLOT(slotViewModeChanged(bool)));
      connect(m_blindAction, SIGNAL(toggled(bool)),
              this, SLOT(slotBlindToggled(bool)));
      connect(m_recordAction, SIGNAL(triggered(bool)),
              this, SLOT(slotRecord()));
      connect(m_chaserCombo, SIGNAL(activated(int)),
              this, SLOT(slotChaserComboActivated(int)));
      connect(m_doc, SIGNAL(modeChanged(Doc::Mode)),
              this, SLOT(slotModeChanged(Doc::Mode)));
  
      /* Toolbar */
      QToolBar* toolBar = new QToolBar(this);
      layout()->setMenuBar(toolBar);
      toolBar->addAction(m_enableCurrentAction);
      toolBar->addAction(m_disableCurrentAction);
      toolBar->addSeparator();
      toolBar->addAction(m_prevTabAction);
      toolBar->addAction(m_nextTabAction);
      toolBar->addSeparator();
      toolBar->addAction(m_copyAction);
      toolBar->addAction(m_pasteAction);
      toolBar->addAction(m_copyToAllAction);
      toolBar->addSeparator();
      toolBar->addAction(m_colorToolAction);
      toolBar->addAction(m_positionToolAction);
      toolBar->addSeparator();
      toolBar->addAction(m_speedDialAction);
      toolBar->addAction(m_tabViewAction);
      toolBar->addSeparator();
      toolBar->addAction(m_blindAction);
      toolBar->addSeparator();
      toolBar->addAction(m_recordAction);
      toolBar->addWidget(m_chaserCombo);
      toolBar->addSeparator();
      toolBar->addWidget(nameLabel);
      toolBar->addWidget(m_nameEdit);
  
      /* Tab widget */
      connect(m_tab, SIGNAL(currentChanged(int)),
              this, SLOT(slotTabChanged(int)));
  
      /* Add & remove buttons */
      connect(m_addFixtureButton, SIGNAL(clicked()),
              this, SLOT(slotAddFixtureClicked()));
      connect(m_removeFixtureButton, SIGNAL(clicked()),
              this, SLOT(slotRemoveFixtureClicked()));
  
      m_nameEdit->setText(m_scene->name());
      m_nameEdit->setSelection(0, m_nameEdit->text().length());
      connect(m_nameEdit, SIGNAL(textEdited(const QString&)),
              this, SLOT(slotNameEdited(const QString&)));
  
      // Channels groups tab
      QList<quint32> chGrpIds = m_scene->channelGroups();
      QListIterator <ChannelsGroup*> scg(m_doc->channelsGroups());
      while (scg.hasNext() == true)
      {
          QTreeWidgetItem* item = new QTreeWidgetItem(m_channelGroupsTree);
          ChannelsGroup *grp = scg.next();
          item->setText(KColumnName, grp->name());
          item->setData(KColumnName, Qt::UserRole, grp->id());
  
          item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
          if (chGrpIds.contains(grp->id()))
              item->setCheckState(KColumnName, Qt::Checked);
          else
              item->setCheckState(KColumnName, Qt::Unchecked);
      }
      connect(m_channelGroupsTree, SIGNAL(itemChanged(QTreeWidgetItem*,int)),
              this, SLOT(slotChannelGroupsChanged(QTreeWidgetItem*,int)));
      updateChannelsGroupsTab();
  
      // Apply any mode related change
      slotModeChanged(m_doc->mode());
  
      // Fixtures & tabs
      // Fill the fixtures list from the Scene fixtures
      foreach (quint32 fixtureID, m_scene->fixtures())
      {
          if (fixtureItem(fixtureID) == NULL)
          {
              Fixture* fixture = m_doc->fixture(fixtureID);
              if (fixture == NULL)
                  continue;
              addFixtureItem(fixture);
          }
      }
  
      // Complete the fixtures list from the Scene values
      // (This should be useless)
      QListIterator <SceneValue> it(m_scene->values());
      while (it.hasNext() == true)
      {
          SceneValue scv(it.next());
  
          if (fixtureItem(scv.fxi) == NULL)
          {
              qWarning() << Q_FUNC_INFO
                  << "Fixture" << scv.fxi << "was not in the scene fixture list!";
              Fixture* fixture = m_doc->fixture(scv.fxi);
              if (fixture == NULL)
                  continue;
  
              addFixtureItem(fixture);
          }
      }
  
      // Create the actual tab view
      if (tabMode.isNull() || tabMode.toInt() == UI_STATE_TABBED_FIXTURES)
          slotViewModeChanged(true, applyValues);
      else
          slotViewModeChanged(false, applyValues);
  }
  
  void SceneEditor::setSceneValue(const SceneValue& scv)
  {
      FixtureConsole* fc;
      Fixture* fixture;
  
      fixture = m_doc->fixture(scv.fxi);
      Q_ASSERT(fixture != NULL);
  
      fc = fixtureConsole(fixture);
      if (fc != NULL)
          fc->setSceneValue(scv);
  }
  
  
  void SceneEditor::setBlindModeEnabled(bool active)
  {
      m_blindAction->setChecked(active);
  }
  
  /*****************************************************************************
   * Common
   *****************************************************************************/
  
  void SceneEditor::slotTabChanged(int tab)
  {
      m_currentTab = tab;
      QLCClipboard *clipboard = m_doc->clipboard();
  
      m_scene->setUiStateValue(UI_STATE_TAB_INDEX, tab);
  
      if (tab == KTabGeneral)
      {
          m_enableCurrentAction->setEnabled(false);
          m_disableCurrentAction->setEnabled(false);
  
          m_copyAction->setEnabled(false);
          m_pasteAction->setEnabled(false);
          m_copyToAllAction->setEnabled(false);
          m_colorToolAction->setEnabled(false);
      }
      else
      {
          m_enableCurrentAction->setEnabled(true);
          m_disableCurrentAction->setEnabled(true);
  
          m_copyAction->setEnabled(true);
          if (clipboard->hasSceneValues())
              m_pasteAction->setEnabled(true);
          else
              m_pasteAction->setEnabled(false);
  
          if (m_tabViewAction->isChecked())
              m_copyToAllAction->setEnabled(true);
          else
              m_copyToAllAction->setEnabled(false);
          m_colorToolAction->setEnabled(isColorToolAvailable());
          m_positionToolAction->setEnabled(isPositionToolAvailable());
      }
  }
  
  void SceneEditor::slotEnableCurrent()
  {
      if (m_tabViewAction->isChecked())
      {
          /* QObject cast fails unless the widget is a FixtureConsole */
          FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
          if (fc != NULL)
              fc->setChecked(true);
      }
      else
      {
          foreach(FixtureConsole *fc, m_consoleList.values())
          {
              if (fc == NULL)
                  continue;
              fc->setChecked(true);
          }
      }
  }
  
  void SceneEditor::slotDisableCurrent()
  {
      if (m_tabViewAction->isChecked())
      {
          /* QObject cast fails unless the widget is a FixtureConsole */
          FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
          if (fc != NULL)
              fc->setChecked(false);
      }
      else
      {
          foreach(FixtureConsole *fc, m_consoleList.values())
          {
              if (fc == NULL)
                  continue;
              fc->setChecked(false);
          }
      }
  }
  
  void SceneEditor::slotCopy()
  {
      QList <SceneValue> copyList;
      QLCClipboard *clipboard = m_doc->clipboard();
  
      /* QObject cast fails unless the widget is a FixtureConsole */
      if (m_tabViewAction->isChecked())
      {
          FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
          if (fc != NULL)
          {
              copyList = fc->values();
              if (fc->hasSelections())
                  m_copyFromSelection = true;
              else
                  m_copyFromSelection = false;
              clipboard->copyContent(m_scene->id(), copyList);
          }
      }
      else
      {
          bool oneHasSelection = false;
          QList <SceneValue> selectedOnlyList;
          foreach(FixtureConsole *fc, m_consoleList.values())
          {
              if (fc == NULL)
                  continue;
              copyList.append(fc->values());
              if (fc->hasSelections())
              {
                  oneHasSelection = true;
                  selectedOnlyList.append(fc->values());
              }
          }
          m_copyFromSelection = oneHasSelection;
          if (m_copyFromSelection == true)
              clipboard->copyContent(m_scene->id(), selectedOnlyList);
          else
              clipboard->copyContent(m_scene->id(), copyList);
      }
      if (copyList.count() > 0)
          m_pasteAction->setEnabled(true);
  }
  
  void SceneEditor::slotPaste()
  {
      QLCClipboard *clipboard = m_doc->clipboard();
  
      if (clipboard->hasSceneValues() == false)
          return;
  
      if (m_tabViewAction->isChecked())
      {
          FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
          if (fc != NULL)
              fc->setValues(clipboard->getSceneValues(), m_copyFromSelection);
      }
      else
      {
          foreach(FixtureConsole *fc, m_consoleList.values())
          {
              if (fc == NULL)
                  continue;
              quint32 fxi = fc->fixture();
              QList<SceneValue>thisFixtureVals;
              foreach(SceneValue val, clipboard->getSceneValues())
              {
                  if (val.fxi == fxi)
                      thisFixtureVals.append(val);
              }
              fc->setValues(thisFixtureVals, m_copyFromSelection);
          }
      }
  }
  
  void SceneEditor::slotCopyToAll()
  {
      slotCopy();
  
      QLCClipboard *clipboard = m_doc->clipboard();
  
      if (clipboard->hasSceneValues())
      {
          for (int i = m_fixtureFirstTabIndex; i < m_tab->count(); i++)
          {
              FixtureConsole* fc = fixtureConsoleTab(i);
              if (fc != NULL)
                  fc->setValues(clipboard->getSceneValues(), m_copyFromSelection);
          }
      }
  
      //m_copy.clear();
      m_pasteAction->setEnabled(false);
  }
  
  void SceneEditor::slotColorTool()
  {
      QColor color = slotColorSelectorChanged(QColor());
  
      QColorDialog dialog(color, this);
      connect(&dialog, SIGNAL(currentColorChanged(const QColor&)),
              this, SLOT(slotColorSelectorChanged(const QColor&)));
  
      int result = dialog.exec();
      if (result == QDialog::Rejected)
      {
          slotColorSelectorChanged(color); // reset color to what it previously was
      }
  }
  
  void SceneEditor::slotPositionTool()
  {
      FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
      if (fc != NULL)
      {
          QList<SceneValue> origValues = fc->values();
  
          Fixture* fxi = m_doc->fixture(fc->fixture());
          QPointF pos;
          QRectF range;
          bool panFound = false;
          bool tiltFound = false;
  
          Q_ASSERT(fxi != NULL);
  
          for (int i = 0; i < fxi->heads(); ++i )
          {
               if (!range.isValid())
                   range = fxi->degreesRange(i);
  
               quint32 panMsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, i);
               quint32 panLsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::LSB, i);
               quint32 tiltMsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, i);
               quint32 tiltLsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::LSB, i);
  
               if (panMsbChannel != QLCChannel::invalid())
               {
                   if (!panFound )
                   {
                       qDebug() << "panFound" << i;
                       panFound = true;
                       qreal v = qreal(fc->value(panMsbChannel));
                       if (panLsbChannel != QLCChannel::invalid())
                       {
                          v += qreal(fc->value(panLsbChannel)) / qreal(256);
                       }
  
                       pos.setX(v);
                   }
               }
  
               if (tiltMsbChannel != QLCChannel::invalid())
               {
                   if (!tiltFound )
                   {
                       tiltFound = true;
                       qDebug() << "tiltFound" << i;
                       qreal v = qreal(fc->value(tiltMsbChannel));
                       if (tiltLsbChannel != QLCChannel::invalid())
                       {
                          v += qreal(fc->value(tiltLsbChannel)) / qreal(256);
                       }
  
                       pos.setY(v);
                   }
               }
          }
  
          PositionTool dialog(pos, range);
          connect(&dialog, SIGNAL(currentPositionChanged(const QPointF&)),
              this, SLOT(slotPositionSelectorChanged(const QPointF&)));
  
          int result = dialog.exec();
          if (result == QDialog::Rejected)
          {
              fc->setValues(origValues, false); // reset position to what it previously was
          }
      }
  }
  
  QColor SceneEditor::slotColorSelectorChanged(const QColor& color)
  {
      QColor returnColor = QColor();
  
      /* QObject cast fails unless the widget is a FixtureConsole */
      FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
      if (fc != NULL)
      {
          Fixture* fxi = m_doc->fixture(fc->fixture());
          Q_ASSERT(fxi != NULL);
  
          QSet <quint32> cyan    = fxi->channels(QLCChannel::Intensity, QLCChannel::Cyan);
          QSet <quint32> magenta = fxi->channels(QLCChannel::Intensity, QLCChannel::Magenta);
          QSet <quint32> yellow  = fxi->channels(QLCChannel::Intensity, QLCChannel::Yellow);
          QSet <quint32> red     = fxi->channels(QLCChannel::Intensity, QLCChannel::Red);
          QSet <quint32> green   = fxi->channels(QLCChannel::Intensity, QLCChannel::Green);
          QSet <quint32> blue    = fxi->channels(QLCChannel::Intensity, QLCChannel::Blue);
  
          if (!cyan.isEmpty() && !magenta.isEmpty() && !yellow.isEmpty())
          {
              returnColor.setCmyk(fc->value(*cyan.begin()),
                                  fc->value(*magenta.begin()),
                                  fc->value(*yellow.begin()),
                                  0);
              if (color.isValid() == true)
              {
                  foreach (quint32 ch, cyan)
                  {
                      fc->setChecked(true, ch);
                      fc->setValue(ch, color.cyan());
                  }
  
                  foreach (quint32 ch, magenta)
                  {
                      fc->setChecked(true, ch);
                      fc->setValue(ch, color.magenta());
                  }
  
                  foreach (quint32 ch, yellow)
                  {
                      fc->setChecked(true, ch);
                      fc->setValue(ch, color.yellow());
                  }
              }
          }
          else if (!red.isEmpty() && !green.isEmpty() && !blue.isEmpty())
          {
              returnColor.setRgb(fc->value(*red.begin()),
                                 fc->value(*green.begin()),
                                 fc->value(*blue.begin()),
                                 0);
  
              if (color.isValid() == true)
              {
                  foreach (quint32 ch, red)
                  {
                      fc->setChecked(true, ch);
                      fc->setValue(ch, color.red());
                  }
  
                  foreach (quint32 ch, green)
                  {
                      fc->setChecked(true, ch);
                      fc->setValue(ch, color.green());
                  }
  
                  foreach (quint32 ch, blue)
                  {
                      fc->setChecked(true, ch);
                      fc->setValue(ch, color.blue());
                  }
              }
          }
          return returnColor;
      }
  
      /* QObject cast fails unless the widget is a GroupsConsole */
      GroupsConsole* gc = groupConsoleTab(m_currentTab);
      if (gc != NULL)
      {
          foreach(ConsoleChannel *cc, gc->groups())
          {
              Fixture* fxi = m_doc->fixture(cc->fixture());
              Q_ASSERT(fxi != NULL);
              const QLCChannel *ch = fxi->channel(cc->channelIndex());
              if (ch->group() == QLCChannel::Intensity)
              {
                  if (ch->colour() == QLCChannel::Red)
                      cc->setValue(color.red());
                  else if (ch->colour() == QLCChannel::Green)
                      cc->setValue(color.green());
                  else if (ch->colour() == QLCChannel::Blue)
                      cc->setValue(color.blue());
                  else if (ch->colour() == QLCChannel::Magenta)
                      cc->setValue(color.magenta());
                  else if (ch->colour() == QLCChannel::Yellow)
                      cc->setValue(color.yellow());
                  else if (ch->colour() == QLCChannel::Cyan)
                      cc->setValue(color.cyan());
              }
          }
      }
  
      return returnColor;
  }
  
  void SceneEditor::slotPositionSelectorChanged(const QPointF& position)
  {
      qreal x = position.x();
      qreal y = position.y();
  
      uchar panMsbNew = x;
      uchar panLsbNew = (x - floor(x)) * 256;
      uchar tiltMsbNew = y;
      uchar tiltLsbNew = (y - floor(y)) * 256;
  
      /* QObject cast fails unless the widget is a FixtureConsole */
      FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
      if (fc != NULL)
      {
          Fixture* fxi = m_doc->fixture(fc->fixture());
          Q_ASSERT(fxi != NULL);
  
          for (int i = 0; i < fxi->heads(); ++i )
          {
               quint32 panMsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, i);
               quint32 panLsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::LSB, i);
               quint32 tiltMsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, i);
               quint32 tiltLsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::LSB, i);
  
               if (panMsbChannel != QLCChannel::invalid())
               {
                   fc->setChecked(true, panMsbChannel);
                   fc->setValue(panMsbChannel, panMsbNew);
  
                   if (panLsbChannel != QLCChannel::invalid())
                   {
                       fc->setChecked(true, panLsbChannel);
                       fc->setValue(panLsbChannel, panLsbNew);
                   }
               }
  
               if (tiltMsbChannel != QLCChannel::invalid())
               {
                   fc->setChecked(true, tiltMsbChannel);
                   fc->setValue(tiltMsbChannel, tiltMsbNew);
  
                   if (tiltLsbChannel != QLCChannel::invalid())
                   {
                       fc->setChecked(true, tiltLsbChannel);
                       fc->setValue(tiltLsbChannel, tiltLsbNew);
                   }
               }
          }
      }
  
      /* QObject cast fails unless the widget is a GroupsConsole */
      GroupsConsole* gc = groupConsoleTab(m_currentTab);
      if (gc != NULL)
      {
          foreach(ConsoleChannel *cc, gc->groups())
          {
              Fixture* fxi = m_doc->fixture(cc->fixture());
              Q_ASSERT(fxi != NULL);
              const QLCChannel *ch = fxi->channel(cc->channelIndex());
              if (ch->group() == QLCChannel::Pan)
              {
                  if (ch->controlByte() == QLCChannel::MSB)
                      cc->setValue(panMsbNew);
                  else
                      cc->setValue(panLsbNew);
              }
              else if(ch->group() == QLCChannel::Tilt)
              {
                  if (ch->controlByte() == QLCChannel::MSB)
                      cc->setValue(tiltMsbNew);
                  else
                      cc->setValue(tiltLsbNew);
              }
          }
      }
  }
  
  void SceneEditor::slotSpeedDialToggle(bool state)
  {
      if (state == true)
      {
          createSpeedDials();
      }
      else
      {
          if (m_speedDials != NULL)
              m_speedDials->deleteLater();
          m_speedDials = NULL;
      }
  
      m_scene->setUiStateValue(UI_STATE_SHOW_DIAL, state);
  }
  
  void SceneEditor::slotBlindToggled(bool state)
  {
      if (m_doc->mode() == Doc::Operate)
      {
          delete m_source;
          m_source = NULL;
  
          if (m_scene != NULL && !m_scene->isRunning())
          {
              m_source = new GenericDMXSource(m_doc);
              foreach(SceneValue scv, m_scene->values())
                  m_source->set(scv.fxi, scv.channel, scv.value);
          }
      }
      else
      {
          if (m_source == NULL)
              m_source = new GenericDMXSource(m_doc);
      }
  
      if (m_source != NULL)
          m_source->setOutputEnabled(!state);
  }
  
  void SceneEditor::slotModeChanged(Doc::Mode mode)
  {
      if (mode == Doc::Operate)
      {
          m_blindAction->setChecked(true);
          slotBlindToggled(true);
      }
      else
      {
          m_blindAction->setChecked(false);
          slotBlindToggled(false);
      }
  
  }
  
  void SceneEditor::slotViewModeChanged(bool tabbed, bool applyValues)
  {
      m_tab->blockSignals(true);
      for (int i = m_tab->count() - 1; i >= m_fixtureFirstTabIndex; i--)
      {
          QScrollArea* area = qobject_cast<QScrollArea*> (m_tab->widget(i));
          Q_ASSERT(area != NULL);
          m_tab->removeTab(i);
          delete area; // Deletes also FixtureConsole
      }
      m_consoleList.clear();
      m_tab->blockSignals(false);
  
      // all fixtures view mode
      if (tabbed == false)
      {
          QListIterator <Fixture*> it(selectedFixtures());
          if (it.hasNext() == true)
          {
              QScrollArea* scrollArea = new QScrollArea(m_tab);
  
              scrollArea->setWidgetResizable(true);
              int tIdx = m_tab->addTab(scrollArea, tr("All fixtures"));
              m_tab->setTabToolTip(tIdx, tr("All fixtures"));
  
              QGroupBox* grpBox = new QGroupBox(scrollArea);
              grpBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
              QHBoxLayout* fixturesLayout = new QHBoxLayout(grpBox);
              grpBox->setLayout(fixturesLayout);
              fixturesLayout->setSpacing(2);
              fixturesLayout->setContentsMargins(0, 2, 2, 2);
  
              int c = 0;
              while (it.hasNext() == true)
              {
                  Fixture* fixture = it.next();
                  Q_ASSERT(fixture != NULL);
                  FixtureConsole* console = NULL;
                  if (c%2 == 0)
                      console = new FixtureConsole(scrollArea, m_doc, FixtureConsole::GroupOdd);
                  else
                      console = new FixtureConsole(scrollArea, m_doc, FixtureConsole::GroupEven);
                  console->setFixture(fixture->id());
                  console->setChecked(false);
                  m_consoleList[fixture->id()] = console;
  
                  connect(console, SIGNAL(valueChanged(quint32,quint32,uchar)),
                          this, SLOT(slotValueChanged(quint32,quint32,uchar)));
                  connect(console, SIGNAL(checked(quint32,quint32,bool)),
                          this, SLOT(slotChecked(quint32,quint32,bool)));
  
                  QListIterator <SceneValue> it(m_scene->values());
                  while (it.hasNext() == true)
                  {
                      SceneValue scv(it.next());
                      if (applyValues == false)
                          scv.value = 0;
                      if (scv.fxi == fixture->id())
                          console->setSceneValue(scv);
                  }
  
                  fixturesLayout->addWidget(console);
                  c++;
              }
              fixturesLayout->addStretch(1);
              scrollArea->setWidget(grpBox);
          }
      }
      // tabbed fixtures view mode
      else
      {
          QListIterator <Fixture*> it(selectedFixtures());
          while (it.hasNext() == true)
          {
              Fixture* fixture = it.next();
              Q_ASSERT(fixture != NULL);
  
              addFixtureTab(fixture);
  
              QListIterator <SceneValue> it(m_scene->values());
              while (it.hasNext() == true)
              {
                  SceneValue scv(it.next());
                  if (applyValues == false)
                      scv.value = 0;
                  if (scv.fxi == fixture->id())
                      setSceneValue(scv);
              }
          }
      }
  
      m_scene->setUiStateValue(UI_STATE_TAB_MODE, tabbed ? UI_STATE_TABBED_FIXTURES : UI_STATE_ALL_FIXTURES);
  
      if (m_tab->count() == 0)
      {
          slotTabChanged(KTabGeneral);
      }
      else
      {
          QVariant tabIndex = m_scene->uiStateValue(UI_STATE_TAB_INDEX);
          int prevTabIdx = tabIndex.isValid() ? tabIndex.toInt() : 0;
          if (prevTabIdx > m_tab->count())
              m_tab->setCurrentIndex(m_fixtureFirstTabIndex);
          else
              m_tab->setCurrentIndex(prevTabIdx);
      }
  
      m_scene->setUiStateValue(UI_STATE_TAB_INDEX, m_tab->currentIndex());
  }
  
  void SceneEditor::slotRecord()
  {
      Chaser* chaser = selectedChaser();
      if (chaser == NULL)
          return;
  
      QString name = chaser->name() + QString(" - %1").arg(chaser->steps().size() + 1);
      Scene* clone = new Scene(m_doc);
      clone->copyFrom(m_scene);
      clone->setName(name);
      m_doc->addFunction(clone);
      chaser->addStep(ChaserStep(clone->id()));
  
      // Switch to the cloned scene
      FunctionManager::instance()->selectFunction(clone->id());
  }
  
  void SceneEditor::slotChaserComboActivated(int index)
  {
      quint32 id = m_chaserCombo->itemData(index).toUInt();
      if (id == Function::invalidId())
          m_recordAction->setEnabled(false);
      else
          m_recordAction->setEnabled(true);
  }
  
  bool SceneEditor::isColorToolAvailable()
  {
      Fixture* fxi = NULL;
      quint32 cyan = QLCChannel::invalid(), magenta = QLCChannel::invalid(), yellow = QLCChannel::invalid();
      quint32 red = QLCChannel::invalid(), green = QLCChannel::invalid(), blue = QLCChannel::invalid();
  
      /* QObject cast fails unless the widget is a FixtureConsole */
      FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
      if (fc != NULL)
      {
          fxi = m_doc->fixture(fc->fixture());
          Q_ASSERT(fxi != NULL);
  
          cyan = fxi->channel(QLCChannel::Intensity, QLCChannel::Cyan);
          magenta = fxi->channel(QLCChannel::Intensity, QLCChannel::Magenta);
          yellow = fxi->channel(QLCChannel::Intensity, QLCChannel::Yellow);
          red = fxi->channel(QLCChannel::Intensity, QLCChannel::Red);
          green = fxi->channel(QLCChannel::Intensity, QLCChannel::Green);
          blue = fxi->channel(QLCChannel::Intensity, QLCChannel::Blue);
      }
  
      GroupsConsole* gc = groupConsoleTab(m_currentTab);
      if (gc != NULL)
      {
          cyan = magenta = yellow = red = green = blue = QLCChannel::invalid();
          foreach(ConsoleChannel *cc, gc->groups())
          {
              fxi = m_doc->fixture(cc->fixture());
              Q_ASSERT(fxi != NULL);
              const QLCChannel *ch = fxi->channel(cc->channelIndex());
              if (ch->group() == QLCChannel::Intensity)
              {
                  if (ch->colour() == QLCChannel::Red)
                      red = 1;
                  else if (ch->colour() == QLCChannel::Green)
                      green = 1;
                  else if (ch->colour() == QLCChannel::Blue)
                      blue = 1;
                  else if (ch->colour() == QLCChannel::Magenta)
                      magenta = 1;
                  else if (ch->colour() == QLCChannel::Yellow)
                      yellow = 1;
                  else if (ch->colour() == QLCChannel::Cyan)
                      cyan = 1;
              }
          }
      }
  
      if (cyan != QLCChannel::invalid() && magenta != QLCChannel::invalid() &&
          yellow != QLCChannel::invalid())
      {
          return true;
      }
      else if (red != QLCChannel::invalid() && green != QLCChannel::invalid() &&
               blue != QLCChannel::invalid())
      {
          return true;
      }
      else
      {
          return false;
      }
  }
  
  bool SceneEditor::isPositionToolAvailable()
  {
      Fixture* fxi = NULL;
  
      /* QObject cast fails unless the widget is a FixtureConsole */
      FixtureConsole* fc = fixtureConsoleTab(m_currentTab);
      if (fc != NULL)
      {
          fxi = m_doc->fixture(fc->fixture());
          Q_ASSERT(fxi != NULL);
  
          for (int i = 0; i < fxi->heads(); ++i)
          {
              if (fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, i) != QLCChannel::invalid())
                  return true;
              if (fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, i) != QLCChannel::invalid())
                  return true;
          }
      }
  
      GroupsConsole* gc = groupConsoleTab(m_currentTab);
      if (gc != NULL)
      {
          foreach(ConsoleChannel *cc, gc->groups())
          {
              fxi = m_doc->fixture(cc->fixture());
              Q_ASSERT(fxi != NULL);
              const QLCChannel *ch = fxi->channel(cc->channelIndex());
              if (ch->group() == QLCChannel::Pan || ch->group() == QLCChannel::Tilt)
                  return true;
          }
      }
  
      return false;
  }
  
  void SceneEditor::createSpeedDials()
  {
      if (m_speedDials != NULL)
          return;
  
      m_speedDials = new SpeedDialWidget(this);
      m_speedDials->setAttribute(Qt::WA_DeleteOnClose);
      m_speedDials->setWindowTitle(m_scene->name());
      m_speedDials->setFadeInSpeed(m_scene->fadeInSpeed());
      m_speedDials->setFadeOutSpeed(m_scene->fadeOutSpeed());
      m_speedDials->setDurationEnabled(false);
      m_speedDials->setDurationVisible(false);
      connect(m_speedDials, SIGNAL(fadeInChanged(int)), this, SLOT(slotFadeInChanged(int)));
      connect(m_speedDials, SIGNAL(fadeOutChanged(int)), this, SLOT(slotFadeOutChanged(int)));
      connect(m_speedDials, SIGNAL(destroyed(QObject*)), this, SLOT(slotDialDestroyed(QObject*)));
      m_speedDials->show();
  }
  
  void SceneEditor::slotDialDestroyed(QObject *)
  {
      m_speedDialAction->setChecked(false);
  }
  
  Chaser* SceneEditor::selectedChaser() const
  {
      QVariant var = m_chaserCombo->itemData(m_chaserCombo->currentIndex());
      if (var.isValid() == false)
          return NULL;
      else
          return qobject_cast<Chaser*> (m_doc->function(var.toUInt()));
  }
  
  /*****************************************************************************
   * General page
   *****************************************************************************/
  
  QTreeWidgetItem* SceneEditor::fixtureItem(quint32 fxi_id)
  {
      QTreeWidgetItemIterator it(m_tree);
      while (*it != NULL)
      {
          QTreeWidgetItem* item = *it;
          if (item->text(KColumnID).toUInt() == fxi_id)
              return item;
          ++it;
      }
  
      return NULL;
  }
  
  QList <Fixture*> SceneEditor::selectedFixtures() const
  {
      QListIterator <QTreeWidgetItem*> it(m_tree->selectedItems());
      QList <Fixture*> list;
  
      while (it.hasNext() == true)
      {
          QTreeWidgetItem* item;
          quint32 fxi_id;
          Fixture* fixture;
  
          item = it.next();
          fxi_id = item->text(KColumnID).toInt();
          fixture = m_doc->fixture(fxi_id);
          Q_ASSERT(fixture != NULL);
  
          list.append(fixture);
      }
  
      return list;
  }
  
  bool SceneEditor::addFixtureItem(Fixture* fixture)
  {
      Q_ASSERT(fixture != NULL);
  
      // check if the fixture is already there
      for (int i = 0; i < m_tree->topLevelItemCount(); i++)
      {
          QTreeWidgetItem *fix = m_tree->topLevelItem(i);
          if (fix != NULL)
          {
              quint32 fxid = fix->text(KColumnID).toUInt();
              if (fxid == fixture->id())
                  return false;
          }
      }
  
      QTreeWidgetItem* item;
  
      item = new QTreeWidgetItem(m_tree);
      item->setText(KColumnName, fixture->name());
      item->setText(KColumnID, QString("%1").arg(fixture->id()));
  
      if (fixture->fixtureDef() == NULL)
      {
          item->setText(KColumnManufacturer, tr("Generic"));
          item->setText(KColumnModel, tr("Generic"));
      }
      else
      {
          item->setText(KColumnManufacturer,
                        fixture->fixtureDef()->manufacturer());
          item->setText(KColumnModel, fixture->fixtureDef()->model());
      }
  
      /* Select newly-added fixtures so that their channels can be
         quickly disabled/enabled */
      item->setSelected(true);
  
      return true;
  }
  
  void SceneEditor::removeFixtureItem(quint32 fixtureID)
  {
      QTreeWidgetItem *item;
  
      item = fixtureItem(fixtureID);
      delete item;
  }
  
  void SceneEditor::slotNameEdited(const QString& name)
  {
      m_scene->setName(name);
      if (m_speedDials != NULL)
          m_speedDials->setWindowTitle(m_scene->name());
  }
  
  void SceneEditor::slotAddFixtureClicked()
  {
      /* Put all fixtures already present into a list of fixtures that
         will be disabled in the fixture selection dialog */
      QList <quint32> disabled;
      QTreeWidgetItemIterator twit(m_tree);
      while (*twit != NULL)
      {
          disabled.append((*twit)->text(KColumnID).toInt());
          twit++;
      }
  
      /* Get a list of new fixtures to add to the scene */
      FixtureSelection fs(this, m_doc);
      fs.setMultiSelection(true);
      fs.setDisabledFixtures(disabled);
      if (fs.exec() == QDialog::Accepted)
      {
          QListIterator <quint32> it(fs.selection());
          while (it.hasNext() == true)
          {
              Fixture *fixture = m_doc->fixture(it.next());
              Q_ASSERT(fixture != NULL);
  
              addFixtureItem(fixture);
              addFixtureTab(fixture);
  
              // Add fixture in scene
              m_scene->addFixture(fixture->id());
          }
      }
  }
  
  void SceneEditor::slotRemoveFixtureClicked()
  {
      int r = QMessageBox::question(
                  this, tr("Remove fixtures"),
                  tr("Do you want to remove the selected fixture(s)?"),
                  QMessageBox::Yes, QMessageBox::No);
  
      if (r == QMessageBox::Yes)
      {
          QListIterator <Fixture*> it(selectedFixtures());
          while (it.hasNext() == true)
          {
              Fixture* fixture = it.next();
              Q_ASSERT(fixture != NULL);
  
              removeFixtureTab(fixture->id());
              removeFixtureItem(fixture->id());
  
              /* Remove all values associated to the fixture */
              for (quint32 i = 0; i < fixture->channels(); i++)
                  m_scene->unsetValue(fixture->id(), i);
  
              // Remove fixture from scene
              m_scene->removeFixture(fixture->id());
          }
      }
  }
  
  void SceneEditor::slotEnableAll()
  {
      foreach (FixtureConsole* fc, m_consoleList.values())
      {
          if (fc != NULL)
              fc->setChecked(true);
      }
  }
  
  void SceneEditor::slotDisableAll()
  {
      foreach (FixtureConsole* fc, m_consoleList.values())
      {
          if (fc != NULL)
              fc->setChecked(false);
      }
  }
  
  void SceneEditor::slotFadeInChanged(int ms)
  {
      m_scene->setFadeInSpeed(ms);
  }
  
  void SceneEditor::slotFadeOutChanged(int ms)
  {
      m_scene->setFadeOutSpeed(ms);
  }
  
  void SceneEditor::slotEnableAllChannelGroups()
  {
      for (int i = 0; i < m_channelGroupsTree->topLevelItemCount(); i++)
      {
          QTreeWidgetItem *item = m_channelGroupsTree->topLevelItem(i);
          item->setCheckState(KColumnName, Qt::Checked);
      }
  }
  
  void SceneEditor::slotDisableAllChannelGroups()
  {
      for (int i = 0; i < m_channelGroupsTree->topLevelItemCount(); i++)
      {
          QTreeWidgetItem *item = m_channelGroupsTree->topLevelItem(i);
          item->setCheckState(KColumnName, Qt::Unchecked);
      }
  }
  
  void SceneEditor::slotChannelGroupsChanged(QTreeWidgetItem *item, int column)
  {
      if (item == NULL)
          return;
  
      quint32 grpID = item->data(column, Qt::UserRole).toUInt();
      ChannelsGroup *grp = m_doc->channelsGroup(grpID);
      if (grp == NULL)
          return;
  
      if (item->checkState(column) == Qt::Checked)
      {
          m_scene->addChannelGroup(grpID);
          foreach (SceneValue val, grp->getChannels())
          {
              Fixture *fixture = m_doc->fixture(val.fxi);
              if (fixture != NULL)
              {
                  if (addFixtureItem(fixture) == true)
                      addFixtureTab(fixture, val.channel);
                  else
                      setTabChannelState(true, fixture, val.channel);
              }
          }
      }
      else
      {
          m_scene->removeChannelGroup(grpID);
          foreach (SceneValue val, grp->getChannels())
          {
              Fixture *fixture = m_doc->fixture(val.fxi);
              if (fixture != NULL)
                  setTabChannelState(false, fixture, val.channel);
          }
      }
  
      qDebug() << Q_FUNC_INFO << "Groups in list: " << m_scene->channelGroups().count();
  
      updateChannelsGroupsTab();
  }
  
  /*********************************************************************
   * Channels groups tabs
   *********************************************************************/
  void SceneEditor::updateChannelsGroupsTab()
  {
      QScrollArea* scrollArea = NULL;
      QList <quint32> ids = m_scene->channelGroups();
  
      if (m_channelGroupsTree->topLevelItemCount() == 0)
      {
          m_fixtureFirstTabIndex = 1;
          return;
      }
  
      /* Get a scroll area for the console */
      if (m_channelGroupsTab != -1)
      {
          scrollArea = qobject_cast<QScrollArea*> (m_tab->widget(m_channelGroupsTab));
          Q_ASSERT(scrollArea != NULL);
          GroupsConsole *tmpGrpConsole = qobject_cast<GroupsConsole*> (scrollArea->widget());
          Q_ASSERT(tmpGrpConsole != NULL);
          delete tmpGrpConsole;
          if (ids.count() == 0)
          {
              m_tab->removeTab(1);
              m_channelGroupsTab = -1;
              m_fixtureFirstTabIndex = 1;
              return;
          }
      }
      else
      {
          if (ids.count() == 0)
              return;
          scrollArea = new QScrollArea(m_tab);
      }
  
      QList<uchar>levels = m_scene->channelGroupsLevels();
      GroupsConsole* console = new GroupsConsole(scrollArea, m_doc, ids, levels);
      scrollArea->setWidget(console);
      scrollArea->setWidgetResizable(true);
      if (m_channelGroupsTab == -1)
      {
          m_tab->insertTab(1, scrollArea, tr("Channels Groups"));
          m_tab->setTabToolTip(1, tr("Channels Groups"));
      }
  
      m_channelGroupsTab = 1;
      m_fixtureFirstTabIndex = 2;
      connect(console, SIGNAL(groupValueChanged(quint32,uchar)),
              this, SLOT(slotGroupValueChanged(quint32,uchar)));
  }
  
  GroupsConsole *SceneEditor::groupConsoleTab(int tab)
  {
      if (tab != m_channelGroupsTab)
          return NULL;
  
      QScrollArea* area = qobject_cast<QScrollArea*> (m_tab->widget(tab));
      Q_ASSERT(area != NULL);
  
      return qobject_cast<GroupsConsole*> (area->widget());
  }
  
  void SceneEditor::slotGroupValueChanged(quint32 groupID, uchar value)
  {
      // Don't modify m_scene contents when doing initialization
      if (m_initFinished == true)
      {
          Q_ASSERT(m_scene != NULL);
          ChannelsGroup *group = m_doc->channelsGroup(groupID);
          if (group == NULL)
              return;
          foreach (SceneValue scv, group->getChannels())
          {
              Fixture *fixture = m_doc->fixture(scv.fxi);
              if (fixture == NULL)
                  continue;
              FixtureConsole *fc = fixtureConsole(fixture);
              if (fc == NULL)
                  continue;
              fc->setValue(scv.channel, value);
          }
          m_scene->setChannelGroupLevel(groupID, value);
      }
  }
  
  /*****************************************************************************
   * Fixture tabs
   *****************************************************************************/
  
  FixtureConsole* SceneEditor::fixtureConsole(Fixture* fixture)
  {
      Q_ASSERT(fixture != NULL);
  
      if (m_consoleList.contains(fixture->id()))
          return m_consoleList[fixture->id()];
  
      return NULL;
  }
  
  void SceneEditor::addFixtureTab(Fixture* fixture, quint32 channel)
  {
      Q_ASSERT(fixture != NULL);
  
      /* Put the console inside a scroll area */
      QScrollArea* scrollArea = new QScrollArea(m_tab);
      scrollArea->setWidgetResizable(true);
  
      FixtureConsole* console = new FixtureConsole(scrollArea, m_doc);
      console->setFixture(fixture->id());
      m_consoleList[fixture->id()] = console;
      scrollArea->setWidget(console);
      int tIdx = m_tab->addTab(scrollArea, fixture->name());
      m_tab->setTabToolTip(tIdx, fixture->name());
  
      /* Start off with all channels disabled */
      console->setChecked(false);
  
      connect(console, SIGNAL(valueChanged(quint32,quint32,uchar)),
              this, SLOT(slotValueChanged(quint32,quint32,uchar)));
      connect(console, SIGNAL(checked(quint32,quint32,bool)),
              this, SLOT(slotChecked(quint32,quint32,bool)));
  
      if (channel != QLCChannel::invalid())
          console->setChecked(true, channel);
  }
  
  void SceneEditor::removeFixtureTab(quint32 fixtureID)
  {
      /* Start searching from the first fixture tab */
      for (int i = m_fixtureFirstTabIndex; i < m_tab->count(); i++)
      {
          FixtureConsole* fc = fixtureConsoleTab(i);
          if (fc != NULL && fc->fixture() == fixtureID)
          {
              /* First remove the tab because otherwise Qt might
                 remove two tabs -- undocumented feature, which
                 might be intended or it might not. */
              QScrollArea* area = qobject_cast<QScrollArea*> (m_tab->widget(i));
              Q_ASSERT(area != NULL);
              m_tab->removeTab(i);
              m_consoleList.take(fixtureID);
              delete area; // Deletes also FixtureConsole
              break;
          }
      }
  }
  
  FixtureConsole* SceneEditor::fixtureConsoleTab(int tab)
  {
      if (tab >= m_tab->count() || tab <= 0)
          return NULL;
  
      QScrollArea* area = qobject_cast<QScrollArea*> (m_tab->widget(tab));
      Q_ASSERT(area != NULL);
  
      return qobject_cast<FixtureConsole*> (area->widget());
  }
  
  void SceneEditor::setTabChannelState(bool status, Fixture *fixture, quint32 channel)
  {
      Q_ASSERT(fixture != NULL);
  
      if (channel == QLCChannel::invalid())
          return;
  
      if (m_consoleList.contains(fixture->id()))
          m_consoleList[fixture->id()]->setChecked(status, channel);
  }
  
  void SceneEditor::slotValueChanged(quint32 fxi, quint32 channel, uchar value)
  {
      // Don't modify m_scene contents when doing initialization
      if (m_initFinished == true)
      {
          Q_ASSERT(m_scene != NULL);
  
          if (m_doc->mode() == Doc::Operate)
              m_scene->setValue(SceneValue(fxi, channel, value), m_blindAction->isChecked(), false);
          else
              m_scene->setValue(SceneValue(fxi, channel, value), m_blindAction->isChecked(), true);
          emit fixtureValueChanged(SceneValue(fxi, channel, value), true);
      }
  
      if (m_source != NULL)
          m_source->set(fxi, channel, value);
  }
  
  void SceneEditor::slotChecked(quint32 fxi, quint32 channel, bool state)
  {
      // Don't modify m_scene contents when doing initialization
      if (m_initFinished == true)
      {
          // When a channel is enabled, its current value is emitted with valueChanged().
          // So, state == true case doesn't need to be handled here.
          Q_ASSERT(m_scene != NULL);
          if (state == false)
          {
              m_scene->unsetValue(fxi, channel);
              if (m_source != NULL)
              {
                  m_source->unset(fxi, channel);
                  emit fixtureValueChanged(SceneValue(fxi, channel, 0), false);
              }
          }
      }
  }
  
  void SceneEditor::slotGoToNextTab()
  {
      m_currentTab++;
      if (m_currentTab == m_tab->count())
          m_currentTab = 0;
      m_tab->setCurrentIndex(m_currentTab);
  }
  
  void SceneEditor::slotGoToPreviousTab()
  {
      if (m_currentTab == 0)
          m_currentTab = m_tab->count() - 1;
      else
          m_currentTab--;
      m_tab->setCurrentIndex(m_currentTab);
  }
  