diff --git a/src/eventmanager.h b/src/eventmanager.h new file mode 100644 index 000000000..e85419c29 --- /dev/null +++ b/src/eventmanager.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007 by Ishan Arora + * ishanarora@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef EVENTMANAGER_H +#define EVENTMANAGER_H + +#include "qtorrenthandle.h" +#include +#include +#include + +class EventManager : public QObject +{ + Q_OBJECT + private: + ulong revision; + QLinkedList > events; + bool modify(QString hash, QString key, QVariant value); + + protected: + void update(QVariantMap event); + + public: + EventManager(QObject *parent = 0); + QVariant querySince(ulong r) const; + bool isUpdated(ulong r) const; + + signals: + void updated(); + + public slots: + void addedTorrent(QString path, QTorrentHandle& h); + void deletedTorrent(QString hash); + void modifiedTorrent(QTorrentHandle h); +}; + +#endif diff --git a/src/httpconnection.cpp b/src/httpconnection.cpp new file mode 100644 index 000000000..77428ce25 --- /dev/null +++ b/src/httpconnection.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2007 by Ishan Arora + * ishanarora@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "httpconnection.h" +#include "httpserver.h" +#include "eventmanager.h" +#include "json.h" +#include +#include +#include +#include +#include +#include +#include + +HttpConnection::HttpConnection(QTcpSocket *socket, HttpServer *parent) + : QObject(parent), socket(socket), parent(parent) +{ + socket->setParent(this); + connect(socket, SIGNAL(readyRead()), this, SLOT(read())); + connect(socket, SIGNAL(disconnected()), this, SLOT(deleteLater())); +} + +HttpConnection::~HttpConnection() +{ +} + +void HttpConnection::read() +{ + QString input = socket->readAll(); + qDebug(" -------"); + qDebug("|REQUEST|"); + qDebug(" -------"); + qDebug(input.toAscii().constData()); + parser.write(input); + if(parser.isError()) + { + generator.setStatusLine(400, "Bad Request"); + write(); + } + else + if (parser.isParsable()) + respond(); +} + +void HttpConnection::write() +{ + QByteArray output = generator.toByteArray(); + qDebug(" --------"); + qDebug("|RESPONSE|"); + qDebug(" --------"); + qDebug()<write(output); + socket->disconnectFromHost(); +} + +void HttpConnection::respond() +{ + QStringList auth = parser.value("Authorization").split(" ", QString::SkipEmptyParts); + if (auth.size() != 2 || QString::compare(auth[0], "Basic", Qt::CaseInsensitive) != 0 || !parent->isAuthorized(auth[1].toUtf8())) + { + generator.setStatusLine(401, "Unauthorized"); + generator.setValue("WWW-Authenticate", "Basic realm=\"you know what\""); + write(); + return; + } + QString url = parser.url(); + QStringList list = url.split('/', QString::SkipEmptyParts); + if (list.contains(".") || list.contains("..")) + { + respondNotFound(); + return; + } + if (list.size() == 0) + list.append("index.html"); + if (list.size() == 2) + { + if (list[0] == "json") + { + if (list[1] == "events") + { + EventManager* manager = parent->eventManager(); + uint r = parser.get("r").toUInt(); + if(manager->isUpdated(r)) + respondJson(); + else + connect(manager, SIGNAL(updated()), this, SLOT(respondJson())); + return; + } + } + if (list[0] == "command") + { + QString command = list[1]; + respondCommand(command); + generator.setStatusLine(200, "OK"); + write(); + return; + } + } + if (list[0] == "images") + list[0] = "Icons"; + else + list.prepend("webui"); + url = ":/" + list.join("/"); + QFile file(url); + if(!file.open(QIODevice::ReadOnly)) + { + respondNotFound(); + return; + } + QString ext = list.last(); + int index = ext.lastIndexOf('.') + 1; + if (index > 0) + ext.remove(0, index); + else + ext.clear(); + QByteArray data = file.readAll(); + generator.setStatusLine(200, "OK"); + generator.setContentTypeByExt(ext); + generator.setMessage(data); + write(); +} + +void HttpConnection::respondNotFound() +{ + generator.setStatusLine(404, "File not found"); + write(); +} + +void HttpConnection::respondJson() +{ + EventManager* manager = parent->eventManager(); + QString temp = parser.get("r"); + uint r = parser.get("r").toUInt(); + QVariant data = manager->querySince(r); + QString string = toJson(data); + generator.setStatusLine(200, "OK"); + generator.setContentTypeByExt("js"); + generator.setMessage(string); + write(); +} + +void HttpConnection::respondCommand(QString command) +{ + if(command == "download") + { + QString urls = parser.post("urls"); + QStringList list = urls.split('\n'); + QStringList url_list_cleaned; + foreach(QString url, list){ + url = url.trimmed(); + if(!url.isEmpty()){ + if(url_list_cleaned.indexOf(QRegExp(url, Qt::CaseInsensitive, QRegExp::FixedString)) < 0){ + url_list_cleaned << url; + } + } + } + emit urlsReadyToBeDownloaded(url_list_cleaned); + return; + } + if(command == "resumeall") + { + emit resumeAllTorrents(); + return; + } + if(command == "pauseall") + { + emit pauseAllTorrents(); + return; + } + if(command == "resume") + { + emit resumeTorrent(parser.post("hash")); + return; + } + if(command == "pause") + { + emit pauseTorrent(parser.post("hash")); + return; + } + if(command == "delete") + { + emit deleteTorrent(parser.post("hash")); + return; + } +} diff --git a/src/httprequestparser.cpp b/src/httprequestparser.cpp new file mode 100644 index 000000000..069836f63 --- /dev/null +++ b/src/httprequestparser.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2007 by Ishan Arora + * ishanarora@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "httprequestparser.h" +#include +#include + +HttpRequestParser::HttpRequestParser() +{ + headerDone = false; + messageDone = false; + error = false; +} + +HttpRequestParser::~HttpRequestParser() +{ +} + +bool HttpRequestParser::isParsable() const +{ + return !error && headerDone && isValid() && (messageDone || !hasContentLength() || contentLength() == 0); +} + +bool HttpRequestParser::isError() const +{ + return error; +} + +QString HttpRequestParser::url() const +{ + return path; +} + +QString HttpRequestParser::message() const +{ + if(isParsable()) + return data; + return QString(); +} + +QString HttpRequestParser::get(const QString key) const +{ + return getMap[key]; +} + +QString HttpRequestParser::post(const QString key) const +{ + return postMap[key]; +} + +void HttpRequestParser::write(QString str) +{ + while (!headerDone && str.size()>0) + { + int index = str.indexOf('\n') + 1; + if(index == 0) + { + data += str; + str.clear(); + } + else + { + data += str.left(index); + str.remove(0, index); + if(data.right(4) == "\r\n\r\n") + { + QHttpRequestHeader::operator=(QHttpRequestHeader(data)); + headerDone = true; + data.clear(); + QUrl url = QUrl::fromEncoded(QHttpRequestHeader::path().toAscii()); + path = url.path(); + qDebug() << path; + QListIterator > i(url.queryItems()); + while (i.hasNext()) + { + QPair pair = i.next(); + getMap[pair.first] = pair.second; + qDebug() << pair.first << "=" << get(pair.first); + } + } + } + } + if(!messageDone && str.size()>0) + { + if(hasContentLength()) + { + data += str; + if(data.size() >= (int) contentLength()) + { + data.resize(contentLength()); + messageDone = true; + //parse POST data + if(contentType() == "application/x-www-form-urlencoded") + { + QUrl url; + url.setEncodedQuery(data.toAscii()); + QListIterator > i(url.queryItems()); + while (i.hasNext()) + { + QPair pair = i.next(); + postMap[pair.first] = pair.second; + qDebug() << pair.first << "=" << post(pair.first); + } + } + } + } + else + error = true; + } +} diff --git a/src/httpresponsegenerator.cpp b/src/httpresponsegenerator.cpp new file mode 100644 index 000000000..f139f1bc7 --- /dev/null +++ b/src/httpresponsegenerator.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 by Ishan Arora + * ishanarora@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "httpresponsegenerator.h" +#include + +void HttpResponseGenerator::setMessage(const QByteArray message) +{ + HttpResponseGenerator::message = message; + setContentLength(message.size()); +} + +void HttpResponseGenerator::setMessage(const QString message) +{ + setMessage(message.QString::toUtf8()); +} + +void HttpResponseGenerator::stripMessage() +{ + message.clear(); +} + +QByteArray HttpResponseGenerator::toByteArray() const +{ + return QHttpResponseHeader::toString().toUtf8() + message; +} + +void HttpResponseGenerator::setContentTypeByExt(const QString ext) +{ + if(ext == "css") + { + setContentType("text/css"); + return; + } + if(ext == "gif") + { + setContentType("image/gif"); + return; + } + if(ext == "htm" || ext == "html") + { + setContentType("text/html"); + return; + } + if(ext == "js") + { + setContentType("text/javascript"); + return; + } + if(ext == "png") + { + setContentType("image/x-png"); + return; + } +} diff --git a/src/httpresponsegenerator.h b/src/httpresponsegenerator.h new file mode 100644 index 000000000..43628a56f --- /dev/null +++ b/src/httpresponsegenerator.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 by Ishan Arora + * ishanarora@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef HTTPRESPONSEGENERATOR_H +#define HTTPRESPONSEGENERATOR_H + +#include + +class HttpResponseGenerator : public QHttpResponseHeader +{ + private: + QByteArray message; + + public: + void setMessage(const QByteArray message); + void setMessage(const QString message); + void stripMessage(); + void setContentTypeByExt(const QString ext); + virtual QByteArray toByteArray() const; +}; + +#endif diff --git a/src/httpserver.h b/src/httpserver.h new file mode 100644 index 000000000..7ef31e66f --- /dev/null +++ b/src/httpserver.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007 by Ishan Arora + * ishanarora@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef HTTPSERVER_H +#define HTTPSERVER_H + +#include +#include + +class bittorrent; + +class EventManager; + +class HttpServer : public QTcpServer +{ + Q_OBJECT + + private: + QByteArray base64; + bittorrent *BTSession; + EventManager *manager; + + public: + HttpServer(bittorrent *BTSession, int msec, QObject* parent = 0); + ~HttpServer(); + void setAuthorization(QString username, QString password); + bool isAuthorized(QByteArray auth) const; + EventManager *eventManager() const; + + private slots: + void newHttpConnection(); + void onTimer(); +}; + +#endif diff --git a/src/json.h b/src/json.h new file mode 100644 index 000000000..e89c9f289 --- /dev/null +++ b/src/json.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2007 by Ishan Arora + * ishanarora@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef JSON_H +#define JSON_H + +#include + +QString toJson(QVariant v); + +#endif diff --git a/src/webui.qrc b/src/webui.qrc new file mode 100644 index 000000000..cab1dbf5b --- /dev/null +++ b/src/webui.qrc @@ -0,0 +1,17 @@ + + + webui/index.html + webui/download.html + webui/about.html + webui/css/mocha.css + webui/css/dynamicTable.css + webui/css/style.css + webui/scripts/excanvas-compressed.js + webui/scripts/mocha-events.js + webui/scripts/mocha.js + webui/scripts/mootools-trunk-1475.js + webui/scripts/dynamicTable.js + webui/scripts/client.js + webui/scripts/download.js + + diff --git a/src/webui/about.html b/src/webui/about.html new file mode 100644 index 000000000..ffff6a713 --- /dev/null +++ b/src/webui/about.html @@ -0,0 +1,380 @@ + + + + qBittorrent web User Interface + + + + + + + +

About

+ +

qBittorrent WebUI version 1.0

+

Copyright (c) 2008 by Christopher Dumez

+

Homepage : http://www.qbittorrent.com

+ +

Authors

+ +Name : Ishan Arora
+Email : ishanarora@gmail.com
+
+Name : Ankit Gupta
+Email : ank.iitd@gmail.com
+
+Name : Chandan Shikhar Dua
+Email : shikhar.ap@gmail.com
+
+Name : Swapnil Kumar
+Email : swapnil.iitd@gmail.com
+ +

License

+
+
+GNU GENERAL PUBLIC LICENSE
+
+Version 2, June 1991
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+
+Preamble
+The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+GNU GENERAL PUBLIC LICENSE
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The 'Program', below,
+refers to any such program or work, and a 'work based on the Program'
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term 'modification'.) Each licensee is addressed as 'you'.
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices
+stating that you changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in
+whole or in part contains or is derived from the Program or any
+part thereof, to be licensed as a whole at no charge to all third
+parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively
+when run, you must cause it, when started running for such
+interactive use in the most ordinary way, to print or display an
+announcement including an appropriate copyright notice and a
+notice that there is no warranty (or else, saying that you provide
+a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this
+License. (Exception: if the Program itself is interactive but
+does not normally print such an announcement, your work based on
+the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable
+source code, which must be distributed under the terms of Sections
+1 and 2 above on a medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three
+years, to give any third party, for a charge no more than your
+cost of physically performing source distribution, a complete
+machine-readable copy of the corresponding source code, to be
+distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer
+to distribute corresponding source code. (This alternative is
+allowed only for noncommercial distribution and only if you
+received the program in object code or executable form with such
+an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and 'any
+later version', you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+
+NO WARRANTY
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the 'copyright' line and a pointer to where the full notice is found.
+
+
+Copyright (C) 
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) year name of author
+Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+This is free software, and you are welcome to redistribute it
+under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a 'copyright disclaimer' for the program, if
+necessary. Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+`Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+'signature of Ty Coon', 1 April 1989
+Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+
+
+
+ + diff --git a/src/webui/css/dynamicTable.css b/src/webui/css/dynamicTable.css new file mode 100644 index 000000000..ccef67487 --- /dev/null +++ b/src/webui/css/dynamicTable.css @@ -0,0 +1,43 @@ + + +/************************************************************** + + Dynamic Table + v 0.4 + +**************************************************************/ + +table { + border: 1px solid #ccc; + width: 100%; +} + +th { + background-color: #eee; + padding: 4px; +} + +tr { + background-color: #fff; + padding: 4px; +} + +tr.alt { + background-color: #eee; + padding: 4px; +} + +td { + padding: 0 2px; +} + +tr.selected { + background-color: #354158; + color: #fff; +} + +tr.over { + background-color: #ee6600; + color: #fff; + cursor: pointer; +} diff --git a/src/webui/css/mocha.css b/src/webui/css/mocha.css new file mode 100644 index 000000000..29f45e89e --- /dev/null +++ b/src/webui/css/mocha.css @@ -0,0 +1,427 @@ +/* MOCHA UI */ + +html { + overflow: auto; /* Removes empty vertical scrollbars in IE */ +} + +body { + margin: 0; /* Required */ +} + +#mochaDesktop { + position: relative; + top: 0; + left: 0; + width: 100%; + height: 500px; + min-width: 750px; /* Helps keep header content from wrapping */ + overflow: hidden; +} + +#mochaDesktopHeader { +} + +#mochaDesktopTitlebar { + padding: 5px 8px 7px 8px; + background: #f0f0f0; + border-top: 1px solid #fff; + border-bottom: 1px solid #bbb; + height: 25px; +} + +#mochaDesktopTitlebar h1 { + margin: 0; + padding: 4px 0 0 0; + font-size: 18px; + font-weight: bold; + color: #e60; +} + +#mochaDesktopTitlebar h1 .version { + font-size: 12px; + color: #333; +} + +#mochaDesktopNavbar { + padding: 0; + background: #f0f0f0; + border-top: 1px solid #fff; + border-bottom: 1px solid #bbb; + height: 22px; +} + +#mochaDesktopNavbar ul { + padding: 0; + margin: 0; + list-style: none; + font-size: 12px; +} + +#mochaDesktopNavbar li { + float: left; +} + +#mochaDesktopNavbar a { + display: block; +} + +#mochaDesktopNavbar ul li a { + padding: 2px 9px 2px 9px; + color: #000; +} + +#mochaDesktopNavbar ul li a:hover { + background: #e4e4e4; +} + +#mochaDesktopNavbar li ul { + padding: 2px; + border: 1px solid #999; + background: #fff; + position: absolute; + width: 148px; + left: -999em; + z-index: 8000; +} + +#mochaDesktopNavbar li ul li { +} + +#mochaDesktopNavbar li ul li a { + padding: 1px 9px 1px 9px; + width: 130px; +} + +#mochaDesktopNavbar li:hover ul, #mochaDesktopNavbar li.ieHover ul { /* lists nested under hovered list items */ + left: auto; +} + +#mochaDesktopNavbar li:hover, #mochaDesktopNavbar li.hover { + position: static; +} + +#mochaToolbar { + padding: 4px 4px 4px 4px; + background: #f0f0f0; + border-top: 1px solid #fff; + border-bottom: 1px solid #bbb; + height: 40px; + width: 100%; +} + +.mochaToolButton { + padding: 4px 4px 4px 4px; +} + +.divider { + margin-top: 2px; + padding-top: 3px; + border-top: 1px solid #ddd; +} + +#mochaPageWrapper { + clear: both; + width: 100%; + overflow: auto; /* This can be set to hidden if you want to treat it more like a desktop than a web page */ +} + +#mochaPage { + padding: 0px 0px 0px 0px; +} + +/* Windows */ + +div.mocha { + position: absolute; + top: 0; + left: 0; + display: none; + overflow: hidden; +} + +div.mochaOverlay { + position: absolute; + top: 2px; + left: 3px; +} + +div.mocha .mochaTitlebar { + width: 100%; + overflow: hidden; +} + +div.mocha .mochaTitlebar h3 { + font-size: 12px; + line-height: 15px; + font-weight: bold; + color: #000; + margin: 6px 10px 4px 12px; + padding: 0; +} + +div.mocha .mochaTitlebar a { + color: #444; +} + +div.mocha .mochaContentBorder { + border-top: 1px solid #bdbdbd; + border-bottom: 1px solid #d7d7d7; +} + +div.mocha .mochaContentWrapper { /* Has a fixed height and scrollbars if required */ + font-size: 11px; + background: #fff; + overflow: auto; +} + +div.mocha .mochaContent { + padding: 10px 12px; +} + +.resizeHandle { + position: absolute; + bottom: 5px; + right: 5px; + width: 12px; + height: 12px; + background: #000; + cursor: se-resize; + opacity: .0; + filter: alpha(opacity=0); + -moz-opacity: 0; +} + +.mochaControls { + position: absolute; + top: 8px; + right: 8px; + width: 0; + height: 14px; + background: #f00; + opacity: .0; + filter: alpha(opacity=0); + -moz-opacity: .0; +} + +.minimizeToggle, .maximizeToggle, .mochaClose { + font-family: Verdana, Arial, Helvetica, sans-serif; + float: right; + width: 14px; + height: 14px; + font-size: 1px; + background: #000; + cursor: pointer; +} + +.mochaLoadingIcon { + position: absolute; + bottom: 7px; + left: 6px; +} + +.mochaIframe { + width: 100%; + height: inherit; +} + +/* Slider */ + +#slider { + position: relative; + font-size: 12px; + font-weight: bold; + width: 200px; + padding-top: 18px; +} + +#sliderarea { + clear: both; + position: relative; + height: 10px; + width: 200px; + font-size: 1px; + background: #ddd; + margin: 2px 0 10px 0; +} + +#sliderknob { + position: absolute; + height: 10px; + width: 20px; + font-size: 1px; + background: #e60; + cursor: pointer; +} + +#update { + position: absolute; + top: 0; + left: 0; +} + +.clear { + clear: both; +} + +/* Window Builder Form Elements */ + +#mochaDesktop form { + margin: 0 0 0 0; + padding: 5px 0 0 0; + width: 320px; +} + +#mochaDesktop textarea, #mochaDesktop input { + color: #555; + font-family: Arial, Helvetica, sans-serif; + font-size: 12px; +} + +#mochaDesktop .input { + background: #fbfbfb; + width: 225px; + padding: 1px 0 1px 3px; + border: 1px solid #ccc; +} + +#mochaDesktop textarea { + background: #fbfbfb; + width: 225px; + height: 100px; + padding: 1px 0 1px 3px; + border: 1px solid #ccc; +} + +#mochaDesktop .formLabel { + float: left; + text-align: right; + width: 80px; + margin: 0 0 5px 0; +} + +#mochaDesktop .formField { + float: right; + margin: 0 0 5px 0; + padding: 0 0 0 0; + width: 230px; +} + +#mochaDesktop form .number { + width: 40px; +} + +/* Modal Windows */ + +#mochaModalOverlay { + display: none; + position: fixed; /* This is fixed rather than absolute to fix Mac FF2 issues */ + top: 0; + left: 0; + width: 100%; + background: #000; + opacity: 0; + filter: alpha(opacity=0); + -moz-opacity: 0; + z-index: 10000; +} + +* html #mochaModalOverlay { + position: absolute; +} + +/* fix for IE6 select z-index issue */ + +iframe.zIndexFix { + display: none; + display: block; + position: absolute; + top: 0; + left: 0; + z-index: -1; + filter: mask(); + width: 100px; + height: 100px; + border: 1px solid transparent; +} + +/* Window Minimize Dock */ + +#mochaDock { + display: none; + position: relative; + background: #fff; + padding: 2px 15px; + bottom: 0; + left: 0; + width: 100%; + background-color: #f0f0f0; + border-top: 1px solid #bbb; + border-bottom: 1px solid #fff; + min-height: 20px; + height: auto; + _height: 20px; /* IE 6.0 hack, for not supporting min-height SFF 12/07/2007*/ +} + +.mochaDockButton { + width: 120px; + height: 20px; + font-family: Verdana, Arial, Helvetica, sans-serif; + text-align: left; + font-size: 10px; + background-color: #f0f0f0; + border-top: 1px solid #fff; + border-right: 1px solid #777; + border-bottom: 1px solid #777; + border-left: 1px solid #fff; + margin: 0 1px 0 0; +} + +#mochaDockPlacement { + position: absolute; + top: 3px; + left: 2px; + width: 10px; + height: 9px; + opacity: 0; + filter: alpha(opacity=0); + -moz-opacity: 0; + background: #f00; /* for troubleshooting */ + cursor: pointer; + z-index: 3; /* for IE */ +} + +#mochaDockAutoHide { + position: absolute; + top: 13px; + left: 2px; + width: 10px; + height: 9px; + opacity: 0; + filter: alpha(opacity=0); + -moz-opacity: 0; + background: #f00; /* for troubleshooting */ + cursor: pointer; + z-index: 3; /* for IE */ +} + +/* Accordian */ + +.accordianToggler { + margin: 0; + padding: 4px 10px; + background: #eee; + border-bottom: 1px solid #ddd; + border-top: 1px solid #f5f5f5; + font-size: 11px; + cursor: pointer; +} + +.accordianContent { + padding: 10px 10px 5px 10px; +} + +/* Screens/Workspaces */ + +.screen { + display: none; +} diff --git a/src/webui/css/style.css b/src/webui/css/style.css new file mode 100644 index 000000000..a7c3f4499 --- /dev/null +++ b/src/webui/css/style.css @@ -0,0 +1,113 @@ +/* Reset */ + +ul,ol,dl,li,dt,dd,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input,object,iframe { margin: 0; padding: 0; } +a img,:link img,:visited img { border: none; } +table { border-collapse: collapse; border-spacing: 0; } +:focus { outline: none; } + +/* Structure */ + +body { + margin: 0; + text-align: left; + font-family: Arial, Helvetica, sans-serif; + font-size: 12px; + line-height: 18px; + color: #555; +} + +.aside { + width: 300px; +} + +/* Typography */ + +h2, h3, h4 { + margin: 0; + padding: 0 0 5px 0; + font-size: 12px; + font-weight: bold; + color: #333; +} + +h2 { + font-size: 14px; + color: #555; + font-weight: bold; +} + +#mochaPage h3 { + display: block; + font-size: 12px; + padding: 6px 0 6px 0; + margin: 0 0 8px 0; + border-bottom: 1px solid #bbb; +} + +h4 { + font-size: 11px; +} + +a { + color: #e60; + text-decoration: none; + cursor: pointer; +} + +a:hover { + text-decoration: none; +} + +p { + margin: 0; + padding: 0 0 9px 0; +} + +/* List Elements */ + +ul { + list-style: outside; + margin: 0 0 9px 16px; +} + +dt { + font-weight: bold; + } + +dd { + padding: 0 0 9px 0; +} + +/* Code */ + +pre { + background-color: #f6f6f6; + color: #006600; + display: block; + font-family: 'Courier New', Courier, monospace; + font-size: 11px; + max-height: 250px; + overflow: auto; + margin: 0 0 10px 0; + padding: 10px; + border: 1px solid #d1d7dc; + } + +/* Dividers */ + +hr { + background-color: #ddd; + color: #ccc; + height: 1px; + border: 0px; +} + +.vcenter { + vertical-align: middle; +} + +#urls { + width:90%; + height:100%; +} + diff --git a/src/webui/download.html b/src/webui/download.html new file mode 100644 index 000000000..9692dea5b --- /dev/null +++ b/src/webui/download.html @@ -0,0 +1,17 @@ + + + + + Download from URL + + + + + + +
+

Download Torrents from URLs

+

Only One URL per Line

Download +
+ + diff --git a/src/webui/index.html b/src/webui/index.html new file mode 100644 index 000000000..13ec0a952 --- /dev/null +++ b/src/webui/index.html @@ -0,0 +1,85 @@ + + + + + qBittorrent web User Interface + + + + + + + + + + + +
+
+
+

qBittorrent Web User Interface version 1.0

+
+ +
+ + + + + + +
+
+
+
+
+
+ + + + + + + + + + + + +
NameSizeProgressDL SpeedUP Speed
+
+
+
+
+
+ + diff --git a/src/webui/scripts/client.js b/src/webui/scripts/client.js new file mode 100644 index 000000000..38a6ae8a7 --- /dev/null +++ b/src/webui/scripts/client.js @@ -0,0 +1,88 @@ +window.addEvent('domready', function(){ + myTable = new dynamicTable('myTable', {overCls: 'over', selectCls: 'selected', altCls: 'alt'}); + var r=0; + var waiting=false; + var round1 = function(val){return Math.round(val*10)/10}; + var fspeed = function(val){return round1(val/1024) + ' KiB/s';}; + var fsize = function(val){ + var units = ['B', 'KiB', 'MiB', 'GiB']; + for(var i=0; i<5; i++){ + if (val < 1024) { + return round1(val) + ' ' + units[i]; + } + val /= 1024; + } + return round1(val) + ' TiB'; + }; + var ajaxfn = function(){ + var url = 'json/events?r='+r; + if (!waiting){ + waiting=true; + var request = new Json.Remote(url, { + method: 'get', + onComplete: function(jsonObj) { + if(jsonObj){ + r=jsonObj.revision; + var events=jsonObj.events; + events.each(function(event){ + switch(event.type){ + case 'add': + var row = new Array(); + row.length = 6; + row[1] = event.name; + myTable.insertRow(event.hash, row); + break; + case 'modify': + var row = new Array(); + if($defined(event.state)) + { + switch (event.state) + { + case 'paused': + row[0] = ''; + break; + case 'seeding': + row[0] = ''; + break; + case 'checking': + row[0] = ''; + break; + case 'downloading': + row[0] = ''; + break; + case 'connecting': + row[0] = ''; + break; + case 'stalled': + row[0] = ''; + break; + } + } + if($defined(event.size)){ + row[2] = fsize(event.size); + } + if($defined(event.progress)) + { + row[3] = round1(event.progress*100) + ' %'; + } + if($defined(event.dlspeed)) + row[4] = fspeed(event.dlspeed); + if($defined(event.upspeed)) + row[5] = fspeed(event.upspeed); + myTable.updateRow(event.hash, row); + break; + case 'delete': + myTable.removeRow(event.hash); + break; + } + }); + } + waiting=false; + ajaxfn.delay(1000); + } + }).send(); + } + }; + ajaxfn(); +// ajaxfn.periodical(5000); +}); diff --git a/src/webui/scripts/download.js b/src/webui/scripts/download.js new file mode 100644 index 000000000..1532e8c4a --- /dev/null +++ b/src/webui/scripts/download.js @@ -0,0 +1,10 @@ +window.addEvent('domready', function(){ + $('downButton').addEvent('click', function(e){ + new Event(e).stop(); + new Ajax('/command/download', {method: 'post', data: {urls: $('urls').value}, + onComplete: function() { + window.parent.document.getElementById('downloadPage').parentNode.removeChild(window.parent.document.getElementById('downloadPage')); + } + }).request(); + }); +}); diff --git a/src/webui/scripts/dynamicTable.js b/src/webui/scripts/dynamicTable.js new file mode 100644 index 000000000..edeba2aa9 --- /dev/null +++ b/src/webui/scripts/dynamicTable.js @@ -0,0 +1,121 @@ + + +/************************************************************** + + Script : Dynamic Table + Version : 0.4 + Authors : Ishan Arora + Desc : Programable sortable table + Licence : Open Source MIT Licence + +**************************************************************/ + +var dynamicTable = new Class ({ + + initialize: function(table, options){ + this.setOptions({ + overCls: false, + selectCls: false, + altCls: false + }, options); + this.table = $(table); + this.rows = new Object(); + this.cur = false; + }, + + altRow: function() + { + var trs = this.table.getElements('tr'); + if(this.options.altCls) + { + trs.each(function(el,i){ + if(i % 2){ + el.addClass(this.options.altCls); + }else{ + el.removeClass(this.options.altCls); + } + }.bind(this)); + } + }, + + insertRow: function(id, row){ + this.removeRow(id); + var tr = new Element('tr'); + this.rows[id] = tr; + for(var i=0; i"){var d="/"+a.tagName,e;while((e=a.nextSibling)&&e.tagName!=d){e.removeNode()}if(e){e.removeNode()}}a.parentNode.replaceChild(c,a);return c},initElement:function(a){a=this.q(a);a.getContext=function(){if(this.l){return this.l}return this.l=new K(this)};a.attachEvent("onpropertychange",V);a.attachEvent("onresize", +W);var b=a.attributes;if(b.width&&b.width.specified){a.style.width=b.width.nodeValue+"px"}else{a.width=a.clientWidth}if(b.height&&b.height.specified){a.style.height=b.height.nodeValue+"px"}else{a.height=a.clientHeight}return a}};function V(a){var b=a.srcElement;switch(a.propertyName){case "width":b.style.width=b.attributes.width.nodeValue+"px";b.getContext().clearRect();break;case "height":b.style.height=b.attributes.height.nodeValue+"px";b.getContext().clearRect();break}}function W(a){var b=a.srcElement; +if(b.firstChild){b.firstChild.style.width=b.clientWidth+"px";b.firstChild.style.height=b.clientHeight+"px"}}Q.init();var R=[];for(var E=0;E<16;E++){for(var F=0;F<16;F++){R[E*16+F]=E.toString(16)+F.toString(16)}}function J(){return[[1,0,0],[0,1,0],[0,0,1]]}function G(a,b){var c=J();for(var d=0;d<3;d++){for(var e=0;e<3;e++){var g=0;for(var h=0;h<3;h++){g+=a[d][h]*b[h][e]}c[d][e]=g}}return c}function N(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit= +a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX;b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.d=a.d;b.e=a.e}function O(a){var b,c=1;a=String(a);if(a.substring(0,3)=="rgb"){var d=a.indexOf("(",3),e=a.indexOf(")",d+1),g=a.substring(d+1,e).split(",");b="#";for(var h=0;h<3;h++){b+=R[Number(g[h])]}if(g.length==4&&a.substr(3,1)=="a"){c=g[3]}}else{b=a}return[b,c]}function S(a){switch(a){case "butt":return"flat";case "round":return"round"; +case "square":default:return"square"}}function K(a){this.a=J();this.m=[];this.k=[];this.c=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=m*1;this.globalAlpha=1;this.canvas=a;var b=a.ownerDocument.createElement("div");b.style.width=a.clientWidth+"px";b.style.height=a.clientHeight+"px";b.style.overflow="hidden";b.style.position="absolute";a.appendChild(b);this.j=b;this.d=1;this.e=1}var j=K.prototype;j.clearRect=function(){this.j.innerHTML= +"";this.c=[]};j.beginPath=function(){this.c=[]};j.moveTo=function(a,b){this.c.push({type:"moveTo",x:a,y:b});this.f=a;this.g=b};j.lineTo=function(a,b){this.c.push({type:"lineTo",x:a,y:b});this.f=a;this.g=b};j.bezierCurveTo=function(a,b,c,d,e,g){this.c.push({type:"bezierCurveTo",cp1x:a,cp1y:b,cp2x:c,cp2y:d,x:e,y:g});this.f=e;this.g=g};j.quadraticCurveTo=function(a,b,c,d){var e=this.f+0.6666666666666666*(a-this.f),g=this.g+0.6666666666666666*(b-this.g),h=e+(c-this.f)/3,l=g+(d-this.g)/3;this.bezierCurveTo(e, +g,h,l,c,d)};j.arc=function(a,b,c,d,e,g){c*=m;var h=g?"at":"wa",l=a+M(d)*c-A,n=b+L(d)*c-A,o=a+M(e)*c-A,f=b+L(e)*c-A;if(l==o&&!g){l+=0.125}this.c.push({type:h,x:a,y:b,radius:c,xStart:l,yStart:n,xEnd:o,yEnd:f})};j.rect=function(a,b,c,d){this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath()};j.strokeRect=function(a,b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.stroke()};j.fillRect=function(a, +b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.fill()};j.createLinearGradient=function(a,b,c,d){var e=new H("gradient");return e};j.createRadialGradient=function(a,b,c,d,e,g){var h=new H("gradientradial");h.n=c;h.o=g;h.i.x=a;h.i.y=b;return h};j.drawImage=function(a,b){var c,d,e,g,h,l,n,o,f=a.runtimeStyle.width,k=a.runtimeStyle.height;a.runtimeStyle.width="auto";a.runtimeStyle.height="auto";var q=a.width,r=a.height;a.runtimeStyle.width= +f;a.runtimeStyle.height=k;if(arguments.length==3){c=arguments[1];d=arguments[2];h=(l=0);n=(e=q);o=(g=r)}else if(arguments.length==5){c=arguments[1];d=arguments[2];e=arguments[3];g=arguments[4];h=(l=0);n=q;o=r}else if(arguments.length==9){h=arguments[1];l=arguments[2];n=arguments[3];o=arguments[4];c=arguments[5];d=arguments[6];e=arguments[7];g=arguments[8]}else{throw"Invalid number of arguments";}var s=this.b(c,d),t=[],v=10,w=10;t.push(" ','","");this.j.insertAdjacentHTML("BeforeEnd",t.join(""))};j.stroke=function(a){var b=[],c=O(a?this.fillStyle:this.strokeStyle),d=c[0],e=c[1]*this.globalAlpha,g=10,h=10;b.push("n.x){n.x=k.x}if(l.y== +null||k.yn.y){n.y=k.y}}}b.push(' ">');if(typeof this.fillStyle=="object"){var v={x:"50%",y:"50%"},w=n.x-l.x,x=n.y-l.y,p=w>x?w:x;v.x=i(this.fillStyle.i.x/w*100+50)+"%";v.y=i(this.fillStyle.i.y/x*100+50)+"%";var y=[];if(this.fillStyle.p=="gradientradial"){var z=this.fillStyle.n/p*100,B=this.fillStyle.o/p*100-z}else{var z=0,B=100}var C={offset:null,color:null},D={offset:null,color:null};this.fillStyle.h.sort(function(T,U){return T.offset-U.offset});for(var o=0;oC.offset||C.offset==null){C.offset=u.offset;C.color=u.color}if(u.offset')}else if(a){b.push('')}else{b.push("')}b.push("");this.j.insertAdjacentHTML("beforeEnd",b.join(""));this.c=[]};j.fill=function(){this.stroke(true)};j.closePath=function(){this.c.push({type:"close"})};j.b=function(a,b){return{x:m*(a*this.a[0][0]+b*this.a[1][0]+this.a[2][0])-A,y:m*(a*this.a[0][1]+b*this.a[1][1]+this.a[2][1])-A}};j.save=function(){var a={};N(this,a); +this.k.push(a);this.m.push(this.a);this.a=G(J(),this.a)};j.restore=function(){N(this.k.pop(),this);this.a=this.m.pop()};j.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];this.a=G(c,this.a)};j.rotate=function(a){var b=M(a),c=L(a),d=[[b,c,0],[-c,b,0],[0,0,1]];this.a=G(d,this.a)};j.scale=function(a,b){this.d*=a;this.e*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];this.a=G(c,this.a)};j.clip=function(){};j.arcTo=function(){};j.createPattern=function(){return new P};function H(a){this.p=a;this.n=0;this.o= +0;this.h=[];this.i={x:0,y:0}}H.prototype.addColorStop=function(a,b){b=O(b);this.h.push({offset:1-a,color:b})};function P(){}G_vmlCanvasManager=Q;CanvasRenderingContext2D=K;CanvasGradient=H;CanvasPattern=P})()}; diff --git a/src/webui/scripts/mocha-events.js b/src/webui/scripts/mocha-events.js new file mode 100644 index 000000000..42d4a65c9 --- /dev/null +++ b/src/webui/scripts/mocha-events.js @@ -0,0 +1,115 @@ +/* ----------------------------------------------------------------- + + ATTACH MOCHA LINK EVENTS + Notes: Here is where you define your windows and the events that open them. + If you are not using links to run Mocha methods you can remove this function. + + If you need to add link events to links within windows you are creating, do + it in the onContentLoaded function of the new window. + + ----------------------------------------------------------------- */ + +function attachMochaLinkEvents(){ + + function addClickEvent(el, fn){ + ['Link','Button'].each(function(item) { + if ($(el+item)){ + $(el+item).addEvent('click', fn); + } + }); + } + + addClickEvent('download', function(e){ + new Event(e).stop(); + document.mochaUI.newWindow({ + id: 'downloadPage', + title: 'Download from URLs', + loadMethod: 'iframe', + contentURL:'download.html', + scrollbars: false, + resizable: false, + maximizable: false, + paddingVertical: 0, + paddingHorizontal: 0, + width: 500, + height: 270 + }); + }); + + addClickEvent('delete', function(e){ + new Event(e).stop(); + var h = myTable.selectedId(); + if(h && confirm('Are you sure you want to delete the selected item in download list?')) + new Ajax('/command/delete', {method: 'post', data: {hash: h}}).request(); + }); + + ['pause','resume'].each(function(item) { + addClickEvent(item, function(e){ + new Event(e).stop(); + var h = myTable.selectedId(); + if(h){ + new Ajax('/command/'+item, {method: 'post', data: {hash: h}}).request(); + } + }); + + addClickEvent(item+'All', function(e){ + new Event(e).stop(); + new Ajax('/command/'+item+'all').request(); + }); + }); + + addClickEvent('bug', function(e){ + new Event(e).stop(); + document.mochaUI.newWindow({ + id: 'bugPage', + title: 'Report a Bug', + loadMethod: 'iframe', + contentURL: 'http://bugs.qbittorrent.org/', + width: 650, + height: 400, + }); + }); + + addClickEvent('site', function(e){ + new Event(e).stop(); + document.mochaUI.newWindow({ + id: 'sitePage', + title: 'qBittorrent Website', + loadMethod: 'iframe', + contentURL: 'http://www.qbittorrent.org/', + width: 650, + height: 400, + }); + }); + + addClickEvent('docs', function(e){ + new Event(e).stop(); + document.mochaUI.newWindow({ + id: 'docsPage', + title: 'qBittorrent official wiki', + loadMethod: 'iframe', + contentURL: 'http://wiki.qbittorrent.org/', + width: 650, + height: 400, + }); + }); + + addClickEvent('about', function(e){ + new Event(e).stop(); + document.mochaUI.newWindow({ + id: 'aboutpage', + title: 'About', + loadMethod: 'iframe', + contentURL: 'about.html', + width: 650, + height: 400, + }); + }); + + // Deactivate menu header links + $$('a.returnFalse').each(function(el){ + el.addEvent('click', function(e){ + new Event(e).stop(); + }); + }); +} diff --git a/src/webui/scripts/mocha.js b/src/webui/scripts/mocha.js new file mode 100644 index 000000000..765275466 --- /dev/null +++ b/src/webui/scripts/mocha.js @@ -0,0 +1,1555 @@ +/* ----------------------------------------------------------------- + + Script: + mocha.js version 0.8 + + Copyright: + Copyright (c) 2007-2008 Greg Houston, + + License: + MIT-style license + + Contributors: + Scott F. Frederick + Joel Lindau + + ----------------------------------------------------------------- */ + +var MochaUI = new Class({ + options: { + // Global options for windows: + // Some of these options can be overriden for individual windows in newWindow() + resizable: true, + draggable: true, + minimizable: true, // Requires dock + maximizable: true, // Requires desktop + closable: true, + effects: true, // Toggles the majority of window fade and move effects + minWidth: 250, // Minimum width of windows when resized + maxWidth: 2500, // Maximum width of windows when resized + minHeight: 100, // Minimum height of windows when resized + maxHeight: 2000, // Maximum height of windows when resized + // Style options: + headerHeight: 25, // Height of window titlebar + footerHeight: 26, + cornerRadius: 9, + bodyBgColor: '#fff', // Body background color - Hex + headerStartColor: [250, 250, 250], // Header gradient's top color - RGB + headerStopColor: [228, 228, 228], // Header gradient's bottom color + footerBgColor: [246, 246, 246], // Background color of the main canvas shape + minimizeColor: [231, 231, 209], // Minimize button color + maximizeColor: [217, 229, 217], // Maximize button color + closeColor: [229, 217, 217], // Close button color + resizableColor: [209, 209, 209], // Resizable icon color + // Cascade options: + desktopTopOffset: 20, // Use a negative number if neccessary to place first window where you want it + desktopLeftOffset: 290, + mochaTopOffset: 70, // Initial vertical spacing of each window + mochaLeftOffset: 70, // Initial horizontal spacing of each window + // Naming options: + // If you change the IDs of the Mocha Desktop containers in your HTML, you need to change them here as well. + desktop: 'mochaDesktop', + desktopHeader: 'mochaDesktopHeader', + desktopNavBar: 'mochaDesktopNavbar', + pageWrapper: 'mochaPageWrapper', + dock: 'mochaDock' + }, + initialize: function(options){ + this.setOptions(options); + // Private properties + this.ieSupport = 'excanvas' // Makes it easier to switch between Excanvas and Moocanvas for testing + this.indexLevel = 1; // Used for z-Index + this.windowIDCount = 0; + this.myTimer = ''; // Used with accordian + this.iconAnimation = ''; // Used with loading icon + this.mochaControlsWidth = 0; + this.minimizebuttonX = 0; // Minimize button horizontal position + this.maximizebuttonX = 0; // Maximize button horizontal position + this.closebuttonX = 0; // Close button horizontal position + this.shadowWidth = 3; + this.shadowOffset = this.shadowWidth * 2; + this.HeaderFooterShadow = this.options.headerHeight + this.options.footerHeight + this.shadowOffset; + + this.desktop = $(this.options.desktop); + this.desktopHeader = $(this.options.desktopHeader); + this.desktopNavBar = $(this.options.desktopNavBar); + this.pageWrapper = $(this.options.pageWrapper); + this.dock = $(this.options.dock); + + this.dockVisible = this.dock ? true : false; // True when dock is visible, false when not + this.dockAutoHide = false; // True when dock autohide is set to on, false if set to off + + if ( this.dock ) { this.initializeDock(); } + + this.setDesktopSize(); + this.newWindowsFromXHTML(); + this.modalInitialize(); + this.menuInitialize(); + + // Resize desktop, page wrapper, modal overlay, and maximized windows when browser window is resized + window.onresize = function(){ this.onBrowserResize(); }.bind(this); + + }, + menuInitialize: function(){ + // Fix for dropdown menus in IE6 + if (Browser.Engine.trident4 && this.desktopNavBar){ + this.desktopNavBar.getElements('li').each(function(element) { + element.addEvent('mouseenter', function(){ + this.addClass('ieHover'); + }) + element.addEvent('mouseleave', function(){ + this.removeClass('ieHover'); + }) + }) + }; + }, + modalInitialize: function(){ + var modalOverlay = new Element('div', { + 'id': 'mochaModalOverlay', + 'styles': { + 'height': document.getCoordinates().height + } + }); + modalOverlay.injectInside(this.desktop ? this.desktop : document.body); + + modalOverlay.setStyle('opacity', .4); + this.modalOpenMorph = new Fx.Morph($('mochaModalOverlay'), { + 'duration': 200 + }); + this.modalCloseMorph = new Fx.Morph($('mochaModalOverlay'), { + 'duration': 200, + onComplete: function(){ + $('mochaModalOverlay').setStyle('display', 'none'); + }.bind(this) + }); + }, + onBrowserResize: function(){ + this.setDesktopSize(); + this.setModalSize(); + // Resize maximized windows to fit new browser window size + setTimeout( function(){ + $$('div.mocha').each(function(el){ + if (el.isMaximized) { + + var iframe = this.getSubElement(el, 'iframe'); + if ( iframe ) { + iframe.setStyle('visibility', 'hidden'); + } + + var windowDimensions = document.getCoordinates(); + var contentWrapper = this.getSubElement(el, 'contentWrapper'); + contentWrapper.setStyles({ + 'height': (windowDimensions.height - this.options.headerHeight - this.options.footerHeight), + 'width': windowDimensions.width + }); + + this.drawWindow(el); + if ( iframe ) { + iframe.setStyles({ + 'height': contentWrapper.getStyle('height') + }); + iframe.setStyle('visibility', 'visible'); + } + + } + }.bind(this)); + }.bind(this), 100); + }, + newWindowsFromXHTML: function(properties, cascade){ + $$('div.mocha').each(function(el, i) { + // Get the window title and destroy that element, so it does not end up in window content + if ( Browser.Engine.presto || Browser.Engine.trident5 ) + el.setStyle('display','block'); // Required by Opera, and probably IE7 + var title = el.getElement('h3.mochaTitle'); + var elDimensions = el.getStyles('height', 'width'); + var properties = { + id: el.getProperty('id'), + height: elDimensions.height.toInt(), + width: elDimensions.width.toInt() + } + // If there is a title element, set title and destroy the element so it does not end up in window content + if ( title ) { + properties.title = title.innerHTML; + title.destroy(); + } + + /* + // Make sure there are no null values + for(var key in properties) { + if ( !properties[key] ) + delete properties[key]; + } */ + + // Get content and destroy the element + properties.content = el.innerHTML; + el.destroy(); + + // Create window + this.newWindow(properties, true); + }.bind(this)); + + this.arrangeCascade(); + }, + /* + + Method: newWindowsFromJSON + + Description: Create one or more windows from JSON data. You can define all the same properties + as you can for newWindow. Undefined properties are set to their defaults. + + */ + newWindowsFromJSON: function(properties){ + properties.each(function(properties) { + this.newWindow(properties); + }.bind(this)); + }, + /* + + Method: newWindow + + Arguments: + properties + cascade - boolean - this is set to true for windows parsed from the original XHTML + + */ + newWindow: function(properties, cascade){ + + + + var windowProperties = $extend({ + id: null, + title: 'New Window', + loadMethod: 'html', // html, xhr, or iframe + content: '', // used if loadMethod is set to 'html' + contentURL: 'pages/lipsum.html', // used if loadMethod is set to 'xhr' or 'iframe' + modal: false, + width: 300, + height: 125, + x: null, + y: null, + scrollbars: true, + draggable: this.options.draggable, + resizable: this.options.resizable, + minimizable: this.options.minimizable, + maximizable: this.options.maximizable, + closable: this.options.closable, + // Styling + paddingVertical: 10, + paddingHorizontal: 12, + bodyBgColor: this.options.bodyBgColor, + headerStartColor: this.options.headerStartColor, // Header gradient's top color + headerStopColor: this.options.headerStopColor, // Header gradient's bottom color + footerBgColor: this.options.footerBgColor, // Background color of the main canvas shape + minimizeColor: this.options.minimizeColor, // Minimize button color + maximizeColor: this.options.maximizeColor, // Maximize button color + closeColor: this.options.closeColor, // Close button color + resizableColor: this.options.resizableColor, // Resizable icon color + // Events + onContentLoaded: $empty, // Event, fired when content is successfully loaded via XHR + onFocus: $empty, // Event, fired when the window is focused + onResize: $empty, // Event, fired when the window is resized + onMinimize: $empty, // Event, fired when the window is minimized + onMaximize: $empty, // Event, fired when the window is maximized + onClose: $empty, // Event, fired just before the window is closed + onCloseComplete: $empty // Event, fired after the window is closed + }, properties || {}); + var windowEl = $(windowProperties.id); + + // Check if window already exists and is not in progress of closing down + if ( windowEl && !windowEl.isClosing ) { + if ( windowEl.isMinimized ) // If minimized -> restore + this.restoreMinimized(windowEl); + else // else focus + setTimeout(function(){ this.focusWindow(windowEl); }.bind(this),10); + return; + } + + // Create window div + var windowEl = new Element('div', { + 'class': 'mocha', + 'id': windowProperties.id && windowProperties.id != null ? windowProperties.id : 'win' + (++this.windowIDCount), + 'styles': { + 'width': windowProperties.width, + 'height': windowProperties.height, + 'display': 'block' + } + }); + + // Part of fix for scrollbar issues in Mac FF2 + if (Browser.Platform.mac && Browser.Engine.gecko){ + windowEl.setStyle('position', 'fixed'); + } + + if (windowProperties.loadMethod == 'iframe') { + // Iframes have their own scrollbars and padding. + windowProperties.scrollbars = false; + windowProperties.paddingVertical = 0; + windowProperties.paddingHorizontal = 0; + } + + // Extend our window element + windowEl = $extend(windowEl, { + // Custom properties + id: windowProperties.id, + oldTop: 0, + oldLeft: 0, + oldWidth: 0, // Using this? + oldHeight: 0, + iconAnimation: $empty, + modal: windowProperties.modal, + scrollbars: windowProperties.scrollbars, + contentBorder: null, + // Always use close buttons for modal windows + closable: windowProperties.closable || windowProperties.modal, + resizable: windowProperties.resizable && !windowProperties.modal, + draggable: windowProperties.draggable && !windowProperties.modal, + // Minimizable, dock is required and window cannot be modal + minimizable: this.dock && windowProperties.minimizable && !windowProperties.modal, + // Maximizable, desktop is required + maximizable: this.desktop && windowProperties.maximizable && !windowProperties.modal, + iframe: windowProperties.loadMethod == 'iframe' ? true : false, + isMaximized: false, + isMinimized: false, + // Custom styling + headerStartColor: windowProperties.headerStartColor, // Header gradient's top color + headerStopColor: windowProperties.headerStopColor, // Header gradient's bottom color + footerBgColor: windowProperties.footerBgColor, // Background color of the main canvas shape + minimizeColor: windowProperties.minimizeColor, // Minimize button color + maximizeColor: windowProperties.maximizeColor, // Maximize button color + closeColor: windowProperties.closeColor, // Close button color + resizableColor: windowProperties.resizableColor, // Resizable icon color + // Custom events + onFocus: windowProperties.onFocus, + onResize: windowProperties.onResize, + onMinimize: windowProperties.onMinimize, + onMaximize: windowProperties.onMaximize, + onClose: windowProperties.onClose, + onCloseComplete: windowProperties.onCloseComplete + }); + + // Insert sub elements inside windowEl and cache them locally while creating the new window + var subElements = this.insertWindowElements(windowEl, windowProperties.height, windowProperties.width); + + // Set title + subElements.title.setHTML(windowProperties.title); + + // Add content to window + switch(windowProperties.loadMethod) { + case 'xhr': + new Request({ + url: windowProperties.contentURL, + onRequest: function(){ + this.showLoadingIcon(subElements.canvasIcon); + }.bind(this), + onFailure: function(){ + subElements.content.setHTML('

Error Loading XMLHttpRequest

Make sure all of your content is uploaded to your server, and that you are attempting to load a document from the same domain as this page. XMLHttpRequests will not work on your local machine.

'); + this.hideLoadingIcon.delay(150, this, subElements.canvasIcon); + }.bind(this), + onSuccess: function(response) { + subElements.content.setHTML(response); + this.hideLoadingIcon.delay(150, this, subElements.canvasIcon); + windowProperties.onContentLoaded(); + }.bind(this) + }).get(); + break; + case 'iframe': + if ( windowProperties.contentURL == '') { + break; + } + subElements.iframe = new Element('iframe', { + 'id': windowEl.id + '_iframe', + 'class': 'mochaIframe', + 'src': windowProperties.contentURL, + 'marginwidth': 0, + 'marginheight': 0, + 'frameBorder': 0, + 'scrolling': 'auto' + }).injectInside(subElements.content); + // Add onload event to iframe so we can stop the loading icon and run onContentLoaded() + subElements.iframe.addEvent('load', function(e) { + this.hideLoadingIcon.delay(150, this, subElements.canvasIcon); + windowProperties.onContentLoaded(); + }.bind(this)); + this.showLoadingIcon(subElements.canvasIcon); + break; + case 'html': + default: + subElements.content.setHTML(windowProperties.content); + windowProperties.onContentLoaded(); + break; + } + + // Set scrollbars, always use 'hidden' for iframe windows + subElements.contentWrapper.setStyles({ + 'overflow': windowProperties.scrollbars && !windowProperties.iframe ? 'auto' : 'hidden', + 'background': windowProperties.bodyBgColor + }); + + // Set content padding + subElements.content.setStyles({ + 'padding-top': windowProperties.paddingVertical, + 'padding-bottom': windowProperties.paddingVertical, + 'padding-left': windowProperties.paddingHorizontal, + 'padding-right': windowProperties.paddingHorizontal + }); + + // Attach events to the window + this.attachResizable(windowEl, subElements); + this.setupEvents(windowEl, subElements); + + // Move new window into position. If position not specified by user then center the window on the page + var dimensions = document.getCoordinates(); + + if (!windowProperties.y) { + var windowPosTop = (dimensions.height * .5) - ((windowProperties.height + this.HeaderFooterShadow) * .5); + } + else { + var windowPosTop = windowProperties.y + } + + if (!windowProperties.x) { + var windowPosLeft = (dimensions.width * .5) - (windowProperties.width * .5); + } + else { + var windowPosLeft = windowProperties.x + } + + if (windowEl.modal) { + $('mochaModalOverlay').setStyle('display', 'block'); + if (this.options.effects == false){ + $('mochaModalOverlay').setStyle('opacity', .55); + } + else { + this.modalCloseMorph.cancel(); + this.modalOpenMorph.start({ + 'opacity': .55 + }); + } + windowEl.setStyles({ + 'top': windowPosTop, + 'left': windowPosLeft, + 'zIndex': 11000 + }); + } + else if (cascade == true) { + // do nothing + } + else if (this.options.effects == false){ + windowEl.setStyles({ + 'top': windowPosTop, + 'left': windowPosLeft + }); + } + else { + windowEl.positionMorph = new Fx.Morph(windowEl, { + 'duration': 300 + }); + windowEl.positionMorph.start({ + 'top': windowPosTop, + 'left': windowPosLeft + }); + setTimeout(function(){ this.focusWindow(windowEl); }.bind(this), 10); + } + + // Inject window into DOM + + windowEl.injectInside(this.desktop ? this.desktop : document.body); + this.drawWindow(windowEl, subElements); + + // Drag.Move() does not work in IE until element has been injected, thus setting here + this.attachDraggable(windowEl, subElements.titleBar); + + }, + /* + + Method: closeWindow + + Arguments: + el: the $(window) to be closed + + Returns: + true: the window was closed + false: the window was not closed + + */ + closeWindow: function(windowEl) { + // Does window exist and is not already in process of closing ? + if ( !(windowEl = $(windowEl)) || windowEl.isClosing ) + return; + + windowEl.isClosing = true; + windowEl.onClose(); + + if (this.options.effects == false){ + if (windowEl.modal) { + $('mochaModalOverlay').setStyle('opacity', 0); + } + windowEl.destroy(); + windowEl.onCloseComplete(); + } + else { + // Redraws IE windows without shadows since IE messes up canvas alpha when you change element opacity + if (Browser.Engine.trident) this.drawWindow(windowEl, null, false); + if (windowEl.modal) { + this.modalCloseMorph.start({ + 'opacity': 0 + }); + } + var closeMorph = new Fx.Morph(windowEl, { + duration: 250, + onComplete: function(){ + windowEl.destroy(); + windowEl.onCloseComplete(); + }.bind(this) + }); + closeMorph.start({ + 'opacity': .4 + }); + } + return true; + }, + /* + + Method: closeAll + + Notes: This closes all the windows + + Returns: + true: the windows were closed + false: the windows were not closed + + */ + closeAll: function() { + $$('div.mocha').each(function(el) { + this.closeWindow(el); + $$('button.mochaDockButton').destroy(); + }.bind(this)); + + return true; + }, + focusWindow: function(windowEl){ + if ( !(windowEl = $(windowEl)) ) + return; + // Only focus when needed + if ( windowEl.getStyle('zIndex').toInt() == this.indexLevel ) + return; + this.indexLevel++; + windowEl.setStyle('zIndex', this.indexLevel); + windowEl.onFocus(); + }, + maximizeWindow: function(windowEl) { + // If window no longer exists or is maximized, stop + if ( !(windowEl = $(windowEl)) || windowEl.isMaximized ) + return; + var contentWrapper = this.getSubElement(windowEl, 'contentWrapper'); + windowEl.onMaximize(); + + // Save original position, width and height + windowEl.oldTop = windowEl.getStyle('top'); + windowEl.oldLeft = windowEl.getStyle('left'); + contentWrapper.oldWidth = contentWrapper.getStyle('width'); + contentWrapper.oldHeight = contentWrapper.getStyle('height'); + + // Hide iframe + // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues + if ( windowEl.iframe ) { + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'hidden'); + } + + var windowDimensions = document.getCoordinates(); + + if (this.options.effects == false){ + windowEl.setStyles({ + 'top': -this.shadowWidth, + 'left': -this.shadowWidth + }); + contentWrapper.setStyles({ + 'height': windowDimensions.height - this.options.headerHeight - this.options.footerHeight, + 'width': windowDimensions.width + }); + this.drawWindow(windowEl); + // Show iframe + if ( windowEl.iframe ) { + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); + } + } + else { + var maximizeMorph = new Fx.Morph(windowEl, { + 'duration': 200, + 'onComplete': function(windowEl){ + contentWrapper.setStyles({ + 'height': (windowDimensions.height - this.options.headerHeight - this.options.footerHeight), + 'width': windowDimensions.width + }); + this.drawWindow(windowEl); + // Show iframe + if ( windowEl.iframe ) { + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); + } + }.bind(this) + }); + maximizeMorph.start({ + 'top': -this.shadowWidth, // Takes shadow width into account + 'left': -this.shadowWidth // Takes shadow width into account + }); + } + + windowEl.isMaximized = true; + }, + restoreWindow: function(windowEl) { + // Window exists and is maximized ? + if ( !(windowEl = $(windowEl)) || !windowEl.isMaximized ) + return; + + // Hide iframe + // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues + if ( windowEl.iframe ) { + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'hidden'); + } + var contentWrapper = this.getSubElement(windowEl, 'contentWrapper'); + contentWrapper.setStyles({ + 'width': contentWrapper.oldWidth, + 'height': contentWrapper.oldHeight + }); + + windowEl.isMaximized = false; + this.drawWindow(windowEl); + + if (this.options.effects == false){ + windowEl.setStyles({ + 'top': windowEl.oldTop, + 'left': windowEl.oldLeft + }); + } + else { + var mochaMorph = new Fx.Morph(windowEl, { + 'duration': 150, + 'onComplete': function(el){ + if ( windowEl.iframe ) { + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); + } + }.bind(this) + }); + mochaMorph.start({ + 'top': windowEl.oldTop, + 'left': windowEl.oldLeft + }); + } + }, + minimizeWindow: function(windowEl) { + // What if there is no dock, react how ?? ignore request? + if ( !(windowEl = $(windowEl)) || !this.dock) + return; + + // Hide iframe + // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues + if ( windowEl.iframe ) { + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'hidden'); + } + + var title = this.getSubElement(windowEl, 'title'); + var mochaContentWrapper = windowEl.getElement('.mochaContentWrapper'); + var titleText = title.innerHTML; + windowEl.onMinimize(); + + // Hide window and add to dock + windowEl.setStyle('visibility', 'hidden'); + + // Fixes a scrollbar issue in Mac FF2 + if (Browser.Platform.mac && Browser.Engine.gecko){ + this.getSubElement(windowEl, 'contentWrapper').setStyle('overflow', 'hidden'); + } + + windowEl.isMinimized = true; + var dockButton = new Element('button', { + 'id': windowEl.id + '_dockButton', + 'class': 'mochaDockButton', + 'title': titleText + }).setHTML(titleText.substring(0,13) + (titleText.length > 13 ? '...' : '')).injectInside($(this.dock)); + dockButton.addEvent('click', function(event) { + this.restoreMinimized(windowEl); + }.bind(this)); + // Fixes a scrollbar issue in Mac FF2. + // Have to use timeout because window gets focused when you click on the minimize button + setTimeout(function(){ windowEl.setStyle('zIndex', 1); }.bind(this),100); + }, + restoreMinimized: function(windowEl) { + + // Part of Mac FF2 scrollbar fix + if (windowEl.scrollbars == true && windowEl.iframe == false){ + this.getSubElement(windowEl, 'contentWrapper').setStyle('overflow', 'auto'); + } + + windowEl.setStyle('visibility', 'visible'); + + // Show iframe + if ( windowEl.iframe ) { + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); + } + + windowEl.isMinimized = false; + this.focusWindow(windowEl); + this.dock.getElementById(windowEl.id + '_dockButton').destroy(); + }, + + /* -- START Private Methods -- */ + + /* + Method: getSubElement() + Description: + Get a single subElement within windowEl. Subelements have IDs that are made up of the windowEl ID plus + an element key. e.g., myWindow_content or myWindow_iframe. + Might rename these parentWindow and childElements in the future. + Arguments: + windowEl, subElementKey + Returns: + subElement + */ + + getSubElement: function(windowEl, subElementKey) { + return windowEl.getElementById((windowEl.id + '_' + subElementKey)); + }, + /* + Method: getSubElements() + Description: + Get subElements within windowEl referenced in array subElementsKeys + Arguments: + windowEl, subElementKeys + Returns: + Object, where elements are object.key + */ + getSubElements: function(windowEl, subElementKeys) { + var subElements = {}; + subElementKeys.each(function(key) { + subElements[key] = this.getSubElement(windowEl, key); + }.bind(this)); + return subElements; + }, + /* + Method: setupControlEvents() + Usage: internal + + Arguments: + windowEl + */ + setupEvents: function(windowEl, subElements) { + /*if ( !subElements ) + subElements = this.getSubElements(windowEl, ['closeButton','minimizeButton','maximizeButton']);*/ + + // Set events + // Note: if a button does not exist, its due to properties passed to newWindow() stating otherwice + if ( subElements.closeButton ) + subElements.closeButton.addEvent('click', function() { this.closeWindow(windowEl); }.bind(this)); + + if ( !windowEl.modal ) + windowEl.addEvent('click', function() { this.focusWindow(windowEl); }.bind(this)); + + if ( subElements.minimizeButton ) + subElements.minimizeButton.addEvent('click', function() { this.minimizeWindow(windowEl); }.bind(this)); + + if ( subElements.maximizeButton ) { + subElements.maximizeButton.addEvent('click', function() { + if ( windowEl.isMaximized ) { + this.restoreWindow(windowEl); + subElements.maximizeButton.setProperty('title', 'Maximize'); + } else { + this.maximizeWindow(windowEl); + subElements.maximizeButton.setProperty('title', 'Restore'); + } + }.bind(this)); + } + }, + /* + Method: attachDraggable() + Description: make window draggable + Usage: internal + + Arguments: + windowEl + */ + attachDraggable: function(windowEl, handleEl){ + if ( !windowEl.draggable ) + return; + new Drag.Move(windowEl, { + handle: handleEl, + onStart: function() { + this.focusWindow(windowEl); + if ( windowEl.iframe ) + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'hidden'); + }.bind(this), + onComplete: function() { + if ( windowEl.iframe ) + this.getSubElement(windowEl, 'iframe').setStyle('visibility', 'visible'); + }.bind(this) + }); + }, + /* + Method: attachResizable() + Description: make window resizable + Usage: internal + + Arguments: + windowEl + */ + attachResizable: function(windowEl, subElements){ + if ( !windowEl.resizable ) + return; + subElements.contentWrapper.makeResizable({ + handle: subElements.resizeHandle, + modifiers: { + x: 'width', + y: 'height' + }, + limit: { + x: [this.options.minWidth, this.options.maxWidth], + y: [this.options.minHeight, this.options.maxHeight] + }, + onStart: function() { + this.cacheSubElements = this.getSubElements(windowEl, ['title', 'content', 'canvas', 'contentWrapper', 'overlay', 'titleBar', 'iframe', 'zIndexFix']); + if ( this.cacheSubElements.iframe ) + this.cacheSubElements.iframe.setStyle('visibility', 'hidden'); + }.bind(this), + onDrag: function() { + this.drawWindow(windowEl, this.cacheSubElements); + }.bind(this), + onComplete: function() { + if ( this.cacheSubElements.iframe ) + this.cacheSubElements.iframe.setStyle('visibility', 'visible'); + delete this.cacheSubElements; + this.cacheSubElements = null; + windowEl.onResize(); + }.bind(this) + }); + }, + setDesktopSize: function(){ + var windowDimensions = document.getCoordinates(); + + if ( this.desktop ){ + this.desktop.setStyle('height', windowDimensions.height); + } + + // Set pageWrapper height so the dock doesn't cover the pageWrapper scrollbars. + + if ( this.pageWrapper && this.desktopHeader) { + var pageWrapperHeight = (windowDimensions.height - this.desktopHeader.offsetHeight - (this.dockVisible ? this.dock.offsetHeight : 0)); + if ( pageWrapperHeight < 0 ) { + pageWrapperHeight = 0; + } + this.pageWrapper.setStyle('height', pageWrapperHeight + 'px'); + } + }, + setModalSize: function(){ + $('mochaModalOverlay').setStyle('height', document.getCoordinates().height); + }, + /* + Method: insertWindowElements + Arguments: + windowEl + Returns: + object containing all elements created within [windowEl] + */ + insertWindowElements: function(windowEl, height, width){ + var subElements = {}; + + if (Browser.Engine.trident4){ + subElements.zIndexFix = new Element('iframe', { + 'class': 'zIndexFix', + 'scrolling': 'no', + 'marginWidth': 0, + 'marginHeight': 0, + 'src': '', + 'id': windowEl.id + '_zIndexFix' + }).injectInside(windowEl); + } + + subElements.overlay = new Element('div', { + 'class': 'mochaOverlay', + 'id': windowEl.id + '_overlay' + }).injectInside(windowEl); + + //Insert mochaTitlebar + subElements.titleBar = new Element('div', { + 'class': 'mochaTitlebar', + 'id': windowEl.id + '_titleBar', + 'styles': { + 'cursor': windowEl.draggable ? 'move' : 'default' + } + }).injectTop(subElements.overlay); + + // Create window header + subElements.title = new Element('h3', { + 'class': 'mochaTitle', + 'id': windowEl.id + '_title' + }).injectInside(subElements.titleBar); + + windowEl.contentBorder = new Element('div', { + 'class': 'mochaContentBorder', + 'id': this.options.id + '_contentBorder' + }).injectInside(subElements.overlay); + + subElements.contentWrapper = new Element('div', { + 'class': 'mochaContentWrapper', + 'id': windowEl.id + '_contentWrapper', + 'styles': { + 'width': width + 'px', + 'height': height + 'px' + } + }).injectInside(windowEl.contentBorder); + + subElements.content = new Element('div', { + 'class': 'mochaContent', + 'id': windowEl.id + '_content' + }).injectInside(subElements.contentWrapper); + + //Insert canvas + subElements.canvas = new Element('canvas', { + 'class': 'mochaCanvas', + 'width': 1, + 'height': 1, + 'id': windowEl.id + '_canvas' + }).injectInside(windowEl); + + // Dynamically initialize canvas using excanvas. This is only required by IE + if ( Browser.Engine.trident && this.ieSupport == 'excanvas' ) { + G_vmlCanvasManager.initElement(subElements.canvas); + // This is odd, .getContext() method does not exist before retrieving the + // element via getElement + subElements.canvas = windowEl.getElement('.mochaCanvas'); + } + + //Insert resize handles + if (windowEl.resizable){ + subElements.resizeHandle = new Element('div', { + 'class': 'resizeHandle', + 'id': windowEl.id + '_resizeHandle' + }).injectAfter(subElements.overlay); + + if ( Browser.Engine.trident ) + subElements.resizeHandle.setStyle('zIndex', 2); + } + + //Insert mochaTitlebar controls + subElements.controls = new Element('div', { + 'class': 'mochaControls', + 'id': windowEl.id + '_controls' + }).injectAfter(subElements.overlay); + + //Insert close button + if (windowEl.closable){ + subElements.closeButton = new Element('div', { + 'class': 'mochaClose', + 'title': 'Close Window', + 'id': windowEl.id + '_closeButton' + }).injectInside(subElements.controls); + } + + //Insert maximize button + if (windowEl.maximizable){ + subElements.maximizeButton = new Element('div', { + 'class': 'maximizeToggle', + 'title': 'Maximize', + 'id': windowEl.id + '_maximizeButton' + }).injectInside(subElements.controls); + } + //Insert minimize button + if (windowEl.minimizable){ + subElements.minimizeButton = new Element('div', { + 'class': 'minimizeToggle', + 'title': 'Minimize', + 'id': windowEl.id + '_minimizeButton' + }).injectInside(subElements.controls); + } + + //Insert canvas + subElements.canvasIcon = new Element('canvas', { + 'class': 'mochaLoadingIcon', + 'width': 18, + 'height': 18, + 'id': windowEl.id + '_canvasIcon' + }).injectBottom(windowEl); + + // Dynamically initialize canvas using excanvas. This is only required by IE + if (Browser.Engine.trident && this.ieSupport == 'excanvas') { + G_vmlCanvasManager.initElement(subElements.canvasIcon); + // This is odd, .getContext() method does not exist before retrieving the + // element via getElement + subElements.canvasIcon = windowEl.getElement('.mochaLoadingIcon'); + } + + if ( Browser.Engine.trident ) { + subElements.controls.setStyle('zIndex', 2) + subElements.overlay.setStyle('zIndex', 2) + } + + // For Mac Firefox 2 to help reduce scrollbar bugs in that browser + if (Browser.Platform.mac && Browser.Engine.gecko) + subElements.overlay.setStyle('overflow', 'auto'); + this.setMochaControlsWidth(windowEl, subElements); + return subElements; + }, + /* + + Method: drawWindow + + Arguments: + windowEl: the $(window) + subElements: children of $(window) + shadows: (boolean) false will draw a window without shadows + + Notes: This is where we create the canvas GUI + + */ + drawWindow: function(windowEl, subElements, shadows) { + if ( !subElements ) { + subElements = this.getSubElements(windowEl, ['title', 'content', 'canvas', 'contentWrapper', 'overlay', 'titleBar', 'iframe', 'zIndexFix']); + } + + windowEl.contentBorder.setStyles({ + 'width': subElements.contentWrapper.offsetWidth + }); + + // Resize iframe when window is resized + if ( windowEl.iframe ) { + subElements.iframe.setStyles({ + 'height': subElements.contentWrapper.offsetHeight + }); + } + + var mochaHeight = subElements.contentWrapper.getStyle('height').toInt() + this.HeaderFooterShadow; + var mochaWidth = subElements.contentWrapper.getStyle('width').toInt() + this.shadowOffset; + + subElements.overlay.setStyle('height', mochaHeight); + windowEl.setStyle('height', mochaHeight); + + // If opera height and width must be set like this, when resizing: + subElements.canvas.height = Browser.Engine.webkit ? 4000 : mochaHeight; + subElements.canvas.width = Browser.Engine.webkit ? 2000 : mochaWidth; + + // Part of the fix for IE6 select z-index bug and FF on Mac scrollbar z-index bug + if ( Browser.Engine.trident4 ){ + subElements.zIndexFix.setStyles({ + 'width': mochaWidth, + 'height': mochaHeight + }) + } + + // Set width + windowEl.setStyle('width', mochaWidth); + subElements.overlay.setStyle('width', mochaWidth); + subElements.titleBar.setStyles({ + 'width': mochaWidth - this.shadowOffset, + 'height': this.options.headerHeight + }); + + // Draw shapes + var ctx = subElements.canvas.getContext('2d'); + var dimensions = document.getCoordinates(); + ctx.clearRect(0, 0, dimensions.width, dimensions.height); + + // This is the drop shadow. It is created onion style with three layers + if ( shadows != false ) { + this.roundedRect(ctx, 0, 0, mochaWidth, mochaHeight, this.options.cornerRadius, [0, 0, 0], 0.06); + this.roundedRect(ctx, 1, 1, mochaWidth - 2, mochaHeight - 2, this.options.cornerRadius, [0, 0, 0], 0.08); + this.roundedRect(ctx, 2, 2, mochaWidth - 4, mochaHeight - 4, this.options.cornerRadius, [0, 0, 0], 0.3); + } + + // Mocha body + this.bodyRoundedRect( + ctx, // context + 3, // x + 2, // y + mochaWidth - this.shadowOffset, // width + mochaHeight - this.shadowOffset, // height + this.options.cornerRadius, // corner radius + windowEl.footerBgColor // Footer color + ); + + // Mocha header + this.topRoundedRect( + ctx, // context + 3, // x + 2, // y + mochaWidth - this.shadowOffset, // width + this.options.headerHeight, // height + this.options.cornerRadius, // corner radius + windowEl.headerStartColor, // Header gradient's top color + windowEl.headerStopColor // Header gradient's bottom color + ); + + // Calculate X position for controlbuttons + this.closebuttonX = mochaWidth - (windowEl.closable ? 15 : -4); + this.maximizebuttonX = this.closebuttonX - (windowEl.maximizable ? 19 : 0); + this.minimizebuttonX = this.maximizebuttonX - (windowEl.minimizable ? 19 : 0); + + if ( windowEl.closable ) + this.closebutton(ctx, this.closebuttonX, 15, windowEl.closeColor, 1.0); + if ( windowEl.maximizable ) + this.maximizebutton(ctx, this.maximizebuttonX, 15, windowEl.maximizeColor, 1.0); + if ( windowEl.minimizable ) + this.minimizebutton(ctx, this.minimizebuttonX, 15, windowEl.minimizeColor, 1.0); // Minimize + if ( windowEl.resizable ) + this.triangle(ctx, mochaWidth - 20, mochaHeight - 20, 12, 12, windowEl.resizableColor, 1.0); // Resize handle + + // Invisible dummy object. The last element drawn is not rendered consistently while resizing in IE6 and IE7. + this.triangle(ctx, 0, 0, 10, 10, windowEl.resizableColor, 0); + + }, + // Window body + bodyRoundedRect: function(ctx, x, y, width, height, radius, rgb){ + ctx.fillStyle = 'rgba(' + rgb.join(',') + ', 100)'; + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y + height - radius); + ctx.quadraticCurveTo(x, y + height, x + radius, y + height); + ctx.lineTo(x + width - radius, y + height); + ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); + ctx.lineTo(x + width, y + radius); + ctx.quadraticCurveTo(x + width, y, x + width - radius, y); + ctx.lineTo(x + radius, y); + ctx.quadraticCurveTo(x, y, x, y + radius); + ctx.fill(); + }, + roundedRect: function(ctx, x, y, width, height, radius, rgb, a){ + ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y + height - radius); + ctx.quadraticCurveTo(x, y + height, x + radius, y + height); + ctx.lineTo(x + width - radius, y + height); + ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); + ctx.lineTo(x + width, y + radius); + ctx.quadraticCurveTo(x + width, y, x + width - radius, y); + ctx.lineTo(x + radius, y); + ctx.quadraticCurveTo(x, y, x, y + radius); + ctx.fill(); + }, + // Window header with gradient background + topRoundedRect: function(ctx, x, y, width, height, radius, headerStartColor, headerStopColor){ + + // Create gradient + if (Browser.Engine.presto != null ){ + var lingrad = ctx.createLinearGradient(0, 0, 0, this.options.headerHeight + 2); + } + else { + var lingrad = ctx.createLinearGradient(0, 0, 0, this.options.headerHeight); + } + + lingrad.addColorStop(0, 'rgba(' + headerStartColor.join(',') + ', 100)'); + lingrad.addColorStop(1, 'rgba(' + headerStopColor.join(',') + ', 100)'); + ctx.fillStyle = lingrad; + + // Draw header + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x, y + height); + ctx.lineTo(x + width, y + height); + ctx.lineTo(x + width, y + radius); + ctx.quadraticCurveTo(x + width, y, x + width - radius, y); + ctx.lineTo(x + radius, y); + ctx.quadraticCurveTo(x, y, x, y + radius); + ctx.fill(); + }, + // Resize handle + triangle: function(ctx, x, y, width, height, rgb, a){ + ctx.beginPath(); + ctx.moveTo(x + width, y); + ctx.lineTo(x, y + height); + ctx.lineTo(x + width, y + height); + ctx.closePath(); + ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; + ctx.fill(); + }, + drawCircle: function(ctx, x, y, diameter, rgb, a){ + // Circle + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.arc(x, y, diameter, 0, Math.PI*2, true); + ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; + ctx.fill(); + }, + maximizebutton: function(ctx, x, y, rgb, a){ // This could reuse the drawCircle method above + // Circle + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.arc(x, y, 7, 0, Math.PI*2, true); + ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; + ctx.fill(); + // X sign + ctx.beginPath(); + ctx.moveTo(x, y - 4); + ctx.lineTo(x, y + 4); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(x - 4, y); + ctx.lineTo(x + 4, y); + ctx.stroke(); + }, + closebutton: function(ctx, x, y, rgb, a){ // This could reuse the drawCircle method above + // Circle + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.arc(x, y, 7, 0, Math.PI*2, true); + ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; + ctx.fill(); + // Plus sign + ctx.beginPath(); + ctx.moveTo(x - 3, y - 3); + ctx.lineTo(x + 3, y + 3); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(x + 3, y - 3); + ctx.lineTo(x - 3, y + 3); + ctx.stroke(); + }, + minimizebutton: function(ctx, x, y, rgb, a){ // This could reuse the drawCircle method above + // Circle + ctx.beginPath(); + ctx.moveTo(x,y); + ctx.arc(x,y,7,0,Math.PI*2,true); + ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')'; + ctx.fill(); + // Minus sign + ctx.beginPath(); + ctx.moveTo(x - 4, y); + ctx.lineTo(x + 4, y); + ctx.stroke(); + }, + hideLoadingIcon: function(canvas) { + $(canvas).setStyle('display', 'none'); + $clear(canvas.iconAnimation); + }, + /* + + Method: showLoadingIcon + + */ + showLoadingIcon: function(canvas) { + $(canvas).setStyles({ + 'display': 'block' + }); + var t = 1; + var iconAnimation = function(canvas){ + var ctx = $(canvas).getContext('2d'); + ctx.clearRect(0, 0, 18, 18); // Clear canvas + ctx.save(); + ctx.translate(9, 9); + ctx.rotate(t*(Math.PI / 8)); + var color = 0; + for (i=0; i < 8; i++){ // Draw individual dots + color = Math.floor(255 / 8 * i); + ctx.fillStyle = "rgb(" + color + "," + color + "," + color + ")"; + ctx.rotate(-Math.PI / 4); + ctx.beginPath(); + ctx.arc(0, 7, 2, 0, Math.PI*2, true); + ctx.fill(); + } + ctx.restore(); + t++; + }.bind(this); + canvas.iconAnimation = iconAnimation.periodical(125, this, canvas); + }, + setMochaControlsWidth: function(windowEl, subElements){ + var controlWidth = 14; + var marginWidth = 5; + this.mochaControlsWidth = 0; + if ( windowEl.minimizable ) + this.mochaControlsWidth += (marginWidth + controlWidth); + if ( windowEl.maximizable ) { + this.mochaControlsWidth += (marginWidth + controlWidth); + subElements.maximizeButton.setStyle('margin-left', marginWidth); + } + if ( windowEl.closable ) { + this.mochaControlsWidth += (marginWidth + controlWidth); + subElements.closeButton.setStyle('margin-left', marginWidth); + } + subElements.controls.setStyle('width', this.mochaControlsWidth); + }, + initializeDock: function (){ + this.dock.setStyles({ + 'display': 'block', + 'position': 'absolute', + 'top': null, + 'bottom': 0, + 'left': 0 + }); + // Probably: this event should be added/removed when toggling AutoHide, since we dont need it when AutoHide is turned off + // this.dockVisible tracks the status of the dock, so that showing/hiding is not done when not needed + document.addEvent('mousemove', function(event) { + if ( !this.dockAutoHide ) + return; + var ev = new Event(event); + if ( ev.client.y > (document.getCoordinates().height - 25) ) { + if ( !this.dockVisible ) { + this.dock.setStyle('display', 'block'); + this.dockVisible = true; + this.setDesktopSize(); + } + } else { + if ( this.dockVisible ) { + this.dock.setStyle('display', 'none'); + this.dockVisible = false; + this.setDesktopSize(); + } + } + }.bind(this)); + + // Insert canvas + var canvas = new Element('canvas', { + 'class': 'mochaCanvas', + 'id': 'dockCanvas', + 'width': '15', + 'height': '18' + }).injectInside(this.dock).setStyles({ + position: 'absolute', + top: '4px', + left: '2px', + zIndex: 2 + }); + + // Dynamically initialize canvas using excanvas. This is only required by IE + if (Browser.Engine.trident && this.ieSupport == 'excanvas') { + G_vmlCanvasManager.initElement(canvas); + } + + // Position top or bottom selector + $('mochaDockPlacement').setProperty('title','Position Dock Top'); + + // Auto Hide toggle switch + $('mochaDockAutoHide').setProperty('title','Turn Auto Hide On'); + + // Attach event + $('mochaDockPlacement').addEvent('click', function(event){ + var ctx = this.dock.getElement('.mochaCanvas').getContext('2d'); + + // Move dock to top position + if (this.dock.getStyle('position') != 'relative'){ + this.dock.setStyles({ + 'position': 'relative', + 'bottom': null, + 'border-top': '1px solid #fff', + 'border-bottom': '1px solid #bbb' + }) + this.setDesktopSize(); + this.dock.setProperty('dockPosition','Top'); + this.drawCircle(ctx, 5, 4, 3, [0, 255, 0], 1.0); // green + this.drawCircle(ctx, 5, 14, 3, [212, 208, 200], 1.0); // gray + $('mochaDockPlacement').setProperty('title', 'Position Dock Bottom'); + $('mochaDockAutoHide').setProperty('title', 'Auto Hide Disabled in Top Dock Position'); + this.dockAutoHide = false; + } + // Move dock to bottom position + else { + this.dock.setStyles({ + 'position': 'absolute', + 'bottom': 0, + 'border-top': '1px solid #bbb', + 'border-bottom': '1px solid #fff' + }) + this.setDesktopSize(); + this.dock.setProperty('dockPosition','Bottom'); + this.drawCircle(ctx, 5, 4, 3, [241, 102, 116], 1.0); // orange + this.drawCircle(ctx, 5 , 14, 3, [241, 102, 116], 1.0); // orange + $('mochaDockPlacement').setProperty('title', 'Position Dock Top'); + $('mochaDockAutoHide').setProperty('title', 'Turn Auto Hide On'); + } + + }.bind(this)); + + // Attach event Auto Hide + $('mochaDockAutoHide').addEvent('click', function(event){ + if ( this.dock.getProperty('dockPosition') == 'Top' ) + return false; + + var ctx = this.dock.getElement('.mochaCanvas').getContext('2d'); + this.dockAutoHide = !this.dockAutoHide; // Toggle + if ( this.dockAutoHide ) { + $('mochaDockAutoHide').setProperty('title', 'Turn Auto Hide Off'); + this.drawCircle(ctx, 5 , 14, 3, [0, 255, 0], 1.0); // green + } else { + $('mochaDockAutoHide').setProperty('title', 'Turn Auto Hide On'); + this.drawCircle(ctx, 5 , 14, 3, [241, 102, 116], 1.0); // orange + } + }.bind(this)); + + this.drawDock(this.dock); + }, + drawDock: function (el){ + var ctx = el.getElement('.mochaCanvas').getContext('2d'); + this.drawCircle(ctx, 5 , 4, 3, [241, 102, 116], 1.0); // orange + this.drawCircle(ctx, 5 , 14, 3, [241, 102, 116], 1.0); // orange + }, + dynamicResize: function (windowEl){ + this.getSubElement(windowEl, 'contentWrapper').setStyle('height', this.getSubElement(windowEl, 'content').offsetHeight); + this.drawWindow(windowEl); + }, + /* + + Method: arrangeCascade + + */ + arrangeCascade: function(){ + var x = this.options.desktopLeftOffset + var y = this.options.desktopTopOffset; + $$('div.mocha').each(function(windowEl){ + if (!windowEl.isMinimized && !windowEl.isMaximized){ + this.focusWindow(windowEl); + x += this.options.mochaLeftOffset; + y += this.options.mochaTopOffset; + + if (this.options.effects == false){ + windowEl.setStyles({ + 'top': y, + 'left': x + }); + } + else { + var cascadeMorph = new Fx.Morph(windowEl, { + 'duration': 550 + }); + cascadeMorph.start({ + 'top': y, + 'left': x + }); + } + } + }.bind(this)); + }, + /* + + Method: garbageCleanup + + Notes: Empties an all windows of their children, removes and garbages the windows. + It is does not trigger onClose() or onCloseComplete(). + This is useful to clear memory before the pageUnload. + + */ + garbageCleanUp: function() { + $$('div.mocha').each(function(el) { + el.destroy(); + }.bind(this)); + } +}); +MochaUI.implement(new Options); + +/* ----------------------------------------------------------------- + + MOCHA SCREENS + Notes: This class can be removed if you are not creating multiple screens/workspaces. + + ----------------------------------------------------------------- */ + +var MochaScreens = new Class({ + options: { + defaultScreen: 0 // Default screen + }, + initialize: function(options){ + this.setOptions(options); + this.setScreen(this.options.defaultScreen); + }, + setScreen: function(index) { + if ( !$('mochaScreens') ) + return; + $$('#mochaScreens div.screen').each(function(el,i) { + el.setStyle('display', i == index ? 'block' : 'none'); + }); + } +}); +MochaScreens.implement(new Options); + +/* ----------------------------------------------------------------- + + MOCHA WINDOW FROM FORM + Notes: This class can be removed if you are not creating new windows dynamically from a form. + + ----------------------------------------------------------------- */ + +var MochaWindowForm = new Class({ + options: { + id: null, + title: 'New Window', + loadMethod: 'html', // html, xhr, or iframe + content: '', // used if loadMethod is set to 'html' + contentURL: 'pages/lipsum.html', // used if loadMethod is set to 'xhr' or 'iframe' + modal: false, + width: 300, + height: 125, + scrollbars: true, // true sets the overflow to auto and false sets it to hidden + x: null, // if x or y is null or modal is false the new window is centered in the browser window + y: null, + paddingVertical: 10, + paddingHorizontal: 12 + }, + initialize: function(options){ + this.setOptions(options); + this.options.id = 'win' + (++document.mochaUI.windowIDCount); + this.options.title = $('mochaNewWindowHeaderTitle').value; + if ($('htmlLoadMethod').checked){ + this.options.loadMethod = 'html'; + } + if ($('xhrLoadMethod').checked){ + this.options.loadMethod = 'xhr'; + } + if ($('iframeLoadMethod').checked){ + this.options.loadMethod = 'iframe'; + } + this.options.content = $('mochaNewWindowContent').value; + if ($('mochaNewWindowContentURL').value){ + this.options.contentURL = $('mochaNewWindowContentURL').value; + } + if ($('mochaNewWindowModal').checked) { + this.options.modal = true; + } + this.options.width = $('mochaNewWindowWidth').value.toInt(); + this.options.height = $('mochaNewWindowHeight').value.toInt(); + this.options.x = $('mochaNewWindowX').value.toInt(); + this.options.y = $('mochaNewWindowY').value.toInt(); + this.options.paddingVertical = $('mochaNewWindowPaddingVertical').value.toInt(); + this.options.paddingHorizontal = $('mochaNewWindowPaddingHorizontal').value.toInt(); + document.mochaUI.newWindow(this.options); + } +}); +MochaWindowForm.implement(new Options); + + +/* ----------------------------------------------------------------- + + Corner Radius Slider + Notes: Remove this function and it's reference in onload if you are not + using the example corner radius slider + + ----------------------------------------------------------------- */ + +function addSlider(){ + if ($('sliderarea')) { + mochaSlide = new Slider($('sliderarea'), $('sliderknob'), { + steps: 20, + offset: 5, + onChange: function(pos){ + $('updatevalue').setHTML(pos); + document.mochaUI.options.cornerRadius = pos; + $$('div.mocha').each(function(windowEl, i) { + document.mochaUI.drawWindow(windowEl); + }); + document.mochaUI.indexLevel++; + } + }).set(document.mochaUI.options.cornerRadius); + } +} + +/* ----------------------------------------------------------------- + + Initialize Everything onLoad + + ----------------------------------------------------------------- */ + +window.addEvent('domready', function(){ + document.mochaScreens = new MochaScreens(); + document.mochaUI = new MochaUI(); + attachMochaLinkEvents(); // See mocha-events.js + addSlider(); // remove this if you remove the example corner radius slider +}); + +// This runs when a person leaves your page. +window.addEvent('unload', function(){ + if (document.mochaUI) document.mochaUI.garbageCleanUp(); +}); diff --git a/src/webui/scripts/mootools-trunk-1475.js b/src/webui/scripts/mootools-trunk-1475.js new file mode 100644 index 000000000..913730d06 --- /dev/null +++ b/src/webui/scripts/mootools-trunk-1475.js @@ -0,0 +1,504 @@ +//MooTools, My Object Oriented Javascript Tools. Copyright (c) 2006-2007 Valerio Proietti, , MIT Style License. + +var MooTools={version:"1.2dev",build:"1475"};var Native=function(J){J=J||{};var F=J.afterImplement||function(){};var G=J.generics;G=(G!==false);var H=J.legacy; +var E=J.initialize;var B=J.protect;var A=J.name;var C=E||H;C.constructor=Native;C.$family={name:"native"};if(H&&E){C.prototype=H.prototype;}C.prototype.constructor=C; +if(A){var D=A.toLowerCase();C.prototype.$family={name:D};Native.typize(C,D);}var I=function(M,K,N,L){if(!B||L||!M.prototype[K]){M.prototype[K]=N;}if(G){Native.genericize(M,K,B); +}F.call(M,K,N);return M;};C.implement=function(L,K,N){if(typeof L=="string"){return I(this,L,K,N);}for(var M in L){I(this,M,L[M],K);}return this;};C.alias=function(K,M,L){K=this.prototype[K]; +if(K){I(this,M,K,L);}return this;};return C;};Native.implement=function(D,C){for(var B=0,A=D.length;B-1:this.indexOf(A)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,"");},clean:function(){return this.replace(/\s+/g," ").trim(); +},camelCase:function(){return this.replace(/-\D/g,function(A){return A.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(A){return("-"+A.charAt(0).toLowerCase()); +});},capitalize:function(){return this.replace(/\b[a-z]/g,function(A){return A.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1"); +},toInt:function(A){return parseInt(this,A||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(B){var A=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); +return(A)?A.slice(1).hexToRgb(B):null;},rgbToHex:function(B){var A=this.match(/\d{1,3}/g);return(A)?A.rgbToHex(B):null;},stripScripts:function(B){var A=""; +var C=this.replace(/]*>([\s\S]*?)<\/script>/gi,function(){A+=arguments[1]+"\n";return"";});if(B===true){$exec(A);}else{if($type(B)=="function"){B(A,C); +}}return C;},substitute:function(A,B){return this.replace(B||/\\?\{([^}]+)\}/g,function(D,C){if(D.charAt(0)=="\\"){return D.slice(1);}return(A[C]!=undefined)?A[C]:""; +});}});Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(B){for(var A in this){if(this.hasOwnProperty(A)&&this[A]===B){return A;}}return null; +},hasValue:function(A){return(Hash.keyOf(this,A)!==null);},extend:function(A){Hash.each(A,function(C,B){Hash.set(this,B,C);},this);return this;},merge:function(A){Hash.each(A,function(C,B){Hash.include(this,B,C); +},this);return this;},erase:function(A){if(this.hasOwnProperty(A)){delete this[A];}return this;},get:function(A){return(this.hasOwnProperty(A))?this[A]:null; +},set:function(A,B){if(!this[A]||this.hasOwnProperty(A)){this[A]=B;}return this;},empty:function(){Hash.each(this,function(B,A){delete this[A];},this); +return this;},include:function(B,C){var A=this[B];if(!$defined(A)){this[B]=C;}return this;},map:function(B,C){var A=new Hash;Hash.each(this,function(E,D){A.set(D,B.call(C,E,D,this)); +},this);return A;},filter:function(B,C){var A=new Hash;Hash.each(this,function(E,D){if(B.call(C,E,D,this)){A.set(D,E);}},this);return A;},every:function(B,C){for(var A in this){if(this.hasOwnProperty(A)&&!B.call(C,this[A],A)){return false; +}}return true;},some:function(B,C){for(var A in this){if(this.hasOwnProperty(A)&&B.call(C,this[A],A)){return true;}}return false;},getKeys:function(){var A=[]; +Hash.each(this,function(C,B){A.push(B);});return A;},getValues:function(){var A=[];Hash.each(this,function(B){A.push(B);});return A;},toQueryString:function(){var A=[]; +Hash.each(this,function(C,B){$splat(C).each(function(D){A.push(B+"="+encodeURIComponent(D));});});return A.join("&");}});Hash.alias("keyOf","indexOf").alias("hasValue","contains"); +Hash.alias("getKeys","keys").alias("getValues","values").alias("has","hasKey");var Abstract=Hash;var Event=new Native({name:"Event",initialize:function(A,F){F=F||window; +A=A||F.event;if(A.$extended){return A;}this.$extended=true;var J=A.type;var G=A.target||A.srcElement;while(G&&G.nodeType==3){G=G.parentNode;}if(J.test(/key/)){var B=A.which||A.keyCode; +var L=Event.Keys.keyOf(B);if(J=="keydown"){var D=B-111;if(D>0&&D<13){L="f"+D;}}L=L||String.fromCharCode(B).toLowerCase();}else{if(J.match(/(click|mouse|menu)/i)){var I={x:A.pageX||A.clientX+F.document.documentElement.scrollLeft,y:A.pageY||A.clientY+F.document.documentElement.scrollTop}; +var C={x:A.pageX?A.pageX-F.pageXOffset:A.clientX,y:A.pageY?A.pageY-F.pageYOffset:A.clientY};if(J.match(/DOMMouseScroll|mousewheel/)){var H=(A.wheelDelta)?A.wheelDelta/120:-(A.detail||0)/3; +}var E=(A.which==3)||(A.button==2);var K=null;if(J.match(/over|out/)){switch(J){case"mouseover":K=A.relatedTarget||A.fromElement;break;case"mouseout":K=A.relatedTarget||A.toElement; +}if(!(function(){while(K&&K.nodeType==3){K=K.parentNode;}return true;}).create({attempt:Browser.Engine.gecko})()){K=false;}}}}return $extend(this,{event:A,type:J,page:I,client:C,rightClick:E,wheel:H,relatedTarget:K,target:G,code:B,key:L,shift:A.shiftKey,control:A.ctrlKey,alt:A.altKey,meta:A.metaKey}); +}});Event.Keys=new Hash({enter:13,up:38,down:40,left:37,right:39,esc:27,space:32,backspace:8,tab:9,"delete":46});Event.implement({stop:function(){return this.stopPropagation().preventDefault(); +},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault(); +}else{this.event.returnValue=false;}return this;}});var Class=new Native({name:"Class",initialize:function(B){B=B||{};var A=function(){for(var D in this){this[D]=$unlink(this[D]); +}this.parent=null;for(var E in Class.Mutators){if(!this[E]){continue;}Class.Mutators[E](this,this[E]);delete this[E];}this.constructor=A;var C=(arguments[0]!==$empty&&this.initialize)?this.initialize.apply(this,arguments):this; +if(this.options&&this.options.initialize){this.options.initialize.call(this);}return C;};$extend(A,this);A.constructor=Class;A.prototype=B;return A;}}); +Class.implement({implement:function(){Class.Mutators.Implements(this.prototype,Array.slice(arguments));return this;}});Class.Mutators={};Class.Mutators.Implements=function(A,B){$splat(B).each(function(C){$extend(A,($type(C)=="class")?new C($empty):C); +});};Class.Mutators.Extends=function(C,A){A=new A($empty);for(var E in A){var B=A[E];var D=C[E];C[E]=(function(G,H){if($defined(H)&&G!=H){var F=$type(H); +if(F!=$type(G)){return H;}switch(F){case"function":return function(){H.parent=this.parent=G.bind(this);var I=H.apply(this,arguments);this.parent=H.parent; +return I;};case"object":return $merge(G,H);default:return H;}}return G;})(B,D);}};Class.empty=$empty;Class.prototype.extend=function(A){A.Extends=this; +return new Class(A);};var Chain=new Class({chain:function(){this.$chain=(this.$chain||[]).extend(arguments);return this;},callChain:function(){return(this.$chain&&this.$chain.length)?this.$chain.shift().apply(this,arguments):false; +},clearChain:function(){if(this.$chain){this.$chain.empty();}return this;}});var Events=new Class({addEvent:function(C,B,A){if(B!=$empty){this.$events=this.$events||{}; +this.$events[C]=this.$events[C]||[];this.$events[C].include(B);if(A){B.internal=true;}}return this;},addEvents:function(A){for(var B in A){this.addEvent(B,A[B]); +}return this;},fireEvent:function(C,B,A){if(!this.$events||!this.$events[C]){return this;}this.$events[C].each(function(D){D.create({bind:this,delay:A,"arguments":B})(); +},this);return this;},removeEvent:function(B,A){if(!this.$events||!this.$events[B]){return this;}if(!A.internal){this.$events[B].erase(A);}return this; +},removeEvents:function(C){for(var D in this.$events){if(C&&C!=D){continue;}var B=this.$events[D];for(var A=B.length;A--;A){this.removeEvent(D,B[A]);}}return this; +}});var Options=new Class({setOptions:function(){this.options=$merge.run([this.options].extend(arguments));if(!this.addEvent){return this;}for(var A in this.options){if($type(this.options[A])!="function"||!(/^on[A-Z]/).test(A)){continue; +}this.addEvent(A,this.options[A]);delete this.options[A];}return this;}});Document.implement({newElement:function(A,B){if(Browser.Engine.trident&&B){["name","type","checked"].each(function(C){if(!B[C]){return ; +}A+=" "+C+'="'+B[C]+'"';if(C!="checked"){delete B[C];}});A="<"+A+">";}return $.element(this.createElement(A)).set(B);},newTextNode:function(A){return this.createTextNode(A); +},getDocument:function(){return this;},getWindow:function(){return this.defaultView||this.parentWindow;},purge:function(){var C=this.getElementsByTagName("*"); +for(var B=0,A=C.length;B1);A.each(function(E){var F=this.getElementsByTagName(E.trim());(B)?C.extend(F):C=F;},this);return new Elements(C,{ddup:B,cash:!D}); +}});Element.Storage={get:function(A){return(this[A]||(this[A]={}));}};Element.Inserters=new Hash({before:function(B,A){if(A.parentNode){A.parentNode.insertBefore(B,A); +}},after:function(B,A){if(!A.parentNode){return ;}var C=A.nextSibling;(C)?A.parentNode.insertBefore(B,C):A.parentNode.appendChild(B);},bottom:function(B,A){A.appendChild(B); +},top:function(B,A){var C=A.firstChild;(C)?A.insertBefore(B,C):A.appendChild(B);}});Element.Inserters.inside=Element.Inserters.bottom;Element.Inserters.each(function(C,B){var A=B.capitalize(); +Element.implement("inject"+A,function(D){Element.Inserters[B](this,$(D,true));return this;});Element.implement("grab"+A,function(D){Element.Inserters[B]($(D,true),this); +return this;});});Element.implement({getDocument:function(){return this.ownerDocument;},getWindow:function(){return this.ownerDocument.getWindow();},getElementById:function(D,C){var B=this.ownerDocument.getElementById(D); +if(!B){return null;}for(var A=B.parentNode;A!=this;A=A.parentNode){if(!A){return null;}}return $.element(B,C);},set:function(D,B){switch($type(D)){case"object":for(var C in D){this.set(C,D[C]); +}break;case"string":var A=Element.Properties.get(D);(A&&A.set)?A.set.apply(this,Array.slice(arguments,1)):this.setProperty(D,B);}return this;},get:function(B){var A=Element.Properties.get(B); +return(A&&A.get)?A.get.apply(this,Array.slice(arguments,1)):this.getProperty(B);},erase:function(B){var A=Element.Properties.get(B);(A&&A.erase)?A.erase.apply(this,Array.slice(arguments,1)):this.removeProperty(B); +return this;},match:function(A){return(!A||Element.get(this,"tag")==A);},inject:function(B,A){Element.Inserters.get(A||"bottom")(this,$(B,true));return this; +},wraps:function(B,A){B=$(B,true);return this.replaces(B).grab(B);},grab:function(B,A){Element.Inserters.get(A||"bottom")($(B,true),this);return this;},appendText:function(B,A){return this.grab(this.getDocument().newTextNode(B),A); +},adopt:function(){Array.flatten(arguments).each(function(A){A=$(A,true);if(A){this.appendChild(A);}},this);return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this; +},clone:function(D,C){switch($type(this)){case"element":var H={};for(var G=0,E=this.attributes.length;G1),cash:!G});}});Element.implement({match:function(B){if(!B){return true;}var D=Selectors.Utils.parseTagAndID(B); +var A=D[0],E=D[1];if(!Selectors.Filters.byID(this,E)||!Selectors.Filters.byTag(this,A)){return false;}var C=Selectors.Utils.parseSelector(B);return(C)?Selectors.Utils.filter(this,C,{}):true; +}});var Selectors={Cache:{nth:{},parsed:{}}};Selectors.RegExps={id:(/#([\w-]+)/),tag:(/^(\w+|\*)/),quick:(/^(\w+|\*)$/),splitter:(/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),combined:(/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)["']?(.*?)["']?)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)}; +Selectors.Utils={chk:function(B,C){if(!C){return true;}var A=$uid(B);if(!C[A]){return C[A]=true;}return false;},parseNthArgument:function(F){if(Selectors.Cache.nth[F]){return Selectors.Cache.nth[F]; +}var C=F.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);if(!C){return false;}var E=parseInt(C[1]);var B=(E||E===0)?E:1;var D=C[2]||false;var A=parseInt(C[3])||0; +if(B!=0){A--;while(A<1){A+=B;}while(A>=B){A-=B;}}else{B=A;D="index";}switch(D){case"n":C={a:B,b:A,special:"n"};break;case"odd":C={a:2,b:0,special:"n"}; +break;case"even":C={a:2,b:1,special:"n"};break;case"first":C={a:0,special:"index"};break;case"last":C={special:"last-child"};break;case"only":C={special:"only-child"}; +break;default:C={a:(B-1),special:"index"};}return Selectors.Cache.nth[F]=C;},parseSelector:function(E){if(Selectors.Cache.parsed[E]){return Selectors.Cache.parsed[E]; +}var D,H={classes:[],pseudos:[],attributes:[]};while((D=Selectors.RegExps.combined.exec(E))){var I=D[1],G=D[2],F=D[3],B=D[4],C=D[5],J=D[6];if(I){H.classes.push(I); +}else{if(C){var A=Selectors.Pseudo.get(C);if(A){H.pseudos.push({parser:A,argument:J});}else{H.attributes.push({name:C,operator:"=",value:J});}}else{if(G){H.attributes.push({name:G,operator:F,value:B}); +}}}}if(!H.classes.length){delete H.classes;}if(!H.attributes.length){delete H.attributes;}if(!H.pseudos.length){delete H.pseudos;}if(!H.classes&&!H.attributes&&!H.pseudos){H=null; +}return Selectors.Cache.parsed[E]=H;},parseTagAndID:function(B){var A=B.match(Selectors.RegExps.tag);var C=B.match(Selectors.RegExps.id);return[(A)?A[1]:"*",(C)?C[1]:false]; +},filter:function(F,C,E){var D;if(C.classes){for(D=C.classes.length;D--;D){var G=C.classes[D];if(!Selectors.Filters.byClass(F,G)){return false;}}}if(C.attributes){for(D=C.attributes.length; +D--;D){var B=C.attributes[D];if(!Selectors.Filters.byAttribute(F,B.name,B.operator,B.value)){return false;}}}if(C.pseudos){for(D=C.pseudos.length;D--;D){var A=C.pseudos[D]; +if(!Selectors.Filters.byPseudo(F,A.parser,A.argument,E)){return false;}}}return true;},getByTagAndID:function(B,A,D){if(D){var C=B.getElementById(D,true); +return(C&&Selectors.Filters.byTag(C,A))?[C]:[];}else{return B.getElementsByTagName(A);}},search:function(J,I,O){var B=[];var C=I.trim().replace(Selectors.RegExps.splitter,function(Z,Y,X){B.push(Y); +return":)"+X;}).split(":)");var K,F,E,V;for(var U=0,Q=C.length;U":function(H,G,I,A,F){var C=Selectors.Utils.getByTagAndID(G,I,A);for(var E=0,D=C.length;EA){return false; +}}return(C==A);},even:function(B,A){return Selectors.Pseudo["nth-child"].call(this,"2n+1",A);},odd:function(B,A){return Selectors.Pseudo["nth-child"].call(this,"2n",A); +}});Native.implement([Element,Document],{getElementsByClassName:function(A){return this.getElements("."+A);},getElementsBySelector:function(A){return this.getElements(A); +}});Elements.implement({filterByTag:function(A){return this.filterBy(A);},filterByClass:function(A){return this.filterBy("."+A);},filterById:function(A){return this.filterBy("#"+A); +},filterByAttribute:function(B,A,C){return this.filterBy("["+B+(A||"")+(C||"")+"]");}});var $E=function(A,B){return($(B)||document).getElement(A);};var $ES=function(A,B){return($(B)||document).getElements(A); +};Element.Events.domready={onAdd:function(A){if(Browser.loaded){A.call(this);}}};(function(){var B=function(){if(Browser.loaded){return ;}Browser.loaded=true; +window.fireEvent("domready");document.fireEvent("domready");};switch(Browser.Engine.name){case"webkit":(function(){(["loaded","complete"].contains(document.readyState))?B():arguments.callee.delay(50); +})();break;case"trident":var A=document.createElement("div");(function(){($try(function(){A.doScroll("left");return $(A).inject(document.body).set("html","temp").dispose(); +}))?B():arguments.callee.delay(50);})();break;default:window.addEvent("load",B);document.addEvent("DOMContentLoaded",B);}})();var JSON=new Hash({encode:function(B){switch($type(B)){case"string":return'"'+B.replace(/[\x00-\x1f\\"]/g,JSON.$replaceChars)+'"'; +case"array":return"["+String(B.map(JSON.encode).filter($defined))+"]";case"object":case"hash":var A=[];Hash.each(B,function(E,D){var C=JSON.encode(E);if(C){A.push(JSON.encode(D)+":"+C); +}});return"{"+A+"}";case"number":case"boolean":return String(B);case false:return"null";}return null;},$specialChars:{"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},$replaceChars:function(A){return JSON.$specialChars[A]||"\\u00"+Math.floor(A.charCodeAt()/16).toString(16)+(A.charCodeAt()%16).toString(16); +},decode:function(string,secure){if($type(string)!="string"||!string.length){return null;}if(secure&&!(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,""))){return null; +}return eval("("+string+")");}});Native.implement([Hash,Array,String,Number],{toJSON:function(){return JSON.encode(this);}});var Json=JSON;JSON.toString=JSON.encode; +JSON.evaluate=JSON.decode;var Cookie=new Class({Implements:Options,options:{path:false,domain:false,duration:false,secure:false,document:document},initialize:function(B,A){this.key=B; +this.setOptions(A);},write:function(B){B=encodeURIComponent(B);if(this.options.domain){B+="; domain="+this.options.domain;}if(this.options.path){B+="; path="+this.options.path; +}if(this.options.duration){var A=new Date();A.setTime(A.getTime()+this.options.duration*24*60*60*1000);B+="; expires="+A.toGMTString();}if(this.options.secure){B+="; secure"; +}this.options.document.cookie=this.key+"="+B;return this;},read:function(){var A=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)"); +return(A)?decodeURIComponent(A[1]):null;},dispose:function(){new Cookie(this.key,$merge(this.options,{duration:-1})).write("");return this;}});Cookie.write=function(B,C,A){return new Cookie(B,A).write(C); +};Cookie.read=function(A){return new Cookie(A).read();};Cookie.dispose=function(B,A){return new Cookie(B,A).dispose();};Cookie.set=function(B,C,A){return new Cookie(B,A).write(C); +};Cookie.get=function(A){return new Cookie(A).read();};Cookie.remove=function(B,A){return new Cookie(B,A).dispose();};var Swiff=new Class({Implements:[Options],options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"transparent",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object; +},initialize:function(L,M){this.instance="Swiff_"+$time();this.setOptions(M);M=this.options;var B=this.id=M.id||this.instance;var A=$(M.container);Swiff.CallBacks[this.instance]={}; +var E=M.params,G=M.vars,F=M.callBacks;var H=$extend({height:M.height,width:M.width},M.properties);var K=this;for(var D in F){Swiff.CallBacks[this.instance][D]=(function(N){return function(){return N.apply(K.object,arguments); +};})(F[D]);G[D]="Swiff.CallBacks."+this.instance+"."+D;}E.flashVars=Hash.toQueryString(G);if(Browser.Engine.trident){H.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"; +E.movie=L;}else{H.type="application/x-shockwave-flash";H.data=L;}var J=''; +}J+="";this.object=(A||new Element("div")).set("html",J).firstChild;},replaces:function(A){A=$(A,true);A.parentNode.replaceChild(this.toElement(),A); +return this;},inject:function(A){$(A,true).appendChild(this.toElement());return this;},remote:function(A){return Swiff.remote(this.toElement(),A);}});Swiff.CallBacks={}; +Swiff.remote=function(obj,fn){var rs=obj.CallFunction(''+__flash__argumentsToXML(arguments,2)+""); +return eval(rs);};var Fx=new Class({Implements:[Chain,Events,Options],options:{fps:50,unit:false,duration:500,link:"ignore",transition:function(A){return -(Math.cos(Math.PI*A)-1)/2; +}},initialize:function(A){this.subject=this.subject||this;this.setOptions(A);this.options.duration=Fx.Durations[this.options.duration]||this.options.duration.toInt(); +var B=this.options.wait;if(B===false){this.options.link="cancel";}},step:function(){var A=$time();if(A=(7-4*B)/11){C=-Math.pow((11-6*B-11*D)/4,2)+A*A;break;}}return C;},Elastic:function(B,A){return Math.pow(2,10*--B)*Math.cos(20*B*Math.PI*(A[0]||1)/3); +}});["Quad","Cubic","Quart","Quint"].each(function(B,A){Fx.Transitions[B]=new Fx.Transition(function(C){return Math.pow(C,[A+2]);});});var Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false},initialize:function(A){this.xhr=new Browser.Request(); +this.setOptions(A);this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.headers=new Hash(this.options.headers);},onStateChange:function(){if(this.xhr.readyState!=4||!this.running){return ; +}this.running=false;this.status=0;$try(function(){this.status=this.xhr.status;}.bind(this));if(this.options.isSuccess.call(this,this.status)){this.response={text:this.xhr.responseText,xml:this.xhr.responseXML}; +this.success(this.response.text,this.response.xml);}else{this.response={text:null,xml:null};this.failure();}this.xhr.onreadystatechange=$empty;},isSuccess:function(){return((this.status>=200)&&(this.status<300)); +},processScripts:function(A){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return $exec(A);}return A.stripScripts(this.options.evalScripts); +},success:function(B,A){this.onSuccess(this.processScripts(B),A);},onSuccess:function(){this.fireEvent("onComplete",arguments).fireEvent("onSuccess",arguments).callChain(); +},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("onComplete").fireEvent("onFailure",this.xhr);},setHeader:function(A,B){this.headers.set(A,B); +return this;},getHeader:function(A){return $try(function(){return this.xhr.getResponseHeader(A);}.bind(this));},check:function(){if(!this.running){return true; +}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.send.bind(this,arguments));return false;}return false;},send:function(D){if(!this.check(D)){return this; +}this.running=true;var E=$type(D);if(E=="string"||E=="element"){D={data:D};}var A=this.options;D=$extend({data:A.data,url:A.url,method:A.method},D);var G=D.data,C=D.url,H=D.method; +switch($type(G)){case"element":G=$(G).toQueryString();break;case"object":case"hash":G=Hash.toQueryString(G);}if(this.options.emulation&&["put","delete"].contains(H)){var B="_method="+H; +G=(G)?B+"&"+G:B;H="post";}if(this.options.urlEncoded&&H=="post"){var F=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers.set("Content-type","application/x-www-form-urlencoded"+F); +}if(G&&H=="get"){C=C+(C.contains("?")?"&":"?")+G;G=null;}this.xhr.open(H.toUpperCase(),C,this.options.async);this.xhr.onreadystatechange=this.onStateChange.bind(this); +this.headers.each(function(J,I){if(!$try(function(){this.xhr.setRequestHeader(I,J);return true;}.bind(this))){this.fireEvent("onException",[e,I,J]);}},this); +this.fireEvent("onRequest");this.xhr.send(G);if(!this.options.async){this.onStateChange();}return this;},cancel:function(){if(!this.running){return this; +}this.running=false;this.xhr.abort();this.xhr.onreadystatechange=$empty;this.xhr=new Browser.Request();this.fireEvent("onCancel");return this;}});(function(){var A={}; +["get","post","GET","POST","PUT","DELETE"].each(function(B){A[B]=function(){var C=Array.link(arguments,{url:String.type,data:$defined});return this.send($extend(C,{method:B.toLowerCase()})); +};});Request.implement(A);})();Element.Properties.send={set:function(A){var B=this.retrieve("send");if(B){B.cancel();}return this.eliminate("send").store("send:options",$extend({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")},A)); +},get:function(A){if(A||!this.retrieve("send")){if(A||!this.retrieve("send:options")){this.set("send",A);}this.store("send",new Request(this.retrieve("send:options"))); +}return this.retrieve("send");}};Element.implement({send:function(A){var B=this.get("send");B.send({data:this,url:A||B.options.url});return this;}});Object.toQueryString=Hash.toQueryString; +var XHR=new Class({Extends:Request,options:{update:false},initialize:function(B,A){arguments.callee.parent(A);this.url=B;},request:function(A){return this.send(this.url,A||this.options.data); +},send:function(A,B){if(!this.check(A,B)){return this;}return arguments.callee.parent({url:A,data:B});},success:function(B,A){B=this.processScripts(B); +if(this.options.update){$(this.options.update).empty().set("html",B);}this.onSuccess(B,A);},failure:function(){this.fireEvent("onFailure",this.xhr);}}); +var Ajax=XHR;Request.HTML=new Class({Extends:Request,options:{update:false,evalScripts:true,filter:false},processHTML:function(C){var B=C.match(/]*>([\s\S]*?)<\/body>/i); +C=(B)?B[1]:C;var A=new Element("div");return $try(function(){var D=""+C+"",G;if(Browser.Engine.trident){G=new ActiveXObject("Microsoft.XMLDOM"); +G.async=false;G.loadXML(D);}else{G=new DOMParser().parseFromString(D,"text/xml");}D=G.getElementsByTagName("root")[0];for(var F=0,E=D.childNodes.length; +Fthis.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});this.fireEvent("onStart",this.element).fireEvent("onSnap",this.element); +}},drag:function(A){this.mouse.now=A.page;for(var B in this.options.modifiers){if(!this.options.modifiers[B]){continue;}this.value.now[B]=this.mouse.now[B]-this.mouse.pos[B]; +if(this.options.invert){this.value.now[B]*=-1;}if(this.options.limit&&this.limit[B]){if($chk(this.limit[B][1])&&(this.value.now[B]>this.limit[B][1])){this.value.now[B]=this.limit[B][1]; +}else{if($chk(this.limit[B][0])&&(this.value.now[B]B.left&&A.xB.top);},checkDroppables:function(){var A=this.droppables.filter(this.checkAgainst,this).getLast(); +if(this.overed!=A){if(this.overed){this.overed.fireEvent("leave",[this.element,this]);}this.overed=A?A.fireEvent("over",[this.element,this]):null;}},drag:function(A){arguments.callee.parent(A); +if(this.droppables.length){this.checkDroppables();}},stop:function(A){this.checkDroppables();if(this.overed){this.overed.fireEvent("drop",[this.element,this]); +}else{this.element.fireEvent("emptydrop",this);}return arguments.callee.parent(A);}});Element.implement({makeDraggable:function(A){return new Drag.Move(this,A); +}});var Color=new Native({initialize:function(B,C){if(arguments.length>=3){C="rgb";B=Array.slice(arguments,0,3);}else{if(typeof B=="string"){if(B.match(/rgb/)){B=B.rgbToHex().hexToRgb(true); +}else{if(B.match(/hsb/)){B=B.hsbToRgb();}else{B=B.hexToRgb(true);}}}}C=C||"rgb";switch(C){case"hsb":var A=B;B=B.hsbToRgb();B.hsb=A;break;case"hex":B=B.hexToRgb(true); +break;}B.rgb=B.slice(0,3);B.hsb=B.hsb||B.rgbToHsb();B.hex=B.rgbToHex();return $extend(B,this);}});Color.implement({mix:function(){var A=Array.slice(arguments); +var C=($type(A.getLast())=="number")?A.pop():50;var B=this.slice();A.each(function(D){D=new Color(D);for(var E=0;E<3;E++){B[E]=Math.round((B[E]/100*(100-C))+(D[E]/100*C)); +}});return new Color(B,"rgb");},invert:function(){return new Color(this.map(function(A){return 255-A;}));},setHue:function(A){return new Color([A,this.hsb[1],this.hsb[2]],"hsb"); +},setSaturation:function(A){return new Color([this.hsb[0],A,this.hsb[2]],"hsb");},setBrightness:function(A){return new Color([this.hsb[0],this.hsb[1],A],"hsb"); +}});function $RGB(C,B,A){return new Color([C,B,A],"rgb");}function $HSB(C,B,A){return new Color([C,B,A],"hsb");}function $HEX(A){return new Color(A,"hex"); +}Array.implement({rgbToHsb:function(){var B=this[0],C=this[1],J=this[2];var G,F,H;var I=Math.max(B,C,J),E=Math.min(B,C,J);var K=I-E;H=I/255;F=(I!=0)?K/I:0; +if(F==0){G=0;}else{var D=(I-B)/K;var A=(I-C)/K;var L=(I-J)/K;if(B==I){G=L-A;}else{if(C==I){G=2+D-L;}else{G=4+A-D;}}G/=6;if(G<0){G++;}}return[Math.round(G*360),Math.round(F*100),Math.round(H*100)]; +},hsbToRgb:function(){var C=Math.round(this[2]/100*255);if(this[1]==0){return[C,C,C];}else{var A=this[0]%360;var E=A%60;var F=Math.round((this[2]*(100-this[1]))/10000*255); +var D=Math.round((this[2]*(6000-this[1]*E))/600000*255);var B=Math.round((this[2]*(6000-this[1]*(60-E)))/600000*255);switch(Math.floor(A/60)){case 0:return[C,B,F]; +case 1:return[D,C,F];case 2:return[F,C,B];case 3:return[F,D,C];case 4:return[B,F,C];case 5:return[C,F,D];}}return false;}});String.implement({rgbToHsb:function(){var A=this.match(/\d{1,3}/g); +return(A)?hsb.rgbToHsb():null;},hsbToRgb:function(){var A=this.match(/\d{1,3}/g);return(A)?A.hsbToRgb():null;}});var Group=new Class({initialize:function(){this.instances=Array.flatten(arguments); +this.events={};this.checker={};},addEvent:function(B,A){this.checker[B]=this.checker[B]||{};this.events[B]=this.events[B]||[];if(this.events[B].contains(A)){return false; +}else{this.events[B].push(A);}this.instances.each(function(C,D){C.addEvent(B,this.check.bind(this,[B,C,D]));},this);return this;},check:function(C,A,B){this.checker[C][B]=true; +var D=this.instances.every(function(F,E){return this.checker[C][E]||false;},this);if(!D){return ;}this.checker[C]={};this.events[C].each(function(E){E.call(this,this.instances,A); +},this);}});Hash.Cookie=new Class({Extends:Cookie,options:{autoSave:true},initialize:function(B,A){this.parent(B,A);this.load();},save:function(){var A=JSON.encode(this.hash); +if(!A||A.length>4096){return false;}if(A=="{}"){this.dispose();}else{this.write(A);}return true;},load:function(){this.hash=new Hash(JSON.decode(this.read(),true)); +return this;}});Hash.Cookie.implement((function(){var A={};Hash.each(Hash.prototype,function(C,B){A[B]=function(){var D=C.apply(this.hash,arguments);if(this.options.autoSave){this.save(); +}return D;};});return A;})());var Sortables=new Class({Implements:[Events,Options],options:{snap:4,handle:false,revert:false,constrain:false,cloneOpacity:0.7,elementOpacity:0.3},initialize:function(A,B){this.setOptions(B); +this.elements=[];this.lists=[];this.idle=true;this.addLists($$($(A)||A));if(this.options.revert){this.effect=new Fx.Morph(null,$merge({duration:250,link:"cancel"},this.options.revert)); +}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(A){this.elements.push(A); +var C=A.retrieve("sortables:start",this.start.bindWithEvent(this,A));var B=A.retrieve("sortables:insert",this.insert.bind(this,A));(this.options.handle?A.getElement(this.options.handle)||A:A).addEvent("mousedown",C); +A.addEvent("over",B);},this);return this;},addLists:function(){Array.flatten(arguments).each(function(A){this.lists.push(A);this.addItems(A.getChildren()); +A.addEvent("over",A.retrieve("sortables:insert",this.insert.bind(this,[A,"inside"])));},this);return this;},removeItems:function(){var A=[];Array.flatten(arguments).each(function(B){A.push(B); +this.elements.erase(B);var D=B.retrieve("sortables:start");var C=B.retrieve("sortables:insert");(this.options.handle?B.getElement(this.options.handle)||B:B).removeEvent("mousedown",D); +B.removeEvent("over",C);},this);return A;},removeLists:function(){var A=[];Array.flatten(arguments).each(function(B){A.push(B);this.lists.erase(B);this.removeItems(B.getChildren()); +B.removeEvent("over",B.retrieve("sortables:insert"));},this);return A;},getClone:function(A){return A.clone(true).setStyles({margin:"0px",position:"absolute",visibility:"hidden"}).inject(this.list).position(A.getRelativePosition()); +},getDroppables:function(){var A=this.list.getChildren();if(!this.options.constrain){A=this.lists.concat(A).erase(this.list);}return A.erase(this.clone).erase(this.element); +},insert:function(B,A){if(A){this.list=B;this.drag.droppables=this.getDroppables();}A=A||(this.element.getAllPrevious().contains(B)?"before":"after");this.element.inject(B,A); +this.fireEvent("onSort",[this.element,this.clone]);},start:function(B,A){if(!this.idle){return ;}this.idle=false;this.element=A;this.opacity=A.get("opacity"); +this.list=A.getParent();this.clone=this.getClone(A);this.drag=this.clone.makeDraggable({snap:this.options.snap,container:this.options.constrain&&this.clone.getParent(),droppables:this.getDroppables(),onStart:function(){B.stop(); +this.clone.set("opacity",this.options.cloneOpacity);this.element.set("opacity",this.options.elementOpacity);this.fireEvent("onStart",[this.element,this.clone]); +}.bind(this),onCancel:this.reset.bind(this),onComplete:this.end.bind(this)});this.drag.start(B);},end:function(){this.element.set("opacity",this.opacity); +this.drag.detach();if(this.effect){var A=this.element.getStyles("width","height");var B=this.clone.computePosition(this.element.getPosition(this.clone.offsetParent),this.clone.getParent().positioned()); +this.effect.element=this.clone;this.effect.start({top:B.top,left:B.left,width:A.width,height:A.height,opacity:0.25}).chain(this.reset.bind(this));}else{this.reset(); +}},reset:function(){this.idle=true;this.clone.destroy();this.fireEvent("onComplete",this.element);},serialize:function(C,A){var B=this.lists.map(function(D){return D.getChildren().map(A||function(F,E){return F.get("id"); +},this);},this);if(this.lists.length==1){C=0;}return $chk(C)&&C>=0&&CB[F]){G=D.page[F]-this.options.offsets[F]-E[F]; +}this.tip.setStyle(C[F],G);}},fill:function(A,B){(typeof B=="string")?A.set("html",B):A.adopt(B);},show:function(){this.fireEvent("onShow",this.tip);},hide:function(){this.fireEvent("onHide",this.tip); +}});var SmoothScroll=new Class({Extends:Fx.Scroll,initialize:function(B,C){C=C||document;var E=C.getDocument(),D=C.getWindow();arguments.callee.parent(E,B); +this.links=(this.options.links)?$$(this.options.links):$$(E.links);var A=D.location.href.match(/^[^#]*/)[0]+"#";this.links.each(function(G){if(G.href.indexOf(A)!=0){return ; +}var F=G.href.substr(A.length);if(F&&$(F)){this.useLink(G,F);}},this);if(!Browser.Engine.webkit419){this.addEvent("onComplete",function(){D.location.hash=this.anchor; +},true);}},useLink:function(B,A){B.addEvent("click",function(C){this.anchor=A;this.toElement(A);C.stop();}.bind(this));}});var Slider=new Class({Implements:[Events,Options],options:{onTick:function(A){if(this.options.snap){A=this.toPosition(this.step); +}this.knob.setStyle(this.property,A);},snap:false,offset:0,range:false,wheel:false,steps:100,mode:"horizontal"},initialize:function(E,A,D){this.setOptions(D); +this.element=$(E);this.knob=$(A);this.previousChange=this.previousEnd=this.step=-1;this.element.addEvent("mousedown",this.clickedElement.bind(this));if(this.options.wheel){this.element.addEvent("mousewheel",this.scrolledElement.bindWithEvent(this)); +}var F,B={},C={x:false,y:false};switch(this.options.mode){case"vertical":this.axis="y";this.property="top";F="offsetHeight";break;case"horizontal":this.axis="x"; +this.property="left";F="offsetWidth";}this.half=this.knob[F]/2;this.full=this.element[F]-this.knob[F]+(this.options.offset*2);this.min=$chk(this.options.range[0])?this.options.range[0]:0; +this.max=$chk(this.options.range[1])?this.options.range[1]:this.options.steps;this.range=this.max-this.min;this.steps=this.options.steps||this.full;this.stepSize=Math.abs(this.range)/this.steps; +this.stepWidth=this.stepSize*this.full/Math.abs(this.range);this.knob.setStyle("position","relative").setStyle(this.property,-this.options.offset);C[this.axis]=this.property; +B[this.axis]=[-this.options.offset,this.full-this.options.offset];this.drag=new Drag(this.knob,{snap:0,limit:B,modifiers:C,onDrag:this.draggedKnob.bind(this),onStart:this.draggedKnob.bind(this),onComplete:function(){this.draggedKnob(); +this.end();}.bind(this)});if(this.options.snap){this.drag.options.grid=Math.ceil(this.stepWidth);this.drag.options.limit[this.axis][1]=this.full;}},set:function(A){if(!((this.range>0)^(A0)^(A>this.max))){A=this.max;}this.step=Math.round(A);this.checkStep();this.end();this.fireEvent("onTick",this.toPosition(this.step)); +return this;},clickedElement:function(C){var B=this.range<0?-1:1;var A=C.page[this.axis]-this.element.getPosition()[this.axis]-this.half;A=A.limit(-this.options.offset,this.full-this.options.offset); +this.step=Math.round(this.min+B*this.toStep(A));this.checkStep();this.end();this.fireEvent("onTick",A);},scrolledElement:function(A){var B=(this.options.mode=="horizontal")?(A.wheel<0):(A.wheel>0); +this.set(B?this.step-this.stepSize:this.step+this.stepSize);A.stop();},draggedKnob:function(){var B=this.range<0?-1:1;var A=this.drag.value.now[this.axis]; +A=A.limit(-this.options.offset,this.full-this.options.offset);this.step=Math.round(this.min+B*this.toStep(A));this.checkStep();},checkStep:function(){if(this.previousChange!=this.step){this.previousChange=this.step; +this.fireEvent("onChange",this.step);}},end:function(){if(this.previousEnd!==this.step){this.previousEnd=this.step;this.fireEvent("onComplete",this.step+""); +}},toStep:function(A){var B=(A+this.options.offset)*this.stepSize/this.full*this.steps;return this.options.steps?Math.round(B-=B%this.stepSize):B;},toPosition:function(A){return(this.full*Math.abs(this.min-A))/(this.steps*this.stepSize)-this.options.offset; +}});var Scroller=new Class({Implements:[Events,Options],options:{area:20,velocity:1,onChange:function(A,B){this.element.scrollTo(A,B);}},initialize:function(B,A){this.setOptions(A); +this.element=$(B);this.listener=($type(this.element)!="element")?$(this.element.getDocument().body):this.element;this.timer=null;},start:function(){this.coord=this.getCoords.bind(this); +this.listener.addEvent("mousemove",this.coord);},stop:function(){this.listener.removeEvent("mousemove",this.coord);this.timer=$clear(this.timer);},getCoords:function(A){this.page=(this.listener.get("tag")=="body")?A.client:A.page; +if(!this.timer){this.timer=this.scroll.periodical(50,this);}},scroll:function(){var B=this.element.getSize(),A=this.element.getScroll(),E=this.element.getPosition(),D={x:0,y:0}; +for(var C in this.page){if(this.page[C]<(this.options.area+E[C])&&A[C]!=0){D[C]=(this.page[C]-this.options.area-E[C])*this.options.velocity;}else{if(this.page[C]+this.options.area>(B[C]+E[C])&&B[C]+B[C]!=A[C]){D[C]=(this.page[C]-B[C]+this.options.area-E[C])*this.options.velocity; +}}}if(D.y||D.x){this.fireEvent("onChange",[A.x+D.x,A.y+D.y]);}}});var Asset=new Hash({javascript:function(F,D){D=$extend({onload:$empty,document:document,check:$lambda(true)},D); +var B=new Element("script",{src:F,type:"text/javascript"});var E=D.onload.bind(B),A=D.check,G=D.document;delete D.onload;delete D.check;delete D.document; +B.addEvents({load:E,readystatechange:function(){if(["loaded","complete"].contains(this.readyState)){E();}}}).setProperties(D);if(Browser.Engine.webkit419){var C=(function(){if(!$try(A)){return ; +}$clear(C);E();}).periodical(50);}return B.inject(G.head);},css:function(B,A){return new Element("link",$merge({rel:"stylesheet",media:"screen",type:"text/css",href:B},A)).inject(document.head); +},image:function(C,B){B=$merge({onload:$empty,onabort:$empty,onerror:$empty},B);var D=new Image();var A=$(D)||new Element("img");["load","abort","error"].each(function(E){var F="on"+E; +var G=B[F];delete B[F];D[F]=function(){if(!D){return ;}if(!A.parentNode){A.width=D.width;A.height=D.height;}D=D.onload=D.onabort=D.onerror=null;G.delay(1,A,A); +A.fireEvent(E,A,1);};});D.src=A.src=C;if(D&&D.complete){D.onload.delay(1);}return A.setProperties(B);},images:function(D,C){C=$merge({onComplete:$empty,onProgress:$empty},C); +if(!D.push){D=[D];}var A=[];var B=0;D.each(function(F){var E=new Asset.image(F,{onload:function(){C.onProgress.call(this,B,D.indexOf(F));B++;if(B==D.length){C.onComplete(); +}}});A.push(E);});return new Elements(A);}});var Accordion=new Class({Extends:Fx.Elements,options:{display:0,show:false,height:true,width:false,opacity:true,fixedHeight:false,fixedWidth:false,wait:false,alwaysHide:false},initialize:function(){var C=Array.link(arguments,{container:Element.type,options:Object.type,togglers:$defined,elements:$defined}); +arguments.callee.parent(C.elements,C.options);this.togglers=$$(C.togglers);this.container=$(C.container);this.previous=-1;if(this.options.alwaysHide){this.options.wait=true; +}if($chk(this.options.show)){this.options.display=false;this.previous=this.options.show;}if(this.options.start){this.options.display=false;this.options.show=false; +}this.effects={};if(this.options.opacity){this.effects.opacity="fullOpacity";}if(this.options.width){this.effects.width=this.options.fixedWidth?"fullWidth":"offsetWidth"; +}if(this.options.height){this.effects.height=this.options.fixedHeight?"fullHeight":"scrollHeight";}for(var B=0,A=this.togglers.length;B0));this.fireEvent(C?"onBackground":"onActive",[this.togglers[D],E]); +for(var F in this.effects){B[D][F]=C?0:E[this.effects[F]];}},this);return this.start(B);}}); \ No newline at end of file