You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeaddons/noatun-plugins/oblique/tree.cpp

813 lines
15 KiB

// Copyright (c) 2003 Charles Samuels <charles@kde.org>
// See the file COPYING for redistribution terms.
#include "tree.h"
#include "file.h"
#include "query.h"
#include "menu.h"
#include "oblique.h"
#include <tqpainter.h>
#include <iostream>
#include <tdelocale.h>
#include <string.h>
#include <noatun/player.h>
// this is used for comparing pointers
// (I should _not_ need this)
template <typename T>
inline static long subtract(const T *end, const T *begin)
{
return long(end-begin);
}
static void treeItemMerge(
TreeItem **set,
TreeItem **intofirst, TreeItem **intolast,
TreeItem **fromfirst, TreeItem **fromlast
)
{
const int items = subtract(intolast, intofirst) + subtract(fromlast, fromfirst)+2;
TreeItem **temp = new TreeItem*[items];
TreeItem **tempat = temp;
while (1)
{
if (intofirst[0]->compare(fromfirst[0], 0, true) >= 0)
{
// from goes before into
*tempat = *fromfirst;
tempat++;
fromfirst++;
if (fromfirst > fromlast) break;
}
else
{
*tempat = *intofirst;
tempat++;
intofirst++;
if (intofirst > intolast) break;
}
}
while (intofirst <= intolast)
*tempat++ = *intofirst++;
while (fromfirst <= fromlast)
*tempat++ = *fromfirst++;
::memcpy(set, temp, items*sizeof(TreeItem**));
delete [] temp;
}
static void treeItemSort(TreeItem **begin, TreeItem **end)
{
if (begin == end) return;
TreeItem **middle = subtract(end, begin)/2 + begin;
if (begin != middle)
treeItemSort(begin, middle);
if (middle+1 != end)
treeItemSort(middle+1, end);
treeItemMerge(begin, begin, middle, middle+1, end);
}
static void treeItemSort(TreeItem *first)
{
const int count = first->parent() ? first->parent()->childCount() : first->listView()->childCount();
if (count < 2) return;
Query *q = first->tree()->query();
TreeItem **set = new TreeItem*[count];
int manually = 0; // I store these starting at the end (of set)
int at=0; // I store these starting at the beginning
for (TreeItem *i = first; i; i = i->nextSibling())
{
File after;
if (i->file() && i->file().getPosition(q, &after))
{
set[count-manually-1] = i;
manually++;
}
else
{
set[at] = i;
at++;
}
}
assert(count == at + manually);
if (at > 1)
treeItemSort(set, set+count-manually-1);
// grr, TQListView sucks
set[0]->moveItem(set[1]);
TreeItem *previous = set[0];
int manualPosition = count - manually;
for (int i=1; i <count-manually; i++)
{
File maybeafter = previous->file();
// perhaps one of the manually sorted ones fit here..
for (int mi = manualPosition; mi < count; mi++)
{
TreeItem *now = set[mi];
File after;
if (now->file() && now->file().getPosition(q, &after))
{
if (after == maybeafter)
{
now->moveItem(previous);
previous = now;
// just try again now, as another manually sorted item
// may be after previous
maybeafter = previous->file();
manualPosition++;
}
}
}
set[i]->moveItem(previous);
previous = set[i];
}
delete [] set;
}
template <class T>
inline static void sortify(T *item)
{
treeItemSort(item->firstChild());
}
TreeItem::TreeItem(Tree *parent, QueryGroup *group, const File &file, const TQString &p)
: TDEListViewItem(parent, p), mGroup(group), mUserOpened(false), mHidden(false)
{
if (group->option(QueryGroup::Playable))
{
if (mFile = file)
parent->mPlayableItemCount++;
}
sortify(parent);
}
TreeItem::TreeItem(TreeItem *parent, QueryGroup *group, const File &file, const TQString &p)
: TDEListViewItem(parent, p), mGroup(group), mUserOpened(false), mHidden(false)
{
if (group->option(QueryGroup::Playable))
{
if (mFile = file)
parent->tree()->mPlayableItemCount++;
}
sortify(parent);
}
TreeItem::~TreeItem()
{
if (playable())
{
tree()->mPlayableItemCount--;
}
// I have to remove my children, because they need their parent
// in tact for the below code
while (TreeItem *c = firstChild())
delete c;
tree()->deleted(this);
}
void Tree::deleted(TreeItem *item)
{
mAutoExpanded.removeRef(item);
if (current() == item)
{
oblique()->next();
}
}
static void pad(TQString &str)
{
int len=str.length();
int at = 0;
int blocklen=0;
static const int paddingsize=12;
// not static for reason
const TQChar chars[paddingsize] =
{
TQChar('0'), TQChar('0'), TQChar('0'), TQChar('0'),
TQChar('0'), TQChar('0'), TQChar('0'), TQChar('0'),
TQChar('0'), TQChar('0'), TQChar('0'), TQChar('0')
};
for (int i=0; i < len; i++)
{
if (str[i].isNumber())
{
if (!blocklen)
at = i;
blocklen++;
}
else if (blocklen)
{
int pads=paddingsize;
pads -= blocklen;
str.insert(at, chars, pads);
i += pads;
blocklen = 0;
}
}
if (blocklen)
{
int pads=paddingsize;
pads -= blocklen;
str.insert(at, chars, pads);
}
}
int TreeItem::compare(TQListViewItem *i, int col, bool) const
{
TQString text1 = text(col);
TQString text2 = i->text(col);
pad(text1);
pad(text2);
return text1.compare(text2);
}
Tree *TreeItem::tree()
{
return static_cast<Tree*>(TDEListViewItem::listView());
}
TQString TreeItem::presentation() const
{
return text(0);
}
TreeItem *TreeItem::find(File item)
{
TreeItem *i = firstChild();
while (i)
{
if (i->file() == item) return i;
TreeItem *found = i->find(item);
if (found and found->playable()) return found;
i = i->nextSibling();
}
return 0;
}
bool TreeItem::playable() const
{
return mFile && mGroup->option(QueryGroup::Playable);
}
TreeItem *TreeItem::nextPlayable()
{
TreeItem *next=this;
do
{
next = next->next();
} while (next && !next->playable());
return next;
}
void TreeItem::paintCell(TQPainter *p, const TQColorGroup &cg, int column, int width, int align)
{
TQFont font = p->font();
if (tree()->current() == this)
{
font.setUnderline(true);
p->setFont(font);
}
TQColorGroup newcg(cg);
if (parent() && parent()->isOpen() && !parent()->mUserOpened)
{
// slow, but not often used
TQColor text = newcg.text();
TQColor bg = newcg.background();
int r = text.red() + bg.red();
int g = text.green() + bg.green();
int b = text.blue() + bg.blue();
text.setRgb(r/2,g/2,b/2);
newcg.setColor(TQColorGroup::Text, text);
}
TDEListViewItem::paintCell(p, newcg, column, width, align);
font.setUnderline(false);
p->setFont(font);
}
void TreeItem::setOpen(bool o)
{
if (!tree()->autoExpanding())
{
mUserOpened = o;
tree()->removeAutoExpanded(this);
}
TDEListViewItem::setOpen(o);
}
void TreeItem::autoExpand()
{
tree()->setAutoExpanding(true);
if (tree()->current() == this)
{
tree()->resetAutoExpanded();
forceAutoExpand();
}
tree()->setAutoExpanding(false);
}
void TreeItem::forceAutoExpand()
{
if (parent())
parent()->forceAutoExpand();
if (!mUserOpened)
tree()->addAutoExpanded(this);
setOpen(true);
}
bool TreeItem::hideIfNoMatch(const TQString &match)
{
if (!firstChild())
{
if (match.length())
{
if (!text(0).contains(match, false))
{
setHidden(true);
return false;
}
}
setHidden(false);
return true;
}
else
{
bool visible=true;
if (match.length())
{
visible = text(0).contains(match, false);
}
if (visible)
{
TQString empty;
for (TreeItem *ch = firstChild(); ch; ch = ch->nextSibling())
{
ch->hideIfNoMatch(empty);
}
}
else
{
for (TreeItem *ch = firstChild(); ch; ch = ch->nextSibling())
{
bool here = ch->hideIfNoMatch(match);
visible = visible || here;
}
}
setHidden(!visible);
return visible;
}
}
void TreeItem::setup()
{
TQListViewItem::setup();
if (mHidden)
setHeight(0);
}
void TreeItem::setHidden(bool h)
{
mHidden = h;
setup();
}
TreeItem *TreeItem::next()
{
if (firstChild())
{
return firstChild();
}
else
{ // go up the tree
TreeItem *upYours = this;
do
{
if (upYours->nextSibling())
return upYours->nextSibling();
upYours = upYours->parent();
} while (upYours);
}
return 0;
}
Tree::Tree(Oblique *oblique, TQWidget *parent)
: TDEListView(parent), mOblique(oblique), mAutoExpanding(0)
{
mCurrent = 0;
lastMenu =0;
mPlayableItemCount = 0;
mLoader = 0;
addColumn("");
setCaption(i18n("Oblique"));
setRootIsDecorated(true);
setAcceptDrops(true);
setDragEnabled(true);
setItemsMovable(true);
setDropVisualizer(true);
setSorting(-1);
((TQWidget*)header())->hide();
connect(
this, TQ_SIGNAL(moved(TQPtrList<TQListViewItem>&, TQPtrList<TQListViewItem>&, TQPtrList<TQListViewItem>&)),
TQ_SLOT(dropped(TQPtrList<TQListViewItem>&, TQPtrList<TQListViewItem>&, TQPtrList<TQListViewItem>&))
);
connect(
this, TQ_SIGNAL(contextMenu(TDEListView*, TQListViewItem*, const TQPoint&)),
TQ_SLOT(contextMenu(TDEListView*, TQListViewItem*, const TQPoint&))
);
connect(
this, TQ_SIGNAL(executed(TQListViewItem*)),
TQ_SLOT(play(TQListViewItem*))
);
Base *base = oblique->base();
connect(base, TQ_SIGNAL(added(File)), TQ_SLOT(insert(File)));
connect(base, TQ_SIGNAL(removed(File)), TQ_SLOT(remove(File)));
connect(base, TQ_SIGNAL(modified(File)), TQ_SLOT(update(File)));
connect(base, TQ_SIGNAL(addedTo(Slice*, File)), TQ_SLOT(checkInsert(Slice*, File)));
connect(base, TQ_SIGNAL(removedFrom(Slice*, File)), TQ_SLOT(checkRemove(Slice*, File)));
connect(this, TQ_SIGNAL(selected(TreeItem*)), oblique, TQ_SLOT(selected(TreeItem*)));
mSlice = oblique->base()->defaultSlice();
TDEConfigGroup g(TDEGlobal::config(), "oblique");
mFileOfQuery = g.readEntry("schema", "standard");
if (!setSchema(mFileOfQuery))
{
setSchema("standard");
}
}
Tree::~Tree()
{
// have to clear here to prevent sigsegv on exit
clear();
}
void Tree::clear()
{
if (mCurrent)
{
napp->player()->stop();
setCurrent(0);
}
TDEListView::clear();
}
void Tree::movableDropEvent (TQListViewItem* parent, TQListViewItem* afterme)
{
TQPtrList<TQListViewItem> items = selectedItems(true);
for (TQPtrList<TQListViewItem>::Iterator i(items.begin()); *i; ++i)
{
if ((*i)->parent() != parent)
return;
}
TDEListView::movableDropEvent(parent, afterme);
}
void Tree::dropped(TQPtrList<TQListViewItem> &items, TQPtrList<TQListViewItem> &, TQPtrList<TQListViewItem> &afterNow)
{
TQPtrList<TQListViewItem>::Iterator itemi = items.begin();
TQPtrList<TQListViewItem>::Iterator afteri = afterNow.begin();
while (*itemi)
{
TreeItem *item = static_cast<TreeItem*>(*itemi);
TreeItem *after = static_cast<TreeItem*>(*afteri);
item->file().setPosition(query(), after ? after->file() : File());
++itemi;
++afteri;
}
}
TreeItem *Tree::firstChild()
{ return static_cast<TreeItem*>(TDEListView::firstChild()); }
TreeItem *Tree::find(File item)
{
TreeItem *i = firstChild();
while (i)
{
if (i->file() == item) return i;
TreeItem *found = i->find(item);
if (found) return found;
i = i->nextSibling();
}
return i;
}
void Tree::insert(TreeItem *replace, File file)
{
TreeItem *created = collate(replace, file);
if (mCurrent == replace)
{
mCurrent = created;
repaintItem(created);
if (isSelected(replace))
setSelected(created, true);
}
if (created != replace)
{
delete replace;
}
}
void Tree::insert(File file)
{
collate(file);
}
void Tree::remove(File file)
{
remove(firstChild(), file);
}
void Tree::checkInsert(Slice *slice, File f)
{
if (slice == mSlice)
insert(f);
}
void Tree::checkRemove(Slice *slice, File f)
{
if (slice == mSlice)
remove(f);
}
void Tree::update(File file)
{
if (TreeItem *item = find(file))
{
insert(item, file);
}
}
void Tree::remove(TreeItem *ti, const File &file)
{
while (ti)
{
if (ti->file() == file)
{
TreeItem *t = ti->nextSibling();
delete ti;
ti = t;
}
else
{
remove(ti->firstChild(), file);
ti = ti->nextSibling();
}
}
}
void Tree::setCurrent(TreeItem *cur)
{
if (cur == mCurrent) return;
// undo the old one
TreeItem *old = mCurrent;
mCurrent = cur;
TQPtrList<TreeItem> oldAutoExpanded = mAutoExpanded;
mAutoExpanded.clear();
repaintItem(old);
repaintItem(cur);
if (cur) cur->autoExpand();
// do an anti-intersection on oldAutoUpdated and the new mAutoExpanded
for (TQPtrListIterator<TreeItem> i(mAutoExpanded); *i; ++i)
{
oldAutoExpanded.removeRef(*i);
}
bool user=false;
for (TQPtrListIterator<TreeItem> i(oldAutoExpanded); *i; ++i)
{
if ((*i)->userOpened())
{
user = true;
break;
}
}
if (!user)
{
for (TQPtrListIterator<TreeItem> i(oldAutoExpanded); *i; ++i)
{
(*i)->setOpen(false);
}
}
ensureItemVisible(cur);
}
void Tree::reload()
{
delete mLoader;
clear();
mLoader = new Loader(this);
connect(mLoader, TQ_SIGNAL(finished()), TQ_SLOT(destroyLoader()));
}
void Tree::setSlice(Slice *slice)
{
if (mSlice == slice) return;
mSlice = slice;
reload();
}
bool Tree::setSchema(const TQString &name)
{
mFileOfQuery = name;
if (!oblique()->loadSchema(mQuery, name))
return false;
reload();
return true;
}
TQDragObject *Tree::dragObject()
{
if (currentItem() && static_cast<TreeItem*>(currentItem())->file())
return TDEListView::dragObject();
return 0;
}
void Tree::destroyLoader()
{
delete mLoader;
mLoader = 0;
}
void Tree::setLimit(const TQString &text)
{
for (TreeItem *ch = firstChild(); ch; ch = ch->nextSibling())
{
ch->hideIfNoMatch(text);
}
}
void Tree::contextMenu(TDEListView*, TQListViewItem* i, const TQPoint& p)
{
if (!i) return;
delete lastMenu;
lastMenu = new FileMenu(this, oblique(), static_cast<TreeItem*>(i) );
lastMenu->popup(p);
}
void Tree::play(TQListViewItem *_item)
{
if (!_item) return;
TreeItem *item = static_cast<TreeItem*>(_item);
if (item->playable())
emit selected(item);
else
play(item->nextPlayable());
}
TreeItem *Tree::collate(TreeItem *fix, QueryGroup *group, const File &file, TreeItem *childOf)
{
do
{
if (group->matches(file))
{
TreeItem *nodefix=0;
if (fix && fix->group() == group)
nodefix = fix;
TreeItem *item = node(nodefix, group, file, childOf);
TreeItem *ti=0;
if (group->firstChild())
{
ti = collate(fix, group->firstChild(), file, item);
}
if (ti && ti->playable())
return ti;
else if(item && item->playable())
return item;
else
return 0;
}
} while (( group = group->nextSibling()));
return 0;
}
TreeItem *Tree::node(TreeItem *fix, QueryGroup *group, const File &file, TreeItem *childOf)
{
// search childOf's immediate children
TreeItem *children;
if (childOf)
children = childOf->firstChild();
else
children = firstChild();
TQString presentation = group->presentation(file);
while (children)
{
// merging would be done here
bool matches=false;
if (group->fuzzyness(QueryGroup::Case))
{
matches = (children->text(0).lower() == presentation.lower());
}
else
{
matches = (children->text(0) == presentation);
}
matches = matches && !children->group()->option(QueryGroup::Playable);
if (matches)
{
children->setFile(File());
return children;
}
children = children->nextSibling();
}
TreeItem *item;
if (group->option(QueryGroup::ChildrenVisible))
{
item = childOf;
}
else if (fix)
{
item = fix;
if (fix->parent() != childOf)
moveItem(fix, childOf, 0);
item->setText(0, presentation);
}
else if (childOf)
{
item = new TreeItem(childOf, group, file, presentation);
}
else
{
item = new TreeItem(this, group, file, presentation);
}
item->setOpen(group->option(QueryGroup::AutoOpen));
return item;
}
#include "tree.moc"