summaryrefslogtreecommitdiffstats
path: root/noatun-plugins/oblique
diff options
context:
space:
mode:
Diffstat (limited to 'noatun-plugins/oblique')
-rw-r--r--noatun-plugins/oblique/COPYING35
-rw-r--r--noatun-plugins/oblique/Makefile.am18
-rw-r--r--noatun-plugins/oblique/base.cpp435
-rw-r--r--noatun-plugins/oblique/base.h105
-rw-r--r--noatun-plugins/oblique/cmodule.cpp663
-rw-r--r--noatun-plugins/oblique/cmodule.h123
-rw-r--r--noatun-plugins/oblique/configure.in.in30
-rw-r--r--noatun-plugins/oblique/file.cpp248
-rw-r--r--noatun-plugins/oblique/file.h115
-rw-r--r--noatun-plugins/oblique/kbuffer.cpp87
-rw-r--r--noatun-plugins/oblique/kbuffer.h48
-rw-r--r--noatun-plugins/oblique/kdatacollection.cpp143
-rw-r--r--noatun-plugins/oblique/kdatacollection.h144
-rw-r--r--noatun-plugins/oblique/kdbt.h59
-rw-r--r--noatun-plugins/oblique/menu.cpp231
-rw-r--r--noatun-plugins/oblique/menu.h99
-rw-r--r--noatun-plugins/oblique/oblique.cpp325
-rw-r--r--noatun-plugins/oblique/oblique.h142
-rw-r--r--noatun-plugins/oblique/oblique.plugin73
-rw-r--r--noatun-plugins/oblique/obliqueui.rc31
-rw-r--r--noatun-plugins/oblique/query.cpp570
-rw-r--r--noatun-plugins/oblique/query.h176
-rw-r--r--noatun-plugins/oblique/schemas/Makefile.am3
-rw-r--r--noatun-plugins/oblique/schemas/obliqueschema.dtd14
-rw-r--r--noatun-plugins/oblique/schemas/standard80
-rw-r--r--noatun-plugins/oblique/selector.cpp226
-rw-r--r--noatun-plugins/oblique/selector.h76
-rw-r--r--noatun-plugins/oblique/tree.cpp812
-rw-r--r--noatun-plugins/oblique/tree.h194
-rw-r--r--noatun-plugins/oblique/view.cpp251
-rw-r--r--noatun-plugins/oblique/view.h71
31 files changed, 5627 insertions, 0 deletions
diff --git a/noatun-plugins/oblique/COPYING b/noatun-plugins/oblique/COPYING
new file mode 100644
index 0000000..34364e3
--- /dev/null
+++ b/noatun-plugins/oblique/COPYING
@@ -0,0 +1,35 @@
+
+ This file is hereby licensed under the GNU General Public License version
+ 2 or later at your option.
+
+ This file is licensed under the Qt Public License version 1 with the
+ condition that the licensed will be governed under the Laws of California
+ (USA) instead of Norway. Disputes will be settled in Santa Clara county
+ courts.
+
+ This file is licensed under the following additional license. Be aware
+ that it is identical to the BSD license, except for the added clause 3:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. By integrating this software into any other software codebase, you waive
+ all rights to any patents associated with the stated codebase.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/noatun-plugins/oblique/Makefile.am b/noatun-plugins/oblique/Makefile.am
new file mode 100644
index 0000000..09b200c
--- /dev/null
+++ b/noatun-plugins/oblique/Makefile.am
@@ -0,0 +1,18 @@
+SUBDIRS = . schemas
+
+INCLUDES= -I$(top_srcdir)/noatun/library $(all_includes)
+kde_module_LTLIBRARIES = noatun_oblique.la
+
+noatun_oblique_la_SOURCES = base.cpp kbuffer.cpp query.cpp oblique.cpp tree.cpp file.cpp selector.cpp menu.cpp cmodule.cpp kdatacollection.cpp view.cpp
+
+noatun_oblique_la_LDFLAGS = $(all_libraries) -module -avoid-version -no-undefined
+noatun_oblique_la_LIBADD = $(LIB_KIO) -lnoatun $(BERKELEY_DB_LIBS)
+
+noatun_oblique_la_METASOURCES = AUTO
+
+noatundata_DATA = oblique.plugin
+noatundatadir = $(kde_datadir)/noatun
+
+rc_DATA = obliqueui.rc
+rcdir = $(kde_datadir)/noatun
+
diff --git a/noatun-plugins/oblique/base.cpp b/noatun-plugins/oblique/base.cpp
new file mode 100644
index 0000000..60bb272
--- /dev/null
+++ b/noatun-plugins/oblique/base.cpp
@@ -0,0 +1,435 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#include "base.h"
+#include "file.h"
+
+#include "kdbt.h"
+#include "kbuffer.h"
+
+#include <qstringlist.h>
+#include <qmap.h>
+#include <qfile.h>
+#include <qdom.h>
+
+#include <cstdlib>
+#include <assert.h>
+#include <db_cxx.h>
+
+
+
+struct Base::Private
+{
+ Private() : db(0, DB_CXX_NO_EXCEPTIONS) { }
+ Db db;
+ typedef KDbt<FileId> Key;
+ typedef KDbt<QStringList> Data;
+
+ FileId high;
+
+ FileId cachedId;
+ mutable QMap<QString,QString> cachedProperties;
+
+ QPtrList<Slice> slices;
+ int sliceHigh;
+};
+
+
+Base::Base(const QString &file)
+{
+ d = new Private;
+ d->cachedId = 0;
+
+ QCString filename = QFile::encodeName(file);
+
+ bool create = true;
+ if (d->db.open(
+#if DB_VERSION_MINOR > 0 && DB_VERSION_MAJOR >= 4
+ NULL,
+#endif
+ filename,
+ 0, DB_BTREE, DB_NOMMAP, 0
+ )==0)
+ { // success
+ Private::Data data;
+ Private::Key key(0);
+ if (d->db.get(0, &key, &data, 0)==0)
+ {
+ QStringList strs;
+ data.get(strs);
+
+ mFormatVersion = strs[0].toUInt(0, 16); // TODO
+ d->high = strs[1].toUInt();
+
+ if (strs.count() == 3)
+ loadMetaXML(strs[2]);
+ else
+ loadMetaXML("");
+
+ create=false;
+ }
+ }
+ if (create)
+ { // failure
+ QFile(filename).remove();
+ d->db.open(
+#if DB_VERSION_MINOR > 0 && DB_VERSION_MAJOR >= 4
+ NULL,
+#endif
+ filename,0, DB_BTREE, DB_NOMMAP|DB_CREATE,0
+ );
+
+ d->high=0;
+ QStringList strs;
+ strs << "00010002" << "0" << "";
+ resetFormatVersion();
+ loadMetaXML("");
+ Private::Data data(strs);
+ Private::Key key(0);
+ // in the stringlist for Key(0), we have the following list:
+ // { "version of the file",
+ // "the high extreme (auto-increment counter in SQL terminology)",
+ // "the metaxml"
+ // }
+ d->db.put(0, &key, &data, 0);
+ }
+}
+
+void Base::resetFormatVersion()
+{
+ mFormatVersion = 0x00010002;
+}
+
+Base::~Base()
+{
+ QStringList strs;
+ strs << QString::number(mFormatVersion, 16) << QString::number(d->high);
+ strs << saveMetaXML();
+
+ Private::Data data(strs);
+ Private::Key key(0);
+ d->db.put(0, &key, &data, 0);
+ d->db.sync(0);
+ d->db.close(0);
+ delete d;
+}
+
+File Base::add(const QString &file)
+{
+ Private::Key key(++d->high);
+ QStringList properties;
+ properties << "file" << file;
+ Private::Data data(properties);
+
+ unless (d->db.put(0, &key, &data, 0))
+ {
+ // success !
+ File f(this, d->high);
+ f.makeCache();
+ emit added(f);
+ return f;
+ }
+
+ return File();
+}
+
+File Base::find(FileId id)
+{
+ if (id == 0) return File();
+
+ Private::Key key(id);
+ Private::Data data;
+
+ unless (d->db.get(0, &key, &data, 0))
+ {
+ // exists
+ return File(this, id);
+ }
+ else
+ {
+ return File(); // null item
+ }
+}
+
+void Base::clear()
+{
+ for (FileId id = high(); id >= 1; id--)
+ {
+ File f = find(id);
+ if (f)
+ f.remove();
+ }
+}
+
+
+FileId Base::high() const
+{
+ return d->high;
+}
+
+File Base::first(FileId first)
+{
+ if (first > high()) return File();
+
+ while (!find(first))
+ {
+ ++first;
+ if (first > high())
+ return File();
+ }
+ return File(this, first);
+}
+
+QString Base::property(FileId id, const QString &property) const
+{
+ loadIntoCache(id);
+ if (!d->cachedProperties.contains(property)) return QString::null;
+ QMap<QString,QString>::Iterator i = d->cachedProperties.find(property);
+ return i.data();
+}
+
+void Base::setProperty(FileId id, const QString &key, const QString &value)
+{
+ loadIntoCache(id);
+ d->cachedProperties.insert(key, value);
+ // reinsert it into the DB
+
+ QStringList props;
+ for (
+ QMap<QString,QString>::Iterator i(d->cachedProperties.begin());
+ i != d->cachedProperties.end(); ++i
+ )
+ {
+ props << i.key() << i.data();
+ }
+
+ Private::Data data(props);
+ Private::Key dbkey(id);
+ d->db.put(0, &dbkey, &data, 0);
+ d->db.sync(0);
+
+ emit modified(File(this, id));
+}
+
+QStringList Base::properties(FileId id) const
+{
+ loadIntoCache(id);
+ QStringList props;
+ for (
+ QMap<QString,QString>::Iterator i(d->cachedProperties.begin());
+ i != d->cachedProperties.end(); ++i
+ )
+ {
+ props << i.key();
+ }
+ return props;
+}
+
+void Base::clearProperty(FileId id, const QString &key)
+{
+ loadIntoCache(id);
+ d->cachedProperties.remove(key);
+ // reinsert it into the DB
+
+ QStringList props;
+ for (
+ QMap<QString,QString>::Iterator i(d->cachedProperties.begin());
+ i != d->cachedProperties.end(); ++i
+ )
+ {
+ if (i.key() != key)
+ props << i.key() << i.data();
+ }
+
+ Private::Data data(props);
+ Private::Key dbkey(id);
+ d->db.put(0, &dbkey, &data, 0);
+ d->db.sync(0);
+
+ emit modified(File(this, id));
+}
+
+void Base::remove(File file)
+{
+ Private::Key key(file.id());
+
+ unless (d->db.del(0, &key, 0))
+ {
+ emit removed(file);
+ if (file.id() == d->high)
+ {
+ d->high--; // optimization
+ }
+ }
+ d->db.sync(0);
+}
+
+void Base::loadIntoCache(FileId id) const
+{
+ if (d->cachedId == id) return;
+
+ d->cachedId = id;
+ d->cachedProperties.clear();
+
+ Private::Key key(id);
+ Private::Data data;
+ unless (d->db.get(0, &key, &data, 0))
+ {
+ QStringList props;
+ data.get(props);
+
+ if (props.count() % 2)
+ { // corrupt?
+ const_cast<Base*>(this)->remove(File(const_cast<Base*>(this), id));
+ return;
+ }
+
+ for (QStringList::Iterator i(props.begin()); i != props.end(); ++i)
+ {
+ QString &key = *i;
+ QString &value = *++i;
+ d->cachedProperties.insert(key, value);
+ }
+ }
+}
+
+QString Base::saveMetaXML()
+{
+ QDomDocument doc;
+ doc.setContent(QString("<meta />"));
+ QDomElement doce = doc.documentElement();
+
+ QDomElement e = doc.createElement("slices");
+ e.setAttribute("highslice", QString::number(d->sliceHigh));
+ doce.appendChild(e);
+
+ for (QPtrListIterator<Slice> i(d->slices); *i; ++i)
+ {
+ QDomElement slice = doc.createElement("slice");
+ slice.setAttribute("id", (*i)->id());
+ slice.setAttribute("name", (*i)->name());
+ e.appendChild(slice);
+ }
+ return doc.toString();
+}
+
+void Base::move(FileId oldid, FileId newid)
+{
+ Private::Key key(oldid);
+ Private::Data data;
+ unless (d->db.get(0, &key, &data, 0))
+ {
+ QStringList props;
+ data.get(props);
+ d->db.del(0, &key, 0);
+
+ Private::Key key2(newid);
+ d->db.put(0, &key2, &data, 0);
+ }
+}
+
+void Base::dump()
+{
+ for (FileId id=1; id <= high(); id++)
+ {
+ QStringList props = properties(id);
+ std::cerr << id << '.';
+ for (QStringList::Iterator i(props.begin()); i != props.end(); ++i)
+ {
+ QString prop = *i;
+ std::cerr << ' ' << prop.latin1() << '=' << property(id, prop).latin1();
+ }
+ std::cerr << std::endl;
+ }
+}
+
+void Base::notifyChanged(const File &file)
+{
+ emit modified(file);
+}
+
+
+QPtrList<Slice> Base::slices()
+{
+ return d->slices;
+}
+
+Slice *Base::addSlice(const QString &name)
+{
+ Slice *sl = new Slice(this, d->sliceHigh++, name);
+ d->slices.append(sl);
+ slicesModified();
+ return sl;
+}
+
+Slice *Base::defaultSlice()
+{
+ for (QPtrListIterator<Slice> i(d->slices); *i; ++i)
+ {
+ if ((*i)->id() == 0) return *i;
+ }
+
+ abort();
+ return 0;
+}
+
+void Base::removeSlice(Slice *slice)
+{
+ assert(slice->id() > 0);
+ d->slices.removeRef(slice);
+ delete slice;
+}
+
+Slice *Base::sliceById(int id)
+{
+ for (QPtrListIterator<Slice> i(d->slices); *i; ++i)
+ {
+ if ((*i)->id() == id) return *i;
+ }
+ return 0;
+}
+
+
+void Base::loadMetaXML(const QString &xml)
+{
+ d->slices.setAutoDelete(true);
+ d->slices.clear();
+ d->slices.setAutoDelete(false);
+
+ QDomDocument doc;
+ doc.setContent(xml);
+ QDomElement doce = doc.documentElement();
+ bool loadedId0=false;
+
+ for (QDomNode n = doce.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ QDomElement e = n.toElement();
+ if (e.isNull()) continue;
+
+ if (e.tagName().lower() == "slices")
+ {
+ d->sliceHigh = e.attribute("highslice", "1").toInt();
+ for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ QDomElement e = n.toElement();
+ if (e.isNull()) continue;
+ if (e.tagName().lower() == "slice")
+ {
+ int id = e.attribute("id").toInt();
+ if (id==0 && loadedId0) break;
+ loadedId0=true;
+ QString name = e.attribute("name");
+ d->slices.append(new Slice(this, id, name));
+ }
+ }
+ }
+ }
+
+ if (d->slices.count() == 0)
+ {
+ // we must have a default
+ d->slices.append(new Slice(this, 0, ""));
+ }
+}
+
+#include "base.moc"
diff --git a/noatun-plugins/oblique/base.h b/noatun-plugins/oblique/base.h
new file mode 100644
index 0000000..c9cb8d4
--- /dev/null
+++ b/noatun-plugins/oblique/base.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2003,2004 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+#ifndef BASE_H
+#define BASE_H
+
+// ;)
+#define unless(e) if(!(e))
+
+#include <qstring.h>
+#include <qobject.h>
+#include <qptrlist.h>
+
+class File;
+class Slice;
+
+typedef unsigned int FileId;
+
+class Base : public QObject
+{
+Q_OBJECT
+
+ struct Private;
+ Private *d; // not for BC, but for compile times :)
+ friend class Slice;
+ friend class File;
+ unsigned int mFormatVersion;
+
+public:
+ Base(const QString &file);
+ ~Base();
+
+ File add(const QString &file);
+
+ File find(FileId id);
+
+ void clear();
+
+ /**
+ * get the highest FileID
+ **/
+ FileId high() const;
+
+ /**
+ * @return first item after the given id (inclusive)
+ **/
+ File first(FileId id=1);
+
+ QString property(FileId id, const QString &property) const;
+ void setProperty(FileId id, const QString &key, const QString &value);
+ QStringList properties(FileId id) const;
+ void clearProperty(FileId, const QString &key);
+
+ /**
+ * same as File::remove
+ **/
+ void remove(File f);
+
+ /**
+ * change the id of a file
+ **/
+ void move(FileId oldid, FileId newid);
+
+ void dump();
+
+ QPtrList<Slice> slices();
+ Slice *addSlice(const QString &name);
+ Slice *defaultSlice();
+ Slice *sliceById(int id);
+
+ unsigned int formatVersion() const { return mFormatVersion; }
+ void resetFormatVersion();
+
+public slots:
+ void notifyChanged(const File &file);
+
+signals:
+ void added(File file);
+ void removed(File file);
+ void modified(File file);
+
+ void addedTo(Slice *slice, File file);
+ void removedFrom(Slice *slice, File file);
+
+ /**
+ * emitted when something of the slices gets modified
+ * @ref Slice calls this itself via a friendship
+ **/
+ void slicesModified();
+
+private:
+ void loadIntoCache(FileId id) const;
+
+private: // friends for Slice
+ void removeSlice(Slice *slice);
+
+private:
+ /**
+ * load the xml that lives at the head of the db and contains
+ * potentially lots of structured data
+ **/
+ void loadMetaXML(const QString &xml);
+ QString saveMetaXML();
+};
+
+#endif
diff --git a/noatun-plugins/oblique/cmodule.cpp b/noatun-plugins/oblique/cmodule.cpp
new file mode 100644
index 0000000..93b4069
--- /dev/null
+++ b/noatun-plugins/oblique/cmodule.cpp
@@ -0,0 +1,663 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#include "cmodule.h"
+#include "oblique.h"
+#include "file.h"
+
+#include <kregexpeditorinterface.h>
+#include <kparts/componentfactory.h>
+#include <klocale.h>
+#include <klistview.h>
+#include <klineedit.h>
+#include <kiconloader.h>
+#include <kinputdialog.h>
+#include <kconfig.h>
+
+#include <qgroupbox.h>
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qvbox.h>
+#include <qhbox.h>
+#include <qfileinfo.h>
+#include <qtooltip.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qcombobox.h>
+#include <qwhatsthis.h>
+#include <qtabwidget.h>
+#include <qheader.h>
+
+SchemaConfig::SchemaConfig(QWidget *parent, Oblique *oblique)
+ : QWidget(parent)
+{
+ mOblique = oblique;
+ mIgnore = true;
+ mRegexpEditor=0;
+
+ {
+ QVBoxLayout *layout = new QVBoxLayout(this, 11, 7);
+ layout->setAutoAdd(true);
+ layout->setSpacing(7);
+ }
+
+
+ {
+ QHBox *box = new QHBox(this);
+ box->setSpacing(7);
+ mSchemaList = new QComboBox(box);
+ connect(
+ mSchemaList, SIGNAL(activated(const QString&)),
+ SLOT(selectSchema(const QString&))
+ );
+
+ mAdd = new QPushButton(BarIconSet("filenew"), 0, box);
+ mAdd->setFixedWidth(mAdd->height());
+ QToolTip::add(mAdd, i18n("Create new schema"));
+ connect(mAdd, SIGNAL(clicked()), SLOT(newSchema()));
+
+ mRemove = new QPushButton(BarIconSet("editdelete"), 0, box);
+ mRemove->setFixedWidth(mRemove->height());
+ QToolTip::add(mRemove, i18n("Remove this schema"));
+ connect(mRemove, SIGNAL(clicked()), SLOT(removeSchema()));
+
+ mCopy = new QPushButton(BarIconSet("editcopy"), 0, box);
+ mCopy->setFixedWidth(mCopy->height());
+ QToolTip::add(mCopy, i18n("Copy this schema"));
+ connect(mCopy, SIGNAL(clicked()), SLOT(copySchema()));
+ }
+
+
+ {
+ QHBox *middle = new QHBox(this);
+ middle->setSpacing(7);
+
+ mSchemaTree = new KListView(middle);
+ connect(
+ mSchemaTree, SIGNAL(currentChanged(QListViewItem*)),
+ SLOT(setCurrent(QListViewItem*))
+ );
+ connect(
+ mSchemaTree, SIGNAL(moved(QListViewItem *, QListViewItem *, QListViewItem *)),
+ SLOT(move(QListViewItem *, QListViewItem *, QListViewItem *))
+ );
+
+ mSchemaTree->setAcceptDrops(true);
+ mSchemaTree->setSorting(-1);
+ mSchemaTree->setDropVisualizer(true);
+ mSchemaTree->setDragEnabled(true);
+ mSchemaTree->setItemsMovable(true);
+
+ mSchemaTree->addColumn(i18n("Property"));
+ mSchemaTree->addColumn(i18n("Value"));
+ mSchemaTree->addColumn(i18n("Presentation"));
+
+ QVBox *buttons = new QVBox(middle);
+
+ mAddSibling = new QPushButton(BarIconSet("1rightarrow", KIcon::SizeSmall), "",buttons);
+ mAddSibling->setFixedWidth(mAddSibling->height());
+ connect(mAddSibling, SIGNAL(clicked()), SLOT(addSibling()));
+ QToolTip::add(mAddSibling, i18n("Create a new item after the selected one"));
+
+ mAddChild = new QPushButton(BarIconSet("2rightarrow", KIcon::SizeSmall), "", buttons);
+ mAddChild->setFixedWidth(mAddChild->height());
+ connect(mAddChild, SIGNAL(clicked()), SLOT(addChild()));
+ QToolTip::add(mAddChild, i18n("Create a new child item under the selected one"));
+
+ mRemoveSelf = new QPushButton(BarIconSet("filenew", KIcon::SizeSmall), "", buttons);
+ mRemoveSelf->setFixedWidth(mRemoveSelf->height());
+ connect(mRemoveSelf, SIGNAL(clicked()), SLOT(removeSelf()));
+ QToolTip::add(mRemoveSelf, i18n("Remove the selected item"));
+
+ new QWidget(buttons);
+ }
+
+
+ {
+ QVBox *side = new QVBox(this);
+ side->setSpacing(7);
+ // the controllers
+ {
+ QWidget *topSide = new QWidget(side);
+ QGridLayout *grid = new QGridLayout(topSide);
+
+ QLabel *label;
+
+ label = new QLabel(i18n("&Property:"), topSide);
+ mPropertyEdit = new KLineEdit(topSide);
+ label->setBuddy(mPropertyEdit);
+ grid->addWidget(label, 0, 0);
+ grid->addMultiCellWidget(mPropertyEdit, 0, 0, 1, 2);
+ connect(mPropertyEdit, SIGNAL(textChanged(const QString&)), SLOT(updateCurrent()));
+
+ label = new QLabel(i18n("&Value:"), topSide);
+ mValueEdit = new KLineEdit(topSide);
+ label->setBuddy(mPropertyEdit);
+ grid->addWidget(label, 1, 0);
+ grid->addMultiCellWidget(mValueEdit, 1, 1, 1, 1);
+ connect(mValueEdit, SIGNAL(textChanged(const QString&)), SLOT(updateCurrent()));
+
+ QPushButton *editRe = new QPushButton(i18n("&Edit..."), topSide);
+ grid->addWidget(editRe, 1, 2);
+ connect(editRe, SIGNAL(clicked()), SLOT(editValueRegexp()));
+
+ label = new QLabel(i18n("Pre&sentation:"), topSide);
+ mPresentationEdit = new KLineEdit(topSide);
+ label->setBuddy(mPropertyEdit);
+ grid->addWidget(label, 2, 0);
+ grid->addMultiCellWidget(mPresentationEdit, 2, 2, 1, 2);
+ connect(mPresentationEdit, SIGNAL(textChanged(const QString&)), SLOT(updateCurrent()));
+ }
+
+ {
+ QGroupBox *groupbox = new QGroupBox(
+ 3, Qt::Horizontal, i18n("Options"), side
+ );
+
+ mOptionPlayable = new QCheckBox(i18n("Play&able"), groupbox);
+ QWhatsThis::add(mOptionPlayable, i18n("This branch represents an individual file. If two items' presentation match, two items are created."));
+ connect(mOptionPlayable, SIGNAL(toggled(bool)), SLOT(updateCurrent()));
+
+ mOptionChildrenVisible = new QCheckBox(i18n("&Children visible"), groupbox);
+ QWhatsThis::add(mOptionChildrenVisible, i18n("Don't create this node, this nodes children become direct children of this node's parent"));
+ connect(mOptionChildrenVisible, SIGNAL(toggled(bool)), SLOT(updateCurrent()));
+
+ mOptionAutoOpen = new QCheckBox(i18n("Auto &open"), groupbox);
+ QWhatsThis::add(mOptionAutoOpen, i18n("This branch is marked as open immediately."));
+ connect(mOptionAutoOpen, SIGNAL(toggled(bool)), SLOT(updateCurrent()));
+ }
+
+ }
+
+}
+
+class QueryGroupItem : public KListViewItem
+{
+ QueryGroup *mItem;
+
+public:
+ QueryGroupItem(QueryGroupItem *parent, QueryGroup *group, QueryGroupItem *after=0)
+ : KListViewItem(parent, after)
+ {
+ init(group);
+ }
+
+ QueryGroupItem(KListView *parent, QueryGroup *group, QueryGroupItem *after=0)
+ : KListViewItem(parent, after)
+ {
+ init(group);
+ }
+
+ QueryGroup *item() { return mItem; }
+ const QueryGroup *item() const { return mItem; }
+
+ void redisplay()
+ {
+ setText(0, item()->propertyName());
+ setText(1, item()->value().pattern());
+ setText(2, item()->presentation());
+ }
+
+ QueryGroupItem *parent()
+ { return static_cast<QueryGroupItem*>(KListViewItem::parent()); }
+
+ KListView *listView()
+ { return static_cast<KListView*>(KListViewItem::listView()); }
+
+private:
+ void init(QueryGroup *group)
+ {
+ mItem = group;
+ redisplay();
+
+ if (group->firstChild())
+ new QueryGroupItem(this, group->firstChild(), this);
+
+ // do siblings now iff I don't already have them
+ if (!nextSibling())
+ {
+ if (QueryGroup *after = group->nextSibling())
+ {
+ if (parent())
+ new QueryGroupItem(parent(), after, this);
+ else
+ new QueryGroupItem(listView(), after, this);
+ }
+ }
+
+ setOpen(true);
+ }
+};
+
+
+void SchemaConfig::reopen()
+{
+ mSchemaList->clear();
+ mQueries.clear();
+ mSchemaTree->clear();
+
+ QStringList names = oblique()->schemaNames();
+ QString firstTitle;
+
+ for (QStringList::Iterator i(names.begin()); i != names.end(); ++i)
+ {
+ QueryItem qi;
+ qi.title = oblique()->loadSchema(qi.query, *i);
+ qi.changed = false;
+ mQueries.insert(*i, qi);
+
+ if (!mSchemaList->count())
+ firstTitle = qi.title;
+ mSchemaList->insertItem(qi.title);
+ }
+ selectSchema(firstTitle);
+
+}
+
+void SchemaConfig::save()
+{
+ for (QMap<QString,QueryItem>::Iterator i(mQueries.begin()); i != mQueries.end(); ++i)
+ {
+ QString name = i.key();
+ name = QFileInfo(name).fileName();
+ if (i.data().changed)
+ {
+ oblique()->saveSchema(i.data().query, name, i.data().title);
+ // TODO update the trees.
+ }
+ }
+}
+
+
+QString SchemaConfig::nameToFilename(const QString &_name)
+{
+ QString name = _name;
+ name = name.replace(QRegExp("[^a-zA-Z0-9]"), "_");
+ return name;
+}
+
+void SchemaConfig::newSchema()
+{
+ bool ok;
+ QString str=KInputDialog::getText(
+ i18n("Name of Schema"),
+ i18n("Please enter the name of the new schema:"),
+ "", &ok, this
+ );
+ if (!ok) return;
+
+ QString filename = nameToFilename(str);
+
+ if (mQueries.contains(nameToFilename(filename))) return;
+
+ QueryItem queryitem;
+ queryitem.query = Query();
+ queryitem.title = str;
+ queryitem.changed=true;
+ mSchemaList->insertItem(str);
+ mQueries.insert(filename, queryitem);
+
+ selectSchema(str);
+}
+
+void SchemaConfig::copySchema()
+{
+ bool ok;
+ QString str=KInputDialog::getText(
+ i18n("Name of Schema"),
+ i18n("Please enter the name of the new schema:"),
+ "", &ok, this
+ );
+ if (!ok) return;
+
+ QString filename = nameToFilename(str);
+
+ if (mQueries.contains(nameToFilename(filename))) return;
+
+ QueryItem queryitem;
+ queryitem.query = currentQuery()->query;
+ queryitem.title = str;
+ queryitem.changed=true;
+ mSchemaList->insertItem(str);
+ mQueries.insert(filename, queryitem);
+
+ selectSchema(str);
+}
+
+void SchemaConfig::removeSchema()
+{
+ QueryItem *item = currentQuery();
+ mSchemaList->removeItem(mSchemaList->currentItem());
+ oblique()->removeSchema(nameToFilename(item->title));
+ selectSchema(mSchemaList->currentText());
+}
+
+void SchemaConfig::selectSchema(const QString &title)
+{
+ mSchemaTree->clear();
+ mSchemaList->setCurrentText(title);
+
+ mIgnore = true;
+ if (QueryItem *grp = currentQuery())
+ {
+ if (QueryGroup *g = grp->query.firstChild())
+ new QueryGroupItem(mSchemaTree, g);
+ }
+ mSchemaTree->setCurrentItem(mSchemaTree->firstChild());
+ setCurrent(mSchemaTree->firstChild());
+ mSchemaTree->setSelected(mSchemaTree->firstChild(), true);
+ mIgnore=false;
+}
+
+void SchemaConfig::editValueRegexp()
+{
+ unless (mRegexpEditor)
+ {
+ mRegexpEditor =
+ KParts::ComponentFactory::createInstanceFromQuery<QDialog>(
+ "KRegExpEditor/KRegExpEditor", QString::null, this
+ );
+ }
+
+ if ( mRegexpEditor )
+ {
+ KRegExpEditorInterface *iface =
+ static_cast<KRegExpEditorInterface*>(
+ mRegexpEditor->qt_cast( "KRegExpEditorInterface")
+ );
+
+ iface->setRegExp(mValueEdit->text());
+ if (mRegexpEditor->exec() == QDialog::Accepted)
+ mValueEdit->setText(iface->regExp());
+ }
+}
+
+void SchemaConfig::setCurrent(QListViewItem *_item)
+{
+ if (!_item) return;
+ QueryGroupItem *item = static_cast<QueryGroupItem*>(_item);
+ mIgnore = true;
+ mPropertyEdit->setText(item->item()->propertyName());
+ mValueEdit->setText(item->item()->value().pattern());
+ mPresentationEdit->setText(item->item()->presentation());
+
+ mOptionPlayable->setChecked(item->item()->option(QueryGroup::Playable));
+ mOptionAutoOpen->setChecked(item->item()->option(QueryGroup::AutoOpen));
+ mOptionChildrenVisible->setChecked(item->item()->option(QueryGroup::ChildrenVisible));
+ mIgnore = false;
+}
+
+
+void SchemaConfig::updateCurrent()
+{
+ QueryGroupItem *item = static_cast<QueryGroupItem*>(mSchemaTree->currentItem());
+ if (mIgnore || !item) return;
+ setCurrentModified();
+
+ QueryGroup *mod = item->item();
+
+ mod->setPropertyName(mPropertyEdit->text());
+ mod->setPresentation(mPresentationEdit->text());
+ mod->setValue(QRegExp(mValueEdit->text()));
+
+ mod->setOption(QueryGroup::AutoOpen, mOptionAutoOpen->isChecked());
+ mod->setOption(QueryGroup::Playable, mOptionPlayable->isChecked());
+ mod->setOption(QueryGroup::ChildrenVisible, mOptionChildrenVisible->isChecked());
+ item->redisplay();
+}
+
+void SchemaConfig::move(QListViewItem *_item, QListViewItem *, QListViewItem *_afterNow)
+{
+ setCurrentModified();
+ QueryGroupItem *item = static_cast<QueryGroupItem*>(_item);
+ QueryGroupItem *afterNow = static_cast<QueryGroupItem*>(_afterNow);
+
+ QueryGroup *after, *under;
+ under = item->parent() ? item->parent()->item() : 0;
+ after = afterNow ? afterNow->item() : 0;
+
+ item->item()->move(&currentQuery()->query, under, after);
+}
+
+
+void SchemaConfig::addSibling()
+{
+ QueryGroupItem *item = static_cast<QueryGroupItem*>(mSchemaTree->currentItem());
+ unless (item)
+ {
+ addChild();
+ return;
+ }
+ setCurrentModified();
+
+ // add it
+ QueryGroup * g = new QueryGroup;
+ item->item()->insertAfter(g);
+
+ // now match the action in the tree
+ QueryGroupItem *created;
+ if (item->parent())
+ created = new QueryGroupItem(item->parent(), g, item);
+ else
+ created = new QueryGroupItem(item->listView(), g, item);
+
+ // select it so the user can edit it now
+ item->listView()->setCurrentItem(created);
+ item->listView()->setSelected(created, true);
+}
+
+
+void SchemaConfig::addChild()
+{
+ QueryGroupItem *item = static_cast<QueryGroupItem*>(mSchemaTree->currentItem());
+ setCurrentModified();
+
+ // add it
+ QueryGroup * g = new QueryGroup;
+ if (item)
+ item->item()->insertUnder(g);
+ else
+ currentQuery()->query.setFirstChild(g);
+
+ // now match the action in the tree
+ QueryGroupItem *created;
+ if (item)
+ created = new QueryGroupItem(item, g);
+ else
+ created = new QueryGroupItem(mSchemaTree, g);
+
+ // select it so the user can edit it now
+ mSchemaTree->setCurrentItem(created);
+ mSchemaTree->setSelected(created, true);
+}
+
+void SchemaConfig::removeSelf()
+{
+ setCurrentModified();
+ QueryGroupItem *item = static_cast<QueryGroupItem*>(mSchemaTree->currentItem());
+ unless (item) return;
+ QueryGroup *grp = item->item();
+ delete item;
+ currentQuery()->query.take(grp);
+ delete grp;
+}
+
+void SchemaConfig::setCurrentModified()
+{
+ if (QueryItem *grp = currentQuery())
+ grp->changed = true;
+}
+
+SchemaConfig::QueryItem *SchemaConfig::currentQuery()
+{
+ QString title = mSchemaList->currentText();
+ for (QMap<QString,QueryItem>::Iterator i(mQueries.begin()); i != mQueries.end(); ++i)
+ {
+ if (i.data().title != title) continue;
+ return &i.data();
+ }
+ return 0;
+}
+
+
+SliceConfig::SliceConfig(QWidget *parent, Oblique *oblique)
+ : QWidget(parent)
+{
+ mOblique = oblique;
+ (new QVBoxLayout(this, 11, 7))->setAutoAdd(true);
+
+ {
+ QHBox *middle = new QHBox(this);
+ middle->setSpacing(7);
+
+ mSliceList = new KListView(middle);
+ QWhatsThis::add(mSliceList, i18n("The list of slices. A Slice is part of the full collection. This allows you to categorize your playlist. You can add an item to the list by right clicking on it and selecting the Slice you want it in."));
+ mSliceList->addColumn("");
+ mSliceList->header()->hide();
+
+ mSliceList->setSorting(0);
+ mSliceList->setItemsRenameable(true);
+
+ QVBox *buttons = new QVBox(middle);
+
+ mAdd = new QPushButton(BarIconSet("1rightarrow", KIcon::SizeSmall), "",buttons);
+ mAdd->setFixedWidth(mAdd->height());
+ connect(mAdd, SIGNAL(clicked()), SLOT(addSibling()));
+ QToolTip::add(mAdd, i18n("Create a new item"));
+
+ mRemove = new QPushButton(BarIconSet("filenew", KIcon::SizeSmall), "", buttons);
+ mRemove->setFixedWidth(mRemove->height());
+ connect(mRemove, SIGNAL(clicked()), SLOT(removeSelf()));
+ QToolTip::add(mRemove, i18n("Remove the selected item"));
+
+ new QWidget(buttons);
+ }
+}
+
+
+class SliceListItem : public KListViewItem
+{
+ Slice *mSlice;
+public:
+ SliceListItem(KListView *parent, Slice *slice)
+ : KListViewItem(parent, slice->name()), mSlice(slice)
+ {
+ }
+
+ /**
+ * new item
+ **/
+ SliceListItem(KListView *parent)
+ : KListViewItem(parent, i18n("New Slice")), mSlice(0)
+ {
+ }
+
+ Slice *slice() { return mSlice; }
+};
+
+SliceListItem *SliceConfig::currentItem()
+{
+ QListViewItem *c = mSliceList->currentItem();
+ return static_cast<SliceListItem*>(c);
+}
+
+
+void SliceConfig::reopen()
+{
+ mSliceList->clear();
+ mRemovedItems.clear();
+ mAddedItems.clear();
+
+ QPtrList<Slice> slices = oblique()->base()->slices();
+
+ for (QPtrListIterator<Slice> i(slices); *i; ++i)
+ {
+ Slice *slice = *i;
+ new SliceListItem(mSliceList, slice);
+ }
+}
+
+void SliceConfig::save()
+{
+ for (
+ QValueList<Slice*>::Iterator i(mRemovedItems.begin());
+ i != mRemovedItems.end();
+ ++i
+ )
+ {
+ (*i)->remove();
+ delete *i;
+ }
+
+ for (
+ QValueList<SliceListItem*>::Iterator i(mAddedItems.begin());
+ i != mAddedItems.end();
+ ++i
+ )
+ {
+ oblique()->base()->addSlice((*i)->text(0));
+ }
+
+ for (QListViewItem *i = mSliceList->firstChild(); i; i = i->nextSibling())
+ {
+ SliceListItem *si = static_cast<SliceListItem*>(i);
+
+ if (si->slice())
+ {
+ si->slice()->setName(si->text(0));
+ }
+ }
+
+ reopen();
+}
+
+
+void SliceConfig::addSibling()
+{
+ SliceListItem *si = new SliceListItem(mSliceList);
+ mAddedItems.append(si);
+}
+
+void SliceConfig::removeSelf()
+{
+ SliceListItem *r = currentItem();
+ if (mAddedItems.contains(r))
+ {
+ mAddedItems.remove(r);
+ }
+ else
+ {
+ Q_ASSERT(r->slice());
+ mRemovedItems.append(r->slice());
+ }
+
+ delete r;
+}
+
+
+
+Configure::Configure(Oblique *oblique)
+ : CModule(i18n("Oblique"), i18n("Configure Oblique Playlist"), "", oblique)
+{
+ (new QVBoxLayout(this))->setAutoAdd(true);
+ tabs = new QTabWidget(this);
+
+ tabs->addTab(slice = new SliceConfig(tabs, oblique), i18n("Slices"));
+ tabs->addTab(schema = new SchemaConfig(tabs, oblique), i18n("Schemas"));
+}
+
+void Configure::reopen()
+{
+ schema->reopen();
+ slice->reopen();
+}
+
+void Configure::save()
+{
+ schema->save();
+ slice->save();
+}
+
+
+#include "cmodule.moc"
+
diff --git a/noatun-plugins/oblique/cmodule.h b/noatun-plugins/oblique/cmodule.h
new file mode 100644
index 0000000..f60c6c3
--- /dev/null
+++ b/noatun-plugins/oblique/cmodule.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#ifndef CMODULE_H
+#define CMODULE_H
+
+#include <noatun/pref.h>
+#include "query.h"
+
+class KListView;
+class KLineEdit;
+class QDialog;
+class Oblique;
+class QCheckBox;
+class QComboBox;
+class QPushButton;
+class QTabWidget;
+
+class SchemaConfig : public QWidget
+{
+Q_OBJECT
+ Oblique *mOblique;
+
+ KListView *mSchemaTree;
+ KLineEdit *mPropertyEdit, *mValueEdit, *mPresentationEdit;
+ QComboBox *mSchemaList;
+
+ QCheckBox *mOptionPlayable, *mOptionChildrenVisible, *mOptionAutoOpen;
+
+ QPushButton *mAdd, *mRemove, *mCopy;
+
+ QPushButton *mAddSibling, *mAddChild, *mRemoveSelf;
+
+ QDialog *mRegexpEditor;
+ bool mIgnore;
+
+ struct QueryItem
+ {
+ Query query;
+ QString title;
+ bool changed;
+ };
+
+ QMap<QString, QueryItem> mQueries;
+
+public:
+ SchemaConfig(QWidget *parent, Oblique *oblique);
+
+ Oblique *oblique() { return mOblique; }
+
+ void reopen();
+ void save();
+
+ static QString nameToFilename(const QString &name);
+ static QString filenameToName(const QString &filename);
+
+public slots:
+ void newSchema();
+ void copySchema();
+ void removeSchema();
+
+ void selectSchema(const QString &title);
+
+ void addSibling();
+ void addChild();
+ void removeSelf();
+
+ void setCurrentModified();
+
+private slots:
+ void editValueRegexp();
+
+ void setCurrent(QListViewItem *_item);
+ void updateCurrent();
+
+ void move(QListViewItem *item, QListViewItem *afterFirst, QListViewItem *afterNow);
+
+private:
+ QueryItem *currentQuery();
+};
+
+class SliceListItem;
+
+class SliceConfig : public QWidget
+{
+Q_OBJECT
+ Oblique *mOblique;
+ KListView *mSliceList;
+ QPushButton *mAdd, *mRemove;
+ QValueList<SliceListItem*> mAddedItems;
+ QValueList<Slice*> mRemovedItems;
+
+public:
+ SliceConfig(QWidget *parent, Oblique *oblique);
+ Oblique *oblique() { return mOblique; }
+
+ void reopen();
+ void save();
+
+
+ SliceListItem *currentItem();
+
+private slots:
+ void addSibling();
+ void removeSelf();
+};
+
+class Configure : public CModule
+{
+Q_OBJECT
+ QTabWidget *tabs;
+ SchemaConfig *schema;
+ SliceConfig *slice;
+
+public:
+ Configure(Oblique *parent);
+
+ virtual void reopen();
+ virtual void save();
+};
+
+
+#endif
diff --git a/noatun-plugins/oblique/configure.in.in b/noatun-plugins/oblique/configure.in.in
new file mode 100644
index 0000000..7b7493f
--- /dev/null
+++ b/noatun-plugins/oblique/configure.in.in
@@ -0,0 +1,30 @@
+AC_ARG_WITH(berkeley-db,
+ [AC_HELP_STRING([--with-berkeley-db],[enable support for Berkeley DB++ @<:@default=check@:>@])],
+ [], with_berkeley_db=check)
+
+AC_ARG_WITH(db-lib,
+ [AC_HELP_STRING([--with-db-lib=NAME],[name of the Berkeley DB++ library @<:@default=db_cxx@:>@])],
+ [ac_db_name="$withval"], [ac_db_name="db_cxx"])
+
+berkeley_db=no
+if test "x$with_berkeley_db" != xno; then
+ berkeley_db=yes
+
+ KDE_CHECK_HEADER([db_cxx.h],
+ [:], [berkeley_db=no])
+
+ AC_CHECK_LIB([$ac_db_name], [main],
+ [:], [berkeley_db=no])
+
+ if test "x$berkeley_db" = xyes; then
+ AC_DEFINE(BERKELEY_DB, 1, [Define if you have Berkeley DB++ installed])
+ BERKELEY_DB_LIBS="-l$ac_db_name"
+ AC_SUBST(BERKELEY_DB_LIBS)
+ fi
+
+ if test "x$with_berkeley_db" != xcheck && test "x$berkeley_db" != xyes; then
+ AC_MSG_ERROR([--with-berkeley-db was given, but test for Berkeley DB++ failed])
+ fi
+fi
+
+AM_CONDITIONAL(include_BERKELEY_DB, test "$berkeley_db" = yes)
diff --git a/noatun-plugins/oblique/file.cpp b/noatun-plugins/oblique/file.cpp
new file mode 100644
index 0000000..b478e0f
--- /dev/null
+++ b/noatun-plugins/oblique/file.cpp
@@ -0,0 +1,248 @@
+// Copyright (c) 2003-2004 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#include "file.h"
+#include "selector.h"
+#include "query.h"
+
+#include <iostream>
+
+#include <klocale.h>
+#include <kfilemetainfo.h>
+#include <kmimetype.h>
+
+
+File::File(Base *base, FileId id)
+{
+ mBase = base;
+ mId = id;
+
+}
+
+File::File(const File &ref)
+{
+ operator =(ref);
+}
+
+File::File()
+{
+ mBase=0;
+ mId = 0;
+}
+
+File &File::operator=(const File &ref)
+{
+ mBase = ref.mBase;
+ mId = ref.mId;
+
+ return *this;
+}
+
+QString File::file() const
+{
+ return property("file");
+}
+
+KURL File::url() const
+{
+ KURL url;
+ url.setPath(file());
+ return url;
+}
+
+struct Map { const char *kfmi; const char *noatun; };
+static const Map propertyMap[] =
+{
+ { "Title", "ob::title_" },
+ { "Artist", "ob::author_" },
+ { "Album", "ob::album_" },
+ { "Genre", "ob::genre_" },
+ { "Tracknumber", "ob::track_" },
+ { "Date", "ob::date_" },
+ { "Comment", "ob::comment_" },
+ { "Location", "ob::location_" },
+ { "Organization", "ob::organization_" },
+ { "Bitrate", "ob::bitrate_" },
+ { "Sample Rate", "ob::samplerate_" },
+ { "Channels", "ob::channels_" },
+ { 0, 0 }
+};
+
+QString File::property(const QString &property) const
+{
+ QString str = base()->property(id(), property);
+
+ if (!str)
+ {
+ QString mangled = "ob::" + property + "_";
+ str = base()->property(id(), mangled);
+ }
+
+ return str;
+}
+
+
+void File::makeCache()
+{
+ setProperty("ob::mimetype_", KMimeType::findByPath(file())->name());
+ KFileMetaInfo info(file());
+
+ for (int i=0; propertyMap[i].kfmi; i++)
+ {
+ QString kname(propertyMap[i].kfmi);
+ if (info.isValid() && kname.length())
+ {
+ QString val = info.item(kname).string(false);
+ if (val=="---" || !val.stripWhiteSpace().length())
+ { // grr
+ val = "";
+ }
+ if (val.length())
+ {
+ setProperty(propertyMap[i].noatun, val);
+ }
+ }
+ }
+}
+
+
+void File::setProperty(const QString &key, const QString &value)
+{
+ if (property(key) == value) return;
+ base()->setProperty(id(), key, value);
+ PlaylistItem p=new Item(*this);
+ p.data()->modified();
+}
+
+void File::clearProperty(const QString &key)
+{
+ if (property(key).isNull()) return;
+ base()->clearProperty(id(), key);
+ PlaylistItem p=new Item(*this);
+ p.data()->modified();
+}
+
+QStringList File::properties() const
+{
+ QStringList l = base()->properties(id());
+
+ for (int i=0; propertyMap[i].noatun; i++)
+ {
+ if (property(propertyMap[i].noatun).length())
+ {
+ l += propertyMap[i].noatun;
+ }
+ }
+ return l;
+}
+
+void File::setId(FileId id)
+{
+ base()->move(mId, id);
+ mId = id;
+}
+
+
+void File::setPosition(Query *query, const File &after)
+{
+ setProperty(
+ "Oblique:after_" + query->name() + '_',
+ QString::number(after.id())
+ );
+}
+
+bool File::getPosition(const Query *query, File *after) const
+{
+ assert(query);
+ assert(after);
+ QString name = "Oblique:after_" + query->name() + '_';
+ if (name.isEmpty()) return false;
+
+ QString val = property(name);
+ if (val.isEmpty())
+ return false;
+ *after = File(mBase, val.toUInt());
+ return true;
+}
+
+
+void File::remove()
+{
+ PlaylistItem p=new Item(*this);
+ p.data()->removed();
+ mBase->remove(*this);
+}
+
+void File::addTo(Slice *slice)
+{
+ QString slices = property("Oblique:slices_");
+ slices += "\n" + QString::number(slice->id(), 16);
+ setProperty("Oblique:slices_", slices);
+ emit mBase->addedTo(slice, *this);
+}
+
+void File::removeFrom(Slice *slice)
+{
+ QString slices = property("Oblique:slices_");
+ QStringList sliceList = QStringList::split('\n', slices);
+ QString sid = QString::number(slice->id(), 16);
+ sliceList.remove(sid);
+
+ slices = sliceList.join("\n");
+ setProperty("Oblique:slices_", slices);
+ emit mBase->removedFrom(slice, *this);
+}
+
+bool File::isIn(const Slice *slice) const
+{
+ int id = slice->id();
+ if (id==0) return true;
+
+ QString slices = property("Oblique:slices_");
+ QStringList sliceList = QStringList::split('\n', slices);
+ for (QStringList::Iterator i(sliceList.begin()); i!= sliceList.end(); ++i)
+ {
+ if ((*i).toInt(0, 16) == id) return true;
+ }
+ return false;
+}
+
+
+
+Slice::Slice(Base *base, int id, const QString &name)
+{
+ mId = id;
+ mBase = base;
+ mName = name;
+}
+
+QString Slice::name() const
+{
+ if (mId == 0) return i18n("Complete Collection");
+ return mName;
+}
+
+void Slice::setName(const QString &name)
+{
+ if (mId == 0) return;
+ mName = name;
+ emit mBase->slicesModified();
+}
+
+void Slice::remove()
+{
+ if (mId == 0) return;
+ mBase->removeSlice(this);
+ Base *base = mBase;
+
+ for (FileId fi=1; ; fi++)
+ {
+ File f = base->first(fi);
+ f.removeFrom(this);
+ fi = f.id();
+ }
+ emit base->slicesModified();
+}
+
+
+
diff --git a/noatun-plugins/oblique/file.h b/noatun-plugins/oblique/file.h
new file mode 100644
index 0000000..21d3be9
--- /dev/null
+++ b/noatun-plugins/oblique/file.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2003,2004 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#ifndef FILE_H
+#define FILE_H
+
+#include "base.h"
+#include <kurl.h>
+
+
+class Slice;
+class Query;
+
+/**
+ * just an file from the list in the database
+ **/
+class File
+{
+
+ friend class Base;
+ File(Base *base, FileId id);
+
+ mutable Base *mBase;
+ FileId mId;
+
+public:
+ /**
+ * create a copy of the reference
+ **/
+ File(const File &ref);
+ /**
+ * create a null reference
+ **/
+ File();
+
+ /**
+ * make me a copy of the reference
+ **/
+ File &operator=(const File &ref);
+
+ bool operator ==(const File &other) { return mId == other.mId; }
+
+ inline operator bool() const { return mId; }
+
+ QString file() const;
+ KURL url() const;
+ QString property(const QString &property) const;
+ void setProperty(const QString &key, const QString &value);
+ void clearProperty(const QString &key);
+ QStringList properties() const;
+ inline FileId id() const { return mId; }
+ void setId(FileId id);
+
+ inline Base *base() { return mBase; }
+ inline const Base *base() const { return mBase; }
+
+ /**
+ * when displaying me in @p query, place
+ * me immediately after @p after
+ **/
+ void setPosition(Query *query, const File &after);
+
+ /**
+ * when displaying @p query, set @p after according
+ * to what @ref setPosition was given.
+ *
+ * @return false if no position was set
+ **/
+ bool getPosition(const Query *query, File *after) const;
+
+ /**
+ * remove this file from the db, and emit Base::removed(File)
+ * the File objects don't change, but become invalid (careful!)
+ **/
+ void remove();
+
+ void addTo(Slice *slice);
+ void removeFrom(Slice *slice);
+ bool isIn(const Slice *slice) const;
+
+ /**
+ * load the tag information into the DB
+ **/
+ void makeCache();
+};
+
+class Slice
+{
+ int mId;
+ QString mName;
+ Base *mBase;
+
+public:
+ /**
+ * @internal
+ * create a slice based on its data
+ **/
+ Slice(Base *base, int id, const QString &name);
+
+ QString name() const;
+ void setName(const QString &name);
+
+ /**
+ * remove this slice from the list of slices
+ * and dereference all Files from this slice
+ **/
+ void remove();
+
+ int id() const { return mId; }
+};
+
+
+
+
+#endif
diff --git a/noatun-plugins/oblique/kbuffer.cpp b/noatun-plugins/oblique/kbuffer.cpp
new file mode 100644
index 0000000..f54110e
--- /dev/null
+++ b/noatun-plugins/oblique/kbuffer.cpp
@@ -0,0 +1,87 @@
+// Author: Eray Ozkural (exa) <erayo@cs.bilkent.edu.tr>, (c) 2002
+//
+// Copyright: GNU LGPL: http://www.gnu.org/licenses/lgpl.html
+
+#include "kbuffer.h"
+#include <algorithm>
+#include <iostream>
+#include <iomanip>
+
+KBuffer::KBuffer()
+{
+ bufPos = buf.end(); // will become 0 in the beginning
+}
+
+KBuffer::~KBuffer(){
+}
+
+/** open a memory buffer */
+bool KBuffer::open(int ) {
+ // ignore mode
+ buf.erase(buf.begin(), buf.end()); // erase buffer
+ buf.reserve(8); // prevent iterators from ever being 0 and start with a reasonable mem
+ bufPos = buf.end(); // reset position
+ return true;
+}
+
+/** Close buffer */
+void KBuffer::close(){
+}
+
+/** No descriptions */
+void KBuffer::flush(){
+}
+
+/** query buffer size */
+Q_ULONG KBuffer::size() const {
+ return buf.size();
+}
+
+/** read a block of memory from buffer, advances read/write position */
+Q_LONG KBuffer::readBlock(char* data, long unsigned int maxLen) {
+ int len;
+ if ((long unsigned)(buf.end()-bufPos) > maxLen)
+ len = maxLen;
+ else
+ len = buf.end()-bufPos;
+ std::vector<char>::iterator bufPosNew = bufPos + len;
+ for (std::vector<char>::iterator it=bufPos; it < bufPosNew; it++, data++)
+ *data = *it;
+ bufPos = bufPosNew;
+ return len;
+}
+
+/** write a block of memory into buffer */
+Q_LONG KBuffer::writeBlock(const char *data, long unsigned int len){
+ int pos = bufPos-buf.begin();
+ copy(data, data+len, inserter(buf,bufPos));
+ bufPos = buf.begin() + pos + len;
+ return len;
+}
+
+/** read a byte */
+int KBuffer::getch() {
+ if (bufPos!=buf.end())
+ return *(bufPos++);
+ else
+ return -1;
+}
+
+/** write a byte */
+int KBuffer::putch(int c) {
+ int pos = bufPos-buf.begin();
+ buf.insert(bufPos, c);
+ bufPos = buf.begin() + pos + 1;
+ return c;
+}
+
+/** undo last getch()
+ */
+int KBuffer::ungetch(int c) {
+ if (bufPos!=buf.begin()) {
+ bufPos--;
+ return c;
+ }
+ else
+ return -1;
+}
diff --git a/noatun-plugins/oblique/kbuffer.h b/noatun-plugins/oblique/kbuffer.h
new file mode 100644
index 0000000..adc62ba
--- /dev/null
+++ b/noatun-plugins/oblique/kbuffer.h
@@ -0,0 +1,48 @@
+// Author: Eray Ozkural (exa) <erayo@cs.bilkent.edu.tr>, (c) 2002
+//
+// Copyright: GNU LGPL: http://www.gnu.org/licenses/lgpl.html
+
+#ifndef KBUFFER_H
+#define KBUFFER_H
+
+#include <qiodevice.h>
+#include <vector>
+#include <queue>
+
+/**A buffer device for flexible and efficient buffers.
+
+ *@author Eray Ozkural (exa)
+ */
+
+class KBuffer : public QIODevice {
+public:
+ KBuffer();
+ ~KBuffer();
+ /** open a memory buffer */
+ bool open(int mode);
+ /** read in a block of memory */
+ Q_LONG readBlock(char* data, long unsigned int maxLen);
+ /** query buffer size */
+ Q_ULONG size() const;
+ /** No descriptions */
+ void flush();
+ /** Close buffer */
+ void close();
+ /** write a block of memory */
+ Q_LONG writeBlock(const char *data, long unsigned int maxLen);
+ /** read a byte */
+ int getch();
+ /** undo last getch()
+ */
+ int ungetch(int);
+ /** write a byte */
+ int putch(int);
+ void* data() {
+ return &buf[0];
+ }
+private:
+ std::vector<char> buf;
+ std::vector<char>::iterator bufPos;
+};
+
+#endif
diff --git a/noatun-plugins/oblique/kdatacollection.cpp b/noatun-plugins/oblique/kdatacollection.cpp
new file mode 100644
index 0000000..9e2ddac
--- /dev/null
+++ b/noatun-plugins/oblique/kdatacollection.cpp
@@ -0,0 +1,143 @@
+/*
+ This file is part of the KDE libraries
+ Copyright (C) 2003 Charles Samuels <charles@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kdatacollection.h"
+
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+
+#include <qfile.h>
+#include <qfileinfo.h>
+
+
+KDataCollection::KDataCollection(
+ KConfig *config, const QString &group, const QString &entry,
+ const char *datadir, const QString &dir
+ )
+{
+ init(config, group, entry, datadir, dir);
+}
+
+KDataCollection::KDataCollection(
+ KConfig *config, const QString &group, const QString &entry,
+ const QString &dir
+ )
+{
+ init(config, group, entry, "appdata", dir);
+}
+
+KDataCollection::KDataCollection(
+ KConfig *config, const QString &group, const QString &dir
+ )
+{
+ init(config, group, dir, "appdata", dir);
+}
+
+KDataCollection::KDataCollection(KConfig *config, const QString &dir)
+{
+ init(config, "KDataCollection", dir, "appdata", dir);
+}
+
+KDataCollection::KDataCollection(const QString &dir)
+{
+ init(KGlobal::config(), "KDataCollection", dir, "appdata", dir);
+}
+
+void KDataCollection::init(
+ KConfig *config, const QString &group, const QString &entry,
+ const char *datadir, const QString &dir
+ )
+{
+ mConfig = config;
+ mGroup = group;
+ mEntry = entry;
+ mDir = dir;
+ mDatadir = datadir;
+}
+
+QStringList KDataCollection::names() const
+{
+ KConfigGroup g(mConfig, mGroup);
+
+ // these are the entries I have
+ QStringList n = g.readListEntry(mEntry);
+ QStringList fs = KGlobal::dirs()->findAllResources(mDatadir, mDir+"/*", false, true);
+ QStringList total;
+
+ for (QStringList::Iterator i(fs.begin()); i != fs.end(); ++i)
+ {
+ QFileInfo fi(*i);
+ QString name = fi.fileName();
+ if (!n.contains(name))
+ {
+ total.append(name);
+ }
+ }
+
+ return total;
+}
+
+void KDataCollection::remove(const QString &name)
+{
+ KConfigGroup g(mConfig, mGroup);
+ QString location = file(name);
+ if (location.isEmpty()) return;
+ if (location == saveFile(name, false))
+ {
+ QFile(location).remove();
+ // is there a system one too?
+ location = file(name, false);
+ if (location.isEmpty()) return;
+ }
+
+ QStringList n = g.readListEntry(mEntry);
+ if (n.contains(name)) return;
+ n.append(name);
+ g.writeEntry(mEntry, n);
+}
+
+QString KDataCollection::file(const QString &name, bool create)
+{
+ QString path = ::locate(mDatadir, mDir+"/"+name);
+
+ if (path.isEmpty() && create)
+ {
+ path = saveFile(name, true);
+ }
+ return path;
+}
+
+QString KDataCollection::saveFile(const QString &name, bool create)
+{
+ if (!KGlobal::dirs()->isRestrictedResource(mDatadir, mDir+"/"+name))
+ {
+ QString s = KGlobal::dirs()->saveLocation(mDatadir, mDir, create);
+
+ if (s.length() && create)
+ {
+ s += "/" + name;
+ QFile(s).open(IO_ReadWrite); // create it
+ }
+ return s;
+ }
+ return QString::null;
+}
+
+
diff --git a/noatun-plugins/oblique/kdatacollection.h b/noatun-plugins/oblique/kdatacollection.h
new file mode 100644
index 0000000..e8820f3
--- /dev/null
+++ b/noatun-plugins/oblique/kdatacollection.h
@@ -0,0 +1,144 @@
+/*
+ This file is part of the KDE libraries
+ Copyright (C) 2003 Charles Samuels <charles@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KDATACOLLECTION_H
+#define KDATACOLLECTION_H
+
+#include <qstring.h>
+#include <qstringlist.h>
+
+class KConfig;
+
+/**
+ * This class allows you to have a set of files. Some of which can be included
+ * with your application, and some can be created by your users.
+ * Examples of a use for this function can be:
+ *<ul>
+ * <li>Noatun's Equalizer preset: Each Equalizer preset is in its own XML
+ * file, and the user can delete, create new ones, and modify old ones</li>
+ *</ul>
+ *
+ *<pre>
+ * KDataCollection profiles("appname/ui_profiles");
+ * QStringList letUserSelectOne = profiles.names();
+ * QString fileToOpen = profiles.file(theOneUserSelected);
+ * QString fileToWriteTo = profiles.saveFile(theOneUserSelected);
+ *</pre>
+ *
+ * @author Charles Samuels <charles@kde.org>
+ **/
+class KDataCollection
+{
+ KConfig *mConfig;
+ QString mGroup, mEntry, mDir;
+ const char *mDatadir;
+
+ struct Private;
+ Private *d;
+
+public:
+ /**
+ * constructor. This gives you most control over the destination of
+ * settings, @p dir is the second argument to locate(datadir, ...)
+ *
+ * @param datadir is what is passed to locate, this is "appdata" by default
+ **/
+ KDataCollection(
+ KConfig *config, const QString &group, const QString &entry,
+ const char *datadir, const QString &dir
+ );
+
+ /**
+ * constructor. This gives you most control over the destination of
+ * settings, @p dir is the second argument to locate("appdata", ...)
+ **/
+ KDataCollection(
+ KConfig *config, const QString &group, const QString &entry,
+ const QString &dir
+ );
+
+ /**
+ * constructor. The entry in the KConfig group will be named the same as
+ * @p dir.
+ *
+ * otherwise the same as the previous function
+ **/
+ KDataCollection(
+ KConfig *config, const QString &group,
+ const QString &dir
+ );
+
+ /**
+ * constructor. The group will be "KDataCollection", The entry in the
+ * KConfig group will be named the same as
+ * @p dir.
+ *
+ * otherwise the same as the previous function
+ **/
+ KDataCollection(KConfig *config, const QString &dir);
+
+ /**
+ * constructor. the KConfig is assumed to be KGlobal::config()
+ *
+ * otherwise the same as the previous function
+ **/
+ KDataCollection(const QString &dir);
+
+
+ /**
+ * returns a list of existant, non hidden files
+ **/
+ QStringList names() const;
+
+ /**
+ * deletes the file if it is in KDEHOME, or marks it as hidden if it's a
+ * system file
+ **/
+ void remove(const QString &name);
+
+ /**
+ * @returns the filename for a file named @p name, if @p create
+ * is true, it will create the file if it doesn't exist, if @p create is false,
+ * it will return an empty string, unless the file already exists
+ *
+ * if you want to modify this file, you should use saveFile instead
+ **/
+ QString file(const QString &name, bool create=true);
+
+ /**
+ * @returns the filename for a file you can save into. If @p create is
+ * false, it'll return an empty string if the file doesn't already exist in
+ * KDEHOME
+ *
+ * This function will not create the file, only return what the name is in
+ * theory.
+ *
+ * It will not return a file if the Kiosk framework claims that it's
+ * restricted
+ **/
+ QString saveFile(const QString &name, bool create=true);
+
+private:
+ void init(
+ KConfig *config, const QString &group, const QString &entry,
+ const char *datadir, const QString &dir
+ );
+};
+
+#endif
diff --git a/noatun-plugins/oblique/kdbt.h b/noatun-plugins/oblique/kdbt.h
new file mode 100644
index 0000000..acaae57
--- /dev/null
+++ b/noatun-plugins/oblique/kdbt.h
@@ -0,0 +1,59 @@
+// Author: Eray Ozkural (exa) <erayo@cs.bilkent.edu.tr>, (c) 2002
+//
+// Copyright: GNU LGPL: http://www.gnu.org/licenses/lgpl.html
+
+
+#ifndef KDbt_Interface
+#define KDbt_Interface
+
+#include <db_cxx.h>
+#include <qdatastream.h>
+#include <qbuffer.h>
+#include "kbuffer.h"
+
+/**A generic wrapper for "database thang" class that abstracts binary streaming operations.
+ *@author Eray Ozkural (exa)
+ */
+
+template <typename T>
+class KDbt : public Dbt {
+public:
+ /* assume streaming operators on QDataStream
+ QDataStream & operator>> ( QDataStream& >>, T &);
+ QDataStream & operator<< ( QDataStream& >>, T &);
+ */
+ KDbt() {
+ }
+ /** construct a Dbt from obj */
+ KDbt(const T& obj) {
+ set(obj);
+ }
+// operator Dbt() {
+// return Dbt(thang.data(), thang.size());
+// }
+ /** set "thang" to the contents of obj */
+ void set(const T& obj) {
+// KBuffer buffer(thang);
+ QDataStream ds(&thang);
+ ds << obj;
+// std::cerr << "thang size " << thang.size() << endl;
+// buffer.close();
+// set_data(thang.data());
+// set_size(buffer.size());
+ set_data(thang.data());
+ set_size(thang.size());
+ }
+ void get(T& obj) {
+ QByteArray buffer;
+ buffer.setRawData((char*)get_data(),get_size());
+ QDataStream ds(buffer,IO_ReadWrite);
+ ds >> obj;
+ buffer.resetRawData((char*)get_data(),get_size());
+ }
+private:
+ /** Internal data */
+// QByteArray thang;
+ KBuffer thang;
+};
+
+#endif
diff --git a/noatun-plugins/oblique/menu.cpp b/noatun-plugins/oblique/menu.cpp
new file mode 100644
index 0000000..0556d88
--- /dev/null
+++ b/noatun-plugins/oblique/menu.cpp
@@ -0,0 +1,231 @@
+// Copyright (c) 2003,2004 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#include "menu.h"
+#include <kiconloader.h>
+#include <klocale.h>
+
+#include "tree.h"
+
+FileMenu::FileMenu(QWidget *parent, Oblique *oblique, File file)
+ : KPopupMenu(parent)
+{
+ if (file)
+ mFiles.append(file);
+ insertItem(
+ BarIconSet("delete"), i18n("&Remove From Playlist"),
+ this, SLOT(removeFromList())
+ );
+ insertItem(i18n("&Properties"), this, SLOT(properties()));
+
+ (new SliceListAction(
+ i18n("&Slices"), oblique,
+ this, SLOT(toggleInSlice(Slice *)),
+ mFiles, this
+ ))->plug(this);
+}
+
+static void addTo(QValueList<File> &files, TreeItem *item)
+{
+ File f = item->file();
+ if (f) files.append(f);
+
+ item = item->firstChild();
+
+ while (item)
+ {
+ addTo(files, item);
+ item = item->nextSibling();
+ }
+}
+
+FileMenu::FileMenu(QWidget *parent, Oblique *oblique, TreeItem *items)
+ : KPopupMenu(parent)
+{
+ addTo(mFiles, items);
+
+ insertItem(
+ BarIconSet("delete"), i18n("&Remove From Playlist"),
+ this, SLOT(removeFromList())
+ );
+ insertItem(i18n("&Properties"), this, SLOT(properties()));
+
+ (new SliceListAction(
+ i18n("&Slices"), oblique,
+ this, SLOT(toggleInSlice(Slice *)),
+ mFiles, this
+ ))->plug(this);
+}
+
+void FileMenu::removeFromList()
+{
+ for (QValueList<File>::Iterator i(mFiles.begin()); i != mFiles.end(); ++i)
+ {
+ (*i).remove();
+ }
+}
+
+void FileMenu::properties()
+{
+ new ObliquePropertiesDialog(mFiles, parentWidget());
+}
+
+void FileMenu::toggleInSlice(Slice *slice)
+{
+ void (File::*task)(Slice*)=0;
+ for (QValueList<File>::Iterator i(mFiles.begin()); i != mFiles.end(); ++i)
+ {
+ if (!task)
+ { // we check with the first one
+ if ((*i).isIn(slice))
+ task = &File::removeFrom;
+ else
+ task = &File::addTo;
+ }
+
+ ((*i).*task)(slice);
+ }
+}
+
+
+
+SliceListAction::SliceListAction(
+ const QString &text, Oblique *oblique,
+ QObject *reciever, const char *slot,
+ const QValueList<File> &files, QObject *parent, const char *name
+ ) : KActionMenu(text, parent, name)
+{
+ mFiles = files;
+ mOblique = oblique;
+ slicesModified();
+ if (reciever)
+ connect(this, SIGNAL(activated(Slice*)), reciever, slot);
+ connect(popupMenu(), SIGNAL(activated(int)), SLOT(hit(int)));
+ connect(oblique->base(), SIGNAL(slicesModified()), SLOT(slicesModified()));
+}
+
+void SliceListAction::slicesModified()
+{
+ mIndexToSlices.clear();
+ KPopupMenu *menu = popupMenu();
+ menu->clear();
+
+ QPtrList<Slice> slices = mOblique->base()->slices();
+ int id=1;
+
+
+
+ for (QPtrListIterator<Slice> i(slices); *i; ++i)
+ {
+ Slice *s = *i;
+ if (s->id()==0 && mFiles.count())
+ {
+ continue;
+ }
+
+ menu->insertItem(s->name(), id);
+ if (mFiles.count())
+ {
+ menu->setItemChecked(id, mFiles.first().isIn(s));
+ }
+// else if (mOblique->slice() == s) // TODO: show the selected one
+// {
+// menu->setItemChecked(id, true);
+// }
+
+ if (mFiles.count() && s->id() == 0)
+ {
+ menu->setItemEnabled(id, false);
+ }
+
+ mIndexToSlices.insert(id, s);
+ id++;
+ }
+}
+
+void SliceListAction::hit(int index)
+{
+ emit activated(mIndexToSlices[index]);
+}
+
+
+
+SchemaListAction::SchemaListAction(
+ const QString &text,
+ QObject *reciever, const char *slot,
+ QObject *parent, const char *name
+ ) : KActionMenu(text, parent, name)
+{
+ mTree = 0;
+ if (reciever)
+ connect(this, SIGNAL(activated(const QString&)), reciever, slot);
+ connect(popupMenu(), SIGNAL(aboutToShow()), SLOT(prepare()));
+ connect(popupMenu(), SIGNAL(activated(int)), SLOT(hit(int)));
+}
+
+void SchemaListAction::prepare()
+{
+ assert(mTree);
+ mIndexToSchemas.clear();
+ KPopupMenu *menu = popupMenu();
+ menu->clear();
+
+ if (!mTree) return;
+
+ int id=1;
+
+ QStringList names = mTree->oblique()->schemaNames();
+
+ for (QStringList::Iterator i(names.begin()); i != names.end(); ++i)
+ {
+ Query q;
+ QString title = mTree->oblique()->loadSchema(q, *i);
+
+ menu->insertItem(title, id);
+
+ menu->setItemChecked(id, mTree->fileOfQuery() == *i);
+
+ mIndexToSchemas.insert(id, *i);
+ id++;
+ }
+}
+
+void SchemaListAction::hit(int index)
+{
+ emit activated(mIndexToSchemas[index]);
+}
+
+
+
+ObliquePropertiesDialog::ObliquePropertiesDialog(const QValueList<File> &files, QWidget *parent)
+ : KPropertiesDialog(makeItems(files), parent), mFiles(files)
+{
+ connect(this, SIGNAL(propertiesClosed()), SLOT(deleteLater()));
+ connect(this, SIGNAL(applied()), SLOT(modified()));
+
+ show();
+}
+
+void ObliquePropertiesDialog::modified()
+{
+ // TODO reload the file's info
+ for (QValueList<File>::Iterator i(mFiles.begin()); i != mFiles.end(); ++i)
+ {
+ (*i).makeCache();
+ (*i).base()->notifyChanged(*i);
+ }
+}
+
+KFileItemList ObliquePropertiesDialog::makeItems(const QValueList<File> &files)
+{
+ KFileItemList kl;
+ for (QValueList<File>::ConstIterator i(files.begin()); i != files.end(); ++i)
+ {
+ File f = *i;
+ kl.append(new KFileItem(f.url(), f.property("mimetype"), KFileItem::Unknown));
+ }
+ return kl;
+}
+
+#include "menu.moc"
+
diff --git a/noatun-plugins/oblique/menu.h b/noatun-plugins/oblique/menu.h
new file mode 100644
index 0000000..793180c
--- /dev/null
+++ b/noatun-plugins/oblique/menu.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#ifndef MENU_H
+#define MENU_H
+
+#include <kpopupmenu.h>
+#include <kpropertiesdialog.h>
+#include <kaction.h>
+
+#include "file.h"
+#include "oblique.h"
+
+/**
+ * a context menu for a item in the list
+ **/
+class FileMenu : public KPopupMenu
+{
+Q_OBJECT
+ QValueList<File> mFiles;
+
+public:
+ FileMenu(QWidget *parent, Oblique *oblique, File file);
+ /**
+ * @recursively uses everything under @p files
+ **/
+ FileMenu(QWidget *parent, Oblique *oblique, TreeItem *files);
+
+private slots:
+ void removeFromList();
+ void properties();
+ void toggleInSlice(Slice *slice);
+};
+
+class SliceListAction : public KActionMenu
+{
+Q_OBJECT
+ QMap<int, Slice*> mIndexToSlices;
+ QValueList<File> mFiles;
+ Oblique *mOblique;
+
+public:
+ SliceListAction(
+ const QString &text, Oblique *oblique,
+ QObject *reciever, const char *slot,
+ const QValueList<File> &files = QValueList<File>(), QObject *parent=0, const char *name=0
+ );
+
+signals:
+ void activated(Slice *slice);
+
+private slots:
+ void hit(int index);
+ void slicesModified();
+};
+
+
+class SchemaListAction : public KActionMenu
+{
+Q_OBJECT
+ QMap<int, QString> mIndexToSchemas;
+ Tree *mTree;
+
+public:
+ SchemaListAction(
+ const QString &text,
+ QObject *reciever, const char *slot,
+ QObject *parent, const char *name
+ );
+
+ void setTree(Tree *tree) { mTree = tree; }
+
+signals:
+ void activated(const QString &);
+
+private slots:
+ void hit(int index);
+ void prepare();
+};
+
+
+class ObliquePropertiesDialog : public KPropertiesDialog
+{
+Q_OBJECT
+ QValueList<File> mFiles;
+
+public:
+ ObliquePropertiesDialog(const QValueList<File> &files, QWidget *parent);
+
+private:
+ static KFileItemList makeItems(const QValueList<File> &files);
+
+private slots:
+ void modified();
+};
+
+
+#endif
+
diff --git a/noatun-plugins/oblique/oblique.cpp b/noatun-plugins/oblique/oblique.cpp
new file mode 100644
index 0000000..97e75be
--- /dev/null
+++ b/noatun-plugins/oblique/oblique.cpp
@@ -0,0 +1,325 @@
+// Copyright (c) 2003-2005 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#include "oblique.h"
+#include "base.h"
+#include "view.h"
+#include "file.h"
+#include "selector.h"
+#include "cmodule.h"
+
+#include <noatun/player.h>
+
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <kio/job.h>
+#include <kfileitem.h>
+
+#include <qtimer.h>
+
+extern "C" Plugin *create_plugin()
+{
+ return new Oblique();
+}
+
+
+
+Oblique::Oblique()
+ : Playlist(0, 0), mSchemaCollection("oblique/schemas")
+{
+ mView = 0;
+ mAdder = 0;
+
+ KConfigGroup g(KGlobal::config(), "oblique");
+
+ mBase = new Base(::locate("data", "noatun/")+"/oblique-list");
+
+ mView = new View(this);
+ connect(napp->player(), SIGNAL(loopTypeChange(int)), SLOT(loopTypeChange(int)));
+
+ mSelector = new SequentialSelector(mView->tree());
+
+ new Configure(this);
+
+ // psst, window's gone, pass it on!
+ connect(mView, SIGNAL(listHidden()), SIGNAL(listHidden()));
+ connect(mView, SIGNAL(listShown()), SIGNAL(listShown()));
+
+ loopTypeChange(napp->player()->loopStyle());
+}
+
+
+Oblique::~Oblique()
+{
+ adderDone();
+ delete mView;
+ delete mBase;
+}
+
+void Oblique::loopTypeChange(int i)
+{
+ PlaylistItem now = current();
+
+ if (i == Player::Random)
+ {
+ if (!dynamic_cast<RandomSelector*>(mSelector))
+ {
+ delete mSelector;
+ mSelector = new RandomSelector(mView->tree());
+ }
+ }
+ else
+ {
+ delete mSelector;
+ mSelector = new SequentialSelector(mView->tree());
+ }
+}
+
+void Oblique::adderDone()
+{
+ delete mAdder;
+ mAdder = 0;
+}
+
+
+void Oblique::reset()
+{
+ mView->tree()->setCurrent(0);
+ loopTypeChange(0);
+}
+
+void Oblique::clear()
+{
+ mBase->clear();
+}
+
+void Oblique::addFile(const KURL &url, bool play)
+{
+ KFileItem fileItem(KFileItem::Unknown, KFileItem::Unknown, url);
+ if (fileItem.isDir())
+ {
+ beginDirectoryAdd(url);
+ }
+ else
+ {
+ File f = mBase->add(url.path());
+ PlaylistItem p=new Item(f);
+ p.data()->added();
+ if (play) setCurrent(p);
+ }
+}
+
+
+PlaylistItem Oblique::next()
+{
+ return mSelector->next();
+}
+
+PlaylistItem Oblique::previous()
+{
+ return mSelector->previous();
+}
+
+
+PlaylistItem Oblique::current()
+{
+ return mSelector->current();
+}
+
+void Oblique::setCurrent(const PlaylistItem &item)
+{
+ if (!item) return;
+ mSelector->setCurrent(*static_cast<Item*>(const_cast<PlaylistItemData*>(item.data())));
+ emit playCurrent();
+}
+
+
+
+PlaylistItem Oblique::getFirst() const
+{
+ FileId first=1;
+ File item = mBase->first(first);
+
+ if (!item) return 0;
+
+ return new Item(item);
+}
+
+
+PlaylistItem Oblique::getAfter(const PlaylistItem &item) const
+{
+ File after = mBase->first(static_cast<const Item*>(item.data())->itemFile().id()+1);
+ if (!after) return 0;
+ return new Item(after);
+}
+
+bool Oblique::listVisible() const
+{
+ return mView->isVisible();
+}
+
+void Oblique::showList()
+{
+ mView->show();
+}
+
+void Oblique::hideList()
+{
+ mView->hide();
+}
+
+
+void Oblique::selected(TreeItem *cur)
+{
+ Item *item = new Item(cur->file());
+ PlaylistItem pli = item;
+ setCurrent(pli);
+}
+
+void Oblique::beginDirectoryAdd(const KURL &url)
+{
+ if (mAdder)
+ {
+ mAdder->add(url);
+ }
+ else
+ {
+ mAdder = new DirectoryAdder(url, this);
+ connect(mAdder, SIGNAL(done()), SLOT(adderDone()));
+ }
+}
+
+
+
+Loader::Loader(Tree *tree)
+ : QObject(tree)
+{
+ mTree = tree;
+ mBase = mTree->oblique()->base();
+ mDeferredLoaderAt=1;
+
+
+ QTimer::singleShot(0, this, SLOT(loadItemsDeferred()));
+}
+
+void Loader::loadItemsDeferred()
+{
+ // do/try 16 at a time
+ for (int xx=0; xx < 16; xx++)
+ {
+ if (mDeferredLoaderAt > mBase->high())
+ {
+ // finished
+ mBase->resetFormatVersion();
+ emit finished();
+ return;
+ }
+
+ File f = mBase->find(mDeferredLoaderAt);
+
+ if (f)
+ {
+ if (mBase->formatVersion() <= 0x00010001)
+ f.makeCache();
+
+ if (f.isIn(mTree->slice()))
+ mTree->insert(f);
+ }
+ mDeferredLoaderAt++;
+ }
+
+ QTimer::singleShot(0, this, SLOT(loadItemsDeferred()));
+}
+
+
+DirectoryAdder::DirectoryAdder(const KURL &dir, Oblique *oblique)
+{
+ listJob=0;
+ mOblique = oblique;
+
+ add(dir);
+}
+
+void DirectoryAdder::add(const KURL &dir)
+{
+ if (dir.upURL().equals(currentJobURL, true))
+ {
+ // We are a subdir of our currentJobURL and need to get listed next,
+ // NOT after all the other dirs that are on the same level as
+ // currentJobURL!
+ lastAddedSubDirectory = pendingAddDirectories.insert(lastAddedSubDirectory, dir);
+ lastAddedSubDirectory++;
+ }
+ else
+ {
+ pendingAddDirectories.append(dir);
+ }
+ addNextPending();
+}
+
+void DirectoryAdder::addNextPending()
+{
+ KURL::List::Iterator pendingIt= pendingAddDirectories.begin();
+ if (!listJob && (pendingIt!= pendingAddDirectories.end()))
+ {
+ currentJobURL= *pendingIt;
+ listJob = KIO::listDir(currentJobURL, false, false);
+ connect(
+ listJob, SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList&)),
+ SLOT(slotEntries(KIO::Job*, const KIO::UDSEntryList&))
+ );
+ connect(
+ listJob, SIGNAL(result(KIO::Job *)),
+ SLOT(slotResult(KIO::Job *))
+ );
+ connect(
+ listJob, SIGNAL(redirection(KIO::Job *, const KURL &)),
+ SLOT(slotRedirection(KIO::Job *, const KURL &))
+ );
+ pendingAddDirectories.remove(pendingIt);
+ lastAddedSubDirectory = pendingAddDirectories.begin();
+ }
+}
+
+void DirectoryAdder::slotResult(KIO::Job *job)
+{
+ listJob= 0;
+ if (job && job->error())
+ job->showErrorDialog();
+ addNextPending();
+ if (!listJob)
+ emit done();
+}
+
+void DirectoryAdder::slotEntries(KIO::Job *, const KIO::UDSEntryList &entries)
+{
+ QMap<QString,KURL> __list; // temp list to sort entries
+
+ KIO::UDSEntryListConstIterator it = entries.begin();
+ KIO::UDSEntryListConstIterator end = entries.end();
+
+ for (; it != end; ++it)
+ {
+ KFileItem file(*it, currentJobURL, false /* no mimetype detection */, true);
+ // "prudhomm:
+ // insert the path + url in the map to sort automatically by path
+ // note also that you use audiocd to rip your CDs then it will be sorted the right way
+ // now it is an easy fix to have a nice sort BUT it is not the best
+ // we should sort based on the tracknumber"
+ // - copied over from old kdirlister hack <hans_meine@gmx.de>
+ __list.insert(file.url().path(), file.url());
+ }
+ QMap<QString,KURL>::Iterator __it;
+ for( __it = __list.begin(); __it != __list.end(); ++__it )
+ {
+ oblique()->addFile(__it.data(), false);
+ }
+}
+
+void DirectoryAdder::slotRedirection(KIO::Job *, const KURL & url)
+{
+ currentJobURL= url;
+}
+
+
+#include "oblique.moc"
diff --git a/noatun-plugins/oblique/oblique.h b/noatun-plugins/oblique/oblique.h
new file mode 100644
index 0000000..93d1e18
--- /dev/null
+++ b/noatun-plugins/oblique/oblique.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2003-2005 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#ifndef OBLIQUE_H
+#define OBLIQUE_H
+
+#include <noatun/playlist.h>
+#include <noatun/plugin.h>
+#include "query.h"
+#include "kdatacollection.h"
+
+#include <kio/global.h>
+
+class View;
+class Tree;
+class Base;
+class Selector;
+class TreeItem;
+class DirectoryAdder;
+
+namespace KIO
+{
+ class ListJob;
+ class Job;
+}
+
+class Oblique : public Playlist, public Plugin
+{
+Q_OBJECT
+ View *mView;
+ Base *mBase;
+ Selector *mSelector;
+ KDataCollection mSchemaCollection;
+ DirectoryAdder *mAdder;
+
+
+public:
+ Oblique();
+ ~Oblique();
+
+ Base *base() { return mBase; }
+
+ QStringList schemaNames() const { return mSchemaCollection.names(); }
+ QString loadSchema(Query &q, const QString &name)
+ {
+ QString t = q.load(mSchemaCollection.file(name));
+ if (t.length())
+ q.setName(name);
+ return t;
+ }
+
+ void saveSchema(Query &q, const QString &name, const QString &title)
+ {
+ q.save(title, mSchemaCollection.saveFile(name));
+ }
+
+ void removeSchema(const QString &name)
+ {
+ mSchemaCollection.remove(name);
+ }
+
+ virtual void reset();
+ virtual void clear();
+ virtual void addFile(const KURL&, bool play=false);
+ virtual PlaylistItem next();
+ virtual PlaylistItem previous();
+ virtual PlaylistItem current();
+ virtual void setCurrent(const PlaylistItem &);
+ virtual PlaylistItem getFirst() const;
+ virtual PlaylistItem getAfter(const PlaylistItem &item) const;
+ virtual bool listVisible() const;
+ virtual void showList();
+ virtual void hideList();
+
+ virtual Playlist *playlist() { return this; }
+
+public slots:
+ void selected(TreeItem *cur);
+ void beginDirectoryAdd(const KURL &url);
+
+private slots:
+ void loopTypeChange(int i);
+ void adderDone();
+};
+
+/**
+ * loads the database into a Tree
+ **/
+class Loader : public QObject
+{
+Q_OBJECT
+ // the id of the next file to load
+ FileId mDeferredLoaderAt;
+ Tree *mTree;
+ Base *mBase;
+
+public:
+ Loader(Tree *into);
+
+signals:
+ void finished();
+
+private slots:
+ void loadItemsDeferred();
+};
+
+/**
+ * Adds a directory to
+ * emits @ref done() when finished so you
+ * can delete it
+ **/
+class DirectoryAdder : public QObject
+{
+ Q_OBJECT
+ Oblique *mOblique;
+ KURL::List pendingAddDirectories;
+ KURL::List::Iterator lastAddedSubDirectory;
+ KIO::ListJob *listJob;
+ KURL currentJobURL;
+
+public:
+ DirectoryAdder(const KURL &dir, Oblique *oblique);
+
+ Oblique *oblique() { return mOblique; }
+
+public slots:
+ void add(const KURL &dir);
+
+signals:
+ void done();
+
+private slots:
+ void slotResult(KIO::Job *job);
+ void slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
+ void slotRedirection(KIO::Job *, const KURL & url);
+
+private:
+
+ void addNextPending();
+};
+
+#endif
diff --git a/noatun-plugins/oblique/oblique.plugin b/noatun-plugins/oblique/oblique.plugin
new file mode 100644
index 0000000..c0b561c
--- /dev/null
+++ b/noatun-plugins/oblique/oblique.plugin
@@ -0,0 +1,73 @@
+Filename=noatun_oblique.la
+Author=Charles Samuels
+Site=http://noatun.kde.org/oblique
+Email=charles@kde.org
+Type=playlist
+License=GPL/QPL/Anti-Patent BSD
+Name=Oblique
+Name[az]=Oblik
+Name[cy]=Lletraws
+Name[eo]=Oblikva
+Name[es]=Oblicuo
+Name[fa]=مورب
+Name[hi]=ऑब्लिक
+Name[km]=បញ្ឆៀង
+Name[ne]=बक्र
+Name[pt_BR]=Oblíquo
+Name[ro]=Oblic
+Name[sl]=Ležeče
+Name[ta]= சாய்வான
+Name[tr]=Eğik
+Name[vi]=Xiên
+Comment=Auto-collating playlist
+Comment[bg]=Списък на файлове за изпълнение, който автоматично се сортира и проверява
+Comment[bs]=Auto-sortirajuća playlista
+Comment[ca]=Llista de reproducció ordenada automàticament
+Comment[cs]=Samořadící seznam skladeb
+Comment[da]=Auto-kollateret spilleliste
+Comment[de]=Selbständige Zusammenstellung der Wiedergabeliste
+Comment[el]=Λίστα αναπαραγωγής με αυτόματη ταξινόμηση
+Comment[eo]=Aŭtomate kunmetanta ludlisto
+Comment[es]=Crear lista de reproducción de forma automática
+Comment[et]=Automaatselt end kokkukeriv esitusnimekiri
+Comment[eu]=Erreproduzio-zerrendaren antolaketa automatikoa
+Comment[fa]=فهرست پخش تلفیق خودکار
+Comment[fi]=Automaattisesti järjestyvä soittolista
+Comment[fr]=Liste de lecture auto-assemblée
+Comment[fy]=Auto-plakkende ôfspyllist
+Comment[ga]=Seinmliosta uath-chomhordaithe
+Comment[gl]=Lista de reproduición auto-incorporante
+Comment[he]=רשימת ניגון נסגרת עצמאית
+Comment[hi]=ऑटो-कोलेटिंग प्लेलिस्ट
+Comment[hr]=Album s automatskim uspoređivanjem
+Comment[hu]=Automatikusan szelektáló lejátszási lista
+Comment[is]=Sjálfvirkur lagalisti
+Comment[it]=Raccoglie automaticamente la lista di selezione
+Comment[ja]=プレイリストの自動照合
+Comment[ka]=სიმღერათა სიის ავტო შეკუნშვა
+Comment[kk]=Автосұрыпталатың орындау тізімі
+Comment[km]=តម្រៀប​បញ្ជី​ចាក់​ដោយ​ស្វ័យ​ប្រវត្តិ​
+Comment[lt]=Automatiškai besikaupiantis gaidaraštis
+Comment[mk]=Самоподредувачка листа со нумери
+Comment[ms]=Mengumpul semak senarai main
+Comment[nb]=Selvjusterende spilleliste
+Comment[nds]=Automaatsch de Afspeellist tosamenstellen
+Comment[ne]=स्वत: समानुक्रमित प्लेसूची
+Comment[nl]=Auto-plakkende afspeellijst
+Comment[nn]=Sjølvjusterande speleliste
+Comment[pl]=Automatycznie zestawiająca się lista odtwarzania
+Comment[pt]=Lista de músicas autocolante
+Comment[pt_BR]=Lista de reprodução de colagem automática
+Comment[ru]=Автоматически собирающийся список воспроизведения
+Comment[sk]=Automaticky triedený playlist
+Comment[sl]=Samodejno zlagajoč predvajalni seznam
+Comment[sr]=Самосређујућа листа нумера
+Comment[sr@Latn]=Samosređujuća lista numera
+Comment[sv]=Spellista med automatiskt ordning
+Comment[ta]=தானாகாவே-சேர்க்கும் வாசிப்புப் பட்டியல்
+Comment[tg]=Худкорона ҷамъ кардани рӯйхати баровардҳо
+Comment[tr]=Otomatik karşılaştırılmış çalma listesi
+Comment[uk]=Список композицій, який автоматично об'єднується
+Comment[vi]=Danh mục nhạc tự động đối chiếu
+Comment[zh_CN]=自动收集播放列表
+Comment[zh_TW]=自動整理播放列表
diff --git a/noatun-plugins/oblique/obliqueui.rc b/noatun-plugins/oblique/obliqueui.rc
new file mode 100644
index 0000000..0ef4740
--- /dev/null
+++ b/noatun-plugins/oblique/obliqueui.rc
@@ -0,0 +1,31 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="Oblique" version="4">
+
+<MenuBar>
+ <Menu name="file" noMerge="1">
+ <text>&amp;Collection</text>
+ <Action name="add_files" />
+ <Action name="add_dir" />
+ <Action name="reload" />
+ <Separator />
+ <Action name="make_slice" />
+ <Action name="remove_slice" />
+ <Action name="slices" />
+ <Action name="schemas" />
+ </Menu>
+ <Menu name="window">
+ <text>&amp;Window</text>
+ <Action name="newtab" />
+ <Action name="removecurrenttab" />
+ </Menu>
+</MenuBar>
+
+
+
+<ToolBar noMerge="1" name="mainToolBar">
+ <text>Main Toolbar</text>
+ <Action name="add_files" />
+ <Action name="jump_label" />
+ <Action name="jump_text" />
+</ToolBar>
+</kpartgui>
diff --git a/noatun-plugins/oblique/query.cpp b/noatun-plugins/oblique/query.cpp
new file mode 100644
index 0000000..292ff16
--- /dev/null
+++ b/noatun-plugins/oblique/query.cpp
@@ -0,0 +1,570 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#include "query.h"
+#include "file.h"
+
+#include <iostream>
+
+#include <klocale.h>
+
+#include <qdom.h>
+#include <qfile.h>
+
+QueryGroup::QueryGroup()
+{
+ mFirstChild=0;
+ mNextSibling=0;
+
+ mFuzzyness = Case | Spaces | Articles;
+ mOptions = AutoHide;
+}
+
+QueryGroup::QueryGroup(const QueryGroup &copy)
+{
+ mFirstChild=0;
+ mNextSibling=0;
+
+ operator=(copy);
+
+}
+
+QueryGroup &QueryGroup::operator =(const QueryGroup &copy)
+{
+ mFuzzyness = copy.mFuzzyness;
+ mOptions = copy.mOptions;
+ mPropertyName = copy.mPropertyName;
+ mPresentation = copy.mPresentation;
+ mValue = copy.mValue;
+ return *this;
+}
+
+QueryGroup::~QueryGroup()
+{
+ delete mFirstChild;
+ delete mNextSibling;
+}
+
+void QueryGroup::insertAfter(QueryGroup *insert)
+{
+ QueryGroup *oldAfter = mNextSibling;
+ insert->setNextSibling(oldAfter);
+ setNextSibling(insert);
+}
+
+void QueryGroup::insertUnder(QueryGroup *insert)
+{
+ QueryGroup *oldUnder = mFirstChild;
+ insert->setNextSibling(oldUnder);
+ setFirstChild(insert);
+
+}
+
+void QueryGroup::move(Query *query, QueryGroup *under, QueryGroup *after)
+{
+ query->dump();
+
+ query->take(this);
+ if (after) after->insertAfter(this);
+ else if (under) under->insertUnder(this);
+ else query->insertFirst(this);
+
+ query->dump();
+}
+
+
+QueryGroup *QueryGroup::previous(Query *query)
+{
+ QueryGroup *f = query->firstChild();
+ if (f == this) return 0;
+
+ return previous(f);
+}
+
+QueryGroup *QueryGroup::previous(QueryGroup *startWith)
+{
+ QueryGroup *current = startWith;
+ QueryGroup *after = 0;
+
+ while (current)
+ {
+ after = current->nextSibling();
+ if (after == this)
+ return current;
+
+ if (QueryGroup *child = current->firstChild())
+ {
+ if (child == this)
+ return current;
+ child = previous(child);
+ if (child) return child;
+ }
+ current = after;
+ }
+ return 0;
+}
+
+
+
+QueryGroup *QueryGroup::lastChild()
+{
+ QueryGroup *first = mFirstChild;
+ if (!first) return 0;
+ while (first->nextSibling())
+ first = first->nextSibling();
+ return first;
+}
+
+
+bool QueryGroup::fuzzyness(Fuzzyness f) const
+{
+ return mFuzzyness & f;
+}
+
+bool QueryGroup::option(Option option) const
+{
+ return mOptions & option;
+}
+
+void QueryGroup::setOption(Option option, bool on)
+{
+ if (on)
+ mOptions |= option;
+ else
+ mOptions &= ~option;
+}
+
+bool QueryGroup::matches(const File &file) const
+{
+ QString prop = file.property(propertyName());
+
+ prop = prop.simplifyWhiteSpace();
+ if (prop.isNull()) prop = "";
+
+ QRegExp re(value());
+ return re.search(prop) != -1;
+}
+
+
+
+QString QueryGroup::presentation(const File &file) const
+{
+ // "$(property)"
+ QString format=presentation();
+
+ QRegExp find("(?:(?:\\\\\\\\))*\\$\\((.*)");
+
+ int start=0;
+ while (start != -1)
+ {
+ start = find.search(format, start);
+ if (start == -1) break;
+
+ // test if there's an odd amount of backslashes
+ if (start>0 && format[start-1]=='\\')
+ {
+ // yes, so half the amount of backslashes
+
+ // count how many there are first
+ QRegExp counter("([\\\\]+)");
+ counter.search(format, start-1);
+ uint len=counter.cap(1).length()-1;
+
+ // and half them, and remove one more
+ format.replace(start-1, len/2+1, "");
+ start=start-1+len/2+find.cap(1).length()+3;
+ continue;
+ }
+
+ // now replace the backslashes with half as many
+
+ if (format[start]=='\\')
+ {
+ // count how many there are first
+ QRegExp counter("([\\\\]+)");
+ counter.search(format, start);
+ uint len=counter.cap(1).length();
+
+ // and half them
+ format.replace(start, len/2, "");
+ start=start+len/2;
+ }
+
+ // "sth"foo"sth"
+ QString cont(find.cap(1));
+ QString prefix,suffix,propname;
+ unsigned int i=0;
+ if (cont[i] == '"')
+ {
+ i++;
+ for (; i < cont.length(); i++)
+ {
+ if (cont[i] != '"')
+ prefix += cont[i];
+ else
+ break;
+ }
+ i++;
+ }
+
+
+ for (; i < cont.length(); ++i)
+ {
+ if (cont[i]!='"' && cont[i]!=')')
+ propname += cont[i];
+ else
+ break;
+ }
+
+ if (cont[i] == '"')
+ {
+ i++;
+ for (; i < cont.length(); i++)
+ {
+ if (cont[i] != '"')
+ suffix += cont[i];
+ else
+ break;
+ }
+ i++;
+ }
+ i++;
+
+
+ QString propval = file.property(propname);
+
+// the following code won't be enabled until the presentation is reloaded
+// at the best times
+/* if (propname == "length")
+ {
+ int len = propval.toInt();
+ if ( len < 0 ) // no file loaded
+ propval = "--:--";
+
+ int secs = length()/1000; // convert milliseconds -> seconds
+ int seconds = secs % 60;
+ propval.sprintf("%.2d:%.2d", ((secs-seconds)/60), seconds);
+ }
+*/
+
+ if (propval.length())
+ {
+ propval = prefix+propval+suffix;
+ format.replace(start, i+2, propval);
+ start += propval.length();
+ }
+ else
+ {
+ format.replace(start, i+2, "");
+ }
+ }
+ return format;
+}
+
+
+Query::Query()
+{
+ mGroupFirst=0;
+}
+
+Query::~Query()
+{
+ delete mGroupFirst;
+}
+
+Query::Query(const Query &copy)
+{
+ mGroupFirst = 0;
+ operator=(copy);
+}
+
+Query &Query::operator =(const Query &copy)
+{
+ if (&copy == this) return *this;
+ delete mGroupFirst;
+ mGroupFirst=0;
+ if (const QueryGroup *parent = copy.firstChild())
+ {
+ mGroupFirst = new QueryGroup(*parent);
+ deepCopy(parent->firstChild(), mGroupFirst);
+ }
+ return *this;
+}
+
+QueryGroup *Query::firstChild()
+{
+ return mGroupFirst;
+}
+
+const QueryGroup *Query::firstChild() const
+{
+ return mGroupFirst;
+}
+
+void Query::setFirstChild(QueryGroup *g)
+{
+ mGroupFirst = g;
+}
+
+void Query::insertFirst(QueryGroup *g)
+{
+ g->setNextSibling(mGroupFirst);
+ mGroupFirst = g;
+}
+
+void Query::clear()
+{
+ delete mGroupFirst;
+ mGroupFirst=0;
+}
+
+QString Query::load(const QString &filename)
+{
+ QFile file(filename);
+ unless (file.open(IO_ReadOnly)) return QString::null;
+
+ QDomDocument doc;
+ doc.setContent(&file);
+ return load(doc.documentElement());
+}
+
+QString Query::load(QDomElement element)
+{
+ clear();
+
+ if (element.tagName().lower() == "obliqueschema")
+ {
+ QDomNode node = element.firstChild();
+
+ while (!node.isNull())
+ {
+ QDomElement e = node.toElement();
+ if (e.tagName().lower() == "group")
+ loadGroup(e);
+ node = node.nextSibling();
+ }
+ }
+ else
+ {
+ return QString::null;
+ }
+
+ // for internationalization:
+ // Add these if you create new schemas and release them with Oblique
+ (void)I18N_NOOP("Standard");
+
+ QString title = element.attribute("title");
+ if (element.hasAttribute("standard"))
+ title = i18n(title.utf8());
+ return title;
+}
+
+void Query::save(const QString &name, QDomElement &element)
+{
+ element.setTagName("ObliqueSchema");
+ element.setAttribute("version", "1.0");
+ element.setAttribute("title", name);
+ for (QueryGroup *g = firstChild(); g; g = g->nextSibling())
+ saveGroup(element, g);
+}
+
+void Query::save(const QString &name, const QString &filename)
+{
+ QFile file(filename);
+ unless (file.open(IO_Truncate|IO_ReadWrite ))
+ return;
+ QDomDocument doc("ObliqueSchema");
+ doc.setContent(QString("<!DOCTYPE ObliqueSchema><ObliqueSchema/>"));
+ QDomElement e = doc.documentElement();
+ save(name, e);
+
+ QTextStream ts(&file);
+ ts.setEncoding(QTextStream::UnicodeUTF8);
+ // scourge elimination
+ QString data = doc.toString();
+ QString old = data;
+ while (data.replace(QRegExp("([\n\r]+)(\t*) "), "\\1\\2\t") != old)
+ {
+ old = data;
+ }
+ ts << data;
+}
+
+
+void Query::take(QueryGroup *item)
+{
+ QueryGroup *previous = item->previous(this);
+
+ if (!previous)
+ {
+ mGroupFirst = item->nextSibling();
+ item->setNextSibling(0);
+ return;
+ }
+
+ if (previous->nextSibling() == item)
+ {
+ previous->setNextSibling(item->nextSibling());
+ item->setNextSibling(0);
+ }
+ else if (previous->firstChild() == item)
+ {
+ previous->setFirstChild(item->nextSibling());
+ item->setNextSibling(0);
+ }
+}
+
+static void dump(QueryGroup *item, int depth)
+{
+ if (!item) return;
+
+ do
+ {
+ for (int d = 0; d < depth; d++)
+ std::cerr << " ";
+ std::cerr << "prop: " << item->propertyName().utf8() << " pres: "
+ << item->presentation().utf8() << std::endl;
+ dump(item->firstChild(), depth+1);
+
+ } while ((item = item->nextSibling()));
+
+}
+
+void Query::dump()
+{
+ ::dump(firstChild(), 0);
+}
+
+
+
+
+void Query::loadGroup(QDomElement element, QueryGroup *parent)
+{
+ QDomNode node = element.firstChild();
+
+ QueryGroup *group = new QueryGroup;
+ if (parent)
+ {
+ if (QueryGroup *last = parent->lastChild())
+ last->setNextSibling(group);
+ else
+ parent->setFirstChild(group);
+ }
+ else
+ {
+ mGroupFirst = group;
+ }
+
+ while (!node.isNull())
+ {
+ QDomElement e = node.toElement();
+ if (e.tagName().lower() == "group")
+ {
+ loadGroup(e, group);
+ }
+ else if (e.tagName().lower() == "property")
+ {
+ group->setPropertyName(e.text());
+ }
+ else if (e.tagName().lower() == "value")
+ {
+ group->setValue(QRegExp(e.text()));
+ }
+ else if (e.tagName().lower() == "presentation")
+ {
+ group->setPresentation(e.text());
+ }
+ else if (e.tagName().lower() == "options")
+ {
+ QDomNode node = e.firstChild();
+ while (!node.isNull())
+ {
+ QDomElement e = node.toElement();
+
+ if (e.tagName().lower() == "disabled")
+ group->setOption(QueryGroup::Disabled, true);
+ else if (e.tagName().lower() == "unique") // backwards compat (for now)
+ group->setOption(QueryGroup::Playable, true);
+ else if (e.tagName().lower() == "playable")
+ group->setOption(QueryGroup::Playable, true);
+ else if (e.tagName().lower() == "childrenvisible")
+ group->setOption(QueryGroup::ChildrenVisible, true);
+ else if (e.tagName().lower() == "autoopen")
+ group->setOption(QueryGroup::AutoOpen, true);
+
+ node = node.nextSibling();
+ }
+
+ }
+ node = node.nextSibling();
+ }
+}
+
+void Query::saveGroup(QDomElement &parent, QueryGroup *group)
+{
+ QDomDocument doc = parent.ownerDocument();
+ QDomElement element = doc.createElement("group");
+ parent.appendChild(element);
+
+ QDomElement childe;
+ QDomText childtext;
+ {
+ childe = doc.createElement("property");
+ element.appendChild(childe);
+ childtext = doc.createTextNode(group->propertyName());
+ childe.appendChild(childtext);
+ }
+ {
+ childe = doc.createElement("value");
+ element.appendChild(childe);
+ childtext = doc.createTextNode(group->value().pattern());
+ childe.appendChild(childtext);
+ }
+ {
+ childe = doc.createElement("presentation");
+ element.appendChild(childe);
+ childtext = doc.createTextNode(group->presentation());
+ childe.appendChild(childtext);
+ }
+ {
+ childe = doc.createElement("options");
+ element.appendChild(childe);
+ if (group->option(QueryGroup::Disabled))
+ childe.appendChild(doc.createElement("disabled"));
+ if (group->option(QueryGroup::Playable))
+ childe.appendChild(doc.createElement("playable"));
+ if (group->option(QueryGroup::ChildrenVisible))
+ childe.appendChild(doc.createElement("childrenvisible"));
+ if (group->option(QueryGroup::AutoOpen))
+ childe.appendChild(doc.createElement("autoopen"));
+ }
+
+ for (QueryGroup *c = group->firstChild(); c; c = c->nextSibling())
+ {
+ saveGroup(element, c);
+ }
+}
+
+void Query::deepCopy(const QueryGroup *from, QueryGroup *toParent)
+{
+ if (!from) return;
+ QueryGroup *last=0;
+
+ while (from)
+ {
+ QueryGroup *copy = new QueryGroup(*from);
+ if (last)
+ {
+ last->setNextSibling(copy);
+ last = copy;
+ }
+ else
+ {
+ toParent->setFirstChild(copy);
+ last = copy;
+ }
+ deepCopy(from->firstChild(), last);
+ from = from->nextSibling();
+ }
+}
+
diff --git a/noatun-plugins/oblique/query.h b/noatun-plugins/oblique/query.h
new file mode 100644
index 0000000..60a19ff
--- /dev/null
+++ b/noatun-plugins/oblique/query.h
@@ -0,0 +1,176 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#ifndef QUERY_H
+#define QUERY_H
+
+#include "base.h"
+
+#include <qregexp.h>
+#include <qstring.h>
+
+class Query;
+class QDomElement;
+
+class QueryGroup
+{
+ friend class Query;
+
+ QueryGroup *mFirstChild;
+ QueryGroup *mNextSibling;
+
+ int mFuzzyness;
+ int mOptions;
+
+ QString mPropertyName;
+ QString mPresentation;
+ QRegExp mValue;
+
+public:
+ QueryGroup();
+ QueryGroup(const QueryGroup &copy);
+ QueryGroup &operator =(const QueryGroup &copy);
+
+
+ /**
+ * delete my first child, and my next sibling
+ **/
+ ~QueryGroup();
+ void setFirstChild(QueryGroup *g) { mFirstChild = g; }
+ void setNextSibling(QueryGroup *g) { mNextSibling = g; }
+
+
+ QueryGroup *firstChild() { return mFirstChild; }
+ const QueryGroup *firstChild() const { return mFirstChild; }
+ QueryGroup *lastChild();
+ QueryGroup *nextSibling() { return mNextSibling; }
+ const QueryGroup *nextSibling() const { return mNextSibling; }
+
+ /**
+ * insert @p after as a sibling immediately after this
+ **/
+ void insertAfter(QueryGroup *insert);
+
+ /**
+ * insert @p immediately after this as a child
+ **/
+ void insertUnder(QueryGroup *insert);
+
+ /**
+ * Try get the "best fit" for the two parameters
+ **/
+ void move(Query *query, QueryGroup *under, QueryGroup *after);
+
+ QString propertyName() const { return mPropertyName; }
+ QRegExp value() const { return mValue; }
+ QString presentation() const { return mPresentation; }
+
+ void setPropertyName(const QString &v) { mPropertyName = v; }
+ void setPresentation(const QString &v) { mPresentation = v; }
+ void setValue(const QRegExp &v) { mValue = v; }
+
+ enum Fuzzyness
+ {
+ Case = 1<<0, Spaces = 1<<1, Articles = 1<<2, Symbols = 1<<3
+ };
+
+ bool fuzzyness(Fuzzyness f) const;
+
+ enum Option
+ {
+ AutoHide = 1<<0, Disabled = 1<<1, Playable = 1<<2,
+ ChildrenVisible = 1<<3, AutoOpen = 1<<4
+ };
+
+ bool option(Option option) const;
+ void setOption(Option option, bool on);
+
+ /**
+ * @return if I match @p file
+ **/
+ bool matches(const File &file) const;
+
+ QString presentation(const File &file) const;
+
+private:
+ /**
+ * apply all the "normalizing" transformations according
+ * to the fuzzyness
+ **/
+ QString fuzzify(const QString &str) const;
+ /**
+ * @returns the previous or parent of this item (slow)
+ **/
+ QueryGroup *previous(Query *query);
+ QueryGroup *previous(QueryGroup *startWith);
+
+};
+
+
+
+
+/**
+ * a query is the tree structure that is shown to the user
+ **/
+class Query
+{
+ QueryGroup *mGroupFirst;
+ QString mName;
+
+public:
+ Query();
+ Query(const Query &copy);
+ ~Query();
+
+ Query &operator =(const Query &copy);
+
+ QueryGroup *firstChild();
+ const QueryGroup *firstChild() const;
+
+ void setFirstChild(QueryGroup *g);
+ void insertFirst(QueryGroup *g);
+
+ void clear();
+
+ /**
+ * @returns the name to be used internally
+ **/
+ QString name() const { return mName; }
+ void setName(const QString &name) { mName = name; }
+
+ /**
+ * @returns the name of the query
+ **/
+ QString load(const QString &filename);
+ void save(const QString &name, QDomElement &element);
+ void save(const QString &name, const QString &filename);
+
+ /**
+ * remove any trace of this from the tree, but don't actually delete it
+ **/
+ void take(QueryGroup *item);
+
+ void dump();
+
+ /**
+ * @returns the name of this query as used internally by the db.
+ *
+ * Will give it a name in the db if necessary
+ **/
+ QString dbname(Base *base);
+
+private:
+ void loadGroup(QDomElement element, QueryGroup *parent=0);
+ void saveGroup(QDomElement &parent, QueryGroup *group);
+
+ void deepCopy(const QueryGroup *from, QueryGroup *toParent);
+
+ /**
+ * @returns the name of the query
+ **/
+ QString load(QDomElement element);
+};
+
+
+
+#endif
diff --git a/noatun-plugins/oblique/schemas/Makefile.am b/noatun-plugins/oblique/schemas/Makefile.am
new file mode 100644
index 0000000..9bda17b
--- /dev/null
+++ b/noatun-plugins/oblique/schemas/Makefile.am
@@ -0,0 +1,3 @@
+schemasdir = $(kde_datadir)/noatun/oblique/schemas
+schemas_DATA = standard
+
diff --git a/noatun-plugins/oblique/schemas/obliqueschema.dtd b/noatun-plugins/oblique/schemas/obliqueschema.dtd
new file mode 100644
index 0000000..5fb45ea
--- /dev/null
+++ b/noatun-plugins/oblique/schemas/obliqueschema.dtd
@@ -0,0 +1,14 @@
+<!ELEMENT ObliqueSchema (group+)>
+<!ELEMENT group (property,value,presentation,options?,group*)>
+<!ELEMENT options (disabled|stoponhit|unique)*>
+<!ATTLIST ObliqueSchema version CDATA "1.0">
+
+<!ELEMENT disabled EMPTY>
+<!ELEMENT stoponhit EMPTY>
+<!ELEMENT unique EMPTY>
+
+
+<!ELEMENT property (#PCDATA)>
+<!ELEMENT value (#PCDATA)>
+<!ELEMENT presentation (#PCDATA)>
+
diff --git a/noatun-plugins/oblique/schemas/standard b/noatun-plugins/oblique/schemas/standard
new file mode 100644
index 0000000..1c00f18
--- /dev/null
+++ b/noatun-plugins/oblique/schemas/standard
@@ -0,0 +1,80 @@
+<!DOCTYPE ObliqueSchema SYSTEM "obliqueschema.dtd">
+<ObliqueSchema title="Standard" i18n="true">
+ <group>
+ <property>mimetype</property>
+ <value>^(?:audio/|.*/ogg$)</value>
+ <presentation>Music</presentation>
+ <options>
+ <autoopen/>
+ </options>
+ <group>
+ <property>author</property>
+ <value>^$</value>
+ <presentation>(Unknown Artist)</presentation>
+
+ <group>
+ <property>title</property>
+ <value>^$</value>
+ <presentation>$(track". ")$(file)$(" ("bitrate"kbps)")</presentation>
+ <options>
+ <playable />
+ </options>
+ </group>
+ </group>
+
+ <group>
+ <property>author</property>
+ <value></value>
+ <presentation>$(author)</presentation>
+
+ <group>
+ <property>album</property>
+ <value>^$</value>
+ <presentation>(Unknown Album)</presentation>
+
+ <group>
+ <property>title</property>
+ <value>^$</value>
+ <presentation>$(track". ")$(file)$(" ("bitrate"kbps)")</presentation>
+ <options>
+ <playable />
+ </options>
+ </group>
+
+ <group>
+ <property>title</property>
+ <value></value>
+ <presentation>$(track". ")$(title)$(" ("bitrate"kbps)")</presentation>
+ <options>
+ <playable />
+ </options>
+ </group>
+ </group>
+
+ <group>
+ <property>album</property>
+ <value></value>
+ <presentation>$(album)</presentation>
+
+ <group>
+ <property>title</property>
+ <value>^$</value>
+ <presentation>$(track". ")$(file)$(" ("bitrate"kbps)")</presentation>
+ <options>
+ <playable />
+ </options>
+ </group>
+
+ <group>
+ <property>title</property>
+ <value></value>
+ <presentation>$(track". ")$(title)$(" ("bitrate"kbps)")</presentation>
+ <options>
+ <playable />
+ </options>
+ </group>
+ </group>
+ </group>
+ </group>
+</ObliqueSchema>
+
diff --git a/noatun-plugins/oblique/selector.cpp b/noatun-plugins/oblique/selector.cpp
new file mode 100644
index 0000000..1c17650
--- /dev/null
+++ b/noatun-plugins/oblique/selector.cpp
@@ -0,0 +1,226 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#include "tree.h"
+#include "selector.h"
+#include "oblique.h"
+
+#include <noatun/app.h>
+#include <noatun/player.h>
+
+Item::Item(const File &file)
+ : mFile(file)
+{
+
+}
+
+QString Item::property(const QString &key, const QString &def) const
+{
+ if (key == "url")
+ {
+ KURL url;
+ url.setPath(property("file"));
+ return url.url();
+ }
+
+ QString str = mFile.property(key);
+ if (str.isNull()) return def;
+ return str;
+}
+
+void Item::setProperty(const QString &key, const QString &property)
+{
+ mFile.setProperty(key, property);
+}
+
+void Item::clearProperty(const QString &key)
+{
+ mFile.clearProperty(key);
+}
+
+QStringList Item::properties() const
+{
+ return mFile.properties();
+}
+
+bool Item::isProperty(const QString &key) const
+{
+ return !mFile.property(key).isNull();
+}
+
+bool Item::operator==(const PlaylistItemData &d) const
+{
+ return mFile == static_cast<const Item&>(d).mFile;
+}
+
+void Item::remove()
+{
+ mFile.remove();
+}
+
+
+
+Selector::Selector()
+{
+}
+
+Selector::~Selector()
+{
+}
+
+
+SequentialSelector::SequentialSelector(Tree *tree)
+{
+ mTree = tree;
+}
+
+SequentialSelector::~SequentialSelector()
+{
+
+}
+
+Item *SequentialSelector::next()
+{
+ TreeItem *current = mTree->current();
+ if (current)
+ {
+ current = current->nextPlayable();
+ }
+ else
+ {
+ current = mTree->firstChild();
+ if (current && !current->playable())
+ {
+ current = current->nextPlayable();
+ }
+ }
+ setCurrent(current);
+ if (current && current->file())
+ return new Item(current->file());
+ return 0;
+}
+
+Item *SequentialSelector::previous()
+{
+ TreeItem *back = mTree->firstChild();
+ TreeItem *after;
+ TreeItem *current = mTree->current();
+ // now we just go forward on back until the item after back is me ;)
+ // this is terribly unoptimized, but I'm terribly lazy
+ while (back and (after = back->nextPlayable()) != current)
+ {
+ back = after;
+ }
+ current = back;
+ setCurrent(current);
+ if (current && current->file())
+ return new Item(current->file());
+ return 0;
+}
+
+Item *SequentialSelector::current()
+{
+ TreeItem *current = mTree->current();
+ if (!current) return next();
+ if (current->file())
+ return new Item(current->file());
+ return 0;
+}
+
+void SequentialSelector::setCurrent(const Item &item)
+{
+ TreeItem *current = mTree->find(item.itemFile());
+ setCurrent(current);
+}
+
+void SequentialSelector::setCurrent(TreeItem *current)
+{
+ if (current)
+ {
+ mTree->setCurrent(current);
+ }
+}
+
+
+
+RandomSelector::RandomSelector(Tree *tree)
+{
+ mTree = tree;
+ mPrevious = 0;
+}
+
+static TreeItem *randomItem(int &at, TreeItem *item)
+{
+ for ( ; item; item = item->nextSibling())
+ {
+ if (item->playable())
+ {
+ if (at==0)
+ return item;
+
+ at--;
+ }
+ if (TreeItem *i = randomItem(at, item->firstChild()))
+ {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+Item *RandomSelector::next()
+{
+ TreeItem *previous = mTree->current();
+ if (!mTree->playableItemCount())
+ {
+ return 0;
+ }
+
+ for (int tries=15; tries; tries--)
+ {
+ int randomIndex = KApplication::random() % (mTree->playableItemCount());
+
+ TreeItem *nowCurrent = randomItem(randomIndex, mTree->firstChild());
+ if (!nowCurrent) continue;
+
+ setCurrent(nowCurrent, previous);
+ return new Item(nowCurrent->file());
+ }
+
+ // !!!!
+ return 0;
+}
+
+Item *RandomSelector::previous()
+{
+ if (!mPrevious) return 0;
+ TreeItem *current = mPrevious;
+
+ mTree->setCurrent(current);
+ return new Item(current->file());
+}
+
+Item *RandomSelector::current()
+{
+ TreeItem *current = mTree->current();
+ if (!current) return 0;
+ return new Item(current->file());
+}
+
+void RandomSelector::setCurrent(const Item &item)
+{
+ setCurrent(mTree->find(item.itemFile()), 0);
+}
+
+void RandomSelector::setCurrent(TreeItem *item, TreeItem *previous)
+{
+ mPrevious = previous;
+ mTree->setCurrent(item);
+
+ napp->player()->stop();
+ napp->player()->play();
+}
+
+
+
diff --git a/noatun-plugins/oblique/selector.h b/noatun-plugins/oblique/selector.h
new file mode 100644
index 0000000..1447dec
--- /dev/null
+++ b/noatun-plugins/oblique/selector.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#ifndef SELECTOR_H
+#define SELECTOR_H
+
+#include <noatun/playlist.h>
+#include "file.h"
+
+class Item : public PlaylistItemData
+{
+ File mFile;
+
+public:
+ Item(const File &file);
+
+ File itemFile() const { return mFile; }
+
+ virtual QString property(const QString &key, const QString &def=0) const;
+ virtual void setProperty(const QString &key, const QString &property);
+ virtual void clearProperty (const QString &key);
+ virtual QStringList properties() const;
+
+ virtual bool isProperty(const QString &key) const;
+
+ virtual bool operator==(const PlaylistItemData &d) const;
+ virtual void remove();
+};
+
+/**
+ * a selector is an object that can get items from the
+ * playlist in a certain order
+ **/
+class Selector
+{
+public:
+ Selector();
+ virtual ~Selector();
+ virtual Item *next()=0;
+ virtual Item *previous()=0;
+ virtual Item *current()=0;
+ virtual void setCurrent(const Item &item)=0;
+};
+
+class Tree;
+class TreeItem;
+
+class SequentialSelector : public Selector
+{
+ Tree *mTree;
+
+public:
+ SequentialSelector(Tree *tree);
+ virtual ~SequentialSelector();
+ virtual Item *next();
+ virtual Item *previous();
+ virtual Item *current();
+ virtual void setCurrent(const Item &item);
+ virtual void setCurrent(TreeItem *current);
+};
+
+class RandomSelector : public Selector
+{
+ Tree *mTree;
+ TreeItem *mPrevious;
+public:
+ RandomSelector(Tree *tree);
+
+ virtual Item *next();
+ virtual Item *previous();
+ virtual Item *current();
+ virtual void setCurrent(const Item &item);
+ virtual void setCurrent(TreeItem *item, TreeItem *previous);
+};
+
+#endif
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"
+
diff --git a/noatun-plugins/oblique/tree.h b/noatun-plugins/oblique/tree.h
new file mode 100644
index 0000000..0d53732
--- /dev/null
+++ b/noatun-plugins/oblique/tree.h
@@ -0,0 +1,194 @@
+// Copyright (c) 2003 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#ifndef TREE_H
+#define TREE_H
+
+#include <qwidget.h>
+#include <klistview.h>
+
+#include "base.h"
+#include "query.h"
+#include "file.h"
+
+class Oblique;
+class Tree;
+
+class TreeItem : public KListViewItem
+{
+ QueryGroup *mGroup;
+ File mFile;
+
+ bool mUserOpened:1;
+ bool mHidden:1;
+
+public:
+ TreeItem(Tree *parent, QueryGroup *group, const File &file, const QString &p=0);
+ TreeItem(TreeItem *parent, QueryGroup *group, const File &file, const QString &p=0);
+ ~TreeItem();
+
+ QueryGroup *group() { return mGroup; }
+ const QueryGroup *group() const { return mGroup; }
+ void setGroup(QueryGroup *group) { mGroup = group; }
+
+ TreeItem *parent() { return static_cast<TreeItem*>(KListViewItem::parent()); }
+ Tree *tree();
+ TreeItem *itemBelow() { return static_cast<TreeItem*>(KListViewItem::itemBelow()); }
+ TreeItem *firstChild() { return static_cast<TreeItem*>(KListViewItem::firstChild()); }
+ TreeItem *nextSibling() { return static_cast<TreeItem*>(KListViewItem::nextSibling()); }
+
+ // for gdb, which sucks.
+ QString presentation() const;
+
+ File file() { return mFile; }
+ void setFile(File file) { mFile = file; }
+
+ void setOpen(bool o);
+
+ TreeItem *find(File item);
+
+ bool playable() const;
+
+ /**
+ * get the next item that is playable logically.
+ * that is, if it has a File, and its parent hasn't a
+ * File
+ **/
+ TreeItem *nextPlayable();
+
+ void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int align);
+
+ virtual int compare(QListViewItem * i, int col, bool) const;
+
+ /**
+ * open my parents so that I'm visible, if I'm playing or
+ * close my parents if I'm not playing, and the user
+ * didn't open
+ **/
+ void autoExpand();
+ bool userOpened() const { return mUserOpened; }
+ bool hideIfNoMatch(const QString &match);
+
+ void setHidden(bool on);
+
+ virtual void setup();
+
+private:
+ TreeItem *next();
+
+ void forceAutoExpand();
+
+};
+
+class FileMenu;
+class Loader;
+
+class Tree : public KListView
+{
+Q_OBJECT
+ Oblique *mOblique;
+
+ Query mQuery;
+ TreeItem *mCurrent;
+ FileMenu *lastMenu;
+ Slice *mSlice;
+ QString mFileOfQuery;
+
+ friend class TreeItem;
+ int mPlayableItemCount; // used by the friendship
+
+ QPtrList<TreeItem> mAutoExpanded;
+ unsigned int mAutoExpanding;
+
+ Loader *mLoader;
+
+public:
+ Tree(Oblique *oblique, QWidget *parent=0);
+ ~Tree();
+ TreeItem *firstChild();
+ TreeItem *find(File item);
+ TreeItem *current() { return mCurrent; }
+ Query *query() { return &mQuery; }
+ Oblique *oblique() { return mOblique; }
+ Slice *slice() { return mSlice; }
+ QString fileOfQuery() const { return mFileOfQuery; }
+
+ void clear();
+
+ int playableItemCount() const { return mPlayableItemCount; }
+
+ void addAutoExpanded(TreeItem *i) { mAutoExpanded.append(i); }
+ void removeAutoExpanded(TreeItem *i) { mAutoExpanded.removeRef(i); }
+ void resetAutoExpanded() { mAutoExpanded.clear(); }
+
+ void setAutoExpanding(bool e) { mAutoExpanding += e ? 1 : -1; }
+ bool autoExpanding() const { return mAutoExpanding; }
+
+ void deleted(TreeItem *item);
+ bool setSchema(const QString &name);
+
+protected:
+ virtual QDragObject *dragObject();
+ void movableDropEvent(QListViewItem* parent, QListViewItem* afterme);
+
+public slots:
+ void insert(TreeItem *replace, File file);
+ void insert(File file);
+ void remove(File file);
+ void update(File file);
+ void setCurrent(TreeItem *cur);
+ void setSlice(Slice *sl);
+
+ void checkInsert(Slice*, File);
+ void checkRemove(Slice*, File);
+
+ /**
+ * the resulting presentation of this item must contain the string @p text
+ * or it will not be displayed
+ * (used for Jump)
+ **/
+ void setLimit(const QString &text);
+
+private slots:
+ void contextMenu(KListView* l, QListViewItem* i, const QPoint& p);
+ void play(QListViewItem *item);
+
+ void destroyLoader();
+
+ void dropped(QPtrList<QListViewItem> &items, QPtrList<QListViewItem> &, QPtrList<QListViewItem> &afterNow);
+
+signals:
+ void selected(TreeItem *);
+
+private:
+ /**
+ * check if it fits into the group, and create
+ * the tree nodes for it
+ **/
+ TreeItem *collate(TreeItem *fix, QueryGroup *group, const File &file, TreeItem *childOf=0);
+ TreeItem *collate(const File &file, TreeItem *childOf=0)
+ {
+ if (!mQuery.firstChild()) return 0;
+ return collate(0, mQuery.firstChild(), file, childOf);
+ }
+
+ TreeItem *collate(TreeItem *fix, const File &file, TreeItem *childOf=0)
+ {
+ if (!mQuery.firstChild()) return 0;
+ return collate(fix, mQuery.firstChild(), file, childOf);
+ }
+
+ TreeItem *node(TreeItem *fix, QueryGroup *group, const File &file, TreeItem *childOf);
+
+ /**
+ * remove the siblings and children of the treeitem
+ **/
+ void remove(TreeItem *, const File &file);
+
+ void limitHide(TreeItem *i, const QString &text);
+
+ void reload();
+};
+
+
+#endif
diff --git a/noatun-plugins/oblique/view.cpp b/noatun-plugins/oblique/view.cpp
new file mode 100644
index 0000000..93b79ce
--- /dev/null
+++ b/noatun-plugins/oblique/view.cpp
@@ -0,0 +1,251 @@
+// Copyright (c) 2003-2005 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#include "view.h"
+#include "oblique.h"
+#include "menu.h"
+
+#include <noatun/app.h>
+
+#include <kstdaction.h>
+#include <klocale.h>
+#include <kedittoolbar.h>
+#include <kfiledialog.h>
+#include <qlabel.h>
+#include <klineeditdlg.h>
+#include <kmessagebox.h>
+#include <ktabwidget.h>
+#include <qtabbar.h>
+
+class TabWidget : public KTabWidget
+{
+public:
+ TabWidget(QWidget *parent)
+ : KTabWidget(parent)
+ {
+ }
+public:
+ QTabBar *tabBar() const { return KTabWidget::tabBar(); }
+};
+
+
+View::View(Oblique *oblique)
+ : KMainWindow(0, 0)
+{
+ mOblique = oblique;
+ mTree = 0;
+
+ mTabs = new TabWidget(this);
+ mTabs->tabBar()->hide();
+ connect(mTabs, SIGNAL(currentChanged(QWidget*)), SLOT(currentTabChanged(QWidget*)));
+
+ setCentralWidget(mTabs);
+
+ KAction *ac;
+ ac = new KAction(i18n("Add &Files..."), "queue", 0, this, SLOT(addFiles()), actionCollection(), "add_files");
+ ac->setWhatsThis(i18n("Add a reference to a media file on disk to this collection."));
+ ac = new KAction(i18n("Add Fol&ders..."), "folder", 0, this, SLOT(addDirectory()), actionCollection(), "add_dir");
+
+
+// ac = new KAction(i18n("&Reload"), "reload", 0, oblique, SLOT(reload()), actionCollection(), "reload");
+// ac->setWhatsThis(i18n("Reread the collection and meta-information from its files."));
+
+ ac = new SliceListAction(
+ i18n("&Slices"), oblique,
+ this, SLOT(use(Slice*)), QValueList<File>(), actionCollection(), "slices"
+ );
+ ac->setWhatsThis(i18n("Select a sub-collection to display"));
+
+ mSchemaListAction = new SchemaListAction(
+ i18n("&Schemas"), this, SLOT(setSchema(const QString&)), actionCollection(), "schemas"
+ );
+ mSchemaListAction->setWhatsThis(i18n("Select a schema to use to collate the tree."));
+
+ ac = new KAction(
+ i18n("&New Tab"), "tab_new", "CTRL+SHIFT+N;CTRL+T", this, SLOT(addTab()),
+ actionCollection(), "newtab"
+ );
+
+ mRemoveTabAction = new KAction(
+ i18n("&Close Current Tab"), "tab_remove", CTRL+Key_W, this, SLOT(removeTab()),
+ actionCollection(), "removecurrenttab"
+ );
+
+ {
+ QLabel *l = new QLabel(i18n("&Jump:"), 0, "kde toolbar widget");
+ l->setBackgroundMode( Qt::PaletteButton );
+ l->setAlignment(
+ (QApplication::reverseLayout() ? Qt::AlignRight : Qt::AlignLeft) |
+ Qt::AlignVCenter | Qt::ShowPrefix
+ );
+ l->adjustSize();
+ new KWidgetAction(l, i18n("&Jump:"), KShortcut(ALT + Key_J), 0, 0, actionCollection(), "jump_label");
+
+ LineEditAction *jumpAction = new LineEditAction(i18n("Jump Bar"), 0, 0, actionCollection(), "jump_text");
+ jumpAction->setWhatsThis(i18n("Only display items which contain this string"));
+ l->setBuddy(jumpAction->lineEdit());
+ connect(jumpAction->lineEdit(), SIGNAL(textChanged(const QString&)), SLOT(jumpTextChanged(const QString&)));
+ }
+
+ KStdAction::configureToolbars(this, SLOT(configureToolBars()), actionCollection());
+
+ applyMainWindowSettings(KGlobal::config(), "Oblique View");
+ createGUI("obliqueui.rc");
+
+ KConfigGroup g(KGlobal::config(), "oblique");
+ QStringList tabids = g.readListEntry("tabids");
+
+ for (QStringList::Iterator i(tabids.begin()); i != tabids.end(); ++i)
+ {
+ QString t = *i;
+ int sliceid = t.section(':', 0, 0).toInt();
+ QString fileOfQuery = t.section(':', 1, 1);
+
+ Slice *slice = oblique->base()->sliceById(sliceid);
+ if (!slice)
+ slice = oblique->base()->defaultSlice();
+ Tree *tree = new Tree(oblique, mTabs);
+ mTrees.append(tree);
+ tree->setSlice(slice);
+ tree->setSchema(fileOfQuery);
+ mTabs->addTab(tree, slice->name());
+ }
+
+ if (mTabs->count() == 0)
+ { // no tabs, so use the "normal" route
+ addTab();
+ }
+ else
+ {
+ // ok, there's a tab, so make it present
+ if (mTabs->count() >= 1)
+ {
+ mTree = static_cast<Tree*>(mTrees.first());
+ currentTabChanged(mTrees.first());
+ }
+
+ if (mTabs->count() > 1)
+ mTabs->tabBar()->show();
+ }
+}
+
+View::~View()
+{
+ QStringList tabids;
+ for (int i=0; i < mTabs->count(); i++)
+ {
+ Tree *tree = static_cast<Tree*>(mTabs->page(i));
+ int slice = tree->slice()->id();
+ QString query = tree->fileOfQuery();
+
+ QString t = QString("%1:%2").arg(slice).arg(query);
+ tabids.append(t);
+ }
+
+ KConfigGroup g(KGlobal::config(), "oblique");
+ g.writeEntry("tabids", tabids);
+ g.sync();
+}
+
+void View::configureToolBars()
+{
+ saveMainWindowSettings(KGlobal::config(), "Oblique View");
+ KEditToolbar dlg(actionCollection(), "obliqueui.rc");
+ connect(&dlg, SIGNAL(newToolbarConfig()), SLOT(newToolBarConfig()));
+ dlg.exec();
+}
+
+void View::newToolBarConfig()
+{
+ createGUI("obliqueui.rc");
+ applyMainWindowSettings(KGlobal::config(), "Oblique View");
+}
+
+void View::closeEvent(QCloseEvent*)
+{
+ hide();
+}
+
+
+void View::addFiles()
+{
+ KURL::List files=KFileDialog::getOpenURLs(":mediadir", napp->mimeTypes(), this, i18n("Select Files to Add"));
+
+ for(KURL::List::Iterator it=files.begin(); it!=files.end(); ++it)
+ oblique()->addFile(KURL(*it));
+}
+
+void View::addDirectory()
+{
+ QString folder = KFileDialog::getExistingDirectory(":mediadir:", this,
+ i18n("Select Folder to Add"));
+
+ if (!folder)
+ return;
+
+ KURL url;
+ url.setPath(folder);
+ oblique()->beginDirectoryAdd(url);
+}
+
+void View::addTab()
+{
+ Tree *t = new Tree(oblique(), mTabs);
+ if (!mTree) mTree = t;
+ mTrees.append(t);
+
+ mTabs->addTab(t, t->slice()->name());
+ mTabs->showPage(t);
+ if (mTabs->count() > 1)
+ mTabs->tabBar()->show();
+ currentTabChanged(t);
+}
+
+void View::removeTab()
+{
+ Tree *current = static_cast<Tree*>(mTabs->currentPage());
+ if (current == mTree) return;
+ mTabs->removePage(current);
+ mTrees.remove(current);
+ delete current;
+
+ if (mTabs->count() == 1)
+ mTabs->tabBar()->hide();
+}
+
+void View::currentTabChanged(QWidget *w)
+{
+ mRemoveTabAction->setEnabled(w != mTree);
+ mSchemaListAction->setTree(static_cast<Tree*>(w));
+}
+
+void View::setSchema(const QString &file)
+{
+ Tree *current = static_cast<Tree*>(mTabs->currentPage());
+ current->setSchema(file);
+}
+
+
+void View::jumpTextChanged(const QString &text)
+{
+ Tree *current = static_cast<Tree*>(mTabs->currentPage());
+ current->setLimit(text);
+}
+
+void View::use(Slice *s)
+{
+ Tree *current = static_cast<Tree*>(mTabs->currentPage());
+ current->setSlice(s);
+ mTabs->setTabLabel(current, current->slice()->name());
+}
+
+
+
+LineEditAction::LineEditAction(const QString &text, const QObject *reciever, const char *slot, KActionCollection *parent, const char *name)
+ : KWidgetAction(new KLineEdit(0), text, KShortcut(0), reciever, slot, parent, name)
+{
+ setAutoSized(true);
+}
+
+#include "view.moc"
+
diff --git a/noatun-plugins/oblique/view.h b/noatun-plugins/oblique/view.h
new file mode 100644
index 0000000..598c0cc
--- /dev/null
+++ b/noatun-plugins/oblique/view.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2003,2004 Charles Samuels <charles@kde.org>
+// See the file COPYING for redistribution terms.
+
+#ifndef VIEW_H
+#define VIEW_H
+
+#include <kmainwindow.h>
+#include <kaction.h>
+#include <klineedit.h>
+
+#include "tree.h"
+
+class SchemaListAction;
+class TabWidget;
+
+class View : public KMainWindow
+{
+Q_OBJECT
+ Oblique *mOblique;
+ Tree *mTree;
+ QValueList<Tree*> mTrees;
+ TabWidget *mTabs;
+ KAction *mRemoveTabAction;
+ SchemaListAction *mSchemaListAction;
+
+public:
+ View(Oblique *oblique);
+ ~View();
+
+ Tree *tree() { return mTree; }
+
+ Oblique *oblique() { return mOblique; }
+
+public slots:
+ void addFiles();
+ void addDirectory();
+ void addTab();
+ void removeTab();
+
+signals:
+ void listHidden();
+ void listShown();
+
+private slots:
+ void configureToolBars();
+ void newToolBarConfig();
+ void jumpTextChanged(const QString &text);
+ void use(Slice*);
+ void currentTabChanged(QWidget *);
+ void setSchema(const QString &file);
+
+protected:
+ virtual void showEvent(QShowEvent *) { emit listShown(); }
+ virtual void hideEvent(QHideEvent *) { emit listHidden(); }
+ void closeEvent(QCloseEvent*);
+};
+
+
+class LineEditAction : public KWidgetAction
+{
+Q_OBJECT
+public:
+ LineEditAction(const QString &text, const QObject *reciever, const char *slot, KActionCollection *parent, const char *name);
+
+ KLineEdit *widget() { return static_cast<KLineEdit*>(KWidgetAction::widget()); }
+ KLineEdit *lineEdit() { return widget(); }
+};
+
+
+#endif
+