Logo Search packages:      
Sourcecode: kdenlive version File versions

doctrackbase.cpp

/***************************************************************************
                         doctrackbase.cpp  -  description
                            -------------------
   begin                : Fri Apr 12 2002
   copyright            : (C) 2002 by Jason Wood
   email                : jasonwood@blueyonder.co.uk
***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "kdebug.h"
#include "doctrackbase.h"
#include "doctrackvideo.h"
#include "doctracksound.h"
#include "doctrackclipiterator.h"
#include "docclipproject.h"
#include "docclipavfile.h"
#include "clipmanager.h"
#include "effectdesc.h"
#include "effectparamdesc.h"
#include "effectparameter.h"
#include "kdenlivesettings.h"



DocTrackBase::DocTrackBase(DocClipProject * project)
{
    m_mute = false;
    m_blind = false;
    m_sortingEnabled = 1;
    m_collisionDetectionEnabled = 1;
    m_project = project;
    m_selectedClipList.setAutoDelete(false);
    m_unselectedClipList.setAutoDelete(false);
}

DocTrackBase::~DocTrackBase()
{
    m_selectedClipList.setAutoDelete(true);
    m_unselectedClipList.setAutoDelete(true);
}

00050 DocClipProject *DocTrackBase::projectClip()
{
    return m_project;
}

void DocTrackBase::mute(bool muted)
{
    m_mute = muted;
}

bool DocTrackBase::isMute()
{
    return m_mute;
}

void DocTrackBase::blind(bool blinded)
{
    m_blind = blinded;
}

bool DocTrackBase::isBlind()
{
    return m_blind;
}

00075 bool DocTrackBase::addClip(DocClipRef * clip, bool selected)
{
    bool result = false;

    if (canAddClip(clip)) {
        if (KdenliveSettings::videothumbnails() && clip->thumbnail().isNull())
                clip->generateThumbnails();
        
      if (selected) {
          m_selectedClipList.inSort(clip);
          emit signalClipSelected(clip);
      } else {
          m_unselectedClipList.inSort(clip);
      }
      clip->setParentTrack(this, m_project->trackIndex(this));
      emit redrawSection(clip->trackNum(), clip->trackStart(), clip->trackEnd());
      result = true;
    }

    if (result) {
      checkTrackLength();
    }
    return result;
}

bool DocTrackBase::loadClip(DocClipRef * clip)
{
    if (canAddClip(clip)) {
      m_unselectedClipList.inSort(clip);
        clip->setParentTrack(this, m_project->trackIndex(this));
      return true;
    }
    return false;
}

00110 QPtrListIterator < DocClipRef > DocTrackBase::firstClip(GenTime startValue,
    GenTime endValue, bool selected)
{
    QPtrListIterator < DocClipRef >
      itt(selected ? m_selectedClipList : m_unselectedClipList);

    DocClipRef *clip;

    if (itt.isEmpty())
      return itt;

    while ((clip = itt.current()) != 0) {
      if (clip->trackStart() > endValue) {
          // out of range, return iterator with a current() value of null.
          itt.toLast();
          ++itt;
          return itt;
      }
      if (clip->trackStart() + clip->cropDuration() >= startValue) {
          if (clip->trackStart() <= endValue) {
            // this clip is at least partially on screen.
            return itt;
          } else {
            // this clip is not on screen, return iterator with current() value of null
            itt.toLast();
            ++itt;
            return itt;
          }
      }
      ++itt;

    }

    return itt;
}

00146 QPtrListIterator < DocClipRef > DocTrackBase::endClip(GenTime startValue,
    GenTime endValue, bool selected)
{
    QPtrListIterator < DocClipRef > itt(firstClip(startValue, endValue,
          selected));

    DocClipRef *clip;

    if (itt.isEmpty())
      return itt;

    while ((clip = itt.current()) != 0) {
      if (clip->trackStart() > endValue) {
          return itt;
      }
      ++itt;
    }

    return itt;
}

00167 DocClipRef *DocTrackBase::getClipAt(const GenTime & value) const
{
    QPtrListIterator < DocClipRef > u_itt(m_unselectedClipList);
    DocClipRef *file;

    while ((file = u_itt.current())) {
      if (file->trackStart() > value)
          break;
      if (file->trackEnd() > value) {
          return file;
      }
      ++u_itt;
    }

    QPtrListIterator < DocClipRef > s_itt(m_selectedClipList);
    while ((file = s_itt.current())) {
      if (file->trackStart() > value)
          break;
      if (file->trackEnd() > value) {
          return file;
      }
      ++s_itt;
    }

    return 0;
}

GenTime DocTrackBase::spaceLength(const GenTime & value) const
{
    QPtrListIterator < DocClipRef > u_itt(m_unselectedClipList);
    DocClipRef *file;
    GenTime previousTime = GenTime(0.0);
    GenTime nextTime = GenTime(0.0);
    bool foundNext = false;

    while ((file = u_itt.current())) {
      if (file->trackEnd() <= value) {
          previousTime = file->trackEnd();
      }
      else if (file->trackStart() > value) {
          nextTime = file->trackStart();
          foundNext = true;
          break;
      }
      ++u_itt;
    }

    QPtrListIterator < DocClipRef > s_itt(m_selectedClipList);
    while ((file = s_itt.current())) {
      if (file->trackEnd() > previousTime && file->trackEnd() <= value) {
          previousTime = file->trackEnd();
      }
      else if (!foundNext || (file->trackStart() > value && file->trackStart() < nextTime)) {
          nextTime = file->trackStart();
          break;
      }
      ++s_itt;
    }

    if (previousTime >= nextTime) return GenTime(0.0);
    else return (GenTime(0.0) - (nextTime - previousTime));
}

00230 bool DocTrackBase::canAddClips(DocClipRefList clipList)
{
    QPtrListIterator < DocClipRef > itt(clipList);

    for (DocClipRef * clip; (clip = itt.current()) != 0; ++itt) {
      if (!canAddClip(clip))
          return false;
    }

    return true;
}

00242 void DocTrackBase::addClips(DocClipRefList list, bool selected)
{
    QPtrListIterator < DocClipRef > itt(list);

    for (DocClipRef * clip; (clip = itt.current()) != -0; ++itt) {
      addClip(clip, selected);
    }

#warning - this check is redundant at the moment because each "addclip" invocation will check the track length. However,
#warning - we should be looking for a way to disable addClips ability to do this for efficiencty, and at that point it is important
#warning - we checkTracklength here!
    checkTrackLength();
}

00256 bool DocTrackBase::clipExists(DocClipRef * clip)
{
    return ((m_unselectedClipList.findRef(clip) != -1)
      || (m_selectedClipList.findRef(clip) != -1));
}

00262 bool DocTrackBase::removeClip(DocClipRef * clip)
{
    bool result = false;

    if (clip) {
      if ((!m_selectedClipList.remove(clip))
          && (!m_unselectedClipList.remove(clip))) {
          kdError() << "Cannot remove clip from track - doesn't exist!"
            << endl;
          result = false;
      } else {
          clip->setParentTrack(0, -1);
          result = true;
      }
    }

    if (result) {
      emit redrawSection(clip->trackNum(), clip->trackStart(), clip->trackEnd());
      checkTrackLength();
    }

    return result;
}

00286 void DocTrackBase::clipMoved(DocClipRef * clip)
{
    if (m_sortingEnabled < 1)
      return;                 // Don't sort if we aren't supposed to.yet.

    // sanity check
    if (!clip) {
      kdError() <<
          "TrackBase has been alerted that a clip has moved... but no clip specified!"
          << endl;
      return;
    }

    int pos;
    DocClipRefList *list = &m_selectedClipList;
    pos = list->find(clip);
    if (pos == -1) {
      list = &m_unselectedClipList;
      pos = list->find(clip);
      if (pos == -1) {
          kdError() <<
            "Track told that non-existant clip has moved (that's gotta be a bug!)"
            << endl;
          return;
      }
    }

    list->take(pos);
    list->inSort(clip);
    emit redrawSection(clip->trackNum(), clip->trackStart(), clip->trackEnd());
    checkTrackLength();
}

00319 int DocTrackBase::hasSelectedClips()
{
    //return (!m_selectedClipList.isEmpty());
    return (m_selectedClipList.count());
}

00325 DocClipRefList DocTrackBase::selectedClips()
{
     return m_selectedClipList;
}

00330 QPtrListIterator < DocClipRef > DocTrackBase::firstClip(bool selected) const
{
    return QPtrListIterator < DocClipRef >
      (selected ? m_selectedClipList : m_unselectedClipList);
}

00336 void DocTrackBase::moveClips(GenTime offset, bool selected)
{
    enableClipSorting(false);
    enableCollisionDetection(false);

    QPtrListIterator < DocClipRef >
      itt((selected) ? m_selectedClipList : m_unselectedClipList);

    DocClipRef *clip;

    while ((clip = itt.current()) != 0) {
      ++itt;

      clip->moveTrackStart(clip->trackStart() + offset);
    }

    enableCollisionDetection(true);
    enableClipSorting(true);

    checkTrackLength();
}

00358 void DocTrackBase::enableClipSorting(bool enabled)
{
    m_sortingEnabled += (enabled) ? 1 : -1;
}

00363 void DocTrackBase::enableCollisionDetection(bool enable)
{
    m_collisionDetectionEnabled += (enable) ? 1 : -1;
}

00368 DocClipRefList DocTrackBase::removeClips(bool selected)
{
    DocClipRefList & list =
      selected ? m_selectedClipList : m_unselectedClipList;
    DocClipRefList returnList;
    DocClipRef *clip;
    while (!list.isEmpty()) {
      clip = list.first();
      // When we are removing clips and placing them into a list, it is likely that we are removing clips from
      // a number of tracks and putting them into a single list elsewhere. We must wipe the parent track pointer,
      // but by keeping the track hint, we make it easier to relocate the clips on a different timeline or some
      // other similar operation. So, the following line, wipes the clip's parent track but keeps the trackNum.
      clip->setParentTrack(0, clip->trackNum());
      list.removeFirst();
      returnList.append(clip);
    }

    emit clipLayoutChanged(clip->trackNum());
    checkTrackLength();
    return returnList;
}

00390 void DocTrackBase::deleteClips(bool selected)
{
    DocClipRefList *list =
      &(selected ? m_selectedClipList : m_unselectedClipList);

    list->setAutoDelete(true);
    list->clear();
    list->setAutoDelete(false);
    emit clipLayoutChanged(-1);
    checkTrackLength();
}

00402 bool DocTrackBase::clipSelected(DocClipRef * clip) const
{
    return (m_selectedClipList.contains(clip) >= 1);
}

00407 void DocTrackBase::resizeClipTrackStart(DocClipRef * clip,
    GenTime newStart)
{
    if (!clipExists(clip)) {
      kdError() <<
          "Trying to resize non-existant clip! (resizeClipTrackStart)" <<
          endl;
      return;
    }
    bool sizeLimit = true;
    DocClipBase::CLIPTYPE t = clip->clipType();
    if (t == DocClipBase::IMAGE || t == DocClipBase::TEXT || t == DocClipBase::COLOR || (t == DocClipBase::SLIDESHOW && clip->referencedClip()->toDocClipAVFile()->loop() == true)) sizeLimit = false;
    newStart = newStart - clip->trackStart();

    if (clip->cropStartTime() + newStart < GenTime()) {
      kdWarning() << "clip new crop start less than 0, trimming..." <<
          endl;
      newStart = GenTime() - clip->cropStartTime();
    }

    if (!sizeLimit && (clip->cropDuration() - newStart).frames(framesPerSecond()) > MAXFRAMEDURATION)
      newStart = clip->cropDuration() - GenTime(MAXFRAMEDURATION, framesPerSecond());

    if ((clip->cropDuration() - newStart) > clip->duration() / clip->speed() && ( sizeLimit )) {
      kdWarning() <<
          "clip new crop duration will be more than clip duration, trimming..."
          << endl;
      
      newStart = clip->cropDuration() - clip->duration() / clip->speed();
    }

    if (clip->cropDuration() - newStart < GenTime()) {
      kdWarning() <<
          "Clip cannot be resized to length < 1 frame, fixing..." <<
          endl;
      newStart =
          clip->cropDuration() - GenTime(1,
          m_project->framesPerSecond());
    }

    // Check that we are not trying to overlap the previous clip.
    DocTrackClipIterator itt(*this);
    DocClipRef *previousClip = 0;

    while (itt.current() && (itt.current() != clip)) {
      previousClip = itt.current();
      ++itt;
    }

    if (previousClip && (itt.current() == clip)) {
      if (previousClip->trackEnd() > newStart + clip->trackStart()) {
          kdWarning() <<
            "clip start may not overlap previous clip, trimming..." <<
            endl;
          newStart = previousClip->trackEnd() - clip->trackStart();
      }
    }

    GenTime repaintStart;
    if (newStart < GenTime()) repaintStart = clip->trackStart() + newStart;
    else repaintStart = clip->trackStart();

    clip->setTrackStart(clip->trackStart() + newStart);
    clip->setCropStartTime(clip->cropStartTime() + newStart);

    emit redrawSection(clip->trackNum(), repaintStart, clip->trackEnd());
    // request for new start clip thumbnail
    if (KdenliveSettings::videothumbnails() && clip->hasVariableThumbnails()) clip->fetchStartThumbnail();
    // Just in case : although resizeClipTrackStart should never cause the length of a track to change.
    checkTrackLength();
}

00479 void DocTrackBase::resizeClipTrackEnd(DocClipRef * clip, GenTime newEnd)
{
    if (!clipExists(clip)) {
      kdError() <<
          "Trying to resize non-existant clip! (resizeClipCropDuration)"
          << endl;
      return;
    }

    GenTime cropDuration = newEnd - clip->trackStart();
    bool sizeLimit = true;
    DocClipBase::CLIPTYPE t = clip->clipType();
    if (t == DocClipBase::IMAGE || t == DocClipBase::TEXT || t == DocClipBase::COLOR || (t == DocClipBase::SLIDESHOW && clip->referencedClip()->toDocClipAVFile()->loop() == true)) sizeLimit = false;
    // If clip is a video, audio or slideshow, make sure user cannot make it bigger than possible
    if (sizeLimit && cropDuration > clip->duration() / clip->speed() - clip->cropStartTime()) {
      kdWarning() <<
          "clip new crop end greater than duration, trimming..." << endl;
      newEnd =
          clip->duration() / clip->speed() - clip->cropStartTime() + clip->trackStart();
      cropDuration = newEnd - clip->trackStart();
    }
    else if (!sizeLimit && cropDuration.frames(framesPerSecond()) > MAXFRAMEDURATION) {
      cropDuration =  GenTime(MAXFRAMEDURATION, framesPerSecond());
      newEnd = clip->trackStart() + cropDuration;
    }

    if (newEnd < clip->trackStart()) {
      kdWarning() <<
          "Clip cannot be resized to < 1 frame in size, fixing..." <<
          endl;
      newEnd =
          clip->trackStart() + GenTime(1, m_project->framesPerSecond());
    }
    // Check that we are not overlapping the next clip on the track.
    DocTrackClipIterator itt(*this);
    DocClipRef *nextClip = 0;

    while (itt.current() && (itt.current() != clip)) {
      ++itt;
    }

    if (itt.current()) {
      ++itt;
      nextClip = itt.current();
    } else {
      kdError() <<
          "Trying to resize clip that does not exist on specified track!!!"
          << endl;
    }

    if (nextClip && (nextClip != clip)) {
      if (nextClip->trackStart() < newEnd) {
          newEnd = nextClip->trackStart();
      }
    }

    GenTime repaintEnd;
    if (newEnd < clip->trackEnd()) repaintEnd = clip->trackEnd();
    else repaintEnd = newEnd;

    clip->setTrackEnd(newEnd);

    emit redrawSection(clip->trackNum(), clip->trackStart(), repaintEnd);
    // request for new end clip thumbnail
    if (clip->hasVariableThumbnails()) clip->fetchEndThumbnail();
    checkTrackLength();
}

/** Returns the total length of the track - in other words, it returns the end of the
last clip on the track. */
00549 const GenTime & DocTrackBase::trackLength() const
{
    return m_trackLength;
}

/** Returns the number of clips contained in this track. */
00555 unsigned int DocTrackBase::numClips() const
{
    return m_selectedClipList.count() + m_unselectedClipList.count();
}

/** Creates a track from the given xml document. Returns the track, or 0 if it could not be created. */
DocTrackBase *DocTrackBase::
00562 createTrack(KdenliveDoc *doc, DocClipProject * project, QDomElement elem)
{
    if (elem.tagName() != "kdenlivetrack") {
      kdError() <<
          "Cannot create track from QDomElement - has wrong tag name : "
          << elem.tagName() << endl;
      return 0;
    }

    QString clipType = elem.attribute("cliptype", "unknown");

    DocTrackBase *track;

    if (clipType == "Video") {
      track = new DocTrackVideo(project);
    } else if (clipType == "Sound") {
      track = new DocTrackSound(project);
    } else {
      kdError() << "Unknown track clip type '" << clipType <<
          "' - cannot create track" << endl;
      return 0;
    }

    if (elem.attribute("muted", "0") == "1") track->mute(true);
    if (elem.attribute("hidden", "0") == "1") track->blind(true);

    QDomNode n = elem.firstChild();

    while (!n.isNull()) {
      QDomElement e = n.toElement();
      if (!e.isNull()) {
          if (e.tagName() == "kdenliveclip") {
            DocClipRef *clip =
                DocClipRef::createClip(doc, e);
            if (clip) {
                track->loadClip(clip);
            } else {
                kdWarning() <<
                  "Clip generation failed, skipping clip..." << endl;
            }
          } else {
            kdWarning() << "Unknown tag " << e.
                tagName() << ", skipping..." << endl;
          }
      }

      n = n.nextSibling();
    }
    track->checkTrackLength();
    return track;
}

/** Alerts the track that it's trackIndex within the document has
changed. The track should update the clips on it with the new
index value. */
00617 void DocTrackBase::trackIndexChanged(int index)
{
    DocTrackClipIterator itt(*this);

    while (itt.current()) {
      itt.current()->setParentTrack(this, index);
      ++itt;
    }
}


bool DocTrackBase::openClip(DocClipRef * clip)
{
    if (clip) {
      emit signalOpenClip(clip);
      }
}

/** Sets the specified clip to be in the specified selection state. Does nothing if the clip is not on the track. */
00636 bool DocTrackBase::selectClip(DocClipRef * clip, bool selected)
{
    bool result = false;

    if (clip) {
      if ((!m_selectedClipList.take(m_selectedClipList.find(clip))) &&
          (!m_unselectedClipList.take(m_unselectedClipList.
                find(clip)))) {
          kdError() << "Cannot select clip on track - doesn't exist!" <<
            endl;
          result = false;
      } else {
          if (selected) {
            m_selectedClipList.inSort(clip);
            emit signalClipSelected(clip);
          } else {
            m_unselectedClipList.inSort(clip);
            emit signalClipSelected(0);
          }
          result = true;
          emit clipSelectionChanged();
      }
    }

    return result;
}

00663 bool DocTrackBase::referencesClip(DocClipBase * clip) const
{
    bool result = false;
    DocTrackClipIterator itt(*this);

    while (itt.current()) {
      if (itt.current()->referencesClip(clip)) {
          result = true;
          break;
      }
      ++itt;
    }

    return result;
}

00679 DocClipRefList DocTrackBase::referencedClips(DocClipBase * clip) const
{
    DocClipRefList list;

    DocTrackClipIterator itt(*this);

    while (itt.current()) {
      if (itt.current()->referencesClip(clip)) {
          list.append(itt.current());
      }
      ++itt;
    }

    return list;
}

00695 double DocTrackBase::framesPerSecond() const
{
    double result = 1;

    if (m_project) {
      result = m_project->framesPerSecond();
    } else {
      kdError() <<
          "DocTrackBase is not in a project clip - cannot determine frames per second."
          << endl;
    }

    return result;
}

/** Returns an xml representation of this track. */
00711 QDomDocument DocTrackBase::toXML()
{
    QDomDocument doc;

    doc.appendChild(doc.createElement("kdenlivetrack"));
    doc.documentElement().setAttribute("cliptype", clipType());
    doc.documentElement().setAttribute("muted", isMute());
    doc.documentElement().setAttribute("hidden", isBlind());

    DocTrackClipIterator itt(*this);

    while (itt.current()) {
      doc.documentElement().appendChild(doc.importNode(itt.current()->
            toXML().documentElement(), true));
      ++itt;
    }

    return doc;
}

00731 bool DocTrackBase::matchesXML(const QDomElement & element) const
{
    bool result = false;

    if (element.tagName() == "kdenlivetrack") {
      if (element.attribute("cliptype") == clipType()) {
          QDomNodeList nodeList = element.elementsByTagName("kdenliveclip");

          if (nodeList.length() == numClips()) {
            result = true;

            DocTrackClipIterator itt(*this);
            uint count = 0;

            while (itt.current()) {
                QDomElement clipElement =
                  nodeList.item(count).toElement();

                if (!clipElement.isNull()) {
                  if (!itt.current()->matchesXML(clipElement)) {
                      result = false;
                      break;
                  }
                } else {
                  result = false;
                  break;
                }
                ++count;
                ++itt;
            }
          }
      }
    }

    return result;
}

00768 void DocTrackBase::notifyClipChanged(DocClipRef * clip)
{
    emit redrawSection(clip->trackNum(), clip->trackStart(), clip->trackEnd());
    //checkTrackLength();
}

void DocTrackBase::notifyTrackChanged(DocClipRef * clip)
{
    emit redrawSection(clip->trackNum(), GenTime(0), GenTime(0));
    checkTrackLength();
}

00780 void DocTrackBase::checkTrackLength()
{
    GenTime slength;
    GenTime ulength;

    if (!m_selectedClipList.isEmpty()) {
      slength =
          m_selectedClipList.last()->trackStart() +
          m_selectedClipList.last()->cropDuration();
    }

    if (!m_unselectedClipList.isEmpty()) {
      ulength =
          m_unselectedClipList.last()->trackStart() +
          m_unselectedClipList.last()->cropDuration();
    }

    GenTime newTrackLength = (slength > ulength) ? slength : ulength;

    if (m_trackLength != newTrackLength) {
      m_trackLength = newTrackLength;
      emit trackLengthChanged(m_trackLength);
    }

}

00806 void DocTrackBase::addEffectToClip(const GenTime & position,
    int effectIndex, Effect * effect)
{
    DocClipRef *clip = getClipAt(position);
    if (clip) {
      // If a new effect is inserted, create default keyframes at end and beginning
      if (effect->parameter(0) != NULL
          && (effect->effectDescription().parameter(0)->type() ==
            "double"
            || effect->effectDescription().parameter(0)->type() ==
            "complex") && effect->parameter(0)->numKeyFrames() == 0) {
          effect->addInitialKeyFrames(0);
      }

      clip->addEffect(effectIndex, effect);
      emit effectStackChanged(clip);
      emit redrawSection(clip->trackNum(), clip->trackStart(), clip->trackEnd());
    } else {
      kdError() <<
          "DocTrackBase::addEffectToClip() - cannot find clip at position "
          << position.seconds() << endl;
    }
}

00830 void DocTrackBase::deleteEffectFromClip(const GenTime & position,
    int effectIndex)
{
    DocClipRef *clip = getClipAt(position);
    if (clip) {
      clip->deleteEffect(effectIndex);
      emit effectStackChanged(clip);
    } else {
      kdError() <<
          "DocTrackBase::deleteEffectFromClip() - cannot find clip at position "
          << position.seconds() << endl;
    }
}

00844 void DocTrackBase::refreshLayout() {
    emit clipLayoutChanged(-1);
}


Generated by  Doxygen 1.6.0   Back to index