diff options
Diffstat (limited to 'noatun-plugins/oblique/tree.cpp')
-rw-r--r-- | noatun-plugins/oblique/tree.cpp | 812 |
1 files changed, 812 insertions, 0 deletions
diff --git a/noatun-plugins/oblique/tree.cpp b/noatun-plugins/oblique/tree.cpp new file mode 100644 index 0000000..4160650 --- /dev/null +++ b/noatun-plugins/oblique/tree.cpp @@ -0,0 +1,812 @@ +// 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 <qpainter.h> +#include <iostream> + +#include <klocale.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, QListView 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 QString &p) + : KListViewItem(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 QString &p) + : KListViewItem(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(QString &str) +{ + int len=str.length(); + int at = 0; + int blocklen=0; + + static const int paddingsize=12; + + // not static for reason + const QChar chars[paddingsize] = + { + QChar('0'), QChar('0'), QChar('0'), QChar('0'), + QChar('0'), QChar('0'), QChar('0'), QChar('0'), + QChar('0'), QChar('0'), QChar('0'), QChar('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(QListViewItem *i, int col, bool) const +{ + QString text1 = text(col); + QString text2 = i->text(col); + + pad(text1); + pad(text2); + return text1.compare(text2); +} + + +Tree *TreeItem::tree() +{ + return static_cast<Tree*>(KListViewItem::listView()); +} + +QString 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(QPainter *p, const QColorGroup &cg, int column, int width, int align) +{ + QFont font = p->font(); + if (tree()->current() == this) + { + font.setUnderline(true); + p->setFont(font); + } + + QColorGroup newcg(cg); + if (parent() && parent()->isOpen() && !parent()->mUserOpened) + { + // slow, but not often used + QColor text = newcg.text(); + QColor 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(QColorGroup::Text, text); + } + KListViewItem::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); + } + KListViewItem::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 QString &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) + { + QString 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() +{ + QListViewItem::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, QWidget *parent) + : KListView(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); + + ((QWidget*)header())->hide(); + + + connect( + this, SIGNAL(moved(QPtrList<QListViewItem>&, QPtrList<QListViewItem>&, QPtrList<QListViewItem>&)), + SLOT(dropped(QPtrList<QListViewItem>&, QPtrList<QListViewItem>&, QPtrList<QListViewItem>&)) + ); + + connect( + this, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + SLOT(contextMenu(KListView*, QListViewItem*, const QPoint&)) + ); + connect( + this, SIGNAL(executed(QListViewItem*)), + SLOT(play(QListViewItem*)) + ); + + Base *base = oblique->base(); + connect(base, SIGNAL(added(File)), SLOT(insert(File))); + connect(base, SIGNAL(removed(File)), SLOT(remove(File))); + connect(base, SIGNAL(modified(File)), SLOT(update(File))); + + connect(base, SIGNAL(addedTo(Slice*, File)), SLOT(checkInsert(Slice*, File))); + connect(base, SIGNAL(removedFrom(Slice*, File)), SLOT(checkRemove(Slice*, File))); + + connect(this, SIGNAL(selected(TreeItem*)), oblique, SLOT(selected(TreeItem*))); + + mSlice = oblique->base()->defaultSlice(); + + KConfigGroup g(KGlobal::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); + } + KListView::clear(); +} + +void Tree::movableDropEvent (QListViewItem* parent, QListViewItem* afterme) +{ + QPtrList<QListViewItem> items = selectedItems(true); + for (QPtrList<QListViewItem>::Iterator i(items.begin()); *i; ++i) + { + if ((*i)->parent() != parent) + return; + } + + KListView::movableDropEvent(parent, afterme); +} + + +void Tree::dropped(QPtrList<QListViewItem> &items, QPtrList<QListViewItem> &, QPtrList<QListViewItem> &afterNow) +{ + QPtrList<QListViewItem>::Iterator itemi = items.begin(); + QPtrList<QListViewItem>::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*>(KListView::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; + QPtrList<TreeItem> oldAutoExpanded = mAutoExpanded; + mAutoExpanded.clear(); + repaintItem(old); + repaintItem(cur); + if (cur) cur->autoExpand(); + + // do an anti-intersection on oldAutoUpdated and the new mAutoExpanded + for (QPtrListIterator<TreeItem> i(mAutoExpanded); *i; ++i) + { + oldAutoExpanded.removeRef(*i); + } + + + bool user=false; + for (QPtrListIterator<TreeItem> i(oldAutoExpanded); *i; ++i) + { + if ((*i)->userOpened()) + { + user = true; + break; + } + } + if (!user) + { + for (QPtrListIterator<TreeItem> i(oldAutoExpanded); *i; ++i) + { + (*i)->setOpen(false); + } + } + + ensureItemVisible(cur); +} + +void Tree::reload() +{ + delete mLoader; + clear(); + mLoader = new Loader(this); + connect(mLoader, SIGNAL(finished()), SLOT(destroyLoader())); +} + +void Tree::setSlice(Slice *slice) +{ + if (mSlice == slice) return; + mSlice = slice; + reload(); +} + +bool Tree::setSchema(const QString &name) +{ + mFileOfQuery = name; + if (!oblique()->loadSchema(mQuery, name)) + return false; + reload(); + return true; +} + +QDragObject *Tree::dragObject() +{ + if (currentItem() && static_cast<TreeItem*>(currentItem())->file()) + return KListView::dragObject(); + return 0; +} + +void Tree::destroyLoader() +{ + delete mLoader; + mLoader = 0; +} + +void Tree::setLimit(const QString &text) +{ + for (TreeItem *ch = firstChild(); ch; ch = ch->nextSibling()) + { + ch->hideIfNoMatch(text); + } +} + + +void Tree::contextMenu(KListView*, QListViewItem* i, const QPoint& p) +{ + if (!i) return; + delete lastMenu; + lastMenu = new FileMenu(this, oblique(), static_cast<TreeItem*>(i) ); + lastMenu->popup(p); +} + +void Tree::play(QListViewItem *_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(); + + QString 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" + |