Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@Belphemur
Last active August 29, 2015 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Belphemur/721a73200764388f0fc5 to your computer and use it in GitHub Desktop.
Save Belphemur/721a73200764388f0fc5 to your computer and use it in GitHub Desktop.
Patch for transmission 2.84+ for Download Group
diff -ruN transmission-2.84+/libtransmission/platform.c transmission-2.84-patched/libtransmission/platform.c
--- transmission-2.84+/libtransmission/platform.c 2015-07-30 09:56:15.405162700 +0300
+++ transmission-2.84-patched/libtransmission/platform.c 2015-07-31 12:48:59.336636000 +0300
@@ -418,6 +418,29 @@
return user_dir;
}
+const struct tr_variant*
+tr_getDefaultDownloadGroups (void)
+{
+ static tr_variant groups;
+
+ tr_variantInitList (&groups, 3);
+ tr_variantListAddStr (&groups, "Movies");
+ tr_variantListAddStr (&groups, "Music");
+ tr_variantListAddStr (&groups, "Software");
+
+ return &groups;
+}
+
+const char*
+tr_getDefaultDownloadGroupDefault (void)
+{
+ static char* defaultDownloadGroup = NULL;
+
+ defaultDownloadGroup = tr_strdup ("movies");
+
+ return defaultDownloadGroup;
+}
+
/***
****
***/
diff -ruN transmission-2.84+/libtransmission/quark.c transmission-2.84-patched/libtransmission/quark.c
--- transmission-2.84+/libtransmission/quark.c 2015-07-30 09:56:15.493162700 +0300
+++ transmission-2.84-patched/libtransmission/quark.c 2015-07-31 12:48:59.342640300 +0300
@@ -15,6 +15,7 @@
#include "ptrarray.h"
#include "quark.h"
#include "utils.h" /* tr_memdup(), tr_strndup() */
+#include "log.h"
struct tr_key_struct
{
@@ -22,6 +23,7 @@
size_t len;
};
+/* this list must be sorted because we are using a binary search of a sorted array (bsearch) */
static const struct tr_key_struct my_static[] =
{
{ "", 0 },
@@ -88,10 +90,13 @@
{ "doneDate", 8 },
{ "download-dir", 12 },
{ "download-dir-free-space", 23 },
+ { "download-group-default", 22 },
+ { "download-groups", 15 },
{ "download-queue-enabled", 22 },
{ "download-queue-size", 19 },
{ "downloadCount", 13 },
{ "downloadDir", 11 },
+ { "downloadGroup", 13 },
{ "downloadLimit", 13 },
{ "downloadLimited", 15 },
{ "downloadSpeed", 13 },
@@ -434,6 +439,7 @@
/* is it in our static array? */
match = bsearch (&tmp, my_static, n_static, sizeof(struct tr_key_struct), compareKeys);
+
if (match != NULL)
{
*setme = match - my_static;
@@ -484,7 +490,7 @@
len = strlen (str);
if (!tr_quark_lookup (str, len, &ret))
- ret = append_new_quark (str, len);
+ ret = append_new_quark (str, len);
return ret;
}
diff -ruN transmission-2.84+/libtransmission/quark.h transmission-2.84-patched/libtransmission/quark.h
--- transmission-2.84+/libtransmission/quark.h 2015-07-30 09:56:15.501162700 +0300
+++ transmission-2.84-patched/libtransmission/quark.h 2015-07-31 12:48:59.347143200 +0300
@@ -90,10 +90,13 @@
TR_KEY_doneDate,
TR_KEY_download_dir,
TR_KEY_download_dir_free_space,
+ TR_KEY_download_group_default,
+ TR_KEY_download_groups,
TR_KEY_download_queue_enabled,
TR_KEY_download_queue_size,
TR_KEY_downloadCount,
TR_KEY_downloadDir,
+ TR_KEY_downloadGroup,
TR_KEY_downloadLimit,
TR_KEY_downloadLimited,
TR_KEY_downloadSpeed,
diff -ruN transmission-2.84+/libtransmission/resume.c transmission-2.84-patched/libtransmission/resume.c
--- transmission-2.84+/libtransmission/resume.c 2015-07-30 09:56:15.737162700 +0300
+++ transmission-2.84-patched/libtransmission/resume.c 2015-07-31 12:48:59.352647000 +0300
@@ -670,6 +670,7 @@
tr_variantDictAddStr (&top, TR_KEY_destination, tor->downloadDir);
if (tor->incompleteDir != NULL)
tr_variantDictAddStr (&top, TR_KEY_incomplete_dir, tor->incompleteDir);
+ tr_variantDictAddStr (&top, TR_KEY_downloadGroup, tor->downloadGroup);
tr_variantDictAddInt (&top, TR_KEY_downloaded, tor->downloadedPrev + tor->downloadedCur);
tr_variantDictAddInt (&top, TR_KEY_uploaded, tor->uploadedPrev + tor->uploadedCur);
tr_variantDictAddInt (&top, TR_KEY_max_peers, tor->maxConnectedPeers);
@@ -755,6 +756,18 @@
fieldsLoaded |= TR_FR_INCOMPLETE_DIR;
}
+ if ((fieldsToLoad & (TR_FR_PROGRESS | TR_FR_GROUP))
+ && (tr_variantDictFindStr (&top, TR_KEY_downloadGroup, &str, &len))
+ && (str && *str))
+ {
+ if (tr_strcmp0 (str, tor->downloadGroup))
+ {
+ tr_free (tor->downloadGroup);
+ tor->downloadGroup = tr_strndup (str, len);
+ }
+ fieldsLoaded |= TR_FR_GROUP;
+ }
+
if ((fieldsToLoad & TR_FR_DOWNLOADED)
&& tr_variantDictFindInt (&top, TR_KEY_downloaded, &i))
{
diff -ruN transmission-2.84+/libtransmission/resume.h transmission-2.84-patched/libtransmission/resume.h
--- transmission-2.84+/libtransmission/resume.h 2015-07-30 09:56:15.349162700 +0300
+++ transmission-2.84-patched/libtransmission/resume.h 2015-07-31 12:48:59.359152500 +0300
@@ -28,16 +28,17 @@
TR_FR_RUN = (1 << 9),
TR_FR_DOWNLOAD_DIR = (1 << 10),
TR_FR_INCOMPLETE_DIR = (1 << 11),
- TR_FR_MAX_PEERS = (1 << 12),
- TR_FR_ADDED_DATE = (1 << 13),
- TR_FR_DONE_DATE = (1 << 14),
- TR_FR_ACTIVITY_DATE = (1 << 15),
- TR_FR_RATIOLIMIT = (1 << 16),
- TR_FR_IDLELIMIT = (1 << 17),
- TR_FR_TIME_SEEDING = (1 << 18),
- TR_FR_TIME_DOWNLOADING = (1 << 19),
- TR_FR_FILENAMES = (1 << 20),
- TR_FR_NAME = (1 << 21),
+ TR_FR_GROUP = (1 << 12),
+ TR_FR_MAX_PEERS = (1 << 13),
+ TR_FR_ADDED_DATE = (1 << 14),
+ TR_FR_DONE_DATE = (1 << 15),
+ TR_FR_ACTIVITY_DATE = (1 << 16),
+ TR_FR_RATIOLIMIT = (1 << 17),
+ TR_FR_IDLELIMIT = (1 << 18),
+ TR_FR_TIME_SEEDING = (1 << 19),
+ TR_FR_TIME_DOWNLOADING = (1 << 20),
+ TR_FR_FILENAMES = (1 << 21),
+ TR_FR_NAME = (1 << 22),
};
/**
diff -ruN transmission-2.84+/libtransmission/rpcimpl.c transmission-2.84-patched/libtransmission/rpcimpl.c
--- transmission-2.84+/libtransmission/rpcimpl.c 2015-07-30 09:56:15.701162700 +0300
+++ transmission-2.84-patched/libtransmission/rpcimpl.c 2015-07-31 12:48:59.364168100 +0300
@@ -124,7 +124,6 @@
{
int i;
const int n = tr_variantListSize (ids);
-
torrents = tr_new0 (tr_torrent *, n);
for (i=0; i<n; ++i)
@@ -620,6 +619,10 @@
tr_variantDictAddStr (d, key, tr_torrentGetDownloadDir (tor));
break;
+ case TR_KEY_downloadGroup:
+ tr_variantDictAddStr (d, key, tr_torrentGetDownloadGroup (tor));
+ break;
+
case TR_KEY_downloadedEver:
tr_variantDictAddInt (d, key, st->downloadedEver);
break;
@@ -1238,8 +1241,8 @@
static const char*
torrentSet (tr_session * session,
- tr_variant * args_in,
- tr_variant * args_out UNUSED,
+ tr_variant * args_in,
+ tr_variant * args_out UNUSED,
struct tr_rpc_idle_data * idle_data UNUSED)
{
int i;
@@ -1255,6 +1258,7 @@
{
int64_t tmp;
double d;
+ const char * str = NULL;
tr_variant * files;
tr_variant * trackers;
bool boolVal;
@@ -1314,6 +1318,9 @@
if (tr_variantDictFindInt (args_in, TR_KEY_queuePosition, &tmp))
tr_torrentSetQueuePosition (tor, tmp);
+ if (tr_variantDictFindStr (args_in, TR_KEY_downloadGroup, &str, NULL))
+ tr_torrentSetDownloadGroup (tor, str);
+
if (!errmsg && tr_variantDictFindList (args_in, TR_KEY_trackerAdd, &trackers))
errmsg = addTrackerUrls (tor, trackers);
@@ -1732,6 +1739,9 @@
if (tr_variantDictFindStr (args_in, TR_KEY_download_dir, &str, NULL))
tr_ctorSetDownloadDir (ctor, TR_FORCE, str);
+ if (tr_variantDictFindStr (args_in, TR_KEY_downloadGroup, &str, NULL))
+ tr_ctorSetDownloadGroup (ctor, TR_FORCE, str);
+
if (tr_variantDictFindBool (args_in, TR_KEY_paused, &boolVal))
tr_ctorSetPaused (ctor, TR_FORCE, boolVal);
@@ -1834,6 +1844,7 @@
double d;
bool boolVal;
const char * str;
+ tr_variant * list;
assert (idle_data == NULL);
@@ -1879,6 +1890,15 @@
if (tr_variantDictFindInt (args_in, TR_KEY_download_queue_size, &i))
tr_sessionSetQueueSize (session, TR_DOWN, i);
+ if (tr_variantDictFindStr (args_in, TR_KEY_download_group_default, &str, NULL))
+ tr_sessionSetDownloadGroupDefault (session, str);
+
+ if (tr_variantDictFindList (args_in, TR_KEY_download_groups, &list))
+ tr_sessionSetDownloadGroups (session, list);
+
+ if (tr_variantDictFindList (args_in, TR_KEY_download_queue_size, &list))
+ tr_sessionSetDownloadGroups (session, list);
+
if (tr_variantDictFindBool (args_in, TR_KEY_download_queue_enabled, &boolVal))
tr_sessionSetQueueEnabled (session, TR_DOWN, boolVal);
@@ -2031,6 +2051,7 @@
struct tr_rpc_idle_data * idle_data UNUSED)
{
const char * str;
+ const tr_variant * knownGroups;
tr_variant * d = args_out;
assert (idle_data == NULL);
@@ -2050,6 +2071,9 @@
tr_variantDictAddInt (d, TR_KEY_download_dir_free_space, tr_device_info_get_free_space (s->downloadDir));
tr_variantDictAddBool (d, TR_KEY_download_queue_enabled, tr_sessionGetQueueEnabled (s, TR_DOWN));
tr_variantDictAddInt (d, TR_KEY_download_queue_size, tr_sessionGetQueueSize (s, TR_DOWN));
+ tr_variantDictAddStr (d, TR_KEY_download_group_default, tr_sessionGetDownloadGroupDefault (s));
+ knownGroups = tr_sessionGetDownloadGroups (s);
+ tr_variantListCopy (tr_variantDictAddList (d, TR_KEY_download_groups, tr_variantListSize (knownGroups)), knownGroups);
tr_variantDictAddInt (d, TR_KEY_peer_limit_global, tr_sessionGetPeerLimit (s));
tr_variantDictAddInt (d, TR_KEY_peer_limit_per_torrent, tr_sessionGetPeerLimitPerTorrent (s));
tr_variantDictAddStr (d, TR_KEY_incomplete_dir, tr_sessionGetIncompleteDir (s));
diff -ruN transmission-2.84+/libtransmission/session.c transmission-2.84-patched/libtransmission/session.c
--- transmission-2.84+/libtransmission/session.c 2015-07-30 09:56:15.633162700 +0300
+++ transmission-2.84-patched/libtransmission/session.c 2015-07-31 12:48:59.373662000 +0300
@@ -315,9 +315,11 @@
void
tr_sessionGetDefaultSettings (tr_variant * d)
{
+ const tr_variant * knownGroups;
+
assert (tr_variantIsDict (d));
- tr_variantDictReserve (d, 63);
+ tr_variantDictReserve (d, 64);
tr_variantDictAddBool (d, TR_KEY_blocklist_enabled, false);
tr_variantDictAddStr (d, TR_KEY_blocklist_url, "http://www.example.com/blocklist");
tr_variantDictAddInt (d, TR_KEY_cache_size_mb, DEFAULT_CACHE_SIZE_MB);
@@ -381,14 +383,19 @@
tr_variantDictAddStr (d, TR_KEY_bind_address_ipv6, TR_DEFAULT_BIND_ADDRESS_IPV6);
tr_variantDictAddBool (d, TR_KEY_start_added_torrents, true);
tr_variantDictAddBool (d, TR_KEY_trash_original_torrent_files, false);
+ tr_variantDictAddStr (d, TR_KEY_download_group_default, tr_getDefaultDownloadGroupDefault ());
+ knownGroups = tr_getDefaultDownloadGroups ();
+ tr_variantListCopy (tr_variantDictAddList (d, TR_KEY_download_groups, tr_variantListSize (knownGroups)), knownGroups);
}
void
tr_sessionGetSettings (tr_session * s, tr_variant * d)
{
+ const tr_variant * knownGroups;
+
assert (tr_variantIsDict (d));
- tr_variantDictReserve (d, 63);
+ tr_variantDictReserve (d, 64);
tr_variantDictAddBool (d, TR_KEY_blocklist_enabled, tr_blocklistIsEnabled (s));
tr_variantDictAddStr (d, TR_KEY_blocklist_url, tr_blocklistGetURL (s));
tr_variantDictAddInt (d, TR_KEY_cache_size_mb, tr_sessionGetCacheLimit_MB (s));
@@ -453,6 +460,9 @@
tr_variantDictAddStr (d, TR_KEY_bind_address_ipv6, tr_address_to_string (&s->public_ipv6->addr));
tr_variantDictAddBool (d, TR_KEY_start_added_torrents, !tr_sessionGetPaused (s));
tr_variantDictAddBool (d, TR_KEY_trash_original_torrent_files, tr_sessionGetDeleteSource (s));
+ tr_variantDictAddStr (d, TR_KEY_download_group_default, tr_sessionGetDownloadGroupDefault (s));
+ knownGroups = tr_sessionGetDownloadGroups (s);
+ tr_variantListCopy (tr_variantDictAddList (d, TR_KEY_download_groups, tr_variantListSize (knownGroups)), knownGroups);
}
bool
@@ -768,6 +778,7 @@
tr_session * session = data->session;
tr_variant * settings = data->clientSettings;
struct tr_turtle_info * turtle = &session->turtle;
+ tr_variant * groups;
assert (tr_isSession (session));
assert (tr_variantIsDict (settings));
@@ -815,6 +826,12 @@
tr_sessionSetDeleteSource (session, boolVal);
if (tr_variantDictFindInt (settings, TR_KEY_peer_id_ttl_hours, &i))
session->peer_id_ttl_hours = i;
+ if (tr_variantDictFindList (settings, TR_KEY_download_groups, &groups))
+ {
+ tr_sessionSetDownloadGroups(session, groups);
+ }
+ if (tr_variantDictFindStr (settings, TR_KEY_download_group_default, &str, NULL))
+ tr_sessionSetDownloadGroupDefault (session, str);
/* torrent queues */
if (tr_variantDictFindInt (settings, TR_KEY_queue_stalled_minutes, &i))
@@ -1006,6 +1023,55 @@
***/
void
+tr_sessionSetDownloadGroups (tr_session * session, const tr_variant * groups)
+{
+ assert (tr_isSession (session));
+
+ tr_variantInitList (&session->downloadGroups, tr_variantListSize (groups));
+ tr_variantListCopy (&session->downloadGroups, groups);
+}
+
+const struct tr_variant*
+tr_sessionGetDownloadGroups (const tr_session * session)
+{
+ static tr_variant groups;
+
+ assert (tr_isSession (session));
+
+ tr_variantInitList (&groups, tr_variantListSize (&session->downloadGroups));
+ tr_variantListCopy (&groups, &session->downloadGroups);
+
+ return &groups;
+}
+
+void
+tr_sessionSetDownloadGroupDefault (tr_session * session, const char * defaultGroup)
+{
+ assert (tr_isSession (session));
+
+ session->downloadGroupDefault = tr_strdup (defaultGroup);
+}
+
+const char*
+tr_sessionGetDownloadGroupDefault (const tr_session * session)
+{
+ const char * defaultGroup = NULL;
+
+ assert (tr_isSession (session));
+
+ if (session->downloadGroupDefault != NULL)
+ {
+ defaultGroup = session->downloadGroupDefault;
+ }
+
+ return defaultGroup;
+}
+
+/***
+****
+***/
+
+void
tr_sessionSetIncompleteFileNamingEnabled (tr_session * session, bool b)
{
assert (tr_isSession (session));
@@ -1919,6 +1985,7 @@
/* free the session memory */
tr_variantFree (&session->removedTorrents);
+ tr_variantFree (&session->downloadGroups);
tr_bandwidthDestruct (&session->bandwidth);
tr_bitfieldDestruct (&session->turtle.minutes);
tr_lockFree (session->lock);
diff -ruN transmission-2.84+/libtransmission/session.h transmission-2.84-patched/libtransmission/session.h
--- transmission-2.84+/libtransmission/session.h 2015-07-30 09:56:15.637162700 +0300
+++ transmission-2.84-patched/libtransmission/session.h 2015-07-31 12:48:59.383169100 +0300
@@ -188,6 +188,8 @@
char * blocklist_url;
struct tr_device_info * downloadDir;
+ tr_variant downloadGroups;
+ char * downloadGroupDefault;
struct tr_list * blocklists;
struct tr_peerMgr * peerMgr;
diff -ruN transmission-2.84+/libtransmission/torrent.c transmission-2.84-patched/libtransmission/torrent.c
--- transmission-2.84+/libtransmission/torrent.c 2015-07-30 09:56:15.429162700 +0300
+++ transmission-2.84-patched/libtransmission/torrent.c 2015-07-31 17:24:23.125784200 +0300
@@ -855,6 +855,7 @@
bool doStart;
uint64_t loaded;
const char * dir;
+ const char * group;
bool isNewTorrent;
tr_session * session = tr_ctorGetSession (ctor);
static int nextUniqueId = 1;
@@ -881,6 +882,12 @@
if (tr_sessionIsIncompleteDirEnabled (session))
tor->incompleteDir = tr_strdup (dir);
+ if (!tr_ctorGetDownloadGroup (ctor, TR_FORCE, &group) ||
+ !tr_ctorGetDownloadGroup (ctor, TR_FALLBACK, &group))
+ {
+ tor->downloadGroup = tr_strdup (group);
+ }
+
tr_bandwidthConstruct (&tor->bandwidth, session, &session->bandwidth);
tor->bandwidth.priority = tr_ctorGetBandwidthPriority (ctor);
@@ -1077,6 +1084,28 @@
**/
void
+tr_torrentSetDownloadGroup (tr_torrent * tor, const char * group)
+{
+ assert (tr_isTorrent (tor));
+
+ if (!group || !tor->downloadGroup || strcmp (group, tor->downloadGroup))
+ {
+ tr_free (tor->downloadGroup);
+ tor->downloadGroup = tr_strdup (group);
+ tor->anyDate = tr_time ();
+ tr_torrentSetDirty (tor);
+ }
+}
+
+const char*
+tr_torrentGetDownloadGroup (const tr_torrent * tor)
+{
+ assert (tr_isTorrent (tor));
+
+ return tor->downloadGroup;
+}
+
+void
tr_torrentSetDownloadDir (tr_torrent * tor, const char * path)
{
assert (tr_isTorrent (tor));
@@ -2111,6 +2140,7 @@
tr_strdup_printf ("TR_APP_VERSION=%s", SHORT_VERSION_STRING),
tr_strdup_printf ("TR_TIME_LOCALTIME=%s", timeStr),
tr_strdup_printf ("TR_TORRENT_DIR=%s", tor->currentDir),
+ tr_strdup_printf ("TR_TORRENT_GROUP=%s", tr_torrentGetDownloadGroup (tor)),
tr_strdup_printf ("TR_TORRENT_HASH=%s", tor->info.hashString),
tr_strdup_printf ("TR_TORRENT_ID=%d", tr_torrentId (tor)),
tr_strdup_printf ("TR_TORRENT_NAME=%s", tr_torrentName (tor)),
diff -ruN transmission-2.84+/libtransmission/torrent.h transmission-2.84-patched/libtransmission/torrent.h
--- transmission-2.84+/libtransmission/torrent.h 2015-07-30 09:56:15.441162700 +0300
+++ transmission-2.84-patched/libtransmission/torrent.h 2015-07-31 12:48:59.408187000 +0300
@@ -163,6 +163,10 @@
/* Where the files will be when it's complete */
char * downloadDir;
+ /* Complete files will be copied to this subdir when it's complete,
+ or not if it contains an empty string */
+ char * downloadGroup;
+
/* Where the files are when the torrent is incomplete */
char * incompleteDir;
diff -ruN transmission-2.84+/libtransmission/torrent-ctor.c transmission-2.84-patched/libtransmission/torrent-ctor.c
--- transmission-2.84+/libtransmission/torrent-ctor.c 2015-07-30 09:56:15.441162700 +0300
+++ transmission-2.84-patched/libtransmission/torrent-ctor.c 2015-07-31 12:50:52.327008700 +0300
@@ -22,10 +22,12 @@
bool isSet_paused;
bool isSet_connected;
bool isSet_downloadDir;
+ bool isSet_downloadGroup;
bool isPaused;
uint16_t peerLimit;
char * downloadDir;
+ char * downloadGroup;
};
/** Opaque class used when instantiating torrents.
@@ -343,6 +345,24 @@
}
void
+tr_ctorSetDownloadGroup (tr_ctor * ctor,
+ tr_ctorMode mode,
+ const char * group)
+{
+ struct optional_args * args = &ctor->optionalArgs[mode];
+
+ tr_free (args->downloadGroup);
+ args->downloadGroup = NULL;
+ args->isSet_downloadGroup = 0;
+ if (group && *group)
+ {
+ args->isSet_downloadGroup = 1;
+ args->downloadGroup = tr_strdup (group);
+ }
+}
+
+
+void
tr_ctorSetIncompleteDir (tr_ctor * ctor, const char * directory)
{
tr_free (ctor->incompleteDir);
@@ -394,6 +414,21 @@
return ret;
}
+int
+tr_ctorGetDownloadGroup (const tr_ctor * ctor,
+ tr_ctorMode mode,
+ const char ** setmeGroup)
+{
+ int err = 0;
+ const struct optional_args * args = &ctor->optionalArgs[mode];
+
+ if (!args->isSet_downloadGroup)
+ err = 1;
+ else if (setmeGroup)
+ *setmeGroup = args->downloadGroup;
+
+ return err;
+}
bool
tr_ctorGetIncompleteDir (const tr_ctor * ctor,
@@ -468,7 +503,8 @@
tr_ctorSetDeleteSource (ctor, tr_sessionGetDeleteSource (session));
tr_ctorSetPaused (ctor, TR_FALLBACK, tr_sessionGetPaused (session));
tr_ctorSetPeerLimit (ctor, TR_FALLBACK, session->peerLimitPerTorrent);
- tr_ctorSetDownloadDir (ctor, TR_FALLBACK, tr_sessionGetDownloadDir(session));
+ tr_ctorSetDownloadDir (ctor, TR_FALLBACK, tr_sessionGetDownloadDir (session));
+ tr_ctorSetDownloadGroup (ctor, TR_FALLBACK, tr_sessionGetDownloadGroupDefault (session));
}
tr_ctorSetSave (ctor, true);
return ctor;
@@ -480,6 +516,8 @@
clearMetainfo (ctor);
tr_free (ctor->optionalArgs[1].downloadDir);
tr_free (ctor->optionalArgs[0].downloadDir);
+ tr_free (ctor->optionalArgs[1].downloadGroup);
+ tr_free (ctor->optionalArgs[0].downloadGroup);
tr_free (ctor->incompleteDir);
tr_free (ctor->want);
tr_free (ctor->notWant);
diff -ruN transmission-2.84+/libtransmission/transmission.h transmission-2.84-patched/libtransmission/transmission.h
--- transmission-2.84+/libtransmission/transmission.h 2015-07-30 09:56:15.645162700 +0300
+++ transmission-2.84-patched/libtransmission/transmission.h 2015-07-31 12:48:59.414190800 +0300
@@ -164,6 +164,15 @@
*/
const char* tr_getDefaultDownloadDir (void);
+/**
+ * @brief returns Transmisson's default download groups.
+ */
+const struct tr_variant* tr_getDefaultDownloadGroups (void);
+
+/**
+ * @brief returns Transmisson's default download group default.
+ */
+const char* tr_getDefaultDownloadGroupDefault (void);
#define TR_DEFAULT_BIND_ADDRESS_IPV4 "0.0.0.0"
#define TR_DEFAULT_BIND_ADDRESS_IPV6 "::"
@@ -315,6 +324,40 @@
*/
int64_t tr_sessionGetDirFreeSpace (tr_session * session, const char * dir);
+//=================================================================================
+
+/**
+ * @brief Set the per-session download groups for new torrents
+ * @see tr_sessionInit ()
+ * @see tr_sessionGetDownloadGroups ()
+ */
+void tr_sessionSetDownloadGroups (tr_session * session, const struct tr_variant * groups);
+
+/**
+ * @brief Get the download groups for new torrents
+ *
+ * This is set by tr_sessionInit () or tr_sessionSetDownloadGroups ()
+ */
+const struct tr_variant * tr_sessionGetDownloadGroups (const tr_session * session);
+
+/**
+ * @brief Set the per-session default download group for new torrents
+ * @see tr_sessionInit ()
+ * @see tr_sessionGetDownloadGroupDefault ()
+ * @see tr_ctorSetDownloadGroups () **
+ */
+void tr_sessionSetDownloadGroupDefault (tr_session * session, const char * defaultGroup);
+
+/**
+ * @brief Get the default download group for new torrents
+ *
+ * This is set by tr_sessionInit () or tr_sessionSetDownloadGroupDefault (),
+ * and can be overridden on a per-torrent basis by tr_ctorSetDownloadGroups ().
+ */
+const char * tr_sessionGetDownloadGroupDefault (const tr_session * session);
+
+//=================================================================================
+
/**
* @brief Set the torrent's bandwidth priority.
*/
@@ -981,6 +1024,12 @@
tr_ctorMode mode,
const char * directory);
+/** @brief Set the download group for the torrent being added with this ctor.
+ */
+void tr_ctorSetDownloadGroup (tr_ctor * ctor,
+ tr_ctorMode mode,
+ const char * group);
+
/**
* @brief Set the incompleteDir for this torrent.
*
@@ -1025,6 +1074,11 @@
tr_ctorMode mode,
const char ** setmeDownloadDir);
+/** @brief Get the download group from this peer constructor */
+int tr_ctorGetDownloadGroup (const tr_ctor * ctor,
+ tr_ctorMode mode,
+ const char ** setmeGroup);
+
/** @brief Get the incomplete directory from this peer constructor */
bool tr_ctorGetIncompleteDir (const tr_ctor * ctor,
const char ** setmeIncompleteDir);
@@ -1353,6 +1407,14 @@
const tr_info * tr_torrentInfo (const tr_torrent * torrent);
+
+/* Raw function to change the torrent's downloadGroup field.
+ This should only be used by libtransmission or to bootstrap
+ a newly-instantiated tr_torrent object. */
+void tr_torrentSetDownloadGroup (tr_torrent * tor, const char * group);
+
+const char * tr_torrentGetDownloadGroup (const tr_torrent * tor);
+
/* Raw function to change the torrent's downloadDir field.
This should only be used by libtransmission or to bootstrap
a newly-instantiated tr_torrent object. */
diff -ruN transmission-2.84+/libtransmission/variant.c transmission-2.84-patched/libtransmission/variant.c
--- transmission-2.84+/libtransmission/variant.c 2015-07-30 09:56:15.449162700 +0300
+++ transmission-2.84-patched/libtransmission/variant.c 2015-07-31 12:48:59.423197400 +0300
@@ -1012,7 +1012,7 @@
****
***/
-static void
+void
tr_variantListCopy (tr_variant * target, const tr_variant * src)
{
int i = 0;
@@ -1136,8 +1136,21 @@
}
else if (tr_variantIsList (val))
{
- if (tr_variantDictFind (target, key) == NULL)
- tr_variantListCopy (tr_variantDictAddList (target, key, tr_variantListSize (val)), val);
+ tr_variant * targetList = tr_variantDictFind (target, key);
+ if (targetList == NULL)
+ {
+ tr_variantListCopy (tr_variantDictAddList (target, key, tr_variantListSize (val)), val);
+ }
+ else
+ {
+ // if we are processing TR_KEY_download_groups list
+ // overwrite target with source
+ if (key == TR_KEY_download_groups && tr_variantListSize(val) > 0)
+ {
+ tr_variantInitList(targetList, tr_variantListSize(val));
+ tr_variantListCopy (targetList, val);
+ }
+ }
}
else if (tr_variantIsDict (val))
{
diff -ruN transmission-2.84+/libtransmission/variant.h transmission-2.84-patched/libtransmission/variant.h
--- transmission-2.84+/libtransmission/variant.h 2015-07-30 09:56:15.461162700 +0300
+++ transmission-2.84-patched/libtransmission/variant.h 2015-07-31 12:48:59.429702100 +0300
@@ -98,6 +98,8 @@
void tr_variantFree (tr_variant *);
+void tr_variantListCopy (tr_variant * target, const tr_variant * src);
+
/***
**** Serialization / Deserialization
***/
diff -ruN transmission-2.84+/web/index.html transmission-2.84-patched/web/index.html
--- transmission-2.84+/web/index.html 2015-07-30 09:56:13.021162800 +0300
+++ transmission-2.84-patched/web/index.html 2015-07-31 12:51:06.605470300 +0300
@@ -18,11 +18,13 @@
-->
<link media="only screen and (max-device-width: 480px)" href="./style/transmission/mobile.css" type= "text/css" rel="stylesheet" />
<link media="screen and (min-device-width: 481px)" href="./style/transmission/common.css" type="text/css" rel="stylesheet" />
+ <link href="./style/transmission/spectrum.css" type="text/css" rel="stylesheet" />
<!--[if IE 8]>
<link media="screen" href="./style/transmission/common.css" type="text/css" rel="stylesheet" />
<![endif]-->
<script type="text/javascript" src="./javascript/jquery/jquery.transmenu.min.js"></script>
<script type="text/javascript" src="./javascript/jquery/json2.min.js"></script>
+ <script type="text/javascript" src="./javascript/jquery/spectrum.js"></script>
<script type="text/javascript" src="./javascript/common.js"></script>
<script type="text/javascript" src="./javascript/inspector.js"></script>
<script type="text/javascript" src="./javascript/prefs-dialog.js"></script>
@@ -48,6 +50,8 @@
<div id="toolbar-start-all" title="Start All Torrents"></div>
<div id="toolbar-pause-all" title="Pause All Torrents"></div>
<div id="toolbar-inspector" title="Toggle Inspector"></div>
+ <div id="toolbar-separator"></div>
+ <div id="toolbar-set-group" title="Change Group"></div>
</div>
<div id="statusbar">
@@ -62,6 +66,7 @@
<option value="finished">Finished</option>
</select>
<select id="filter-tracker"></select>
+ <select id="filter-group"></select>
<input type="search" id="torrent_search" placeholder="Filter" />
<span id="filter-count">&nbsp;</span>
</div>
@@ -84,6 +89,7 @@
<li id="prefs-tab-speed"><a href="#prefs-page-speed">Speed</a></li>
<li id="prefs-tab-peers"><a href="#prefs-page-peers">Peers</a></li>
<li id="prefs-tab-network"><a href="#prefs-page-network">Network</a></li>
+ <li id="prefs-tab-groups"><a href="#prefs-page-groups">Groups</a></li>
<li class="ui-tab-dialog-close"></li>
</ul>
<div>
@@ -91,6 +97,12 @@
<div class="prefs-section">
<div class="title">Downloading</div>
<div class="row"><div class="key">Download to:</div><div class="value"><input type="text" id="download-dir"/></div></div>
+ <div class="row">
+ <div class="key">Default download group:</div>
+ <div class="value">
+ <select id="download-group-default"></select>
+ </div>
+ </div>
<div class="checkbox-row"><input type="checkbox" id="start-added-torrents"/><label for="start-added-torrents">Start when added</label></div>
<div class="checkbox-row"><input type="checkbox" id="rename-partial-files"/><label for="rename-partial-files">Append &quot;.part&quot; to incomplete files' names</label></div>
</div>
@@ -182,6 +194,20 @@
<label for="utp-enabled" title="uTP is a tool for reducing network congestion.">Enable uTP for peer communication</label></div>
</div>
</div>
+ <div id="prefs-page-groups">
+ <div class="prefs-section">
+ <div class="title">Group colors and order</div>
+ <div class="row">
+ <div class="value colors" id="group-colors"></div>
+ </div>
+ <div class="row">
+ <div class="key">
+ <input type="button" value="Update" id="groups-update-button">
+ </div>
+ </div>
+ <div class="message">&nbsp;</div>
+ </div>
+ </div>
</div>
</div>
@@ -215,6 +241,7 @@
<div class="title">Details</div>
<div class="row"><div class="key">Size:</div><div class="value" id="inspector-info-size">&nbsp;</div></div>
<div class="row"><div class="key">Location:</div><div class="value" id="inspector-info-location">&nbsp;</div></div>
+ <div class="row"><div class="key">Group:</div><div class="value" id="inspector-info-group">&nbsp;</div></div>
<div class="row"><div class="key">Hash:</div><div class="value" id="inspector-info-hash">&nbsp;</div></div>
<div class="row"><div class="key">Privacy:</div><div class="value" id="inspector-info-privacy">&nbsp;</div></div>
<div class="row"><div class="key">Origin:</div><div class="value" id="inspector-info-origin">&nbsp;</div></div>
@@ -291,6 +318,8 @@
<input type="file" name="torrent_files[]" id="torrent_upload_file" multiple="multiple" />
<label for="torrent_upload_url">Or enter a URL:</label>
<input type="url" id="torrent_upload_url"/>
+ <label for="download-groups" id="download_groups_label">Download group:</label>
+ <select id="download-groups"></select>
<label id='add-dialog-folder-label' for="add-dialog-folder-input">Destination folder:</label>
<input type="text" id="add-dialog-folder-input"/>
<input type="checkbox" id="torrent_auto_start" />
@@ -332,6 +361,23 @@
</form>
</div>
</div>
+
+ <div class="dialog_container" id="set_group_container" style="display:none;">
+ <div class="dialog_top_bar"></div>
+ <div class="dialog_window">
+ <div class="dialog_logo" id="set_group_dialog_logo"></div>
+ <h2 class="dialog_heading">Set group</h2>
+ <form action="#" method="post" id="torrent_set_group_form"
+ enctype="multipart/form-data" target="torrent_set_group_frame">
+ <div class="dialog_message">
+ <label for="torrent_set_group">Group:</label>
+ <select id="torrent_set_group"></select>
+ </div>
+ <a href="#set_group" id="set_group_confirm_button">Apply</a>
+ <a href="#cancel" id="set_group_cancel_button">Cancel</a>
+ </form>
+ </div>
+ </div>
<div class="torrent_footer">
<div id="settings_menu" title="Settings Menu">&nbsp;</div>
diff -ruN transmission-2.84+/web/javascript/inspector.js transmission-2.84-patched/web/javascript/inspector.js
--- transmission-2.84+/web/javascript/inspector.js 2015-07-30 09:56:13.633162800 +0300
+++ transmission-2.84-patched/web/javascript/inspector.js 2015-07-31 12:52:34.341830300 +0300
@@ -453,6 +453,23 @@
}
}
setTextContent(e.foldername_lb, str);
+ //
+ // group
+ //
+
+ str = '';
+ if(torrents.length < 1)
+ str = none;
+ else {
+ for(i=0; t=torrents[i]; ++i) {
+ if(str != t.getDownloadGroup()) {
+ str += '<span class="group" style="' + t.getDownloadGroupColor() + '">' + t.getDownloadGroup() + '</span>';
+ }
+ }
+ str = '<span class="groupwrap">' + str + '</span>';
+ }
+ setInnerHTML(e.group_lb, str);
+
},
/****
@@ -787,6 +804,7 @@
data.elements.last_activity_lb = $('#inspector-info-last-activity')[0];
data.elements.error_lb = $('#inspector-info-error')[0];
data.elements.size_lb = $('#inspector-info-size')[0];
+ data.elements.group_lb = $('#inspector-info-group')[0];
data.elements.foldername_lb = $('#inspector-info-location')[0];
data.elements.hash_lb = $('#inspector-info-hash')[0];
data.elements.privacy_lb = $('#inspector-info-privacy')[0];
diff -ruN transmission-2.84+/web/javascript/jquery/spectrum.js transmission-2.84-patched/web/javascript/jquery/spectrum.js
--- transmission-2.84+/web/javascript/jquery/spectrum.js 1970-01-01 03:00:00.000000000 +0300
+++ transmission-2.84-patched/web/javascript/jquery/spectrum.js 2015-07-30 20:48:52.000000000 +0300
@@ -0,0 +1,2317 @@
+// Spectrum Colorpicker v1.7.1
+// https://github.com/bgrins/spectrum
+// Author: Brian Grinstead
+// License: MIT
+
+(function (factory) {
+ "use strict";
+
+ if (typeof define === 'function' && define.amd) { // AMD
+ define(['jquery'], factory);
+ }
+ else if (typeof exports == "object" && typeof module == "object") { // CommonJS
+ module.exports = factory;
+ }
+ else { // Browser
+ factory(jQuery);
+ }
+})(function($, undefined) {
+ "use strict";
+
+ var defaultOpts = {
+
+ // Callbacks
+ beforeShow: noop,
+ move: noop,
+ change: noop,
+ show: noop,
+ hide: noop,
+
+ // Options
+ color: false,
+ flat: false,
+ showInput: false,
+ allowEmpty: false,
+ showButtons: true,
+ clickoutFiresChange: true,
+ showInitial: false,
+ showPalette: false,
+ showPaletteOnly: false,
+ hideAfterPaletteSelect: false,
+ togglePaletteOnly: false,
+ showSelectionPalette: true,
+ localStorageKey: false,
+ appendTo: "body",
+ maxSelectionSize: 7,
+ cancelText: "cancel",
+ chooseText: "choose",
+ togglePaletteMoreText: "more",
+ togglePaletteLessText: "less",
+ clearText: "Clear Color Selection",
+ noColorSelectedText: "No Color Selected",
+ preferredFormat: false,
+ className: "", // Deprecated - use containerClassName and replacerClassName instead.
+ containerClassName: "",
+ replacerClassName: "",
+ showAlpha: false,
+ theme: "sp-light",
+ palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]],
+ selectionPalette: [],
+ disabled: false,
+ offset: null
+ },
+ spectrums = [],
+ IE = !!/msie/i.exec( window.navigator.userAgent ),
+ rgbaSupport = (function() {
+ function contains( str, substr ) {
+ return !!~('' + str).indexOf(substr);
+ }
+
+ var elem = document.createElement('div');
+ var style = elem.style;
+ style.cssText = 'background-color:rgba(0,0,0,.5)';
+ return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
+ })(),
+ replaceInput = [
+ "<div class='sp-replacer'>",
+ "<div class='sp-preview'><div class='sp-preview-inner'></div></div>",
+ "<div class='sp-dd'>&#9660;</div>",
+ "</div>"
+ ].join(''),
+ markup = (function () {
+
+ // IE does not support gradients with multiple stops, so we need to simulate
+ // that for the rainbow slider with 8 divs that each have a single gradient
+ var gradientFix = "";
+ if (IE) {
+ for (var i = 1; i <= 6; i++) {
+ gradientFix += "<div class='sp-" + i + "'></div>";
+ }
+ }
+
+ return [
+ "<div class='sp-container sp-hidden'>",
+ "<div class='sp-palette-container'>",
+ "<div class='sp-palette sp-thumb sp-cf'></div>",
+ "<div class='sp-palette-button-container sp-cf'>",
+ "<button type='button' class='sp-palette-toggle'></button>",
+ "</div>",
+ "</div>",
+ "<div class='sp-picker-container'>",
+ "<div class='sp-top sp-cf'>",
+ "<div class='sp-fill'></div>",
+ "<div class='sp-top-inner'>",
+ "<div class='sp-color'>",
+ "<div class='sp-sat'>",
+ "<div class='sp-val'>",
+ "<div class='sp-dragger'></div>",
+ "</div>",
+ "</div>",
+ "</div>",
+ "<div class='sp-clear sp-clear-display'>",
+ "</div>",
+ "<div class='sp-hue'>",
+ "<div class='sp-slider'></div>",
+ gradientFix,
+ "</div>",
+ "</div>",
+ "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>",
+ "</div>",
+ "<div class='sp-input-container sp-cf'>",
+ "<input class='sp-input' type='text' spellcheck='false' />",
+ "</div>",
+ "<div class='sp-initial sp-thumb sp-cf'></div>",
+ "<div class='sp-button-container sp-cf'>",
+ "<a class='sp-cancel' href='#'></a>",
+ "<button type='button' class='sp-choose'></button>",
+ "</div>",
+ "</div>",
+ "</div>"
+ ].join("");
+ })();
+
+ function paletteTemplate (p, color, className, opts) {
+ var html = [];
+ for (var i = 0; i < p.length; i++) {
+ var current = p[i];
+ if(current) {
+ var tiny = tinycolor(current);
+ var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light";
+ c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : "";
+ var formattedString = tiny.toString(opts.preferredFormat || "rgb");
+ var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter();
+ html.push('<span title="' + formattedString + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>');
+ } else {
+ var cls = 'sp-clear-display';
+ html.push($('<div />')
+ .append($('<span data-color="" style="background-color:transparent;" class="' + cls + '"></span>')
+ .attr('title', opts.noColorSelectedText)
+ )
+ .html()
+ );
+ }
+ }
+ return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>";
+ }
+
+ function hideAll() {
+ for (var i = 0; i < spectrums.length; i++) {
+ if (spectrums[i]) {
+ spectrums[i].hide();
+ }
+ }
+ }
+
+ function instanceOptions(o, callbackContext) {
+ var opts = $.extend({}, defaultOpts, o);
+ opts.callbacks = {
+ 'move': bind(opts.move, callbackContext),
+ 'change': bind(opts.change, callbackContext),
+ 'show': bind(opts.show, callbackContext),
+ 'hide': bind(opts.hide, callbackContext),
+ 'beforeShow': bind(opts.beforeShow, callbackContext)
+ };
+
+ return opts;
+ }
+
+ function spectrum(element, o) {
+
+ var opts = instanceOptions(o, element),
+ flat = opts.flat,
+ showSelectionPalette = opts.showSelectionPalette,
+ localStorageKey = opts.localStorageKey,
+ theme = opts.theme,
+ callbacks = opts.callbacks,
+ resize = throttle(reflow, 10),
+ visible = false,
+ isDragging = false,
+ dragWidth = 0,
+ dragHeight = 0,
+ dragHelperHeight = 0,
+ slideHeight = 0,
+ slideWidth = 0,
+ alphaWidth = 0,
+ alphaSlideHelperWidth = 0,
+ slideHelperHeight = 0,
+ currentHue = 0,
+ currentSaturation = 0,
+ currentValue = 0,
+ currentAlpha = 1,
+ palette = [],
+ paletteArray = [],
+ paletteLookup = {},
+ selectionPalette = opts.selectionPalette.slice(0),
+ maxSelectionSize = opts.maxSelectionSize,
+ draggingClass = "sp-dragging",
+ shiftMovementDirection = null;
+
+ var doc = element.ownerDocument,
+ body = doc.body,
+ boundElement = $(element),
+ disabled = false,
+ container = $(markup, doc).addClass(theme),
+ pickerContainer = container.find(".sp-picker-container"),
+ dragger = container.find(".sp-color"),
+ dragHelper = container.find(".sp-dragger"),
+ slider = container.find(".sp-hue"),
+ slideHelper = container.find(".sp-slider"),
+ alphaSliderInner = container.find(".sp-alpha-inner"),
+ alphaSlider = container.find(".sp-alpha"),
+ alphaSlideHelper = container.find(".sp-alpha-handle"),
+ textInput = container.find(".sp-input"),
+ paletteContainer = container.find(".sp-palette"),
+ initialColorContainer = container.find(".sp-initial"),
+ cancelButton = container.find(".sp-cancel"),
+ clearButton = container.find(".sp-clear"),
+ chooseButton = container.find(".sp-choose"),
+ toggleButton = container.find(".sp-palette-toggle"),
+ isInput = boundElement.is("input"),
+ isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(),
+ shouldReplace = isInput && !flat,
+ replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]),
+ offsetElement = (shouldReplace) ? replacer : boundElement,
+ previewElement = replacer.find(".sp-preview-inner"),
+ initialColor = opts.color || (isInput && boundElement.val()),
+ colorOnShow = false,
+ preferredFormat = opts.preferredFormat,
+ currentPreferredFormat = preferredFormat,
+ clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange,
+ isEmpty = !initialColor,
+ allowEmpty = opts.allowEmpty && !isInputTypeColor;
+
+ function applyOptions() {
+
+ if (opts.showPaletteOnly) {
+ opts.showPalette = true;
+ }
+
+ toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
+
+ if (opts.palette) {
+ palette = opts.palette.slice(0);
+ paletteArray = $.isArray(palette[0]) ? palette : [palette];
+ paletteLookup = {};
+ for (var i = 0; i < paletteArray.length; i++) {
+ for (var j = 0; j < paletteArray[i].length; j++) {
+ var rgb = tinycolor(paletteArray[i][j]).toRgbString();
+ paletteLookup[rgb] = true;
+ }
+ }
+ }
+
+ container.toggleClass("sp-flat", flat);
+ container.toggleClass("sp-input-disabled", !opts.showInput);
+ container.toggleClass("sp-alpha-enabled", opts.showAlpha);
+ container.toggleClass("sp-clear-enabled", allowEmpty);
+ container.toggleClass("sp-buttons-disabled", !opts.showButtons);
+ container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly);
+ container.toggleClass("sp-palette-disabled", !opts.showPalette);
+ container.toggleClass("sp-palette-only", opts.showPaletteOnly);
+ container.toggleClass("sp-initial-disabled", !opts.showInitial);
+ container.addClass(opts.className).addClass(opts.containerClassName);
+
+ reflow();
+ }
+
+ function initialize() {
+
+ if (IE) {
+ container.find("*:not(input)").attr("unselectable", "on");
+ }
+
+ applyOptions();
+
+ if (shouldReplace) {
+ boundElement.after(replacer).hide();
+ }
+
+ if (!allowEmpty) {
+ clearButton.hide();
+ }
+
+ if (flat) {
+ boundElement.after(container).hide();
+ }
+ else {
+
+ var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo);
+ if (appendTo.length !== 1) {
+ appendTo = $("body");
+ }
+
+ appendTo.append(container);
+ }
+
+ updateSelectionPaletteFromStorage();
+
+ offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
+ if (!disabled) {
+ toggle();
+ }
+
+ e.stopPropagation();
+
+ if (!$(e.target).is("input")) {
+ e.preventDefault();
+ }
+ });
+
+ if(boundElement.is(":disabled") || (opts.disabled === true)) {
+ disable();
+ }
+
+ // Prevent clicks from bubbling up to document. This would cause it to be hidden.
+ container.click(stopPropagation);
+
+ // Handle user typed input
+ textInput.change(setFromTextInput);
+ textInput.bind("paste", function () {
+ setTimeout(setFromTextInput, 1);
+ });
+ textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });
+
+ cancelButton.text(opts.cancelText);
+ cancelButton.bind("click.spectrum", function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ revert();
+ hide();
+ });
+
+ clearButton.attr("title", opts.clearText);
+ clearButton.bind("click.spectrum", function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ isEmpty = true;
+ move();
+
+ if(flat) {
+ //for the flat style, this is a change event
+ updateOriginalInput(true);
+ }
+ });
+
+ chooseButton.text(opts.chooseText);
+ chooseButton.bind("click.spectrum", function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (IE && textInput.is(":focus")) {
+ textInput.trigger('change');
+ }
+
+ if (isValid()) {
+ updateOriginalInput(true);
+ hide();
+ }
+ });
+
+ toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
+ toggleButton.bind("click.spectrum", function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ opts.showPaletteOnly = !opts.showPaletteOnly;
+
+ // To make sure the Picker area is drawn on the right, next to the
+ // Palette area (and not below the palette), first move the Palette
+ // to the left to make space for the picker, plus 5px extra.
+ // The 'applyOptions' function puts the whole container back into place
+ // and takes care of the button-text and the sp-palette-only CSS class.
+ if (!opts.showPaletteOnly && !flat) {
+ container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5));
+ }
+ applyOptions();
+ });
+
+ draggable(alphaSlider, function (dragX, dragY, e) {
+ currentAlpha = (dragX / alphaWidth);
+ isEmpty = false;
+ if (e.shiftKey) {
+ currentAlpha = Math.round(currentAlpha * 10) / 10;
+ }
+
+ move();
+ }, dragStart, dragStop);
+
+ draggable(slider, function (dragX, dragY) {
+ currentHue = parseFloat(dragY / slideHeight);
+ isEmpty = false;
+ if (!opts.showAlpha) {
+ currentAlpha = 1;
+ }
+ move();
+ }, dragStart, dragStop);
+
+ draggable(dragger, function (dragX, dragY, e) {
+
+ // shift+drag should snap the movement to either the x or y axis.
+ if (!e.shiftKey) {
+ shiftMovementDirection = null;
+ }
+ else if (!shiftMovementDirection) {
+ var oldDragX = currentSaturation * dragWidth;
+ var oldDragY = dragHeight - (currentValue * dragHeight);
+ var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY);
+
+ shiftMovementDirection = furtherFromX ? "x" : "y";
+ }
+
+ var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x";
+ var setValue = !shiftMovementDirection || shiftMovementDirection === "y";
+
+ if (setSaturation) {
+ currentSaturation = parseFloat(dragX / dragWidth);
+ }
+ if (setValue) {
+ currentValue = parseFloat((dragHeight - dragY) / dragHeight);
+ }
+
+ isEmpty = false;
+ if (!opts.showAlpha) {
+ currentAlpha = 1;
+ }
+
+ move();
+
+ }, dragStart, dragStop);
+
+ if (!!initialColor) {
+ set(initialColor);
+
+ // In case color was black - update the preview UI and set the format
+ // since the set function will not run (default color is black).
+ updateUI();
+ currentPreferredFormat = preferredFormat || tinycolor(initialColor).format;
+
+ addColorToSelectionPalette(initialColor);
+ }
+ else {
+ updateUI();
+ }
+
+ if (flat) {
+ show();
+ }
+
+ function paletteElementClick(e) {
+ if (e.data && e.data.ignore) {
+ set($(e.target).closest(".sp-thumb-el").data("color"));
+ move();
+ }
+ else {
+ set($(e.target).closest(".sp-thumb-el").data("color"));
+ move();
+ updateOriginalInput(true);
+ if (opts.hideAfterPaletteSelect) {
+ hide();
+ }
+ }
+
+ return false;
+ }
+
+ var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
+ paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick);
+ initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick);
+ }
+
+ function updateSelectionPaletteFromStorage() {
+
+ if (localStorageKey && window.localStorage) {
+
+ // Migrate old palettes over to new format. May want to remove this eventually.
+ try {
+ var oldPalette = window.localStorage[localStorageKey].split(",#");
+ if (oldPalette.length > 1) {
+ delete window.localStorage[localStorageKey];
+ $.each(oldPalette, function(i, c) {
+ addColorToSelectionPalette(c);
+ });
+ }
+ }
+ catch(e) { }
+
+ try {
+ selectionPalette = window.localStorage[localStorageKey].split(";");
+ }
+ catch (e) { }
+ }
+ }
+
+ function addColorToSelectionPalette(color) {
+ if (showSelectionPalette) {
+ var rgb = tinycolor(color).toRgbString();
+ if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) {
+ selectionPalette.push(rgb);
+ while(selectionPalette.length > maxSelectionSize) {
+ selectionPalette.shift();
+ }
+ }
+
+ if (localStorageKey && window.localStorage) {
+ try {
+ window.localStorage[localStorageKey] = selectionPalette.join(";");
+ }
+ catch(e) { }
+ }
+ }
+ }
+
+ function getUniqueSelectionPalette() {
+ var unique = [];
+ if (opts.showPalette) {
+ for (var i = 0; i < selectionPalette.length; i++) {
+ var rgb = tinycolor(selectionPalette[i]).toRgbString();
+
+ if (!paletteLookup[rgb]) {
+ unique.push(selectionPalette[i]);
+ }
+ }
+ }
+
+ return unique.reverse().slice(0, opts.maxSelectionSize);
+ }
+
+ function drawPalette() {
+
+ var currentColor = get();
+
+ var html = $.map(paletteArray, function (palette, i) {
+ return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts);
+ });
+
+ updateSelectionPaletteFromStorage();
+
+ if (selectionPalette) {
+ html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts));
+ }
+
+ paletteContainer.html(html.join(""));
+ }
+
+ function drawInitial() {
+ if (opts.showInitial) {
+ var initial = colorOnShow;
+ var current = get();
+ initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts));
+ }
+ }
+
+ function dragStart() {
+ if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) {
+ reflow();
+ }
+ isDragging = true;
+ container.addClass(draggingClass);
+ shiftMovementDirection = null;
+ boundElement.trigger('dragstart.spectrum', [ get() ]);
+ }
+
+ function dragStop() {
+ isDragging = false;
+ container.removeClass(draggingClass);
+ boundElement.trigger('dragstop.spectrum', [ get() ]);
+ }
+
+ function setFromTextInput() {
+
+ var value = textInput.val();
+
+ if ((value === null || value === "") && allowEmpty) {
+ set(null);
+ updateOriginalInput(true);
+ }
+ else {
+ var tiny = tinycolor(value);
+ if (tiny.isValid()) {
+ set(tiny);
+ updateOriginalInput(true);
+ }
+ else {
+ textInput.addClass("sp-validation-error");
+ }
+ }
+ }
+
+ function toggle() {
+ if (visible) {
+ hide();
+ }
+ else {
+ show();
+ }
+ }
+
+ function show() {
+ var event = $.Event('beforeShow.spectrum');
+
+ if (visible) {
+ reflow();
+ return;
+ }
+
+ boundElement.trigger(event, [ get() ]);
+
+ if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) {
+ return;
+ }
+
+ hideAll();
+ visible = true;
+
+ $(doc).bind("keydown.spectrum", onkeydown);
+ $(doc).bind("click.spectrum", clickout);
+ $(window).bind("resize.spectrum", resize);
+ replacer.addClass("sp-active");
+ container.removeClass("sp-hidden");
+
+ reflow();
+ updateUI();
+
+ colorOnShow = get();
+
+ drawInitial();
+ callbacks.show(colorOnShow);
+ boundElement.trigger('show.spectrum', [ colorOnShow ]);
+ }
+
+ function onkeydown(e) {
+ // Close on ESC
+ if (e.keyCode === 27) {
+ hide();
+ }
+ }
+
+ function clickout(e) {
+ // Return on right click.
+ if (e.button == 2) { return; }
+
+ // If a drag event was happening during the mouseup, don't hide
+ // on click.
+ if (isDragging) { return; }
+
+ if (clickoutFiresChange) {
+ updateOriginalInput(true);
+ }
+ else {
+ revert();
+ }
+ hide();
+ }
+
+ function hide() {
+ // Return if hiding is unnecessary
+ if (!visible || flat) { return; }
+ visible = false;
+
+ $(doc).unbind("keydown.spectrum", onkeydown);
+ $(doc).unbind("click.spectrum", clickout);
+ $(window).unbind("resize.spectrum", resize);
+
+ replacer.removeClass("sp-active");
+ container.addClass("sp-hidden");
+
+ callbacks.hide(get());
+ boundElement.trigger('hide.spectrum', [ get() ]);
+ }
+
+ function revert() {
+ set(colorOnShow, true);
+ }
+
+ function set(color, ignoreFormatChange) {
+ if (tinycolor.equals(color, get())) {
+ // Update UI just in case a validation error needs
+ // to be cleared.
+ updateUI();
+ return;
+ }
+
+ var newColor, newHsv;
+ if (!color && allowEmpty) {
+ isEmpty = true;
+ } else {
+ isEmpty = false;
+ newColor = tinycolor(color);
+ newHsv = newColor.toHsv();
+
+ currentHue = (newHsv.h % 360) / 360;
+ currentSaturation = newHsv.s;
+ currentValue = newHsv.v;
+ currentAlpha = newHsv.a;
+ }
+ updateUI();
+
+ if (newColor && newColor.isValid() && !ignoreFormatChange) {
+ currentPreferredFormat = preferredFormat || newColor.getFormat();
+ }
+ }
+
+ function get(opts) {
+ opts = opts || { };
+
+ if (allowEmpty && isEmpty) {
+ return null;
+ }
+
+ return tinycolor.fromRatio({
+ h: currentHue,
+ s: currentSaturation,
+ v: currentValue,
+ a: Math.round(currentAlpha * 100) / 100
+ }, { format: opts.format || currentPreferredFormat });
+ }
+
+ function isValid() {
+ return !textInput.hasClass("sp-validation-error");
+ }
+
+ function move() {
+ updateUI();
+
+ callbacks.move(get());
+ boundElement.trigger('move.spectrum', [ get() ]);
+ }
+
+ function updateUI() {
+
+ textInput.removeClass("sp-validation-error");
+
+ updateHelperLocations();
+
+ // Update dragger background color (gradients take care of saturation and value).
+ var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 });
+ dragger.css("background-color", flatColor.toHexString());
+
+ // Get a format that alpha will be included in (hex and names ignore alpha)
+ var format = currentPreferredFormat;
+ if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) {
+ if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") {
+ format = "rgb";
+ }
+ }
+
+ var realColor = get({ format: format }),
+ displayColor = '';
+
+ //reset background info for preview element
+ previewElement.removeClass("sp-clear-display");
+ previewElement.css('background-color', 'transparent');
+
+ if (!realColor && allowEmpty) {
+ // Update the replaced elements background with icon indicating no color selection
+ previewElement.addClass("sp-clear-display");
+ }
+ else {
+ var realHex = realColor.toHexString(),
+ realRgb = realColor.toRgbString();
+
+ // Update the replaced elements background color (with actual selected color)
+ if (rgbaSupport || realColor.alpha === 1) {
+ previewElement.css("background-color", realRgb);
+ }
+ else {
+ previewElement.css("background-color", "transparent");
+ previewElement.css("filter", realColor.toFilter());
+ }
+
+ if (opts.showAlpha) {
+ var rgb = realColor.toRgb();
+ rgb.a = 0;
+ var realAlpha = tinycolor(rgb).toRgbString();
+ var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")";
+
+ if (IE) {
+ alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex));
+ }
+ else {
+ alphaSliderInner.css("background", "-webkit-" + gradient);
+ alphaSliderInner.css("background", "-moz-" + gradient);
+ alphaSliderInner.css("background", "-ms-" + gradient);
+ // Use current syntax gradient on unprefixed property.
+ alphaSliderInner.css("background",
+ "linear-gradient(to right, " + realAlpha + ", " + realHex + ")");
+ }
+ }
+
+ displayColor = realColor.toString(format);
+ }
+
+ // Update the text entry input as it changes happen
+ if (opts.showInput) {
+ textInput.val(displayColor);
+ }
+
+ if (opts.showPalette) {
+ drawPalette();
+ }
+
+ drawInitial();
+ }
+
+ function updateHelperLocations() {
+ var s = currentSaturation;
+ var v = currentValue;
+
+ if(allowEmpty && isEmpty) {
+ //if selected color is empty, hide the helpers
+ alphaSlideHelper.hide();
+ slideHelper.hide();
+ dragHelper.hide();
+ }
+ else {
+ //make sure helpers are visible
+ alphaSlideHelper.show();
+ slideHelper.show();
+ dragHelper.show();
+
+ // Where to show the little circle in that displays your current selected color
+ var dragX = s * dragWidth;
+ var dragY = dragHeight - (v * dragHeight);
+ dragX = Math.max(
+ -dragHelperHeight,
+ Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
+ );
+ dragY = Math.max(
+ -dragHelperHeight,
+ Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
+ );
+ dragHelper.css({
+ "top": dragY + "px",
+ "left": dragX + "px"
+ });
+
+ var alphaX = currentAlpha * alphaWidth;
+ alphaSlideHelper.css({
+ "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px"
+ });
+
+ // Where to show the bar that displays your current selected hue
+ var slideY = (currentHue) * slideHeight;
+ slideHelper.css({
+ "top": (slideY - slideHelperHeight) + "px"
+ });
+ }
+ }
+
+ function updateOriginalInput(fireCallback) {
+ var color = get(),
+ displayColor = '',
+ hasChanged = !tinycolor.equals(color, colorOnShow);
+
+ if (color) {
+ displayColor = color.toString(currentPreferredFormat);
+ // Update the selection palette with the current color
+ addColorToSelectionPalette(color);
+ }
+
+ if (isInput) {
+ boundElement.val(displayColor);
+ }
+
+ if (fireCallback && hasChanged) {
+ callbacks.change(color);
+ boundElement.trigger('change', [ color ]);
+ }
+ }
+
+ function reflow() {
+ dragWidth = dragger.width();
+ dragHeight = dragger.height();
+ dragHelperHeight = dragHelper.height();
+ slideWidth = slider.width();
+ slideHeight = slider.height();
+ slideHelperHeight = slideHelper.height();
+ alphaWidth = alphaSlider.width();
+ alphaSlideHelperWidth = alphaSlideHelper.width();
+
+ if (!flat) {
+ container.css("position", "absolute");
+ if (opts.offset) {
+ container.offset(opts.offset);
+ } else {
+ container.offset(getOffset(container, offsetElement));
+ }
+ }
+
+ updateHelperLocations();
+
+ if (opts.showPalette) {
+ drawPalette();
+ }
+
+ boundElement.trigger('reflow.spectrum');
+ }
+
+ function destroy() {
+ boundElement.show();
+ offsetElement.unbind("click.spectrum touchstart.spectrum");
+ container.remove();
+ replacer.remove();
+ spectrums[spect.id] = null;
+ }
+
+ function option(optionName, optionValue) {
+ if (optionName === undefined) {
+ return $.extend({}, opts);
+ }
+ if (optionValue === undefined) {
+ return opts[optionName];
+ }
+
+ opts[optionName] = optionValue;
+ applyOptions();
+ }
+
+ function enable() {
+ disabled = false;
+ boundElement.attr("disabled", false);
+ offsetElement.removeClass("sp-disabled");
+ }
+
+ function disable() {
+ hide();
+ disabled = true;
+ boundElement.attr("disabled", true);
+ offsetElement.addClass("sp-disabled");
+ }
+
+ function setOffset(coord) {
+ opts.offset = coord;
+ reflow();
+ }
+
+ initialize();
+
+ var spect = {
+ show: show,
+ hide: hide,
+ toggle: toggle,
+ reflow: reflow,
+ option: option,
+ enable: enable,
+ disable: disable,
+ offset: setOffset,
+ set: function (c) {
+ set(c);
+ updateOriginalInput();
+ },
+ get: get,
+ destroy: destroy,
+ container: container
+ };
+
+ spect.id = spectrums.push(spect) - 1;
+
+ return spect;
+ }
+
+ /**
+ * checkOffset - get the offset below/above and left/right element depending on screen position
+ * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
+ */
+ function getOffset(picker, input) {
+ var extraY = 0;
+ var dpWidth = picker.outerWidth();
+ var dpHeight = picker.outerHeight();
+ var inputHeight = input.outerHeight();
+ var doc = picker[0].ownerDocument;
+ var docElem = doc.documentElement;
+ var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
+ var viewHeight = docElem.clientHeight + $(doc).scrollTop();
+ var offset = input.offset();
+ offset.top += inputHeight;
+
+ offset.left -=
+ Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
+ Math.abs(offset.left + dpWidth - viewWidth) : 0);
+
+ offset.top -=
+ Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
+ Math.abs(dpHeight + inputHeight - extraY) : extraY));
+
+ return offset;
+ }
+
+ /**
+ * noop - do nothing
+ */
+ function noop() {
+
+ }
+
+ /**
+ * stopPropagation - makes the code only doing this a little easier to read in line
+ */
+ function stopPropagation(e) {
+ e.stopPropagation();
+ }
+
+ /**
+ * Create a function bound to a given object
+ * Thanks to underscore.js
+ */
+ function bind(func, obj) {
+ var slice = Array.prototype.slice;
+ var args = slice.call(arguments, 2);
+ return function () {
+ return func.apply(obj, args.concat(slice.call(arguments)));
+ };
+ }
+
+ /**
+ * Lightweight drag helper. Handles containment within the element, so that
+ * when dragging, the x is within [0,element.width] and y is within [0,element.height]
+ */
+ function draggable(element, onmove, onstart, onstop) {
+ onmove = onmove || function () { };
+ onstart = onstart || function () { };
+ onstop = onstop || function () { };
+ var doc = document;
+ var dragging = false;
+ var offset = {};
+ var maxHeight = 0;
+ var maxWidth = 0;
+ var hasTouch = ('ontouchstart' in window);
+
+ var duringDragEvents = {};
+ duringDragEvents["selectstart"] = prevent;
+ duringDragEvents["dragstart"] = prevent;
+ duringDragEvents["touchmove mousemove"] = move;
+ duringDragEvents["touchend mouseup"] = stop;
+
+ function prevent(e) {
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ }
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+ e.returnValue = false;
+ }
+
+ function move(e) {
+ if (dragging) {
+ // Mouseup happened outside of window
+ if (IE && doc.documentMode < 9 && !e.button) {
+ return stop();
+ }
+
+ var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0];
+ var pageX = t0 && t0.pageX || e.pageX;
+ var pageY = t0 && t0.pageY || e.pageY;
+
+ var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
+ var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
+
+ if (hasTouch) {
+ // Stop scrolling in iOS
+ prevent(e);
+ }
+
+ onmove.apply(element, [dragX, dragY, e]);
+ }
+ }
+
+ function start(e) {
+ var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);
+
+ if (!rightclick && !dragging) {
+ if (onstart.apply(element, arguments) !== false) {
+ dragging = true;
+ maxHeight = $(element).height();
+ maxWidth = $(element).width();
+ offset = $(element).offset();
+
+ $(doc).bind(duringDragEvents);
+ $(doc.body).addClass("sp-dragging");
+
+ move(e);
+
+ prevent(e);
+ }
+ }
+ }
+
+ function stop() {
+ if (dragging) {
+ $(doc).unbind(duringDragEvents);
+ $(doc.body).removeClass("sp-dragging");
+
+ // Wait a tick before notifying observers to allow the click event
+ // to fire in Chrome.
+ setTimeout(function() {
+ onstop.apply(element, arguments);
+ }, 0);
+ }
+ dragging = false;
+ }
+
+ $(element).bind("touchstart mousedown", start);
+ }
+
+ function throttle(func, wait, debounce) {
+ var timeout;
+ return function () {
+ var context = this, args = arguments;
+ var throttler = function () {
+ timeout = null;
+ func.apply(context, args);
+ };
+ if (debounce) clearTimeout(timeout);
+ if (debounce || !timeout) timeout = setTimeout(throttler, wait);
+ };
+ }
+
+ function inputTypeColorSupport() {
+ return $.fn.spectrum.inputTypeColorSupport();
+ }
+
+ /**
+ * Define a jQuery plugin
+ */
+ var dataID = "spectrum.id";
+ $.fn.spectrum = function (opts, extra) {
+
+ if (typeof opts == "string") {
+
+ var returnValue = this;
+ var args = Array.prototype.slice.call( arguments, 1 );
+
+ this.each(function () {
+ var spect = spectrums[$(this).data(dataID)];
+ if (spect) {
+ var method = spect[opts];
+ if (!method) {
+ throw new Error( "Spectrum: no such method: '" + opts + "'" );
+ }
+
+ if (opts == "get") {
+ returnValue = spect.get();
+ }
+ else if (opts == "container") {
+ returnValue = spect.container;
+ }
+ else if (opts == "option") {
+ returnValue = spect.option.apply(spect, args);
+ }
+ else if (opts == "destroy") {
+ spect.destroy();
+ $(this).removeData(dataID);
+ }
+ else {
+ method.apply(spect, args);
+ }
+ }
+ });
+
+ return returnValue;
+ }
+
+ // Initializing a new instance of spectrum
+ return this.spectrum("destroy").each(function () {
+ var options = $.extend({}, opts, $(this).data());
+ var spect = spectrum(this, options);
+ $(this).data(dataID, spect.id);
+ });
+ };
+
+ $.fn.spectrum.load = true;
+ $.fn.spectrum.loadOpts = {};
+ $.fn.spectrum.draggable = draggable;
+ $.fn.spectrum.defaults = defaultOpts;
+ $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() {
+ if (typeof inputTypeColorSupport._cachedResult === "undefined") {
+ var colorInput = $("<input type='color'/>")[0]; // if color element is supported, value will default to not null
+ inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== "";
+ }
+ return inputTypeColorSupport._cachedResult;
+ };
+
+ $.spectrum = { };
+ $.spectrum.localization = { };
+ $.spectrum.palettes = { };
+
+ $.fn.spectrum.processNativeColorInputs = function () {
+ var colorInputs = $("input[type=color]");
+ if (colorInputs.length && !inputTypeColorSupport()) {
+ colorInputs.spectrum({
+ preferredFormat: "hex6"
+ });
+ }
+ };
+
+ // TinyColor v1.1.2
+ // https://github.com/bgrins/TinyColor
+ // Brian Grinstead, MIT License
+
+ (function() {
+
+ var trimLeft = /^[\s,#]+/,
+ trimRight = /\s+$/,
+ tinyCounter = 0,
+ math = Math,
+ mathRound = math.round,
+ mathMin = math.min,
+ mathMax = math.max,
+ mathRandom = math.random;
+
+ var tinycolor = function(color, opts) {
+
+ color = (color) ? color : '';
+ opts = opts || { };
+
+ // If input is already a tinycolor, return itself
+ if (color instanceof tinycolor) {
+ return color;
+ }
+ // If we are called as a function, call using new instead
+ if (!(this instanceof tinycolor)) {
+ return new tinycolor(color, opts);
+ }
+
+ var rgb = inputToRGB(color);
+ this._originalInput = color,
+ this._r = rgb.r,
+ this._g = rgb.g,
+ this._b = rgb.b,
+ this._a = rgb.a,
+ this._roundA = mathRound(100*this._a) / 100,
+ this._format = opts.format || rgb.format;
+ this._gradientType = opts.gradientType;
+
+ // Don't let the range of [0,255] come back in [0,1].
+ // Potentially lose a little bit of precision here, but will fix issues where
+ // .5 gets interpreted as half of the total, instead of half of 1
+ // If it was supposed to be 128, this was already taken care of by `inputToRgb`
+ if (this._r < 1) { this._r = mathRound(this._r); }
+ if (this._g < 1) { this._g = mathRound(this._g); }
+ if (this._b < 1) { this._b = mathRound(this._b); }
+
+ this._ok = rgb.ok;
+ this._tc_id = tinyCounter++;
+ };
+
+ tinycolor.prototype = {
+ isDark: function() {
+ return this.getBrightness() < 128;
+ },
+ isLight: function() {
+ return !this.isDark();
+ },
+ isValid: function() {
+ return this._ok;
+ },
+ getOriginalInput: function() {
+ return this._originalInput;
+ },
+ getFormat: function() {
+ return this._format;
+ },
+ getAlpha: function() {
+ return this._a;
+ },
+ getBrightness: function() {
+ var rgb = this.toRgb();
+ return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
+ },
+ setAlpha: function(value) {
+ this._a = boundAlpha(value);
+ this._roundA = mathRound(100*this._a) / 100;
+ return this;
+ },
+ toHsv: function() {
+ var hsv = rgbToHsv(this._r, this._g, this._b);
+ return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
+ },
+ toHsvString: function() {
+ var hsv = rgbToHsv(this._r, this._g, this._b);
+ var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
+ return (this._a == 1) ?
+ "hsv(" + h + ", " + s + "%, " + v + "%)" :
+ "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
+ },
+ toHsl: function() {
+ var hsl = rgbToHsl(this._r, this._g, this._b);
+ return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
+ },
+ toHslString: function() {
+ var hsl = rgbToHsl(this._r, this._g, this._b);
+ var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
+ return (this._a == 1) ?
+ "hsl(" + h + ", " + s + "%, " + l + "%)" :
+ "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")";
+ },
+ toHex: function(allow3Char) {
+ return rgbToHex(this._r, this._g, this._b, allow3Char);
+ },
+ toHexString: function(allow3Char) {
+ return '#' + this.toHex(allow3Char);
+ },
+ toHex8: function() {
+ return rgbaToHex(this._r, this._g, this._b, this._a);
+ },
+ toHex8String: function() {
+ return '#' + this.toHex8();
+ },
+ toRgb: function() {
+ return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
+ },
+ toRgbString: function() {
+ return (this._a == 1) ?
+ "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
+ "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
+ },
+ toPercentageRgb: function() {
+ return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
+ },
+ toPercentageRgbString: function() {
+ return (this._a == 1) ?
+ "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
+ "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
+ },
+ toName: function() {
+ if (this._a === 0) {
+ return "transparent";
+ }
+
+ if (this._a < 1) {
+ return false;
+ }
+
+ return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
+ },
+ toFilter: function(secondColor) {
+ var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a);
+ var secondHex8String = hex8String;
+ var gradientType = this._gradientType ? "GradientType = 1, " : "";
+
+ if (secondColor) {
+ var s = tinycolor(secondColor);
+ secondHex8String = s.toHex8String();
+ }
+
+ return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")";
+ },
+ toString: function(format) {
+ var formatSet = !!format;
+ format = format || this._format;
+
+ var formattedString = false;
+ var hasAlpha = this._a < 1 && this._a >= 0;
+ var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");
+
+ if (needsAlphaFormat) {
+ // Special case for "transparent", all other non-alpha formats
+ // will return rgba when there is transparency.
+ if (format === "name" && this._a === 0) {
+ return this.toName();
+ }
+ return this.toRgbString();
+ }
+ if (format === "rgb") {
+ formattedString = this.toRgbString();
+ }
+ if (format === "prgb") {
+ formattedString = this.toPercentageRgbString();
+ }
+ if (format === "hex" || format === "hex6") {
+ formattedString = this.toHexString();
+ }
+ if (format === "hex3") {
+ formattedString = this.toHexString(true);
+ }
+ if (format === "hex8") {
+ formattedString = this.toHex8String();
+ }
+ if (format === "name") {
+ formattedString = this.toName();
+ }
+ if (format === "hsl") {
+ formattedString = this.toHslString();
+ }
+ if (format === "hsv") {
+ formattedString = this.toHsvString();
+ }
+
+ return formattedString || this.toHexString();
+ },
+
+ _applyModification: function(fn, args) {
+ var color = fn.apply(null, [this].concat([].slice.call(args)));
+ this._r = color._r;
+ this._g = color._g;
+ this._b = color._b;
+ this.setAlpha(color._a);
+ return this;
+ },
+ lighten: function() {
+ return this._applyModification(lighten, arguments);
+ },
+ brighten: function() {
+ return this._applyModification(brighten, arguments);
+ },
+ darken: function() {
+ return this._applyModification(darken, arguments);
+ },
+ desaturate: function() {
+ return this._applyModification(desaturate, arguments);
+ },
+ saturate: function() {
+ return this._applyModification(saturate, arguments);
+ },
+ greyscale: function() {
+ return this._applyModification(greyscale, arguments);
+ },
+ spin: function() {
+ return this._applyModification(spin, arguments);
+ },
+
+ _applyCombination: function(fn, args) {
+ return fn.apply(null, [this].concat([].slice.call(args)));
+ },
+ analogous: function() {
+ return this._applyCombination(analogous, arguments);
+ },
+ complement: function() {
+ return this._applyCombination(complement, arguments);
+ },
+ monochromatic: function() {
+ return this._applyCombination(monochromatic, arguments);
+ },
+ splitcomplement: function() {
+ return this._applyCombination(splitcomplement, arguments);
+ },
+ triad: function() {
+ return this._applyCombination(triad, arguments);
+ },
+ tetrad: function() {
+ return this._applyCombination(tetrad, arguments);
+ }
+ };
+
+ // If input is an object, force 1 into "1.0" to handle ratios properly
+ // String input requires "1.0" as input, so 1 will be treated as 1
+ tinycolor.fromRatio = function(color, opts) {
+ if (typeof color == "object") {
+ var newColor = {};
+ for (var i in color) {
+ if (color.hasOwnProperty(i)) {
+ if (i === "a") {
+ newColor[i] = color[i];
+ }
+ else {
+ newColor[i] = convertToPercentage(color[i]);
+ }
+ }
+ }
+ color = newColor;
+ }
+
+ return tinycolor(color, opts);
+ };
+
+ // Given a string or object, convert that input to RGB
+ // Possible string inputs:
+ //
+ // "red"
+ // "#f00" or "f00"
+ // "#ff0000" or "ff0000"
+ // "#ff000000" or "ff000000"
+ // "rgb 255 0 0" or "rgb (255, 0, 0)"
+ // "rgb 1.0 0 0" or "rgb (1, 0, 0)"
+ // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
+ // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
+ // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
+ // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
+ // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
+ //
+ function inputToRGB(color) {
+
+ var rgb = { r: 0, g: 0, b: 0 };
+ var a = 1;
+ var ok = false;
+ var format = false;
+
+ if (typeof color == "string") {
+ color = stringInputToObject(color);
+ }
+
+ if (typeof color == "object") {
+ if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
+ rgb = rgbToRgb(color.r, color.g, color.b);
+ ok = true;
+ format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
+ }
+ else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
+ color.s = convertToPercentage(color.s);
+ color.v = convertToPercentage(color.v);
+ rgb = hsvToRgb(color.h, color.s, color.v);
+ ok = true;
+ format = "hsv";
+ }
+ else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
+ color.s = convertToPercentage(color.s);
+ color.l = convertToPercentage(color.l);
+ rgb = hslToRgb(color.h, color.s, color.l);
+ ok = true;
+ format = "hsl";
+ }
+
+ if (color.hasOwnProperty("a")) {
+ a = color.a;
+ }
+ }
+
+ a = boundAlpha(a);
+
+ return {
+ ok: ok,
+ format: color.format || format,
+ r: mathMin(255, mathMax(rgb.r, 0)),
+ g: mathMin(255, mathMax(rgb.g, 0)),
+ b: mathMin(255, mathMax(rgb.b, 0)),
+ a: a
+ };
+ }
+
+
+ // Conversion Functions
+ // --------------------
+
+ // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
+ // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
+
+ // `rgbToRgb`
+ // Handle bounds / percentage checking to conform to CSS color spec
+ // <http://www.w3.org/TR/css3-color/>
+ // *Assumes:* r, g, b in [0, 255] or [0, 1]
+ // *Returns:* { r, g, b } in [0, 255]
+ function rgbToRgb(r, g, b){
+ return {
+ r: bound01(r, 255) * 255,
+ g: bound01(g, 255) * 255,
+ b: bound01(b, 255) * 255
+ };
+ }
+
+ // `rgbToHsl`
+ // Converts an RGB color value to HSL.
+ // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
+ // *Returns:* { h, s, l } in [0,1]
+ function rgbToHsl(r, g, b) {
+
+ r = bound01(r, 255);
+ g = bound01(g, 255);
+ b = bound01(b, 255);
+
+ var max = mathMax(r, g, b), min = mathMin(r, g, b);
+ var h, s, l = (max + min) / 2;
+
+ if(max == min) {
+ h = s = 0; // achromatic
+ }
+ else {
+ var d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch(max) {
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+
+ h /= 6;
+ }
+
+ return { h: h, s: s, l: l };
+ }
+
+ // `hslToRgb`
+ // Converts an HSL color value to RGB.
+ // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
+ // *Returns:* { r, g, b } in the set [0, 255]
+ function hslToRgb(h, s, l) {
+ var r, g, b;
+
+ h = bound01(h, 360);
+ s = bound01(s, 100);
+ l = bound01(l, 100);
+
+ function hue2rgb(p, q, t) {
+ if(t < 0) t += 1;
+ if(t > 1) t -= 1;
+ if(t < 1/6) return p + (q - p) * 6 * t;
+ if(t < 1/2) return q;
+ if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+ return p;
+ }
+
+ if(s === 0) {
+ r = g = b = l; // achromatic
+ }
+ else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1/3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3);
+ }
+
+ return { r: r * 255, g: g * 255, b: b * 255 };
+ }
+
+ // `rgbToHsv`
+ // Converts an RGB color value to HSV
+ // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
+ // *Returns:* { h, s, v } in [0,1]
+ function rgbToHsv(r, g, b) {
+
+ r = bound01(r, 255);
+ g = bound01(g, 255);
+ b = bound01(b, 255);
+
+ var max = mathMax(r, g, b), min = mathMin(r, g, b);
+ var h, s, v = max;
+
+ var d = max - min;
+ s = max === 0 ? 0 : d / max;
+
+ if(max == min) {
+ h = 0; // achromatic
+ }
+ else {
+ switch(max) {
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+ h /= 6;
+ }
+ return { h: h, s: s, v: v };
+ }
+
+ // `hsvToRgb`
+ // Converts an HSV color value to RGB.
+ // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
+ // *Returns:* { r, g, b } in the set [0, 255]
+ function hsvToRgb(h, s, v) {
+
+ h = bound01(h, 360) * 6;
+ s = bound01(s, 100);
+ v = bound01(v, 100);
+
+ var i = math.floor(h),
+ f = h - i,
+ p = v * (1 - s),
+ q = v * (1 - f * s),
+ t = v * (1 - (1 - f) * s),
+ mod = i % 6,
+ r = [v, q, p, p, t, v][mod],
+ g = [t, v, v, q, p, p][mod],
+ b = [p, p, t, v, v, q][mod];
+
+ return { r: r * 255, g: g * 255, b: b * 255 };
+ }
+
+ // `rgbToHex`
+ // Converts an RGB color to hex
+ // Assumes r, g, and b are contained in the set [0, 255]
+ // Returns a 3 or 6 character hex
+ function rgbToHex(r, g, b, allow3Char) {
+
+ var hex = [
+ pad2(mathRound(r).toString(16)),
+ pad2(mathRound(g).toString(16)),
+ pad2(mathRound(b).toString(16))
+ ];
+
+ // Return a 3 character hex if possible
+ if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
+ }
+
+ return hex.join("");
+ }
+ // `rgbaToHex`
+ // Converts an RGBA color plus alpha transparency to hex
+ // Assumes r, g, b and a are contained in the set [0, 255]
+ // Returns an 8 character hex
+ function rgbaToHex(r, g, b, a) {
+
+ var hex = [
+ pad2(convertDecimalToHex(a)),
+ pad2(mathRound(r).toString(16)),
+ pad2(mathRound(g).toString(16)),
+ pad2(mathRound(b).toString(16))
+ ];
+
+ return hex.join("");
+ }
+
+ // `equals`
+ // Can be called with any tinycolor input
+ tinycolor.equals = function (color1, color2) {
+ if (!color1 || !color2) { return false; }
+ return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
+ };
+ tinycolor.random = function() {
+ return tinycolor.fromRatio({
+ r: mathRandom(),
+ g: mathRandom(),
+ b: mathRandom()
+ });
+ };
+
+
+ // Modification Functions
+ // ----------------------
+ // Thanks to less.js for some of the basics here
+ // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>
+
+ function desaturate(color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var hsl = tinycolor(color).toHsl();
+ hsl.s -= amount / 100;
+ hsl.s = clamp01(hsl.s);
+ return tinycolor(hsl);
+ }
+
+ function saturate(color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var hsl = tinycolor(color).toHsl();
+ hsl.s += amount / 100;
+ hsl.s = clamp01(hsl.s);
+ return tinycolor(hsl);
+ }
+
+ function greyscale(color) {
+ return tinycolor(color).desaturate(100);
+ }
+
+ function lighten (color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var hsl = tinycolor(color).toHsl();
+ hsl.l += amount / 100;
+ hsl.l = clamp01(hsl.l);
+ return tinycolor(hsl);
+ }
+
+ function brighten(color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var rgb = tinycolor(color).toRgb();
+ rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100))));
+ rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100))));
+ rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100))));
+ return tinycolor(rgb);
+ }
+
+ function darken (color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var hsl = tinycolor(color).toHsl();
+ hsl.l -= amount / 100;
+ hsl.l = clamp01(hsl.l);
+ return tinycolor(hsl);
+ }
+
+ // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
+ // Values outside of this range will be wrapped into this range.
+ function spin(color, amount) {
+ var hsl = tinycolor(color).toHsl();
+ var hue = (mathRound(hsl.h) + amount) % 360;
+ hsl.h = hue < 0 ? 360 + hue : hue;
+ return tinycolor(hsl);
+ }
+
+ // Combination Functions
+ // ---------------------
+ // Thanks to jQuery xColor for some of the ideas behind these
+ // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>
+
+ function complement(color) {
+ var hsl = tinycolor(color).toHsl();
+ hsl.h = (hsl.h + 180) % 360;
+ return tinycolor(hsl);
+ }
+
+ function triad(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [
+ tinycolor(color),
+ tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
+ tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
+ ];
+ }
+
+ function tetrad(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [
+ tinycolor(color),
+ tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
+ tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
+ tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
+ ];
+ }
+
+ function splitcomplement(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [
+ tinycolor(color),
+ tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
+ tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
+ ];
+ }
+
+ function analogous(color, results, slices) {
+ results = results || 6;
+ slices = slices || 30;
+
+ var hsl = tinycolor(color).toHsl();
+ var part = 360 / slices;
+ var ret = [tinycolor(color)];
+
+ for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
+ hsl.h = (hsl.h + part) % 360;
+ ret.push(tinycolor(hsl));
+ }
+ return ret;
+ }
+
+ function monochromatic(color, results) {
+ results = results || 6;
+ var hsv = tinycolor(color).toHsv();
+ var h = hsv.h, s = hsv.s, v = hsv.v;
+ var ret = [];
+ var modification = 1 / results;
+
+ while (results--) {
+ ret.push(tinycolor({ h: h, s: s, v: v}));
+ v = (v + modification) % 1;
+ }
+
+ return ret;
+ }
+
+ // Utility Functions
+ // ---------------------
+
+ tinycolor.mix = function(color1, color2, amount) {
+ amount = (amount === 0) ? 0 : (amount || 50);
+
+ var rgb1 = tinycolor(color1).toRgb();
+ var rgb2 = tinycolor(color2).toRgb();
+
+ var p = amount / 100;
+ var w = p * 2 - 1;
+ var a = rgb2.a - rgb1.a;
+
+ var w1;
+
+ if (w * a == -1) {
+ w1 = w;
+ } else {
+ w1 = (w + a) / (1 + w * a);
+ }
+
+ w1 = (w1 + 1) / 2;
+
+ var w2 = 1 - w1;
+
+ var rgba = {
+ r: rgb2.r * w1 + rgb1.r * w2,
+ g: rgb2.g * w1 + rgb1.g * w2,
+ b: rgb2.b * w1 + rgb1.b * w2,
+ a: rgb2.a * p + rgb1.a * (1 - p)
+ };
+
+ return tinycolor(rgba);
+ };
+
+
+ // Readability Functions
+ // ---------------------
+ // <http://www.w3.org/TR/AERT#color-contrast>
+
+ // `readability`
+ // Analyze the 2 colors and returns an object with the following properties:
+ // `brightness`: difference in brightness between the two colors
+ // `color`: difference in color/hue between the two colors
+ tinycolor.readability = function(color1, color2) {
+ var c1 = tinycolor(color1);
+ var c2 = tinycolor(color2);
+ var rgb1 = c1.toRgb();
+ var rgb2 = c2.toRgb();
+ var brightnessA = c1.getBrightness();
+ var brightnessB = c2.getBrightness();
+ var colorDiff = (
+ Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) +
+ Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) +
+ Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b)
+ );
+
+ return {
+ brightness: Math.abs(brightnessA - brightnessB),
+ color: colorDiff
+ };
+ };
+
+ // `readable`
+ // http://www.w3.org/TR/AERT#color-contrast
+ // Ensure that foreground and background color combinations provide sufficient contrast.
+ // *Example*
+ // tinycolor.isReadable("#000", "#111") => false
+ tinycolor.isReadable = function(color1, color2) {
+ var readability = tinycolor.readability(color1, color2);
+ return readability.brightness > 125 && readability.color > 500;
+ };
+
+ // `mostReadable`
+ // Given a base color and a list of possible foreground or background
+ // colors for that base, returns the most readable color.
+ // *Example*
+ // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000"
+ tinycolor.mostReadable = function(baseColor, colorList) {
+ var bestColor = null;
+ var bestScore = 0;
+ var bestIsReadable = false;
+ for (var i=0; i < colorList.length; i++) {
+
+ // We normalize both around the "acceptable" breaking point,
+ // but rank brightness constrast higher than hue.
+
+ var readability = tinycolor.readability(baseColor, colorList[i]);
+ var readable = readability.brightness > 125 && readability.color > 500;
+ var score = 3 * (readability.brightness / 125) + (readability.color / 500);
+
+ if ((readable && ! bestIsReadable) ||
+ (readable && bestIsReadable && score > bestScore) ||
+ ((! readable) && (! bestIsReadable) && score > bestScore)) {
+ bestIsReadable = readable;
+ bestScore = score;
+ bestColor = tinycolor(colorList[i]);
+ }
+ }
+ return bestColor;
+ };
+
+
+ // Big List of Colors
+ // ------------------
+ // <http://www.w3.org/TR/css3-color/#svg-color>
+ var names = tinycolor.names = {
+ aliceblue: "f0f8ff",
+ antiquewhite: "faebd7",
+ aqua: "0ff",
+ aquamarine: "7fffd4",
+ azure: "f0ffff",
+ beige: "f5f5dc",
+ bisque: "ffe4c4",
+ black: "000",
+ blanchedalmond: "ffebcd",
+ blue: "00f",
+ blueviolet: "8a2be2",
+ brown: "a52a2a",
+ burlywood: "deb887",
+ burntsienna: "ea7e5d",
+ cadetblue: "5f9ea0",
+ chartreuse: "7fff00",
+ chocolate: "d2691e",
+ coral: "ff7f50",
+ cornflowerblue: "6495ed",
+ cornsilk: "fff8dc",
+ crimson: "dc143c",
+ cyan: "0ff",
+ darkblue: "00008b",
+ darkcyan: "008b8b",
+ darkgoldenrod: "b8860b",
+ darkgray: "a9a9a9",
+ darkgreen: "006400",
+ darkgrey: "a9a9a9",
+ darkkhaki: "bdb76b",
+ darkmagenta: "8b008b",
+ darkolivegreen: "556b2f",
+ darkorange: "ff8c00",
+ darkorchid: "9932cc",
+ darkred: "8b0000",
+ darksalmon: "e9967a",
+ darkseagreen: "8fbc8f",
+ darkslateblue: "483d8b",
+ darkslategray: "2f4f4f",
+ darkslategrey: "2f4f4f",
+ darkturquoise: "00ced1",
+ darkviolet: "9400d3",
+ deeppink: "ff1493",
+ deepskyblue: "00bfff",
+ dimgray: "696969",
+ dimgrey: "696969",
+ dodgerblue: "1e90ff",
+ firebrick: "b22222",
+ floralwhite: "fffaf0",
+ forestgreen: "228b22",
+ fuchsia: "f0f",
+ gainsboro: "dcdcdc",
+ ghostwhite: "f8f8ff",
+ gold: "ffd700",
+ goldenrod: "daa520",
+ gray: "808080",
+ green: "008000",
+ greenyellow: "adff2f",
+ grey: "808080",
+ honeydew: "f0fff0",
+ hotpink: "ff69b4",
+ indianred: "cd5c5c",
+ indigo: "4b0082",
+ ivory: "fffff0",
+ khaki: "f0e68c",
+ lavender: "e6e6fa",
+ lavenderblush: "fff0f5",
+ lawngreen: "7cfc00",
+ lemonchiffon: "fffacd",
+ lightblue: "add8e6",
+ lightcoral: "f08080",
+ lightcyan: "e0ffff",
+ lightgoldenrodyellow: "fafad2",
+ lightgray: "d3d3d3",
+ lightgreen: "90ee90",
+ lightgrey: "d3d3d3",
+ lightpink: "ffb6c1",
+ lightsalmon: "ffa07a",
+ lightseagreen: "20b2aa",
+ lightskyblue: "87cefa",
+ lightslategray: "789",
+ lightslategrey: "789",
+ lightsteelblue: "b0c4de",
+ lightyellow: "ffffe0",
+ lime: "0f0",
+ limegreen: "32cd32",
+ linen: "faf0e6",
+ magenta: "f0f",
+ maroon: "800000",
+ mediumaquamarine: "66cdaa",
+ mediumblue: "0000cd",
+ mediumorchid: "ba55d3",
+ mediumpurple: "9370db",
+ mediumseagreen: "3cb371",
+ mediumslateblue: "7b68ee",
+ mediumspringgreen: "00fa9a",
+ mediumturquoise: "48d1cc",
+ mediumvioletred: "c71585",
+ midnightblue: "191970",
+ mintcream: "f5fffa",
+ mistyrose: "ffe4e1",
+ moccasin: "ffe4b5",
+ navajowhite: "ffdead",
+ navy: "000080",
+ oldlace: "fdf5e6",
+ olive: "808000",
+ olivedrab: "6b8e23",
+ orange: "ffa500",
+ orangered: "ff4500",
+ orchid: "da70d6",
+ palegoldenrod: "eee8aa",
+ palegreen: "98fb98",
+ paleturquoise: "afeeee",
+ palevioletred: "db7093",
+ papayawhip: "ffefd5",
+ peachpuff: "ffdab9",
+ peru: "cd853f",
+ pink: "ffc0cb",
+ plum: "dda0dd",
+ powderblue: "b0e0e6",
+ purple: "800080",
+ rebeccapurple: "663399",
+ red: "f00",
+ rosybrown: "bc8f8f",
+ royalblue: "4169e1",
+ saddlebrown: "8b4513",
+ salmon: "fa8072",
+ sandybrown: "f4a460",
+ seagreen: "2e8b57",
+ seashell: "fff5ee",
+ sienna: "a0522d",
+ silver: "c0c0c0",
+ skyblue: "87ceeb",
+ slateblue: "6a5acd",
+ slategray: "708090",
+ slategrey: "708090",
+ snow: "fffafa",
+ springgreen: "00ff7f",
+ steelblue: "4682b4",
+ tan: "d2b48c",
+ teal: "008080",
+ thistle: "d8bfd8",
+ tomato: "ff6347",
+ turquoise: "40e0d0",
+ violet: "ee82ee",
+ wheat: "f5deb3",
+ white: "fff",
+ whitesmoke: "f5f5f5",
+ yellow: "ff0",
+ yellowgreen: "9acd32"
+ };
+
+ // Make it easy to access colors via `hexNames[hex]`
+ var hexNames = tinycolor.hexNames = flip(names);
+
+
+ // Utilities
+ // ---------
+
+ // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
+ function flip(o) {
+ var flipped = { };
+ for (var i in o) {
+ if (o.hasOwnProperty(i)) {
+ flipped[o[i]] = i;
+ }
+ }
+ return flipped;
+ }
+
+ // Return a valid alpha value [0,1] with all invalid values being set to 1
+ function boundAlpha(a) {
+ a = parseFloat(a);
+
+ if (isNaN(a) || a < 0 || a > 1) {
+ a = 1;
+ }
+
+ return a;
+ }
+
+ // Take input from [0, n] and return it as [0, 1]
+ function bound01(n, max) {
+ if (isOnePointZero(n)) { n = "100%"; }
+
+ var processPercent = isPercentage(n);
+ n = mathMin(max, mathMax(0, parseFloat(n)));
+
+ // Automatically convert percentage into number
+ if (processPercent) {
+ n = parseInt(n * max, 10) / 100;
+ }
+
+ // Handle floating point rounding errors
+ if ((math.abs(n - max) < 0.000001)) {
+ return 1;
+ }
+
+ // Convert into [0, 1] range if it isn't already
+ return (n % max) / parseFloat(max);
+ }
+
+ // Force a number between 0 and 1
+ function clamp01(val) {
+ return mathMin(1, mathMax(0, val));
+ }
+
+ // Parse a base-16 hex value into a base-10 integer
+ function parseIntFromHex(val) {
+ return parseInt(val, 16);
+ }
+
+ // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
+ // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
+ function isOnePointZero(n) {
+ return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
+ }
+
+ // Check to see if string passed in is a percentage
+ function isPercentage(n) {
+ return typeof n === "string" && n.indexOf('%') != -1;
+ }
+
+ // Force a hex value to have 2 characters
+ function pad2(c) {
+ return c.length == 1 ? '0' + c : '' + c;
+ }
+
+ // Replace a decimal with it's percentage value
+ function convertToPercentage(n) {
+ if (n <= 1) {
+ n = (n * 100) + "%";
+ }
+
+ return n;
+ }
+
+ // Converts a decimal to a hex value
+ function convertDecimalToHex(d) {
+ return Math.round(parseFloat(d) * 255).toString(16);
+ }
+ // Converts a hex value to a decimal
+ function convertHexToDecimal(h) {
+ return (parseIntFromHex(h) / 255);
+ }
+
+ var matchers = (function() {
+
+ // <http://www.w3.org/TR/css3-values/#integers>
+ var CSS_INTEGER = "[-\\+]?\\d+%?";
+
+ // <http://www.w3.org/TR/css3-values/#number-value>
+ var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
+
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
+ var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
+
+ // Actual matching.
+ // Parentheses and commas are optional, but not required.
+ // Whitespace can take the place of commas or opening paren
+ var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
+ var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
+
+ return {
+ rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
+ rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
+ hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
+ hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
+ hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
+ hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
+ hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
+ hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
+ hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
+ };
+ })();
+
+ // `stringInputToObject`
+ // Permissive string parsing. Take in a number of formats, and output an object
+ // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
+ function stringInputToObject(color) {
+
+ color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
+ var named = false;
+ if (names[color]) {
+ color = names[color];
+ named = true;
+ }
+ else if (color == 'transparent') {
+ return { r: 0, g: 0, b: 0, a: 0, format: "name" };
+ }
+
+ // Try to match string input using regular expressions.
+ // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
+ // Just return an object and let the conversion functions handle that.
+ // This way the result will be the same whether the tinycolor is initialized with string or object.
+ var match;
+ if ((match = matchers.rgb.exec(color))) {
+ return { r: match[1], g: match[2], b: match[3] };
+ }
+ if ((match = matchers.rgba.exec(color))) {
+ return { r: match[1], g: match[2], b: match[3], a: match[4] };
+ }
+ if ((match = matchers.hsl.exec(color))) {
+ return { h: match[1], s: match[2], l: match[3] };
+ }
+ if ((match = matchers.hsla.exec(color))) {
+ return { h: match[1], s: match[2], l: match[3], a: match[4] };
+ }
+ if ((match = matchers.hsv.exec(color))) {
+ return { h: match[1], s: match[2], v: match[3] };
+ }
+ if ((match = matchers.hsva.exec(color))) {
+ return { h: match[1], s: match[2], v: match[3], a: match[4] };
+ }
+ if ((match = matchers.hex8.exec(color))) {
+ return {
+ a: convertHexToDecimal(match[1]),
+ r: parseIntFromHex(match[2]),
+ g: parseIntFromHex(match[3]),
+ b: parseIntFromHex(match[4]),
+ format: named ? "name" : "hex8"
+ };
+ }
+ if ((match = matchers.hex6.exec(color))) {
+ return {
+ r: parseIntFromHex(match[1]),
+ g: parseIntFromHex(match[2]),
+ b: parseIntFromHex(match[3]),
+ format: named ? "name" : "hex"
+ };
+ }
+ if ((match = matchers.hex3.exec(color))) {
+ return {
+ r: parseIntFromHex(match[1] + '' + match[1]),
+ g: parseIntFromHex(match[2] + '' + match[2]),
+ b: parseIntFromHex(match[3] + '' + match[3]),
+ format: named ? "name" : "hex"
+ };
+ }
+
+ return false;
+ }
+
+ window.tinycolor = tinycolor;
+ })();
+
+ $(function () {
+ if ($.fn.spectrum.load) {
+ $.fn.spectrum.processNativeColorInputs();
+ }
+ });
+
+});
diff -ruN transmission-2.84+/web/javascript/prefs-dialog.js transmission-2.84-patched/web/javascript/prefs-dialog.js
--- transmission-2.84+/web/javascript/prefs-dialog.js 2015-07-30 09:56:13.545162800 +0300
+++ transmission-2.84-patched/web/javascript/prefs-dialog.js 2015-07-31 15:01:23.354890600 +0300
@@ -43,6 +43,7 @@
'speed-limit-up',
'speed-limit-up-enabled',
'start-added-torrents',
+ 'download-group-default',
'utp-enabled'
],
@@ -57,7 +58,32 @@
'seedRatioLimited': [ 'seedRatioLimit' ],
'speed-limit-down-enabled': [ 'speed-limit-down' ],
'speed-limit-up-enabled': [ 'speed-limit-up' ]
- }
+ },
+
+ dGroups: [],
+ dGroupsDirty: false,
+ spectrumOpt: {
+ className: 'full-spectrum',
+ showInput: true,
+ showInitial: false,
+ showPalette: true,
+ showSelectionPalette: false,
+ maxPaletteSize: 10,
+ preferredFormat: 'hex',
+ showButtons: false,
+ hide: function (color) {
+ onControlChangedGroupColor(this, color);
+ },
+ palette: [
+ ['rgb(0,0,0)','rgb(67,67,67)','rgb(102,102,102)','rgb(153,153,153)','rgb(183,183,183)','rgb(204,204,204)','rgb(217,217,217)','rgb(239,239,239)','rgb(243,243,243)','rgb(255,255,255)'],
+ ['rgb(152,0,0)','rgb(255,0,0)','rgb(255,153,0)','rgb(255,255,0)','rgb(0,255,0)','rgb(0,255,255)','rgb(74,134,232)','rgb(0,0,255)','rgb(153,0,255)','rgb(255,0,255)'],
+ ['rgb(221,126,107)','rgb(234,153,153)','rgb(249,203,156)','rgb(255,229,153)','rgb(182,215,168)','rgb(162,196,201)','rgb(164,194,244)','rgb(159,197,232)','rgb(180,167,214)','rgb(213,166,189)'],
+ ['rgb(204,65,37)','rgb(224,102,102)','rgb(246,178,107)','rgb(255,217,102)','rgb(147,196,125)','rgb(118,165,175)','rgb(109,158,235)','rgb(111,168,220)','rgb(142,124,195)','rgb(194,123,160)'],
+ ['rgb(166,28,0)','rgb(204,0,0)','rgb(230,145,56)','rgb(241,194,50)','rgb(106,168,79)','rgb(69,129,142)','rgb(60,120,216)','rgb(61,133,198)','rgb(103,78,167)','rgb(166,77,121)'],
+ ['rgb(133,32,12)','rgb(153,0,0)','rgb(180,95,6)','rgb(191,144,0)','rgb(56,118,29)','rgb(19,79,92)','rgb(17,85,204)','rgb(11,83,148)','rgb(53,28,117)','rgb(116,27,71)'],
+ ['rgb(91,15,0)','rgb(102,0,0)','rgb(120,63,4)','rgb(127,96,0)','rgb(39,78,19)','rgb(12,52,61)','rgb(28,69,135)','rgb(7,55,99)','rgb(32,18,77)','rgb(76,17,48)']
+ ]
+ }
},
initTimeDropDown = function(e)
@@ -162,7 +188,187 @@
delete data.oldValue;
}
},
-
+ onControlChangedGroupColor = function(obj, color) {
+ var curidx = obj.id.replace('color-', '');
+ $('#group-colors div[rel="' + curidx + '"] input.color').val(color.toHexString());
+ data.dGroupsDirty = true;
+ },
+ onControlClickGroupUp = function(ev)
+ {
+ // bail when changes have been made but something is wrong
+ if(data.dGroupsDirty && !checkGroupValues()) return;
+
+ var curidx = +$(ev.target).parents('div[rel]').attr('rel');
+ if(curidx < 1) return;
+
+ var tmp = data.dGroups[curidx];
+ data.dGroups[curidx] = data.dGroups[curidx-1];
+ data.dGroups[curidx-1] = tmp;
+ getGroupsTable();
+ },
+ onControlClickGroupDown = function(ev)
+ {
+ // bail when changes have been made but something is wrong
+ if(data.dGroupsDirty && !checkGroupValues()) return;
+
+ var curidx = +$(ev.target).parents('div[rel]').attr('rel');
+ if(curidx + 1 >= data.dGroups.length) return;
+
+ var tmp = data.dGroups[curidx];
+ data.dGroups[curidx] = data.dGroups[curidx+1];
+ data.dGroups[curidx+1] = tmp;
+ getGroupsTable();
+ },
+ onControlClickGroupAdd = function(ev)
+ {
+ // bail when changes have been made but something is wrong
+ if(data.dGroupsDirty && !checkGroupValues()) return;
+
+ var curidx = +$(ev.target).parents('div[rel]').attr('rel');
+ data.dGroups.splice(curidx + 1, 0, ['','','', 2]);
+ getGroupsTable();
+ data.dGroupsDirty = true;
+ },
+ onControlClickGroupDelete = function(ev)
+ {
+ var curidx = +$(ev.target).parents('div[rel]').attr('rel');
+ if(data.dGroups[curidx][3] == 2) { // just added
+ data.dGroups.splice(curidx,1); // no pity to remove
+ } else {
+ // bail when changes have been made but something is wrong
+ if(data.dGroupsDirty && !checkGroupValues()) return;
+
+ data.dGroups[curidx][3] = !data.dGroups[curidx][3]; // mark as removed
+ }
+ getGroupsTable();
+ data.dGroupsDirty = true;
+ },
+ getGroupsTable = function(e)
+ {
+ var optSnip = '',
+ tabSnip = '',
+ dglen = data.dGroups.length;
+ // remove old color-pickers
+ $('#group-colors .color-row .color').spectrum('destroy');
+ // create snippet text
+ for (var idx = 0; idx < dglen; idx++) {
+ var addGroup = data.dGroups[idx];
+ optSnip += '<option value="' + addGroup[0].toLowerCase() + '">' + addGroup[0] + '</option>';
+
+ var isDel = (addGroup[3] == 1);
+ tabSnip += '<div rel="' + idx + '" class="color-row' + (isDel ? ' removed' : '') + '" name="' + addGroup[0].toLowerCase() + '">';
+ tabSnip += '<input type="text" class="group" value="' + addGroup[0] + '" ' + (isDel ? ' disabled="disabled"' : '') + '/>';
+ tabSnip += '<input type="text" class="color" id="color-' + idx + '" value="' + addGroup[1] + '" style="display:none;" ' + (isDel ? ' disabled="disabled"' : '') + '/>';
+ //tabSnip += '<input type="text" class="code" value="' + addGroup[2] + '" style="dislay:none;" ' + (isDel ? ' disabled="disabled"' : '') + '/>';
+ tabSnip += '<span class="controls">';
+ tabSnip += '<span class="ui-icon up ui-icon-triangle-1-n' + (idx == 0 ? ' disabled' : '') + '"></span>';
+ tabSnip += '<span class="ui-icon down ui-icon-triangle-1-s' + (idx + 1 == dglen ? ' disabled' : '') + '"></span>';
+ tabSnip += '<span class="ui-icon add ui-icon-plus"></span>';
+ tabSnip += '<span class="ui-icon delete ui-icon-' + (isDel ? 'cancel' : 'close') + (dglen == 1 ? ' disabled' : '') + '"></span>';
+ tabSnip += '</span>';
+ tabSnip += '</div>';
+ }
+ // apply snippets
+ if(e && optSnip.length > 0) {
+ e.empty().append($(optSnip));
+ }
+ gtab = $('#group-colors').empty().append($(tabSnip));
+ // apply color pickers
+ gtab.find('.color-row .color').spectrum(data.spectrumOpt);
+ // events
+ gtab.find('.controls span.up').click('', onControlClickGroupUp);
+ gtab.find('.controls span.down').click('', onControlClickGroupDown);
+ gtab.find('.controls span.add').click('', onControlClickGroupAdd);
+ gtab.find('.controls span.delete').click('', onControlClickGroupDelete);
+ // handle changes in inputs
+ gtab.find('input').change(function() {
+ var curidx = +$(this).parents('div[rel]').attr('rel');
+ var fldclass = $(this).attr('class');
+ var fldidx = fldclass == 'group' ? 0 :
+ fldclass == 'color' ? 1 : 2;
+ data.dGroups[curidx][fldidx] = $(this).val();
+ data.dGroupsDirty = true;
+ });
+ },
+ setGroupButtonEnabled = function(b)
+ {
+ var e = $('#groups-update-button');
+ e.attr('disabled', !b);
+ e.val(b ? 'Update' : 'Updating...');
+ },
+ onControlClickGroupUpdate = function()
+ {
+ // notinhg to change
+ if(!data.dGroupsDirty) return;
+ // changes have errors
+ if(!checkGroupValues()) return;
+
+ // push new data to server
+ setGroupButtonEnabled(false);
+ // save prefs
+ var o = {};
+ o['download-groups'] = data.newGroups;
+ data.remote.savePrefs(o, function () {
+ data.remote._controller.loadDaemonPrefs();
+ data.remote._controller.refilter(true);
+ getGroupsTable();
+ });
+ data.dGroupsDirty = false;
+ data.dGroups = data.newGroups;
+ },
+ checkGroupValues = function()
+ {
+ var newValue = [],
+ dupGroup = []
+ dupCode = [],
+ errorCount = 0;
+
+ $('#prefs-page-groups .message').html('');
+ $('#group-colors').find('.color-row').each(function() {
+ $(this).removeClass('error');
+ idx = $(this).attr('rel');
+ if(data.dGroups[idx][3] != 1) // not removed
+ {
+ var rowVal = [$.trim($(this).find('input.group').val()),
+ $(this).find('input.color').val()];
+ // keep track of dups
+ if($.inArray(rowVal[0], dupGroup) == -1) {
+ dupGroup.push(rowVal[0]);
+ }
+ // check if all values are correct
+ if(rowVal[0] != '') {
+ newValue.push(rowVal);
+ return;
+ }
+ // something must be wrong
+ $(this).addClass('error');
+ if(rowVal[0] == '') errorCount++;
+ }
+ });
+ // text for content errors
+ var errMsg = '';
+ if(errorCount != 0) {
+ errMsg += 'One or more \'Title\' fields are empty. See marked row(s).';
+ $('#prefs-page-groups .message').html(errMsg);
+ return false;
+ }
+ // any duplicates
+ if(dupGroup.length != newValue.length)
+ {
+ errMsg += '\'Title\' fields must be unique.';
+ $('#prefs-page-groups .message').html(errMsg);
+ return false;
+ }
+ if(newValue.length == 0)
+ {
+ $('#prefs-page-groups .message').html('At least one group must be defined.');
+ return false;
+ }
+
+ data.newGroups = newValue;
+ return true;
+ },
+
getDefaultMobileOptions = function()
{
return {
@@ -186,7 +392,7 @@
o = isMobileDevice
? getDefaultMobileOptions()
- : { width: 350, height: 400 };
+ : { width: 380, height: 400 };
o.autoOpen = false;
o.show = o.hide = 'fade';
o.close = onDialogClosed;
@@ -240,6 +446,7 @@
onDialogClosed = function()
{
transmission.hideMobileAddressbar();
+ $('#group-colors .color-row .color').spectrum('hide');
$(data.dialog).trigger('closed', getValues());
};
@@ -266,8 +473,23 @@
{
// special case -- regular text area
e.text('' + val.toStringWithCommas());
- }
- else switch (e[0].type)
+ } else if (key === 'download-group-default')
+ {
+ // defaults are new or more/less items (TODO: check if there are differences)
+ if (e.children().length == 0 || e.children().length != o['download-groups'].length)
+ {
+ data.dGroups = o['download-groups'];
+ data.dGroupsDirty = false;
+ getGroupsTable(e);
+ root.find('input#groups-update-button').click(onControlClickGroupUpdate);
+ }
+
+ var currDefGroup = $('#download-group-default');
+ if(currDefGroup.val() != val) // default has changed
+ {
+ currDefGroup.val(val);
+ }
+ } else switch (e[0].type)
{
case 'checkbox':
case 'radio':
@@ -298,6 +520,7 @@
transmission.hideMobileAddressbar();
setBlocklistButtonEnabled(true);
+ setGroupButtonEnabled(true);
data.remote.checkPort(onPortChecked,this);
data.elements.root.dialog('open');
};
@@ -314,5 +537,5 @@
};
data.dialog = this;
- initialize (remote);
+ initialize(remote);
};
diff -ruN transmission-2.84+/web/javascript/remote.js transmission-2.84-patched/web/javascript/remote.js
--- transmission-2.84+/web/javascript/remote.js 2015-07-30 09:56:13.541162800 +0300
+++ transmission-2.84-patched/web/javascript/remote.js 2015-07-31 12:51:06.638493800 +0300
@@ -191,6 +191,17 @@
{"move": true, "location": new_location}, callback, context);
},
+ setGroupTorrents: function(torrent_ids, new_group, callback, context) {
+ var args = {
+ ids: torrent_ids,
+ downloadGroup: new_group
+ };
+ this.sendRequest({
+ arguments: args,
+ method: 'torrent-set'
+ }, callback, context);
+ },
+
removeTorrents: function(torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-remove', torrent_ids, callback, context);
},
@@ -235,15 +246,18 @@
remote._controller.refreshTorrents();
});
},
- savePrefs: function(args) {
+ savePrefs: function(args, callback) {
var remote = this;
var o = {
method: 'session-set',
arguments: args
};
- this.sendRequest(o, function() {
- remote._controller.loadDaemonPrefs();
- });
+ if(typeof(callback) === 'undefined') {
+ callback = function() {
+ remote._controller.loadDaemonPrefs();
+ };
+ }
+ this.sendRequest(o, callback);
},
updateBlocklist: function() {
var remote = this;
diff -ruN transmission-2.84+/web/javascript/torrent.js transmission-2.84-patched/web/javascript/torrent.js
--- transmission-2.84+/web/javascript/torrent.js 2015-07-30 09:56:13.549162800 +0300
+++ transmission-2.84-patched/web/javascript/torrent.js 2015-07-31 12:51:06.650003500 +0300
@@ -77,6 +77,7 @@
'status',
'trackers',
'downloadDir',
+ 'downloadGroup',
'uploadedEver',
'uploadRatio',
'webseedsSendingToUs'
@@ -171,6 +172,16 @@
return announces.join('\t');
},
+ collateGroups: function(groups)
+ {
+ var i, t, announces = [];
+
+ for (i=0; t=groups[i]; ++i)
+ announces.push(t.announce.toLowerCase());
+ return announces.join('\t');
+ },
+
+
refreshFields: function(data)
{
var key,
@@ -214,6 +225,7 @@
getDateCreated: function() { return this.fields.dateCreated; },
getDesiredAvailable: function() { return this.fields.desiredAvailable; },
getDownloadDir: function() { return this.fields.downloadDir; },
+ getDownloadGroup: function() { return this.fields.downloadGroup; },
getDownloadSpeed: function() { return this.fields.rateDownload; },
getDownloadedEver: function() { return this.fields.downloadedEver; },
getError: function() { return this.fields.error; },
@@ -253,6 +265,12 @@
getWebseedsSendingToUs: function() { return this.fields.webseedsSendingToUs; },
isFinished: function() { return this.fields.isFinished; },
+ getDownloadGroupColor: function() {
+ var groupName = this.fields.downloadGroup.toLowerCase();
+ var $op = $('#group-colors div[name="' + groupName + '"] input.color');
+ return $op.length != 0 ? 'background-color:'+ $op.val() : '';
+ },
+
// derived accessors
hasExtraInfo: function() { return 'hashString' in this.fields; },
isSeeding: function() { return this.getStatus() === Torrent._StatusSeed; },
@@ -310,6 +328,13 @@
f.collatedTrackers = this.collateTrackers(f.trackers);
return f.collatedTrackers || '';
},
+ getCollatedGroup: function() {
+ var f = this.fields;
+ if (!f.collatedGroup && f.downloadGroup)
+ f.collatedGroup = f.downloadGroup;
+ return f.collatedGroup || '';
+ },
+
/****
*****
@@ -346,7 +371,7 @@
* @param search substring to look for, or null
* @return true if it passes the test, false if it fails
*/
- test: function(state, search, tracker)
+ test: function(state, search, tracker, group)
{
// flter by state...
var pass = this.testState(state);
@@ -359,6 +384,10 @@
if (pass && tracker && tracker.length)
pass = this.getCollatedTrackers().indexOf(tracker) !== -1;
+ // maybe filter by group...
+ if (pass && group && group.length)
+ pass = this.getCollatedGroup().indexOf(group) !== -1;
+
return pass;
}
};
diff -ruN transmission-2.84+/web/javascript/torrent-row.js transmission-2.84-patched/web/javascript/torrent-row.js
--- transmission-2.84+/web/javascript/torrent-row.js 2015-07-30 09:56:13.557162800 +0300
+++ transmission-2.84-patched/web/javascript/torrent-row.js 2015-07-31 12:51:06.644498900 +0300
@@ -110,11 +110,14 @@
{
createRow: function()
{
- var root, name, peers, progressbar, details, image, button;
+ var root, name, group, peers, progressbar, details, image, button;
root = document.createElement('li');
root.className = 'torrent';
+ group = document.createElement('div');
+ group.className = 'torrent_group';
+
name = document.createElement('div');
name.className = 'torrent_name';
@@ -130,6 +133,7 @@
button = document.createElement('a');
button.appendChild(image);
+ root.appendChild(group);
root.appendChild(name);
root.appendChild(peers);
root.appendChild(button);
@@ -137,6 +141,7 @@
root.appendChild(details);
root._name_container = name;
+ root._group_container = group;
root._peer_details_container = peers;
root._progress_details_container = details;
root._progressbar = progressbar;
@@ -268,6 +273,9 @@
// name
setTextContent(root._name_container, t.getName());
+ // group
+ $(root._group_container).attr('style',t.getDownloadGroupColor());
+
// progressbar
TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
@@ -301,17 +309,21 @@
{
createRow: function()
{
- var progressbar, details, name, root;
+ var progressbar, details, name, root, group;
progressbar = TorrentRendererHelper.createProgressbar('compact');
details = document.createElement('div');
details.className = 'torrent_peer_details compact';
+ group = document.createElement('div');
+ group.className = 'torrent_group';
+
name = document.createElement('div');
name.className = 'torrent_name compact';
root = document.createElement('li');
+ root.appendChild(group);
root.appendChild(progressbar.element);
root.appendChild(details);
root.appendChild(name);
@@ -319,6 +331,7 @@
root._progressbar = progressbar;
root._details_container = details;
root._name_container = name;
+ root._group_container = group;
return root;
},
@@ -357,6 +370,9 @@
$(e).toggleClass('paused', is_stopped);
setTextContent(e, t.getName());
+ // group
+ $(root._group_container).attr('style',t.getDownloadGroupColor());
+
// peer details
var has_error = t.getError() !== Torrent._ErrNone;
e = root._details_container;
diff -ruN transmission-2.84+/web/javascript/transmission.js transmission-2.84-patched/web/javascript/transmission.js
--- transmission-2.84+/web/javascript/transmission.js 2015-07-30 09:56:13.645162800 +0300
+++ transmission-2.84-patched/web/javascript/transmission.js 2015-07-31 16:13:25.477309100 +0300
@@ -47,6 +47,7 @@
$('#toolbar-start-all').click($.proxy(this.startAllClicked,this));
$('#toolbar-remove').click($.proxy(this.removeClicked,this));
$('#toolbar-open').click($.proxy(this.openTorrentClicked,this));
+ $('#toolbar-set-group').click($.proxy(this.setGroupClicked,this));
$('#prefs-button').click($.proxy(this.togglePrefsDialogClicked,this));
@@ -60,6 +61,9 @@
$('#move_confirm_button').click($.proxy(this.confirmMoveClicked,this));
$('#move_cancel_button').click($.proxy(this.hideMoveDialog,this));
+ $('#set_group_confirm_button').click($.proxy(this.confirmSetGroupClicked,this));
+ $('#set_group_cancel_button').click($.proxy(this.hideSetGroupDialog,this));
+
$('#turtle-button').click($.proxy(this.toggleTurtleClicked,this));
$('#compact-button').click($.proxy(this.toggleCompactClicked,this));
@@ -74,6 +78,7 @@
e.val(this[Prefs._FilterMode]);
e.change($.proxy(this.onFilterModeClicked,this));
$('#filter-tracker').change($.proxy(this.onFilterTrackerClicked,this));
+ $('#filter-group').change($.proxy(this.onFilterGroupClicked,this));
if (!isMobileDevice) {
$(document).bind('keydown', $.proxy(this.keyDown,this) );
@@ -97,6 +102,7 @@
e.toolbar_pause_button = $('#toolbar-pause')[0];
e.toolbar_start_button = $('#toolbar-start')[0];
e.toolbar_remove_button = $('#toolbar-remove')[0];
+ e.toolbar_set_group = $('#toolbar-set-group')[0];
this.elements = e;
// Apply the prefs settings to the gui
@@ -194,7 +200,8 @@
move_down: function() { tr.moveDown(); },
move_bottom: function() { tr.moveBottom(); },
select_all: function() { tr.selectAll(); },
- deselect_all: function() { tr.deselectAll(); }
+ deselect_all: function() { tr.deselectAll(); },
+ set_group: function() { tr.setGroupSelectedTorrents(false); }
};
// Set up the context menu
@@ -578,11 +585,21 @@
this.updateButtonStates();
},
+ hideSetGroupDialog: function() {
+ $('#set_group_container').hide();
+ this.updateButtonStates();
+ },
+
confirmMoveClicked: function() {
this.moveSelectedTorrents(true);
this.hideUploadDialog();
},
+ confirmSetGroupClicked: function() {
+ this.setGroupSelectedTorrents(true);
+ this.hideUploadDialog();
+ },
+
hideRenameDialog: function() {
$('body.open_showing').removeClass('open_showing');
$('#rename_container').hide();
@@ -601,6 +618,12 @@
}
},
+ setGroupClicked: function(ev) {
+ if (this.isButtonEnabled(ev)) {
+ this.setGroupSelectedTorrents(false);
+ }
+ },
+
// turn the periodic ajax session refresh on & off
togglePeriodicSessionRefresh: function(enabled) {
clearInterval(this.sessionInterval);
@@ -917,10 +940,11 @@
{
var i, file,
reader,
- fileInput = $('input#torrent_upload_file'),
- folderInput = $('input#add-dialog-folder-input'),
- startInput = $('input#torrent_auto_start'),
- urlInput = $('input#torrent_upload_url');
+ fileInput = $('input#torrent_upload_file'),
+ folderInput = $('input#add-dialog-folder-input'),
+ startInput = $('input#torrent_auto_start'),
+ urlInput = $('input#torrent_upload_url');
+ downloadGroups = $('select#download-groups');
if (!confirmed)
{
@@ -930,6 +954,7 @@
startInput.attr('checked', this.shouldAddedTorrentsStart());
folderInput.attr('value', $("#download-dir").val());
folderInput.change($.proxy(this.updateFreeSpaceInAddDialog,this));
+ downloadGroups.val($('#download-group-default').val());
this.updateFreeSpaceInAddDialog();
// show the dialog
@@ -940,6 +965,7 @@
{
var paused = !startInput.is(':checked'),
destination = folderInput.val(),
+ group = downloadGroups.val(),
remote = this.remote;
jQuery.each (fileInput[0].files, function(i,file) {
@@ -955,6 +981,7 @@
arguments: {
'paused': paused,
'download-dir': destination,
+ 'downloadGroup': group,
'metainfo': metainfo
}
};
@@ -976,6 +1003,7 @@
arguments: {
'paused': paused,
'download-dir': destination,
+ 'downloadGroup': group,
'filename': url
}
};
@@ -1015,6 +1043,34 @@
this.promptSetLocation(confirmed, torrents);
},
+ promptSetGroup: function(confirmed, torrents) {
+ if (! confirmed) {
+ var group;
+ if (torrents.length === 1) {
+ group = torrents[0].getDownloadGroup();
+ } else {
+ group = $("#download-group-default").val();
+ }
+ $('select#torrent_set_group').val(group);
+ $('#set_group_container').show();
+ $('#download-group-default').focus();
+ } else {
+ var ids = this.getTorrentIds(torrents);
+ this.remote.setGroupTorrents(
+ ids,
+ $("select#torrent_set_group").val(),
+ this.refreshTorrents,
+ this);
+ $('#set_group_container').hide();
+ }
+ },
+
+ setGroupSelectedTorrents: function(confirmed) {
+ var torrents = this.getSelectedTorrents();
+ if (torrents.length)
+ this.promptSetGroup(confirmed, torrents);
+ },
+
removeSelectedTorrents: function() {
var torrents = this.getSelectedTorrents();
if (torrents.length)
@@ -1206,6 +1262,38 @@
this.prefsDialog.set(o);
+ // seperate function for other prefs??
+ if (typeof(o['download-groups']) !== 'undefined'
+ && typeof(o['download-group-default']) !== 'undefined')
+ {
+ var groupDefault = o['download-group-default'].toLowerCase();
+ var ddAdd = $('#download-groups'); // a drop-box when add a torrent
+ var ddSet = $('#torrent_set_group'); // a drop-box when change group
+ // defaults are new or more/less items (TODO: check if there are differences)
+ if (ddAdd.children().length == 0 || ddAdd.children().length != o['download-groups'].length)
+ {
+ ddAdd = ddAdd.empty(); // clear children
+ ddSet = ddSet.empty();
+ for (var idx = 0; idx < o['download-groups'].length; idx++)
+ {
+ var addItem = o['download-groups'][idx];
+ if( Object.prototype.toString.call( addItem ) === '[object Array]') {
+ addItem = o['download-groups'][idx][0];
+ }
+
+ ddAdd.append($('<option>' + addItem + '</option>').val(addItem.toLowerCase()));
+ ddSet.append($('<option>' + addItem + '</option>').val(addItem.toLowerCase()));
+ }
+
+ if(ddAdd.val() != groupDefault) // default has changed
+ {
+ ddAdd.val(groupDefault);
+ }
+ }
+
+
+ }
+
if (RPC._TurtleState in o)
{
b = o[RPC._TurtleState];
@@ -1280,9 +1368,42 @@
updateFilterSelect: function()
{
var i, names, name, str, o,
+ g = $('#filter-group'),
+ downloadGroups = this.getGroups(),
e = $('#filter-tracker'),
trackers = this.getTrackers();
+ if (!this.filterGroup)
+ str = '<option value="all" selected="selected">All</option>';
+ else
+ str = '<option value="all">All</option>';
+
+ var p = this.sessionProperties;
+ if(p && typeof(p['download-groups']) !== 'undefined')
+ {
+ for(var i in p["download-groups"]) {
+ var key = p["download-groups"][i][0].toLowerCase();
+ if(typeof(downloadGroups[key]) != 'undefined')
+ {
+ var item = downloadGroups[key];
+ str += '<option value="' + item.group + '"' + (item.group === this.filterGroup ? ' selected="selected"' : '') + '>' + p["download-groups"][i][0] + '</option>';
+ // exclude processed object to get lost keys
+ delete downloadGroups[key];
+ }
+ }
+ // lost keys
+ for(var i in downloadGroups)
+ {
+ o = downloadGroups[i];
+ str += '<option value="' + o.group + '"' + (o.group === this.filterGroup ? ' selected="selected"' : '') + '>[' + o.group + ']</option>';
+ }
+ }
+
+ if (!this.filterGroupsStr || (this.filterGroupsStr !== str)) {
+ this.filterGroupsStr = str;
+ $('#filter-group').html(str);
+ }
+
// build a sorted list of names
names = [];
for (name in trackers)
@@ -1353,6 +1474,7 @@
tr.setEnabled(e.toolbar_pause_button, s.activeSel > 0);
tr.setEnabled(e.toolbar_start_button, s.pausedSel > 0);
tr.setEnabled(e.toolbar_remove_button, s.sel > 0);
+ tr.setEnabled(e.toolbar_set_group, s.sel > 0);
});
},
@@ -1430,6 +1552,7 @@
filter_mode = this[Prefs._FilterMode],
filter_text = this.filterText,
filter_tracker = this.filterTracker,
+ filter_group = this.filterGroup,
renderer = this.torrentRenderer,
list = this.elements.torrent_list,
old_sel_count = $(list).children('.selected').length;
@@ -1468,7 +1591,7 @@
for (i=0; row=dirty_rows[i]; ++i) {
id = row.getTorrentId();
t = this._torrents[ id ];
- if (t && t.test(filter_mode, filter_text, filter_tracker))
+ if (t && t.test(filter_mode, filter_text, filter_tracker, filter_group))
tmp.push(row);
delete this.dirtyTorrents[id];
}
@@ -1478,7 +1601,7 @@
// but don't already have a row
for (id in this.dirtyTorrents) {
t = this._torrents[id];
- if (t && t.test(filter_mode, filter_text, filter_tracker)) {
+ if (t && t.test(filter_mode, filter_text, filter_tracker, filter_group)) {
row = new TorrentRow(renderer, this, t);
e = row.getElement();
e.row = row;
@@ -1564,6 +1687,23 @@
this.setFilterTracker(tracker==='all' ? null : tracker);
},
+ onFilterGroupClicked: function(ev)
+ {
+ var group = $('#filter-group').val();
+ this.setFilterGroup(group==='all' ? null : group);
+ },
+
+ setFilterGroup: function(group)
+ {
+ // update which tracker is selected in the popup
+ var key = group ? group : 'all',
+ id = '#show-group-' + key;
+ $(id).addClass('selected').siblings().removeClass('selected');
+
+ this.filterGroup = group;
+ this.refilter(true);
+ },
+
setFilterTracker: function(domain)
{
// update which tracker is selected in the popup
@@ -1631,6 +1771,31 @@
return ret;
},
+ getGroups: function()
+ {
+ var ret = {};
+
+ var torrents = this.getAllTorrents();
+ for (var i=0, torrent; torrent=torrents[i]; ++i)
+ {
+ var names = [];
+ var group = torrent.getDownloadGroup();
+
+ if (!(group in ret))
+ ret[group] = { 'group': group,
+ 'count': 0 };
+
+ if (names.indexOf(group) === -1)
+ names.push(group);
+
+ for (var j=0, name; name=names[j]; ++j)
+ ret[name].count++;
+ }
+
+ return ret;
+ },
+
+
/***
****
**** Compact Mode
diff -ruN transmission-2.84+/web/style/transmission/common.css transmission-2.84-patched/web/style/transmission/common.css
--- transmission-2.84+/web/style/transmission/common.css 2015-07-30 09:56:13.041162800 +0300
+++ transmission-2.84-patched/web/style/transmission/common.css 2015-07-31 15:30:28.803497200 +0300
@@ -90,6 +90,8 @@
background-image: url("images/toolbar-start-all.png"); }
div#toolbar > div#toolbar-pause-all {
background-image: url("images/toolbar-pause-all.png"); }
+ div#toolbar > div#toolbar-set-group {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADhUlEQVR4XsWWQWgcVRjHf9+bmd1N69YkSKRaCl578SCIXtSTEQzmoNiqaJMmu1vaiGLqwZNID4K2RUWTnVljSittshY14kEPQioVUfRgTyqCKK2CbBPadJvdpvM+xTwYlkWoMhN/zIPv8DHvz/y/958nqsr/iQC5RqPRfnpiAlSphlX2VioYzzD53CSHjxzGxpZqGFKpVAAIw/A/90xXq4yO7iGMIgYGBvIC9D2+a9fSibm5O4A1NoZg585Hv52fr/f7QOCJAjSBZTaGPnFCfICZcJrjJ+dXbj74aQQMqyqsPwkoIN31dfaIACK0rCxcevH+fWE1ZG6+jg9QGR8H6EEYHr/3Lopb2gAI6aCuuHgpz9Tpb4aBSTcr+EkLBbUKGjPzZRNDuljgkR0B1q7vBSQCarUas/UPjKriGcOtRR9PSJVYwRMhthbARFHIXN1ZUCqVnFGQ94WtxQCRdC1QhXxgMMYAUC53WCBuUHyKhQLbez0k5SFQoFjIIeLjSASEb88wW3/fxitXF1/66Ox9WaWjimBXriwCNoyixIKx8gRA68IrD04ANwE+2XANaACtSnlvYoHaHMDa4Mu8hsdQbMkEYyCO+fizF9gPQnIKond4tz4bqMfQkYefwRR+ATXpTqFYbGsb+0+9OQQ8G/59Ck7ir09kGaBgFVa0wXc/fAhKygLg9u2P4ShUklPgAINC2yxjCmSShC2zjMN0BFEURRyv1xCgbZaQzaT6BcS9q2UuoBYAnAWdQWQE4p4lcgqqKTsgoJuWCTwA6LDA6jUA277M568f/fEeVRwp/QwdYn5i7a89ANthwVT4BidOHW0tHmTfRuXAdHUqsWBkbDfAKvCHW0I2KNACiiOjIyRRHEX4PT3n1Q8IazOUymWMEQ4ceJ5Dh17FWqUW1SiVSwC4+l/3qLVE1Sp7nnyCcOotFhYWEKAP2AYEG3kndHud84EmcA7YdObuHWet0q+qoEICKIogXfV19wiIgVVrPxn86vsxZ3nTB666pbHQf+fup7Bbf4MM7kTm91v4+tixB4AVt/BJyFlA2y2uhGdATMoXAsvmwYewLoq7BTg8GxPc2AsmZQHW4tkYh+DoEhAo6JZe1BjSRKwlUDroEiAiiOeR7+vPIAoUPA8R+WcBaszl0/X3brBkgwGWYvsFoDiEhF7gNpcLknEU/9o9hNAEfgbOA2Qcxas4/gQshph1zke9VQAAAABJRU5ErkJggg=="); }
div#toolbar > div#toolbar-inspector {
background-image: url("images/toolbar-info.png");
float: right;
@@ -225,6 +227,7 @@
white-space: nowrap;
color: #222;
margin-top: 2px;
+ margin-left: 26px;
margin-bottom: 2px; }
ul.torrent_list li.torrent div.torrent_name.compact {
font-size: 1.0em;
@@ -232,12 +235,24 @@
ul.torrent_list li.torrent div.torrent_name.paused {
font-weight: normal;
color: #777; }
+ ul.torrent_list li.torrent div.torrent_group {
+ height: 58px;
+ float: left;
+ width: 16px;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 3px;
+ margin-top: 2px; }
+ ul.torrent_list li.compact div.torrent_group {
+ height: 16px;
+ margin-top: 0;
+ }
ul.torrent_list li.torrent div.torrent_progress_details,
ul.torrent_list li.torrent div.torrent_peer_details {
- clear: left;
+ clear: none;
overflow: hidden;
text-overflow: ellipsis;
- white-space: nowrap; }
+ white-space: nowrap;
+ margin-left: 26px; }
ul.torrent_list li.torrent div.torrent_progress_details.error,
ul.torrent_list li.torrent div.torrent_peer_details.error {
color: #F00; }
@@ -254,7 +269,8 @@
/*float: right;*/ }
ul.torrent_list div.torrent_progress_bar_container.full {
margin-top: 2px;
- margin-bottom: 5px; }
+ margin-bottom: 5px;
+ margin-left: 26px; }
ul.torrent_list div.torrent_peer_details.compact {
margin-top: 2px;
margin-right: 65px;
@@ -335,6 +351,8 @@
margin-left: 0px; }
.prefs-section .row .value {
margin-left: 150px; }
+ .prefs-section .row .value.colors {
+ margin-left: 0; }
.prefs-section .row .value > * {
width: 100%; }
.prefs-section .checkbox-row > input {
@@ -775,6 +793,18 @@
background-position: center;
background-repeat: no-repeat; }
+#inspector-page-info .row .groupwrap {
+ display: inline-block;
+ overflow: hidden;
+ white-space:nowrap; }
+#inspector-page-info .row .group {
+ display: inline-block;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 3px;
+ padding: 0 4px; }
+#inspector-page-info .row .group:not(:first-child) {
+ margin-left: 10px; }
+
/****
*****
***** MAIN WINDOW FOOTER
@@ -956,6 +986,11 @@
/* Opera 11.10+ */
background-position: center;
background-repeat: no-repeat; }
+ div.torrent_footer #freespace-info {
+ float: right;
+ text-align: right;
+ border: 0px;
+ width: 100px; }
/****
*****
@@ -1087,3 +1122,78 @@
#footer_super_menu {
font-size: 1em;
z-index: 3; }
+/***
+****
+**** GROUP COLOR
+****
+***/
+.color-row {
+ overflow: hidden;
+ white-space: nowrap;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ margin-bottom: 1px;
+ padding: 0 0 1px 1px; }
+ .color-row.error{
+ border-color: rgba(250, 0, 0, .75);
+ background-color: rgba(250, 0, 0, .15); }
+ .color-row .controls {
+ position:relative;
+ top: 5px; }
+ .color-row .controls .ui-icon {
+ display: inline-block;
+ border-radius:4px;
+ margin-left:3px;
+ cursor:pointer; }
+ .color-row .controls .ui-icon.disabled {
+ visibility:hidden; }
+ .color-row .controls .ui-icon:hover {
+ background-color: rgba(120, 120, 120, .5); }
+ .color-row input {
+ min-width:80px; width: 30%; } /* should be 30% if code column is displayed */
+ .color-row.removed {
+ opacity: 0.5; }
+ #prefs-page-groups .message {
+ clear: both;
+ color: red; }
+ /*--------------------------------------
+*
+* ColorPicker (http://www.laktek.com/2008/10/27/really-simple-color-picker-in-jquery/)
+*
+*--------------------------------------*/
+div.colorPicker-picker {
+ width: 16px;
+ height: 16px;
+ padding: 0 !important;
+ border: 1px solid grey;
+ background: url(images/picker-arrow.png) no-repeat top right;
+ cursor: pointer;
+ font-weight:bold;
+ text-align: center;
+ display: inline-block;
+ position:relative;
+ top:-2px;
+ margin: 0 4px;
+}
+
+div.colorPicker-palette {
+ width: 110px;
+ position: absolute;
+ border: 1px solid grey;
+ background-color: white;
+ padding: 2px;
+ z-index: 9999;
+ box-shadow: black 4px 4px 12px;
+}
+div.colorPicker_hexWrap {width: 100%; float:left }
+div.colorPicker_hexWrap label {font-size: 95%; color: #2F2F2F; margin: 5px 2px; width: 25%}
+div.colorPicker_hexWrap input {margin: 5px 2px; padding: 0; font-size: 95%; border: 1px solid #000; width: 65%; }
+div.colorPicker-swatch {
+ height: 12px;
+ width: 12px;
+ border: 1px solid #000;
+ margin: 2px;
+ float: left;
+ cursor: pointer;
+ line-height: 12px;
+}
diff -ruN transmission-2.84+/web/style/transmission/common.scss transmission-2.84-patched/web/style/transmission/common.scss
--- transmission-2.84+/web/style/transmission/common.scss 2015-07-30 09:56:13.049162800 +0300
+++ transmission-2.84-patched/web/style/transmission/common.scss 2015-07-31 13:08:49.953236300 +0300
@@ -328,18 +328,44 @@
white-space: nowrap;
color: #222;
margin-top: 2px;
+ margin-left: 26px;
margin-bottom: 2px;
&.compact { font-size: 1.0em; font-weight: normal; }
&.paused { font-weight: normal; color: #777; }
}
+ div.torrent_group {
+ height: 58px;
+ float: left;
+ width: 16px;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 3px;
+ margin-top: 2px;
+ &.movies {
+ background-color: rgb(128, 159, 255);
+ }
+ &.tv {
+ background-color: rgb(159, 128, 255);
+ }
+ &.anime {
+ background-color: rgb(223, 128, 255);
+ }
+ &.music {
+ background-color: rgb(159, 255, 128);
+ }
+ &.software {
+ background-color: rgb(255, 208, 66);
+ }
+ }
+
div.torrent_progress_details,
div.torrent_peer_details {
- clear: left;
+ clear: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+ margin-left: 26px;
}
div.torrent_progress_details.error,
@@ -382,6 +408,7 @@
&.full {
margin-top: 2px;
margin-bottom: 5px;
+ margin-left: 26px;
}
}
div.torrent_peer_details.compact
@@ -475,7 +502,42 @@
font-size: smaller;
margin-top: 3px;
}
+
}
+/***
+****
+**** GROUP COLOR
+****
+***/
+.color-row {
+ overflow: hidden;
+ white-space: nowrap;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ margin-bottom: 1px;
+ padding: 0 0 1px 1px; }
+ .color-row.error{
+ border-color: rgba(250, 0, 0, .75);
+ background-color: rgba(250, 0, 0, .15); }
+ .color-row .controls {
+ position:relative;
+ top: 5px; }
+ .color-row .controls .ui-icon {
+ display: inline-block;
+ border-radius:4px;
+ margin-left:3px;
+ cursor:pointer; }
+ .color-row .controls .ui-icon.disabled {
+ visibility:hidden; }
+ .color-row .controls .ui-icon:hover {
+ background-color: rgba(120, 120, 120, .5); }
+ .color-row input {
+ min-width:80px; width: 30%; } /* should be 30% if code column is displayed */
+ .color-row.removed {
+ opacity: 0.5; }
+ #prefs-page-groups .message {
+ clear: both;
+ color: red; }
/***
****
@@ -965,3 +1027,44 @@
font-size: 1em;
z-index: 3;
}
+/*--------------------------------------
+*
+* ColorPicker (http://www.laktek.com/2008/10/27/really-simple-color-picker-in-jquery/)
+*
+*--------------------------------------*/
+div.colorPicker-picker {
+ width: 16px;
+ height: 16px;
+ padding: 0 !important;
+ border: 1px solid grey;
+ background: url(images/picker-arrow.png) no-repeat top right;
+ cursor: pointer;
+ font-weight:bold;
+ text-align: center;
+ display: inline-block;
+ position:relative;
+ top:-2px;
+ margin: 0 4px;
+}
+
+div.colorPicker-palette {
+ width: 110px;
+ position: absolute;
+ border: 1px solid grey;
+ background-color: white;
+ padding: 2px;
+ z-index: 9999;
+ box-shadow: black 4px 4px 12px;
+}
+div.colorPicker_hexWrap {width: 100%; float:left }
+div.colorPicker_hexWrap label {font-size: 95%; color: #2F2F2F; margin: 5px 2px; width: 25%}
+div.colorPicker_hexWrap input {margin: 5px 2px; padding: 0; font-size: 95%; border: 1px solid #000; width: 65%; }
+div.colorPicker-swatch {
+ height: 12px;
+ width: 12px;
+ border: 1px solid #000;
+ margin: 2px;
+ float: left;
+ cursor: pointer;
+ line-height: 12px;
+}
diff -ruN transmission-2.84+/web/style/transmission/spectrum.css transmission-2.84-patched/web/style/transmission/spectrum.css
--- transmission-2.84+/web/style/transmission/spectrum.css 1970-01-01 03:00:00.000000000 +0300
+++ transmission-2.84-patched/web/style/transmission/spectrum.css 2015-07-30 20:48:52.000000000 +0300
@@ -0,0 +1,507 @@
+/***
+Spectrum Colorpicker v1.7.1
+https://github.com/bgrins/spectrum
+Author: Brian Grinstead
+License: MIT
+***/
+
+.sp-container {
+ position:absolute;
+ top:0;
+ left:0;
+ display:inline-block;
+ *display: inline;
+ *zoom: 1;
+ /* https://github.com/bgrins/spectrum/issues/40 */
+ z-index: 9999994;
+ overflow: hidden;
+}
+.sp-container.sp-flat {
+ position: relative;
+}
+
+/* Fix for * { box-sizing: border-box; } */
+.sp-container,
+.sp-container * {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
+.sp-top {
+ position:relative;
+ width: 100%;
+ display:inline-block;
+}
+.sp-top-inner {
+ position:absolute;
+ top:0;
+ left:0;
+ bottom:0;
+ right:0;
+}
+.sp-color {
+ position: absolute;
+ top:0;
+ left:0;
+ bottom:0;
+ right:20%;
+}
+.sp-hue {
+ position: absolute;
+ top:0;
+ right:0;
+ bottom:0;
+ left:84%;
+ height: 100%;
+}
+
+.sp-clear-enabled .sp-hue {
+ top:33px;
+ height: 77.5%;
+}
+
+.sp-fill {
+ padding-top: 80%;
+}
+.sp-sat, .sp-val {
+ position: absolute;
+ top:0;
+ left:0;
+ right:0;
+ bottom:0;
+}
+
+.sp-alpha-enabled .sp-top {
+ margin-bottom: 18px;
+}
+.sp-alpha-enabled .sp-alpha {
+ display: block;
+}
+.sp-alpha-handle {
+ position:absolute;
+ top:-4px;
+ bottom: -4px;
+ width: 6px;
+ left: 50%;
+ cursor: pointer;
+ border: 1px solid black;
+ background: white;
+ opacity: .8;
+}
+.sp-alpha {
+ display: none;
+ position: absolute;
+ bottom: -14px;
+ right: 0;
+ left: 0;
+ height: 8px;
+}
+.sp-alpha-inner {
+ border: solid 1px #333;
+}
+
+.sp-clear {
+ display: none;
+}
+
+.sp-clear.sp-clear-display {
+ background-position: center;
+}
+
+.sp-clear-enabled .sp-clear {
+ display: block;
+ position:absolute;
+ top:0px;
+ right:0;
+ bottom:0;
+ left:84%;
+ height: 28px;
+}
+
+/* Don't allow text selection */
+.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
+ -webkit-user-select:none;
+ -moz-user-select: -moz-none;
+ -o-user-select:none;
+ user-select: none;
+}
+
+.sp-container.sp-input-disabled .sp-input-container {
+ display: none;
+}
+.sp-container.sp-buttons-disabled .sp-button-container {
+ display: none;
+}
+.sp-container.sp-palette-buttons-disabled .sp-palette-button-container {
+ display: none;
+}
+.sp-palette-only .sp-picker-container {
+ display: none;
+}
+.sp-palette-disabled .sp-palette-container {
+ display: none;
+}
+
+.sp-initial-disabled .sp-initial {
+ display: none;
+}
+
+
+/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
+.sp-sat {
+ background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
+ background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
+ background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
+ background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
+ background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
+ background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
+ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
+ filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
+}
+.sp-val {
+ background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
+ background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
+ background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
+ background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
+ background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
+ background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
+ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
+ filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
+}
+
+.sp-hue {
+ background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+ background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+ background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+ background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
+ background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+ background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+}
+
+/* IE filters do not support multiple color stops.
+ Generate 6 divs, line them up, and do two color gradients for each.
+ Yes, really.
+ */
+.sp-1 {
+ height:17%;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
+}
+.sp-2 {
+ height:16%;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
+}
+.sp-3 {
+ height:17%;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
+}
+.sp-4 {
+ height:17%;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
+}
+.sp-5 {
+ height:16%;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
+}
+.sp-6 {
+ height:17%;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
+}
+
+.sp-hidden {
+ display: none !important;
+}
+
+/* Clearfix hack */
+.sp-cf:before, .sp-cf:after { content: ""; display: table; }
+.sp-cf:after { clear: both; }
+.sp-cf { *zoom: 1; }
+
+/* Mobile devices, make hue slider bigger so it is easier to slide */
+@media (max-device-width: 480px) {
+ .sp-color { right: 40%; }
+ .sp-hue { left: 63%; }
+ .sp-fill { padding-top: 60%; }
+}
+.sp-dragger {
+ border-radius: 5px;
+ height: 5px;
+ width: 5px;
+ border: 1px solid #fff;
+ background: #000;
+ cursor: pointer;
+ position:absolute;
+ top:0;
+ left: 0;
+}
+.sp-slider {
+ position: absolute;
+ top:0;
+ cursor:pointer;
+ height: 3px;
+ left: -1px;
+ right: -1px;
+ border: 1px solid #000;
+ background: white;
+ opacity: .8;
+}
+
+/*
+Theme authors:
+Here are the basic themeable display options (colors, fonts, global widths).
+See http://bgrins.github.io/spectrum/themes/ for instructions.
+*/
+
+.sp-container {
+ border-radius: 0;
+ background-color: #ECECEC;
+ border: solid 1px #f0c49B;
+ padding: 0;
+}
+.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear {
+ font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.sp-top {
+ margin-bottom: 3px;
+}
+.sp-color, .sp-hue, .sp-clear {
+ border: solid 1px #666;
+}
+
+/* Input */
+.sp-input-container {
+ float:right;
+ width: 100px;
+ margin-bottom: 4px;
+}
+.sp-initial-disabled .sp-input-container {
+ width: 100%;
+}
+.sp-input {
+ font-size: 12px !important;
+ border: 1px inset;
+ padding: 4px 5px;
+ margin: 0;
+ width: 100%;
+ background:transparent;
+ border-radius: 3px;
+ color: #222;
+}
+.sp-input:focus {
+ border: 1px solid orange;
+}
+.sp-input.sp-validation-error {
+ border: 1px solid red;
+ background: #fdd;
+}
+.sp-picker-container , .sp-palette-container {
+ float:left;
+ position: relative;
+ padding: 10px;
+ padding-bottom: 300px;
+ margin-bottom: -290px;
+}
+.sp-picker-container {
+ width: 172px;
+ border-left: solid 1px #fff;
+}
+
+/* Palettes */
+.sp-palette-container {
+ border-right: solid 1px #ccc;
+}
+
+.sp-palette-only .sp-palette-container {
+ border: 0;
+}
+
+.sp-palette .sp-thumb-el {
+ display: block;
+ position:relative;
+ float:left;
+ width: 24px;
+ height: 15px;
+ margin: 3px;
+ cursor: pointer;
+ border:solid 2px transparent;
+}
+.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
+ border-color: orange;
+}
+.sp-thumb-el {
+ position:relative;
+}
+
+/* Initial */
+.sp-initial {
+ float: left;
+ border: solid 1px #333;
+}
+.sp-initial span {
+ width: 30px;
+ height: 25px;
+ border:none;
+ display:block;
+ float:left;
+ margin:0;
+}
+
+.sp-initial .sp-clear-display {
+ background-position: center;
+}
+
+/* Buttons */
+.sp-palette-button-container,
+.sp-button-container {
+ float: right;
+}
+
+/* Replacer (the little preview div that shows up instead of the <input>) */
+.sp-replacer {
+ margin:0;
+ overflow:hidden;
+ cursor:pointer;
+ padding: 4px;
+ display:inline-block;
+ *zoom: 1;
+ *display: inline;
+ border: solid 1px #91765d;
+ background: #eee;
+ color: #333;
+ vertical-align: middle;
+}
+.sp-replacer:hover, .sp-replacer.sp-active {
+ border-color: #F0C49B;
+ color: #111;
+}
+.sp-replacer.sp-disabled {
+ cursor:default;
+ border-color: silver;
+ color: silver;
+}
+.sp-dd {
+ padding: 2px 0;
+ height: 16px;
+ line-height: 16px;
+ float:left;
+ font-size:10px;
+}
+.sp-preview {
+ position:relative;
+ width:25px;
+ height: 20px;
+ border: solid 1px #222;
+ margin-right: 5px;
+ float:left;
+ z-index: 0;
+}
+
+.sp-palette {
+ *width: 220px;
+ max-width: 220px;
+}
+.sp-palette .sp-thumb-el {
+ width:16px;
+ height: 16px;
+ margin:2px 1px;
+ border: solid 1px #d0d0d0;
+}
+
+.sp-container {
+ padding-bottom:0;
+}
+
+
+/* Buttons: http://hellohappy.org/css3-buttons/ */
+.sp-container button {
+ background-color: #eeeeee;
+ background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
+ background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
+ background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
+ background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
+ background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
+ border: 1px solid #ccc;
+ border-bottom: 1px solid #bbb;
+ border-radius: 3px;
+ color: #333;
+ font-size: 14px;
+ line-height: 1;
+ padding: 5px 4px;
+ text-align: center;
+ text-shadow: 0 1px 0 #eee;
+ vertical-align: middle;
+}
+.sp-container button:hover {
+ background-color: #dddddd;
+ background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
+ background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
+ background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
+ background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
+ background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
+ border: 1px solid #bbb;
+ border-bottom: 1px solid #999;
+ cursor: pointer;
+ text-shadow: 0 1px 0 #ddd;
+}
+.sp-container button:active {
+ border: 1px solid #aaa;
+ border-bottom: 1px solid #888;
+ -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+ -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+ -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+ -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+ box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+}
+.sp-cancel {
+ font-size: 11px;
+ color: #d93f3f !important;
+ margin:0;
+ padding:2px;
+ margin-right: 5px;
+ vertical-align: middle;
+ text-decoration:none;
+
+}
+.sp-cancel:hover {
+ color: #d93f3f !important;
+ text-decoration: underline;
+}
+
+
+.sp-palette span:hover, .sp-palette span.sp-thumb-active {
+ border-color: #000;
+}
+
+.sp-preview, .sp-alpha, .sp-thumb-el {
+ position:relative;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
+}
+.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner {
+ display:block;
+ position:absolute;
+ top:0;left:0;bottom:0;right:0;
+}
+
+.sp-palette .sp-thumb-inner {
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+}
+
+.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
+}
+
+.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
+}
+
+.sp-clear-display {
+ background-repeat:no-repeat;
+ background-position: center;
+ background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment