Repository: Anodynous/stenogotchi
Branch: main
Commit: 54da07d0d681
Files: 104
Total size: 2.8 MB
Directory structure:
gitextract_1w8uxzjn/
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── BUILDNOTES.md
├── CHANGELOG.md
├── LICENSE
├── README.md
├── initial_setup.sh
├── plover_plugin/
│ ├── setup.cfg
│ ├── setup.py
│ └── stenogotchi_link/
│ ├── __init__.py
│ ├── clients.py
│ ├── com.github.stenogotchi.conf
│ ├── keymap.py
│ ├── stenogotchi_link.py
│ └── wpm.py
├── requirements.txt
├── stenogotchi/
│ ├── __init__.py
│ ├── _version.py
│ ├── agent.py
│ ├── automata.py
│ ├── defaults.toml
│ ├── fs/
│ │ └── __init__.py
│ ├── log.py
│ ├── plugins/
│ │ ├── __init__.py
│ │ ├── cmd.py
│ │ └── default/
│ │ ├── buttonshim.py
│ │ ├── dict_lookup.py
│ │ ├── evdevkb.py
│ │ ├── example.py
│ │ ├── led.py
│ │ ├── logtail.py
│ │ ├── memtemp.py
│ │ ├── plover_link.py
│ │ ├── plover_link_btserver_sdp_record.xml
│ │ └── upslite.py
│ ├── ui/
│ │ ├── __init__.py
│ │ ├── components.py
│ │ ├── display.py
│ │ ├── faces.py
│ │ ├── fonts.py
│ │ ├── hw/
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── libs/
│ │ │ │ ├── __init__.py
│ │ │ │ └── waveshare/
│ │ │ │ ├── __init__.py
│ │ │ │ └── v2/
│ │ │ │ ├── __init__.py
│ │ │ │ └── epd2in13_V2.py
│ │ │ └── waveshare2.py
│ │ ├── state.py
│ │ ├── view.py
│ │ └── web/
│ │ ├── __init__.py
│ │ ├── handler.py
│ │ ├── server.py
│ │ ├── static/
│ │ │ ├── css/
│ │ │ │ ├── jquery.jqplot.css
│ │ │ │ └── style.css
│ │ │ └── js/
│ │ │ ├── jquery.jqplot.js
│ │ │ ├── jquery.mobile/
│ │ │ │ ├── jquery.mobile-1.4.5.css
│ │ │ │ ├── jquery.mobile-1.4.5.js
│ │ │ │ ├── jquery.mobile.external-png-1.4.5.css
│ │ │ │ ├── jquery.mobile.icons-1.4.5.css
│ │ │ │ ├── jquery.mobile.inline-png-1.4.5.css
│ │ │ │ ├── jquery.mobile.inline-svg-1.4.5.css
│ │ │ │ ├── jquery.mobile.structure-1.4.5.css
│ │ │ │ └── jquery.mobile.theme-1.4.5.css
│ │ │ ├── jquery.timeago.js
│ │ │ ├── plugins/
│ │ │ │ ├── jqplot.BezierCurveRenderer.js
│ │ │ │ ├── jqplot.barRenderer.js
│ │ │ │ ├── jqplot.blockRenderer.js
│ │ │ │ ├── jqplot.bubbleRenderer.js
│ │ │ │ ├── jqplot.canvasAxisLabelRenderer.js
│ │ │ │ ├── jqplot.canvasAxisTickRenderer.js
│ │ │ │ ├── jqplot.canvasOverlay.js
│ │ │ │ ├── jqplot.canvasTextRenderer.js
│ │ │ │ ├── jqplot.categoryAxisRenderer.js
│ │ │ │ ├── jqplot.ciParser.js
│ │ │ │ ├── jqplot.cursor.js
│ │ │ │ ├── jqplot.dateAxisRenderer.js
│ │ │ │ ├── jqplot.donutRenderer.js
│ │ │ │ ├── jqplot.dragable.js
│ │ │ │ ├── jqplot.enhancedLegendRenderer.js
│ │ │ │ ├── jqplot.enhancedPieLegendRenderer.js
│ │ │ │ ├── jqplot.funnelRenderer.js
│ │ │ │ ├── jqplot.highlighter.js
│ │ │ │ ├── jqplot.json2.js
│ │ │ │ ├── jqplot.logAxisRenderer.js
│ │ │ │ ├── jqplot.mekkoAxisRenderer.js
│ │ │ │ ├── jqplot.mekkoRenderer.js
│ │ │ │ ├── jqplot.meterGaugeRenderer.js
│ │ │ │ ├── jqplot.mobile.js
│ │ │ │ ├── jqplot.ohlcRenderer.js
│ │ │ │ ├── jqplot.pieRenderer.js
│ │ │ │ ├── jqplot.pointLabels.js
│ │ │ │ ├── jqplot.pyramidAxisRenderer.js
│ │ │ │ ├── jqplot.pyramidGridRenderer.js
│ │ │ │ ├── jqplot.pyramidRenderer.js
│ │ │ │ └── jqplot.trendline.js
│ │ │ └── viewportHeight.js
│ │ └── templates/
│ │ ├── base.html
│ │ ├── index.html
│ │ ├── plugins.html
│ │ └── status.html
│ ├── utils.py
│ └── voice.py
└── stenogotchi.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To reproduce**
Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots/debug logs**
If applicable, add screenshots or debug logs to help explain your problem.
**Software/hardware configuration (please complete the following information):**
- Stenogotchi version:
- Plugin version:
- OS:
- Hardware:
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
================================================
FILE: BUILDNOTES.md
================================================
# Build Notes
Connect all the parts before soldering anything in place. They will not function without properly secured connections, but checking how they fit together can save you from time-consuming mistakes. You'll need a soldering iron, solder and preferably also cutting pliers, a metal file and some flux to complete the build. Below you find some tips on how to fit the parts together as neatly as possible.
## Build Process
The depicted device did not have pre-soldered header pins, allowing me to shorten the pins on the underside rather than the top. Leaving the ends connecting to the display unaltered. Depending on your model you may therefore need to make some changes to the below steps.
- Push the header pins through from the underside, leaving 4.5 mm of the pins exposed on the top (pre-soldered headers extend 9 mm above the board for comparison).
- Layer the buttonSHIM on the top directly against the RPI0w.
- Solder the headers on the top to the buttonSHIM using plenty of flux. Leaving as much of the pins clean as possible so they still fit into the display's socket.
- Cut and remove the black plastic separator from the headers on the underside, leaving only bare pins on this side of the board as well.
- Solder the pins to the underside of the board, ensuring a good electrical connection to the RPI0w.
- Cut and file down the pins on the underside to make room for the UPS-Lite. Shortening them enough so they don't poke or scratch the battery.
- Secure the UPS-Lite using the included hexagonal nuts. The screws are just barely long enough to accommodate the thickness of both the RPI0w and buttonSHIM.
- Carefully press down the eINK module on the male pin headers without applying excessive force to the display itself.
## GPIO Header
Whether your RPI0w comes with pre-soldered male header or not, I recommend removing the black spacer. This way you can position the buttonSHIM directly against the board on the top with only the female header of the eINK display module and the buttonSHIM itself adding to the overall height of the device.
## Waveshare Display:
- In order to fit the screen onto the device as snugly as possible, make sure to desolder or by other means detach the large white connector. The yellow kapton tape didn't end up being needed since the covered components don't make contact with the RPI0w.


## ButtonSHIM:
- Take care to orient the module correctly.
- Soldering it into place you want to aim for small but secure solder joints. Any extra solder on the pins will limit how close to the board you can fit the screen. An X-Acto knife or small file can help with potentially needed clean-up.

## UPS-Lite:
- Make sure the pins on the bottom don't extend too far before screwing the module onto the board. They should not be allowed to touch the battery pack. I ended up filing the pins down quite a bit to ensure sufficient clearance. Shape and length of the pins is important for the pogo pins to make good contact.
- In the picture you can also spot a couple of the pins on the top and just how low the solder joints need to be for connecting the display.

## Real Time Clock
- If you want to add a RTC module to keep the device synced while powered off, I recommend the [DS3231](https://www.pishop.us/product/ds3231-real-time-clock-module-for-raspberry-pi/). A good [setup guide](https://learn.adafruit.com/adding-a-real-time-clock-to-raspberry-pi/set-rtc-time) has been published by Adafruit.
- Removing the female headers, the module fits neatly inside the UPS-Lite. You can see the correct wiring and positioning in the below picture. Don't forget to isolate it with some tape and form neat solder joints for the pins shared by the UPS-Lite.

================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0] - 2022-01-02
### Added
- Modifier, function and navigation-key support, both individually and in combination (unsupported inputs logged in plover.log).
- Reactions to new records in WPM-tracking mode.
- Plover dictionary lookup with eINK display and STENO input support. The plugin (dict_lookup) utilizes native Plover lookup functionality for full compatibility with supported dictionary formats.
- Buttonshim actions, both built-in and custom, accessible through web UI.
- Hardware build guide.
### Changed
- Plover v4.0.0.dev10 or higher required.
- Stenogotchi_link v0.3.0 required. Plugin now logs to plover.log.
- Character and personality adjustments.
- Simplified installation process.
- Added documentation for aligning text output with target device expected input language and layout.
- Web UI default port changed from 8080 to 80.
- Bluetooth connection established only once Plover has started.
### Fixed
- Significantly reduced input latency in STENO mode.
- Improved Bluetooth pairing and connection management.
- Web UI now starts only once a wifi connection has been established.
## [Unreleased]
### Added
### Changed
### Fixed
### Removed
## [0.0.5] - 2021-03-24
### Added
- DS3231 real time clock module wiring and positioning reference picture to README.
- Four new faces, producing processing animation when combined.
- WPM stats now track and display top result for session.
### Changed
- Led plugin and default patterns to better indicate noteworthy events.
### Fixed
- QWERTY-mode breaking bug introduced in v0.0.4.
- Letter capitalization, symbol characters and return key in STENO-mode.
## [0.0.4] - 2021-03-21
### Added
- This CHANGELOG file.
- User configurable bluetooth device name using main.plugins.plover_link.bt_device_name.
- User configurable list of bluetooth mac addresses, in order of priority, to auto-connect to using main.plugins.plover_link.bt_autoconnect_mac.
- User configurable option to clear eINK display at shutdown using ui.display.clear_at_shutdown.
- User configurable wpm calculation method using main.plugins.plover_link.wpm_method.
- User configurable wpm update frequency and calculation window in seconds using main.plugins.plover_link.wpm_timeout.
- More variety in mood indicators on common events.
- Requirements file.
### Changed
- Improved installation guide and documentation in README.
- All functionality in buttonshim plugin reworked into class for better integration with the project.
- More consistent logging messages.
- Stenogotchi_link version upgrade to v0.0.4.
### Fixed
- Reboot after initial setup or hostname change not working.
- Wifi status not showing as [OFF] if wifi is disabled at boot.
- Mode not changing to STENO when Plover becomes operational.
- All button press events not producing logging messages.
- Dependencies for stenogotchi_link Plover plugin corrected in setup.cfg.
## [0.0.3] - 2021-03-18
### Added
- First public pre-release version on GitHub.
- Stenogotchi, portable stenography using Plover and bluetooth keyboard emulation on a Raspberry Pi Zero W. With support for Waveshare 2.13 v2, ButtonSHIM and UPS-Lite v1.2 modules.
- Plover plugin stenogotchi_link for communicating between Plover and Stenogotchi.
- README now includes tested installation guide no longer requiring building PyQt5 from source.
- README now includes basic configuration and usage documentation.
- LICENSE file.
[Unreleased]: https://github.com/Anodynous/stenogotchi/compare/v0.1.0...dev
[0.1.0]: https://github.com/Anodynous/stenogotchi/compare/v0.0.5...v0.1.0
[0.0.5]: https://github.com/Anodynous/stenogotchi/compare/v0.0.4...v0.0.5
[0.0.4]: https://github.com/Anodynous/stenogotchi/compare/v0.0.3...v0.0.4
[0.0.3]: https://github.com/Anodynous/stenogotchi/releases/tag/v0.0.3
================================================
FILE: LICENSE
================================================
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
.
================================================
FILE: README.md
================================================
# Stenogotchi

Aim of the project is to deliver a cheap and portable device for running [Plover](https://www.openstenoproject.org/ "Plover: Open Steno Project") where local installation on the host is impossible or simply not preferred. A stand-alone link enabling stenography using any input device supported by Plover on any device accepting bluetooth keyboards.
Likely use-cases include:
- Mobile devices
- Corporate and public computers restricting software installations
- Hassle-free switching between devices without the need to install and configure Plover
- On-the-go stenographic recording
Stenogotchi is built on top of [Pwnagotchi](https://github.com/evilsocket/pwnagotchi), but instead of hungering for WPA handshakes it feeds on your steno chords. It emulates a BT HID device for connecting to a host and output can be toggled between STENO and QWERTY mode on the fly. The friendly UI optimized for low-power eINK displays is also accessible as a web UI version, making both the eINK display and buttonSHIM modules optional. If the RPI0w always will be powered over microUSB a separate battery pack is not needed. The suggested UPS-Lite 1000 mAH battery provides 3+ hours of runtime and supports pass-through charging.
## Hardware
| Module | Status |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------|
| [Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/) or [Raspberry Pi Zero 2 W](https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/) | Required |
| MicroSD card (min 4 GiB) | Required |
| [Waveshare 2.13 v.2](https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT) | Recommended |
| [ButtonSHIM](https://shop.pimoroni.com/products/button-shim) | Recommended |
| [UPS-Lite v1.2](https://hackaday.io/project/173847-ups-lite) | Recommended |
| [DS3231 RTC Module](https://www.pishop.us/product/ds3231-real-time-clock-module-for-raspberry-pi/) | Optional |
See the [build notes](BUILDNOTES.md) for guidance on fitting the parts together.
## Installation
All commands should be executed as root. The installation process can be completed headless.
1. Flash and configure DietPi image, see https://dietpi.com/docs/install/
* For headless installation, set AUTO_SETUP_NET_WIFI_ENABLED=1 in dietpi.txt and enter wifi credentials in dietpi-wifi.txt before first boot.
* When prompted allow dietpi to update, change passwords and disable serial console. Under dietpi-config > advanced options enable:
* Bluetooth
* SPI state (needed by eINK screen)
* I2C state (needed by Buttonshim module and for UPS-Lite power readings)
* Using command 'dietpi-autostart' enable automatic login of user 'root' to local terminal (option #7)
2. Install dependencies
apt-get install git xorg xserver-xorg-video-fbdev python3-pip python3-rpi.gpio python3-gi libtiff5 libopenjp2-7 bluez screen rfkill -y
pip3 install file_read_backwards flask flask-wtf flask-cors evdev python-xlib pillow spidev jsonpickle dbus-python toml
3. Download and install Plover (v4.0.0.dev10)
wget https://github.com/openstenoproject/plover/releases/download/v4.0.0.dev10/plover-4.0.0.dev10-py3-none-any.whl
pip3 install plover-4.0.0.dev10-py3-none-any.whl
* If you'd rather try the [continuous build of Plover](https://github.com/openstenoproject/plover/releases/tag/continuous) for the latest improvements, you will need to install both build-essential and python3-dev through apt-get first. Switch back to dev10 if you experience issues.
4. Clone the Stenogotchi repository and install the plover plugin "stenogotchi_link"
git clone https://github.com/Anodynous/stenogotchi.git
pip3 install ./stenogotchi/plover_plugin/
5. Configure Plover. Setup will ultimately depend on your own preferences and keyboard, but below is what I use. Make sure to include at least 'auto_start = True' and the '[Plugins]' section in your own config.
mkdir -p /root/.config/plover/
nano /root/.config/plover/plover.cfg
#----------
[Output Configuration]
space_placement = After Output
start_attached = True
start_capitalize = False
undo_levels = 30
[Logging Configuration]
enable_stroke_logging = False
enable_translation_logging = False
[Machine Configuration]
auto_start = True
machine_type = Gemini PR
[Gemini PR]
baudrate = 9600
bytesize = 8
parity = N
port = /dev/ttyACM0
stopbits = 1
timeout = 2.0
[Plugins]
enabled_extensions = ["stenogotchi_link"]
6. Run installation script to finalize setup of Stenogotchi. Optional boot time improvements offered by script described in "Significantly reduce boot time" section below.
chmod +x ./stenogotchi/initial_setup.sh
./stenogotchi/initial_setup.sh
7. Configure Stenogotchi settings after reboot completes
nano /etc/stenogotchi/config.toml
#----------modify the config as you see fit----------#
main.plugins.buttonshim.enabled = true
main.plugins.upslite.enabled = true
main.plugins.evdevkb.enabled = true
main.plugins.plover_link.bt_autoconnect_mac = '00:DE:AD:BE:EF:00,11:DE:AD:BE:EF:11'
#----------
## Updating
cd ~/stenogotchi
git pull
pip3 install ~/stenogotchi/plover_plugin/
## Configuration / Troubleshooting
* Configuration files are placed in /etc/stenogotchi/. Create a separate file named config.toml containing overrides to the defaults. Don't edit default.toml directly as it will be overwritten on Stenogotchi version updates.
* The logfile is created in /var/log, which dietpi by default mounts to RAM to preserve the SD card lifespan. To make the file persistent across reboots it needs to be written to the disk. To aid with troubleshooting, either set the location to another existing folder using 'main.log.path' in config.toml or change the global dietpi setting using dietpi-software > Log System.
* If your target device expects a different input language or keyboard layout than US qwerty, use setxkbmap to align it. This should be added to the beginning of your .xinitrc file to run automatically at startup. For German language and dvorak layout for example the below would be used.
setxkbmap -layout de -variant dvorak
### Bluetooth connections
* Define your bluetooth devices in main.plugins.plover_link.bt_autoconnect_mac to auto-connect on boot. Multiple comma-separated devices in order of priority can be given. If no connection attempts are successful at boot, the device will fall back to listening for incoming connection and pairing attempts.
* Only one active connection at a time is supported. To switch remote devices, disable bluetooth on the remote device and wait around 10 seconds before initiating a new connection. The Stenogotchi will attempt to reconnect to the lost device for a few seconds before falling back to listening for new incoming connections.
* Issues with pairing or connecting after changes in bluetooth configurations can normally be fixed through unpairing and re-pairing. On the Stenogotchi side this is best handled through bluetoothctl using the below process. Re-initiate pairing process from remote device after the pairing information has been cleared on both host and client side.
bluetoothctl
[bluetooth]# paired-devices
Device 00:DE:AD:BE:EF:00 Anodynous' Ipad
[bluetooth]# remove 00:DE:AD:BE:EF:00
[bluetooth]# exit
### Significantly reduce boot time
* Set ARM initial turbo to the max (60s) under dietpi-config > performance options to reduce boot time. You can also play around with overclocking, throttling and cpu governor to find a suitable balance between performance and power draw.
* Disable dietpi and apt update check at boot:
nano /boot/dietpi.txt
#----------
CONFIG_CHECK_DIETPI_UPDATES=0
CONFIG_CHECK_APT_UPDATES=0
* Disable waiting for network and time sync at boot. Doing this you should be aware that the RPI0w does not have a hardware clock. It will lose track of real world time as soon it is powered off, making log timestamps or any time based action you may set up unreliable. None of this is important for the core functionality of the Stenogotchi and disabling time-sync at boot can shave up to a minute off the boot process. By adding a cheap I2C hardware clock you can completely remove the need for network sync. Many modules are small enough to fit in the empty space of the UPS-Lite or under the eINK screen. See the [build notes](BUILDNOTES.md) for more directions.
nano /boot/dietpi.txt
#----------
CONFIG_BOOT_WAIT_FOR_NETWORK=0
CONFIG_NTP_MODE=0
## Usage

### Buttonshim
Below long-press (1s) actions are pre-defined. Short-press triggers user configurable terminal commands, e.g. rclone sync of Plover dictionaries with cloud storage.
* Button A - toggle QWERTY / STENO mode
* Button B - toggle wpm & strokes readings
* Button C - toggle dictionary lookup mode
* Button D - toggle wifi (reboot persistent)
* Button E - shutdown
## Project roadmap
- [x] Proof of concept. Headless RPI0W running Plover, emulating bluetooth HID device and seamlessly piping steno output over BT to host
- [x] Create proper plugin for integration with Plover
- [x] Integrate bluetooth HID server as Stenogotchi plugin
- [x] Support for eINK display and web UI
- [x] Support for buttons for built-in and user customizable actions
- [x] Support for external battery charge readings on UI
- [x] Create Stenogotchi plugin to capture QWERTY input while blocking Plover
- [x] ButtonSHIM toggle to enable/disable WIFI, persisting reboot
- [x] ButtonSHIM toggle between STENO and QWERTY output mode
- [x] WPM readings for STENO mode
- [x] Full installation guide for Plover and Stenogotchi using DietPi as base image
- [x] Dictionary lookup using eINK screen
- [x] Decrease steno latency
- [x] Improved web UI, including buttonSHIM functionality
- [ ] Plover chords for triggering Stenogotchi actions
- [ ] Dictionary additions using eINK screen
- [ ] Document configuration options and buttonSHIM functionality
- [ ] Clean up and optimize code, fix bugs and add test suite
- [ ] Expand Stenogotchi statuses, reactions and mood indicators
- [ ] On-the-fly updating and reloading of Plover dictionaries
- [ ] Simple AI for shaping personality of the Stenogotchi
- [ ] Proper usage and configuration documentation
- [ ] Support for other eINK display modules
## Pictures



## License
Released under the GPL3 license.
================================================
FILE: initial_setup.sh
================================================
#!/bin/bash
# Finalize setup of Stenogotchi. Only needs to be run once.
# Use the below steps if you prefer not executing the automated script or run into issues.
# ------------------------------------------------------------------------------------------
# 1) Add configuration file for D-Bus service used by Stenogotchi and the Plover plugin to communicate
#cp ./stenogotchi/plover_plugin/stenogotchi_link/com.github.stenogotchi.conf /etc/dbus-1/system.d/
# 2) Modify service file to remove input bluetooth plugin so it does not grab the sockets Stenogotchi requires access to.
# Append '-P input' to existing line in '/lib/systemd/system/bluetooth.service' file to end up with:
#ExecStart=/usr/libexec/bluetooth/bluetoothd -P input
# 3) Ensure 'root' user autologin to local terminal is enabled using dietpi-autostart
# 4) Create file '~/.bash_profile' with the below content (excluding #-characters)
#if [[ -z $DISPLAY ]] && [[ $(tty) = /dev/tty1 ]]; then
# screen -S stenogotchi -dm python3 /root/stenogotchi/stenogotchi.py --debug
# xinit
#fi
# 5) Create file '~/.xinitrc' with the below content
#screen -S plover plover -g none -l debug
# 6) Launch Stenogotchi manually once which will complete setup and reboot device.
# Configure settings per preference in '/etc/stenogotchi/config.toml' after reboot.
# 7) Optional but highly recommended. Reduce boot time by disabling Dietpi and APT update check and setting initial turbo boost to 60s.
# In /boot/dietpi.txt set below values:
#CONFIG_CHECK_DIETPI_UPDATES=0
#CONFIG_CHECK_APT_UPDATES=0
# In /boot/config.txt set:
#initial_turbo=60
# 8) Optional but recommended. Reduce boot time further by disabling waiting for network and NTP time sync.
# Doing this you should be aware that the RPI0w does not have a hardware clock. It will lose track of real world time as soon it is powered off, making log timestamps or any time based action you may set up unreliable. None of this is important for the core functionality of the Stenogotchi and disabling time-sync at boot can shave up to a minute off the boot process. By adding a cheap I2C hardware clock you can completely remove the need for network sync.
# In /boot/dietpi.txt set below values:
#CONFIG_BOOT_WAIT_FOR_NETWORK=0
#CONFIG_NTP_MODE=0
# 9) Launch stenogotchi manually and configure using /etc/stenogotchi/config.toml after reboot has completed
#python3 ./stenogotchi/stenogotchi.py
FLAG="/var/log/_stenogotchi_setup_completed.log"
BASEDIR=$(cd `dirname $0` && pwd)
# Check and run script only if it hasn't been done already
if [ ! -f $FLAG ]; then
# Set flag indicating script has been run
touch $FLAG
# Configure bluetooth service
printf "Adding configuration file for D-Bus service used by Stenogotchi and the Plover plugin to communicate.\n"
sleep 1
cp $BASEDIR/plover_plugin/stenogotchi_link/com.github.stenogotchi.conf /etc/dbus-1/system.d/ && printf "Success!\n" || printf "Failed! \nPlease manually copy 'com.github.stenogotchi.conf' from stenogotchi_link folder to '/etc/dbus-1/system.d/'"
printf "\nModifying service file to remove input bluetooth plugin so it does not grab the sockets Stenogotchi requires access to.\n"
sleep 1
# Only add in case not appended yet
if ! grep -q "\-P input" /lib/systemd/system/bluetooth.service; then
sed '/^ExecStart=/s/$/ -P input/' /lib/systemd/system/bluetooth.service -i.bkp && printf "Success!\n" || printf "Failed! \nPlease manually modify existing line in '/lib/systemd/system/bluetooth.service' to 'ExecStart=/usr/lib/bluetooth/bluetoothd -P input' "
else
printf "Skipped! Bluetooth input plugin already disabled\n"
fi
# Configure autostart of plover
printf "\nConfigure Stenogotchi to auto-start at boot. Will run under screen session named 'stenogotchi'\n"
sleep 1
echo "screen -S plover plover -g none -l debug" > ~/.xinitrc && printf "Success!\n" || printf "Failed! \nPlease manually create file '~/.xinitrc' with contents 'screen -S plover plover -g none -l debug'"
# Configure autostart of stenogotchi
read -r -d '' AUTOSTART << "EOM"
if [[ -z $DISPLAY ]] && [[ $(tty) = /dev/tty1 ]]; then
screen -S stenogotchi -dm python3 /root/stenogotchi/stenogotchi.py --debug
xinit
fi
EOM
printf "\nConfigure Plover to auto-start at boot. Will run under screen session named 'plover'\n"
sleep 1
echo "$AUTOSTART" > ~/.bash_profile && printf "Success!\n" || printf "Failed! \nPlease manually create '~/.bash_profile' file with contents \n"$AUTOSTART" "
# Optionally: Optimize boot time
printf "\nReduce boot time by disabling automatic Dietpi and APT update checks. Set initial turbo boost to 60s."
printf "\nSettings can be changed later in '/boot/dietpi.txt' and '/boot/config.txt'\n"
while true; do
read -p "Do you wish to enable these highly recommended optimizations? (y/n)" yn
case $yn in
[Yy]* )
sed -i 's/CONFIG_CHECK_DIETPI_UPDATES=.*/CONFIG_CHECK_DIETPI_UPDATES=0/' /boot/dietpi.txt && printf "set CONFIG_CHECK_DIETPI_UPDATES=0 in /boot/dietpi.txt\n";
sed -i 's/CONFIG_CHECK_APT_UPDATES=.*/CONFIG_CHECK_APT_UPDATES=0/' /boot/dietpi.txt && printf "set CONFIG_CHECK_APT_UPDATES=0 in /boot/dietpi.txt\n";
sed -i 's/initial_turbo=.*/initial_turbo=60/' /boot/config.txt && printf "set initial_turbo=60 in /boot/config.txt\n";
break;;
[Nn]* )
printf "Boot optimization skipped. Enable later by setting CONFIG_CHECK_DIETPI_UPDATES=0 and CONFIG_CHECK_APT_UPDATES=0 in /boot/dietpi.txt and initial_turbo=60 in /boot/config.txt\n";
break;;
* ) echo "Please answer yes or no.";;
esac
done
# Optionally: Disable waiting for network and NTP-sync
printf "\nDisable waiting for network and NTP-sync at boot to significantly reduce boot time when networks are unavailable."
printf "\nSettings can be changed later in '/boot/dietpi.txt'\n"
while true; do
read -p "Do you wish to enable these optimizations? (y/n)" yn
case $yn in
[Yy]* )
sed -i 's/CONFIG_BOOT_WAIT_FOR_NETWORK=.*/CONFIG_BOOT_WAIT_FOR_NETWORK=0/' /boot/dietpi.txt && printf "set CONFIG_BOOT_WAIT_FOR_NETWORK=0 in /boot/dietpi.txt\n";
sed -i 's/CONFIG_NTP_MODE=.*/CONFIG_NTP_MODE=0/' /boot/dietpi.txt && printf "set CONFIG_NTP_MODE=0 in /boot/dietpi.txt\n";
break;;
[Nn]* )
printf "Boot optimization skipped. Enable later by setting CONFIG_BOOT_WAIT_FOR_NETWORK=0 and CONFIG_NTP_MODE=0 in /boot/dietpi.txt\n";
break;;
* ) echo "Please answer yes or no.";;
esac
done
# Initial launch of Stenogotchi which triggers reboot
printf "\nFirst time launch of Stenogotchi. Configure settings in /etc/stenogotchi/config.toml after reboot\n"
sleep 5
python3 $BASEDIR/stenogotchi.py
else
printf "Setup script has already been executed and should not need to be run again. (Delete '/var/log/_stenogotchi_setup_completed.log' to bypass check)\n"
fi
================================================
FILE: plover_plugin/setup.cfg
================================================
[metadata]
name = stenogotchi_link
version = 0.3.0
description = A plugin for exposing Plover events and communicating with Stenogotchi over D-Bus
long_description =
author = Anodynous
author_email =
license = GNU General Public License v3 or later (GPLv3+)
url = https://github.com/Anodynous/Stenogotchi
classifiers =
Development Status :: 4 - Beta
Environment :: Plugins
Intended Audience :: End Users/Desktop
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Operating System :: Linux
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
keywords = plover plover_plugin stenogotchi
[options]
zip_safe = True
install_requires =
plover>=4.0.0.dev10
jsonpickle
dbus-python
textstat
PyGObject
packages =
stenogotchi_link
[options.entry_points]
plover.extension =
stenogotchi_link = stenogotchi_link.stenogotchi_link:EngineServer
================================================
FILE: plover_plugin/setup.py
================================================
from setuptools import setup
setup()
================================================
FILE: plover_plugin/stenogotchi_link/__init__.py
================================================
================================================
FILE: plover_plugin/stenogotchi_link/clients.py
================================================
#!/usr/bin/env python3
import plover.log
import dbus, dbus.exceptions
from dbus.mainloop.glib import DBusGMainLoop
from time import sleep
from threading import Thread
from gi.repository import GLib
from Xlib import X, XK
from plover.oslayer.xkeyboardcontrol import KeyboardEmulation, uchr_to_keysym
from plover import key_combo as plover_key_combo
from stenogotchi_link.keymap import plover_convert, plover_modkey
SERVER_DBUS = 'com.github.stenogotchi'
SERVER_SRVC = '/com/github/stenogotchi'
ERROR_NO_SERVER: str = 'A server is not currently running'
ERROR_SERVER_RUNNING: str = 'A server is already running'
TIME_SLEEP = 0
class StenogotchiClient:
"""
Transmits Plover event updates to, and listens for signals from, Stenogotchi over D-Bus.
"""
def __init__(self, engineserver):
self._engineserver = engineserver
self._setup_dbus_loop()
self._setup_object()
def _setup_dbus_loop(self):
DBusGMainLoop(set_as_default=True)
self._mainloop = GLib.MainLoop()
self._thread = Thread(target=self._mainloop.run)
self._thread.start()
def _setup_object(self):
try:
self.bus = dbus.SystemBus()
self.stenogotchiobject = self.bus.get_object(SERVER_DBUS, SERVER_SRVC)
self.stenogotchi_service = dbus.Interface(self.stenogotchiobject, SERVER_DBUS)
# Add signal receiver for incoming messages
self.stenogotchi_signal = self.bus.add_signal_receiver(path=SERVER_SRVC,
handler_function=self.stenogotchi_signal_handler,
dbus_interface=SERVER_DBUS,
signal_name='signal_to_plover')
except dbus.exceptions.DBusException as e:
plover.log.error(f'[stenogotchi_link] Failed to initialize D-Bus object: {str(e)}')
def _exit(self):
self._mainloop.quit()
def plover_is_running(self, b):
self.stenogotchi_service.plover_is_running(b)
# If plover is shutting down, quit mainloop
if not b:
self._exit()
def plover_is_ready(self, b):
self.stenogotchi_service.plover_is_ready(b)
def plover_machine_state(self, s):
self.stenogotchi_service.plover_machine_state(s)
def plover_output_enabled(self, b):
self.stenogotchi_service.plover_output_enabled(b)
def plover_wpm_stats(self, s):
self.stenogotchi_service.plover_wpm_stats(s)
def plover_strokes_stats(self, s):
self.stenogotchi_service.plover_strokes_stats(s)
def send_backspaces(self, y):
self.stenogotchi_service.send_backspaces_stenogotchi(y)
def send_string(self, s):
self.stenogotchi_service.send_string_stenogotchi(s)
def send_key_combination(self, s):
self.stenogotchi_service.send_key_combination_stenogotchi(s)
def send_lookup_results(self, l):
self.stenogotchi_service.plover_translation_handler(l)
def stenogotchi_signal_handler(self, dict):
# Enable and disable wpm/strokes meters
if 'lookup_word' in dict:
self._engineserver.lookup_word(dict['lookup_word'])
if 'lookup_stroke' in dict:
self._engineserver.lookup_stroke(dict['lookup_stroke'])
if 'output_to_stenogotchi' in dict:
self._engineserver._output_to_stenogotchi = dict['output_to_stenogotchi']
if 'start_wpm_meter' in dict:
wpm_method = dict['wpm_method']
wpm_timeout = int(dict['wpm_timeout'])
plover.log.info('[stenogotchi_link] Starting WPM meter')
if dict['start_wpm_meter'] == 'wpm and strokes':
self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=True, wpm_method=wpm_method, wpm_timeout=wpm_timeout)
elif dict['start_wpm_meter'] == 'wpm':
self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=False, wpm_method=wpm_method, wpm_timeout=wpm_timeout)
elif dict['start_wpm_meter'] == 'strokes':
self._engineserver.start_wpm_meter(enable_wpm=False, enable_strokes=True, wpm_method=wpm_method, wpm_timeout=wpm_timeout)
if 'stop_wpm_meter' in dict:
plover.log.info('[stenogotchi_link] Stopping WPM meter')
if dict['stop_wpm_meter'] == 'wpm and strokes':
self._engineserver.stop_wpm_meter(disable_wpm=True, disable_strokes=True)
elif dict['stop_wpm_meter'] == 'wpm':
self._engineserver.stop_wpm_meter(disable_wpm=True, disable_strokes=False)
elif dict['stop_wpm_meter'] == 'strokes':
self._engineserver.stop_wpm_meter(disable_wpm=False, disable_strokes=True)
class CustomKeyboardEmulation(KeyboardEmulation):
"""
We use the Plover implementation, but change _update_keymap()
to prefer lower keycode rather than low modkeys combos in mapping.
This way we avoid some issues when mapping to BT HID keycodes.
"""
def _update_keymap(self):
'''Analyse keymap, build a mapping of keysym to (keycode + modifiers),
and find unused keycodes that can be used for unmapped keysyms.
'''
self._keymap = {}
self._custom_mappings_queue = []
# Analyse X11 keymap.
keycode = self._display.display.info.min_keycode
keycode_count = self._display.display.info.max_keycode - keycode + 1
for mapping in self._display.get_keyboard_mapping(keycode, keycode_count):
mapping = tuple(mapping)
while mapping and X.NoSymbol == mapping[-1]:
mapping = mapping[:-1]
if not mapping:
# Free never used before keycode.
custom_mapping = [self.UNUSED_KEYSYM] * self.CUSTOM_MAPPING_LENGTH
custom_mapping[-1] = self.PLOVER_MAPPING_KEYSYM
mapping = custom_mapping
elif self.CUSTOM_MAPPING_LENGTH == len(mapping) and \
self.PLOVER_MAPPING_KEYSYM == mapping[-1]:
# Keycode was previously used by Plover.
custom_mapping = list(mapping)
else:
# Used keycode.
custom_mapping = None
for keysym_index, keysym in enumerate(mapping):
if keysym == self.PLOVER_MAPPING_KEYSYM:
continue
if keysym_index not in (0, 1, 4, 5):
continue
modifiers = 0
if 1 == (keysym_index % 2):
# The keycode needs the Shift modifier.
modifiers |= X.ShiftMask
if 4 <= keysym_index <= 5:
# 3rd (AltGr) level.
modifiers |= X.Mod5Mask
mapping = self.Mapping(keycode, modifiers, keysym, custom_mapping)
if keysym != X.NoSymbol and keysym != self.UNUSED_KEYSYM:
# Some keysym are mapped multiple times, prefer lower keycode (Plover prefers lower modifiers combos).
previous_mapping = self._keymap.get(keysym)
if previous_mapping is None or mapping.keycode < previous_mapping.keycode:
self._keymap[keysym] = mapping
if custom_mapping is not None:
self._custom_mappings_queue.append(mapping)
keycode += 1
# Determine the backspace mapping.
backspace_keysym = XK.string_to_keysym('BackSpace')
self._backspace_mapping = self._get_mapping(backspace_keysym)
assert self._backspace_mapping is not None
assert self._backspace_mapping.custom_mapping is None
# Get modifier mapping.
self.modifier_mapping = self._display.get_modifier_mapping()
class BTClient:
"""
Transmits keystroke output from Plover to Stenogotchi as HID messages over D-Bus.
"""
def __init__(self):
self.target_length = 6
self.mod_keys = 0b00000000
self.pressed_keys = []
self.bus = dbus.SystemBus()
self.btkobject = self.bus.get_object(SERVER_DBUS, SERVER_SRVC)
self.btk_service = dbus.Interface(self.btkobject, SERVER_DBUS)
self.ke = CustomKeyboardEmulation()
self._backspace_mapping_hid = plover_convert(self.ke._backspace_mapping.keycode)
def update_mod_keys(self, mod_key, value):
"""
Which modifier keys are active is stored in an 8 bit number.
Each bit represents a different key. This method takes which bit
and its new value as input
:param mod_key: The value of the bit to be updated with new value
:param value: Binary 1 or 0 depending if pressed or released
"""
bit_mask = 1 << (7-mod_key)
if value: # set bit
self.mod_keys |= bit_mask
else: # clear bit
self.mod_keys &= ~bit_mask
def update_keys(self, norm_key, value):
"""
Sets the active normal keys
"""
if norm_key < 0: # Log for debug purposes in case keycode isn't valid
plover.log.error(f"[stenogotchi_link] KeyError in update_keys, unable to send keycode: {norm_key}")
return
if value < 1:
self.pressed_keys.remove(norm_key)
elif norm_key not in self.pressed_keys:
self.pressed_keys.insert(0, norm_key)
len_delta = self.target_length - len(self.pressed_keys)
if len_delta < 0:
self.pressed_keys = self.pressed_keys[:len_delta]
elif len_delta > 0:
self.pressed_keys.extend([0] * len_delta)
@property
def state(self):
"""
property with the HID message to be sent
:return: bytes of HID message
"""
return [0xA1, 0x01, self.mod_keys, 0, *self.pressed_keys]
def clear_mod_keys(self):
self.mod_keys = 0b00000000
def clear_keys(self):
self.pressed_keys = []
def send_keys(self, state_list=None):
if not state_list:
self.btk_service.send_keys([state_list])
else:
flat_list = state_list
self.btk_service.send_keys(flat_list)
def send_backspaces(self, number_of_backspaces):
self.clear_keys()
self.clear_mod_keys()
state_list = []
for x in range(number_of_backspaces):
self.update_keys(self._backspace_mapping_hid, 1)
state_list.append(self.state)
self.update_keys(self._backspace_mapping_hid, 0)
state_list.append(self.state)
self.send_keys(state_list)
def map_hid_events(self, keycode, modifiers=None):
""" Returns a list of HID bytearrays to produce the key combination.
Arguments:
keycode -- An integer in the inclusive range [8-255].
modifiers -- An 8-bit bit mask indicating if the key
pressed is modified by other keys, such as Shift, Capslock,
Control, and Alt.
TODO: International, accented characters and a range of symbols are not working with the US keyboard layout.
Could be solved using Alt codes on Windows, but Mac/iOS uses different Option codes. Complicated to solve
in a clean way as we only emulate a BT keyboard and all characters must be produced on the remote host.
-> Look into separate WIN/MAC translation tables. Profiles which can be linked to BT MAC adresses.
"""
self.clear_keys()
self.clear_mod_keys()
state_sublist = []
#modifiers_list = [
# self.ke.modifier_mapping[n][0]
# for n in range(8)
# if (modifiers & (1 << n))
#]
#for mod_keycode in modifiers_list:
# self.update_mod_keys(plover_modkey(mod_keycode), 1)
normkey_hid = plover_convert(keycode)
# Log issues with mapping any keycodes
if normkey_hid < 0:
plover.log.error(f"[stenogotchi_link] Unable to map keycode: {keycode} using plover_convert in keymap.py.")
return
if modifiers:
modkey_hid = plover_modkey(modifiers)
if modkey_hid < 0:
plover.log.error(f"[stenogotchi_link] Unable to map keycode: {keycode} using plover_modkey in keymap.py.")
# Apply modifiers
if modifiers:
# TODO: Handle combination of modifiers. 129 for example is shift (1) + AltGr (128)
self.update_mod_keys(modkey_hid, 1)
# Press and release the base key.
self.update_keys(normkey_hid, 1)
state_sublist.append(self.state)
sleep(TIME_SLEEP)
self.update_keys(normkey_hid, 0)
state_sublist.append(self.state)
# Release modifiers
if modifiers > 0:
self.update_mod_keys(modkey_hid, 0)
state_sublist.append(self.state)
return state_sublist
#for mod_keycode in reversed(modifiers_list):
# self.update_mod_keys(plover_modkey(mod_keycode), 0)
def send_string(self, s):
state_list = []
for char in s:
keysym = uchr_to_keysym(char)
mapping = self.ke._get_mapping(keysym)
#plover.log.debug(f"[stenogotchi_link] mapping : '{mapping}' mapping.keycode : '{mapping.keycode}' and mapping.modifier : '{mapping.modifiers}' (for keysym : '{keysym}' given char : '{char}')")
if mapping is None:
continue
sublist = self.map_hid_events(mapping.keycode, mapping.modifiers)
if sublist:
state_list.extend(sublist)
if len(state_list) > 0:
self.send_keys(state_list)
def send_key_combination(self, combo_string: str):
"""
Custom implementation of Plover send_key_combination function
since we do not work with emulated key events or need to fight
with a KeyboardCapture instance.
Arguments:
combo_string -- A string representing a sequence of key
combinations. Keys are represented by their names in the
Xlib.XK module, without the 'XK_' prefix. For example, the
left Alt key is represented by 'Alt_L'. Keys are either
separated by a space or a left or right parenthesis.
Parentheses must be properly formed in pairs and may be
nested. A key immediately followed by a parenthetical
indicates that the key is pressed down while all keys enclosed
in the parenthetical are pressed and released in turn. For
example, Alt_L(Tab) means to hold the left Alt key down, press
and release the Tab key, and then release the left Alt key.
"""
self.clear_keys()
self.clear_mod_keys()
state_list = []
# Parse and validate combo.
key_events = [
(keycode, 1 if pressed else 0) for keycode, pressed
in plover_key_combo.parse_key_combo(combo_string, self.ke._get_keycode_from_keystring)
]
# Send key events to emulate combination.
for keycode, event_type in key_events:
# Convert to HID
normkey_hid = plover_convert(keycode)
if normkey_hid == -1:
modkey_hid = plover_modkey(keycode)
else:
modkey_hid = -1
# Update and send keycode if mapped, otherwise log
if modkey_hid > -1:
self.update_mod_keys(modkey_hid, event_type)
state_list.append(self.state)
elif normkey_hid > -1:
self.update_keys(normkey_hid, event_type)
state_list.append(self.state)
else:
plover.log.debug(f"[stenogotchi_link]Received key_combination from Plover: {combo_string}, resulting in key_events: {key_events}")
plover.log.error(f"Unable to map keycode: {keycode}, in keymap.py (event_type: {event_type})")
self.send_keys(state_list)
================================================
FILE: plover_plugin/stenogotchi_link/com.github.stenogotchi.conf
================================================
================================================
FILE: plover_plugin/stenogotchi_link/keymap.py
================================================
# Find HID keycode mapping here: https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
# Linux keycodes can be found in /usr/share/X11/xkb/keycodes/evdev
# Linux keycode used by Plover : HID keycode used for BT
plover_keytable = {
# Function row.
67: 58, #"F1",
68: 59, #"F2",
69: 60, #"F3",
70: 61, #"F4",
71: 62, #"F5",
72: 63, #"F6",
73: 64, #"F7",
74: 65, #"F8",
75: 66, #"F9",
76: 67, #"F10",
95: 68, #"F11",
96: 69, #"F12",
# Number row.
49: 53, #"`",
10: 30, #"1",
11: 31, #"2",
12: 32, #"3",
13: 33, #"4",
14: 34, #"5",
15: 35, #"6",
16: 36, #"7",
17: 37, #"8",
18: 38, #"9",
19: 39, #"0",
20: 45, #"-",
21: 46, #"=",
51: 50, #"\\",
# Upper row.
24: 20, #"q",
25: 26, #"w",
26: 8, #"e",
27: 21, #"r",
28: 23, #"t",
29: 28, #"y",
30: 24, #"u",
31: 12, #"i",
32: 18, #"o",
33: 19, #"p",
34: 47, #"[",
35: 48, #"]",
# Home row.
38: 4, #"a",
39: 22, #"s",
40: 7, #"d",
41: 9, #"f",
42: 10, #"g",
43: 11, #"h",
44: 13, #"j",
45: 14, #"k",
46: 15, #"l",
47: 51, #";",
48: 52, #"'",
# Bottom row.
52: 29, #"z",
53: 27, #"x",
54: 6, #"c",
55: 25, #"v",
56: 5, #"b",
57: 17, #"n",
58: 16, #"m",
59: 54, #",",
60: 55, #".",
61: 56, #"/",
# Other keys.
22 : 42, #"BackSpace",
119: 76, #"Delete",
116: 81, #"Down",
115: 77, #"End",
9 : 41, #"Escape",
110: 74, #"Home",
118: 73, #"Insert",
113: 80, #"Left",
117: 78, #"Page_Down",
112: 75, #"Page_Up",
36 : 40, #"Return",
114: 79, #"Right",
23 : 43, #"Tab",
111: 82, #"Up",
65 : 44, #"Space",
66 : 57, #Caps_Lock,
#"KEY_BACK": 241,
#"KEY_FORWARD": 242,
#"KEY_REFRESH": 250,
#"KEY_SYSRQ": 70,
# Keypad keys
#"KEY_NUMLOCK": 83,
#"KEY_SCROLLLOCK": 71,
79: 95, #kp_7 - "KEY_KP7"
80: 96, #kp_8 - "KEY_KP8"
81: 97, #kp_9 - "KEY_KP9"
#"KEY_KPMINUS": 86,
83: 92, #kp_4 - "KEY_KP4"
84: 93, #kp_5 - "KEY_KP5"
85: 94, #kp_6 - "KEY_KP6"
#"KEY_KPPLUS": 87,
87: 89, #kp_1 - "KEY_KP1"
88: 90, #kp_2 - "KEY_KP2"
89: 91, #kp_3 - "KEY_KP3"
90: 98, #kp_0 - "KEY_KP0"
126: 215, # "Keypad ±"
#"KEY_KPDOT": 99,
# TODO: Media keys not working correctly
# Media keys
172: 232, # AudioPlay - "KEY_PLAYPAUSE"
209: 232, # AudioPause - "KEY_PLAYPAUSE"
121: 239, # AudioMute - "KEY_MUTE"
122: 238, #AudioLowerVolume - "KEY_VOLUMEDOWN"
123: 237, #AudioRaiseVolume - "KEY_VOLUMEUP"
173: 234, #AudioPrev - "KEY_PREVIOUSSONG"
171: 235, #AudioNext - "KEY_NEXTSONG"
174: 233, #AudioStop - "KEY_STOPCD"
169: 236, #Eject - "KEY_EJECTCD"
}
# Map modifier keys to array element in the bit array
plover_modkeys = {
# TODO: AltGr will not on MacOS/iOs/iPadOS produce expected character.
134: 0, #Super_R - "KEY_RIGHTMETA", WIN-key
128: 1, #"KEY_RIGHTALT", AltGr used to produce some symbols
62: 2, #"KEY_RIGHTSHIFT"
105: 3, #"KEY_RIGHTCTRL"
133: 4, #Super_L - "KEY_LEFTMETA", WIN-key
64: 5, #"KEY_LEFTALT"
50 : 6, #"KEY_LEFTSHIFT"
1 : 6, #"KEY_LEFTSHIFT", mapped to both 50 and 1 as '1' is used by Plover as shift modifier to capitalize letters and '50' when sent in key-combo
37: 7 #"KEY_LEFTCTRL"
}
def plover_convert(plover_keycode):
if plover_keycode in plover_keytable:
return plover_keytable[plover_keycode]
else:
return -1 # Return an invalid keycode
def plover_modkey(plover_keycode):
if plover_keycode in plover_modkeys:
return plover_modkeys[plover_keycode]
else:
return -1 # Return an invalid array element
================================================
FILE: plover_plugin/stenogotchi_link/stenogotchi_link.py
================================================
#!/usr/bin/env python3
# This is a Plover plugin acting as link between Plover and Stenogotchi
# Based on: https://github.com/nsmarkop/plover_websocket_server
import plover.log
import json
import jsonpickle
from typing import Optional, List
from time import sleep
from plover.engine import StenoEngine
from plover.steno import Stroke
from plover.config import Config
from plover.formatting import _Action
from plover.steno_dictionary import StenoDictionaryCollection
from stenogotchi_link.clients import BTClient, StenogotchiClient
from stenogotchi_link.wpm import PloverWpmMeter, PloverStrokesMeter
ERROR_MISSING_ENGINE = 'Plover engine not provided'
class EngineServer():
"""
Hooks into Plover events and makes them available to Stenogotchi.
"""
# Called once to initialize an instance which lives until Plover exits
def __init__(self, engine: StenoEngine) -> None:
self._engine: StenoEngine = engine
self._stenogotchiclient = StenogotchiClient(self)
self._btclient = BTClient()
self._wpm_meter = None
self._strokes_meter = None
self._output_to_stenogotchi = False
# Started when user enables extension
def start(self):
""" Starts the server. """
self._connect_hooks()
plover.log.info("[stenogotchi_link] Plover_link started")
self._stenogotchiclient.plover_is_running(True)
# Called when Plover exits or user disables the extension
def stop(self):
""" Stops the server. """
self._disconnect_hooks()
self._stenogotchiclient.plover_is_running(False)
def start_wpm_meter(self, enable_wpm=False, enable_strokes=False, wpm_method='ncra', wpm_timeout=60):
""" Starts WPM and/or Strokes meters
"""
if enable_wpm:
self._wpm_meter = PloverWpmMeter(stenogotchi_link=self, wpm_method=wpm_method, timeout=wpm_timeout)
if enable_strokes:
self._strokes_meter = PloverStrokesMeter(stenogotchi_link=self, strokes_method=wpm_method, timeout=wpm_timeout)
def stop_wpm_meter(self, disable_wpm=True, disable_strokes=True):
if disable_wpm:
self._wpm_meter.quit()
self._wpm_meter = None
if disable_strokes:
self._strokes_meter.quit()
self._strokes_meter = None
def _on_wpm_meter_update_strokes(self, stats):
""" Sends strokes stats to stenogotchi as a string """
self._stenogotchiclient.plover_strokes_stats(stats['strokes_user'])
def _on_wpm_meter_update_wpm(self, stats):
""" Sends wpm stats to stenogotchi as a string """
self._stenogotchiclient.plover_wpm_stats(stats['wpm_user'])
def _on_plover_translation(self, results, type):
""" Sends translation results from Plover to stenogotchi as list of strings"""
if type == 'word': # Result from Plover will be in format List[Tuple[str]], examine: {('EBGS', 'APL', '*EUPB'), ('KP*PL',), ('KPAPL', 'PHEUPB'), ('KPAPL', '-PB'), ('KP-PB',), ('EBGS', 'APL', '-PB'), ('KP',), ('EBGS', 'APL', 'PHEUPB'), ('EBGS', 'APL', 'EUPB')}
results_list = []
chords = None
for touple in results:
if chords:
results_list.append(chords)
chords = None
for result in touple:
if chords:
chords = chords + '/' + result
else:
chords = result
if chords:
results_list.append(chords)
chords = None
elif type == 'stroke': # Result from Plover will be in format str
# TODO: implement functionality for stroke-lookup
plover.log.debug(f"Lookup_stroke results: {results}")
results_list = [results]
self._stenogotchiclient.send_lookup_results(results_list)
def lookup_word(self, word):
matches = self._engine.reverse_lookup(word)
self._on_plover_translation(matches, 'word')
def lookup_stroke(self, stroke):
matches = self._engine.lookup(stroke)
self._on_plover_translation(matches, 'stroke')
def get_server_status(self):
"""Gets the status of the server.
Returns:
The status of the server.
"""
pass
def _connect_hooks(self):
"""Creates hooks into all of Plover's events."""
if not self._engine:
plover.log.error(f'[stenogotchi_link] {ERROR_MISSING_ENGINE}')
raise AssertionError(ERROR_MISSING_ENGINE)
for hook in self._engine.HOOKS:
callback = getattr(self, f'_on_{hook}')
self._engine.hook_connect(hook, callback)
def _disconnect_hooks(self):
"""Removes hooks from all of Plover's events."""
if not self._engine:
raise AssertionError(ERROR_MISSING_ENGINE)
for hook in self._engine.HOOKS:
callback = getattr(self, f'_on_{hook}')
self._engine.hook_disconnect(hook, callback)
def _on_stroked(self, stroke: Stroke):
""" Broadcasts when a new stroke is performed. """
pass
def _on_translated(self, old: List[_Action], new: List[_Action]):
"""Broadcasts when a new translation occurs.
Args:
old: A list of the previous actions for the current translation.
new: A list of the new actions for the current translation.
"""
# Send to WPM meter if we have one
if self._wpm_meter:
self._wpm_meter.on_translation(old, new)
# Send to Strokes meter if we have one
if self._strokes_meter:
self._strokes_meter.on_translation(old, new)
# print(self._wpm_meter.get_stats())
# print(self._strokes_meter.get_stats())
def _on_machine_state_changed(self, machine_type: str, machine_state: str):
"""Broadcasts when the active machine state changes.
Args:
machine_type: The name of the active machine.
machine_state: The new machine state. This should be one of the
state constants listed in plover.machine.base.
"""
self._stenogotchiclient.plover_machine_state("machine_type: " + machine_type + " machine_state: " + machine_state)
def _on_output_changed(self, enabled: bool):
"""Broadcasts when the state of output changes.
Args:
enabled: If the output is now enabled or not.
"""
data = {'output_changed': enabled}
plover.log.debug(f'[stenogotchi_link] _on_output_changed data: {data}')
self._stenogotchiclient.plover_output_enabled(enabled)
def _on_config_changed(self, config_update: Config):
"""Broadcasts when the configuration changes.
Args:
config_update: An object containing the full configuration or a
part of the configuration that was updated.
"""
config_json = jsonpickle.encode(config_update, unpicklable=False)
data = {'config_changed': json.loads(config_json)}
plover.log.debug(f'[stenogotchi_link] _on_config_changed data: {data}')
def _on_dictionaries_loaded(self, dictionaries: StenoDictionaryCollection):
"""Broadcasts when all of the dictionaries get loaded.
Args:
dictionaries: A collection of the dictionaries that loaded.
"""
self._stenogotchiclient.plover_is_ready(True)
def _on_send_string(self, text: str):
"""Broadcasts when a new string is output.
Args:
text: The string that was output.
"""
if self._output_to_stenogotchi:
self._stenogotchiclient.send_string(text)
else:
self._btclient.send_string(text)
def _on_send_backspaces(self, count: int):
"""Broadcasts when backspaces are output.
Args:
count: The number of backspaces that were output.
"""
if self._output_to_stenogotchi:
self._stenogotchiclient.send_backspaces(count)
else:
self._btclient.send_backspaces(count)
def _on_send_key_combination(self, combination: str):
"""Broadcasts when a key combination is output.
Args:
combination: A string representing a sequence of key combinations.
Keys are represented by their names based on the OS-specific
keyboard implementations in plover.oslayer.
"""
if self._output_to_stenogotchi:
self._stenogotchiclient.send_key_combination(combination)
else:
self._btclient.send_key_combination(combination)
def _on_add_translation(self):
"""Broadcasts when the add translation tool is opened via a command."""
data = {'add_translation': True}
plover.log.debug(f'[stenogotchi_link] _on_add_translation data: {data}')
def _on_focus(self):
"""Broadcasts when the main window is focused via a command."""
data = {'focus': True}
plover.log.debug(f'[stenogotchi_link] _on_focus data: {data}')
def _on_configure(self):
"""Broadcasts when the configuration tool is opened via a command."""
data = {'configure': True}
plover.log.debug(f'[stenogotchi_link] _on_configure data: {data}')
def _on_lookup(self):
"""Broadcasts when the lookup tool is opened via a command."""
data = {'lookup': True}
plover.log.debug(f'[stenogotchi_link] _on_lookup data: {data}')
def _on_suggestions(self):
"""Broadcasts when the lookup tool is opened via a command."""
data = {'suggestions': True}
plover.log.debug(f'[stenogotchi_link] _on_lookup data: {data}')
def _on_quit(self):
"""Broadcasts when the application is terminated.
Can be either a full quit or a restart.
"""
data = {'quit': True}
plover.log.debug(f'[stenogotchi_link] _on_quit data: {data}')
self._stenogotchiclient.plover_is_running(False)
if __name__ == '__main__':
print("Please enable and run as Plover plugin")
================================================
FILE: plover_plugin/stenogotchi_link/wpm.py
================================================
#!/usr/bin/env python3
#
# Based on: https://github.com/arxanas/plover_wpm_meter.git, modified to remove QT dependency.
#
# This is a plugin for Plover to display your typing speed as you type, in either or both of words per minute or strokes per minute:
## Words per minute: Shows the rate at which you stroked words in the last 10 and 60 seconds.
## Strokes per minute: Shows how efficiently you stroked words in the last 10 and 60 seconds. Lower values are more efficient, e.g. a value of 1 means that on average it took you one stroke to write out a word.
#
# A word is defined in one of three ways:
## NCRA: The National Court Reporters Association defines a “word” as 1.4 syllables. This is the measure used for official NCRA testing material.
## Traditional: The traditional metric for “word” in the context of keyboarding is defined to be 5 characters per word, including spaces. This is compatible with the notion of “word” in many typing speed utilities.
## Spaces: A word is a whitespace-separated sequence of characters. This metric of course doesn’t take into account the fact that some words are longer than others, both in length and syllables.
import time
from threading import Timer
from textstat.textstat import textstat
from plover.formatting import OutputHelper
class RepeatTimer(Timer):
"""
Perpetually repeating timer implementation of threading.Timer
"""
def run(self):
while not self.finished.wait(self.interval):
self.function(*self.args, **self.kwargs)
class CaptureOutput(object):
def __init__(self, chars):
self.chars = chars
def send_backspaces(self, n):
del self.chars[-n:]
def send_string(self, s):
self.chars += _timestamp_items(s)
def send_key_combination(self, c):
pass
def send_engine_command(self, c):
pass
class BaseMeter():
def __init__(self, timeout=60):
# Set timer to calculate wpm/strokes stats each second
self._timer = RepeatTimer(1, self.on_timer)
self._timer.start()
# Set timer to publish wpm/strokes stats each minute
self._event_timer = RepeatTimer(timeout, self.trigger_event_update)
self._event_timer.start()
self.chars = []
def on_translation(self, old, new):
output = CaptureOutput(self.chars)
output_helper = OutputHelper(output, False, False)
output_helper.render(None, old, new)
def on_timer(self):
raise NotImplementedError()
def trigger_event_update(self):
raise NotImplementedError()
def quit(self):
self._timer.cancel()
self._event_timer.cancel()
class PloverWpmMeter(BaseMeter):
def __init__(self, stenogotchi_link, wpm_method='ncra', timeout=60):
super().__init__(timeout)
self._stenogotchi_link = stenogotchi_link
self.strokes = []
self.wpm_methods = {
'ncra': False, # NCRA (by syllables)
'traditional': False, # Traditional (by characters)
'spaces': False, # Spaces (by whitespace)
}
self._timeouts = {
"wpm10": 10,
"wpm_user": timeout,
}
self.set_wpm_method(wpm_method)
self.wpm_stats = {}
def set_wpm_method(self, method):
self.wpm_methods = dict.fromkeys(self.wpm_methods, False)
self.wpm_methods[method] = True
def get_wpm_method(self):
for method, enabled in self.wpm_methods.items():
if enabled:
return method
def get_stats(self):
return self.wpm_stats
def on_timer(self):
max_timeout = max(self._timeouts.values())
self.chars = _filter_old_items(self.chars, max_timeout)
for name, timeout in self._timeouts.items():
chars = _filter_old_items(self.chars, timeout)
wpm = _wpm_of_chars(chars, method=self.get_wpm_method())
self.wpm_stats[name] = str(wpm)
def trigger_event_update(self):
self._stenogotchi_link._on_wpm_meter_update_wpm(stats=self.wpm_stats)
class PloverStrokesMeter(BaseMeter):
def __init__(self, stenogotchi_link, strokes_method='ncra', timeout=60):
super().__init__(timeout)
self._stenogotchi_link = stenogotchi_link
self.actions = []
self.strokes_methods = {
'ncra': False, # NCRA (by syllables)
'traditional': False, # Traditional (by characters)
'spaces': False, # Spaces (by whitespace)
}
self._timeouts = {
"strokes10": 10,
"strokes_user": timeout,
}
self.set_strokes_method(strokes_method)
self.strokes_stats = {}
# By default, the QLCDNumbers will just display "0", without a decimal
# point, on initial render. Render them ourselves so that we don't
# switch from "0" to "0.00" after a second.
self.on_timer()
def set_strokes_method(self, method):
self.strokes_methods = dict.fromkeys(self.strokes_methods, False)
self.strokes_methods[method] = True
def get_strokes_method(self):
for method, enabled in self.strokes_methods.items():
if enabled:
return method
def get_stats(self):
return self.strokes_stats
def on_translation(self, old, new):
super().on_translation(old, new)
if len(old) > 0:
self.actions = self.actions[:-len(old)]
self.actions += _timestamp_items(new)
def on_timer(self):
max_timeout = max(self._timeouts.values())
self.chars = _filter_old_items(self.chars, max_timeout)
self.actions = _filter_old_items(self.actions, max_timeout)
for name, timeout in self._timeouts.items():
chars = _filter_old_items(self.chars, timeout)
num_strokes = len(_filter_old_items(self.actions, timeout))
strokes_per_word = _spw_of_chars(
num_strokes,
chars,
method=self.get_strokes_method()
)
self.strokes_stats[name] = str("{:0.2f}".format(strokes_per_word))
def trigger_event_update(self):
self._stenogotchi_link._on_wpm_meter_update_strokes(stats=self.strokes_stats)
def _timestamp_items(items):
current_time = time.time()
return [(i, current_time) for i in items]
def _filter_old_items(items, timeout):
current_time = time.time()
return [(i, t) for i, t in items
if (current_time - t) <= timeout]
def _words_in_chars(chars, method):
text = "".join(c for c, _ in chars)
if method == "ncra":
# The NCRA defines a "word" to be 1.4 syllables, which is the average
# number of syllables per English word.
syllables_per_word = 1.4
# For some reason, textstat returns syllable counts such as a
# one-syllable word like "the" being 0.9 syllables.
syllables_in_text = textstat.syllable_count(text) / 0.9
return syllables_in_text * (1 / syllables_per_word)
elif method == "traditional":
# Formal definition; see https://en.wikipedia.org/wiki/Words_per_minute
return len(text) / 5
elif method == "spaces":
return len([i for i in text.split() if i])
else:
assert False, "bad wpm method: " + method
def _time_interval_of_chars(chars):
start_time = min(t for _, t in chars)
current_time = time.time()
time_interval = current_time - start_time
time_interval = max(1, time_interval)
return time_interval
def _wpm_of_chars(chars, method):
num_words = _words_in_chars(chars, method)
if not num_words:
return 0
time_interval = _time_interval_of_chars(chars)
num_minutes = time_interval / 60
num_words_per_minute = num_words / num_minutes
return int(round(num_words_per_minute))
def _spw_of_chars(num_strokes, chars, method):
num_words = _words_in_chars(chars, method)
if not num_words:
return 0
return num_strokes / num_words
================================================
FILE: requirements.txt
================================================
PyGObject == 3.30.4
dbus_python == 1.2.16
evdev == 1.4.0
RPi.GPIO == 0.7.0
file_read_backwards == 2.0.0
toml == 0.10.2
spidev == 3.5
Flask == 1.1.2
Flask_Cors == 3.0.10
Flask_WTF == 0.14.3
requests == 2.25.1
Pillow >= 8.3.2
plover >= 4.0.0.dev10
================================================
FILE: stenogotchi/__init__.py
================================================
import os
import logging
import time
import re
import subprocess
from stenogotchi._version import __version__
_name = None
config = None
def set_name(new_name):
if new_name is None:
return
new_name = new_name.strip()
if new_name == '':
return
if not re.match(r'^[a-zA-Z0-9\-]{2,25}$', new_name):
logging.warning("name '%s' is invalid: min length is 2, max length 25, only a-zA-Z0-9- allowed", new_name)
return
current = name()
if new_name != current:
global _name
logging.info("setting unit hostname '%s' -> '%s'", current, new_name)
with open('/etc/hostname', 'wt') as fp:
fp.write(new_name)
with open('/etc/hosts', 'rt') as fp:
prev = fp.read()
logging.debug("old hosts:\n%s\n", prev)
with open('/etc/hosts', 'wt') as fp:
patched = prev.replace(current, new_name, -1)
logging.debug("new hosts:\n%s\n", patched)
fp.write(patched)
os.system("hostname '%s'" % new_name)
reboot()
def name():
global _name
if _name is None:
with open('/etc/hostname', 'rt') as fp:
_name = fp.read().strip()
return _name
def uptime():
with open('/proc/uptime') as fp:
return int(fp.read().split('.')[0])
def mem_usage():
with open('/proc/meminfo') as fp:
for line in fp:
line = line.strip()
if line.startswith("MemTotal:"):
kb_mem_total = int(line.split()[1])
if line.startswith("MemFree:"):
kb_mem_free = int(line.split()[1])
if line.startswith("Buffers:"):
kb_main_buffers = int(line.split()[1])
if line.startswith("Cached:"):
kb_main_cached = int(line.split()[1])
kb_mem_used = kb_mem_total - kb_mem_free - kb_main_cached - kb_main_buffers
return round(kb_mem_used / kb_mem_total, 1)
return 0
def _cpu_stat():
"""
Returns the splitted first line of the /proc/stat file
"""
with open('/proc/stat', 'rt') as fp:
return list(map(int,fp.readline().split()[1:]))
def cpu_load(s=0.1):
"""
Returns the average cpuload over a 's'-seconds period
"""
parts0 = _cpu_stat()
time.sleep(s)
parts1 = _cpu_stat()
parts_diff = [p1 - p0 for (p0, p1) in zip(parts0, parts1)]
user, nice, sys, idle, iowait, irq, softirq, steal, _guest, _guest_nice = parts_diff
idle_sum = idle + iowait
non_idle_sum = user + nice + sys + irq + softirq + steal
total = idle_sum + non_idle_sum
return non_idle_sum / total
def temperature(celsius=True):
with open('/sys/class/thermal/thermal_zone0/temp', 'rt') as fp:
temp = int(fp.read().strip())
c = int(temp / 1000)
return c if celsius else ((c * (9 / 5)) + 32)
def get_wifi_status():
try:
parts = os.popen("sudo ip -br addr show wlan0").read().split()
state = parts[1]
ip = parts[2].split('/')[0]
return state, ip
except:
return None, None
def get_wifi_ssid():
try:
ssid = os.popen("sudo iwgetid -r").read().split('\n')[0]
return ssid
except:
return ""
def set_wifi_onoff():
wifi_updown = get_wifi_status()[0]
if wifi_updown == 'UP':
# switch wifi off
subprocess.call(['sudo', 'ip', 'link', 'set', 'dev', 'wlan0', 'down'])
subprocess.call(['sudo', 'dhclient', '-r', 'wlan0'])
subprocess.call(['sudo', 'rfkill', 'block', '0'])
return False
else:
# switch wifi on
subprocess.call(['sudo', 'rfkill', 'unblock', '0'])
subprocess.call(['sudo', 'dhclient', 'wlan0'])
subprocess.call(['sudo', 'ip', 'link', 'set', 'dev', 'wlan0', 'up'])
return True
def shutdown():
logging.warning("shutting down ...")
from stenogotchi.ui import view
if view.ROOT:
view.ROOT.on_shutdown()
# give it some time to refresh the ui
time.sleep(5)
if view.ROOT._config['ui']['display']['enabled']:
if view.ROOT._config['ui']['display']['clear_at_shutdown']:
view.ROOT._agent._view.init_display()
view.ROOT._agent._view.clear()
# give it some time to clear the ui
time.sleep(5)
logging.warning("syncing...")
from stenogotchi import fs
for m in fs.mounts:
m.sync()
os.system("sync")
os.system("halt")
def restart(mode):
logging.warning("restarting in %s mode ...", mode)
if mode == 'AUTO':
os.system("touch /root/.stenogotchi-auto")
else:
os.system("touch /root/.stenogotchi-manual")
#os.system("service stenogotchi restart")
def reboot(mode=None):
if mode is not None:
mode = mode.upper()
logging.warning("rebooting in %s mode ...", mode)
else:
logging.warning("rebooting ...")
from stenogotchi.ui import view
if view.ROOT:
view.ROOT.on_rebooting()
# give it some time to refresh the ui
time.sleep(10)
if mode == 'AUTO':
os.system("touch /root/.stenogotchi-auto")
elif mode == 'MANU':
os.system("touch /root/.stenogotchi-manual")
logging.warning("syncing...")
from stenogotchi import fs
for m in fs.mounts:
m.sync()
os.system("sync")
os.system("shutdown -r now")
================================================
FILE: stenogotchi/_version.py
================================================
__version__ = '0.1.0'
================================================
FILE: stenogotchi/agent.py
================================================
import time
import json
import os
import re
import logging
import asyncio
import _thread
import stenogotchi
import stenogotchi.utils as utils
import stenogotchi.plugins as plugins
from stenogotchi.ui.web.server import Server
from stenogotchi.automata import Automata
#from stenogotchi.log import LastSession
RECOVERY_DATA_FILE = '/root/.stenogotchi-recovery'
class Agent(Automata):
def __init__(self, view, config):
Automata.__init__(self, config, view)
self._started_at = time.time()
self._view = view
self._view.set_agent(self)
self._web_ui = None
self._wifi_connected = None
self._history = {}
#self.last_session = LastSession(self._config)
self.mode = 'auto'
logging.info("%s (v%s)", stenogotchi.name(), stenogotchi.__version__)
for _, plugin in plugins.loaded.items():
logging.debug("plugin '%s' v%s", plugin.__class__.__name__, plugin.__version__)
def config(self):
return self._config
def view(self):
return self._view
def start(self):
self.set_starting()
# self.start_event_polling()
# print initial stats
self.start_session_fetcher()
self.set_ready()
def _update_uptime(self):
secs = stenogotchi.uptime()
self._view.set('uptime', utils.secs_to_hhmmss(secs))
def _update_wifi(self):
status, ip = stenogotchi.get_wifi_status()
if status == 'UP':
if self._wifi_connected:
return
ssid = stenogotchi.get_wifi_ssid()
if ssid and ip:
self._wifi_connected = True
# Create web-ui only once a wifi connection is established
if not self._web_ui:
self._web_ui = Server(self, self._config['ui'])
else:
if not ssid:
ssid = "[Searching]"
ip = "the ghost"
if not ip:
ip = "the ghost"
self._wifi_connected = False
self.set_wifi_connected(ssid, ip)
elif status == 'DOWN' or not status:
if self._wifi_connected == False:
return
self._wifi_connected = False
self.set_wifi_disconnected('[OFF]')
def _reboot(self):
self.set_rebooting()
self._save_recovery_data()
stenogotchi.reboot()
def _save_recovery_data(self):
logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE)
with open(RECOVERY_DATA_FILE, 'w') as fp:
data = {
'started_at': self._started_at,
'history': self._history,
}
json.dump(data, fp)
def _load_recovery_data(self, delete=True, no_exceptions=True):
try:
with open(RECOVERY_DATA_FILE, 'rt') as fp:
data = json.load(fp)
logging.info("found recovery data: %s", data)
self._started_at = data['started_at']
self._history = data['history']
if delete:
logging.info("deleting %s", RECOVERY_DATA_FILE)
os.unlink(RECOVERY_DATA_FILE)
except:
if not no_exceptions:
raise
def start_session_fetcher(self):
_thread.start_new_thread(self._fetch_stats, ())
async def plover_status_update(self, msg):
pass
def _fetch_stats(self):
while True:
#s = self.session() # this is just the request object for connection to bettercap -> to be used for fetching plover stats instead
self._update_uptime()
self._update_wifi()
time.sleep(30)
async def _on_event(self, msg): # no bettercap to produce events for us, to be used for plover events
pass
def _event_poller(self, loop):
self._load_recovery_data()
#while True:
def start_event_polling(self):
# start a thread and pass in the mainloop
_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),))
================================================
FILE: stenogotchi/automata.py
================================================
import logging
import stenogotchi.plugins as plugins
# basic mood system
class Automata(object):
def __init__(self, config, view):
self._config = config
self._view = view
def set_starting(self):
self._view.on_starting()
def set_ready(self):
plugins.on('ready', self)
def in_good_mood(self):
pass
# triggered when it's a sad/bad day but you have good friends around ^_^
def set_grateful(self):
self._view.on_grateful()
plugins.on('grateful', self)
def set_lonely(self):
self._view.on_lonely()
plugins.on('lonely', self)
def set_bored(self):
self._view.on_bored()
plugins.on('bored', self)
def set_sad(self):
self._view.on_sad()
plugins.on('sad', self)
def set_angry(self):
self._view.on_angry()
plugins.on('angry', self)
def set_excited(self):
self._view.on_excited()
plugins.on('excited', self)
def set_processing(self):
self._view.on_processing()
plugins.on('processing', self)
def set_rebooting(self):
self._view.on_rebooting()
plugins.on('rebooting', self)
def wait_for(self, t, sleeping=True):
self._view.wait(t, sleeping)
def set_plover_boot(self):
self._view.on_plover_boot()
plugins.on('plover_boot', self)
def set_plover_ready(self):
self._view.on_plover_ready()
plugins.on('plover_ready', self)
def set_plover_quit(self):
self._view.on_plover_quit()
plugins.on('plover_quit', self)
def set_bt_connected(self, bthost_name):
self._view.set('bthost', bthost_name)
self._view.on_bt_connected(bthost_name)
plugins.on('bt_connected', self, bthost_name)
def set_bt_disconnected(self):
self._view.set('bthost', "")
self._view.on_bt_disconnected()
plugins.on('bt_disconnected', self)
def set_wifi_connected(self, ssid, ip):
self._view.set('wifi', ssid)
self._view.on_wifi_connected(ssid, ip)
plugins.on('wifi_connected', self, ssid, ip)
def set_wifi_disconnected(self, ssid=''):
self._view.set('wifi', ssid)
self._view.on_wifi_disconnected()
plugins.on('wifi_disconnected', self)
def set_wpm(self, wpm, wpm_top):
stats = '{:3s} {:5s}'.format(str(wpm), f'({wpm_top})')
self._view.on_wpm(stats)
plugins.on('wpm_set', self)
def set_strokes(self, stats):
self._view.on_strokes(stats)
plugins.on('strokes_set', self)
def set_wpm_record(self, wpm_top):
self._view.on_wpm_record(wpm_top)
plugins.on('wpm_record', self)
self.set_wpm(wpm_top, wpm_top) # Since we set a new record wpm and wpm_top will be equal
def set_on_dict_lookup_done(self):
self._view.on_dict_lookup_done()
plugins.on('dict_lookup_done', self)
================================================
FILE: stenogotchi/defaults.toml
================================================
# This is a TOML document
# This is the default config template. To change the default values, please save your modifications to /etc/stenogotchi/config.toml
# Main
main.name = "Stenogotchi"
main.lang = "en"
main.confd = "/etc/stenogotchi/conf.d/"
main.custom_plugins = ""
# Filesystem
fs.memory.enabled = false
fs.memory.mounts.log.enabled = false
fs.memory.mounts.log.mount = "/var/log"
fs.memory.mounts.log.size = "50M"
fs.memory.mounts.log.sync = 60
fs.memory.mounts.log.zram = true
fs.memory.mounts.log.rsync = true
fs.memory.mounts.data.enabled = false
fs.memory.mounts.data.mount = "/var/tmp/stenogotchi"
fs.memory.mounts.data.size = "10M"
fs.memory.mounts.data.sync = 3600
fs.memory.mounts.data.zram = false
fs.memory.mounts.data.rsync = true
# UI
# The lifespan of an eINK display depends on the cumulative number of refreshes. To preserve your display
# over time it is recommended to keep ui.fps value 0.0 so that refreshes only take place upon major updates.
# Uptime, battery status, the blinking cursor and similar will not trigger a refresh
ui.fps = 0.0
ui.font.name = "DejaVuSansMono"
ui.font.size_offset = 0 # added to the font size
ui.faces.look_r = "( ⚆_⚆)"
ui.faces.look_l = "(☉_☉ )"
ui.faces.look_r_happy = "( ◕‿◕)"
ui.faces.look_l_happy = "(◕‿◕ )"
ui.faces.sleep = "(⇀‿‿↼)"
ui.faces.sleep2 = "(≖‿‿≖)"
ui.faces.awake = "(◕‿‿◕)"
ui.faces.bored = "(-__-)"
ui.faces.intense = "(°▃▃°)"
ui.faces.cool = "(⌐■_■)"
ui.faces.happy = "(•‿‿•)"
ui.faces.excited = "(ᵔ◡◡ᵔ)"
ui.faces.grateful = "(^‿‿^)"
ui.faces.motivated = "(☼‿‿☼)"
ui.faces.demotivated = "(≖__≖)"
ui.faces.smart = "(✜‿‿✜)"
ui.faces.lonely = "(ب__ب)"
ui.faces.sad = "(╥☁╥ )"
ui.faces.angry = "(-_-')"
ui.faces.friend = "(♥‿‿♥)"
ui.faces.broken = "(☓‿‿☓)"
ui.faces.debug = "(#__#)"
ui.faces.process_1 = "(1__0)"
ui.faces.process_2 = "(1__1)"
ui.faces.process_3 = "(0__1)"
ui.faces.process_4 = "(0__0)"
ui.display.enabled = true
ui.display.rotation = 0
ui.display.type = "waveshare_2"
ui.display.color = "black"
ui.display.clear_at_shutdown = true
# The web UI provides an alternative way of interacting with the device to an eINK display and ButtonSHIM module. Enabling the web UI will also load the buttonshim plugin.
ui.web.enabled = false
ui.web.address = "192.168.1.100"
ui.web.username = "changeme"
ui.web.password = "changeme"
ui.web.origin = ""
ui.web.port = 80
ui.web.on_frame = ""
# Plugins
# Populate bt_autoconnect_mac with the MAC addresses of devices to attempt auto connecting to at boot. Multiple comma-separated devices in order of priority can be given.
# After exhausting the options, the device will fall back to listening for incoming connection and pairing attempts.
## Example:
## main.plugins.plover_link.bt_autoconnect_mac = "00:DE:AD:BE:EF:00, 11:DE:AD:BE:EF:11"
main.plugins.plover_link.enabled = true
main.plugins.plover_link.bt_autoconnect_mac = ""
main.plugins.plover_link.bt_device_name = "Stenogotchi"
# For WPM readings a word is defined in one of three ways:
## NCRA: The National Court Reporters Association defines a “word” as 1.4 syllables. This is the measure used for official NCRA testing material.
## Traditional: The traditional metric for “word” in the context of keyboarding is defined to be 5 characters per word, including spaces. This is compatible with the notion of “word” in many typing speed utilities.
## Spaces: A word is a whitespace-separated sequence of characters. This metric of course doesn’t take into account the fact that some words are longer than others, both in length and syllables.
# Specify either "ncra", "traditional" or "spaces" as preferred method using main.plugins.plover_link.wpm_method
# Specify in seconds moving time window for which wpm is calculated and updated using main.plugins.plover_link.wpm_timeout
main.plugins.plover_link.wpm_method = "traditional"
main.plugins.plover_link.wpm_timeout = "60"
# Plugin evdevkb is responsible for direct keyboard capturing, bypassing Plover. Input mode toggleable when enabled.
main.plugins.evdevkb.enabled = true
# Plugin dict_lookup handles dictionary lookup from all enabled Plover dictionaries.
main.plugins.dict_lookup.enabled = true
# Plugin upslite provides battery level, charging indicator (+) and auto-shutdown at specified battery level when a UPS-Lite v1.2 module is connected.
main.plugins.upslite.enabled = false
main.plugins.upslite.shutdown_level = 5
# Plugin buttonshim enables a way to interact with the device using the five physical buttons on the ButtonSHIM hardware module or through the web UI. Enabling the web UI will automatically load the buttonshim plugin.
# Each button has a predefined long-press (1s) action. The short-press action can be freely configured to any terminal command.
main.plugins.buttonshim.enabled = false
main.plugins.buttonshim.buttons.A.command = ""
main.plugins.buttonshim.buttons.A.blink.enabled = false
main.plugins.buttonshim.buttons.A.blink.red = 0
main.plugins.buttonshim.buttons.A.blink.green = 0
main.plugins.buttonshim.buttons.A.blink.blue = 0
main.plugins.buttonshim.buttons.A.blink.on_time = 0
main.plugins.buttonshim.buttons.A.blink.off_time = 0
main.plugins.buttonshim.buttons.A.blink.blink_times = 0
main.plugins.buttonshim.buttons.B.command = ""
main.plugins.buttonshim.buttons.B.blink.enabled = false
main.plugins.buttonshim.buttons.B.blink.red = 0
main.plugins.buttonshim.buttons.B.blink.green = 0
main.plugins.buttonshim.buttons.B.blink.blue = 0
main.plugins.buttonshim.buttons.B.blink.on_time = 0
main.plugins.buttonshim.buttons.B.blink.off_time = 0
main.plugins.buttonshim.buttons.B.blink.blink_times = 0
main.plugins.buttonshim.buttons.C.command = ""
main.plugins.buttonshim.buttons.C.blink.enabled = false
main.plugins.buttonshim.buttons.C.blink.red = 0
main.plugins.buttonshim.buttons.C.blink.green = 0
main.plugins.buttonshim.buttons.C.blink.blue = 0
main.plugins.buttonshim.buttons.C.blink.on_time = 0
main.plugins.buttonshim.buttons.C.blink.off_time = 0
main.plugins.buttonshim.buttons.C.blink.blink_times = 0
main.plugins.buttonshim.buttons.D.command = ""
main.plugins.buttonshim.buttons.D.blink.enabled = false
main.plugins.buttonshim.buttons.D.blink.red = 0
main.plugins.buttonshim.buttons.D.blink.green = 0
main.plugins.buttonshim.buttons.D.blink.blue = 0
main.plugins.buttonshim.buttons.D.blink.on_time = 0
main.plugins.buttonshim.buttons.D.blink.off_time = 0
main.plugins.buttonshim.buttons.D.blink.blink_times = 0
main.plugins.buttonshim.buttons.E.command = ""
main.plugins.buttonshim.buttons.E.blink.enabled = false
main.plugins.buttonshim.buttons.E.blink.red = 0
main.plugins.buttonshim.buttons.E.blink.green = 0
main.plugins.buttonshim.buttons.E.blink.blue = 0
main.plugins.buttonshim.buttons.E.blink.on_time = 0
main.plugins.buttonshim.buttons.E.blink.off_time = 0
main.plugins.buttonshim.buttons.E.blink.blink_times = 0
# Plugin led provides an alternative way to be alerted to events through the built-in LED on the rpi0w without the need of a display.
main.plugins.led.enabled = false
main.plugins.led.led = 0
main.plugins.led.delay = 200
main.plugins.led.patterns.loaded = ""
main.plugins.led.patterns.updating = ""
main.plugins.led.patterns.ready = ""
main.plugins.led.patterns.grateful = ""
main.plugins.led.patterns.lonely = ""
main.plugins.led.patterns.bored = ""
main.plugins.led.patterns.sad = ""
main.plugins.led.patterns.angry = ""
main.plugins.led.patterns.excited = ""
main.plugins.led.patterns.rebooting = "oo oo oo oo oo"
main.plugins.led.patterns.wait = ""
main.plugins.led.patterns.sleep = ""
main.plugins.led.patterns.epoch = ""
main.plugins.led.patterns.bt_connected = "oo oo oo oo oo"
main.plugins.led.patterns.bt_disconnected = "oo oo oo oo oo"
main.plugins.led.patterns.plover_boot = ""
main.plugins.led.patterns.plover_ready = "oo oo oo oo oo"
main.plugins.led.patterns.plover_quit = "oo oo oo oo oo"
main.plugins.led.patterns.wifi_connected = "oo oo oo oo oo"
main.plugins.led.patterns.wifi_disconnected = "oo oo oo oo oo"
main.plugins.led.patterns.wpm_set = ""
main.plugins.led.patterns.strokes_set = ""
# Plugin memtemp displays CPU temperature and RAM/CPU usage.
main.plugins.memtemp.enabled = false
main.plugins.memtemp.scale = "celsius"
main.plugins.memtemp.orientation = "horizontal"
# Plugin logtail gives access to the Stenogotchi log through the web UI.
main.plugins.logtail.enabled = false
main.plugins.logtail.max-lines = 10000
main.plugins.session-stats.enabled = false
main.plugins.session-stats.save_directory = "/var/tmp/stenogotchi/sessions/"
# Log
main.log.path = "/var/log/stenogotchi.log"
main.log.rotation.enabled = true
main.log.rotation.size = "10M"
================================================
FILE: stenogotchi/fs/__init__.py
================================================
import os
import re
import tempfile
import contextlib
import shutil
import _thread
import logging
from time import sleep
from distutils.dir_util import copy_tree
mounts = list()
@contextlib.contextmanager
def ensure_write(filename, mode='w'):
path = os.path.dirname(filename)
fd, tmp = tempfile.mkstemp(dir=path)
with os.fdopen(fd, mode) as f:
yield f
f.flush()
os.fsync(f.fileno())
os.replace(tmp, filename)
def size_of(path):
"""
Calculate the sum of all the files in path
"""
total = 0
for root, _, files in os.walk(path):
for f in files:
total += os.path.getsize(os.path.join(root, f))
return total
def is_mountpoint(path):
"""
Checks if path is mountpoint
"""
return os.system(f"mountpoint -q {path}") == 0
def setup_mounts(config):
"""
Sets up all the configured mountpoints
"""
global mounts
fs_cfg = config['fs']['memory']
if not fs_cfg['enabled']:
return
for name, options in fs_cfg['mounts'].items():
if not options['enabled']:
continue
logging.debug("[FS] Trying to setup mount %s (%s)", name, options['mount'])
size,unit = re.match(r"(\d+)([a-zA-Z]+)", options['size']).groups()
target = os.path.join('/run/stenogotchi/disk/', os.path.basename(options['mount']))
is_mounted = is_mountpoint(target)
logging.debug("[FS] %s is %s mounted", options['mount'],
"already" if is_mounted else "not yet")
m = MemoryFS(
options['mount'],
target,
size=options['size'],
zram=options['zram'],
zram_disk_size=f"{int(size)*2}{unit}",
rsync=options['rsync'])
if not is_mounted:
if not m.mount():
logging.error(f"Error while mounting {m.mountpoint}")
continue
if not m.sync(to_ram=True):
logging.error(f"Error while syncing to {m.mountpoint}")
m.umount()
continue
interval = int(options['sync'])
if interval:
logging.debug("[FS] Starting thread to sync %s (interval: %d)",
options['mount'], interval)
_thread.start_new_thread(m.daemonize, (interval,))
else:
logging.debug("[FS] Not syncing %s, because interval is 0",
options['mount'])
mounts.append(m)
class MemoryFS:
@staticmethod
def zram_install():
if not os.path.exists("/sys/class/zram-control"):
logging.debug("[FS] Installing zram")
return os.system("modprobe zram") == 0
return True
@staticmethod
def zram_dev():
logging.debug("[FS] Adding zram device")
return open("/sys/class/zram-control/hot_add", "rt").read().strip("\n")
def __init__(self, mount, disk, size="40M",
zram=True, zram_alg="lz4", zram_disk_size="100M",
zram_fs_type="ext4", rsync=True):
self.mountpoint = mount
self.disk = disk
self.size = size
self.zram = zram
self.zram_alg = zram_alg
self.zram_disk_size = zram_disk_size
self.zram_fs_type = zram_fs_type
self.zdev = None
self.rsync = True
self._setup()
def _setup(self):
if self.zram and MemoryFS.zram_install():
# setup zram
self.zdev = MemoryFS.zram_dev()
open(f"/sys/block/zram{self.zdev}/comp_algorithm", "wt").write(self.zram_alg)
open(f"/sys/block/zram{self.zdev}/disksize", "wt").write(self.zram_disk_size)
open(f"/sys/block/zram{self.zdev}/mem_limit", "wt").write(self.size)
logging.debug("[FS] Creating fs (type: %s)", self.zram_fs_type)
os.system(f"mke2fs -t {self.zram_fs_type} /dev/zram{self.zdev} >/dev/null 2>&1")
# ensure mountpoints exist
if not os.path.exists(self.disk):
logging.debug("[FS] Creating %s", self.disk)
os.makedirs(self.disk)
if not os.path.exists(self.mountpoint):
logging.debug("[FS] Creating %s", self.mountpoint)
os.makedirs(self.mountpoint)
def daemonize(self, interval=60):
logging.debug("[FS] Daemonized...")
while True:
self.sync()
sleep(interval)
def sync(self, to_ram=False):
source, dest = (self.disk, self.mountpoint) if to_ram else (self.mountpoint, self.disk)
needed, actually_free = size_of(source), shutil.disk_usage(dest)[2]
if actually_free >= needed:
logging.debug("[FS] Syncing %s -> %s", source,dest)
if self.rsync:
os.system(f"rsync -aXv --inplace --no-whole-file --delete-after {source}/ {dest}/ >/dev/null 2>&1")
else:
copy_tree(source, dest, preserve_symlinks=True)
os.system("sync")
return True
return False
def mount(self):
if os.system(f"mount --bind {self.mountpoint} {self.disk}"):
return False
if os.system(f"mount --make-private {self.disk}"):
return False
if self.zram and self.zdev is not None:
if os.system(f"mount -t {self.zram_fs_type} -o nosuid,noexec,nodev,user=stenogotchi /dev/zram{self.zdev} {self.mountpoint}/"):
return False
else:
if os.system(f"mount -t tmpfs -o nosuid,noexec,nodev,mode=0755,size={self.size} stenogotchi {self.mountpoint}/"):
return False
return True
def umount(self):
if os.system(f"umount -l {self.mountpoint}"):
return False
if os.system(f"umount -l {self.disk}"):
return False
return True
================================================
FILE: stenogotchi/log.py
================================================
import hashlib
import time
import re
import os
import logging
import shutil
import gzip
import warnings
from datetime import datetime
from stenogotchi.voice import Voice
from file_read_backwards import FileReadBackwards
LAST_SESSION_FILE = '/root/.stenogotchi-last-session'
class LastSession(object):
START_TOKEN = 'connecting to http'
def __init__(self, config):
self.config = config
self.voice = Voice(lang=config['main']['lang'])
self.path = config['main']['log']['path']
self.last_session = []
self.last_session_id = ''
self.last_saved_session_id = ''
self.duration = ''
self.duration_human = ''
def _get_last_saved_session_id(self):
saved = ''
try:
with open(LAST_SESSION_FILE, 'rt') as fp:
saved = fp.read().strip()
except:
saved = ''
return saved
def save_session_id(self):
with open(LAST_SESSION_FILE, 'w+t') as fp:
fp.write(self.last_session_id)
self.last_saved_session_id = self.last_session_id
def _parse_datetime(self, dt):
dt = dt.split('.')[0]
dt = dt.split(',')[0]
dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S')
return time.mktime(dt.timetuple())
def _parse_stats(self):
self.duration = ''
self.duration_human = ''
started_at = None
stopped_at = None
cache = {}
for line in self.last_session:
parts = line.split(']')
if len(parts) < 2:
continue
try:
line_timestamp = parts[0].strip('[')
line = ']'.join(parts[1:])
stopped_at = self._parse_datetime(line_timestamp)
if started_at is None:
started_at = stopped_at
except Exception as e:
logging.error("error parsing line '%s': %s" % (line, e))
if started_at is not None:
self.duration = stopped_at - started_at
mins, secs = divmod(self.duration, 60)
hours, mins = divmod(mins, 60)
else:
hours = mins = secs = 0
self.duration = '%02d:%02d:%02d' % (hours, mins, secs)
self.duration_human = []
if hours > 0:
self.duration_human.append('%d %s' % (hours, self.voice.hhmmss(hours, 'h')))
if mins > 0:
self.duration_human.append('%d %s' % (mins, self.voice.hhmmss(mins, 'm')))
if secs > 0:
self.duration_human.append('%d %s' % (secs, self.voice.hhmmss(secs, 's')))
self.duration_human = ', '.join(self.duration_human)
def parse(self, ui, skip=False):
if skip:
logging.debug("skipping parsing of the last session logs ...")
else:
logging.debug("reading last session logs ...")
ui.on_reading_logs()
lines = []
if os.path.exists(self.path):
with FileReadBackwards(self.path, encoding="utf-8") as fp:
for line in fp:
line = line.strip()
if line != "" and line[0] != '[':
continue
lines.append(line)
if LastSession.START_TOKEN in line:
break
lines_so_far = len(lines)
if lines_so_far % 100 == 0:
ui.on_reading_logs(lines_so_far)
lines.reverse()
if len(lines) == 0:
lines.append("Initial Session");
ui.on_reading_logs()
self.last_session = lines
self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest()
self.last_saved_session_id = self._get_last_saved_session_id()
logging.debug("parsing last session logs (%d lines) ..." % len(lines))
self._parse_stats()
self.parsed = True
def is_new(self):
return self.last_session_id != self.last_saved_session_id
def setup_logging(args, config):
cfg = config['main']['log']
filename = cfg['path']
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
root = logging.getLogger()
root.setLevel(logging.DEBUG if args.debug else logging.INFO)
if filename:
# since python default log rotation might break session data in different files,
# we need to do log rotation ourselves
log_rotation(filename, cfg)
file_handler = logging.FileHandler(filename)
file_handler.setFormatter(formatter)
root.addHandler(file_handler)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
root.addHandler(console_handler)
def log_rotation(filename, cfg):
rotation = cfg['rotation']
if not rotation['enabled']:
return
elif not os.path.isfile(filename):
return
stats = os.stat(filename)
# specify a maximum size to rotate ( format is 10/10B, 10K, 10M 10G )
if rotation['size']:
max_size = parse_max_size(rotation['size'])
if stats.st_size >= max_size:
do_rotate(filename, stats, cfg)
else:
raise Exception("log rotation is enabled but log.rotation.size was not specified")
def parse_max_size(s):
parts = re.findall(r'(^\d+)([bBkKmMgG]?)', s)
if len(parts) != 1 or len(parts[0]) != 2:
raise Exception("can't parse %s as a max size" % s)
num, unit = parts[0]
num = int(num)
unit = unit.lower()
if unit == 'k':
return num * 1024
elif unit == 'm':
return num * 1024 * 1024
elif unit == 'g':
return num * 1024 * 1024 * 1024
else:
return num
def do_rotate(filename, stats, cfg):
base_path = os.path.dirname(filename)
name = os.path.splitext(os.path.basename(filename))[0]
archive_filename = os.path.join(base_path, "%s.gz" % name)
counter = 2
while os.path.exists(archive_filename):
archive_filename = os.path.join(base_path, "%s-%d.gz" % (name, counter))
counter += 1
log_filename = archive_filename.replace('gz', 'log')
print("%s is %d bytes big, rotating to %s ..." % (filename, stats.st_size, log_filename))
shutil.move(filename, log_filename)
print("compressing to %s ..." % archive_filename)
with open(log_filename, 'rb') as src:
with gzip.open(archive_filename, 'wb') as dst:
dst.writelines(src)
================================================
FILE: stenogotchi/plugins/__init__.py
================================================
import os
import glob
import _thread
import threading
import importlib, importlib.util
import logging
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
loaded = {}
database = {}
locks = {}
class Plugin:
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
global loaded, locks
plugin_name = cls.__module__.split('.')[0]
plugin_instance = cls()
logging.debug("loaded plugin %s as %s" % (plugin_name, plugin_instance))
loaded[plugin_name] = plugin_instance
for attr_name in plugin_instance.__dir__():
if attr_name.startswith('on_'):
cb = getattr(plugin_instance, attr_name, None)
if cb is not None and callable(cb):
locks["%s::%s" % (plugin_name, attr_name)] = threading.Lock()
def toggle_plugin(name, enable=True):
"""
Load or unload a plugin
returns True if changed, otherwise False
"""
import stenogotchi
from stenogotchi.ui import view
from stenogotchi.utils import save_config
global loaded, database
# log event
if enable:
logging.debug(f"enabled plugin '{name}' in config")
else:
logging.debug(f"disabled plugin '{name}' in config")
if stenogotchi.config:
if not name in stenogotchi.config['main']['plugins']:
stenogotchi.config['main']['plugins'][name] = dict()
stenogotchi.config['main']['plugins'][name]['enabled'] = enable
save_config(stenogotchi.config, '/etc/stenogotchi/config.toml')
if not enable and name in loaded:
if getattr(loaded[name], 'on_unload', None):
loaded[name].on_unload(view.ROOT)
del loaded[name]
return True
if enable and name in database and name not in loaded:
load_from_file(database[name])
if name in loaded and stenogotchi.config and name in stenogotchi.config['main']['plugins']:
loaded[name].options = stenogotchi.config['main']['plugins'][name]
one(name, 'loaded')
if stenogotchi.config:
one(name, 'config_changed', stenogotchi.config)
one(name, 'ui_setup', view.ROOT)
one(name, 'ready', view.ROOT._agent)
return True
return False
def on(event_name, *args, **kwargs):
for plugin_name in loaded.keys():
one(plugin_name, event_name, *args, **kwargs)
def locked_cb(lock_name, cb, *args, **kwargs):
global locks
if lock_name not in locks:
locks[lock_name] = threading.Lock()
with locks[lock_name]:
cb(*args, *kwargs)
def one(plugin_name, event_name, *args, **kwargs):
global loaded
if plugin_name in loaded:
plugin = loaded[plugin_name]
cb_name = 'on_%s' % event_name
callback = getattr(plugin, cb_name, None)
if callback is not None and callable(callback):
try:
lock_name = "%s::%s" % (plugin_name, cb_name)
locked_cb_args = (lock_name, callback, *args, *kwargs)
_thread.start_new_thread(locked_cb, locked_cb_args)
except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True)
def load_from_file(filename):
logging.debug("loading %s" % filename)
plugin_name = os.path.basename(filename.replace(".py", ""))
spec = importlib.util.spec_from_file_location(plugin_name, filename)
instance = importlib.util.module_from_spec(spec)
spec.loader.exec_module(instance)
return plugin_name, instance
def load_from_path(path, enabled=()):
global loaded, database
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
for filename in glob.glob(os.path.join(path, "*.py")):
plugin_name = os.path.basename(filename.replace(".py", ""))
database[plugin_name] = filename
if plugin_name in enabled:
try:
load_from_file(filename)
except Exception as e:
logging.warning("error while loading %s: %s" % (filename, e))
logging.debug(e, exc_info=True)
return loaded
def load(config):
enabled = [name for name, options in config['main']['plugins'].items() if
'enabled' in options and options['enabled']]
# force enable buttonshim plugin when web ui is enabled
logging.debug(f"REMOVEME pre status : '{enabled}'")
if config['ui']['web']['enabled'] and 'buttonshim' not in enabled:
enabled.append('buttonshim')
logging.debug(f"REMOVEME post status : '{enabled}'")
# load default plugins
load_from_path(default_path, enabled=enabled)
# load custom ones
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
if custom_path is not None:
load_from_path(custom_path, enabled=enabled)
# propagate options
for name, plugin in loaded.items():
plugin.options = config['main']['plugins'][name]
print("loaded plugin:", name, plugin)
on('loaded')
on('config_changed', config)
================================================
FILE: stenogotchi/plugins/cmd.py
================================================
# Handles the commandline stuff
import os
import logging
import glob
import re
import shutil
from fnmatch import fnmatch
from stenogotchi.plugins import default_path
SAVE_DIR = '/usr/local/share/stenogotchi/availaible-plugins/'
DEFAULT_INSTALL_PATH = '/usr/local/share/stenogotchi/installed-plugins/'
def add_parsers(parser):
"""
Adds the plugins subcommand to a given argparse.ArgumentParser
"""
subparsers = parser.add_subparsers()
## pwnagotchi plugins
parser_plugins = subparsers.add_parser('plugins')
plugin_subparsers = parser_plugins.add_subparsers(dest='plugincmd')
## pwnagotchi plugins search
parser_plugins_search = plugin_subparsers.add_parser('search', help='Search for stenogotchi plugins')
parser_plugins_search.add_argument('pattern', type=str, help="Search expression (wildcards allowed)")
## pwnagotchi plugins list
parser_plugins_list = plugin_subparsers.add_parser('list', help='List available stenogotchi plugins')
parser_plugins_list.add_argument('-i', '--installed', action='store_true', required=False, help='List also installed plugins')
## pwnagotchi plugins update
parser_plugins_update = plugin_subparsers.add_parser('update', help='Updates the database')
## pwnagotchi plugins upgrade
parser_plugins_upgrade = plugin_subparsers.add_parser('upgrade', help='Upgrades plugins')
parser_plugins_upgrade.add_argument('pattern', type=str, nargs='?', default='*', help="Filter expression (wildcards allowed)")
## pwnagotchi plugins enable
parser_plugins_enable = plugin_subparsers.add_parser('enable', help='Enables a plugin')
parser_plugins_enable.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins disable
parser_plugins_disable = plugin_subparsers.add_parser('disable', help='Disables a plugin')
parser_plugins_disable.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins install
parser_plugins_install = plugin_subparsers.add_parser('install', help='Installs a plugin')
parser_plugins_install.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins uninstall
parser_plugins_uninstall = plugin_subparsers.add_parser('uninstall', help='Uninstalls a plugin')
parser_plugins_uninstall.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins edit
parser_plugins_edit = plugin_subparsers.add_parser('edit', help='Edit the options')
parser_plugins_edit.add_argument('name', type=str, help='Name of the plugin')
return parser
def used_plugin_cmd(args):
"""
Checks if the plugins subcommand was used
"""
return hasattr(args, 'plugincmd')
def handle_cmd(args, config):
"""
Parses the arguments and does the thing the user wants
"""
if args.plugincmd == 'update':
return update(config)
elif args.plugincmd == 'search':
args.installed = True # also search in installed plugins
return list_plugins(args, config, args.pattern)
elif args.plugincmd == 'install':
return install(args, config)
elif args.plugincmd == 'uninstall':
return uninstall(args, config)
elif args.plugincmd == 'list':
return list_plugins(args, config)
elif args.plugincmd == 'enable':
return enable(args, config)
elif args.plugincmd == 'disable':
return disable(args, config)
elif args.plugincmd == 'upgrade':
return upgrade(args, config, args.pattern)
elif args.plugincmd == 'edit':
return edit(args, config)
raise NotImplementedError()
def edit(args, config):
pass
def enable(args, config):
pass
def disable(args, config):
pass
def upgrade(args, config, pattern='*'):
pass
def list_plugins(args, config, pattern='*'):
"""
Lists the available and installed plugins
"""
installed = _get_installed(config)
print(installed)
return 0
def _extract_version(filename):
"""
Extracts the version from a python file
"""
return None
def _get_available():
"""
Get all availaible plugins
"""
return None
def _get_installed(config):
"""
Get all installed plugins
"""
installed = dict()
search_dirs = [ default_path, config['main']['custom_plugins'] ]
for search_dir in search_dirs:
if search_dir:
for filename in glob.glob(os.path.join(search_dir, "*.py")):
plugin_name = os.path.basename(filename.replace(".py", ""))
installed[plugin_name] = filename
return installed
def uninstall(args, config):
"""
Uninstalls a plugin
"""
return 0
def install(args, config):
"""
Installs the given plugin
"""
return 0
def _analyse_dir(path):
return None
def update(config):
"""
Updates the database
"""
return 0
================================================
FILE: stenogotchi/plugins/default/buttonshim.py
================================================
# ###############################################################
# Updated 20-03-2021 by Anodynous
# - Moved all functionality into class to remove dependency on global variables and improve integration with other plugins and core Stenogotchi functionality.
#
# Updated 13-01-2021 by Anodynous
# - Added support for hold action in addition to press.
# Based on: https://github.com/evilsocket/pwnagotchi-plugins-contrib/blob/master/buttonshim.py
# which in turn is based on https://github.com/pimoroni/button-shim/commit/143f35b4b56626bd7062bdff3245658af19822b4
#
################################################################
import logging
import RPi.GPIO as GPIO
import subprocess
import signal
import smbus
import time
from threading import Thread
import atexit
from colorsys import hsv_to_rgb
import stenogotchi
import stenogotchi.plugins as plugins
try:
import queue
except ImportError:
import Queue as queue
ADDR = 0x3f
LED_DATA = 7
LED_CLOCK = 6
REG_INPUT = 0x00
REG_OUTPUT = 0x01
REG_POLARITY = 0x02
REG_CONFIG = 0x03
NUM_BUTTONS = 5
BUTTON_A = 0
"""Button A"""
BUTTON_B = 1
"""Button B"""
BUTTON_C = 2
"""Button C"""
BUTTON_D = 3
"""Button D"""
BUTTON_E = 4
"""Button E"""
NAMES = ['A', 'B', 'C', 'D', 'E']
"""Sometimes you want to print the plain text name of the button that's triggered.
You can use::
buttonshim.NAMES[button_index]
To accomplish this.
"""
ERROR_LIMIT = 10
FPS = 60
LED_GAMMA = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11,
11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18,
19, 19, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28,
29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40,
40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 88, 89,
90, 91, 93, 94, 95, 96, 98, 99, 100, 102, 103, 104, 106, 107, 109, 110,
111, 113, 114, 116, 117, 119, 120, 121, 123, 124, 126, 128, 129, 131, 132, 134,
135, 137, 138, 140, 142, 143, 145, 146, 148, 150, 151, 153, 155, 157, 158, 160,
162, 163, 165, 167, 169, 170, 172, 174, 176, 178, 179, 181, 183, 185, 187, 189,
191, 193, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220,
222, 224, 227, 229, 231, 233, 235, 237, 239, 241, 244, 246, 248, 250, 252, 255]
# The LED is an APA102 driven via the i2c IO expander.
# We must set and clear the Clock and Data pins
# Each byte in self._reg_queue represents a snapshot of the pin state
class Handler():
plugin = None
def __init__(self, plugin):
self.press = None
self.release = None
self.hold = None
self.hold_time = 0
self.repeat = False
self.repeat_time = 0
self.t_pressed = 0
self.t_repeat = 0
self.hold_fired = False
self.plugin = plugin
class Buttonshim(plugins.Plugin):
__author__ = 'gon@o2online.de, Anodynous'
__version__ = '0.0.2'
__license__ = 'GPL3'
__description__ = 'Pimoroni Button Shim GPIO Button and RGB LED support plugin based on the pimoroni-buttonshim-lib and the pwnagotchi-gpio-buttons-plugin'
def __init__(self):
self._agent = None
self.running = False
self.options = dict()
self._running = False
self._plover_wpm_meters_enabled = False
self._states = None
self._bus = None
self._reg_queue = []
self._update_queue = []
self._brightness = 0.5
self._led_queue = queue.Queue()
self._t_poll = None
self._running = False
self._states = 0b00011111
self._handlers = [None,None,None,None,None]
self._button_was_held = False
def on_loaded(self):
logging.info("[buttonshim] GPIO Button plugin loaded.")
self.running = True
self._handlers = [Handler(self) for x in range(NUM_BUTTONS)]
self.on_press([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], self.press_handler)
self.on_hold([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], self.hold_handler)
self.on_release([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], self.release_handler)
def on_config_changed(self, config):
self.config = config
def on_ready(self, agent):
self._agent = agent
def set_ui_update(self, key, value):
self._agent.view().set(key, value)
def trigger_ui_update(self):
self._agent.view().update()
def _run(self):
self._running = True
_last_states = 0b00011111
_errors = 0
while self._running:
led_data = None
try:
led_data = self._led_queue.get(False)
self._led_queue.task_done()
except queue.Empty:
pass
try:
if led_data:
for chunk in self._chunk(led_data, 32):
self._bus.write_i2c_block_data(ADDR, REG_OUTPUT, chunk)
self._states = self._bus.read_byte_data(ADDR, REG_INPUT)
except IOError:
_errors += 1
if _errors > ERROR_LIMIT:
self._running = False
raise IOError("More than {} IO errors have occurred!".format(ERROR_LIMIT))
for x in range(NUM_BUTTONS):
last = (_last_states >> x) & 1
curr = (self._states >> x) & 1
handler = self._handlers[x]
# If last > curr then it's a transition from 1 to 0
# since the buttons are active low, that's a press event
if last > curr:
handler.t_pressed = time.time()
handler.hold_fired = False
if callable(handler.press):
handler.t_repeat = time.time()
Thread(target=handler.press, args=(x, True, handler.plugin)).start()
continue
if last < curr and callable(handler.release):
Thread(target=handler.release, args=(x, False, handler.plugin)).start()
continue
if curr == 0:
if callable(handler.hold) and not handler.hold_fired and (time.time() - handler.t_pressed) > handler.hold_time:
Thread(target=handler.hold, args=(x,)).start()
handler.hold_fired = True
if handler.repeat and callable(handler.press) and (time.time() - handler.t_repeat) > handler.repeat_time:
self._handlers[x].t_repeat = time.time()
Thread(target=self._handlers[x].press, args=(x, True, handler.plugin)).start()
_last_states = self._states
time.sleep(1.0 / FPS)
def _quit(self):
if self._running:
self._led_queue.join()
self.set_pixel(0, 0, 0)
self._led_queue.join()
self._running = False
self._t_poll.join()
def setup(self):
if self._bus is not None:
return
try:
self._bus = smbus.SMBus(1)
self._bus.write_byte_data(ADDR, REG_CONFIG, 0b00011111)
self._bus.write_byte_data(ADDR, REG_POLARITY, 0b00000000)
self._bus.write_byte_data(ADDR, REG_OUTPUT, 0b00000000)
self._t_poll = Thread(target=self._run)
self._t_poll.daemon = True
self._t_poll.start()
self.set_pixel(0, 0, 0)
atexit.register(self._quit)
except OSError as ex:
logging.error(f"[buttonshim] Ignore if no ButtonSHIM hw module present and web UI enabled: OSError encountered during setup: {ex}")
def _set_bit(self, pin, value):
if value:
self._reg_queue[-1] |= (1 << pin)
else:
self._reg_queue[-1] &= ~(1 << pin)
def _next(self):
if len(self._reg_queue) == 0:
self._reg_queue = [0b00000000]
else:
self._reg_queue.append(self._reg_queue[-1])
def _enqueue(self):
self._led_queue.put(self._reg_queue)
self._reg_queue = []
def _chunk(self, l, n):
for i in range(0, len(l)+1, n):
yield l[i:i + n]
def _write_byte(self, byte):
for i in range(8):
self._next()
self._set_bit(LED_CLOCK, 0)
self._set_bit(LED_DATA, byte & 0b10000000)
self._next()
self._set_bit(LED_CLOCK, 1)
byte <<= 1
def on_hold(self, buttons, handler=None, hold_time=1):
"""Attach a hold handler to one or more buttons.
This handler is fired when you hold a button for hold_time seconds.
When fired it will run in its own Thread.
It will be passed one argument, the button index::
@buttonshim.on_hold(buttonshim.BUTTON_A)
def handler(button):
# Your code here
:param buttons: A single button, or a list of buttons
:param handler: Optional: a function to bind as the handler
:param hold_time: Optional: the hold time in seconds (default 2)
"""
self.setup()
if buttons is None:
buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E]
if isinstance(buttons, int):
buttons = [buttons]
def attach_handler(handler):
for button in buttons:
self._handlers[button].hold = handler
self._handlers[button].hold_time = hold_time
if handler is not None:
attach_handler(handler)
else:
return attach_handler
def on_press(self, buttons, handler=None, repeat=False, repeat_time=0.5):
"""Attach a press handler to one or more buttons.
This handler is fired when you press a button.
When fired it will be run in its own Thread.
It will be passed two arguments, the button index and a
boolean indicating whether the button has been pressed/released::
@buttonshim.on_press(buttonshim.BUTTON_A)
def handler(button, pressed):
# Your code here
:param buttons: A single button, or a list of buttons
:param handler: Optional: a function to bind as the handler
:param repeat: Optional: Repeat the handler if the button is held
:param repeat_time: Optional: Time, in seconds, after which to repeat
"""
self.setup()
if buttons is None:
buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E]
if isinstance(buttons, int):
buttons = [buttons]
def attach_handler(handler):
for button in buttons:
self._handlers[button].press = handler
self._handlers[button].repeat = repeat
self._handlers[button].repeat_time = repeat_time
if handler is not None:
attach_handler(handler)
else:
return attach_handler
def on_release(self, buttons=None, handler=None):
"""Attach a release handler to one or more buttons.
This handler is fired when you let go of a button.
When fired it will be run in its own Thread.
It will be passed two arguments, the button index and a
boolean indicating whether the button has been pressed/released::
@buttonshim.on_release(buttonshim.BUTTON_A)
def handler(button, pressed):
# Your code here
:param buttons: A single button, or a list of buttons
:param handler: Optional: a function to bind as the handler
"""
self.setup()
if buttons is None:
buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E]
if isinstance(buttons, int):
buttons = [buttons]
def attach_handler(handler):
for button in buttons:
self._handlers[button].release = handler
if handler is not None:
attach_handler(handler)
else:
return attach_handler
def set_brightness(self, brightness):
self.setup()
if not isinstance(brightness, int) and not isinstance(brightness, float):
raise ValueError("Brightness should be an int or float")
if brightness < 0.0 or brightness > 1.0:
raise ValueError("Brightness should be between 0.0 and 1.0")
self._brightness = brightness
def set_pixel(self, r, g, b):
"""Set the Button SHIM RGB pixel
Display an RGB colour on the Button SHIM pixel.
:param r: Amount of red, from 0 to 255
:param g: Amount of green, from 0 to 255
:param b: Amount of blue, from 0 to 255
You can use HTML colours directly with hexadecimal notation in Python. EG::
buttonshim.self.set_pixel(0xFF, 0x00, 0xFF)
"""
self.setup()
if not isinstance(r, int) or r < 0 or r > 255:
raise ValueError("Argument r should be an int from 0 to 255")
if not isinstance(g, int) or g < 0 or g > 255:
raise ValueError("Argument g should be an int from 0 to 255")
if not isinstance(b, int) or b < 0 or b > 255:
raise ValueError("Argument b should be an int from 0 to 255")
r, g, b = [int(x * self._brightness) for x in (r, g, b)]
self._write_byte(0)
self._write_byte(0)
self._write_byte(0b11101111)
self._write_byte(LED_GAMMA[b & 0xff])
self._write_byte(LED_GAMMA[g & 0xff])
self._write_byte(LED_GAMMA[r & 0xff])
self._write_byte(0)
self._write_byte(0)
self._enqueue()
def blink(self, r, g, b, ontime, offtime, blinktimes):
logging.debug("[buttonshim] Blink")
for i in range(0, blinktimes):
self.set_pixel(r, g, b)
time.sleep(ontime)
self.set_pixel(0, 0, 0)
time.sleep(offtime)
def press_handler(self, button, pressed, plugin):
""" On press reset button held status """
self._button_was_held = False
def toggle_qwerty_steno(self):
try:
cap_state = plugins.loaded['evdevkb'].get_capture_state()
if not cap_state:
plugins.loaded['evdevkb'].start_capture()
logging.info(f"[buttonshim] Switched to QWERTY mode")
else:
plugins.loaded['evdevkb'].stop_capture()
logging.info(f"[buttonshim] Switched to STENO mode")
except Exception as ex:
logging.exception(f"[buttonshim] Check if evdevkb is loaded, exception: {str(ex)}")
def toggle_wpm_meters(self):
command = {}
try:
wpm_method = plugins.loaded['plover_link'].options['wpm_method']
wpm_timeout = plugins.loaded['plover_link'].options['wpm_timeout']
except Exception as ex:
logging.exception(f"[buttonshim] Check that wpm_method and wpm_timeout is configured. Falling back to defaults. Exception: {str(ex)}")
wpm_method = 'ncra'
wpm_timeout = '60'
if self._plover_wpm_meters_enabled:
command = {'stop_wpm_meter': 'wpm and strokes'}
self.set_ui_update('wpm', '')
self.set_ui_update('strokes', '')
self.trigger_ui_update()
logging.info(f"[buttonshim] Disabled WPM readings")
elif not self._plover_wpm_meters_enabled:
command = {'start_wpm_meter': 'wpm and strokes',
'wpm_method' : wpm_method,
'wpm_timeout' : wpm_timeout}
self.set_ui_update('wpm', wpm_method)
self.set_ui_update('strokes', f"{wpm_timeout}s")
self.trigger_ui_update()
logging.info(f"[buttonshim] Enabled WPM readings using method {wpm_method} and timeout {wpm_timeout}")
self._plover_wpm_meters_enabled = not self._plover_wpm_meters_enabled
plugins.loaded['plover_link'].send_signal_to_plover(command)
def toggle_dictionary_lookup(self):
if plugins.loaded['dict_lookup'].get_running():
if not plugins.loaded['dict_lookup'].get_input_mode(): # If not currently enabled, enable input mode
plugins.loaded['dict_lookup'].enable_input_mode()
else: # If currently enabled, revert to normal view
plugins.loaded['dict_lookup'].disable_input_mode()
else:
logging.debug(f"[buttonshim] dict_lookup is not ready yet. Check that Plover is running.")
def hold_handler(self, button):
""" On long press run built in internal Stenogotchi commands """
# Set button held status to prevent release_handler from triggering on release
self._button_was_held = True
# Blink in response to long hold event
red = 0
green = 70
blue = 70
on_time = 1
off_time = 0
blink_times = 1
thread = Thread(target=self.blink, args=(red, green, blue, on_time, off_time, blink_times))
thread.start()
if NAMES[button] == 'A':
# Toggle QWERTY/STENO mode
self.toggle_qwerty_steno()
elif NAMES[button] == 'B':
# Toggle WPM & strokes meters for Plover
self.toggle_wpm_meters()
elif NAMES[button] == 'C':
# Toggle dictionary lookup mode
self.toggle_dictionary_lookup()
elif NAMES[button] == 'D':
# Toggle wifi on/off
stenogotchi.set_wifi_onoff()
# Check for changes in wifi status over a short while
for i in range(5):
self._agent._update_wifi()
time.sleep(2)
logging.info(f"[buttonshim] Toggled wifi state")
elif NAMES[button] == 'E':
# Initiate clean shutdown process
logging.info(f"[buttonshim] Initiated clean shutdown")
stenogotchi.shutdown()
def release_handler(self, button, pressed, plugin):
""" On short press run command from config """
if not self._button_was_held:
logging.info(f"[buttonshim] Button Pressed! Loading command from slot '{button}' for button '{NAMES[button]}'")
bCfg = plugin.options['buttons'][NAMES[button]]
blinkCfg = bCfg['blink']
logging.debug(f'[buttonshim] {self.blink}')
if blinkCfg['enabled'] == True:
logging.debug(f"[buttonshim] Blinking led")
red = int(blinkCfg['red'])
green = int(blinkCfg['green'])
blue = int(blinkCfg['blue'])
on_time = float(blinkCfg['on_time'])
off_time = float(blinkCfg['off_time'])
blink_times = int(blinkCfg['blink_times'])
logging.debug(f"[buttonshim] red {red} green {green} blue {blue} on_time {on_time} off_time {off_time} blink_times {blink_times}")
thread = Thread(target=self.blink, args=(red, green, blue, on_time, off_time, blink_times))
thread.start()
logging.debug(f"[buttonshim] Blink thread started")
command = bCfg['command']
if command == '':
logging.debug(f"[buttonshim] Command empty")
else:
logging.debug(f"[buttonshim] Process create: {command}")
process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
process = None
logging.debug(f"[buttonshim] Process end")
================================================
FILE: stenogotchi/plugins/default/dict_lookup.py
================================================
#!/usr/bin/env python3
import logging
import time
import random
import stenogotchi.plugins as plugins
from stenogotchi.ui.components import LabeledValue, Text, Line
from stenogotchi.ui.view import BLACK
from stenogotchi.ui import view, state, faces
import stenogotchi.ui.fonts as fonts
class UiHandler():
def __init__(self, agent):
self.clear_elements = ('name', 'ups', 'wpm', 'status', 'strokes', 'uptime', 'line1')
self.input_mode = False
self._agent = agent
self._view = view.ROOT
self._stored_state = state.State()
self.minion_font = fonts.ImageFont.truetype("%s-Bold" % fonts.FONT_NAME, 18)
self.minion_offset = 65
def store_state(self):
for key, element in self._view._state.items():
if key in self.clear_elements or key == 'face':
self._stored_state.add_element(key, element)
def restore_state(self):
# Add all removed items back
for key, element in self._stored_state.items():
if key in self.clear_elements:
self.add_element(key, element)
def surprise_exit(self):
if not self._view.get('face') == faces.LOOK_L:
self._view.set('face', faces.LOOK_L)
self.update_view()
time.sleep(1)
self._view.set('face', faces.LOOK_R)
self.update_view()
time.sleep(1)
def relocate_face(self, minion):
self.remove_element('face')
if minion:
# Shrink and relocate
face = random.choices((faces.LOOK_L_HAPPY, faces.LOOK_L), weights=(0.9, 0.10), k=1)
self.add_element('face', Text(value=face[0], position=((self._view._width - self.minion_offset + 2), 0), color=BLACK, font=self.minion_font))
else:
# Grow and relocate, state doesn't matter as we will update it before view refresh
self.add_element('face', self._stored_state._state['face'])
def check_element(self, key):
return self._view.has_element(key)
def remove_element(self, key):
try:
self._view.remove_element(key)
except KeyError:
# TODO: fix self._view.has_element to replace this. Always seems to return None right now.
logging.error(f"[dict_lookup] No element '{key}' available for removal")
def add_element(self, key, element):
self._view.add_element(key, element)
def update_view(self):
self._view.update()
def enable_input_mode(self):
# Storing current state
self.store_state()
# Removing elements we will be overlapping and block them from triggering ui updates
for element in self.clear_elements:
self.remove_element(element)
# Block update of ui elements we removed
self._view._ignore_changes = self.clear_elements
# Shrink stenogotchi face
self.relocate_face(minion=True)
# Add new ui elements for input and output
line1_offset = self._view._layout['line1'].copy()
line1_offset[2] = self._view._width - self.minion_offset
self.add_element('input', LabeledValue(color=BLACK, label='', value='', position=(0, 0) , label_font=fonts.Bold, text_font=fonts.Medium, max_length=29))
self.add_element('line1_offset', Line(line1_offset, color=BLACK))
self.add_element('out1', Text(value='', position=self._agent._view._layout['name'], color=BLACK, font=fonts.Bold, wrap=True, max_length=self._agent._view._layout['status']['max']-1))
self.add_element('out2', Text(value='', position=self._agent._view._layout['status']['pos'], color=BLACK, font=self._agent._view._layout['status']['font'], wrap=True, max_length=self._agent._view._layout['status']['max']))
self.input_mode = True
# Used instead of self.update_view() to indicate input position from the start
self.display_input("")
logging.info("[dict_lookup] Enabled dictionary lookup mode")
def disable_input_mode(self):
# Add some variety to the exit
if random.randint(0, 9) > 7:
self.surprise_exit()
self._view.remove_element('line1_offset')
self._view.remove_element('input')
self._view.remove_element('out1')
self._view.remove_element('out2')
# Restore removed elements
self.restore_state()
# Grow stenogotchi face
self.relocate_face(minion=False)
# revert default update-ignore state
if self._view._config['ui']['fps'] > 0.0:
self._view._ignore_changes = ()
else:
self._view._ignore_changes = ('uptime', 'name')
# Prepare new empty state object
self._stored_state = state.State()
# Refresh uptime, set return message which will trigger ui update
self._agent._update_uptime()
self._agent.set_on_dict_lookup_done()
self.input_mode = False
logging.info("[dict_lookup] Disabled dictionary lookup mode")
def get_input_mode(self):
return self.input_mode
def display_output(self, list):
if self.get_input_mode():
out1_str = ""
out2_str = ""
#TODO: handle list items longer than max supported length for column
cnt = 0
for item in list:
if cnt < 6: # lines 0-5 fit in first column
out1_str += f"{item}\n"
elif cnt < 13: # lines 6-11 fit in second column
out2_str += f"{item}\n"
else:
break # no more room
cnt += 1
self._view.set('out1', out1_str)
self._view.set('out2', out2_str)
self.update_view()
def display_input(self, string, position_indicator="_"):
if self.get_input_mode():
string += position_indicator # Character added to highlight input position
self._agent._view.set('input', string)
self.update_view()
class InputHandler():
# TODO: Add support for evdevkb as input device
def __init__(self, agent):
self._agent = agent
self.input_mode = False
self._input = ""
def _on_send_string(self, text: str):
# If enter key received
if text in ('\n', '\r', '\r\n'):
if plugins.loaded['dict_lookup'].get_input_mode():
self._input += " "
self.push_input(position_indicator="")
plugins.loaded['dict_lookup'].lookup_word(self._input)
else:
self._input += text
self.push_input()
def _on_send_backspaces(self, count: int):
if count >= len(self._input):
self.clear_input()
else:
self._input = self._input[:-count]
self.push_input()
def _on_send_key_combination(self, combination: str):
if combination == 'Control_L(BackSpace)':
self.clear_input()
else:
logging.warning(f"[dict_lookup] Key-combinations not supported. Input '{combination}' ignored")
def _on_lookup_results(self, results_list):
# logging.debug(f"[dict_lookup] Lookup result from Plover '{results_list}'")
plugins.loaded['dict_lookup'].display_lookup_result(results_list)
def enable_input_mode(self):
# TODO: trigger routing from evdevkb to here
command = {'output_to_stenogotchi': True}
plugins.loaded['plover_link'].send_signal_to_plover(command)
self.input_mode = True
def disable_input_mode(self):
# TODO: revert routing from evdevkb to btclient
command = {'output_to_stenogotchi': False}
plugins.loaded['plover_link'].send_signal_to_plover(command)
self.input_mode = False
def push_input(self, position_indicator="_"):
plugins.loaded['dict_lookup'].push_input(self._input, position_indicator)
def clear_input(self):
self._input = ""
self.push_input()
class DictLookup(plugins.Plugin):
__autohor__ = 'Anodynous'
__version__ = '0.3'
__license__ = 'GPL3'
__description__ = 'This plugin enables looking up words and strokes in enabled plover dictionaries'
def __init__(self):
self._agent = None
self.config = None
self.running = False
self.input_mode = False
self.input_handler = None
self.ui_handler = None
def on_plover_ready(self, agent):
self._agent = agent
self.ui_handler = UiHandler(agent)
self.input_handler = InputHandler(agent)
self.running = True
def on_config_changed(self, config):
self.config = config
def on_unload(self, ui):
self.lookup = None
self.ui_handler = None
def get_running(self):
return self.running
def get_input_mode(self):
return self.input_mode
def enable_input_mode(self):
self.ui_handler.enable_input_mode()
self.input_handler.enable_input_mode()
self.input_mode = True
def disable_input_mode(self):
self.ui_handler.disable_input_mode()
self.input_handler.disable_input_mode()
self.input_handler.clear_input()
self.input_mode = False
def lookup_word(self, word):
# Remove leading/trailing whitespaces and send word to plover for dictionary lookup
word = word.strip()
command = {'lookup_word': word}
plugins.loaded['plover_link'].send_signal_to_plover(command)
def lookup_stroke(self, stroke):
# Remove leading/trailing whitespaces and send stroke to plover for dictionary lookup
# TODO: implement functionality to use stroke-lookup
stroke = stroke.strip()
logging.debug(f"[dict_lookup] Looking up stroke '{stroke}'")
command = {'lookup_word': stroke}
plugins.loaded['plover_link'].send_signal_to_plover(command)
def display_lookup_result(self, results_list):
self.push_output(results_list)
def sort_list(self, rlist):
# Sorts the results in ascending order.
# Primary sorting key: number of chords
# Secondary sorting key: length of chord(combination)
sort_key = lambda k : (k.count('/'), len(k))
rlist_sorted = []
if len(rlist) < 2:
return rlist
else:
rlist_sorted = sorted(rlist, key=sort_key)
return rlist_sorted
def push_input(self, string="", position_indicator="_"):
if self.input_mode:
self.ui_handler.display_input(string, position_indicator)
def push_output(self, rlist):
if self.input_mode:
if rlist:
rlist_sorted = self.sort_list(rlist)
else:
rlist_sorted = []
self.ui_handler.display_output(rlist_sorted)
if __name__ == '__main__':
print("Please enable and run as Stenogotchi plugin")
================================================
FILE: stenogotchi/plugins/default/evdevkb.py
================================================
"""
Evdev based keyboard client for capturing input and relays the keypress to a Bluetooth HID keyboard emulator D-BUS Service
Based on: https://gist.github.com/ukBaz/a47e71e7b87fbc851b27cde7d1c0fcf0#file-readme-md
Which in turn takes the original idea from: http://yetanotherpointlesstechblog.blogspot.com/2016/04/emulating-bluetooth-keyboard-with.html
Tested on:
Python 3.7 (needs 3.4+)
Evdev 1.3.0
"""
import logging
import evdev
from select import select
from time import sleep
if not __name__ == '__main__':
import stenogotchi.plugins as plugins
ObjectClass = plugins.Plugin
else:
import dbus
ObjectClass = object
HID_DBUS = 'com.github.stenogotchi'
HID_SRVC = '/com/github/stenogotchi'
KEYTABLE = {
"KEY_RESERVED": 0,
"KEY_ESC": 41,
"KEY_1": 30,
"KEY_2": 31,
"KEY_3": 32,
"KEY_4": 33,
"KEY_5": 34,
"KEY_6": 35,
"KEY_7": 36,
"KEY_8": 37,
"KEY_9": 38,
"KEY_0": 39,
"KEY_MINUS": 45,
"KEY_EQUAL": 46,
"KEY_BACKSPACE": 42,
"KEY_TAB": 43,
"KEY_Q": 20,
"KEY_W": 26,
"KEY_E": 8,
"KEY_R": 21,
"KEY_T": 23,
"KEY_Y": 28,
"KEY_U": 24,
"KEY_I": 12,
"KEY_O": 18,
"KEY_P": 19,
"KEY_LEFTBRACE": 47,
"KEY_RIGHTBRACE": 48,
"KEY_ENTER": 40,
"KEY_LEFTCTRL": 224,
"KEY_A": 4,
"KEY_S": 22,
"KEY_D": 7,
"KEY_F": 9,
"KEY_G": 10,
"KEY_H": 11,
"KEY_J": 13,
"KEY_K": 14,
"KEY_L": 15,
"KEY_SEMICOLON": 51,
"KEY_APOSTROPHE": 52,
"KEY_GRAVE": 53,
"KEY_LEFTSHIFT": 225,
"KEY_BACKSLASH": 50,
"KEY_Z": 29,
"KEY_X": 27,
"KEY_C": 6,
"KEY_V": 25,
"KEY_B": 5,
"KEY_N": 17,
"KEY_M": 16,
"KEY_COMMA": 54,
"KEY_DOT": 55,
"KEY_SLASH": 56,
"KEY_RIGHTSHIFT": 229,
"KEY_KPASTERISK": 85,
"KEY_LEFTALT": 226,
"KEY_SPACE": 44,
"KEY_CAPSLOCK": 57,
"KEY_F1": 58,
"KEY_F2": 59,
"KEY_F3": 60,
"KEY_F4": 61,
"KEY_F5": 62,
"KEY_F6": 63,
"KEY_F7": 64,
"KEY_F8": 65,
"KEY_F9": 66,
"KEY_F10": 67,
"KEY_NUMLOCK": 83,
"KEY_SCROLLLOCK": 71,
"KEY_KP7": 95,
"KEY_KP8": 96,
"KEY_KP9": 97,
"KEY_KPMINUS": 86,
"KEY_KP4": 92,
"KEY_KP5": 93,
"KEY_KP6": 94,
"KEY_KPPLUS": 87,
"KEY_KP1": 89,
"KEY_KP2": 90,
"KEY_KP3": 91,
"KEY_KP0": 98,
"KEY_KPDOT": 99,
"KEY_ZENKAKUHANKAKU": 148,
"KEY_102ND": 100,
"KEY_F11": 68,
"KEY_F12": 69,
"KEY_RO": 135,
"KEY_KATAKANA": 146,
"KEY_HIRAGANA": 147,
"KEY_HENKAN": 138,
"KEY_KATAKANAHIRAGANA": 136,
"KEY_MUHENKAN": 139,
"KEY_KPJPCOMMA": 140,
"KEY_KPENTER": 88,
"KEY_RIGHTCTRL": 228,
"KEY_KPSLASH": 84,
"KEY_SYSRQ": 70,
"KEY_RIGHTALT": 230,
"KEY_HOME": 74,
"KEY_UP": 82,
"KEY_PAGEUP": 75,
"KEY_LEFT": 80,
"KEY_RIGHT": 79,
"KEY_END": 77,
"KEY_DOWN": 81,
"KEY_PAGEDOWN": 78,
"KEY_INSERT": 73,
"KEY_DELETE": 76,
"KEY_MUTE": 239,
"KEY_VOLUMEDOWN": 238,
"KEY_VOLUMEUP": 237,
"KEY_POWER": 102,
"KEY_KPEQUAL": 103,
"KEY_PAUSE": 72,
"KEY_KPCOMMA": 133,
"KEY_HANGEUL": 144,
"KEY_HANJA": 145,
"KEY_YEN": 137,
"KEY_LEFTMETA": 227,
"KEY_RIGHTMETA": 231,
"KEY_COMPOSE": 101,
"KEY_STOP": 243,
"KEY_AGAIN": 121,
"KEY_PROPS": 118,
"KEY_UNDO": 122,
"KEY_FRONT": 119,
"KEY_COPY": 124,
"KEY_OPEN": 116,
"KEY_PASTE": 125,
"KEY_FIND": 244,
"KEY_CUT": 123,
"KEY_HELP": 117,
"KEY_CALC": 251,
"KEY_SLEEP": 248,
"KEY_WWW": 240,
"KEY_COFFEE": 249,
"KEY_BACK": 241,
"KEY_FORWARD": 242,
"KEY_EJECTCD": 236,
"KEY_NEXTSONG": 235,
"KEY_PLAYPAUSE": 232,
"KEY_PREVIOUSSONG": 234,
"KEY_STOPCD": 233,
"KEY_REFRESH": 250,
"KEY_EDIT": 247,
"KEY_SCROLLUP": 245,
"KEY_SCROLLDOWN": 246,
"KEY_F13": 104,
"KEY_F14": 105,
"KEY_F15": 106,
"KEY_F16": 107,
"KEY_F17": 108,
"KEY_F18": 109,
"KEY_F19": 110,
"KEY_F20": 111,
"KEY_F21": 112,
"KEY_F22": 113,
"KEY_F23": 114,
"KEY_F24": 115
}
# Map modifier keys to array element in the bit array
MODKEYS = {
"KEY_RIGHTMETA": 0,
"KEY_RIGHTALT": 1,
"KEY_RIGHTSHIFT": 2,
"KEY_RIGHTCTRL": 3,
"KEY_LEFTMETA": 4,
"KEY_LEFTALT": 5,
"KEY_LEFTSHIFT": 6,
"KEY_LEFTCTRL": 7
}
class EvdevKbrd:
"""
Take the events from a physically attached keyboard and send the
HID messages to the keyboard D-Bus server.
"""
def __init__(self, skip_dbus = False):
self._skip_dbus = skip_dbus
self.do_capture = False
self.keytable = KEYTABLE
self.modkeys = MODKEYS
self.target_length = 6
self.mod_keys = 0b00000000
self.pressed_keys = []
self.have_kb = False
self.devs = None
if self._skip_dbus:
self.bus = None
self.btkobject = None
self.btk_service = None
else:
self.bus = dbus.SystemBus()
self.btkobject = self.bus.get_object(HID_DBUS, HID_SRVC)
self.btk_service = dbus.Interface(self.btkobject, HID_DBUS)
def convert(self, evdev_keycode):
return self.keytable[evdev_keycode]
def modkey(self, evdev_keycode):
if evdev_keycode in self.modkeys:
return self.modkeys[evdev_keycode]
else:
return -1 # Return an invalid array element
def set_do_capture(self, toggle):
self.do_capture = toggle
def grab(self):
# Make input device unavailable for other applications
for dev in self.devs:
dev.grab()
def ungrab(self):
# Release input device for other applications
for dev in self.devs:
dev.ungrab()
def get_input_devices(self):
# Returns all input devices connected to device
input_devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
return input_devices
def get_keyboards(self):
# Returns all input devices that look like keyboards that are connected to device
input_devices = self.get_input_devices()
keyboards = []
for device in input_devices:
# Check if the input device has a KEY_A
has_key_a = evdev.ecodes.KEY_A in device.capabilities().get(evdev.ecodes.EV_KEY, [])
if has_key_a:
keyboards.append(device)
logging.debug(f"[evdevkb] Found keyboard '{device.name}' at path '{device.path}'")
return keyboards
def set_keyboards(self):
# Sets all keyboards as device to listen for key-inputs from
while not self.have_kb:
if not self.do_capture:
break
keyboards = self.get_keyboards()
if keyboards:
self.devs = keyboards
self.have_kb = True
else:
logging.debug('[evdevkb] Keyboard not found, waiting 3 seconds and retrying')
sleep(3)
def update_mod_keys(self, mod_key, value):
"""
Which modifier keys are active is stored in an 8 bit number.
Each bit represents a different key. This method takes which bit
and its new value as input
:param mod_key: The value of the bit to be updated with new value
:param value: Binary 1 or 0 depending if pressed or released
"""
bit_mask = 1 << (7-mod_key)
if value: # set bit
self.mod_keys |= bit_mask
else: # clear bit
self.mod_keys &= ~bit_mask
def update_keys(self, norm_key, value):
if value < 1:
self.pressed_keys.remove(norm_key)
elif norm_key not in self.pressed_keys:
self.pressed_keys.insert(0, norm_key)
len_delta = self.target_length - len(self.pressed_keys)
if len_delta < 0:
self.pressed_keys = self.pressed_keys[:len_delta]
elif len_delta > 0:
self.pressed_keys.extend([0] * len_delta)
@property
def state(self):
"""
property with the HID message to send for the current keys pressed
on the keyboards
:return: bytes of HID message
"""
return [0xA1, 0x01, self.mod_keys, 0, *self.pressed_keys]
def send_keys(self):
# If ran as part of Stenogotchi, communicate directly with plugin
if self._skip_dbus:
plugins.loaded['plover_link']._stenogotchiservice.send_keys([self.state])
# If ran as stand-alone, assume dbus is needed to access send_keys() function
else:
self.btk_service.send_keys(self.state)
def event_loop(self):
"""
Reads keypresses from all identified keyboards and sends them to emulated
bluetooth HID as long as do_capture is True.
"""
self.do_capture = True
self.grab()
while self.do_capture:
r, w, x = select(self.devs, [], [], 0.01) # 0.1 is default
for fd in r:
for event in fd.read():
# We only want up/down key-events
if event.type == evdev.ecodes.EV_KEY and event.value < 2:
key_str = evdev.ecodes.KEY[event.code]
mod_key = self.modkey(key_str)
if mod_key > -1:
self.update_mod_keys(mod_key, event.value)
else:
self.update_keys(self.convert(key_str), event.value)
self.send_keys()
self.ungrab()
for dev in self.devs:
dev.close()
class EvdevKeyboard(ObjectClass):
__autohor__ = 'Anodynous'
__version__ = '0.2'
__license__ = 'MIT'
__description__ = 'This plugin captures and blocks keypress events using evdev and sends to module emulating bluetooth HID device.'
def __init__(self):
self._agent = None
self.evdevkb = None
self.do_capture = False
def on_ready(self, agent):
self._agent = agent
def on_config_changed(self, config):
self.config = config
def trigger_ui_update(self, input_mode):
self._agent.view().set('mode', input_mode)
self._agent.view().update()
def start_capture(self):
logging.info('[evdevkb] Capturing evdev keypress events...')
self.trigger_ui_update('QWERTY')
self.evdevkb = EvdevKbrd(skip_dbus=True)
self.evdevkb.set_do_capture(True)
self.do_capture = True
self.evdevkb.set_keyboards()
self.evdevkb.event_loop()
def stop_capture(self):
logging.info('[evdevkb] Ignoring evdev keypress events...')
self.evdevkb.set_do_capture(False)
self.do_capture = False
self.evdevkb = None
self.trigger_ui_update('STENO')
def get_capture_state(self):
return self.do_capture
if __name__ == '__main__':
try:
print('Setting up keyboard')
kb = EvdevKbrd()
print('starting event loop')
kb.set_keyboards()
kb.event_loop()
except RuntimeError: pass # Handling for bug in evdev 1.3.0, see https://github.com/gvalkov/python-evdev/issues/120
================================================
FILE: stenogotchi/plugins/default/example.py
================================================
# ###############################################################
# Based on: https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/plugins/default/example.py
#
# Changed 22-03-2021 by Anodynous
# - Changed to fit Stenogotchi events
#
################################################################
import logging
import stenogotchi.plugins as plugins
from stenogotchi.ui.components import LabeledValue
from stenogotchi.ui.view import BLACK
import stenogotchi.ui.fonts as fonts
class Example(plugins.Plugin):
__author__ = 'evilsocket@gmail.com, Anodynous'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
def __init__(self):
logging.debug("[example] example plugin created")
# called when http://:/plugins// is called
# must return a html page
# IMPORTANT: If you use "POST"s, add a csrf-token (via csrf_token() and render_template_string)
def on_webhook(self, path, request):
logging.info("[example] webhook established")
# called when the plugin is loaded
def on_loaded(self):
logging.warning("[example] WARNING: this plugin should be disabled! options = " % self.options)
# called before the plugin is unloaded
def on_unload(self, ui):
logging.info("[example] is unloaded")
# called hen there's internet connectivity
def on_internet_available(self, agent):
logging.info("[example] unit has internet connection")
# called to setup the ui elements
def on_ui_setup(self, ui):
# add custom UI elements
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
# called when the ui is updated
def on_ui_update(self, ui):
# update those elements
some_voltage = 0.1
some_capacity = 100.0
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
# called when the hardware display setup is done, display is an hardware specific object
def on_display_setup(self, display):
logging.info("[example] unit has set up display")
# called when everything is ready and the main loop is about to start
def on_ready(self, agent):
logging.info("[example] unit is ready")
# you can run custom bettercap commands if you want
# agent.run('ble.recon on')
# or set a custom state
# agent.set_bored()
# called when the status is set to grateful
def on_grateful(self, agent):
logging.info("[example] unit is grateful")
# called when the status is set to lonely
def on_lonely(self, agent):
logging.info("[example] unit is lonely")
# called when the status is set to bored
def on_bored(self, agent):
logging.info("[example] unit is bored")
# called when the status is set to sad
def on_sad(self, agent):
logging.info("[example] unit is sad")
# called when the status is set to angry
def on_angry(self, agent):
logging.info("[example] unit is angry")
# called when the status is set to excited
def on_excited(self, agent):
logging.info("[example] unit is excited")
# called when the agent is rebooting the board
def on_rebooting(self, agent):
logging.info("[example] unit is rebooting")
# called when the agent is waiting for t seconds
def on_wait(self, agent, t):
logging.info(f"[example] unit is waiting {t} seconds")
# called when the agent is sleeping for t seconds
def on_sleep(self, agent, t):
logging.info(f"[example] unit is sleeping {t} seconds")
# called when an epoch is over (where an epoch is a single loop of the main algorithm)
def on_epoch(self, agent, epoch, epoch_data):
logging.info(f"[example] epoch is over")
# called when successfully connected to a bluetooth host
def on_bt_connected(self, agent, bthost_name):
logging.info(f"[example] unit connected to bluetooth host '{bthost_name}'")
# called when disconnected from bluetooth host
def on_bt_disconnected(self, agent):
logging.info("[example] unit disconnected from bluetooth host")
# called when plover boots
def on_plover_boot(self, agent):
logging.info("[example] Plover is starting up")
# called when plover is ready
def on_plover_ready(self, agent):
logging.info("[example] Plover is ready")
# called when plover quits
def on_plover_quit(self, agent):
logging.info("[example] Plover has quit")
# called when wifi is connected
def on_wifi_connected(self, agent, ssid, ip):
logging.info(f"[example] unit is connected to wifi '{ssid}' using ip '{ip}'")
# called when wifi is disconnected
def on_wifi_disconnected(self, agent):
logging.info("[example] unit is disconnected from wifi")
# called when wpm stats are updated
def on_wpm_stats(self, agent):
logging.info("[example] unit received new WPM stats")
# called when wpm-strokes stats are updated
def on_strokes_stats(self, agent):
logging.info("[example] unit received new WPM-strokes stats")
================================================
FILE: stenogotchi/plugins/default/led.py
================================================
# ###############################################################
# Based on: https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/plugins/default/led.py
#
# Changed 22-03-2021 by Anodynous
# - Changed to fit Stenogotchi events
#
################################################################
from threading import Event
import _thread
import logging
import time
import stenogotchi.plugins as plugins
class Led(plugins.Plugin):
__author__ = 'evilsocket@gmail.com, Anodynous'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'This plugin blinks the PWR led with different patterns depending on the event.'
def __init__(self):
self._is_busy = False
self._event = Event()
self._event_name = None
self._led_file = "/sys/class/leds/led0/brightness"
self._delay = 200
# called when the plugin is loaded
def on_loaded(self):
self._led_file = "/sys/class/leds/led%d/brightness" % self.options['led']
self._delay = int(self.options['delay'])
logging.info("[led] plugin loaded for %s" % self._led_file)
self._on_event('loaded')
_thread.start_new_thread(self._worker, ())
def on_config_changed(self, config):
self.config = config
def _on_event(self, event):
if not self._is_busy:
self._event_name = event
self._event.set()
logging.debug("[led] event '%s' set", event)
else:
logging.debug("[led] skipping event '%s' because the worker is busy", event)
def _led(self, on):
with open(self._led_file, 'wt') as fp:
fp.write(str(on))
def _blink(self, pattern):
logging.debug("[led] using pattern '%s' ..." % pattern)
for c in pattern:
if c == ' ':
self._led(1)
else:
self._led(0)
time.sleep(self._delay / 1000.0)
# reset
self._led(0)
def _worker(self):
while True:
self._event.wait()
self._event.clear()
self._is_busy = True
try:
if self._event_name in self.options['patterns']:
pattern = self.options['patterns'][self._event_name]
self._blink(pattern)
else:
logging.debug("[led] no pattern defined for %s" % self._event_name)
except Exception as e:
logging.exception("[led] error while blinking")
finally:
self._is_busy = False
# called when the unit is updating its software
def on_updating(self):
self._on_event('updating')
# called when there's internet connectivity
def on_internet_available(self, agent):
self._on_event('internet_available')
# called when everything is ready and the main loop is about to start
def on_ready(self, agent):
self._on_event('ready')
# called when the status is set to grateful
def on_grateful(self, agent):
self._on_event('grateful')
# called when the status is set to lonely
def on_lonely(self, agent):
self._on_event('lonely')
# called when the status is set to bored
def on_bored(self, agent):
self._on_event('bored')
# called when the status is set to sad
def on_sad(self, agent):
self._on_event('sad')
# called when the status is set to angry
def on_angry(self, agent):
self._on_event('angry')
# called when the status is set to excited
def on_excited(self, agent):
self._on_event('excited')
# called when the agent is rebooting the board
def on_rebooting(self, agent):
self._on_event('rebooting')
# called when the agent is waiting for t seconds
def on_wait(self, agent, t):
self._on_event('wait')
# called when the agent is sleeping for t seconds
def on_sleep(self, agent, t):
self._on_event('sleep')
# called when an epoch is over (where an epoch is a single loop of the main algorithm)
def on_epoch(self, agent, epoch, epoch_data):
self._on_event('epoch')
# called when successfully connected to a bluetooth host
def on_bt_connected(self, agent, bthost_name):
self._on_event('bt_connected')
# called when disconnected from bluetooth host
def on_bt_disconnected(self, agent):
self._on_event('bt_disconnected')
# called when plover boots
def on_plover_boot(self, agent):
self._on_event('plover_boot')
# called when plover is ready
def on_plover_ready(self, agent):
self._on_event('plover_ready')
# called when plover quits
def on_plover_quit(self, agent):
self._on_event('plover_quit')
# called when wifi is connected
def on_wifi_connected(self, agent, ssid, ip):
self._on_event('wifi_connected')
# called when wifi is disconnected
def on_wifi_disconnected(self, agent):
self._on_event('wifi_disconnected')
# called when wpm stats are updated
def on_wpm_stats(self, agent):
self._on_event('wpm_set')
# called when wpm-strokes stats are updated
def on_strokes_stats(self, agent):
self._on_event('strokes_set')
================================================
FILE: stenogotchi/plugins/default/logtail.py
================================================
import os
import logging
import threading
from itertools import islice
from time import sleep
from datetime import datetime,timedelta
from stenogotchi import plugins
from stenogotchi.utils import StatusFile
from flask import render_template_string
from flask import jsonify
from flask import abort
from flask import Response
TEMPLATE = """
{% extends "base.html" %}
{% set active_page = "plugins" %}
{% block title %}
Logtail
{% endblock %}
{% block styles %}
{{ super() }}
{% endblock %}
{% block script %}
var table = document.getElementById('table');
var filter = document.getElementById('filter');
var filterVal = filter.value.toUpperCase();
var xhr = new XMLHttpRequest();
xhr.open('GET', '{{ url_for('plugins') }}/logtail/stream');
xhr.send();
var position = 0;
var data;
var time;
var level;
var msg;
var colorClass;
function handleNewData() {
var messages = xhr.responseText.split('\\n');
filterVal = filter.value.toUpperCase();
messages.slice(position, -1).forEach(function(value) {
if (value.charAt(0) != '[') {
msg = value;
time = '';
level = '';
} else {
data = value.split(']');
time = data.shift() + ']';
level = data.shift() + ']';
msg = data.join(']');
switch(level) {
case ' [INFO]':
colorClass = 'info';
break;
case ' [WARNING]':
colorClass = 'warning';
break;
case ' [ERROR]':
colorClass = 'error';
break;
case ' [DEBUG]':
colorClass = 'debug';
break;
default:
colorClass = 'default';
break;
}
}
var tr = document.createElement('tr');
var td1 = document.createElement('td');
var td2 = document.createElement('td');
var td3 = document.createElement('td');
td1.textContent = time;
td2.textContent = level;
td3.textContent = msg;
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
tr.className = colorClass;
if (filterVal.length > 0 && value.toUpperCase().indexOf(filterVal) == -1) {
tr.style.display = "none";
}
table.appendChild(tr);
});
position = messages.length - 1;
}
var scrollingElement = (document.scrollingElement || document.body)
function scrollToBottom () {
scrollingElement.scrollTop = scrollingElement.scrollHeight;
}
var timer;
var scrollElm = document.getElementById('autoscroll');
timer = setInterval(function() {
handleNewData();
if (scrollElm.checked) {
scrollToBottom();
}
if (xhr.readyState == XMLHttpRequest.DONE) {
clearInterval(timer);
}
}, 1000);
var typingTimer;
var doneTypingInterval = 1000;
filter.onkeyup = function() {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
}
filter.onkeydown = function() {
clearTimeout(typingTimer);
}
function doneTyping() {
document.body.style.cursor = 'progress';
var tr, tds, td, i, txtValue;
filterVal = filter.value.toUpperCase();
tr = table.getElementsByTagName("tr");
for (i = 1; i < tr.length; i++) {
txtValue = tr[i].textContent || tr[i].innerText;
if (txtValue.toUpperCase().indexOf(filterVal) > -1) {
tr[i].style.display = "table-row";
} else {
tr[i].style.display = "none";
}
}
document.body.style.cursor = 'default';
}
{% endblock %}
{% block content %}
Time
Level
Message
{% endblock %}
"""
class Logtail(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '0.1.0'
__license__ = 'GPL3'
__description__ = 'This plugin tails the logfile.'
def __init__(self):
self.lock = threading.Lock()
self.options = dict()
self.ready = False
def on_config_changed(self, config):
self.config = config
self.ready = True
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
logging.info("[logtail] Logtail plugin loaded.")
def on_webhook(self, path, request):
if not self.ready:
return "Plugin not ready"
if not path or path == "/":
return render_template_string(TEMPLATE)
if path == 'stream':
def generate():
with open(self.config['main']['log']['path']) as f:
yield ''.join(f.readlines()[-self.options.get('max-lines', 4096):])
while True:
yield f.readline()
return Response(generate(), mimetype='text/plain')
abort(404)
================================================
FILE: stenogotchi/plugins/default/memtemp.py
================================================
# memtemp shows memory infos and cpu temperature
#
# mem usage, cpu load, cpu temp
#
###############################################################
#
# Updated 13-01-2021 by Anodynous
# - Changed CPU reading from using default method to having a
# continuously updating 60s average to draw upon. Default
# method returns 90-100% on RPI0 when triggered alongside
# an on_ui_update event
#
# Updated 18-10-2019 by spees
# - Changed the place where the data was displayed on screen
# - Made the data a bit more compact and easier to read
# - removed the label so we wont waste screen space
# - Updated version to 1.0.1
#
# 20-10-2019 by spees
# - Refactored to use the already existing functions
# - Now only shows memory usage in percentage
# - Added CPU load
# - Added horizontal and vertical orientation
#
###############################################################
from stenogotchi.ui.components import LabeledValue
from stenogotchi.ui.view import BLACK
import stenogotchi.ui.fonts as fonts
import stenogotchi.plugins as plugins
import stenogotchi
import logging
import _thread
class MemTemp(plugins.Plugin):
__author__ = 'https://github.com/xenDE'
__version__ = '1.0.1'
__license__ = 'GPL3'
__description__ = 'A plugin that will display memory/cpu usage and temperature'
def __init__(self):
self.cpu_load_avg = 0
def on_loaded(self):
logging.info("[memtemp] memtemp plugin loaded.")
_thread.start_new_thread(self._cpu_poller(), ())
def on_config_changed(self, config):
self.config = config
def mem_usage(self):
return int(stenogotchi.mem_usage() * 100)
def cpu_load(self):
#return int(stenogotchi.cpu_load() * 100)
return int(self.cpu_load_avg * 100)
def _cpu_poller(self, s=60):
"""
Runs in own thread and continually recalculates the CPU load over a (s)-second long window
"""
while True:
self.cpu_load_avg = stenogotchi.cpu_load(s)
def on_ui_setup(self, ui):
if ui.is_waveshare_v2():
h_pos = (180, 80)
v_pos = (180, 61)
elif ui.is_waveshare_v1():
h_pos = (170, 80)
v_pos = (170, 61)
elif ui.is_waveshare144lcd():
h_pos = (53, 77)
v_pos = (78, 67)
elif ui.is_inky():
h_pos = (140, 68)
v_pos = (165, 54)
elif ui.is_waveshare27inch():
h_pos = (192, 138)
v_pos = (216, 122)
else:
h_pos = (155, 76)
v_pos = (180, 61)
if self.options['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
position=v_pos,
label_font=fonts.Small, text_font=fonts.Small))
else:
# default to horizontal
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
position=h_pos,
label_font=fonts.Small, text_font=fonts.Small))
def on_unload(self, ui):
with ui._lock:
ui.remove_element('memtemp')
def on_ui_update(self, ui):
if self.options['scale'] == "fahrenheit":
temp = (stenogotchi.temperature() * 9 / 5) + 32
symbol = "f"
elif self.options['scale'] == "kelvin":
temp = stenogotchi.temperature() + 273.15
symbol = "k"
else:
# default to celsius
temp = stenogotchi.temperature()
symbol = "c"
if self.options['orientation'] == "vertical":
ui.set('memtemp',
" mem:%s%%\n cpu:%s%%\ntemp:%s%s" % (self.mem_usage(), self.cpu_load(), temp, symbol))
else:
# default to horizontal
ui.set('memtemp',
" mem cpu temp\n %s%% %s%% %s%s" % (self.mem_usage(), self.cpu_load(), temp, symbol))
================================================
FILE: stenogotchi/plugins/default/plover_link.py
================================================
"""
Stenogotchi and Bluetooth HID keyboard emulator D-BUS Service
Based on: https://gist.github.com/ukBaz/a47e71e7b87fbc851b27cde7d1c0fcf0#file-readme-md
Which in turn takes the original idea from: http://yetanotherpointlesstechblog.blogspot.com/2016/04/emulating-bluetooth-keyboard-with.html
Tested on:
Python 3.7-3.9
BlueZ 5.5
"""
import os
import sys
import logging
from types import MethodDescriptorType
import dbus
import dbus.service
import socket
from time import sleep
from gi.repository import GLib
from dbus.mainloop.glib import DBusGMainLoop
if not __name__ == '__main__':
import stenogotchi.plugins as plugins
ObjectClass = plugins.Plugin
else:
ObjectClass = object
class BluezErrorRejected(dbus.DBusException):
_dbus_error_name = "org.bluez.Error.Rejected"
class BluezErrorCanceled(dbus.DBusException):
_dbus_error_name = "org.bluez.Error.Canceled"
class Agent(dbus.service.Object):
"""
BT Pairing agent
API: https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/agent-api.txt
examples: https://github.com/elsampsa/btdemo/blob/master/bt_studio.py
"""
@dbus.service.method('org.bluez.Agent1',
in_signature='os', out_signature='')
def AuthorizeService(self, device, uuid):
logging.info(f"[plover_link] Successfully paired device: {device} using Secure Simple Pairing (SSP)")
return
@dbus.service.method('org.bluez.Agent1',
in_signature='o', out_signature='')
def RequestAuthorization(self, device):
logging.info(f"[plover_link] Accepted RequestAuthorization from {device}")
return
@dbus.service.method('org.bluez.Agent1',
in_signature='', out_signature='')
def Cancel(self):
logging.info("[plover_link] Cancel request received from BT client")
raise(BluezErrorCanceled)
@dbus.service.method('org.bluez.Agent1',
in_signature='', out_signature='')
def Release(self):
self.logging("[plover_link] Connection released due to BT client request")
mainloop.quit()
class HumanInterfaceDeviceProfile(dbus.service.Object):
"""
BlueZ D-Bus Profile for HID
"""
fd = -1
@dbus.service.method('org.bluez.Profile1',
in_signature='', out_signature='')
def Release(self):
logging.info('[plover_link] PloverLink: Release')
mainloop.quit()
@dbus.service.method('org.bluez.Profile1',
in_signature='oha{sv}', out_signature='')
def NewConnection(self, path, fd, properties):
self.fd = fd.take()
logging.info('[plover_link] NewConnection({}, {})'.format(path, self.fd))
for key in properties.keys():
if key == 'Version' or key == 'Features':
logging.info('[plover_link] {} = 0x{:04x}'.format(key,
properties[key]))
else:
logging.info('[plover_link] {} = {}'.format(key, properties[key]))
@dbus.service.method('org.bluez.Profile1',
in_signature='o', out_signature='')
def RequestDisconnection(self, path):
logging.info('[plover_link] RequestDisconnection {}'.format(path))
if self.fd > 0:
os.close(self.fd)
self.fd = -1
class BTKbDevice:
"""
Create a bluetooth device to emulate a HID keyboard
"""
# Service control port - must match port configured in SDP record
P_CTRL = 17
# Service interrupt port - must match port configured in SDP record
P_INTR = 19
# BlueZ dbus
PROFILE_DBUS_PATH = '/bluez/yaptb/btkb_profile'
AGENT_DBUS_PATH = '/org/bluez'
ADAPTER_IFACE = 'org.bluez.Adapter1'
DEVICE_INTERFACE = 'org.bluez.Device1'
DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
# file path of the sdp record to laod
install_dir = os.path.dirname(os.path.realpath(__file__))
SDP_RECORD_PATH = os.path.join(install_dir,
'plover_link_btserver_sdp_record.xml')
# UUID for HID service (1124)
# https://www.bluetooth.com/specifications/assigned-numbers/service-discovery
UUID = '00001124-0000-1000-8000-00805f9b34fb'
def __init__(self, hci=0):
self._agent = plugins.loaded['plover_link']._agent
self.bt_autoconnect_list = None
self.bt_last_conn = None
self.autoconnect_in_progress = False
self.bt_agent_running = False
self.scontrol = None
self.ccontrol = None # Socket object for control
self.sinterrupt = None
self.cinterrupt = None # Socket object for interrupt
self.dev_path = '/org/bluez/hci{}'.format(hci)
logging.info('[plover_link] Setting up BT device')
self.bus = dbus.SystemBus()
self.adapter_methods = dbus.Interface(self.bus.get_object('org.bluez',
self.dev_path), self.ADAPTER_IFACE)
self.adapter_property = dbus.Interface(self.bus.get_object('org.bluez',
self.dev_path), self.DBUS_PROP_IFACE)
self.bus.add_signal_receiver(self.interfaces_added,
dbus_interface=self.DBUS_OM_IFACE,
signal_name='InterfacesAdded')
self.bus.add_signal_receiver(self._properties_changed,
dbus_interface=self.DBUS_PROP_IFACE,
signal_name='PropertiesChanged',
arg0=self.DEVICE_INTERFACE,
path_keyword='path')
self.register_hid_profile()
# Set the Bluetooth device configuration
try:
self.alias = plugins.loaded['plover_link'].options['bt_device_name']
except:
self.alias = 'Stenogotchi'
self.discoverabletimeout = 0
self.discoverable = True
self.bthost_mac = None
self.bthost_name = ""
# Get list of Bluetooth devices to auto connect to
bt_autoconnect_str = plugins.loaded['plover_link'].options['bt_autoconnect_mac']
if bt_autoconnect_str:
self.bt_autoconnect_list = list(map(str.strip, bt_autoconnect_str.split(',')))
else:
self.bt_autoconnect_list = None
logging.info('[plover_link] Configured BT device with name {}'.format(self.alias))
def interfaces_added(self, path, device_info):
pass
def _properties_changed(self, interface, changed, invalidated, path):
if self.on_disconnect is not None:
if 'Connected' in changed:
if not changed['Connected']:
self.on_disconnect()
def on_disconnect(self):
if self.autoconnect_in_progress:
return
logging.info('[plover_link] The client has been disconnected')
self.bthost_mac = None
self.bthost_name = ""
self._agent.set_bt_disconnected()
# Attempt to auto_connect once, then go back to listening mode
connected = self.auto_connect()
if not connected:
if not self.bt_agent_running:
self.register_bt_pairing_agent()
if not self.scontrol:
self.listen()
@property
def address(self):
""" Return the adapter MAC address. """
return self.adapter_property.Get(self.ADAPTER_IFACE,
'Address')
@property
def powered(self):
""" Power state of the Adapter. """
return self.adapter_property.Get(self.ADAPTER_IFACE, 'Powered')
@powered.setter
def powered(self, new_state):
self.adapter_property.Set(self.ADAPTER_IFACE, 'Powered', new_state)
@property
def alias(self):
return self.adapter_property.Get(self.ADAPTER_IFACE,
'Alias')
@alias.setter
def alias(self, new_alias):
self.adapter_property.Set(self.ADAPTER_IFACE,
'Alias',
new_alias)
@property
def discoverabletimeout(self):
""" Discoverable timeout of the Adapter. """
return self.adapter_props.Get(self.ADAPTER_IFACE,
'DiscoverableTimeout')
@discoverabletimeout.setter
def discoverabletimeout(self, new_timeout):
self.adapter_property.Set(self.ADAPTER_IFACE,
'DiscoverableTimeout',
dbus.UInt32(new_timeout))
@property
def discoverable(self):
""" Discoverable state of the Adapter. """
return self.adapter_props.Get(
self.ADAPTER_INTERFACE, 'Discoverable')
@discoverable.setter
def discoverable(self, new_state):
self.adapter_property.Set(self.ADAPTER_IFACE,
'Discoverable',
new_state)
def register_hid_profile(self):
"""
Setup and register HID Profile
"""
logging.debug('[plover_link] Configuring Bluez Profile')
service_record = self.read_sdp_service_record()
opts = {
'Role': 'server',
'RequireAuthentication': True,
'RequireAuthorization': True,
'AutoConnect': True,
'ServiceRecord': service_record,
}
manager = dbus.Interface(self.bus.get_object('org.bluez',
'/org/bluez'),
'org.bluez.ProfileManager1')
HumanInterfaceDeviceProfile(self.bus,
BTKbDevice.PROFILE_DBUS_PATH)
manager.RegisterProfile(BTKbDevice.PROFILE_DBUS_PATH,
BTKbDevice.UUID,
opts)
logging.debug('[plover_link] Profile registered ')
def register_bt_pairing_agent(self):
"""
Setup and register BT paring agent
"""
capability = 'NoInputNoOutput'
manager = dbus.Interface(self.bus.get_object('org.bluez',
'/org/bluez'),
'org.bluez.AgentManager1')
Agent(self.bus, BTKbDevice.AGENT_DBUS_PATH)
manager.RegisterAgent(BTKbDevice.AGENT_DBUS_PATH, capability)
#manager.UnregisterAgent(BTKbDevice.AGENT_DBUS_PATH, capability)
manager.RequestDefaultAgent(BTKbDevice.AGENT_DBUS_PATH)
self.bt_agent_running = True
logging.debug(f'[plover_link] Registered secure Bluez pairing agent with capability: {capability}')
@staticmethod
def read_sdp_service_record():
"""
Read and return SDP record from a file
:return: (string) SDP record
"""
logging.debug('[plover_link] Reading service record')
try:
fh = open(BTKbDevice.SDP_RECORD_PATH, 'r')
except OSError:
sys.exit('Could not open the sdp record. Exiting...')
return fh.read()
def create_ssockets(self):
""" Create passive listening sockets and close possibly exising ones """
logging.debug("[plover_link] Creating BT listening ssockets")
if self.scontrol:
self.scontrol.close()
if self.sinterrupt:
self.sinterrupt.close()
self.scontrol = socket.socket(socket.AF_BLUETOOTH,
socket.SOCK_SEQPACKET,
socket.BTPROTO_L2CAP)
self.scontrol.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sinterrupt = socket.socket(socket.AF_BLUETOOTH,
socket.SOCK_SEQPACKET,
socket.BTPROTO_L2CAP)
self.sinterrupt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
def listen(self):
"""
Listen for connections coming from HID client
"""
if not self.scontrol or not self.sinterrupt:
self.create_ssockets()
try:
# Bind address/port to existing sockets
self.scontrol.bind((self.address, self.P_CTRL))
self.sinterrupt.bind((self.address, self.P_INTR))
# Start listening on the server sockets with limit of 1 connection per socket
self.scontrol.listen(1)
self.sinterrupt.listen(1)
logging.info('[plover_link] Waiting for connections')
self.ccontrol, cinfo = self.scontrol.accept()
logging.debug('[plover_link] {} connected on the ccontrol socket'.format(cinfo[0]))
self.cinterrupt, cinfo = self.sinterrupt.accept()
logging.debug('[plover_link] {} connected on the cinterrupt channel'.format(cinfo[0]))
self.bthost_mac = cinfo[0]
self.bthost_name = self.get_connected_device_name()
self._agent.set_bt_connected(self.bthost_name)
self.bt_last_conn = self.bthost_mac
except OSError as ex:
logging.error(f"[plover_link] Failed to enter listening mode: {ex}")
logging.info(f"[plover_link] If issue persists, check that your '/lib/systemd/system/bluetooth.service' file still contains 'ExecStart=/usr/lib/bluetooth/bluetoothd -P input'")
# def reconnect(self):
# print("Trying reconnect...")
# if not self.bt_last_conn:
# return
# logging.info('[plover_link] Waiting 5 seconds before attempting to reconnect to lost BT device...')
# sleep(5)
# for i in range(3):
# try:
# hidHost = self.bt_last_conn
# self.ccontrol = socket.socket(socket.AF_BLUETOOTH,
# socket.SOCK_SEQPACKET,
# socket.BTPROTO_L2CAP)
# self.cinterrupt = socket.socket(socket.AF_BLUETOOTH,
# socket.SOCK_SEQPACKET,
# socket.BTPROTO_L2CAP)
# self.ccontrol.connect((hidHost, self.P_CTRL))
# self.cinterrupt.connect((hidHost, self.P_INTR))
# print("Connected!")
# break
# except Exception as ex:
# logging.info(f"[plover_link] Failed to reconnect, will retry in 5s... Reason: '{ex}'")
# sleep(5)
def auto_connect(self):
""" Automatically connects to preferred BT devices in listed order. Also handles reconnect attempts at lost connection. """
# Check if we should make a reconnect attempt to previous BT device
if self.bt_last_conn:
reconnect = True
else:
reconnect = False
if not self.bt_autoconnect_list and not reconnect:
logging.info('[plover_link] No bt_autoconnect_mac set in config. Listening for incoming connections instead...')
return False
else:
self.autoconnect_in_progress = True
if reconnect:
if not self.bt_last_conn:
return False
logging.info('[plover_link] Waiting 5 seconds before attempting to reconnect to lost BT device...')
sleep(5) # Sleep 5 seconds before attempting a reconnect to lost BT device
autoconnect_list = [self.bt_last_conn]
else:
logging.info('[plover_link] Trying to auto connect to preferred BT host(s)...')
autoconnect_list = self.bt_autoconnect_list.copy()
for i in autoconnect_list:
logging.info(f'[plover_link] Trying to auto connect to {i}')
try:
self.ccontrol = socket.socket(socket.AF_BLUETOOTH,
socket.SOCK_SEQPACKET,
socket.BTPROTO_L2CAP)
self.cinterrupt = socket.socket(socket.AF_BLUETOOTH,
socket.SOCK_SEQPACKET,
socket.BTPROTO_L2CAP)
self.ccontrol.connect((i, self.P_CTRL))
self.cinterrupt.connect((i, self.P_INTR))
# On successful connection
self.autoconnect_in_progress = False
self.bthost_mac = i
self.bt_last_conn = i
self.bthost_name = self.get_connected_device_name()
self._agent.set_bt_connected(self.bthost_name)
return True # stop trying to auto connect upon success
except Exception as e:
if e.__class__.__name__ == "OSError" and str(e) == "[Errno 52] Invalid exchange":
self.unpair_device(i)
logging.info(f'[plover_link] Invalid handshake exchange with {i}. Unpaired device. Please re-initiate pairing from remote device.')
self._agent._view.on_custom(f"Had to purge {i} due to invalid handshake. Please re-pair us.")
elif (e.__class__.__name__ == "PermissionError" and str(e) == "[Errno 13] Permission denied"):
logging.info(f'[plover_link] Permission to connect to {i} denied. Ensure device has been paired.')
self._agent._view.on_custom(f"Hey... {i} refused my connection! Please re-pair us.")
elif (e.__class__.__name__ == "ConnectionRefusedError" and str(e) == "[Errno 111] Connection refused"):
logging.info(f'[plover_link] Connection to {i} refused. Ensure device has been paired and is available.')
self._agent._view.on_custom(f"Hmpf... {i} refused my connection. Ensure device has been paired and is available.")
elif e.__class__.__name__ == "OSError" and str(e) == "[Errno 112] Host is down":
logging.info(f'[plover_link] Host {i} is down.')
else:
logging.info(f'[plover_link] Failed to connect to {i} due to "{e.__class__.__name__}" : "{e}"')
if self.bt_autoconnect_list and not reconnect:
if i in self.bt_autoconnect_list:
self.bt_autoconnect_list.remove(i)
# If all addresses attempted without success
if not self.bt_autoconnect_list or reconnect:
logging.info('[plover_link] Unsuccessful auto connect attempt. Listening for incoming connections instead...')
# self.ccontrol.close()
# self.cinterrupt.close()
self.autoconnect_in_progress = False
self.bt_last_conn = None
return False
sleep(2)
def unpair_device(self, address):
""" Removes remote device including pairing information """
proxy_object = self.bus.get_object("org.bluez","/")
manager = dbus.Interface(proxy_object, "org.freedesktop.DBus.ObjectManager")
managed_objects = manager.GetManagedObjects()
for path in managed_objects:
adr = managed_objects[path].get('org.bluez.Device1', {}).get('Address', False)
if adr == address:
self.adapter_methods.RemoveDevice(path)
def get_connected_device_name(self):
""" Returns name (Alias) of connected BT device """
proxy_object = self.bus.get_object("org.bluez","/")
manager = dbus.Interface(proxy_object, "org.freedesktop.DBus.ObjectManager")
managed_objects = manager.GetManagedObjects()
for path in managed_objects:
con_state = managed_objects[path].get('org.bluez.Device1', {}).get('Connected', False)
if con_state:
addr = managed_objects[path].get('org.bluez.Device1', {}).get('Address')
alias = managed_objects[path].get('org.bluez.Device1', {}).get('Alias')
logging.info(f'[plover_link] Device {alias} [{addr}] is connected')
return alias
def send(self, msg):
"""
Send HID message
:param msg: (bytes) HID packet to send
"""
self.cinterrupt.send(bytes(bytearray(msg)))
class StenogotchiService(dbus.service.Object):
"""
Setup of a D-Bus service to receive:
Status updates and HID messages from Plover plugin.
HID messages from Stenogotchi evdevkb plugin
"""
def __init__(self):
logging.info('[plover_link] Setting up Stenogotchi D-Bus service')
bus_name = dbus.service.BusName('com.github.stenogotchi', bus=dbus.SystemBus())
dbus.service.Object.__init__(self, bus_name, '/com/github/stenogotchi')
self._agent = plugins.loaded['plover_link']._agent
self.device = BTKbDevice() # create and setup our BTKbDevice
self.wpm_top = None
def auto_connect(self):
""" Connect to preferred bt_mac(s). If unspecified or unavailable fall back to await new and trusted incoming connections """
connected = self.device.auto_connect()
if not connected:
self.device.register_bt_pairing_agent() # Handler for new pairing attempts
self.device.listen() # Handler for incoming trusted connections
@dbus.service.method('com.github.stenogotchi', in_signature='aay') # array of bytearrays
def send_keys(self, key_list):
for key in key_list:
self.device.send(key)
@dbus.service.method('com.github.stenogotchi', in_signature='b') # boolean
def plover_is_running(self, b):
logging.debug('[plover_link] plover_is_running = ' + str(b))
if b:
self._agent.set_plover_boot()
else:
self._agent.set_plover_quit()
@dbus.service.method('com.github.stenogotchi', in_signature='b') # boolean
def plover_is_ready(self, b):
logging.debug('[plover_link] plover_is_ready = ' + str(b))
self._agent.set_plover_ready()
@dbus.service.method('com.github.stenogotchi', in_signature='s') # string
def plover_machine_state(self, s):
logging.debug('[plover_link] plover_machine_state = ' + s)
@dbus.service.method('com.github.stenogotchi', in_signature='b') # boolean
def plover_output_enabled(self, b):
logging.debug('[plover_link] plover_output_enabled = ' + str(b))
@dbus.service.method('com.github.stenogotchi', in_signature='s') # string
def plover_wpm_stats(self, s):
logging.debug('[plover_link] plover_wpm_stats = ' + s)
wpm = int(s)
if not self.wpm_top:
self.wpm_top = wpm
self._agent.set_wpm(wpm, self.wpm_top)
else:
if wpm > self.wpm_top:
self.wpm_top = wpm
logging.debug(f'[plover_link] new wpm record: {self.wpm_top}')
self._agent.set_wpm_record(self.wpm_top)
else:
self._agent.set_wpm(wpm, self.wpm_top)
@dbus.service.method('com.github.stenogotchi', in_signature='s') # string
def plover_strokes_stats(self, s):
# logging.debug(f"[plover_link] plover_strokes_stats = '{s}'")
self._agent.set_strokes(s)
@dbus.service.method('com.github.stenogotchi', in_signature='s') # string
def send_string_stenogotchi(self, s):
plugins.loaded['dict_lookup'].input_handler._on_send_string(s)
# logging.debug(f"[plover_link] send_string_stenogotchi = '{s}'")
@dbus.service.method('com.github.stenogotchi', in_signature='n') # 16-bit signed int
def send_backspaces_stenogotchi(self, n):
plugins.loaded['dict_lookup'].input_handler._on_send_backspaces(n)
# logging.debug(f"[plover_link] send_backspaces_stenogotchi = '{n}'")
@dbus.service.method('com.github.stenogotchi', in_signature='s') # string
def send_key_combination_stenogotchi(self, s):
plugins.loaded['dict_lookup'].input_handler._on_send_key_combination(s)
# logging.debug(f"[plover_link] send_key_combination_stenogotchi = '{s}'")
@dbus.service.method('com.github.stenogotchi', in_signature='as') # list of strings
def plover_translation_handler(self, l):
plugins.loaded['dict_lookup'].input_handler._on_lookup_results(l)
# logging.debug(f"[plover_link] plover_translation_handler = '{l}'")
@dbus.service.signal('com.github.stenogotchi', signature='a{sv}') # dictionary of strings to variants
def signal_to_plover(self, message):
# The signal is emitted when this method exits
pass
class PloverLink(ObjectClass):
__autohor__ = 'Anodynous'
__version__ = '0.3'
__license__ = 'MIT'
__description__ = 'This plugin enables connectivity to Plover through D-Bus. Note that it needs root permissions due to using sockets'
def __init__(self):
self._agent = None
self.running = False
self._stenogotchiservice = None
self.mainloop = None
# called when everything is ready and the main loop is about to start
def on_ready(self, agent):
self._agent = agent # used for agent/automata functionsadded to be able to do callbacks to agent events
DBusGMainLoop(set_as_default=True)
self._stenogotchiservice = StenogotchiService()
self.mainloop = GLib.MainLoop()
try:
self.mainloop.run()
self.running = True
logging.info("[plover_link] PloverLink is up")
except:
logging.error("[plover_link] Could not start PloverLink")
def on_plover_ready(self, agent):
self._stenogotchiservice.auto_connect()
def on_config_changed(self, config):
self.config = config
def on_unload(self, ui):
self.mainloop.quit()
def send_signal_to_plover(self, message):
self._stenogotchiservice.signal_to_plover(message)
if __name__ == '__main__':
# The sockets require root permission
if not os.geteuid() == 0:
sys.exit('Only root can run this script')
DBusGMainLoop(set_as_default=True)
stenogotchiservice = StenogotchiService()
mainloop = GLib.MainLoop()
mainloop.run()
================================================
FILE: stenogotchi/plugins/default/plover_link_btserver_sdp_record.xml
================================================
================================================
FILE: stenogotchi/plugins/default/upslite.py
================================================
#!/usr/bin/env python3
"""
Based on: https://github.com/linshuqin329/UPS-Lite
Requires i2c to be enabled in dietpi-config (or raspi-config)
Supports readings from the https://hackaday.io/project/173847-ups-lite platform for Raspberry Pi Zero W
"""
import logging
import struct
import smbus
import sys
import time
import RPi.GPIO as GPIO
import stenogotchi
if not __name__ == '__main__':
import stenogotchi.plugins as plugins
ObjectClass = plugins.Plugin
else:
ObjectClass = object
I2CBUS = 1 # Run "sudo i2cdetect -l" to see which bus is being used (bcm2835 is what you are looking for). 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
I2CADDRESS = 0x36 # Run "sudo i2cdetect -y 1" to see address 36 mounted on the i2c bus. This is MAXI17040G which we want to interact with
class Upslite(ObjectClass):
__autohor__ = 'Anodynous'
__version__ = '0.1'
__license__ = 'GPL3'
__description__ = 'This plugin enables battery readings for the UPS-Lite V1.2 RPI0 module using integrated MAX17040'
def __init__(self):
self.GPIO = GPIO
self.bus = None
self.address = I2CADDRESS
self.is_plugged = False
self.voltage = 0
self.charge = 0
# Called when plugin is loaded
def on_loaded(self):
try:
self.GPIO.setmode(self.GPIO.BCM)
self.GPIO.setwarnings(False)
self.GPIO.setup(4,self.GPIO.IN)
self.bus = smbus.SMBus(I2CBUS)
self._power_on_reset()
self._quickstart()
except:
logging.error("[upslite] Could not start UPS-Lite plugin")
def on_config_changed(self, config):
self.config = config
# Called when the ui is updated
def on_ui_update(self, ui):
# update those elements
self._read_charge()
self._check_plugged()
# Set battery reading
ui_string = str(self.charge)
if self.is_plugged:
ui_string += "+"
ui.set('ups', ui_string)
# Check for critical level. Initiate shutdown if too low
if not self.is_plugged:
if self.charge <= self.options['shutdown_level']:
logging.info(f'[upslite] Battery charge critical: {self.charge}')
ui.update(force=True, new_data={'status': 'Battery level critical. Shutting down in 1m unless connected to charger...'})
time.sleep(60)
self._check_plugged()
if not self.is_plugged:
logging.info('[upslite] Shutting down')
stenogotchi.shutdown()
else:
logging.info('[upslite] Battery charging. Aborting shutdown process')
ui.update(force=True, new_data={'status': 'Pheew... That was a close one! Feeling better already.'})
def _read_voltage(self):
""" Reads and sets as a float the voltage from the Raspi UPS Hat via the provided SMBus object"""
read = self.bus.read_word_data(self.address, 0X02)
swapped = struct.unpack("H", read))[0]
voltage = swapped * 1.25 /1000/16
if voltage > 0: # if we get a non-zero value
self.voltage = round(5.2 % voltage, 2)
def _read_charge(self):
""" Reads and sets as an int the remaining charge of the battery connected to the Raspi UPS Hat via the provided SMBus object. """
self.is_full = False
self.is_low = False
self.is_critical = False
read = self.bus.read_word_data(self.address, 0X04)
swapped = struct.unpack("H", read))[0]
charge = swapped/256
if charge > 100:
charge = 100
self.charge = round(charge)
def _check_plugged(self):
if (self.GPIO.input(4) == self.GPIO.HIGH):
self.is_plugged = True
elif (self.GPIO.input(4) == self.GPIO.LOW):
self.is_plugged = False
def _quickstart(self):
self.bus.write_word_data(self.address, 0x06,0x4000)
def _power_on_reset(self):
self.bus.write_word_data(self.address, 0xfe,0x0054)
def get_charge(self):
self._read_charge()
return self.charge
def get_voltage(self):
self._read_voltage()
return self.voltage
def get_is_plugged(self):
self._check_plugged()
return self.is_plugged
if __name__ == '__main__':
ups = Upslite()
ups.on_loaded()
time.sleep(1) # needs a second to ensure non-zero values are returned on initial read
print("++++++++++++++++++++")
while True:
voltage = ups.get_voltage()
charge = ups.get_charge()
is_plugged = ups.get_is_plugged()
print("Voltage:%5.2fV" % voltage)
print("Battery:%5i%%" % charge)
if charge > 99:
print("Battery FULL")
else:
if charge < 2:
print("Battery CRITICAL")
elif charge < 6:
print("Battery LOW")
if is_plugged:
print("Power Adapter Plugged In ")
else:
print("Power Adapter Unplugged")
print("++++++++++++++++++++")
time.sleep(5)
================================================
FILE: stenogotchi/ui/__init__.py
================================================
================================================
FILE: stenogotchi/ui/components.py
================================================
from PIL import Image
from textwrap import TextWrapper
class Widget(object):
def __init__(self, xy, color=0):
self.xy = xy
self.color = color
def draw(self, canvas, drawer):
raise Exception("not implemented")
class Bitmap(Widget):
def __init__(self, path, xy, color=0):
super().__init__(xy, color)
self.image = Image.open(path)
def draw(self, canvas, drawer):
canvas.paste(self.image, self.xy)
class Line(Widget):
def __init__(self, xy, color=0, width=1):
super().__init__(xy, color)
self.width = width
def draw(self, canvas, drawer):
drawer.line(self.xy, fill=self.color, width=self.width)
class Rect(Widget):
def draw(self, canvas, drawer):
drawer.rectangle(self.xy, outline=self.color)
class FilledRect(Widget):
def draw(self, canvas, drawer):
drawer.rectangle(self.xy, fill=self.color)
class Text(Widget):
def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0):
super().__init__(position, color)
self.value = value
self.font = font
self.wrap = wrap
self.max_length = max_length
self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None
def draw(self, canvas, drawer):
if self.value is not None:
if self.wrap:
text = '\n'.join(self.wrapper.wrap(self.value))
else:
text = self.value
drawer.text(self.xy, text, font=self.font, fill=self.color)
class LabeledValue(Widget):
def __init__(self, label, value="", position=(0, 0), label_font=None, text_font=None, color=0, label_spacing=5, max_length=0):
super().__init__(position, color)
self.label = label
self.label_font = label_font
self.max_length = max_length
self.value = value
self.text_font = text_font
self.label_spacing = label_spacing
def draw(self, canvas, drawer):
if self.max_length > 0:
value = self.value[:self.max_length]
else:
value = self.value
if self.label is None:
drawer.text(self.xy, value, font=self.label_font, fill=self.color)
else:
pos = self.xy
drawer.text(pos, self.label, font=self.label_font, fill=self.color)
drawer.text((pos[0] + self.label_spacing + 5 * len(self.label), pos[1]), value, font=self.text_font, fill=self.color)
================================================
FILE: stenogotchi/ui/display.py
================================================
import os
import logging
import threading
import stenogotchi.plugins as plugins
import stenogotchi.ui.hw as hw
from stenogotchi.ui.view import View
class Display(View):
def __init__(self, config, state={}):
super(Display, self).__init__(config, hw.display_for(config), state)
config = config['ui']['display']
self._enabled = config['enabled']
self._rotation = config['rotation']
self.init_display()
self._canvas_next_event = threading.Event()
self._canvas_next = None
self._render_thread_instance = threading.Thread(
target=self._render_thread,
daemon=True
)
self._render_thread_instance.start()
def is_waveshare_v2(self):
return self._implementation.name == 'waveshare_2'
def init_display(self):
if self._enabled:
self._implementation.initialize()
plugins.on('display_setup', self._implementation)
else:
logging.warning("display module is disabled")
self.on_render(self._on_view_rendered)
def clear(self):
self._implementation.clear()
def image(self):
img = None
if self._canvas is not None:
img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation)
return img
def _render_thread(self):
"""Used for non-blocking screen updating."""
while True:
self._canvas_next_event.wait()
self._canvas_next_event.clear()
self._implementation.render(self._canvas_next)
def _on_view_rendered(self, img):
try:
if self._config['ui']['web']['on_frame'] != '':
os.system(self._config['ui']['web']['on_frame'])
except Exception as e:
logging.error("%s" % e)
if self._enabled:
self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
if self._implementation is not None:
self._canvas_next = self._canvas
self._canvas_next_event.set()
================================================
FILE: stenogotchi/ui/faces.py
================================================
LOOK_R = '( ⚆_⚆)'
LOOK_L = '(☉_☉ )'
LOOK_R_HAPPY = '( ◕‿◕)'
LOOK_L_HAPPY = '(◕‿◕ )'
SLEEP = '(⇀‿‿↼)'
SLEEP2 = '(≖‿‿≖)'
AWAKE = '(◕‿‿◕)'
BORED = '(-__-)'
INTENSE = '(°▃▃°)'
COOL = '(⌐■_■)'
HAPPY = '(•‿‿•)'
GRATEFUL = '(^‿‿^)'
EXCITED = '(ᵔ◡◡ᵔ)'
MOTIVATED = '(☼‿‿☼)'
DEMOTIVATED = '(≖__≖)'
SMART = '(✜‿‿✜)'
LONELY = '(ب__ب)'
SAD = '(╥☁╥ )'
ANGRY = "(-_-')"
FRIEND = '(♥‿‿♥)'
BROKEN = '(☓‿‿☓)'
DEBUG = '(#__#)'
PROCESS_1 = '(1__0)'
PROCESS_2 = '(1__1)'
PROCESS_3 = '(0__1)'
PROCESS_4 = '(0__0)'
def load_from_config(config):
for face_name, face_value in config.items():
globals()[face_name.upper()] = face_value
================================================
FILE: stenogotchi/ui/fonts.py
================================================
from PIL import ImageFont
# should not be changed
FONT_NAME = 'DejaVuSansMono'
# can be changed
STATUS_FONT_NAME = None
SIZE_OFFSET = 0
Bold = None
BoldSmall = None
BoldBig = None
Medium = None
Small = None
Huge = None
def init(config):
global STATUS_FONT_NAME, SIZE_OFFSET
STATUS_FONT_NAME = config['ui']['font']['name']
SIZE_OFFSET = config['ui']['font']['size_offset']
setup(10, 9, 10, 35, 25, 9)
def status_font(old_font):
global STATUS_FONT_NAME, SIZE_OFFSET
return ImageFont.truetype(STATUS_FONT_NAME, size=old_font.size + SIZE_OFFSET)
def setup(bold, bold_small, medium, huge, bold_big, small):
global Bold, BoldSmall, Medium, Huge, BoldBig, Small, FONT_NAME
Small = ImageFont.truetype(FONT_NAME, small)
Medium = ImageFont.truetype(FONT_NAME, medium)
BoldSmall = ImageFont.truetype("%s-Bold" % FONT_NAME, bold_small)
Bold = ImageFont.truetype("%s-Bold" % FONT_NAME, bold)
BoldBig = ImageFont.truetype("%s-Bold" % FONT_NAME, bold_big)
Huge = ImageFont.truetype("%s-Bold" % FONT_NAME, huge)
================================================
FILE: stenogotchi/ui/hw/__init__.py
================================================
from stenogotchi.ui.hw.waveshare2 import WaveshareV2
def display_for(config):
# config has been normalized already in utils.load_config
if config['ui']['display']['type'] == 'waveshare_2':
return WaveshareV2(config)
else:
print("no display specified")
================================================
FILE: stenogotchi/ui/hw/base.py
================================================
import stenogotchi.ui.fonts as fonts
class DisplayImpl(object):
def __init__(self, config, name):
self.name = name
self.config = config['ui']['display']
self._layout = {
'width': 0,
'height': 0,
'face': (0, 0),
'name': (0, 0),
'ups': (0, 0),
'wpm': (0, 0),
'strokes': (0, 0),
'uptime': (0, 0),
'line1': (0, 0),
'line2': (0, 0),
'friend_face': (0, 0),
'friend_name': (0, 0),
'bthost': {
'pos': (0, 0),
'max': 20
},
'wifi': {
'pos': (0, 0),
'max': 20
},
'mode': (0, 0),
# status is special :D
'status': {
'pos': (0, 0),
'font': fonts.status_font(fonts.Medium),
'max': 20
}
}
def layout(self):
raise NotImplementedError
def initialize(self):
raise NotImplementedError
def render(self, canvas):
raise NotImplementedError
def clear(self):
raise NotImplementedError
================================================
FILE: stenogotchi/ui/hw/libs/__init__.py
================================================
================================================
FILE: stenogotchi/ui/hw/libs/waveshare/__init__.py
================================================
================================================
FILE: stenogotchi/ui/hw/libs/waveshare/v2/__init__.py
================================================
================================================
FILE: stenogotchi/ui/hw/libs/waveshare/v2/epd2in13_V2.py
================================================
# //*****************************************************************************
# * | File : epd2in13.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V3.0
# * | Date : 2018-11-01
# * | Info : python2 demo
# * 1.Remove:
# digital_write(self, pin, value)
# digital_read(self, pin)
# delay_ms(self, delaytime)
# set_lut(self, lut)
# self.lut = self.lut_full_update
# * 2.Change:
# display_frame -> TurnOnDisplay
# set_memory_area -> SetWindow
# set_memory_pointer -> SetCursor
# * 3.How to use
# epd = epd2in13.EPD()
# epd.init(epd.lut_full_update)
# image = Image.new('1', (epd2in13.EPD_WIDTH, epd2in13.EPD_HEIGHT), 255)
# ...
# drawing ......
# ...
# epd.display(getbuffer(image))
# ******************************************************************************//
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and//or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import time
import spidev
import RPi.GPIO as GPIO
from PIL import Image
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
# SPI device, bus = 0, device = 0
SPI = spidev.SpiDev(0, 0)
def digital_write(pin, value):
GPIO.output(pin, value)
def digital_read(pin):
return GPIO.input(BUSY_PIN)
def delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(data):
SPI.writebytes(data)
def module_init():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BUSY_PIN, GPIO.IN)
SPI.max_speed_hz = 2000000
SPI.mode = 0b00
return 0;
# Display resolution
EPD_WIDTH = 122
EPD_HEIGHT = 250
class EPD:
def __init__(self):
self.reset_pin = RST_PIN
self.dc_pin = DC_PIN
self.busy_pin = BUSY_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
FULL_UPDATE = 0
PART_UPDATE = 1
lut_full_update = [
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, # LUT0: BB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, # LUT1: BW: VS 0 ~7
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, # LUT2: WB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, # LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT4: VCOM: VS 0 ~7
0x03, 0x03, 0x00, 0x00, 0x02, # TP0 A~D RP0
0x09, 0x09, 0x00, 0x00, 0x02, # TP1 A~D RP1
0x03, 0x03, 0x00, 0x00, 0x02, # TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, # TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, # TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, # TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, # TP6 A~D RP6
0x15, 0x41, 0xA8, 0x32, 0x30, 0x0A,
]
lut_partial_update = [ # 20 bytes
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT0: BB: VS 0 ~7
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT1: BW: VS 0 ~7
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT2: WB: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT4: VCOM: VS 0 ~7
0x0A, 0x00, 0x00, 0x00, 0x00, # TP0 A~D RP0
0x00, 0x00, 0x00, 0x00, 0x00, # TP1 A~D RP1
0x00, 0x00, 0x00, 0x00, 0x00, # TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, # TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, # TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, # TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, # TP6 A~D RP6
0x15, 0x41, 0xA8, 0x32, 0x30, 0x0A,
]
# Hardware reset
def reset(self):
digital_write(self.reset_pin, GPIO.HIGH)
delay_ms(200)
digital_write(self.reset_pin, GPIO.LOW) # module reset
delay_ms(200)
digital_write(self.reset_pin, GPIO.HIGH)
delay_ms(200)
def send_command(self, command):
digital_write(self.dc_pin, GPIO.LOW)
spi_writebyte([command])
def send_data(self, data):
digital_write(self.dc_pin, GPIO.HIGH)
spi_writebyte([data])
def wait_until_idle(self):
while (digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
delay_ms(100)
def TurnOnDisplay(self):
self.send_command(0x22)
self.send_data(0xC7)
self.send_command(0x20)
self.wait_until_idle()
def init(self, update):
if (module_init() != 0):
return -1
# EPD hardware init start
self.reset()
if (update == self.FULL_UPDATE):
self.wait_until_idle()
self.send_command(0x12) # soft reset
self.wait_until_idle()
self.send_command(0x74) # set analog block control
self.send_data(0x54)
self.send_command(0x7E) # set digital block control
self.send_data(0x3B)
self.send_command(0x01) # Driver output control
self.send_data(0xF9)
self.send_data(0x00)
self.send_data(0x00)
self.send_command(0x11) # data entry mode
self.send_data(0x01)
self.send_command(0x44) # set Ram-X address start//end position
self.send_data(0x00)
self.send_data(0x0F) # 0x0C-->(15+1)*8=128
self.send_command(0x45) # set Ram-Y address start//end position
self.send_data(0xF9) # 0xF9-->(249+1)=250
self.send_data(0x00)
self.send_data(0x00)
self.send_data(0x00)
self.send_command(0x3C) # BorderWavefrom
self.send_data(0x03)
self.send_command(0x2C) # VCOM Voltage
self.send_data(0x55) #
self.send_command(0x03)
self.send_data(self.lut_full_update[70])
self.send_command(0x04) #
self.send_data(self.lut_full_update[71])
self.send_data(self.lut_full_update[72])
self.send_data(self.lut_full_update[73])
self.send_command(0x3A) # Dummy Line
self.send_data(self.lut_full_update[74])
self.send_command(0x3B) # Gate time
self.send_data(self.lut_full_update[75])
self.send_command(0x32)
for count in range(70):
self.send_data(self.lut_full_update[count])
self.send_command(0x4E) # set RAM x address count to 0
self.send_data(0x00)
self.send_command(0x4F) # set RAM y address count to 0X127
self.send_data(0xF9)
self.send_data(0x00)
self.wait_until_idle()
else:
self.send_command(0x2C) # VCOM Voltage
self.send_data(0x26)
self.wait_until_idle()
self.send_command(0x32)
for count in range(70):
self.send_data(self.lut_partial_update[count])
self.send_command(0x37)
self.send_data(0x00)
self.send_data(0x00)
self.send_data(0x00)
self.send_data(0x00)
self.send_data(0x40)
self.send_data(0x00)
self.send_data(0x00)
self.send_command(0x22)
self.send_data(0xC0)
self.send_command(0x20)
self.wait_until_idle()
self.send_command(0x3C) # BorderWavefrom
self.send_data(0x01)
return 0
def getbuffer(self, image):
if self.width % 8 == 0:
linewidth = self.width // 8
else:
linewidth = self.width // 8 + 1
buf = [0xFF] * (linewidth * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
if (imwidth == self.width and imheight == self.height):
# print("Vertical")
for y in range(imheight):
for x in range(imwidth):
if pixels[x, y] == 0:
x = imwidth - x
buf[x // 8 + y * linewidth] &= ~(0x80 >> (x % 8))
elif (imwidth == self.height and imheight == self.width):
# print("Horizontal")
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
newy = imwidth - newy - 1
buf[newx // 8 + newy * linewidth] &= ~(0x80 >> (y % 8))
return buf
def display(self, image):
if self.width % 8 == 0:
linewidth = self.width // 8
else:
linewidth = self.width // 8 + 1
self.send_command(0x24)
for j in range(0, self.height):
for i in range(0, linewidth):
self.send_data(image[i + j * linewidth])
self.TurnOnDisplay()
def displayPartial(self, image):
if self.width % 8 == 0:
linewidth = self.width // 8
else:
linewidth = self.width // 8 + 1
self.send_command(0x24)
for j in range(0, self.height):
for i in range(0, linewidth):
self.send_data(image[i + j * linewidth])
self.send_command(0x26)
for j in range(0, self.height):
for i in range(0, linewidth):
self.send_data(~image[i + j * linewidth])
self.TurnOnDisplay()
def Clear(self, color):
if self.width % 8 == 0:
linewidth = self.width // 8
else:
linewidth = self.width // 8 + 1
# print(linewidth)
self.send_command(0x24)
for j in range(0, self.height):
for i in range(0, linewidth):
self.send_data(color)
self.TurnOnDisplay()
def sleep(self):
self.send_command(0x22) # POWER OFF
self.send_data(0xC3)
self.send_command(0x20)
self.send_command(0x10) # enter deep sleep
self.send_data(0x01)
delay_ms(100)
### END OF FILE ###
================================================
FILE: stenogotchi/ui/hw/waveshare2.py
================================================
import logging
import stenogotchi.ui.fonts as fonts
from stenogotchi.ui.hw.base import DisplayImpl
class WaveshareV2(DisplayImpl):
def __init__(self, config):
super(WaveshareV2, self).__init__(config, 'waveshare_2')
self._display = None
def layout(self):
if self.config['color'] == 'black':
fonts.setup(10, 9, 10, 35, 25, 9)
self._layout['width'] = 250
self._layout['height'] = 122
self._layout['face'] = (0, 40)
self._layout['name'] = (5, 20)
self._layout['ups'] = (0, 0)
self._layout['wpm'] = {
'pos': (50, 0),
'max': 9
}
self._layout['strokes'] = {
'pos': (128, 0),
'max': 5
}
self._layout['uptime'] = (185, 0)
self._layout['line1'] = [0, 14, 250, 14]
self._layout['line2'] = [0, 108, 250, 108]
self._layout['friend_face'] = (0, 92)
self._layout['friend_name'] = (40, 94)
self._layout['bthost'] = {
'pos': (0, 109),
'max': 15
}
self._layout['wifi'] = {
'pos': (113, 109),
'max': 12
}
self._layout['mode'] = (210, 109)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.status_font(fonts.Medium),
'max': 20
}
else: # these are not correctly configured based on different width and height of color display
fonts.setup(10, 9, 10, 35, 25, 9)
self._layout['width'] = 212
self._layout['height'] = 104
self._layout['face'] = (0, 26)
self._layout['name'] = (5, 15)
self._layout['ups'] = (0, 0)
self._layout['wpm'] = {
'pos': (50, 0),
'max': 9
}
self._layout['strokes'] = {
'pos': (128, 0),
'max': 5
}
self._layout['status'] = (91, 15)
self._layout['uptime'] = (147, 0)
self._layout['line1'] = [0, 12, 212, 12]
self._layout['line2'] = [0, 92, 212, 92]
self._layout['friend_face'] = (0, 76)
self._layout['friend_name'] = (40, 78)
self._layout['bthost'] = {
'pos': (0, 93),
'max': 15
}
self._layout['wifi'] = {
'pos': (113, 109),
'max': 12
}
self._layout['mode'] = (187, 93)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.status_font(fonts.Medium),
'max': 14
}
return self._layout
def initialize(self):
logging.info("initializing waveshare v2 display")
from stenogotchi.ui.hw.libs.waveshare.v2.epd2in13_V2 import EPD
self._display = EPD()
self._display.init(self._display.FULL_UPDATE)
self._display.Clear(0xff)
self._display.init(self._display.PART_UPDATE)
def render(self, canvas):
buf = self._display.getbuffer(canvas)
self._display.displayPartial(buf)
def clear(self):
self._display.Clear(0xff)
================================================
FILE: stenogotchi/ui/state.py
================================================
from threading import Lock
class State(object):
def __init__(self, state={}):
self._state = state
self._lock = Lock()
self._listeners = {}
self._changes = {}
def add_element(self, key, elem):
self._state[key] = elem
self._changes[key] = True
def has_element(self, key):
return key in self._state
def remove_element(self, key):
del self._state[key]
self._changes[key] = True
def add_listener(self, key, cb):
with self._lock:
self._listeners[key] = cb
def items(self):
with self._lock:
return self._state.items()
def get(self, key):
with self._lock:
return self._state[key].value if key in self._state else None
def reset(self):
with self._lock:
self._changes = {}
def changes(self, ignore=()):
with self._lock:
changes = []
for change in self._changes.keys():
if change not in ignore:
changes.append(change)
return changes
def has_changes(self):
with self._lock:
return len(self._changes) > 0
def set(self, key, value):
with self._lock:
if key in self._state:
prev = self._state[key].value
self._state[key].value = value
if prev != value:
self._changes[key] = True
if key in self._listeners and self._listeners[key] is not None:
self._listeners[key](prev, value)
================================================
FILE: stenogotchi/ui/view.py
================================================
import _thread
from threading import Lock
import time
import logging
import random
from PIL import ImageDraw
import stenogotchi
import stenogotchi.utils as utils
import stenogotchi.plugins as plugins
from stenogotchi.voice import Voice
import stenogotchi.ui.web as web
import stenogotchi.ui.fonts as fonts
import stenogotchi.ui.faces as faces
from stenogotchi.ui.components import *
from stenogotchi.ui.state import State
WHITE = 0xff
BLACK = 0x00
ROOT = None
class View(object):
def __init__(self, config, impl, state=None):
global ROOT
# setup faces from the configuration in case the user customized them
faces.load_from_config(config['ui']['faces'])
self._agent = None
self._render_cbs = []
self._config = config
self._canvas = None
self._frozen = False
self._lock = Lock()
self._voice = Voice(lang=config['main']['lang'])
self._implementation = impl
self._layout = impl.layout()
self._width = self._layout['width']
self._height = self._layout['height']
self._state = State(state={
'ups': LabeledValue(color=BLACK, label='BAT', value='', position=self._layout['ups'],
label_font=fonts.Bold,text_font=fonts.Medium),
'wpm': LabeledValue(color=BLACK, label='WPM', value='', position=self._layout['wpm']['pos'],
label_font=fonts.Bold, text_font=fonts.Medium, max_length=self._layout['wpm']['max']),
'strokes': LabeledValue(color=BLACK, label='STR', value='', position=self._layout['strokes']['pos'],
label_font=fonts.Bold, text_font=fonts.Medium, max_length=self._layout['strokes']['max']),
'uptime': LabeledValue(color=BLACK, label='UP', value='', position=self._layout['uptime'],
label_font=fonts.Bold, text_font=fonts.Medium),
'line1': Line(self._layout['line1'], color=BLACK),
'line2': Line(self._layout['line2'], color=BLACK),
'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge),
'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall, color=BLACK),
'name': Text(value='%s>' % 'stenogotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
'status': Text(value=self._voice.default(),
position=self._layout['status']['pos'],
color=BLACK,
font=self._layout['status']['font'],
wrap=True,
# the current maximum number of characters per line, assuming each character is 6 pixels wide
max_length=self._layout['status']['max']),
'bthost': LabeledValue(label='BT', value='', color=BLACK,
position=self._layout['bthost']['pos'], label_font=fonts.Bold,
text_font=fonts.Medium, max_length=self._layout['bthost']['max']),
'wifi': LabeledValue(label='WIFI', value='', color=BLACK,
position=self._layout['wifi']['pos'], label_font=fonts.Bold,
text_font=fonts.Medium, max_length=self._layout['wifi']['max']),
'mode': Text(value='NONE', position=self._layout['mode'],
font=fonts.Bold, color=BLACK),
})
if state:
for key, value in state.items():
self._state.set(key, value)
plugins.on('ui_setup', self)
if config['ui']['fps'] > 0.0:
_thread.start_new_thread(self._refresh_handler, ())
self._ignore_changes = ()
else:
logging.warning("ui.fps is 0, the display will only update for major changes")
self._ignore_changes = ('uptime', 'name')
ROOT = self
def set_agent(self, agent):
self._agent = agent
def has_element(self, key):
self._state.has_element(key)
def add_element(self, key, elem):
self._state.add_element(key, elem)
def remove_element(self, key):
self._state.remove_element(key)
def width(self):
return self._width
def height(self):
return self._height
def on_state_change(self, key, cb):
self._state.add_listener(key, cb)
def on_render(self, cb):
if cb not in self._render_cbs:
self._render_cbs.append(cb)
def _refresh_handler(self):
delay = 1.0 / self._config['ui']['fps']
while True:
try:
name = self._state.get('name')
self.set('name', name.rstrip('█').strip() if '█' in name else (name + ' █'))
self.update()
except Exception as e:
logging.warning("non fatal error while updating view: %s" % e)
time.sleep(delay)
def set(self, key, value):
self._state.set(key, value)
def get(self, key):
return self._state.get(key)
def on_starting(self):
self.set('status', self._voice.on_starting() + ("\n(v%s)" % stenogotchi.__version__))
self.set('face', faces.AWAKE)
self.update()
def on_manual_mode(self, last_session):
self.set('mode', 'MANU')
self.set('face', faces.HAPPY)
self.set('status', self._voice.on_last_session_data(last_session))
self.set('uptime', last_session.duration)
self.update()
def is_normal(self):
return self._state.get('face') not in (
faces.INTENSE,
faces.COOL,
faces.BORED,
faces.HAPPY,
faces.EXCITED,
faces.MOTIVATED,
faces.DEMOTIVATED,
faces.SMART,
faces.SAD,
faces.LONELY)
def on_keys_generation(self):
self.set('face', faces.AWAKE)
self.set('status', self._voice.on_keys_generation())
self.update()
def on_normal(self):
self.set('face', faces.AWAKE)
self.set('status', self._voice.on_normal())
self.update()
def on_reading_logs(self, lines_so_far=0):
self.set('face', faces.SMART)
self.set('status', self._voice.on_reading_logs(lines_so_far))
self.update()
def wait(self, secs, sleeping=True):
was_normal = self.is_normal()
part = secs / 10.0
for step in range(0, 10):
# if we weren't in a normal state before going
# to sleep, keep that face and status on for
# a while, otherwise the sleep animation will
# always override any minor state change before it
if was_normal or step > 5:
if sleeping:
if secs > 1:
self.set('face', faces.SLEEP)
self.set('status', self._voice.on_napping(int(secs)))
else:
self.set('face', faces.SLEEP2)
self.set('status', self._voice.on_awakening())
else:
self.set('status', self._voice.on_waiting(int(secs)))
good_mood = self._agent.in_good_mood()
if step % 2 == 0:
self.set('face', faces.LOOK_R_HAPPY if good_mood else faces.LOOK_R)
else:
self.set('face', faces.LOOK_L_HAPPY if good_mood else faces.LOOK_L)
time.sleep(part)
secs -= part
self.on_normal()
def on_shutdown(self):
face = random.choices((faces.SLEEP, faces.SLEEP2), weights=[0.7, 0.3], k=1)
self.set('face', face[0])
self.set('status', self._voice.on_shutdown())
self.update(force=True)
self._frozen = True
def on_bored(self):
self.set('face', faces.BORED)
self.set('status', self._voice.on_bored())
self.update()
def on_sad(self):
self.set('face', faces.SAD)
self.set('status', self._voice.on_sad())
self.update()
def on_angry(self):
self.set('face', faces.ANGRY)
self.set('status', self._voice.on_angry())
self.update()
def on_motivated(self, reward):
self.set('face', faces.MOTIVATED)
self.set('status', self._voice.on_motivated(reward))
self.update()
def on_demotivated(self, reward):
self.set('face', faces.DEMOTIVATED)
self.set('status', self._voice.on_demotivated(reward))
self.update()
def on_excited(self):
self.set('face', faces.EXCITED)
self.set('status', self._voice.on_excited())
self.update()
def on_miss(self, who):
self.set('face', faces.SAD)
self.set('status', self._voice.on_miss(who))
self.update()
def on_grateful(self):
self.set('face', faces.GRATEFUL)
self.set('status', self._voice.on_grateful())
self.update()
def on_lonely(self):
self.set('face', faces.LONELY)
self.set('status', self._voice.on_lonely())
self.update()
def on_processing(self):
start_face = self._state.get('face')
processing_faces = [faces.PROCESS_1, faces.PROCESS_2, faces.PROCESS_3, faces.PROCESS_4]
for face in processing_faces:
self.set('face', face)
self.update()
time.sleep(2)
self.set('face', start_face)
self.update()
def on_rebooting(self):
self.set('face', faces.BROKEN)
self.set('status', self._voice.on_rebooting())
self.update()
def on_custom(self, text):
self.set('face', faces.DEBUG)
self.set('status', self._voice.custom(text))
self.update()
def on_plover_boot(self):
face = random.choices([faces.SLEEP, faces.SLEEP2, faces.BORED], weights=[0.6, 0.3, 0.1], k=1)
self.set('face', face[0])
self.set('status', self._voice.on_plover_boot())
self.update()
def on_plover_ready(self):
face = random.choices([faces.AWAKE, faces.LOOK_R_HAPPY, faces.LOOK_L_HAPPY, faces.EXCITED, faces.GRATEFUL], weights=[0.5, 0.2, 0.2, 0.05, 0.05], k=1)
self.set('face', face[0])
self.set('status', self._voice.on_plover_ready())
if self._state.get('mode') == 'NONE':
self.set('mode', 'STENO')
self.update()
def on_plover_quit(self):
face = random.choice((faces.BROKEN, faces.DEBUG))
self.set('face', face)
self.set('status', 'Uh-oh... I think Plover just quit on us')
if self._state.get('mode') == 'STENO':
self.set('mode', 'NONE')
self.update()
def on_wpm(self, wpm):
self.set('wpm', wpm)
self.update()
def on_strokes(self, strokes):
self.set('strokes', strokes)
self.update()
def on_wpm_record(self, wpm_top):
if wpm_top < 100:
self.set('face', faces.LOOK_R_HAPPY)
elif wpm_top < 200:
self.set('face', faces.EXCITED)
else:
self.set('face', faces.INTENSE)
self.set('status', self._voice.on_wpm_record(wpm_top))
# Don't trigger self.update as we always will call set_wpm() afterwards.
def on_bt_connected(self, bthost_name):
self.set('face', faces.LOOK_L_HAPPY)
self.set('status', self._voice.on_bt_connected(bthost_name))
self.update()
def on_bt_disconnected(self):
self.set('face', faces.INTENSE)
self.set('status', self._voice.on_bt_disconnected())
self.update()
def on_wifi_connected(self, ssid, ip):
self.set('face', faces.COOL)
self.set('status', self._voice.on_wifi_connected(ssid, ip))
self.update()
def on_wifi_disconnected(self):
self.set('face', faces.LONELY)
self.set('status', self._voice.on_wifi_disconnected())
self.update()
def on_dict_lookup_done(self):
face = random.choices([faces.AWAKE, faces.LOOK_R_HAPPY, faces.LOOK_L_HAPPY, faces.EXCITED, faces.GRATEFUL], weights=[0.5, 0.2, 0.2, 0.05, 0.05], k=1)
self.set('face', face[0])
self.set('status', self._voice.on_dict_lookup_done())
self.update()
def update(self, force=False, new_data={}):
for key, val in new_data.items():
self.set(key, val)
with self._lock:
if self._frozen:
return
state = self._state
changes = state.changes(ignore=self._ignore_changes)
if force or len(changes):
self._canvas = Image.new('1', (self._width, self._height), WHITE)
drawer = ImageDraw.Draw(self._canvas)
plugins.on('ui_update', self)
for key, lv in state.items():
lv.draw(self._canvas, drawer)
web.update_frame(self._canvas)
for cb in self._render_cbs:
cb(self._canvas)
self._state.reset()
================================================
FILE: stenogotchi/ui/web/__init__.py
================================================
import os
from threading import Lock
frame_path = '/var/tmp/stenogotchi/stenogotchi.png'
frame_format = 'PNG'
frame_ctype = 'image/png'
frame_lock = Lock()
def update_frame(img):
global frame_lock, frame_path, frame_format
if not os.path.exists(os.path.dirname(frame_path)):
os.makedirs(os.path.dirname(frame_path))
with frame_lock:
img.save(frame_path, format=frame_format)
================================================
FILE: stenogotchi/ui/web/handler.py
================================================
import logging
import os
import _thread
import secrets
import subprocess
from functools import wraps
# https://stackoverflow.com/questions/14888799/disable-console-messages-in-flask-server
logging.getLogger('werkzeug').setLevel(logging.ERROR)
os.environ['WERKZEUG_RUN_MAIN'] = 'true'
import stenogotchi
import stenogotchi.ui.web as web
from stenogotchi import plugins
from flask import send_file
from flask import Response
from flask import request
from flask import abort
from flask import redirect
from flask import render_template, render_template_string
class Handler:
def __init__(self, config, agent, app):
self._config = config
self._agent = agent
self._app = app
self._app.add_url_rule('/', 'index', self.with_auth(self.index))
self._app.add_url_rule('/ui', 'ui', self.with_auth(self.ui))
self._app.add_url_rule('/shutdown', 'shutdown', self.with_auth(self.shutdown), methods=['POST'])
self._app.add_url_rule('/toggle_input', 'toggle_input', self.with_auth(self.toggle_input), methods=['POST'])
self._app.add_url_rule('/toggle_wpm', 'toggle_wpm', self.with_auth(self.toggle_wpm), methods=['POST'])
self._app.add_url_rule('/toggle_lookup', 'toggle_lookup', self.with_auth(self.toggle_lookup), methods=['POST'])
self._app.add_url_rule('/buttonshim/
*
*
Localizations must be an object and have the following properties defined: monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:
*
* jsDate.regional['en'] = {
* monthNames : 'January February March April May June July August September October November December'.split(' '),
* monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '),
* dayNames : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '),
* dayNamesShort : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')
* };
*
*
After adding localizations, call jsDate.regional.getLocale(); to update the locale setting with the
* new localizations.
*/
jsDate.regional = {
'en': {
monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'fr': {
monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'],
dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'de': {
monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'],
dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'es': {
monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],
monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'],
dayNames: ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'],
dayNamesShort: ['Dom','Lun','Mar','Mié','Juv','Vie','Sáb'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'ru': {
monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'],
dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'],
dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'ar': {
monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'],
monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'],
dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'],
dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'pt': {
monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'],
dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'pt-BR': {
monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'],
dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'pl': {
monthNames: ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec','Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'],
monthNamesShort: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze','Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
dayNames: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
dayNamesShort: ['Ni', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'Sb'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'nl': {
monthNames: ['Januari','Februari','Maart','April','Mei','Juni','July','Augustus','September','Oktober','November','December'],
monthNamesShort: ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Aug','Sep','Okt','Nov','Dec'],
dayNames:','['Zondag','Maandag','Dinsdag','Woensdag','Donderdag','Vrijdag','Zaterdag'],
dayNamesShort: ['Zo','Ma','Di','Wo','Do','Vr','Za'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'sv': {
monthNames: ['januari','februari','mars','april','maj','juni','juli','augusti','september','oktober','november','december'],
monthNamesShort: ['jan','feb','mar','apr','maj','jun','jul','aug','sep','okt','nov','dec'],
dayNames: ['söndag','måndag','tisdag','onsdag','torsdag','fredag','lördag'],
dayNamesShort: ['sön','mån','tis','ons','tor','fre','lör'],
formatString: '%Y-%m-%d %H:%M:%S'
},
'it': {
monthNames: ['Gennaio','Febbraio','Marzo','Aprile','Maggio','Giugno','Luglio','Agosto','Settembre','Ottobre','Novembre','Dicembre'],
monthNamesShort: ['Gen','Feb','Mar','Apr','Mag','Giu','Lug','Ago','Set','Ott','Nov','Dic'],
dayNames: ['Domenica','Lunedi','Martedi','Mercoledi','Giovedi','Venerdi','Sabato'],
dayNamesShort: ['Dom','Lun','Mar','Mer','Gio','Ven','Sab'],
formatString: '%d-%m-%Y %H:%M:%S'
}
};
// Set english variants to 'en'
jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en'];
/**
* Try to determine the users locale based on the lang attribute of the html page. Defaults to 'en'
* if it cannot figure out a locale of if the locale does not have a localization defined.
* @returns {String} locale
*/
jsDate.regional.getLocale = function () {
var l = jsDate.config.defaultLocale;
if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) {
l = document.getElementsByTagName('html')[0].lang;
if (!jsDate.regional.hasOwnProperty(l)) {
l = jsDate.config.defaultLocale;
}
}
return l;
};
// ms in day
var day = 24 * 60 * 60 * 1000;
// padd a number with zeros
var addZeros = function(num, digits) {
num = String(num);
var i = digits - num.length;
var s = String(Math.pow(10, i)).slice(1);
return s.concat(num);
};
// representations used for calculating differences between dates.
// This borrows heavily from Ken Snyder's work.
var multipliers = {
millisecond: 1,
second: 1000,
minute: 60 * 1000,
hour: 60 * 60 * 1000,
day: day,
week: 7 * day,
month: {
// add a number of months
add: function(d, number) {
// add any years needed (increments of 12)
multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12));
// ensure that we properly wrap betwen December and January
// 11 % 12 = 11
// 12 % 12 = 0
var prevMonth = d.getMonth() + (number % 12);
if (prevMonth == 12) {
prevMonth = 0;
d.setYear(d.getFullYear() + 1);
} else if (prevMonth == -1) {
prevMonth = 11;
d.setYear(d.getFullYear() - 1);
}
d.setMonth(prevMonth);
},
// get the number of months between two Date objects (decimal to the nearest day)
diff: function(d1, d2) {
// get the number of years
var diffYears = d1.getFullYear() - d2.getFullYear();
// get the number of remaining months
var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12);
// get the number of remaining days
var diffDays = d1.getDate() - d2.getDate();
// return the month difference with the days difference as a decimal
return diffMonths + (diffDays / 30);
}
},
year: {
// add a number of years
add: function(d, number) {
d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number));
},
// get the number of years between two Date objects (decimal to the nearest day)
diff: function(d1, d2) {
return multipliers.month.diff(d1, d2) / 12;
}
}
};
//
// Alias each multiplier with an 's' to allow 'year' and 'years' for example.
// This comes from Ken Snyders work.
//
for (var unit in multipliers) {
if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :|
multipliers[unit + 's'] = multipliers[unit];
}
}
//
// take a jsDate instance and a format code and return the formatted value.
// This is a somewhat modified version of Ken Snyder's method.
//
var format = function(d, code, syntax) {
// if shorcut codes are used, recursively expand those.
if (jsDate.formats[syntax]["shortcuts"][code]) {
return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax);
} else {
// get the format code function and addZeros() argument
var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.');
var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : '';
if (getter[1]) {
nbr = addZeros(nbr, getter[1]);
}
return nbr;
}
};
/**
* @static
* Static function for convert a date to a string according to a given format. Also acts as namespace for strftime format codes.
*
strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():
* @param {String | Number | Array | jsDate Object | Date Object} date A parsable date string, JavaScript time stamp, Array of form [year, month, day, hours, minutes, seconds, milliseconds], jsDate Object or Date object.
* @param {String} formatString String with embedded date formatting codes.
* See: {@link jsDate.formats}.
* @param {String} syntax Optional syntax to use [default perl].
* @param {String} locale Optional locale to use.
* @returns {String} Formatted representation of the date.
*/
//
// Logic as implemented here is very similar to Ken Snyder's Date Instance Methods.
//
jsDate.strftime = function(d, formatString, syntax, locale) {
var syn = 'perl';
var loc = jsDate.regional.getLocale();
// check if syntax and locale are available or reversed
if (syntax && jsDate.formats.hasOwnProperty(syntax)) {
syn = syntax;
}
else if (syntax && jsDate.regional.hasOwnProperty(syntax)) {
loc = syntax;
}
if (locale && jsDate.formats.hasOwnProperty(locale)) {
syn = locale;
}
else if (locale && jsDate.regional.hasOwnProperty(locale)) {
loc = locale;
}
if (get_type(d) != "[object Object]" || d._type != "jsDate") {
d = new jsDate(d);
d.locale = loc;
}
if (!formatString) {
formatString = d.formatString || jsDate.regional[loc]['formatString'];
}
// default the format string to year-month-day
var source = formatString || '%Y-%m-%d',
result = '',
match;
// replace each format code
while (source.length > 0) {
if (match = source.match(jsDate.formats[syn].codes.matcher)) {
result += source.slice(0, match.index);
result += (match[1] || '') + format(d, match[2], syn);
source = source.slice(match.index + match[0].length);
} else {
result += source;
source = '';
}
}
return result;
};
/**
* @namespace
* Namespace to hold format codes and format shortcuts. "perl" and "php" format codes
* and shortcuts are defined by default. Additional codes and shortcuts can be
* added like:
*
*
* jsDate.formats["perl"] = {
* "codes": {
* matcher: /someregex/,
* Y: "fullYear", // name of "get" method without the "get",
* ..., // more codes
* },
* "shortcuts": {
* F: '%Y-%m-%d',
* ..., // more shortcuts
* }
* };
*
*
*
Additionally, ISO and SQL shortcuts are defined and can be accesses via:
* jsDate.formats.ISO and jsDate.formats.SQL
*/
jsDate.formats = {
ISO:'%Y-%m-%dT%H:%M:%S.%N%G',
SQL:'%Y-%m-%d %H:%M:%S'
};
/**
* Perl format codes and shortcuts for strftime.
*
* A hash (object) of codes where each code must be an array where the first member is
* the name of a Date.prototype or jsDate.prototype function to call
* and optionally a second member indicating the number to pass to addZeros()
*
*
The following format codes are defined:
*
*
* Code Result Description
* == Years ==
* %Y 2008 Four-digit year
* %y 08 Two-digit year
*
* == Months ==
* %m 09 Two-digit month
* %#m 9 One or two-digit month
* %B September Full month name
* %b Sep Abbreviated month name
*
* == Days ==
* %d 05 Two-digit day of month
* %#d 5 One or two-digit day of month
* %e 5 One or two-digit day of month
* %A Sunday Full name of the day of the week
* %a Sun Abbreviated name of the day of the week
* %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday)
*
* == Hours ==
* %H 23 Hours in 24-hour format (two digits)
* %#H 3 Hours in 24-hour integer format (one or two digits)
* %I 11 Hours in 12-hour format (two digits)
* %#I 3 Hours in 12-hour integer format (one or two digits)
* %p PM AM or PM
*
* == Minutes ==
* %M 09 Minutes (two digits)
* %#M 9 Minutes (one or two digits)
*
* == Seconds ==
* %S 02 Seconds (two digits)
* %#S 2 Seconds (one or two digits)
* %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00)
*
* == Milliseconds ==
* %N 008 Milliseconds (three digits)
* %#N 8 Milliseconds (one to three digits)
*
* == Timezone ==
* %O 360 difference in minutes between local time and GMT
* %Z Mountain Standard Time Name of timezone as reported by browser
* %G 06:00 Hours and minutes between GMT
*
* == Shortcuts ==
* %F 2008-03-26 %Y-%m-%d
* %T 05:06:30 %H:%M:%S
* %X 05:06:30 %H:%M:%S
* %x 03/26/08 %m/%d/%y
* %D 03/26/08 %m/%d/%y
* %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y
* %v 3-Sep-2008 %e-%b-%Y
* %R 15:31 %H:%M
* %r 03:31:00 PM %I:%M:%S %p
*
* == Characters ==
* %n \n Newline
* %t \t Tab
* %% % Percent Symbol
*
*
*
Formatting shortcuts that will be translated into their longer version.
* Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.
*
*
Format codes and format shortcuts can be redefined after the jsDate
* module is imported.
*
*
Note that if you redefine the whole hash (object), you must supply a "matcher"
* regex for the parser. The default matcher is:
*
* /()%(#?(%|[a-z]))/i
*
*
which corresponds to the Perl syntax used by default.
*
*
By customizing the matcher and format codes, nearly any strftime functionality is possible.
*/
jsDate.formats.perl = {
codes: {
//
// 2-part regex matcher for format codes
//
// first match must be the character before the code (to account for escaping)
// second match must be the format code character(s)
//
matcher: /()%(#?(%|[a-z]))/i,
// year
Y: 'FullYear',
y: 'ShortYear.2',
// month
m: 'MonthNumber.2',
'#m': 'MonthNumber',
B: 'MonthName',
b: 'AbbrMonthName',
// day
d: 'Date.2',
'#d': 'Date',
e: 'Date',
A: 'DayName',
a: 'AbbrDayName',
w: 'Day',
// hours
H: 'Hours.2',
'#H': 'Hours',
I: 'Hours12.2',
'#I': 'Hours12',
p: 'AMPM',
// minutes
M: 'Minutes.2',
'#M': 'Minutes',
// seconds
S: 'Seconds.2',
'#S': 'Seconds',
s: 'Unix',
// milliseconds
N: 'Milliseconds.3',
'#N': 'Milliseconds',
// timezone
O: 'TimezoneOffset',
Z: 'TimezoneName',
G: 'GmtOffset'
},
shortcuts: {
// date
F: '%Y-%m-%d',
// time
T: '%H:%M:%S',
X: '%H:%M:%S',
// local format date
x: '%m/%d/%y',
D: '%m/%d/%y',
// local format extended
'#c': '%a %b %e %H:%M:%S %Y',
// local format short
v: '%e-%b-%Y',
R: '%H:%M',
r: '%I:%M:%S %p',
// tab and newline
t: '\t',
n: '\n',
'%': '%'
}
};
/**
* PHP format codes and shortcuts for strftime.
*
* A hash (object) of codes where each code must be an array where the first member is
* the name of a Date.prototype or jsDate.prototype function to call
* and optionally a second member indicating the number to pass to addZeros()
*
*
The following format codes are defined:
*
*
* Code Result Description
* === Days ===
* %a Sun through Sat An abbreviated textual representation of the day
* %A Sunday - Saturday A full textual representation of the day
* %d 01 to 31 Two-digit day of the month (with leading zeros)
* %e 1 to 31 Day of the month, with a space preceding single digits.
* %j 001 to 366 Day of the year, 3 digits with leading zeros
* %u 1 - 7 (Mon - Sun) ISO-8601 numeric representation of the day of the week
* %w 0 - 6 (Sun - Sat) Numeric representation of the day of the week
*
* === Week ===
* %U 13 Full Week number, starting with the first Sunday as the first week
* %V 01 through 53 ISO-8601:1988 week number, starting with the first week of the year
* with at least 4 weekdays, with Monday being the start of the week
* %W 46 A numeric representation of the week of the year,
* starting with the first Monday as the first week
* === Month ===
* %b Jan through Dec Abbreviated month name, based on the locale
* %B January - December Full month name, based on the locale
* %h Jan through Dec Abbreviated month name, based on the locale (an alias of %b)
* %m 01 - 12 (Jan - Dec) Two digit representation of the month
*
* === Year ===
* %C 19 Two digit century (year/100, truncated to an integer)
* %y 09 for 2009 Two digit year
* %Y 2038 Four digit year
*
* === Time ===
* %H 00 through 23 Two digit representation of the hour in 24-hour format
* %I 01 through 12 Two digit representation of the hour in 12-hour format
* %l 1 through 12 Hour in 12-hour format, with a space preceeding single digits
* %M 00 through 59 Two digit representation of the minute
* %p AM/PM UPPER-CASE 'AM' or 'PM' based on the given time
* %P am/pm lower-case 'am' or 'pm' based on the given time
* %r 09:34:17 PM Same as %I:%M:%S %p
* %R 00:35 Same as %H:%M
* %S 00 through 59 Two digit representation of the second
* %T 21:34:17 Same as %H:%M:%S
* %X 03:59:16 Preferred time representation based on locale, without the date
* %z -0500 or EST Either the time zone offset from UTC or the abbreviation
* %Z -0500 or EST The time zone offset/abbreviation option NOT given by %z
*
* === Time and Date ===
* %D 02/05/09 Same as %m/%d/%y
* %F 2009-02-05 Same as %Y-%m-%d (commonly used in database datestamps)
* %s 305815200 Unix Epoch Time timestamp (same as the time() function)
* %x 02/05/09 Preferred date representation, without the time
*
* === Miscellaneous ===
* %n --- A newline character (\n)
* %t --- A Tab character (\t)
* %% --- A literal percentage character (%)
*
*/
jsDate.formats.php = {
codes: {
//
// 2-part regex matcher for format codes
//
// first match must be the character before the code (to account for escaping)
// second match must be the format code character(s)
//
matcher: /()%((%|[a-z]))/i,
// day
a: 'AbbrDayName',
A: 'DayName',
d: 'Date.2',
e: 'Date',
j: 'DayOfYear.3',
u: 'DayOfWeek',
w: 'Day',
// week
U: 'FullWeekOfYear.2',
V: 'IsoWeek.2',
W: 'WeekOfYear.2',
// month
b: 'AbbrMonthName',
B: 'MonthName',
m: 'MonthNumber.2',
h: 'AbbrMonthName',
// year
C: 'Century.2',
y: 'ShortYear.2',
Y: 'FullYear',
// time
H: 'Hours.2',
I: 'Hours12.2',
l: 'Hours12',
p: 'AMPM',
P: 'AmPm',
M: 'Minutes.2',
S: 'Seconds.2',
s: 'Unix',
O: 'TimezoneOffset',
z: 'GmtOffset',
Z: 'TimezoneAbbr'
},
shortcuts: {
D: '%m/%d/%y',
F: '%Y-%m-%d',
T: '%H:%M:%S',
X: '%H:%M:%S',
x: '%m/%d/%y',
R: '%H:%M',
r: '%I:%M:%S %p',
t: '\t',
n: '\n',
'%': '%'
}
};
//
// Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods.
// I use his idea of a set of parsers which can be regular expressions or functions,
// iterating through those, and then seeing if Date.parse() will create a date.
// The parser expressions and functions are a little different and some bugs have been
// worked out. Also, a lot of "pre-parsing" is done to fix implementation
// variations of Date.parse() between browsers.
//
jsDate.createDate = function(date) {
// if passing in multiple arguments, try Date constructor
if (date == null) {
return new Date();
}
// If the passed value is already a date object, return it
if (date instanceof Date) {
return date;
}
// if (typeof date == 'number') return new Date(date * 1000);
// If the passed value is an integer, interpret it as a javascript timestamp
if (typeof date == 'number') {
return new Date(date);
}
// Before passing strings into Date.parse(), have to normalize them for certain conditions.
// If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent.
//
// For example:
// * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century.
// * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse.
// * Both FF, Chrome and Opera will parse '1984/1/25' into localtime.
// remove leading and trailing spaces
var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1');
// replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn]
parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3");
/////////
// Need to check for '15-Dec-09' also.
// FF will not parse, but Chrome will.
// Chrome will set date to 2009 as well.
/////////
// first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010'
parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3");
// Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century.
var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i);
if (match && match.length > 3) {
var m3 = parseFloat(match[3]);
var ny = jsDate.config.defaultCentury + m3;
ny = String(ny);
// now replace 2 digit year with 4 digit year
parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny);
}
// Check for '1/19/70 8:14PM'
// where starts with mm/dd/yy or yy/mm/dd and have something after
// Check if 1st postiion is greater than 31, assume it is year.
// Assme all 2 digit years are 1900's.
// Finally, change them into US style mm/dd/yyyy representations.
match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/);
function h1(parsable, match) {
var m1 = parseFloat(match[1]);
var m2 = parseFloat(match[2]);
var m3 = parseFloat(match[3]);
var cent = jsDate.config.defaultCentury;
var ny, nd, nm, str;
if (m1 > 31) { // first number is a year
nd = m3;
nm = m2;
ny = cent + m1;
}
else { // last number is the year
nd = m2;
nm = m1;
ny = cent + m3;
}
str = nm+'/'+nd+'/'+ny;
// now replace 2 digit year with 4 digit year
return parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str);
}
if (match && match.length > 3) {
parsable = h1(parsable, match);
}
// Now check for '1/19/70' with nothing after and do as above
var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/);
if (match && match.length > 3) {
parsable = h1(parsable, match);
}
var i = 0;
var length = jsDate.matchers.length;
var pattern,
ms,
current = parsable,
obj;
while (i < length) {
ms = Date.parse(current);
if (!isNaN(ms)) {
return new Date(ms);
}
pattern = jsDate.matchers[i];
if (typeof pattern == 'function') {
obj = pattern.call(jsDate, current);
if (obj instanceof Date) {
return obj;
}
} else {
current = parsable.replace(pattern[0], pattern[1]);
}
i++;
}
return NaN;
};
/**
* @static
* Handy static utility function to return the number of days in a given month.
* @param {Integer} year Year
* @param {Integer} month Month (1-12)
* @returns {Integer} Number of days in the month.
*/
//
// handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods.
//
jsDate.daysInMonth = function(year, month) {
if (month == 2) {
return new Date(year, 1, 29).getDate() == 29 ? 29 : 28;
}
return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month];
};
//
// An Array of regular expressions or functions that will attempt to match the date string.
// Functions are called with scope of a jsDate instance.
//
jsDate.matchers = [
// convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date).
[/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'],
// convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date).
[/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'],
// Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part.
function(str) {
var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i);
// opt. date hour opt. minute opt. second opt. msec opt. am or pm
if (match) {
if (match[1]) {
var d = this.createDate(match[1]);
if (isNaN(d)) {
return;
}
} else {
var d = new Date();
d.setMilliseconds(0);
}
var hour = parseFloat(match[2]);
if (match[6]) {
hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12);
}
d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000);
return d;
}
else {
return str;
}
},
// Handle ISO timestamp with time zone.
function(str) {
var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i);
if (match) {
if (match[1]) {
var d = this.createDate(match[1]);
if (isNaN(d)) {
return;
}
} else {
var d = new Date();
d.setMilliseconds(0);
}
var hour = parseFloat(match[2]);
d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000);
return d;
}
else {
return str;
}
},
// Try to match ambiguous strings like 12/8/22.
// Use FF date assumption that 2 digit years are 20th century (i.e. 1900's).
// This may be redundant with pre processing of date already performed.
function(str) {
var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/);
if (match) {
var d = new Date();
var cent = jsDate.config.defaultCentury;
var m1 = parseFloat(match[1]);
var m3 = parseFloat(match[3]);
var ny, nd, nm;
if (m1 > 31) { // first number is a year
nd = m3;
ny = cent + m1;
}
else { // last number is the year
nd = m1;
ny = cent + m3;
}
var nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNamesShort"]);
if (nm == -1) {
nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNames"]);
}
d.setFullYear(ny, nm, nd);
d.setHours(0,0,0,0);
return d;
}
else {
return str;
}
}
];
//
// I think John Reisig published this method on his blog, ejohn.
//
function inArray( elem, array ) {
if ( array.indexOf ) {
return array.indexOf( elem );
}
for ( var i = 0, length = array.length; i < length; i++ ) {
if ( array[ i ] === elem ) {
return i;
}
}
return -1;
}
//
// Thanks to Kangax, Christian Sciberras and Stack Overflow for this method.
//
function get_type(thing){
if(thing===null) return "[object Null]"; // special case
return Object.prototype.toString.call(thing);
}
$.jsDate = jsDate;
/**
* JavaScript printf/sprintf functions.
*
* This code has been adapted from the publicly available sprintf methods
* by Ash Searle. His original header follows:
*
* This code is unrestricted: you are free to use it however you like.
*
* The functions should work as expected, performing left or right alignment,
* truncating strings, outputting numbers with a required precision etc.
*
* For complex cases, these functions follow the Perl implementations of
* (s)printf, allowing arguments to be passed out-of-order, and to set the
* precision or length of the output based on arguments instead of fixed
* numbers.
*
* See http://perldoc.perl.org/functions/sprintf.html for more information.
*
* Implemented:
* - zero and space-padding
* - right and left-alignment,
* - base X prefix (binary, octal and hex)
* - positive number prefix
* - (minimum) width
* - precision / truncation / maximum width
* - out of order arguments
*
* Not implemented (yet):
* - vector flag
* - size (bytes, words, long-words etc.)
*
* Will not implement:
* - %n or %p (no pass-by-reference in JavaScript)
*
* @version 2007.04.27
* @author Ash Searle
*
* You can see the original work and comments on his blog:
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
*/
/**
* @Modifications 2009.05.26
* @author Chris Leonello
*
* Added %p %P specifier
* Acts like %g or %G but will not add more significant digits to the output than present in the input.
* Example:
* Format: '%.3p', Input: 0.012, Output: 0.012
* Format: '%.3g', Input: 0.012, Output: 0.0120
* Format: '%.4p', Input: 12.0, Output: 12.0
* Format: '%.4g', Input: 12.0, Output: 12.00
* Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5
* Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5
*
* Example:
* >>> $.jqplot.sprintf('%.2f, %d', 23.3452, 43.23)
* "23.35, 43"
* >>> $.jqplot.sprintf("no value: %n, decimal with thousands separator: %'d", 23.3452, 433524)
* "no value: , decimal with thousands separator: 433,524"
*/
$.jqplot.sprintf = function() {
function pad(str, len, chr, leftJustify) {
var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
return leftJustify ? str + padding : padding + str;
}
function thousand_separate(value) {
var value_str = new String(value);
for (var i=10; i>0; i--) {
if (value_str == (value_str = value_str.replace(/^(\d+)(\d{3})/, "$1"+$.jqplot.sprintf.thousandsSeparator+"$2"))) break;
}
return value_str;
}
function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) {
var diff = minWidth - value.length;
if (diff > 0) {
var spchar = ' ';
if (htmlSpace) { spchar = ' '; }
if (leftJustify || !zeroPad) {
value = pad(value, minWidth, spchar, leftJustify);
} else {
value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
}
}
return value;
}
function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
// Note: casts negative numbers to positive ones
var number = value >>> 0;
prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
value = prefix + pad(number.toString(base), precision || 0, '0', false);
return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
}
function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
if (precision != null) {
value = value.slice(0, precision);
}
return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace);
}
var a = arguments, i = 0, format = a[i++];
return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) {
if (substring == '%%') { return '%'; }
// parse flags
var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false, thousandSeparation = false;
for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) {
case ' ': positivePrefix = ' '; break;
case '+': positivePrefix = '+'; break;
case '-': leftJustify = true; break;
case '0': zeroPad = true; break;
case '#': prefixBaseX = true; break;
case '&': htmlSpace = true; break;
case '\'': thousandSeparation = true; break;
}
// parameters may be null, undefined, empty-string or real valued
// we want to ignore null, undefined and empty-string values
if (!minWidth) {
minWidth = 0;
}
else if (minWidth == '*') {
minWidth = +a[i++];
}
else if (minWidth.charAt(0) == '*') {
minWidth = +a[minWidth.slice(1, -1)];
}
else {
minWidth = +minWidth;
}
// Note: undocumented perl feature:
if (minWidth < 0) {
minWidth = -minWidth;
leftJustify = true;
}
if (!isFinite(minWidth)) {
throw new Error('$.jqplot.sprintf: (minimum-)width must be finite');
}
if (!precision) {
precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
}
else if (precision == '*') {
precision = +a[i++];
}
else if (precision.charAt(0) == '*') {
precision = +a[precision.slice(1, -1)];
}
else {
precision = +precision;
}
// grab value using valueIndex if required?
var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
switch (type) {
case 's': {
if (value == null) {
return '';
}
return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
}
case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace);
case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase();
case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
case 'i': {
var number = parseInt(+value, 10);
if (isNaN(number)) {
return '';
}
var prefix = number < 0 ? '-' : positivePrefix;
var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
value = prefix + pad(number_str, precision, '0', false);
//value = prefix + pad(String(Math.abs(number)), precision, '0', false);
return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
}
case 'd': {
var number = Math.round(+value);
if (isNaN(number)) {
return '';
}
var prefix = number < 0 ? '-' : positivePrefix;
var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
value = prefix + pad(number_str, precision, '0', false);
return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
}
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
{
var number = +value;
if (isNaN(number)) {
return '';
}
var prefix = number < 0 ? '-' : positivePrefix;
var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
var number_str = Math.abs(number)[method](precision);
// Apply the decimal mark properly by splitting the number by the
// decimalMark, applying thousands separator, and then placing it
// back in.
var parts = number_str.toString().split('.');
parts[0] = thousandSeparation ? thousand_separate(parts[0]) : parts[0];
number_str = parts.join($.jqplot.sprintf.decimalMark);
value = prefix + number_str;
var justified = justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
return justified;
}
case 'p':
case 'P':
{
// make sure number is a number
var number = +value;
if (isNaN(number)) {
return '';
}
var prefix = number < 0 ? '-' : positivePrefix;
var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : String(number).length;
var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
if (Math.abs(number) < 1) {
if (sd + zeros <= precision) {
value = prefix + Math.abs(number).toPrecision(sd);
}
else {
if (sd <= precision - 1) {
value = prefix + Math.abs(number).toExponential(sd-1);
}
else {
value = prefix + Math.abs(number).toExponential(precision-1);
}
}
}
else {
var prec = (sd <= precision) ? sd : precision;
value = prefix + Math.abs(number).toPrecision(prec);
}
var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2];
return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
}
case 'n': return '';
default: return substring;
}
});
};
$.jqplot.sprintf.thousandsSeparator = ',';
// Specifies the decimal mark for floating point values. By default a period '.'
// is used. If you change this value to for example a comma be sure to also
// change the thousands separator or else this won't work since a simple String
// replace is used (replacing all periods with the mark specified here).
$.jqplot.sprintf.decimalMark = '.';
$.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0&\' ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g;
$.jqplot.getSignificantFigures = function(number) {
var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
// total significant digits
var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length;
var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
// exponent
var expn = parseInt(parts[1], 10);
// digits to the left of the decimal place
var dleft = (expn + 1 > 0) ? expn + 1 : 0;
// digits to the right of the decimal place
var dright = (sd <= dleft) ? 0 : sd - expn - 1;
return {significantDigits: sd, digitsLeft: dleft, digitsRight: dright, zeros: zeros, exponent: expn} ;
};
$.jqplot.getPrecision = function(number) {
return $.jqplot.getSignificantFigures(number).digitsRight;
};
var backCompat = $.uiBackCompat !== false;
$.jqplot.effects = {
effect: {}
};
// prefix used for storing data on .data()
var dataSpace = "jqplot.storage.";
/******************************************************************************/
/*********************************** EFFECTS **********************************/
/******************************************************************************/
$.extend( $.jqplot.effects, {
version: "1.9pre",
// Saves a set of properties in a data storage
save: function( element, set ) {
for( var i=0; i < set.length; i++ ) {
if ( set[ i ] !== null ) {
element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
}
}
},
// Restores a set of previously saved properties from a data storage
restore: function( element, set ) {
for( var i=0; i < set.length; i++ ) {
if ( set[ i ] !== null ) {
element.css( set[ i ], element.data( dataSpace + set[ i ] ) );
}
}
},
setMode: function( el, mode ) {
if (mode === "toggle") {
mode = el.is( ":hidden" ) ? "show" : "hide";
}
return mode;
},
// Wraps the element around a wrapper that copies position properties
createWrapper: function( element ) {
// if the element is already wrapped, return it
if ( element.parent().is( ".ui-effects-wrapper" )) {
return element.parent();
}
// wrap the element
var props = {
width: element.outerWidth(true),
height: element.outerHeight(true),
"float": element.css( "float" )
},
wrapper = $( "" )
.addClass( "ui-effects-wrapper" )
.css({
fontSize: "100%",
background: "transparent",
border: "none",
margin: 0,
padding: 0
}),
// Store the size in case width/height are defined in % - Fixes #5245
size = {
width: element.width(),
height: element.height()
},
active = document.activeElement;
element.wrap( wrapper );
// Fixes #7595 - Elements lose focus when wrapped.
if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
$( active ).focus();
}
wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
// transfer positioning properties to the wrapper
if ( element.css( "position" ) === "static" ) {
wrapper.css({ position: "relative" });
element.css({ position: "relative" });
} else {
$.extend( props, {
position: element.css( "position" ),
zIndex: element.css( "z-index" )
});
$.each([ "top", "left", "bottom", "right" ], function(i, pos) {
props[ pos ] = element.css( pos );
if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
props[ pos ] = "auto";
}
});
element.css({
position: "relative",
top: 0,
left: 0,
right: "auto",
bottom: "auto"
});
}
element.css(size);
return wrapper.css( props ).show();
},
removeWrapper: function( element ) {
var active = document.activeElement;
if ( element.parent().is( ".ui-effects-wrapper" ) ) {
element.parent().replaceWith( element );
// Fixes #7595 - Elements lose focus when wrapped.
if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
$( active ).focus();
}
}
return element;
}
});
// return an effect options object for the given parameters:
function _normalizeArguments( effect, options, speed, callback ) {
// short path for passing an effect options object:
if ( $.isPlainObject( effect ) ) {
return effect;
}
// convert to an object
effect = { effect: effect };
// catch (effect)
if ( options === undefined ) {
options = {};
}
// catch (effect, callback)
if ( $.isFunction( options ) ) {
callback = options;
speed = null;
options = {};
}
// catch (effect, speed, ?)
if ( $.type( options ) === "number" || $.fx.speeds[ options ]) {
callback = speed;
speed = options;
options = {};
}
// catch (effect, options, callback)
if ( $.isFunction( speed ) ) {
callback = speed;
speed = null;
}
// add options to effect
if ( options ) {
$.extend( effect, options );
}
speed = speed || options.duration;
effect.duration = $.fx.off ? 0 : typeof speed === "number"
? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default;
effect.complete = callback || options.complete;
return effect;
}
function standardSpeed( speed ) {
// valid standard speeds
if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
return true;
}
// invalid strings - treat as "normal" speed
if ( typeof speed === "string" && !$.jqplot.effects.effect[ speed ] ) {
// TODO: remove in 2.0 (#7115)
if ( backCompat && $.jqplot.effects[ speed ] ) {
return false;
}
return true;
}
return false;
}
$.fn.extend({
jqplotEffect: function( effect, options, speed, callback ) {
var args = _normalizeArguments.apply( this, arguments ),
mode = args.mode,
queue = args.queue,
effectMethod = $.jqplot.effects.effect[ args.effect ],
// DEPRECATED: remove in 2.0 (#7115)
oldEffectMethod = !effectMethod && backCompat && $.jqplot.effects[ args.effect ];
if ( $.fx.off || !( effectMethod || oldEffectMethod ) ) {
// delegate to the original method (e.g., .show()) if possible
if ( mode ) {
return this[ mode ]( args.duration, args.complete );
} else {
return this.each( function() {
if ( args.complete ) {
args.complete.call( this );
}
});
}
}
function run( next ) {
var elem = $( this ),
complete = args.complete,
mode = args.mode;
function done() {
if ( $.isFunction( complete ) ) {
complete.call( elem[0] );
}
if ( $.isFunction( next ) ) {
next();
}
}
// if the element is hiddden and mode is hide,
// or element is visible and mode is show
if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
done();
} else {
effectMethod.call( elem[0], args, done );
}
}
// TODO: remove this check in 2.0, effectMethod will always be true
if ( effectMethod ) {
return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
} else {
// DEPRECATED: remove in 2.0 (#7115)
return oldEffectMethod.call(this, {
options: args,
duration: args.duration,
callback: args.complete,
mode: args.mode
});
}
}
});
var rvertical = /up|down|vertical/,
rpositivemotion = /up|left|vertical|horizontal/;
$.jqplot.effects.effect.blind = function( o, done ) {
// Create element
var el = $( this ),
props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
mode = $.jqplot.effects.setMode( el, o.mode || "hide" ),
direction = o.direction || "up",
vertical = rvertical.test( direction ),
ref = vertical ? "height" : "width",
ref2 = vertical ? "top" : "left",
motion = rpositivemotion.test( direction ),
animation = {},
show = mode === "show",
wrapper, distance, top;
// // if already wrapped, the wrapper's properties are my property. #6245
if ( el.parent().is( ".ui-effects-wrapper" ) ) {
$.jqplot.effects.save( el.parent(), props );
} else {
$.jqplot.effects.save( el, props );
}
el.show();
top = parseInt(el.css('top'), 10);
wrapper = $.jqplot.effects.createWrapper( el ).css({
overflow: "hidden"
});
distance = vertical ? wrapper[ ref ]() + top : wrapper[ ref ]();
animation[ ref ] = show ? String(distance) : '0';
if ( !motion ) {
el
.css( vertical ? "bottom" : "right", 0 )
.css( vertical ? "top" : "left", "" )
.css({ position: "absolute" });
animation[ ref2 ] = show ? '0' : String(distance);
}
// // start at 0 if we are showing
if ( show ) {
wrapper.css( ref, 0 );
if ( ! motion ) {
wrapper.css( ref2, distance );
}
}
// // Animate
wrapper.animate( animation, {
duration: o.duration,
easing: o.easing,
queue: false,
complete: function() {
if ( mode === "hide" ) {
el.hide();
}
$.jqplot.effects.restore( el, props );
$.jqplot.effects.removeWrapper( el );
done();
}
});
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/jquery.mobile/jquery.mobile-1.4.5.css
================================================
/*!
* jQuery Mobile 1.4.5
* Git HEAD hash: 68e55e78b292634d3991c795f06f5e37a512decc <> Date: Fri Oct 31 2014 17:33:30 UTC
* http://jquerymobile.com
*
* Copyright 2010, 2014 jQuery Foundation, Inc. and othercontributors
* Released under the MIT license.
* http://jquery.org/license
*
*/
/* SVG icons */
.ui-icon-action:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M9%2C5v3l5-4L9%2C0v3c0%2C0-5%2C0-5%2C7C6%2C5%2C9%2C5%2C9%2C5z%20M11%2C12H2V5h1l2-2H0v11h13V7l-2%2C2V12z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-alert:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M7%2C0L0%2C12h14L7%2C0z%20M7%2C11c-0.553%2C0-1-0.447-1-1s0.447-1%2C1-1c0.553%2C0%2C1%2C0.447%2C1%2C1S7.553%2C11%2C7%2C11z%20M7%2C8%20C6.447%2C8%2C6%2C7.553%2C6%2C7V5c0-0.553%2C0.447-1%2C1-1c0.553%2C0%2C1%2C0.447%2C1%2C1v2C8%2C7.553%2C7.553%2C8%2C7%2C8z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-arrow-d-l:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%2214%2C3%2011%2C0%203.5%2C7.5%200%2C4%200%2C14%2010%2C14%206.5%2C10.5%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-arrow-d-r:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%2210.5%2C7.5%203%2C0%200%2C3%207.5%2C10.5%204%2C14%2014%2C14%2014%2C4%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-arrow-d:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%229%2C7%209%2C0%205%2C0%205%2C7%200%2C7%207%2C14%2014%2C7%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-arrow-l:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%227%2C5%207%2C0%200%2C7%207%2C14%207%2C9%2014%2C9%2014%2C5%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-arrow-r:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%2214%2C7%207%2C0%207%2C5%200%2C5%200%2C9%207%2C9%207%2C14%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-arrow-u-l:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%2214%2C11%206.5%2C3.5%2010%2C0%200%2C0%200%2C10%203.5%2C6.5%2011%2C14%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-arrow-u-r:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%2214%2C0%204%2C0%207.5%2C3.5%200%2C11%203%2C14%2010.5%2C6.5%2014%2C10%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-arrow-u:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%227%2C0%200%2C7%205%2C7%205%2C14%209%2C14%209%2C7%2014%2C7%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-audio:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214.018px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014.018%2014%22%20style%3D%22enable-background%3Anew%200%200%2014.018%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M1%2C4C0.447%2C4%2C0%2C4.447%2C0%2C5v4c0%2C0.553%2C0.447%2C1%2C1%2C1h1l4%2C4V0L2%2C4H1z%20M10.346%2C7c0-1.699-1.042-3.154-2.546-3.867L6.982%2C4.68%20C7.885%2C5.107%2C8.51%2C5.98%2C8.51%2C7S7.885%2C8.893%2C6.982%2C9.32L7.8%2C10.867C9.304%2C10.154%2C10.346%2C8.699%2C10.346%2C7z%20M9.447%2C0.017L8.618%2C1.586%20C10.723%2C2.584%2C12.182%2C4.621%2C12.182%2C7s-1.459%2C4.416-3.563%2C5.414l0.829%2C1.569c2.707-1.283%2C4.57-3.925%2C4.57-6.983%20S12.154%2C1.3%2C9.447%2C0.017z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-back:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M5%2C3V0L1%2C4l4%2C4V5c0%2C0%2C6%2C0%2C6%2C3s-5%2C4-5%2C4v2c0%2C0%2C7-1%2C7-6C13%2C4%2C8%2C3%2C5%2C3z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-bars:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M1%2C4h12c0.553%2C0%2C1-0.447%2C1-1s-0.447-1-1-1H1C0.447%2C2%2C0%2C2.447%2C0%2C3S0.447%2C4%2C1%2C4z%20M13%2C6H1%20C0.447%2C6%2C0%2C6.447%2C0%2C7c0%2C0.553%2C0.447%2C1%2C1%2C1h12c0.553%2C0%2C1-0.447%2C1-1C14%2C6.447%2C13.553%2C6%2C13%2C6z%20M13%2C10H1c-0.553%2C0-1%2C0.447-1%2C1%20s0.447%2C1%2C1%2C1h12c0.553%2C0%2C1-0.447%2C1-1S13.553%2C10%2C13%2C10z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-bullets:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M5%2C4h8c0.553%2C0%2C1-0.447%2C1-1s-0.447-1-1-1H5C4.447%2C2%2C4%2C2.447%2C4%2C3S4.447%2C4%2C5%2C4z%20M13%2C6H5%20C4.447%2C6%2C4%2C6.447%2C4%2C7c0%2C0.553%2C0.447%2C1%2C1%2C1h8c0.553%2C0%2C1-0.447%2C1-1C14%2C6.447%2C13.553%2C6%2C13%2C6z%20M13%2C10H5c-0.553%2C0-1%2C0.447-1%2C1%20s0.447%2C1%2C1%2C1h8c0.553%2C0%2C1-0.447%2C1-1S13.553%2C10%2C13%2C10z%20M1%2C2C0.447%2C2%2C0%2C2.447%2C0%2C3s0.447%2C1%2C1%2C1s1-0.447%2C1-1S1.553%2C2%2C1%2C2z%20M1%2C6%20C0.447%2C6%2C0%2C6.447%2C0%2C7c0%2C0.553%2C0.447%2C1%2C1%2C1s1-0.447%2C1-1C2%2C6.447%2C1.553%2C6%2C1%2C6z%20M1%2C10c-0.553%2C0-1%2C0.447-1%2C1s0.447%2C1%2C1%2C1s1-0.447%2C1-1%20S1.553%2C10%2C1%2C10z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-calendar:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M0%2C8h2V6H0V8z%20M3%2C8h2V6H3V8z%20M6%2C8h2V6H6V8z%20M9%2C8h2V6H9V8z%20M12%2C8h2V6h-2V8z%20M0%2C11h2V9H0V11z%20M3%2C11h2V9H3V11z%20M6%2C11h2V9H6V11z%20%20M9%2C11h2V9H9V11z%20M12%2C11h2V9h-2V11z%20M0%2C14h2v-2H0V14z%20M3%2C14h2v-2H3V14z%20M6%2C14h2v-2H6V14z%20M9%2C14h2v-2H9V14z%20M12%2C1%20c0-0.553-0.447-1-1-1s-1%2C0.447-1%2C1H4c0-0.553-0.447-1-1-1S2%2C0.447%2C2%2C1H0v4h14V1H12z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-camera:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M12%2C2.5H9.908c-0.206-0.581-0.756-1-1.408-1h-3c-0.652%2C0-1.202%2C0.419-1.408%2C1H2c-1.104%2C0-2%2C0.896-2%2C2%20v6c0%2C1.104%2C0.896%2C2%2C2%2C2h10c1.104%2C0%2C2-0.896%2C2-2v-6C14%2C3.396%2C13.104%2C2.5%2C12%2C2.5z%20M7%2C10.5c-1.657%2C0-3-1.344-3-3c0-1.657%2C1.343-3%2C3-3%20s3%2C1.343%2C3%2C3C10%2C9.156%2C8.657%2C10.5%2C7%2C10.5z%20M7%2C5.5c-1.104%2C0-2%2C0.896-2%2C2c0%2C1.104%2C0.896%2C2%2C2%2C2c1.104%2C0%2C2-0.896%2C2-2%20C9%2C6.396%2C8.104%2C5.5%2C7%2C5.5z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-carat-d:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20style%3D%22fill%3A%23FFFFFF%3B%22%20points%3D%2211.949%2C3.404%207%2C8.354%202.05%2C3.404%20-0.071%2C5.525%207%2C12.596%2014.07%2C5.525%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-carat-l:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20style%3D%22fill%3A%23FFFFFF%3B%22%20points%3D%2210.596%2C11.949%205.646%2C7%2010.596%2C2.05%208.475%2C-0.071%201.404%2C7%208.475%2C14.07%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-carat-r:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20style%3D%22fill%3A%23FFFFFF%3B%22%20points%3D%223.404%2C2.051%208.354%2C7%203.404%2C11.95%205.525%2C14.07%2012.596%2C7%205.525%2C-0.071%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-carat-u:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20style%3D%22fill%3A%23FFFFFF%3B%22%20points%3D%222.051%2C10.596%207%2C5.646%2011.95%2C10.596%2014.07%2C8.475%207%2C1.404%20-0.071%2C8.475%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-check:after,
/* Used ui-checkbox-on twice to increase specificity. If active state has background-image for gradient this rule overrides. */
html .ui-btn.ui-checkbox-on.ui-checkbox-on:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20style%3D%22fill%3A%23FFFFFF%3B%22%20points%3D%2214%2C4%2011%2C1%205.003%2C6.997%203%2C5%200%2C8%204.966%2C13%204.983%2C12.982%205%2C13%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-clock:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M7%2C0C3.134%2C0%2C0%2C3.134%2C0%2C7s3.134%2C7%2C7%2C7s7-3.134%2C7-7S10.866%2C0%2C7%2C0z%20M7%2C12c-2.762%2C0-5-2.238-5-5s2.238-5%2C5-5s5%2C2.238%2C5%2C5%20S9.762%2C12%2C7%2C12z%20M9%2C6H8V4c0-0.553-0.447-1-1-1S6%2C3.447%2C6%2C4v3c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1S9.553%2C6%2C9%2C6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-cloud:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M14%2C9.5c0-0.793-0.465-1.473-1.134-1.795C12.949%2C7.484%2C13%2C7.249%2C13%2C7c0-1.104-0.896-2-2-2%20c-0.158%2C0-0.311%2C0.023-0.457%2C0.058C9.816%2C3.549%2C8.286%2C2.5%2C6.5%2C2.5c-2.33%2C0-4.224%2C1.777-4.454%2C4.046C0.883%2C6.76%2C0%2C7.773%2C0%2C9%20c0%2C1.381%2C1.119%2C2.5%2C2.5%2C2.5h10v-0.07C13.361%2C11.206%2C14%2C10.432%2C14%2C9.5z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-comment:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M12%2C0H2C0.896%2C0%2C0%2C0.896%2C0%2C2v7c0%2C1.104%2C0.896%2C2%2C2%2C2h1v3l3-3h6c1.104%2C0%2C2-0.896%2C2-2V2C14%2C0.896%2C13.104%2C0%2C12%2C0z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-delete:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%2214%2C3%2011%2C0%207%2C4%203%2C0%200%2C3%204%2C7%200%2C11%203%2C14%207%2C10%2011%2C14%2014%2C11%2010%2C7%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-edit:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M1%2C10l-1%2C4l4-1l7-7L8%2C3L1%2C10z%20M11%2C0L9%2C2l3%2C3l2-2L11%2C0z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-eye:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M7%2C2C3%2C2%2C0%2C7%2C0%2C7s3%2C5%2C7%2C5s7-5%2C7-5S11%2C2%2C7%2C2z%20M7%2C10c-1.657%2C0-3-1.344-3-3c0-1.657%2C1.343-3%2C3-3%20s3%2C1.343%2C3%2C3C10%2C8.656%2C8.657%2C10%2C7%2C10z%20M7%2C6C6.448%2C6%2C6%2C6.447%2C6%2C7c0%2C0.553%2C0.448%2C1%2C1%2C1s1-0.447%2C1-1C8%2C6.447%2C7.552%2C6%2C7%2C6z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-forbidden:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M12.601%2C11.187C13.476%2C10.018%2C14%2C8.572%2C14%2C7c0-3.866-3.134-7-7-7C5.428%2C0%2C3.982%2C0.524%2C2.813%2C1.399L2.757%2C1.343L2.053%2C2.048%20L2.048%2C2.053L1.343%2C2.758l0.056%2C0.056C0.524%2C3.982%2C0%2C5.428%2C0%2C7c0%2C3.866%2C3.134%2C7%2C7%2C7c1.572%2C0%2C3.018-0.524%2C4.187-1.399l0.056%2C0.057%20l0.705-0.705l0.005-0.005l0.705-0.705L12.601%2C11.187z%20M7%2C2c2.761%2C0%2C5%2C2.238%2C5%2C5c0%2C1.019-0.308%2C1.964-0.832%2C2.754L4.246%2C2.832%20C5.036%2C2.308%2C5.981%2C2%2C7%2C2z%20M7%2C12c-2.761%2C0-5-2.238-5-5c0-1.019%2C0.308-1.964%2C0.832-2.754l6.922%2C6.922C8.964%2C11.692%2C8.019%2C12%2C7%2C12z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-forward:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M13%2C4L9%2C0v3C6%2C3%2C1%2C4%2C1%2C8c0%2C5%2C7%2C6%2C7%2C6v-2c0%2C0-5-1-5-4s6-3%2C6-3v3L13%2C4z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-gear:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M13.621%2C5.904l-1.036-0.259c-0.168-0.042-0.303-0.168-0.355-0.332c-0.092-0.284-0.205-0.559-0.339-0.82%20c-0.079-0.153-0.073-0.337%2C0.017-0.486l0.549-0.915c0.118-0.196%2C0.088-0.448-0.075-0.61l-0.862-0.863%20c-0.162-0.163-0.414-0.193-0.611-0.075l-0.916%2C0.55C9.844%2C2.182%2C9.659%2C2.188%2C9.506%2C2.109C9.244%2C1.975%2C8.97%2C1.861%2C8.686%2C1.77%20c-0.165-0.052-0.29-0.187-0.332-0.354L8.095%2C0.379C8.039%2C0.156%2C7.839%2C0%2C7.609%2C0H6.391c-0.229%2C0-0.43%2C0.156-0.485%2C0.379L5.646%2C1.415%20C5.604%2C1.582%2C5.479%2C1.718%2C5.313%2C1.77c-0.284%2C0.092-0.559%2C0.206-0.82%2C0.34C4.339%2C2.188%2C4.155%2C2.182%2C4.007%2C2.093L3.092%2C1.544%20c-0.196-0.118-0.448-0.087-0.61%2C0.075L1.619%2C2.481C1.457%2C2.644%2C1.426%2C2.896%2C1.544%2C3.093l0.549%2C0.914%20c0.089%2C0.148%2C0.095%2C0.332%2C0.017%2C0.486C1.975%2C4.755%2C1.861%2C5.029%2C1.77%2C5.314c-0.053%2C0.164-0.188%2C0.29-0.354%2C0.332L0.379%2C5.905%20C0.156%2C5.961%2C0%2C6.161%2C0%2C6.391v1.219c0%2C0.229%2C0.156%2C0.43%2C0.379%2C0.485l1.036%2C0.26C1.582%2C8.396%2C1.717%2C8.521%2C1.77%2C8.687%20c0.092%2C0.284%2C0.205%2C0.559%2C0.34%2C0.82C2.188%2C9.66%2C2.182%2C9.844%2C2.093%2C9.993l-0.549%2C0.915c-0.118%2C0.195-0.087%2C0.448%2C0.075%2C0.61%20l0.862%2C0.862c0.162%2C0.163%2C0.414%2C0.193%2C0.61%2C0.075l0.915-0.549c0.148-0.089%2C0.332-0.095%2C0.486-0.017%20c0.262%2C0.135%2C0.536%2C0.248%2C0.82%2C0.34c0.165%2C0.053%2C0.291%2C0.187%2C0.332%2C0.354l0.259%2C1.036C5.96%2C13.844%2C6.16%2C14%2C6.39%2C14h1.22%20c0.229%2C0%2C0.43-0.156%2C0.485-0.379l0.259-1.036c0.042-0.167%2C0.168-0.302%2C0.333-0.354c0.284-0.092%2C0.559-0.205%2C0.82-0.34%20c0.154-0.078%2C0.338-0.072%2C0.486%2C0.017l0.914%2C0.549c0.197%2C0.118%2C0.449%2C0.088%2C0.611-0.074l0.862-0.863%20c0.163-0.162%2C0.193-0.415%2C0.075-0.611l-0.549-0.915c-0.089-0.148-0.096-0.332-0.017-0.485c0.134-0.263%2C0.248-0.536%2C0.339-0.82%20c0.053-0.165%2C0.188-0.291%2C0.355-0.333l1.036-0.259C13.844%2C8.039%2C14%2C7.839%2C14%2C7.609V6.39C14%2C6.16%2C13.844%2C5.96%2C13.621%2C5.904z%20M7%2C10%20c-1.657%2C0-3-1.343-3-3s1.343-3%2C3-3s3%2C1.343%2C3%2C3S8.657%2C10%2C7%2C10z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-grid:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M3%2C0H1C0.447%2C0%2C0%2C0.447%2C0%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V1C4%2C0.447%2C3.553%2C0%2C3%2C0z%20M8%2C0H6%20C5.447%2C0%2C5%2C0.447%2C5%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V1C9%2C0.447%2C8.553%2C0%2C8%2C0z%20M13%2C0h-2c-0.553%2C0-1%2C0.447-1%2C1v2%20c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V1C14%2C0.447%2C13.553%2C0%2C13%2C0z%20M3%2C5H1C0.447%2C5%2C0%2C5.447%2C0%2C6v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2%20c0.553%2C0%2C1-0.447%2C1-1V6C4%2C5.447%2C3.553%2C5%2C3%2C5z%20M8%2C5H6C5.447%2C5%2C5%2C5.447%2C5%2C6v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V6%20C9%2C5.447%2C8.553%2C5%2C8%2C5z%20M13%2C5h-2c-0.553%2C0-1%2C0.447-1%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V6C14%2C5.447%2C13.553%2C5%2C13%2C5z%20M3%2C10%20H1c-0.553%2C0-1%2C0.447-1%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1v-2C4%2C10.447%2C3.553%2C10%2C3%2C10z%20M8%2C10H6c-0.553%2C0-1%2C0.447-1%2C1v2%20c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1v-2C9%2C10.447%2C8.553%2C10%2C8%2C10z%20M13%2C10h-2c-0.553%2C0-1%2C0.447-1%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2%20c0.553%2C0%2C1-0.447%2C1-1v-2C14%2C10.447%2C13.553%2C10%2C13%2C10z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-heart:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M7%2C1.872c-2-3-7-2-7%2C2c0%2C3%2C4%2C7%2C4%2C7s2.417%2C2.479%2C3%2C3c0.583-0.521%2C3-3%2C3-3s4-4%2C4-7%20C14-0.128%2C9-1.128%2C7%2C1.872z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-home:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%227%2C0%200%2C7%202%2C7%202%2C14%205%2C14%205%2C9%209%2C9%209%2C14%2012%2C14%2012%2C7%2014%2C7%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-info:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M7%2C0C3.134%2C0%2C0%2C3.134%2C0%2C7s3.134%2C7%2C7%2C7s7-3.134%2C7-7S10.866%2C0%2C7%2C0z%20M7%2C2c0.552%2C0%2C1%2C0.447%2C1%2C1S7.552%2C4%2C7%2C4S6%2C3.553%2C6%2C3%20S6.448%2C2%2C7%2C2z%20M9%2C11H5v-1h1V6H5V5h3v5h1V11z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-location:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M7%2C0C4.791%2C0%2C3%2C1.791%2C3%2C4c0%2C2%2C4%2C10%2C4%2C10s4-8%2C4-10C11%2C1.791%2C9.209%2C0%2C7%2C0z%20M7%2C6C5.896%2C6%2C5%2C5.104%2C5%2C4%20s0.896-2%2C2-2c1.104%2C0%2C2%2C0.896%2C2%2C2S8.104%2C6%2C7%2C6z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-lock:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M12%2C6V5c0-2.762-2.238-5-5-5C4.239%2C0%2C2%2C2.238%2C2%2C5v1H1v8h12V6H12z%20M7.5%2C9.848V12h-1V9.848%20C6.207%2C9.673%2C6%2C9.366%2C6%2C9c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C8%2C9.366%2C7.793%2C9.673%2C7.5%2C9.848z%20M10%2C6H4V5c0-1.657%2C1.343-3%2C3-3%20s3%2C1.343%2C3%2C3V6z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-mail:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M0%2C3.75V12h14V3.75L7%2C9L0%2C3.75z%20M14%2C2H0l7%2C5L14%2C2z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-minus:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Crect%20y%3D%225%22%20style%3D%22fill%3A%23FFFFFF%3B%22%20width%3D%2214%22%20height%3D%224%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-navigation:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20style%3D%22fill%3A%23FFFFFF%3B%22%20points%3D%2213%2C1%200%2C6%207%2C7%208%2C14%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-phone:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%22-0.01%200.008%2014%2014%22%20style%3D%22enable-background%3Anew%20-0.01%200.008%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M6.939%2C9.189C6.165%2C8.557%2C5.271%2C7.705%2C4.497%2C6.744C3.953%2C6.071%2C3.473%2C5.363%2C3.969%2C4.866l-3.482-3.48%20C-0.021%2C2.02-1.146%2C5.04%2C3.675%2C9.984c5.08%2C5.211%2C8.356%2C4.097%2C8.92%2C3.511l-3.396-3.4C8.725%2C10.568%2C8.113%2C10.146%2C6.939%2C9.189z%20%20M13.82%2C11.519v-0.004c0%2C0-2.648-2.646-2.649-2.647c-0.21-0.211-0.546-0.205-0.754%2C0.002L9.455%2C9.831l3.403%2C3.407%20c0%2C0%2C0.962-0.96%2C0.961-0.961l0.002-0.001C14.043%2C12.056%2C14.021%2C11.721%2C13.82%2C11.519z%20M5.192%2C3.644V3.642%20c0.222-0.222%2C0.2-0.557%2C0-0.758V2.881c0%2C0-2.726-2.725-2.727-2.726C2.255-0.055%2C1.92-0.05%2C1.712%2C0.156L0.751%2C1.121l3.479%2C3.482%20C4.231%2C4.604%2C5.192%2C3.645%2C5.192%2C3.644z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-plus:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20fill%3D%22%23FFF%22%20points%3D%2214%2C5%209%2C5%209%2C0%205%2C0%205%2C5%200%2C5%200%2C9%205%2C9%205%2C14%209%2C14%209%2C9%2014%2C9%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-power:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M11.243%2C2.408c-0.392-0.401-1.024-0.401-1.415%2C0c-0.391%2C0.401-0.391%2C1.054%2C0%2C1.455%20C10.584%2C4.642%2C11%2C5.675%2C11%2C6.773s-0.416%2C2.133-1.172%2C2.91c-1.512%2C1.558-4.145%2C1.558-5.656%2C0C3.416%2C8.904%2C3%2C7.872%2C3%2C6.773%20C3%2C5.673%2C3.416%2C4.64%2C4.172%2C3.863c0.39-0.401%2C0.39-1.054%2C0-1.455c-0.391-0.401-1.024-0.401-1.415%2C0C1.624%2C3.574%2C1%2C5.125%2C1%2C6.773%20c0%2C1.647%2C0.624%2C3.199%2C1.757%2C4.365c1.134%2C1.166%2C2.64%2C1.809%2C4.243%2C1.809c1.604%2C0%2C3.109-0.645%2C4.243-1.811%20C12.376%2C9.975%2C13%2C8.423%2C13%2C6.773C13%2C5.125%2C12.376%2C3.574%2C11.243%2C2.408z%20M7%2C8.053c0.553%2C0%2C1-0.445%2C1-1v-6c0-0.553-0.447-1-1-1%20c-0.553%2C0-1%2C0.447-1%2C1v6C6%2C7.604%2C6.447%2C8.053%2C7%2C8.053z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-recycle:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M3%2C7h1L2%2C4L0%2C7h1c0%2C3.313%2C2.687%2C6%2C6%2C6c0.702%2C0%2C1.374-0.127%2C2-0.35v-2.205C8.41%2C10.789%2C7.732%2C11%2C7%2C11%20C4.791%2C11%2C3%2C9.209%2C3%2C7z%20M13%2C7c0-3.313-2.688-6-6-6C6.298%2C1%2C5.626%2C1.127%2C5%2C1.349v2.206C5.59%2C3.211%2C6.268%2C3%2C7%2C3c2.209%2C0%2C4%2C1.791%2C4%2C4%20h-1l2%2C3l2-3H13z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-refresh:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214.001px%22%20height%3D%2214.002px%22%20viewBox%3D%220%200%2014.001%2014.002%22%20style%3D%22enable-background%3Anew%200%200%2014.001%2014.002%3B%22%20%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M14.001%2C6.001v-6l-2.06%2C2.06c-0.423-0.424-0.897-0.809-1.44-1.122C7.153-0.994%2C2.872%2C0.153%2C0.939%2C3.501%20c-1.933%2C3.348-0.786%2C7.629%2C2.562%2C9.562c3.348%2C1.933%2C7.629%2C0.785%2C9.562-2.562l-1.732-1c-1.381%2C2.392-4.438%2C3.211-6.83%2C1.83%20s-3.211-4.438-1.83-6.83s4.438-3.211%2C6.83-1.83c0.389%2C0.225%2C0.718%2C0.506%2C1.02%2C0.81l-2.52%2C2.52H14.001z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-search:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M10.171%2C8.766c0.617-0.888%2C0.979-1.964%2C0.979-3.126c0-3.037-2.463-5.5-5.5-5.5s-5.5%2C2.463-5.5%2C5.5%20s2.463%2C5.5%2C5.5%2C5.5c1.152%2C0%2C2.223-0.355%2C3.104-0.962l3.684%2C3.683l1.414-1.414L10.171%2C8.766z%20M5.649%2C9.14c-1.933%2C0-3.5-1.567-3.5-3.5%20c0-1.933%2C1.567-3.5%2C3.5-3.5c1.933%2C0%2C3.5%2C1.567%2C3.5%2C3.5C9.149%2C7.572%2C7.582%2C9.14%2C5.649%2C9.14z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-shop:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M10%2C4V3c0-1.657-1.343-3-3-3S4%2C1.343%2C4%2C3v1H1v10h12V4H10z%20M4.5%2C6C4.224%2C6%2C4%2C5.776%2C4%2C5.5%20S4.224%2C5%2C4.5%2C5S5%2C5.224%2C5%2C5.5S4.776%2C6%2C4.5%2C6z%20M5%2C3c0-1.104%2C0.896-2%2C2-2c1.104%2C0%2C2%2C0.896%2C2%2C2v1H5V3z%20M9.5%2C6C9.225%2C6%2C9%2C5.776%2C9%2C5.5%20S9.225%2C5%2C9.5%2C5S10%2C5.224%2C10%2C5.5S9.775%2C6%2C9.5%2C6z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-star:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20style%3D%22fill%3A%23FFFFFF%3B%22%20points%3D%2214%2C5%209%2C5%207%2C0%205%2C5%200%2C5%204%2C8%202.625%2C13%207%2C10%2011.375%2C13%2010%2C8%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-icon-tag:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M5%2C0H0v5l9%2C9l5-5L5%2C0z%20M3%2C4C2.447%2C4%2C2%2C3.553%2C2%2C3s0.447-1%2C1-1s1%2C0.447%2C1%2C1S3.553%2C4%2C3%2C4z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-user:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M8.851%2C10.101c-0.18-0.399-0.2-0.763-0.153-1.104C9.383%2C8.49%2C9.738%2C7.621%2C9.891%2C6.465C10.493%2C6.355%2C10.5%2C5.967%2C10.5%2C5.5%20c0-0.437-0.008-0.804-0.502-0.94C9.999%2C4.539%2C10%2C4.521%2C10%2C4.5c0-2.103-1-4-2-4C8%2C0.5%2C7.5%2C0%2C6.5%2C0C5%2C0%2C4%2C1.877%2C4%2C4.5%20c0%2C0.021%2C0.001%2C0.039%2C0.002%2C0.06C3.508%2C4.696%2C3.5%2C5.063%2C3.5%2C5.5c0%2C0.467%2C0.007%2C0.855%2C0.609%2C0.965%20C4.262%2C7.621%2C4.617%2C8.49%2C5.303%2C8.997c0.047%2C0.341%2C0.026%2C0.704-0.153%2C1.104C1.503%2C10.503%2C0%2C12%2C0%2C12v2h14v-2%20C14%2C12%2C12.497%2C10.503%2C8.851%2C10.101z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-icon-video:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%20-2%2014%2014%22%20style%3D%22enable-background%3Anew%200%20-2%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20style%3D%22fill%3A%23FFFFFF%3B%22%20d%3D%22M8%2C0H2C0.896%2C0%2C0%2C0.896%2C0%2C2v6c0%2C1.104%2C0.896%2C2%2C2%2C2h6c1.104%2C0%2C2-0.896%2C2-2V5V2C10%2C0.896%2C9.104%2C0%2C8%2C0z%20%20M10%2C5l4%2C4V1L10%2C5z%22%2F%3E%3C%2Fsvg%3E");
}
/* Alt icons */
.ui-alt-icon.ui-icon-action:after,
.ui-alt-icon .ui-icon-action:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M9%2C5v3l5-4L9%2C0v3c0%2C0-5%2C0-5%2C7C6%2C5%2C9%2C5%2C9%2C5z%20M11%2C12H2V5h1l2-2H0v11h13V7l-2%2C2V12z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-alert:after,
.ui-alt-icon .ui-icon-alert:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M7%2C0L0%2C12h14L7%2C0z%20M7%2C11c-0.553%2C0-1-0.447-1-1s0.447-1%2C1-1c0.553%2C0%2C1%2C0.447%2C1%2C1S7.553%2C11%2C7%2C11z%20M7%2C8C6.447%2C8%2C6%2C7.553%2C6%2C7V5%20c0-0.553%2C0.447-1%2C1-1c0.553%2C0%2C1%2C0.447%2C1%2C1v2C8%2C7.553%2C7.553%2C8%2C7%2C8z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-arrow-d:after,
.ui-alt-icon .ui-icon-arrow-d:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%229%2C7%209%2C0%205%2C0%205%2C7%200%2C7%207%2C14%2014%2C7%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-arrow-d-l:after,
.ui-alt-icon .ui-icon-arrow-d-l:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2214%2C3%2011%2C0%203.5%2C7.5%200%2C4%200%2C14%2010%2C14%206.5%2C10.5%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-arrow-d-r:after,
.ui-alt-icon .ui-icon-arrow-d-r:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2210.5%2C7.5%203%2C0%200%2C3%207.5%2C10.5%204%2C14%2014%2C14%2014%2C4%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-arrow-l:after,
.ui-alt-icon .ui-icon-arrow-l:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%227%2C5%207%2C0%200%2C7%207%2C14%207%2C9%2014%2C9%2014%2C5%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-arrow-r:after,
.ui-alt-icon .ui-icon-arrow-r:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2214%2C7%207%2C0%207%2C5%200%2C5%200%2C9%207%2C9%207%2C14%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-arrow-u:after,
.ui-alt-icon .ui-icon-arrow-u:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%227%2C0%200%2C7%205%2C7%205%2C14%209%2C14%209%2C7%2014%2C7%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-arrow-u-l:after,
.ui-alt-icon .ui-icon-arrow-u-l:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2214%2C11%206.5%2C3.5%2010%2C0%200%2C0%200%2C10%203.5%2C6.5%2011%2C14%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-arrow-u-r:after,
.ui-alt-icon .ui-icon-arrow-u-r:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2214%2C0%204%2C0%207.5%2C3.5%200%2C11%203%2C14%2010.5%2C6.5%2014%2C10%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-audio:after,
.ui-alt-icon .ui-icon-audio:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214.018px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014.018%2014%22%20style%3D%22enable-background%3Anew%200%200%2014.018%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M1%2C4C0.447%2C4%2C0%2C4.447%2C0%2C5v4c0%2C0.553%2C0.447%2C1%2C1%2C1h1l4%2C4V0L2%2C4H1z%20M10.346%2C7c0-1.699-1.042-3.154-2.546-3.867L6.982%2C4.68%20C7.885%2C5.107%2C8.51%2C5.98%2C8.51%2C7S7.885%2C8.893%2C6.982%2C9.32L7.8%2C10.867C9.304%2C10.154%2C10.346%2C8.699%2C10.346%2C7z%20M9.447%2C0.017L8.618%2C1.586%20C10.723%2C2.584%2C12.182%2C4.621%2C12.182%2C7s-1.459%2C4.416-3.563%2C5.414l0.829%2C1.569c2.707-1.283%2C4.57-3.925%2C4.57-6.983%20S12.154%2C1.3%2C9.447%2C0.017z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-back:after,
.ui-alt-icon .ui-icon-back:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M5%2C3V0L1%2C4l4%2C4V5c0%2C0%2C6%2C0%2C6%2C3s-5%2C4-5%2C4v2c0%2C0%2C7-1%2C7-6C13%2C4%2C8%2C3%2C5%2C3z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-bars:after,
.ui-alt-icon .ui-icon-bars:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M1%2C4h12c0.553%2C0%2C1-0.447%2C1-1s-0.447-1-1-1H1C0.447%2C2%2C0%2C2.447%2C0%2C3S0.447%2C4%2C1%2C4z%20M13%2C6H1C0.447%2C6%2C0%2C6.447%2C0%2C7%20c0%2C0.553%2C0.447%2C1%2C1%2C1h12c0.553%2C0%2C1-0.447%2C1-1C14%2C6.447%2C13.553%2C6%2C13%2C6z%20M13%2C10H1c-0.553%2C0-1%2C0.447-1%2C1s0.447%2C1%2C1%2C1h12%20c0.553%2C0%2C1-0.447%2C1-1S13.553%2C10%2C13%2C10z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-bullets:after,
.ui-alt-icon .ui-icon-bullets:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M5%2C4h8c0.553%2C0%2C1-0.447%2C1-1s-0.447-1-1-1H5C4.447%2C2%2C4%2C2.447%2C4%2C3S4.447%2C4%2C5%2C4z%20M13%2C6H5C4.447%2C6%2C4%2C6.447%2C4%2C7%20c0%2C0.553%2C0.447%2C1%2C1%2C1h8c0.553%2C0%2C1-0.447%2C1-1C14%2C6.447%2C13.553%2C6%2C13%2C6z%20M13%2C10H5c-0.553%2C0-1%2C0.447-1%2C1s0.447%2C1%2C1%2C1h8%20c0.553%2C0%2C1-0.447%2C1-1S13.553%2C10%2C13%2C10z%20M1%2C2C0.447%2C2%2C0%2C2.447%2C0%2C3s0.447%2C1%2C1%2C1s1-0.447%2C1-1S1.553%2C2%2C1%2C2z%20M1%2C6C0.447%2C6%2C0%2C6.447%2C0%2C7%20c0%2C0.553%2C0.447%2C1%2C1%2C1s1-0.447%2C1-1C2%2C6.447%2C1.553%2C6%2C1%2C6z%20M1%2C10c-0.553%2C0-1%2C0.447-1%2C1s0.447%2C1%2C1%2C1s1-0.447%2C1-1S1.553%2C10%2C1%2C10z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-calendar:after,
.ui-alt-icon .ui-icon-calendar:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M0%2C8h2V6H0V8z%20M3%2C8h2V6H3V8z%20M6%2C8h2V6H6V8z%20M9%2C8h2V6H9V8z%20M12%2C8h2V6h-2V8z%20M0%2C11h2V9H0V11z%20M3%2C11h2V9H3V11z%20M6%2C11h2V9H6V11z%20%20M9%2C11h2V9H9V11z%20M12%2C11h2V9h-2V11z%20M0%2C14h2v-2H0V14z%20M3%2C14h2v-2H3V14z%20M6%2C14h2v-2H6V14z%20M9%2C14h2v-2H9V14z%20M12%2C1%20c0-0.553-0.447-1-1-1s-1%2C0.447-1%2C1H4c0-0.553-0.447-1-1-1S2%2C0.447%2C2%2C1H0v4h14V1H12z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-camera:after,
.ui-alt-icon .ui-icon-camera:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M12%2C2.5H9.908c-0.206-0.581-0.756-1-1.408-1h-3c-0.652%2C0-1.202%2C0.419-1.408%2C1H2c-1.104%2C0-2%2C0.896-2%2C2v6c0%2C1.104%2C0.896%2C2%2C2%2C2%20h10c1.104%2C0%2C2-0.896%2C2-2v-6C14%2C3.396%2C13.104%2C2.5%2C12%2C2.5z%20M7%2C10.5c-1.657%2C0-3-1.344-3-3c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3%20C10%2C9.156%2C8.657%2C10.5%2C7%2C10.5z%20M7%2C5.5c-1.104%2C0-2%2C0.896-2%2C2c0%2C1.104%2C0.896%2C2%2C2%2C2c1.104%2C0%2C2-0.896%2C2-2C9%2C6.396%2C8.104%2C5.5%2C7%2C5.5z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-carat-d:after,
.ui-alt-icon .ui-icon-carat-d:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2211.949%2C3.404%207%2C8.354%202.05%2C3.404%20-0.071%2C5.525%207%2C12.596%2014.07%2C5.525%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-carat-l:after,
.ui-alt-icon .ui-icon-carat-l:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2210.596%2C11.949%205.646%2C7%2010.596%2C2.05%208.475%2C-0.071%201.404%2C7%208.475%2C14.07%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-carat-r:after,
.ui-alt-icon .ui-icon-carat-r:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%223.404%2C2.051%208.354%2C7%203.404%2C11.95%205.525%2C14.07%2012.596%2C7%205.525%2C-0.071%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-carat-u:after,
.ui-alt-icon .ui-icon-carat-u:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%222.051%2C10.596%207%2C5.646%2011.95%2C10.596%2014.07%2C8.475%207%2C1.404%20-0.071%2C8.475%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-check:after,
.ui-alt-icon .ui-icon-check:after,
html .ui-alt-icon.ui-btn.ui-checkbox-on:after,
html .ui-alt-icon .ui-btn.ui-checkbox-on:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2214%2C4%2011%2C1%205.003%2C6.997%203%2C5%200%2C8%204.966%2C13%204.983%2C12.982%205%2C13%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-clock:after,
.ui-alt-icon .ui-icon-clock:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M7%2C0C3.134%2C0%2C0%2C3.134%2C0%2C7s3.134%2C7%2C7%2C7s7-3.134%2C7-7S10.866%2C0%2C7%2C0z%20M7%2C12c-2.762%2C0-5-2.238-5-5s2.238-5%2C5-5s5%2C2.238%2C5%2C5%20S9.762%2C12%2C7%2C12z%20M9%2C6H8V4c0-0.553-0.447-1-1-1S6%2C3.447%2C6%2C4v3c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1S9.553%2C6%2C9%2C6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-cloud:after,
.ui-alt-icon .ui-icon-cloud:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M14%2C9.5c0-0.793-0.465-1.473-1.134-1.795C12.949%2C7.484%2C13%2C7.249%2C13%2C7c0-1.104-0.896-2-2-2c-0.158%2C0-0.311%2C0.023-0.457%2C0.058%20C9.816%2C3.549%2C8.286%2C2.5%2C6.5%2C2.5c-2.33%2C0-4.224%2C1.777-4.454%2C4.046C0.883%2C6.76%2C0%2C7.773%2C0%2C9c0%2C1.381%2C1.119%2C2.5%2C2.5%2C2.5h10v-0.07%20C13.361%2C11.206%2C14%2C10.432%2C14%2C9.5z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-comment:after,
.ui-alt-icon .ui-icon-comment:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M12%2C0H2C0.896%2C0%2C0%2C0.896%2C0%2C2v7c0%2C1.104%2C0.896%2C2%2C2%2C2h1v3l3-3h6c1.104%2C0%2C2-0.896%2C2-2V2C14%2C0.896%2C13.104%2C0%2C12%2C0z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-delete:after,
.ui-alt-icon .ui-icon-delete:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2214%2C3%2011%2C0%207%2C4%203%2C0%200%2C3%204%2C7%200%2C11%203%2C14%207%2C10%2011%2C14%2014%2C11%2010%2C7%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-edit:after,
.ui-alt-icon .ui-icon-edit:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M1%2C10l-1%2C4l4-1l7-7L8%2C3L1%2C10z%20M11%2C0L9%2C2l3%2C3l2-2L11%2C0z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-eye:after,
.ui-alt-icon .ui-icon-eye:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M7%2C2C3%2C2%2C0%2C7%2C0%2C7s3%2C5%2C7%2C5s7-5%2C7-5S11%2C2%2C7%2C2z%20M7%2C10c-1.657%2C0-3-1.344-3-3c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3%20C10%2C8.656%2C8.657%2C10%2C7%2C10z%20M7%2C6C6.448%2C6%2C6%2C6.447%2C6%2C7c0%2C0.553%2C0.448%2C1%2C1%2C1s1-0.447%2C1-1C8%2C6.447%2C7.552%2C6%2C7%2C6z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-forbidden:after,
.ui-alt-icon .ui-icon-forbidden:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M12.601%2C11.187C13.476%2C10.018%2C14%2C8.572%2C14%2C7c0-3.866-3.134-7-7-7C5.428%2C0%2C3.982%2C0.524%2C2.813%2C1.399L2.757%2C1.343L2.053%2C2.048%20L2.048%2C2.053L1.343%2C2.758l0.056%2C0.056C0.524%2C3.982%2C0%2C5.428%2C0%2C7c0%2C3.866%2C3.134%2C7%2C7%2C7c1.572%2C0%2C3.018-0.524%2C4.187-1.399l0.056%2C0.057%20l0.705-0.705l0.005-0.005l0.705-0.705L12.601%2C11.187z%20M7%2C2c2.761%2C0%2C5%2C2.238%2C5%2C5c0%2C1.019-0.308%2C1.964-0.832%2C2.754L4.246%2C2.832%20C5.036%2C2.308%2C5.981%2C2%2C7%2C2z%20M7%2C12c-2.761%2C0-5-2.238-5-5c0-1.019%2C0.308-1.964%2C0.832-2.754l6.922%2C6.922C8.964%2C11.692%2C8.019%2C12%2C7%2C12z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-forward:after,
.ui-alt-icon .ui-icon-forward:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M13%2C4L9%2C0v3C6%2C3%2C1%2C4%2C1%2C8c0%2C5%2C7%2C6%2C7%2C6v-2c0%2C0-5-1-5-4s6-3%2C6-3v3L13%2C4z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-gear:after,
.ui-alt-icon .ui-icon-gear:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M13.621%2C5.904l-1.036-0.259c-0.168-0.042-0.303-0.168-0.355-0.332c-0.092-0.284-0.205-0.559-0.339-0.82%20c-0.079-0.153-0.073-0.337%2C0.017-0.486l0.549-0.915c0.118-0.196%2C0.088-0.448-0.075-0.61l-0.862-0.863%20c-0.162-0.163-0.414-0.193-0.611-0.075l-0.916%2C0.55C9.844%2C2.182%2C9.659%2C2.188%2C9.506%2C2.109C9.244%2C1.975%2C8.97%2C1.861%2C8.686%2C1.77%20c-0.165-0.052-0.29-0.187-0.332-0.354L8.095%2C0.379C8.039%2C0.156%2C7.839%2C0%2C7.609%2C0H6.391c-0.229%2C0-0.43%2C0.156-0.485%2C0.379L5.646%2C1.415%20C5.604%2C1.582%2C5.479%2C1.718%2C5.313%2C1.77c-0.284%2C0.092-0.559%2C0.206-0.82%2C0.34C4.339%2C2.188%2C4.155%2C2.182%2C4.007%2C2.093L3.092%2C1.544%20c-0.196-0.118-0.448-0.087-0.61%2C0.075L1.619%2C2.481C1.457%2C2.644%2C1.426%2C2.896%2C1.544%2C3.093l0.549%2C0.914%20c0.089%2C0.148%2C0.095%2C0.332%2C0.017%2C0.486C1.975%2C4.755%2C1.861%2C5.029%2C1.77%2C5.314c-0.053%2C0.164-0.188%2C0.29-0.354%2C0.332L0.379%2C5.905%20C0.156%2C5.961%2C0%2C6.161%2C0%2C6.391v1.219c0%2C0.229%2C0.156%2C0.43%2C0.379%2C0.485l1.036%2C0.26C1.582%2C8.396%2C1.717%2C8.521%2C1.77%2C8.687%20c0.092%2C0.284%2C0.205%2C0.559%2C0.34%2C0.82C2.188%2C9.66%2C2.182%2C9.844%2C2.093%2C9.993l-0.549%2C0.915c-0.118%2C0.195-0.087%2C0.448%2C0.075%2C0.61%20l0.862%2C0.862c0.162%2C0.163%2C0.414%2C0.193%2C0.61%2C0.075l0.915-0.549c0.148-0.089%2C0.332-0.095%2C0.486-0.017%20c0.262%2C0.135%2C0.536%2C0.248%2C0.82%2C0.34c0.165%2C0.053%2C0.291%2C0.187%2C0.332%2C0.354l0.259%2C1.036C5.96%2C13.844%2C6.16%2C14%2C6.39%2C14h1.22%20c0.229%2C0%2C0.43-0.156%2C0.485-0.379l0.259-1.036c0.042-0.167%2C0.168-0.302%2C0.333-0.354c0.284-0.092%2C0.559-0.205%2C0.82-0.34%20c0.154-0.078%2C0.338-0.072%2C0.486%2C0.017l0.914%2C0.549c0.197%2C0.118%2C0.449%2C0.088%2C0.611-0.074l0.862-0.863%20c0.163-0.162%2C0.193-0.415%2C0.075-0.611l-0.549-0.915c-0.089-0.148-0.096-0.332-0.017-0.485c0.134-0.263%2C0.248-0.536%2C0.339-0.82%20c0.053-0.165%2C0.188-0.291%2C0.355-0.333l1.036-0.259C13.844%2C8.039%2C14%2C7.839%2C14%2C7.609V6.39C14%2C6.16%2C13.844%2C5.96%2C13.621%2C5.904z%20M7%2C10%20c-1.657%2C0-3-1.343-3-3s1.343-3%2C3-3s3%2C1.343%2C3%2C3S8.657%2C10%2C7%2C10z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-grid:after,
.ui-alt-icon .ui-icon-grid:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M3%2C0H1C0.447%2C0%2C0%2C0.447%2C0%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V1C4%2C0.447%2C3.553%2C0%2C3%2C0z%20M8%2C0H6%20C5.447%2C0%2C5%2C0.447%2C5%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V1C9%2C0.447%2C8.553%2C0%2C8%2C0z%20M13%2C0h-2c-0.553%2C0-1%2C0.447-1%2C1v2%20c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V1C14%2C0.447%2C13.553%2C0%2C13%2C0z%20M3%2C5H1C0.447%2C5%2C0%2C5.447%2C0%2C6v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2%20c0.553%2C0%2C1-0.447%2C1-1V6C4%2C5.447%2C3.553%2C5%2C3%2C5z%20M8%2C5H6C5.447%2C5%2C5%2C5.447%2C5%2C6v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V6%20C9%2C5.447%2C8.553%2C5%2C8%2C5z%20M13%2C5h-2c-0.553%2C0-1%2C0.447-1%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1V6C14%2C5.447%2C13.553%2C5%2C13%2C5z%20M3%2C10%20H1c-0.553%2C0-1%2C0.447-1%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1v-2C4%2C10.447%2C3.553%2C10%2C3%2C10z%20M8%2C10H6c-0.553%2C0-1%2C0.447-1%2C1v2%20c0%2C0.553%2C0.447%2C1%2C1%2C1h2c0.553%2C0%2C1-0.447%2C1-1v-2C9%2C10.447%2C8.553%2C10%2C8%2C10z%20M13%2C10h-2c-0.553%2C0-1%2C0.447-1%2C1v2c0%2C0.553%2C0.447%2C1%2C1%2C1h2%20c0.553%2C0%2C1-0.447%2C1-1v-2C14%2C10.447%2C13.553%2C10%2C13%2C10z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-heart:after,
.ui-alt-icon .ui-icon-heart:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M7%2C1.958c-2-3-7-2.128-7%2C1.872c0%2C3%2C4%2C7%2C4%2C7s2.417%2C2.48%2C3%2C3c0.583-0.52%2C3-3%2C3-3s4-4%2C4-7C14-0.169%2C9-1.042%2C7%2C1.958z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-home:after,
.ui-alt-icon .ui-icon-home:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%227%2C0%200%2C7%202%2C7%202%2C14%205%2C14%205%2C9%209%2C9%209%2C14%2012%2C14%2012%2C7%2014%2C7%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-info:after,
.ui-alt-icon .ui-icon-info:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M7%2C0C3.134%2C0%2C0%2C3.134%2C0%2C7s3.134%2C7%2C7%2C7s7-3.134%2C7-7S10.866%2C0%2C7%2C0z%20M7%2C2c0.552%2C0%2C1%2C0.447%2C1%2C1S7.552%2C4%2C7%2C4S6%2C3.553%2C6%2C3%20S6.448%2C2%2C7%2C2z%20M9%2C11H5v-1h1V6H5V5h3v5h1V11z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-location:after,
.ui-alt-icon .ui-icon-location:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M7%2C0C4.791%2C0%2C3%2C1.791%2C3%2C4c0%2C2%2C4%2C10%2C4%2C10s4-8%2C4-10C11%2C1.791%2C9.209%2C0%2C7%2C0z%20M7%2C6C5.896%2C6%2C5%2C5.104%2C5%2C4s0.896-2%2C2-2%20c1.104%2C0%2C2%2C0.896%2C2%2C2S8.104%2C6%2C7%2C6z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-lock:after,
.ui-alt-icon .ui-icon-lock:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M12%2C6V5c0-2.762-2.238-5-5-5C4.239%2C0%2C2%2C2.238%2C2%2C5v1H1v8h12V6H12z%20M7.5%2C9.848V12h-1V9.848C6.207%2C9.673%2C6%2C9.366%2C6%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C8%2C9.366%2C7.793%2C9.673%2C7.5%2C9.848z%20M10%2C6H4V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-mail:after,
.ui-alt-icon .ui-icon-mail:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M0%2C3.75V12h14V3.75L7%2C9L0%2C3.75z%20M14%2C2H0l7%2C5L14%2C2z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-minus:after,
.ui-alt-icon .ui-icon-minus:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Crect%20y%3D%225%22%20width%3D%2214%22%20height%3D%224%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-navigation:after,
.ui-alt-icon .ui-icon-navigation:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2213%2C1%200%2C6%207%2C7%208%2C14%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-phone:after,
.ui-alt-icon .ui-icon-phone:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M6.949%2C9.182C6.175%2C8.549%2C5.281%2C7.697%2C4.507%2C6.736C3.963%2C6.063%2C3.483%2C5.355%2C3.979%2C4.858l-3.482-3.48%20c-0.508%2C0.634-1.633%2C3.654%2C3.188%2C8.598c5.08%2C5.211%2C8.356%2C4.097%2C8.92%2C3.511l-3.396-3.399C8.734%2C10.561%2C8.123%2C10.139%2C6.949%2C9.182z%20%20M13.83%2C11.512v-0.004c0%2C0-2.648-2.646-2.649-2.647c-0.21-0.212-0.546-0.205-0.754%2C0.002L9.465%2C9.823l3.402%2C3.407%20c0%2C0%2C0.963-0.961%2C0.961-0.961l0.002-0.002C14.053%2C12.049%2C14.031%2C11.713%2C13.83%2C11.512z%20M5.202%2C3.636V3.634%20c0.222-0.222%2C0.2-0.557%2C0-0.758V2.873c0%2C0-2.726-2.725-2.727-2.726c-0.21-0.21-0.545-0.205-0.753%2C0.001L0.761%2C1.113L4.24%2C4.595%20C4.241%2C4.596%2C5.202%2C3.637%2C5.202%2C3.636z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-plus:after,
.ui-alt-icon .ui-icon-plus:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2214%2C5%209%2C5%209%2C0%205%2C0%205%2C5%200%2C5%200%2C9%205%2C9%205%2C14%209%2C14%209%2C9%2014%2C9%20%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-power:after,
.ui-alt-icon .ui-icon-power:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11.243%2C2.408c-0.392-0.401-1.024-0.401-1.415%2C0c-0.391%2C0.401-0.391%2C1.054%2C0%2C1.455C10.584%2C4.642%2C11%2C5.675%2C11%2C6.773%20s-0.416%2C2.133-1.172%2C2.91c-1.512%2C1.558-4.145%2C1.558-5.656%2C0C3.416%2C8.904%2C3%2C7.872%2C3%2C6.773C3%2C5.673%2C3.416%2C4.64%2C4.172%2C3.863%20c0.39-0.401%2C0.39-1.054%2C0-1.455c-0.391-0.401-1.024-0.401-1.415%2C0C1.624%2C3.574%2C1%2C5.125%2C1%2C6.773c0%2C1.647%2C0.624%2C3.199%2C1.757%2C4.365%20c1.134%2C1.166%2C2.64%2C1.809%2C4.243%2C1.809c1.604%2C0%2C3.109-0.645%2C4.243-1.811C12.376%2C9.975%2C13%2C8.423%2C13%2C6.773%20C13%2C5.125%2C12.376%2C3.574%2C11.243%2C2.408z%20M7%2C8.053c0.553%2C0%2C1-0.445%2C1-1v-6c0-0.553-0.447-1-1-1c-0.553%2C0-1%2C0.447-1%2C1v6%20C6%2C7.604%2C6.447%2C8.053%2C7%2C8.053z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-recycle:after,
.ui-alt-icon .ui-icon-recycle:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M3%2C7h1L2%2C4L0%2C7h1c0%2C3.313%2C2.687%2C6%2C6%2C6c0.702%2C0%2C1.374-0.127%2C2-0.35v-2.205C8.41%2C10.789%2C7.732%2C11%2C7%2C11C4.791%2C11%2C3%2C9.209%2C3%2C7z%20%20M13%2C7c0-3.313-2.688-6-6-6C6.298%2C1%2C5.626%2C1.127%2C5%2C1.349v2.206C5.59%2C3.211%2C6.268%2C3%2C7%2C3c2.209%2C0%2C4%2C1.791%2C4%2C4h-1l2%2C3l2-3H13z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-refresh:after,
.ui-alt-icon .ui-icon-refresh:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214.001px%22%20height%3D%2214.002px%22%20viewBox%3D%220%200%2014.001%2014.002%22%20style%3D%22enable-background%3Anew%200%200%2014.001%2014.002%3B%22%20%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M14.001%2C6.001v-6l-2.06%2C2.06c-0.423-0.424-0.897-0.809-1.44-1.122C7.153-0.994%2C2.872%2C0.153%2C0.939%2C3.501%20c-1.933%2C3.348-0.786%2C7.629%2C2.562%2C9.562c3.348%2C1.933%2C7.629%2C0.785%2C9.562-2.562l-1.732-1c-1.381%2C2.392-4.438%2C3.211-6.83%2C1.83%20s-3.211-4.438-1.83-6.83s4.438-3.211%2C6.83-1.83c0.389%2C0.225%2C0.718%2C0.506%2C1.02%2C0.81l-2.52%2C2.52H14.001z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-search:after,
.ui-alt-icon .ui-icon-search:after,
.ui-input-search:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M10.171%2C8.766c0.617-0.888%2C0.979-1.964%2C0.979-3.126c0-3.037-2.463-5.5-5.5-5.5s-5.5%2C2.463-5.5%2C5.5s2.463%2C5.5%2C5.5%2C5.5%20c1.152%2C0%2C2.223-0.355%2C3.104-0.962l3.684%2C3.683l1.414-1.414L10.171%2C8.766z%20M5.649%2C9.14c-1.933%2C0-3.5-1.567-3.5-3.5%20c0-1.933%2C1.567-3.5%2C3.5-3.5c1.933%2C0%2C3.5%2C1.567%2C3.5%2C3.5C9.149%2C7.572%2C7.582%2C9.14%2C5.649%2C9.14z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-shop:after,
.ui-alt-icon .ui-icon-shop:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M10%2C4V3c0-1.657-1.343-3-3-3S4%2C1.343%2C4%2C3v1H1v10h12V4H10z%20M4.5%2C6C4.224%2C6%2C4%2C5.776%2C4%2C5.5S4.224%2C5%2C4.5%2C5S5%2C5.224%2C5%2C5.5%20S4.776%2C6%2C4.5%2C6z%20M5%2C3c0-1.104%2C0.896-2%2C2-2c1.104%2C0%2C2%2C0.896%2C2%2C2v1H5V3z%20M9.5%2C6C9.225%2C6%2C9%2C5.776%2C9%2C5.5S9.225%2C5%2C9.5%2C5S10%2C5.224%2C10%2C5.5%20S9.775%2C6%2C9.5%2C6z%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-star:after,
.ui-alt-icon .ui-icon-star:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpolygon%20points%3D%2214%2C5%209%2C5%207%2C0%205%2C5%200%2C5%204%2C8%202.625%2C13%207%2C10%2011.375%2C13%2010%2C8%20%22%2F%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-tag:after,
.ui-alt-icon .ui-icon-tag:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M5%2C0H0v5l9%2C9l5-5L5%2C0z%20M3%2C4C2.447%2C4%2C2%2C3.553%2C2%2C3s0.447-1%2C1-1s1%2C0.447%2C1%2C1S3.553%2C4%2C3%2C4z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-user:after,
.ui-alt-icon .ui-icon-user:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22enable-background%3Anew%200%200%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M8.851%2C10.101c-0.18-0.399-0.2-0.763-0.153-1.104C9.383%2C8.49%2C9.738%2C7.621%2C9.891%2C6.465C10.493%2C6.355%2C10.5%2C5.967%2C10.5%2C5.5%20c0-0.437-0.008-0.804-0.502-0.94C9.999%2C4.539%2C10%2C4.521%2C10%2C4.5c0-2.103-1-4-2-4C8%2C0.5%2C7.5%2C0%2C6.5%2C0C5%2C0%2C4%2C1.877%2C4%2C4.5%20c0%2C0.021%2C0.001%2C0.039%2C0.002%2C0.06C3.508%2C4.696%2C3.5%2C5.063%2C3.5%2C5.5c0%2C0.467%2C0.007%2C0.855%2C0.609%2C0.965%20C4.262%2C7.621%2C4.617%2C8.49%2C5.303%2C8.997c0.047%2C0.341%2C0.026%2C0.704-0.153%2C1.104C1.503%2C10.503%2C0%2C12%2C0%2C12v2h14v-2%20C14%2C12%2C12.497%2C10.503%2C8.851%2C10.101z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.ui-alt-icon.ui-icon-video:after,
.ui-alt-icon .ui-icon-video:after {
background-image: url("data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%20-2%2014%2014%22%20style%3D%22enable-background%3Anew%200%20-2%2014%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M8%2C0H2C0.896%2C0%2C0%2C0.896%2C0%2C2v6c0%2C1.104%2C0.896%2C2%2C2%2C2h6c1.104%2C0%2C2-0.896%2C2-2V5V2C10%2C0.896%2C9.104%2C0%2C8%2C0z%20M10%2C5l4%2C4V1L10%2C5z%22%2F%3E%3C%2Fsvg%3E");
}
/* PNG icons */
.ui-nosvg .ui-icon-action:after {
background-image: url(images/icons-png/action-white.png);
}
.ui-nosvg .ui-icon-alert:after {
background-image: url(images/icons-png/alert-white.png);
}
.ui-nosvg .ui-icon-arrow-d-l:after {
background-image: url(images/icons-png/arrow-d-l-white.png);
}
.ui-nosvg .ui-icon-arrow-d-r:after {
background-image: url(images/icons-png/arrow-d-r-white.png);
}
.ui-nosvg .ui-icon-arrow-d:after {
background-image: url(images/icons-png/arrow-d-white.png);
}
.ui-nosvg .ui-icon-arrow-l:after {
background-image: url(images/icons-png/arrow-l-white.png);
}
.ui-nosvg .ui-icon-arrow-r:after {
background-image: url(images/icons-png/arrow-r-white.png);
}
.ui-nosvg .ui-icon-arrow-u-l:after {
background-image: url(images/icons-png/arrow-u-l-white.png);
}
.ui-nosvg .ui-icon-arrow-u-r:after {
background-image: url(images/icons-png/arrow-u-r-white.png);
}
.ui-nosvg .ui-icon-arrow-u:after {
background-image: url(images/icons-png/arrow-u-white.png);
}
.ui-nosvg .ui-icon-audio:after {
background-image: url(images/icons-png/audio-white.png);
}
.ui-nosvg .ui-icon-back:after {
background-image: url(images/icons-png/back-white.png);
}
.ui-nosvg .ui-icon-bars:after {
background-image: url(images/icons-png/bars-white.png);
}
.ui-nosvg .ui-icon-bullets:after {
background-image: url(images/icons-png/bullets-white.png);
}
.ui-nosvg .ui-icon-calendar:after {
background-image: url(images/icons-png/calendar-white.png);
}
.ui-nosvg .ui-icon-camera:after {
background-image: url(images/icons-png/camera-white.png);
}
.ui-nosvg .ui-icon-carat-d:after {
background-image: url(images/icons-png/carat-d-white.png);
}
.ui-nosvg .ui-icon-carat-l:after {
background-image: url(images/icons-png/carat-l-white.png);
}
.ui-nosvg .ui-icon-carat-r:after {
background-image: url(images/icons-png/carat-r-white.png);
}
.ui-nosvg .ui-icon-carat-u:after {
background-image: url(images/icons-png/carat-u-white.png);
}
.ui-nosvg .ui-icon-check:after,
html.ui-nosvg .ui-btn.ui-checkbox-on:after {
background-image: url(images/icons-png/check-white.png);
}
.ui-nosvg .ui-icon-clock:after {
background-image: url(images/icons-png/clock-white.png);
}
.ui-nosvg .ui-icon-cloud:after {
background-image: url(images/icons-png/cloud-white.png);
}
.ui-nosvg .ui-icon-comment:after {
background-image: url(images/icons-png/comment-white.png);
}
.ui-nosvg .ui-icon-delete:after {
background-image: url(images/icons-png/delete-white.png);
}
.ui-nosvg .ui-icon-edit:after {
background-image: url(images/icons-png/edit-white.png);
}
.ui-nosvg .ui-icon-eye:after {
background-image: url(images/icons-png/eye-white.png);
}
.ui-nosvg .ui-icon-forbidden:after {
background-image: url(images/icons-png/forbidden-white.png);
}
.ui-nosvg .ui-icon-forward:after {
background-image: url(images/icons-png/forward-white.png);
}
.ui-nosvg .ui-icon-gear:after {
background-image: url(images/icons-png/gear-white.png);
}
.ui-nosvg .ui-icon-grid:after {
background-image: url(images/icons-png/grid-white.png);
}
.ui-nosvg .ui-icon-heart:after {
background-image: url(images/icons-png/heart-white.png);
}
.ui-nosvg .ui-icon-home:after {
background-image: url(images/icons-png/home-white.png);
}
.ui-nosvg .ui-icon-info:after {
background-image: url(images/icons-png/info-white.png);
}
.ui-nosvg .ui-icon-location:after {
background-image: url(images/icons-png/location-white.png);
}
.ui-nosvg .ui-icon-lock:after {
background-image: url(images/icons-png/lock-white.png);
}
.ui-nosvg .ui-icon-mail:after {
background-image: url(images/icons-png/mail-white.png);
}
.ui-nosvg .ui-icon-minus:after {
background-image: url(images/icons-png/minus-white.png);
}
.ui-nosvg .ui-icon-navigation:after {
background-image: url(images/icons-png/navigation-white.png);
}
.ui-nosvg .ui-icon-phone:after {
background-image: url(images/icons-png/phone-white.png);
}
.ui-nosvg .ui-icon-plus:after {
background-image: url(images/icons-png/plus-white.png);
}
.ui-nosvg .ui-icon-power:after {
background-image: url(images/icons-png/power-white.png);
}
.ui-nosvg .ui-icon-recycle:after {
background-image: url(images/icons-png/recycle-white.png);
}
.ui-nosvg .ui-icon-refresh:after {
background-image: url(images/icons-png/refresh-white.png);
}
.ui-nosvg .ui-icon-search:after {
background-image: url(images/icons-png/search-white.png);
}
.ui-nosvg .ui-icon-shop:after {
background-image: url(images/icons-png/shop-white.png);
}
.ui-nosvg .ui-icon-star:after {
background-image: url(images/icons-png/star-white.png);
}
.ui-nosvg .ui-icon-tag:after {
background-image: url(images/icons-png/tag-white.png);
}
.ui-nosvg .ui-icon-user:after {
background-image: url(images/icons-png/user-white.png);
}
.ui-nosvg .ui-icon-video:after {
background-image: url(images/icons-png/video-white.png);
}
/* Alt icons */
.ui-nosvg .ui-alt-icon.ui-icon-action:after,
.ui-nosvg .ui-alt-icon .ui-icon-action:after {
background-image: url(images/icons-png/action-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-alert:after,
.ui-nosvg .ui-alt-icon .ui-icon-alert:after {
background-image: url(images/icons-png/alert-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-arrow-d:after,
.ui-nosvg .ui-alt-icon .ui-icon-arrow-d:after {
background-image: url(images/icons-png/arrow-d-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-arrow-d-l:after,
.ui-nosvg .ui-alt-icon .ui-icon-arrow-d-l:after {
background-image: url(images/icons-png/arrow-d-l-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-arrow-d-r:after,
.ui-nosvg .ui-alt-icon .ui-icon-arrow-d-r:after {
background-image: url(images/icons-png/arrow-d-r-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-arrow-l:after,
.ui-nosvg .ui-alt-icon .ui-icon-arrow-l:after {
background-image: url(images/icons-png/arrow-l-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-arrow-r:after,
.ui-nosvg .ui-alt-icon .ui-icon-arrow-r:after {
background-image: url(images/icons-png/arrow-r-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-arrow-u:after,
.ui-nosvg .ui-alt-icon .ui-icon-arrow-u:after {
background-image: url(images/icons-png/arrow-u-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-arrow-u-l:after,
.ui-nosvg .ui-alt-icon .ui-icon-arrow-u-l:after {
background-image: url(images/icons-png/arrow-u-l-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-arrow-u-r:after,
.ui-nosvg .ui-alt-icon .ui-icon-arrow-u-r:after {
background-image: url(images/icons-png/arrow-u-r-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-audio:after,
.ui-nosvg .ui-alt-icon .ui-icon-audio:after {
background-image: url(images/icons-png/audio-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-back:after,
.ui-nosvg .ui-alt-icon .ui-icon-back:after {
background-image: url(images/icons-png/back-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-bars:after,
.ui-nosvg .ui-alt-icon .ui-icon-bars:after {
background-image: url(images/icons-png/bars-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-bullets:after,
.ui-nosvg .ui-alt-icon .ui-icon-bullets:after {
background-image: url(images/icons-png/bullets-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-calendar:after,
.ui-nosvg .ui-alt-icon .ui-icon-calendar:after {
background-image: url(images/icons-png/calendar-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-camera:after,
.ui-nosvg .ui-alt-icon .ui-icon-camera:after {
background-image: url(images/icons-png/camera-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-carat-d:after,
.ui-nosvg .ui-alt-icon .ui-icon-carat-d:after {
background-image: url(images/icons-png/carat-d-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-carat-l:after,
.ui-nosvg .ui-alt-icon .ui-icon-carat-l:after {
background-image: url(images/icons-png/carat-l-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-carat-r:after,
.ui-nosvg .ui-alt-icon .ui-icon-carat-r:after {
background-image: url(images/icons-png/carat-r-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-carat-u:after,
.ui-nosvg .ui-alt-icon .ui-icon-carat-u:after {
background-image: url(images/icons-png/carat-u-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-check:after,
.ui-nosvg .ui-alt-icon .ui-icon-check:after,
.ui-nosvg .ui-alt-icon.ui-btn.ui-checkbox-on:after,
.ui-nosvg .ui-alt-icon .ui-btn.ui-checkbox-on:after {
background-image: url(images/icons-png/check-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-clock:after,
.ui-nosvg .ui-alt-icon .ui-icon-clock:after {
background-image: url(images/icons-png/clock-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-cloud:after,
.ui-nosvg .ui-alt-icon .ui-icon-cloud:after {
background-image: url(images/icons-png/cloud-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-comment:after,
.ui-nosvg .ui-alt-icon .ui-icon-comment:after {
background-image: url(images/icons-png/comment-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-delete:after,
.ui-nosvg .ui-alt-icon .ui-icon-delete:after {
background-image: url(images/icons-png/delete-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-edit:after,
.ui-nosvg .ui-alt-icon .ui-icon-edit:after {
background-image: url(images/icons-png/edit-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-eye:after,
.ui-nosvg .ui-alt-icon .ui-icon-eye:after {
background-image: url(images/icons-png/eye-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-forbidden:after,
.ui-nosvg .ui-alt-icon .ui-icon-forbidden:after {
background-image: url(images/icons-png/forbidden-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-forward:after,
.ui-nosvg .ui-alt-icon .ui-icon-forward:after {
background-image: url(images/icons-png/forward-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-gear:after,
.ui-nosvg .ui-alt-icon .ui-icon-gear:after {
background-image: url(images/icons-png/gear-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-grid:after,
.ui-nosvg .ui-alt-icon .ui-icon-grid:after {
background-image: url(images/icons-png/grid-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-heart:after,
.ui-nosvg .ui-alt-icon .ui-icon-heart:after {
background-image: url(images/icons-png/heart-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-home:after,
.ui-nosvg .ui-alt-icon .ui-icon-home:after {
background-image: url(images/icons-png/home-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-info:after,
.ui-nosvg .ui-alt-icon .ui-icon-info:after {
background-image: url(images/icons-png/info-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-location:after,
.ui-nosvg .ui-alt-icon .ui-icon-location:after {
background-image: url(images/icons-png/location-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-lock:after,
.ui-nosvg .ui-alt-icon .ui-icon-lock:after {
background-image: url(images/icons-png/lock-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-mail:after,
.ui-nosvg .ui-alt-icon .ui-icon-mail:after {
background-image: url(images/icons-png/mail-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-minus:after,
.ui-nosvg .ui-alt-icon .ui-icon-minus:after {
background-image: url(images/icons-png/minus-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-navigation:after,
.ui-nosvg .ui-alt-icon .ui-icon-navigation:after {
background-image: url(images/icons-png/navigation-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-phone:after,
.ui-nosvg .ui-alt-icon .ui-icon-phone:after {
background-image: url(images/icons-png/phone-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-plus:after,
.ui-nosvg .ui-alt-icon .ui-icon-plus:after {
background-image: url(images/icons-png/plus-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-power:after,
.ui-nosvg .ui-alt-icon .ui-icon-power:after {
background-image: url(images/icons-png/power-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-recycle:after,
.ui-nosvg .ui-alt-icon .ui-icon-recycle:after {
background-image: url(images/icons-png/recycle-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-refresh:after,
.ui-nosvg .ui-alt-icon .ui-icon-refresh:after {
background-image: url(images/icons-png/refresh-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-search:after,
.ui-nosvg .ui-alt-icon .ui-icon-search:after,
.ui-nosvg .ui-input-search:after {
background-image: url(images/icons-png/search-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-shop:after,
.ui-nosvg .ui-alt-icon .ui-icon-shop:after {
background-image: url(images/icons-png/shop-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-star:after,
.ui-nosvg .ui-alt-icon .ui-icon-star:after {
background-image: url(images/icons-png/star-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-tag:after,
.ui-nosvg .ui-alt-icon .ui-icon-tag:after {
background-image: url(images/icons-png/tag-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-user:after,
.ui-nosvg .ui-alt-icon .ui-icon-user:after {
background-image: url(images/icons-png/user-black.png);
}
.ui-nosvg .ui-alt-icon.ui-icon-video:after,
.ui-nosvg .ui-alt-icon .ui-icon-video:after {
background-image: url(images/icons-png/video-black.png);
}
/* Globals */
/* Font
-----------------------------------------------------------------------------------------------------------*/
html {
font-size: 100%;
}
body,
input,
select,
textarea,
button,
.ui-btn {
font-size: 1em;
line-height: 1.3;
font-family: sans-serif /*{global-font-family}*/;
}
legend,
.ui-input-text input,
.ui-input-search input {
color: inherit;
text-shadow: inherit;
}
/* Form labels (overrides font-weight bold in bars, and mini font-size) */
.ui-mobile label,
div.ui-controlgroup-label {
font-weight: normal;
font-size: 16px;
}
/* Separators
-----------------------------------------------------------------------------------------------------------*/
/* Field contain separator (< 28em) */
.ui-field-contain {
border-bottom-color: #828282;
border-bottom-color: rgba(0,0,0,.15);
border-bottom-width: 1px;
border-bottom-style: solid;
}
/* Table opt-in classes: strokes between each row, and alternating row stripes */
/* Classes table-stroke and table-stripe are deprecated in 1.4. */
.table-stroke thead th,
.table-stripe thead th,
.table-stripe tbody tr:last-child {
border-bottom: 1px solid #d6d6d6; /* non-RGBA fallback */
border-bottom: 1px solid rgba(0,0,0,.1);
}
.table-stroke tbody th,
.table-stroke tbody td {
border-bottom: 1px solid #e6e6e6; /* non-RGBA fallback */
border-bottom: 1px solid rgba(0,0,0,.05);
}
.table-stripe.table-stroke tbody tr:last-child th,
.table-stripe.table-stroke tbody tr:last-child td {
border-bottom: 0;
}
.table-stripe tbody tr:nth-child(odd) td,
.table-stripe tbody tr:nth-child(odd) th {
background-color: #eeeeee; /* non-RGBA fallback */
background-color: rgba(0,0,0,.04);
}
/* Buttons
-----------------------------------------------------------------------------------------------------------*/
.ui-btn,
label.ui-btn {
font-weight: bold;
border-width: 1px;
border-style: solid;
}
.ui-btn {
text-decoration: none !important;
}
.ui-btn-active {
cursor: pointer;
}
/* Corner rounding
-----------------------------------------------------------------------------------------------------------*/
/* Class ui-btn-corner-all deprecated in 1.4 */
.ui-corner-all {
-webkit-border-radius: .3125em /*{global-radii-blocks}*/;
border-radius: .3125em /*{global-radii-blocks}*/;
}
/* Buttons */
.ui-btn-corner-all,
.ui-btn.ui-corner-all,
/* Slider track */
.ui-slider-track.ui-corner-all,
/* Flipswitch */
.ui-flipswitch.ui-corner-all,
/* Count bubble */
.ui-li-count {
-webkit-border-radius: .3125em /*{global-radii-buttons}*/;
border-radius: .3125em /*{global-radii-buttons}*/;
}
/* Icon-only buttons */
.ui-btn-icon-notext.ui-btn-corner-all,
.ui-btn-icon-notext.ui-corner-all {
-webkit-border-radius: 1em;
border-radius: 1em;
}
/* Radius clip workaround for cleaning up corner trapping */
.ui-btn-corner-all,
.ui-corner-all {
-webkit-background-clip: padding;
background-clip: padding-box;
}
/* Popup arrow */
.ui-popup.ui-corner-all > .ui-popup-arrow-guide {
left: .6em /*{global-radii-blocks}*/;
right: .6em /*{global-radii-blocks}*/;
top: .6em /*{global-radii-blocks}*/;
bottom: .6em /*{global-radii-blocks}*/;
}
/* Shadow
-----------------------------------------------------------------------------------------------------------*/
.ui-shadow {
-webkit-box-shadow: 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.15) /*{global-box-shadow-color}*/;
-moz-box-shadow: 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.15) /*{global-box-shadow-color}*/;
box-shadow: 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.15) /*{global-box-shadow-color}*/;
}
.ui-shadow-inset {
-webkit-box-shadow: inset 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/;
-moz-box-shadow: inset 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/;
box-shadow: inset 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/;
}
.ui-overlay-shadow {
-webkit-box-shadow: 0 0 12px rgba(0,0,0,.6);
-moz-box-shadow: 0 0 12px rgba(0,0,0,.6);
box-shadow: 0 0 12px rgba(0,0,0,.6);
}
/* Icons
-----------------------------------------------------------------------------------------------------------*/
.ui-btn-icon-left:after,
.ui-btn-icon-right:after,
.ui-btn-icon-top:after,
.ui-btn-icon-bottom:after,
.ui-btn-icon-notext:after {
background-color: #666 /*{global-icon-color}*/;
background-color: rgba(0,0,0,.3) /*{global-icon-disc}*/;
background-position: center center;
background-repeat: no-repeat;
-webkit-border-radius: 1em;
border-radius: 1em;
}
/* Alt icons */
.ui-alt-icon.ui-btn:after,
.ui-alt-icon .ui-btn:after,
html .ui-alt-icon.ui-checkbox-off:after,
html .ui-alt-icon.ui-radio-off:after,
html .ui-alt-icon .ui-checkbox-off:after,
html .ui-alt-icon .ui-radio-off:after {
background-color: #666 /*{global-icon-color}*/;
background-color: rgba(0,0,0,.15) /*{global-icon-disc}*/;
}
/* No disc */
.ui-nodisc-icon.ui-btn:after,
.ui-nodisc-icon .ui-btn:after {
background-color: transparent;
}
/* Icon shadow */
.ui-shadow-icon.ui-btn:after,
.ui-shadow-icon .ui-btn:after {
-webkit-box-shadow: 0 1px 0 rgba(255,255,255,.3) /*{global-icon-shadow}*/;
-moz-box-shadow: 0 1px 0 rgba(255,255,255,.3) /*{global-icon-shadow}*/;
box-shadow: 0 1px 0 rgba(255,255,255,.3) /*{global-icon-shadow}*/;
}
/* Checkbox and radio */
.ui-btn.ui-checkbox-off:after,
.ui-btn.ui-checkbox-on:after,
.ui-btn.ui-radio-off:after,
.ui-btn.ui-radio-on:after {
display: block;
width: 18px;
height: 18px;
margin: -9px 2px 0 2px;
}
.ui-checkbox-off:after,
.ui-btn.ui-radio-off:after {
filter: Alpha(Opacity=30);
opacity: .3;
}
.ui-btn.ui-checkbox-off:after,
.ui-btn.ui-checkbox-on:after {
-webkit-border-radius: .1875em;
border-radius: .1875em;
}
.ui-btn.ui-checkbox-off:after {
background-color: #666;
background-color: rgba(0,0,0,.3);
}
.ui-radio .ui-btn.ui-radio-on:after {
background-image: none;
background-color: #fff;
width: 8px;
height: 8px;
border-width: 5px;
border-style: solid;
}
.ui-alt-icon.ui-btn.ui-radio-on:after,
.ui-alt-icon .ui-btn.ui-radio-on:after {
background-color: #000;
}
/* Loader */
.ui-icon-loading {
background: url(images/ajax-loader.gif);
background-size: 2.875em 2.875em;
}
/* Swatches */
/* A
-----------------------------------------------------------------------------------------------------------*/
/* Bar: Toolbars, dividers, slider track */
.ui-bar-a,
.ui-page-theme-a .ui-bar-inherit,
html .ui-bar-a .ui-bar-inherit,
html .ui-body-a .ui-bar-inherit,
html body .ui-group-theme-a .ui-bar-inherit {
background-color: #e9e9e9 /*{a-bar-background-color}*/;
border-color: #ddd /*{a-bar-border}*/;
color: #333 /*{a-bar-color}*/;
text-shadow: 0 /*{a-bar-shadow-x}*/ 1px /*{a-bar-shadow-y}*/ 0 /*{a-bar-shadow-radius}*/ #eee /*{a-bar-shadow-color}*/;
font-weight: bold;
}
.ui-bar-a {
border-width: 1px;
border-style: solid;
}
/* Page and overlay */
.ui-overlay-a,
.ui-page-theme-a,
.ui-page-theme-a .ui-panel-wrapper {
background-color: #f9f9f9 /*{a-page-background-color}*/;
border-color: #bbb /*{a-page-border}*/;
color: #333 /*{a-page-color}*/;
text-shadow: 0 /*{a-page-shadow-x}*/ 1px /*{a-page-shadow-y}*/ 0 /*{a-page-shadow-radius}*/ #f3f3f3 /*{a-page-shadow-color}*/;
}
/* Body: Read-only lists, text inputs, collapsible content */
.ui-body-a,
.ui-page-theme-a .ui-body-inherit,
html .ui-bar-a .ui-body-inherit,
html .ui-body-a .ui-body-inherit,
html body .ui-group-theme-a .ui-body-inherit,
html .ui-panel-page-container-a {
background-color: #fff /*{a-body-background-color}*/;
border-color: #ddd /*{a-body-border}*/;
color: #333 /*{a-body-color}*/;
text-shadow: 0 /*{a-body-shadow-x}*/ 1px /*{a-body-shadow-y}*/ 0 /*{a-body-shadow-radius}*/ #f3f3f3 /*{a-body-shadow-color}*/;
}
.ui-body-a {
border-width: 1px;
border-style: solid;
}
/* Links */
.ui-page-theme-a a,
html .ui-bar-a a,
html .ui-body-a a,
html body .ui-group-theme-a a {
color: #3388cc /*{a-link-color}*/;
font-weight: bold;
}
.ui-page-theme-a a:visited,
html .ui-bar-a a:visited,
html .ui-body-a a:visited,
html body .ui-group-theme-a a:visited {
color: #3388cc /*{a-link-visited}*/;
}
.ui-page-theme-a a:hover,
html .ui-bar-a a:hover,
html .ui-body-a a:hover,
html body .ui-group-theme-a a:hover {
color: #005599 /*{a-link-hover}*/;
}
.ui-page-theme-a a:active,
html .ui-bar-a a:active,
html .ui-body-a a:active,
html body .ui-group-theme-a a:active {
color: #005599 /*{a-link-active}*/;
}
/* Button up */
.ui-page-theme-a .ui-btn,
html .ui-bar-a .ui-btn,
html .ui-body-a .ui-btn,
html body .ui-group-theme-a .ui-btn,
html head + body .ui-btn.ui-btn-a,
/* Button visited */
.ui-page-theme-a .ui-btn:visited,
html .ui-bar-a .ui-btn:visited,
html .ui-body-a .ui-btn:visited,
html body .ui-group-theme-a .ui-btn:visited,
html head + body .ui-btn.ui-btn-a:visited {
background-color: #f6f6f6 /*{a-bup-background-color}*/;
border-color: #ddd /*{a-bup-border}*/;
color: #333 /*{a-bup-color}*/;
text-shadow: 0 /*{a-bup-shadow-x}*/ 1px /*{a-bup-shadow-y}*/ 0 /*{a-bup-shadow-radius}*/ #f3f3f3 /*{a-bup-shadow-color}*/;
}
/* Button hover */
.ui-page-theme-a .ui-btn:hover,
html .ui-bar-a .ui-btn:hover,
html .ui-body-a .ui-btn:hover,
html body .ui-group-theme-a .ui-btn:hover,
html head + body .ui-btn.ui-btn-a:hover {
background-color: #ededed /*{a-bhover-background-color}*/;
border-color: #ddd /*{a-bhover-border}*/;
color: #333 /*{a-bhover-color}*/;
text-shadow: 0 /*{a-bhover-shadow-x}*/ 1px /*{a-bhover-shadow-y}*/ 0 /*{a-bhover-shadow-radius}*/ #f3f3f3 /*{a-bhover-shadow-color}*/;
}
/* Button down */
.ui-page-theme-a .ui-btn:active,
html .ui-bar-a .ui-btn:active,
html .ui-body-a .ui-btn:active,
html body .ui-group-theme-a .ui-btn:active,
html head + body .ui-btn.ui-btn-a:active {
background-color: #e8e8e8 /*{a-bdown-background-color}*/;
border-color: #ddd /*{a-bdown-border}*/;
color: #333 /*{a-bdown-color}*/;
text-shadow: 0 /*{a-bdown-shadow-x}*/ 1px /*{a-bdown-shadow-y}*/ 0 /*{a-bdown-shadow-radius}*/ #f3f3f3 /*{a-bdown-shadow-color}*/;
}
/* Active button */
.ui-page-theme-a .ui-btn.ui-btn-active,
html .ui-bar-a .ui-btn.ui-btn-active,
html .ui-body-a .ui-btn.ui-btn-active,
html body .ui-group-theme-a .ui-btn.ui-btn-active,
html head + body .ui-btn.ui-btn-a.ui-btn-active,
/* Active checkbox icon */
.ui-page-theme-a .ui-checkbox-on:after,
html .ui-bar-a .ui-checkbox-on:after,
html .ui-body-a .ui-checkbox-on:after,
html body .ui-group-theme-a .ui-checkbox-on:after,
.ui-btn.ui-checkbox-on.ui-btn-a:after,
/* Active flipswitch background */
.ui-page-theme-a .ui-flipswitch-active,
html .ui-bar-a .ui-flipswitch-active,
html .ui-body-a .ui-flipswitch-active,
html body .ui-group-theme-a .ui-flipswitch-active,
html body .ui-flipswitch.ui-bar-a.ui-flipswitch-active,
/* Active slider track */
.ui-page-theme-a .ui-slider-track .ui-btn-active,
html .ui-bar-a .ui-slider-track .ui-btn-active,
html .ui-body-a .ui-slider-track .ui-btn-active,
html body .ui-group-theme-a .ui-slider-track .ui-btn-active,
html body div.ui-slider-track.ui-body-a .ui-btn-active {
background-color: #3388cc /*{a-active-background-color}*/;
border-color: #3388cc /*{a-active-border}*/;
color: #fff /*{a-active-color}*/;
text-shadow: 0 /*{a-active-shadow-x}*/ 1px /*{a-active-shadow-y}*/ 0 /*{a-active-shadow-radius}*/ #005599 /*{a-active-shadow-color}*/;
}
/* Active radio button icon */
.ui-page-theme-a .ui-radio-on:after,
html .ui-bar-a .ui-radio-on:after,
html .ui-body-a .ui-radio-on:after,
html body .ui-group-theme-a .ui-radio-on:after,
.ui-btn.ui-radio-on.ui-btn-a:after {
border-color: #3388cc /*{a-active-background-color}*/;
}
/* Focus */
.ui-page-theme-a .ui-btn:focus,
html .ui-bar-a .ui-btn:focus,
html .ui-body-a .ui-btn:focus,
html body .ui-group-theme-a .ui-btn:focus,
html head + body .ui-btn.ui-btn-a:focus,
/* Focus buttons and text inputs with div wrap */
.ui-page-theme-a .ui-focus,
html .ui-bar-a .ui-focus,
html .ui-body-a .ui-focus,
html body .ui-group-theme-a .ui-focus,
html head + body .ui-btn-a.ui-focus,
html head + body .ui-body-a.ui-focus {
-webkit-box-shadow: 0 0 12px #3388cc /*{a-active-background-color}*/;
-moz-box-shadow: 0 0 12px #3388cc /*{a-active-background-color}*/;
box-shadow: 0 0 12px #3388cc /*{a-active-background-color}*/;
}
/* B
-----------------------------------------------------------------------------------------------------------*/
/* Bar: Toolbars, dividers, slider track */
.ui-bar-b,
.ui-page-theme-b .ui-bar-inherit,
html .ui-bar-b .ui-bar-inherit,
html .ui-body-b .ui-bar-inherit,
html body .ui-group-theme-b .ui-bar-inherit {
background-color: #1d1d1d /*{b-bar-background-color}*/;
border-color: #1b1b1b /*{b-bar-border}*/;
color: #fff /*{b-bar-color}*/;
text-shadow: 0 /*{b-bar-shadow-x}*/ 1px /*{b-bar-shadow-y}*/ 0 /*{b-bar-shadow-radius}*/ #111 /*{b-bar-shadow-color}*/;
font-weight: bold;
}
.ui-bar-b {
border-width: 1px;
border-style: solid;
}
/* Page and overlay */
.ui-overlay-b,
.ui-page-theme-b,
.ui-page-theme-b .ui-panel-wrapper {
background-color: #252525 /*{b-page-background-color}*/;
border-color: #454545 /*{b-page-border}*/;
color: #fff /*{b-page-color}*/;
text-shadow: 0 /*{b-page-shadow-x}*/ 1px /*{b-page-shadow-y}*/ 0 /*{b-page-shadow-radius}*/ #111 /*{b-page-shadow-color}*/;
}
/* Body: Read-only lists, text inputs, collapsible content */
.ui-body-b,
.ui-page-theme-b .ui-body-inherit,
html .ui-bar-b .ui-body-inherit,
html .ui-body-b .ui-body-inherit,
html body .ui-group-theme-b .ui-body-inherit,
html .ui-panel-page-container-b {
background-color: #2a2a2a /*{b-body-background-color}*/;
border-color: #1d1d1d /*{b-body-border}*/;
color: #fff /*{b-body-color}*/;
text-shadow: 0 /*{b-body-shadow-x}*/ 1px /*{b-body-shadow-y}*/ 0 /*{b-body-shadow-radius}*/ #111 /*{b-body-shadow-color}*/;
}
.ui-body-b {
border-width: 1px;
border-style: solid;
}
/* Links */
.ui-page-theme-b a,
html .ui-bar-b a,
html .ui-body-b a,
html body .ui-group-theme-b a {
color: #22aadd /*{b-link-color}*/;
font-weight: bold;
}
.ui-page-theme-b a:visited,
html .ui-bar-b a:visited,
html .ui-body-b a:visited,
html body .ui-group-theme-b a:visited {
color: #22aadd /*{b-link-visited}*/;
}
.ui-page-theme-b a:hover,
html .ui-bar-b a:hover,
html .ui-body-b a:hover,
html body .ui-group-theme-b a:hover {
color: #0088bb /*{b-link-hover}*/;
}
.ui-page-theme-b a:active,
html .ui-bar-b a:active,
html .ui-body-b a:active,
html body .ui-group-theme-b a:active {
color: #0088bb /*{b-link-active}*/;
}
/* Button up */
.ui-page-theme-b .ui-btn,
html .ui-bar-b .ui-btn,
html .ui-body-b .ui-btn,
html body .ui-group-theme-b .ui-btn,
html head + body .ui-btn.ui-btn-b,
/* Button visited */
.ui-page-theme-b .ui-btn:visited,
html .ui-bar-b .ui-btn:visited,
html .ui-body-b .ui-btn:visited,
html body .ui-group-theme-b .ui-btn:visited,
html head + body .ui-btn.ui-btn-b:visited {
background-color: #333 /*{b-bup-background-color}*/;
border-color: #1f1f1f /*{b-bup-border}*/;
color: #fff /*{b-bup-color}*/;
text-shadow: 0 /*{b-bup-shadow-x}*/ 1px /*{b-bup-shadow-y}*/ 0 /*{b-bup-shadow-radius}*/ #111 /*{b-bup-shadow-color}*/;
}
/* Button hover */
.ui-page-theme-b .ui-btn:hover,
html .ui-bar-b .ui-btn:hover,
html .ui-body-b .ui-btn:hover,
html body .ui-group-theme-b .ui-btn:hover,
html head + body .ui-btn.ui-btn-b:hover {
background-color: #373737 /*{b-bhover-background-color}*/;
border-color: #1f1f1f /*{b-bhover-border}*/;
color: #fff /*{b-bhover-color}*/;
text-shadow: 0 /*{b-bhover-shadow-x}*/ 1px /*{b-bhover-shadow-y}*/ 0 /*{b-bhover-shadow-radius}*/ #111 /*{b-bhover-shadow-color}*/;
}
/* Button down */
.ui-page-theme-b .ui-btn:active,
html .ui-bar-b .ui-btn:active,
html .ui-body-b .ui-btn:active,
html body .ui-group-theme-b .ui-btn:active,
html head + body .ui-btn.ui-btn-b:active {
background-color: #404040 /*{b-bdown-background-color}*/;
border-color: #1f1f1f /*{b-bdown-border}*/;
color: #fff /*{b-bdown-color}*/;
text-shadow: 0 /*{b-bdown-shadow-x}*/ 1px /*{b-bdown-shadow-y}*/ 0 /*{b-bdown-shadow-radius}*/ #111 /*{b-bdown-shadow-color}*/;
}
/* Active button */
.ui-page-theme-b .ui-btn.ui-btn-active,
html .ui-bar-b .ui-btn.ui-btn-active,
html .ui-body-b .ui-btn.ui-btn-active,
html body .ui-group-theme-b .ui-btn.ui-btn-active,
html head + body .ui-btn.ui-btn-b.ui-btn-active,
/* Active checkbox icon */
.ui-page-theme-b .ui-checkbox-on:after,
html .ui-bar-b .ui-checkbox-on:after,
html .ui-body-b .ui-checkbox-on:after,
html body .ui-group-theme-b .ui-checkbox-on:after,
.ui-btn.ui-checkbox-on.ui-btn-b:after,
/* Active flipswitch background */
.ui-page-theme-b .ui-flipswitch-active,
html .ui-bar-b .ui-flipswitch-active,
html .ui-body-b .ui-flipswitch-active,
html body .ui-group-theme-b .ui-flipswitch-active,
html body .ui-flipswitch.ui-bar-b.ui-flipswitch-active,
/* Active slider track */
.ui-page-theme-b .ui-slider-track .ui-btn-active,
html .ui-bar-b .ui-slider-track .ui-btn-active,
html .ui-body-b .ui-slider-track .ui-btn-active,
html body .ui-group-theme-b .ui-slider-track .ui-btn-active,
html body div.ui-slider-track.ui-body-b .ui-btn-active {
background-color: #22aadd /*{b-active-background-color}*/;
border-color: #22aadd /*{b-active-border}*/;
color: #fff /*{b-active-color}*/;
text-shadow: 0 /*{b-active-shadow-x}*/ 1px /*{b-active-shadow-y}*/ 0 /*{b-active-shadow-radius}*/ #0088bb /*{b-active-shadow-color}*/;
}
/* Active radio button icon */
.ui-page-theme-b .ui-radio-on:after,
html .ui-bar-b .ui-radio-on:after,
html .ui-body-b .ui-radio-on:after,
html body .ui-group-theme-b .ui-radio-on:after,
.ui-btn.ui-radio-on.ui-btn-b:after {
border-color: #22aadd /*{b-active-background-color}*/;
}
/* Focus */
.ui-page-theme-b .ui-btn:focus,
html .ui-bar-b .ui-btn:focus,
html .ui-body-b .ui-btn:focus,
html body .ui-group-theme-b .ui-btn:focus,
html head + body .ui-btn.ui-btn-b:focus,
/* Focus buttons and text inputs with div wrap */
.ui-page-theme-b .ui-focus,
html .ui-bar-b .ui-focus,
html .ui-body-b .ui-focus,
html body .ui-group-theme-b .ui-focus,
html head + body .ui-btn-b.ui-focus,
html head + body .ui-body-b.ui-focus {
-webkit-box-shadow: 0 0 12px #22aadd /*{b-active-background-color}*/;
-moz-box-shadow: 0 0 12px #22aadd /*{b-active-background-color}*/;
box-shadow: 0 0 12px #22aadd /*{b-active-background-color}*/;
}
/* Structure */
/* Disabled
-----------------------------------------------------------------------------------------------------------*/
/* Class ui-disabled deprecated in 1.4. :disabled not supported by IE8 so we use [disabled] */
.ui-disabled,
.ui-state-disabled,
button[disabled],
.ui-select .ui-btn.ui-state-disabled {
filter: Alpha(Opacity=30);
opacity: .3;
cursor: default !important;
pointer-events: none;
}
/* Focus state outline
-----------------------------------------------------------------------------------------------------------*/
.ui-btn:focus,
.ui-btn.ui-focus {
outline: 0;
}
/* Unset box-shadow in browsers that don't do it right */
.ui-noboxshadow .ui-shadow,
.ui-noboxshadow .ui-shadow-inset,
.ui-noboxshadow .ui-overlay-shadow,
.ui-noboxshadow .ui-shadow-icon.ui-btn:after,
.ui-noboxshadow .ui-shadow-icon .ui-btn:after,
.ui-noboxshadow .ui-focus,
.ui-noboxshadow .ui-btn:focus,
.ui-noboxshadow input:focus,
.ui-noboxshadow .ui-panel {
-webkit-box-shadow: none !important;
-moz-box-shadow: none !important;
box-shadow: none !important;
}
.ui-noboxshadow .ui-btn:focus,
.ui-noboxshadow .ui-focus {
outline-width: 1px;
outline-style: auto;
}
/* Some unsets */
.ui-mobile,
.ui-mobile body {
height: 99.9%;
}
.ui-mobile fieldset,
.ui-page {
padding: 0;
margin: 0;
}
.ui-mobile a img,
.ui-mobile fieldset {
border-width: 0;
}
/* Fixes for fieldset issues on IE10 and FF (see #6077) */
.ui-mobile fieldset {
min-width: 0;
}
@-moz-document url-prefix() {
.ui-mobile fieldset {
display: table-column;
vertical-align: middle;
}
}
/* Viewport */
.ui-mobile-viewport {
margin: 0;
overflow-x: visible;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust:none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Issue #2066 */
body.ui-mobile-viewport,
div.ui-mobile-viewport {
overflow-x: hidden;
}
/* "page" containers - full-screen views, one should always be in view post-pageload */
.ui-mobile [data-role=page],
.ui-mobile [data-role=dialog],
.ui-page {
top: 0;
left: 0;
width: 100%;
min-height: 100%;
position: absolute;
display: none;
border: 0;
}
/* On ios4, setting focus on the page element causes flashing during transitions when there is an outline, so we turn off outlines */
.ui-page {
outline: none;
}
.ui-mobile .ui-page-active {
display: block;
overflow: visible;
overflow-x: hidden;
}
@media screen and (orientation: portrait) {
.ui-mobile .ui-page {
min-height: 420px;
}
}
@media screen and (orientation: landscape) {
.ui-mobile .ui-page {
min-height: 300px;
}
}
/* Fouc */
.ui-mobile-rendering > * {
visibility: hidden;
}
/* Non-js content hiding */
.ui-nojs {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
/* Loading screen */
.ui-loading .ui-loader {
display: block;
}
.ui-loader {
display: none;
z-index: 9999999;
position: fixed;
top: 50%;
left: 50%;
border:0;
}
.ui-loader-default {
background: none;
filter: Alpha(Opacity=18);
opacity: .18;
width: 2.875em;
height: 2.875em;
margin-left: -1.4375em;
margin-top: -1.4375em;
}
.ui-loader-verbose {
width: 12.5em;
filter: Alpha(Opacity=88);
opacity: .88;
box-shadow: 0 1px 1px -1px #fff;
height: auto;
margin-left: -6.875em;
margin-top: -2.6875em;
padding: .625em;
}
.ui-loader-default h1 {
font-size: 0;
width: 0;
height: 0;
overflow: hidden;
}
.ui-loader-verbose h1 {
font-size: 1em;
margin: 0;
text-align: center;
}
.ui-loader .ui-icon-loading {
background-color: #000;
display: block;
margin: 0;
width: 2.75em;
height: 2.75em;
padding: .0625em;
-webkit-border-radius: 2.25em;
border-radius: 2.25em;
}
.ui-loader-verbose .ui-icon-loading {
margin: 0 auto .625em;
filter: Alpha(Opacity=75);
opacity: .75;
}
.ui-loader-textonly {
padding: .9375em;
margin-left: -7.1875em;
}
.ui-loader-textonly .ui-icon-loading {
display: none;
}
.ui-loader-fakefix {
position: absolute;
}
/* Headers, content panels */
.ui-bar,
.ui-body {
position: relative;
padding: .4em 1em;
overflow: hidden;
display: block;
clear: both;
}
.ui-bar h1,
.ui-bar h2,
.ui-bar h3,
.ui-bar h4,
.ui-bar h5,
.ui-bar h6 {
margin: 0;
padding: 0;
font-size: 1em;
display: inline-block;
}
.ui-header,
.ui-footer {
border-width: 1px 0;
border-style: solid;
position: relative;
}
.ui-header:empty,
.ui-footer:empty {
min-height: 2.6875em;
}
.ui-header .ui-title,
.ui-footer .ui-title {
font-size: 1em;
min-height: 1.1em;
text-align: center;
display: block;
margin: 0 30%;
padding: .7em 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
outline: 0 !important;
}
.ui-footer .ui-title {
margin: 0 1em;
}
.ui-content {
border-width: 0;
overflow: visible;
overflow-x: hidden;
padding: 1em;
}
/* Corner styling for dialogs and popups */
.ui-corner-all > .ui-header:first-child,
.ui-corner-all > .ui-content:first-child,
.ui-corner-all > .ui-footer:first-child {
-webkit-border-top-left-radius: inherit;
border-top-left-radius: inherit;
-webkit-border-top-right-radius: inherit;
border-top-right-radius: inherit;
}
.ui-corner-all > .ui-header:last-child,
.ui-corner-all > .ui-content:last-child,
.ui-corner-all > .ui-footer:last-child {
-webkit-border-bottom-left-radius: inherit;
border-bottom-left-radius: inherit;
-webkit-border-bottom-right-radius: inherit;
border-bottom-right-radius: inherit;
}
/* Buttons and icons */
.ui-btn {
font-size: 16px;
margin: .5em 0;
padding: .7em 1em;
display: block;
position: relative;
text-align: center;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ui-btn-icon-notext,
.ui-header button.ui-btn.ui-btn-icon-notext,
.ui-footer button.ui-btn.ui-btn-icon-notext {
padding: 0;
width: 1.75em;
height: 1.75em;
text-indent: -9999px;
white-space: nowrap !important;
}
.ui-mini {
font-size: 12.5px;
}
.ui-mini .ui-btn {
font-size: inherit;
}
/* Make buttons in toolbars default to mini and inline. */
.ui-header .ui-btn,
.ui-footer .ui-btn {
font-size: 12.5px;
display: inline-block;
vertical-align: middle;
}
.ui-header .ui-controlgroup .ui-btn-icon-notext,
.ui-footer .ui-controlgroup .ui-btn-icon-notext {
font-size: 12.5px;
}
/* To ensure same top and left/right position when ui-btn-left/right are added to something other than buttons. */
.ui-header .ui-btn-left,
.ui-header .ui-btn-right {
font-size: 12.5px;
}
.ui-mini.ui-btn-icon-notext,
.ui-mini .ui-btn-icon-notext,
.ui-header .ui-btn-icon-notext,
.ui-footer .ui-btn-icon-notext {
font-size: 16px;
padding: 0;
}
.ui-btn-inline {
display: inline-block;
vertical-align: middle;
margin-right: .625em;
}
.ui-btn-icon-left {
padding-left: 2.5em;
}
.ui-btn-icon-right {
padding-right: 2.5em;
}
.ui-btn-icon-top {
padding-top: 2.5em;
}
.ui-btn-icon-bottom {
padding-bottom: 2.5em;
}
.ui-header .ui-btn-icon-top,
.ui-footer .ui-btn-icon-top,
.ui-header .ui-btn-icon-bottom,
.ui-footer .ui-btn-icon-bottom {
padding-left: .3125em;
padding-right: .3125em;
}
.ui-btn-icon-left:after,
.ui-btn-icon-right:after,
.ui-btn-icon-top:after,
.ui-btn-icon-bottom:after,
.ui-btn-icon-notext:after {
content: "";
position: absolute;
display: block;
width: 22px;
height: 22px;
}
.ui-btn-icon-notext:after,
.ui-btn-icon-left:after,
.ui-btn-icon-right:after {
top: 50%;
margin-top: -11px;
}
.ui-btn-icon-left:after {
left: .5625em;
}
.ui-btn-icon-right:after {
right: .5625em;
}
.ui-mini.ui-btn-icon-left:after,
.ui-mini .ui-btn-icon-left:after,
.ui-header .ui-btn-icon-left:after,
.ui-footer .ui-btn-icon-left:after {
left: .37em;
}
.ui-mini.ui-btn-icon-right:after,
.ui-mini .ui-btn-icon-right:after,
.ui-header .ui-btn-icon-right:after,
.ui-footer .ui-btn-icon-right:after {
right: .37em;
}
.ui-btn-icon-notext:after,
.ui-btn-icon-top:after,
.ui-btn-icon-bottom:after {
left: 50%;
margin-left: -11px;
}
.ui-btn-icon-top:after {
top: .5625em;
}
.ui-btn-icon-bottom:after {
top: auto;
bottom: .5625em;
}
/* Buttons in header position classes */
.ui-header .ui-btn-left,
.ui-header .ui-btn-right,
.ui-btn-left > [class*="ui-"],
.ui-btn-right > [class*="ui-"] {
margin: 0;
}
.ui-btn-left,
.ui-btn-right {
position: absolute;
top: .24em;
}
.ui-btn-left {
left: .4em;
}
.ui-btn-right {
right: .4em;
}
.ui-btn-icon-notext.ui-btn-left {
top: .3125em;
left: .3125em;
}
.ui-btn-icon-notext.ui-btn-right {
top: .3125em;
right: .3125em;
}
/* Button elements */
button.ui-btn,
.ui-controlgroup-controls button.ui-btn-icon-notext {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
width: 100%;
}
button.ui-btn-inline,
.ui-header button.ui-btn,
.ui-footer button.ui-btn {
width: auto;
}
/* Firefox adds a 1px border in a button element. We negate this to make sure they have the same height as other buttons in controlgroups. */
button.ui-btn::-moz-focus-inner {
border: 0;
}
button.ui-btn-icon-notext,
.ui-controlgroup-horizontal .ui-controlgroup-controls button.ui-btn {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
width: 1.75em;
}
/* Form labels */
.ui-mobile label,
.ui-controlgroup-label {
display: block;
margin: 0 0 .4em;
}
/* Accessible content hiding */
/* ui-hide-label deprecated in 1.4. TODO: Remove in 1.5 */
.ui-hide-label > label,
.ui-hide-label .ui-controlgroup-label,
.ui-hide-label .ui-rangeslider label,
.ui-hidden-accessible {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
/* Used for hiding elements by the filterable widget. You can also use this class to hide list items or buttons in controlgroups; this ensures correct corner styling. */
.ui-screen-hidden {
display: none !important;
}
/* Transitions originally inspired by those from jQtouch, nice work, folks */
.ui-mobile-viewport-transitioning,
.ui-mobile-viewport-transitioning .ui-page {
width: 100%;
height: 100%;
overflow: hidden;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.ui-page-pre-in {
opacity: 0;
}
.in {
-webkit-animation-timing-function: ease-out;
-webkit-animation-duration: 350ms;
-moz-animation-timing-function: ease-out;
-moz-animation-duration: 350ms;
animation-timing-function: ease-out;
animation-duration: 350ms;
}
.out {
-webkit-animation-timing-function: ease-in;
-webkit-animation-duration: 225ms;
-moz-animation-timing-function: ease-in;
-moz-animation-duration: 225ms;
animation-timing-function: ease-in;
animation-duration: 225ms;
}
@-webkit-keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
@-moz-keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
@-webkit-keyframes fadeout {
from { opacity: 1; }
to { opacity: 0; }
}
@-moz-keyframes fadeout {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes fadeout {
from { opacity: 1; }
to { opacity: 0; }
}
.fade.out {
opacity: 0;
-webkit-animation-duration: 125ms;
-webkit-animation-name: fadeout;
-moz-animation-duration: 125ms;
-moz-animation-name: fadeout;
animation-duration: 125ms;
animation-name: fadeout;
}
.fade.in {
opacity: 1;
-webkit-animation-duration: 225ms;
-webkit-animation-name: fadein;
-moz-animation-duration: 225ms;
-moz-animation-name: fadein;
animation-duration: 225ms;
animation-name: fadein;
}
.pop {
-webkit-transform-origin: 50% 50%;
-moz-transform-origin: 50% 50%;
transform-origin: 50% 50%;
}
.pop.in {
-webkit-transform: scale(1);
-webkit-animation-name: popin;
-webkit-animation-duration: 350ms;
-moz-transform: scale(1);
-moz-animation-name: popin;
-moz-animation-duration: 350ms;
transform: scale(1);
animation-name: popin;
animation-duration: 350ms;
opacity: 1;
}
.pop.out {
-webkit-animation-name: fadeout;
-webkit-animation-duration: 100ms;
-moz-animation-name: fadeout;
-moz-animation-duration: 100ms;
animation-name: fadeout;
animation-duration: 100ms;
opacity: 0;
}
.pop.in.reverse {
-webkit-animation-name: fadein;
-moz-animation-name: fadein;
animation-name: fadein;
}
.pop.out.reverse {
-webkit-transform: scale(.8);
-webkit-animation-name: popout;
-moz-transform: scale(.8);
-moz-animation-name: popout;
transform: scale(.8);
animation-name: popout;
}
@-webkit-keyframes popin {
from {
-webkit-transform: scale(.8);
opacity: 0;
}
to {
-webkit-transform: scale(1);
opacity: 1;
}
}
@-moz-keyframes popin {
from {
-moz-transform: scale(.8);
opacity: 0;
}
to {
-moz-transform: scale(1);
opacity: 1;
}
}
@keyframes popin {
from {
transform: scale(.8);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
@-webkit-keyframes popout {
from {
-webkit-transform: scale(1);
opacity: 1;
}
to {
-webkit-transform: scale(.8);
opacity: 0;
}
}
@-moz-keyframes popout {
from {
-moz-transform: scale(1);
opacity: 1;
}
to {
-moz-transform: scale(.8);
opacity: 0;
}
}
@keyframes popout {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(.8);
opacity: 0;
}
}
/* keyframes for slidein from sides */
@-webkit-keyframes slideinfromright {
from { -webkit-transform: translate3d(100%,0,0); }
to { -webkit-transform: translate3d(0,0,0); }
}
@-moz-keyframes slideinfromright {
from { -moz-transform: translateX(100%); }
to { -moz-transform: translateX(0); }
}
@keyframes slideinfromright {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@-webkit-keyframes slideinfromleft {
from { -webkit-transform: translate3d(-100%,0,0); }
to { -webkit-transform: translate3d(0,0,0); }
}
@-moz-keyframes slideinfromleft {
from { -moz-transform: translateX(-100%); }
to { -moz-transform: translateX(0); }
}
@keyframes slideinfromleft {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
/* keyframes for slideout to sides */
@-webkit-keyframes slideouttoleft {
from { -webkit-transform: translate3d(0,0,0); }
to { -webkit-transform: translate3d(-100%,0,0); }
}
@-moz-keyframes slideouttoleft {
from { -moz-transform: translateX(0); }
to { -moz-transform: translateX(-100%); }
}
@keyframes slideouttoleft {
from { transform: translateX(0); }
to { transform: translateX(-100%); }
}
@-webkit-keyframes slideouttoright {
from { -webkit-transform: translate3d(0,0,0); }
to { -webkit-transform: translate3d(100%,0,0); }
}
@-moz-keyframes slideouttoright {
from { -moz-transform: translateX(0); }
to { -moz-transform: translateX(100%); }
}
@keyframes slideouttoright {
from { transform: translateX(0); }
to { transform: translateX(100%); }
}
.slide.out, .slide.in {
-webkit-animation-timing-function: ease-out;
-webkit-animation-duration: 350ms;
-moz-animation-timing-function: ease-out;
-moz-animation-duration: 350ms;
animation-timing-function: ease-out;
animation-duration: 350ms;
}
.slide.out {
-webkit-transform: translate3d(-100%,0,0);
-webkit-animation-name: slideouttoleft;
-moz-transform: translateX(-100%);
-moz-animation-name: slideouttoleft;
transform: translateX(-100%);
animation-name: slideouttoleft;
}
.slide.in {
-webkit-transform: translate3d(0,0,0);
-webkit-animation-name: slideinfromright;
-moz-transform: translateX(0);
-moz-animation-name: slideinfromright;
transform: translateX(0);
animation-name: slideinfromright;
}
.slide.out.reverse {
-webkit-transform: translate3d(100%,0,0);
-webkit-animation-name: slideouttoright;
-moz-transform: translateX(100%);
-moz-animation-name: slideouttoright;
transform: translateX(100%);
animation-name: slideouttoright;
}
.slide.in.reverse {
-webkit-transform: translate3d(0,0,0);
-webkit-animation-name: slideinfromleft;
-moz-transform: translateX(0);
-moz-animation-name: slideinfromleft;
transform: translateX(0);
animation-name: slideinfromleft;
}
.slidefade.out {
-webkit-transform: translateX(-100%);
-webkit-animation-name: slideouttoleft;
-webkit-animation-duration: 225ms;
-moz-transform: translateX(-100%);
-moz-animation-name: slideouttoleft;
-moz-animation-duration: 225ms;
transform: translateX(-100%);
animation-name: slideouttoleft;
animation-duration: 225ms;
}
.slidefade.in {
-webkit-transform: translateX(0);
-webkit-animation-name: fadein;
-webkit-animation-duration: 200ms;
-moz-transform: translateX(0);
-moz-animation-name: fadein;
-moz-animation-duration: 200ms;
transform: translateX(0);
animation-name: fadein;
animation-duration: 200ms;
}
.slidefade.out.reverse {
-webkit-transform: translateX(100%);
-webkit-animation-name: slideouttoright;
-webkit-animation-duration: 200ms;
-moz-transform: translateX(100%);
-moz-animation-name: slideouttoright;
-moz-animation-duration: 200ms;
transform: translateX(100%);
animation-name: slideouttoright;
animation-duration: 200ms;
}
.slidefade.in.reverse {
-webkit-transform: translateX(0);
-webkit-animation-name: fadein;
-webkit-animation-duration: 200ms;
-moz-transform: translateX(0);
-moz-animation-name: fadein;
-moz-animation-duration: 200ms;
transform: translateX(0);
animation-name: fadein;
animation-duration: 200ms;
}
/* slide down */
.slidedown.out {
-webkit-animation-name: fadeout;
-webkit-animation-duration: 100ms;
-moz-animation-name: fadeout;
-moz-animation-duration: 100ms;
animation-name: fadeout;
animation-duration: 100ms;
}
.slidedown.in {
-webkit-transform: translateY(0);
-webkit-animation-name: slideinfromtop;
-webkit-animation-duration: 250ms;
-moz-transform: translateY(0);
-moz-animation-name: slideinfromtop;
-moz-animation-duration: 250ms;
transform: translateY(0);
animation-name: slideinfromtop;
animation-duration: 250ms;
}
.slidedown.in.reverse {
-webkit-animation-name: fadein;
-webkit-animation-duration: 150ms;
-moz-animation-name: fadein;
-moz-animation-duration: 150ms;
animation-name: fadein;
animation-duration: 150ms;
}
.slidedown.out.reverse {
-webkit-transform: translateY(-100%);
-webkit-animation-name: slideouttotop;
-webkit-animation-duration: 200ms;
-moz-transform: translateY(-100%);
-moz-animation-name: slideouttotop;
-moz-animation-duration: 200ms;
transform: translateY(-100%);
animation-name: slideouttotop;
animation-duration: 200ms;
}
@-webkit-keyframes slideinfromtop {
from { -webkit-transform: translateY(-100%); }
to { -webkit-transform: translateY(0); }
}
@-moz-keyframes slideinfromtop {
from { -moz-transform: translateY(-100%); }
to { -moz-transform: translateY(0); }
}
@keyframes slideinfromtop {
from { transform: translateY(-100%); }
to { transform: translateY(0); }
}
@-webkit-keyframes slideouttotop {
from { -webkit-transform: translateY(0); }
to { -webkit-transform: translateY(-100%); }
}
@-moz-keyframes slideouttotop {
from { -moz-transform: translateY(0); }
to { -moz-transform: translateY(-100%); }
}
@keyframes slideouttotop {
from { transform: translateY(0); }
to { transform: translateY(-100%); }
}
/* slide up */
.slideup.out {
-webkit-animation-name: fadeout;
-webkit-animation-duration: 100ms;
-moz-animation-name: fadeout;
-moz-animation-duration: 100ms;
animation-name: fadeout;
animation-duration: 100ms;
}
.slideup.in {
-webkit-transform: translateY(0);
-webkit-animation-name: slideinfrombottom;
-webkit-animation-duration: 250ms;
-moz-transform: translateY(0);
-moz-animation-name: slideinfrombottom;
-moz-animation-duration: 250ms;
transform: translateY(0);
animation-name: slideinfrombottom;
animation-duration: 250ms;
}
.slideup.in.reverse {
-webkit-animation-name: fadein;
-webkit-animation-duration: 150ms;
-moz-animation-name: fadein;
-moz-animation-duration: 150ms;
animation-name: fadein;
animation-duration: 150ms;
}
.slideup.out.reverse {
-webkit-transform: translateY(100%);
-webkit-animation-name: slideouttobottom;
-webkit-animation-duration: 200ms;
-moz-transform: translateY(100%);
-moz-animation-name: slideouttobottom;
-moz-animation-duration: 200ms;
transform: translateY(100%);
animation-name: slideouttobottom;
animation-duration: 200ms;
}
@-webkit-keyframes slideinfrombottom {
from { -webkit-transform: translateY(100%); }
to { -webkit-transform: translateY(0); }
}
@-moz-keyframes slideinfrombottom {
from { -moz-transform: translateY(100%); }
to { -moz-transform: translateY(0); }
}
@keyframes slideinfrombottom {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@-webkit-keyframes slideouttobottom {
from { -webkit-transform: translateY(0); }
to { -webkit-transform: translateY(100%); }
}
@-moz-keyframes slideouttobottom {
from { -moz-transform: translateY(0); }
to { -moz-transform: translateY(100%); }
}
@keyframes slideouttobottom {
from { transform: translateY(0); }
to { transform: translateY(100%); }
}
/* The properties in this rule are only necessary for the 'flip' transition.
* We need specify the perspective to create a projection matrix. This will add
* some depth as the element flips. The depth number represents the distance of
* the viewer from the z-plane. According to the CSS3 spec, 1000 is a moderate
* value.
*/
.viewport-flip {
-webkit-perspective: 1000;
-moz-perspective: 1000;
perspective: 1000;
position: absolute;
}
.flip {
-webkit-backface-visibility: hidden;
-webkit-transform: translateX(0); /* Needed to work around an iOS 3.1 bug that causes listview thumbs to disappear when -webkit-visibility:hidden is used. */
-moz-backface-visibility: hidden;
-moz-transform: translateX(0);
backface-visibility: hidden;
transform: translateX(0);
}
.flip.out {
-webkit-transform: rotateY(-90deg) scale(.9);
-webkit-animation-name: flipouttoleft;
-webkit-animation-duration: 175ms;
-moz-transform: rotateY(-90deg) scale(.9);
-moz-animation-name: flipouttoleft;
-moz-animation-duration: 175ms;
transform: rotateY(-90deg) scale(.9);
animation-name: flipouttoleft;
animation-duration: 175ms;
}
.flip.in {
-webkit-animation-name: flipintoright;
-webkit-animation-duration: 225ms;
-moz-animation-name: flipintoright;
-moz-animation-duration: 225ms;
animation-name: flipintoright;
animation-duration: 225ms;
}
.flip.out.reverse {
-webkit-transform: rotateY(90deg) scale(.9);
-webkit-animation-name: flipouttoright;
-moz-transform: rotateY(90deg) scale(.9);
-moz-animation-name: flipouttoright;
transform: rotateY(90deg) scale(.9);
animation-name: flipouttoright;
}
.flip.in.reverse {
-webkit-animation-name: flipintoleft;
-moz-animation-name: flipintoleft;
animation-name: flipintoleft;
}
@-webkit-keyframes flipouttoleft {
from { -webkit-transform: rotateY(0); }
to { -webkit-transform: rotateY(-90deg) scale(.9); }
}
@-moz-keyframes flipouttoleft {
from { -moz-transform: rotateY(0); }
to { -moz-transform: rotateY(-90deg) scale(.9); }
}
@keyframes flipouttoleft {
from { transform: rotateY(0); }
to { transform: rotateY(-90deg) scale(.9); }
}
@-webkit-keyframes flipouttoright {
from { -webkit-transform: rotateY(0) ; }
to { -webkit-transform: rotateY(90deg) scale(.9); }
}
@-moz-keyframes flipouttoright {
from { -moz-transform: rotateY(0); }
to { -moz-transform: rotateY(90deg) scale(.9); }
}
@keyframes flipouttoright {
from { transform: rotateY(0); }
to { transform: rotateY(90deg) scale(.9); }
}
@-webkit-keyframes flipintoleft {
from { -webkit-transform: rotateY(-90deg) scale(.9); }
to { -webkit-transform: rotateY(0); }
}
@-moz-keyframes flipintoleft {
from { -moz-transform: rotateY(-90deg) scale(.9); }
to { -moz-transform: rotateY(0); }
}
@keyframes flipintoleft {
from { transform: rotateY(-90deg) scale(.9); }
to { transform: rotateY(0); }
}
@-webkit-keyframes flipintoright {
from { -webkit-transform: rotateY(90deg) scale(.9); }
to { -webkit-transform: rotateY(0); }
}
@-moz-keyframes flipintoright {
from { -moz-transform: rotateY(90deg) scale(.9); }
to { -moz-transform: rotateY(0); }
}
@keyframes flipintoright {
from { transform: rotateY(90deg) scale(.9); }
to { transform: rotateY(0); }
}
/* The properties in this rule are only necessary for the 'flip' transition.
* We need specify the perspective to create a projection matrix. This will add
* some depth as the element flips. The depth number represents the distance of
* the viewer from the z-plane. According to the CSS3 spec, 1000 is a moderate
* value.
*/
.viewport-turn {
-webkit-perspective: 200px;
-moz-perspective: 200px;
-ms-perspective: 200px;
perspective: 200px;
position: absolute;
}
.turn {
-webkit-backface-visibility: hidden;
-webkit-transform: translateX(0); /* Needed to work around an iOS 3.1 bug that causes listview thumbs to disappear when -webkit-visibility:hidden is used. */
-webkit-transform-origin: 0;
-moz-backface-visibility: hidden;
-moz-transform: translateX(0);
-moz-transform-origin: 0;
backface-visibility :hidden;
transform: translateX(0);
transform-origin: 0;
}
.turn.out {
-webkit-transform: rotateY(-90deg) scale(.9);
-webkit-animation-name: flipouttoleft;
-webkit-animation-duration: 125ms;
-moz-transform: rotateY(-90deg) scale(.9);
-moz-animation-name: flipouttoleft;
-moz-animation-duration: 125ms;
transform: rotateY(-90deg) scale(.9);
animation-name: flipouttoleft;
animation-duration: 125ms;
}
.turn.in {
-webkit-animation-name: flipintoright;
-webkit-animation-duration: 250ms;
-moz-animation-name: flipintoright;
-moz-animation-duration: 250ms;
animation-name: flipintoright;
animation-duration: 250ms;
}
.turn.out.reverse {
-webkit-transform: rotateY(90deg) scale(.9);
-webkit-animation-name: flipouttoright;
-moz-transform: rotateY(90deg) scale(.9);
-moz-animation-name: flipouttoright;
transform: rotateY(90deg) scale(.9);
animation-name: flipouttoright;
}
.turn.in.reverse {
-webkit-animation-name: flipintoleft;
-moz-animation-name: flipintoleft;
animation-name: flipintoleft;
}
@-webkit-keyframes flipouttoleft {
from { -webkit-transform: rotateY(0); }
to { -webkit-transform: rotateY(-90deg) scale(.9); }
}
@-moz-keyframes flipouttoleft {
from { -moz-transform: rotateY(0); }
to { -moz-transform: rotateY(-90deg) scale(.9); }
}
@keyframes flipouttoleft {
from { transform: rotateY(0); }
to { transform: rotateY(-90deg) scale(.9); }
}
@-webkit-keyframes flipouttoright {
from { -webkit-transform: rotateY(0) ; }
to { -webkit-transform: rotateY(90deg) scale(.9); }
}
@-moz-keyframes flipouttoright {
from { -moz-transform: rotateY(0); }
to { -moz-transform: rotateY(90deg) scale(.9); }
}
@keyframes flipouttoright {
from { transform: rotateY(0); }
to { transform: rotateY(90deg) scale(.9); }
}
@-webkit-keyframes flipintoleft {
from { -webkit-transform: rotateY(-90deg) scale(.9); }
to { -webkit-transform: rotateY(0); }
}
@-moz-keyframes flipintoleft {
from { -moz-transform: rotateY(-90deg) scale(.9); }
to { -moz-transform: rotateY(0); }
}
@keyframes flipintoleft {
from { transform: rotateY(-90deg) scale(.9); }
to { transform: rotateY(0); }
}
@-webkit-keyframes flipintoright {
from { -webkit-transform: rotateY(90deg) scale(.9); }
to { -webkit-transform: rotateY(0); }
}
@-moz-keyframes flipintoright {
from { -moz-transform: rotateY(90deg) scale(.9); }
to { -moz-transform: rotateY(0); }
}
@keyframes flipintoright {
from { transform: rotateY(90deg) scale(.9); }
to { transform: rotateY(0); }
}
/* flow transition */
.flow {
-webkit-transform-origin: 50% 30%;
-webkit-box-shadow: 0 0 20px rgba(0,0,0,.4);
-moz-transform-origin: 50% 30%;
-moz-box-shadow: 0 0 20px rgba(0,0,0,.4);
transform-origin: 50% 30%;
box-shadow: 0 0 20px rgba(0,0,0,.4);
}
.ui-dialog.flow {
-webkit-transform-origin: none;
-webkit-box-shadow: none;
-moz-transform-origin: none;
-moz-box-shadow: none;
transform-origin: none;
box-shadow: none;
}
.flow.out {
-webkit-transform: translateX(-100%) scale(.7);
-webkit-animation-name: flowouttoleft;
-webkit-animation-timing-function: ease;
-webkit-animation-duration: 350ms;
-moz-transform: translateX(-100%) scale(.7);
-moz-animation-name: flowouttoleft;
-moz-animation-timing-function: ease;
-moz-animation-duration: 350ms;
transform: translateX(-100%) scale(.7);
animation-name: flowouttoleft;
animation-timing-function: ease;
animation-duration: 350ms;
}
.flow.in {
-webkit-transform: translateX(0) scale(1);
-webkit-animation-name: flowinfromright;
-webkit-animation-timing-function: ease;
-webkit-animation-duration: 350ms;
-moz-transform: translateX(0) scale(1);
-moz-animation-name: flowinfromright;
-moz-animation-timing-function: ease;
-moz-animation-duration: 350ms;
transform: translateX(0) scale(1);
animation-name: flowinfromright;
animation-timing-function: ease;
animation-duration: 350ms;
}
.flow.out.reverse {
-webkit-transform: translateX(100%);
-webkit-animation-name: flowouttoright;
-moz-transform: translateX(100%);
-moz-animation-name: flowouttoright;
transform: translateX(100%);
animation-name: flowouttoright;
}
.flow.in.reverse {
-webkit-animation-name: flowinfromleft;
-moz-animation-name: flowinfromleft;
animation-name: flowinfromleft;
}
@-webkit-keyframes flowouttoleft {
0% { -webkit-transform: translateX(0) scale(1); }
60%, 70% { -webkit-transform: translateX(0) scale(.7); }
100% { -webkit-transform: translateX(-100%) scale(.7); }
}
@-moz-keyframes flowouttoleft {
0% { -moz-transform: translateX(0) scale(1); }
60%, 70% { -moz-transform: translateX(0) scale(.7); }
100% { -moz-transform: translateX(-100%) scale(.7); }
}
@keyframes flowouttoleft {
0% { transform: translateX(0) scale(1); }
60%, 70% { transform: translateX(0) scale(.7); }
100% { transform: translateX(-100%) scale(.7); }
}
@-webkit-keyframes flowouttoright {
0% { -webkit-transform: translateX(0) scale(1); }
60%, 70% { -webkit-transform: translateX(0) scale(.7); }
100% { -webkit-transform: translateX(100%) scale(.7); }
}
@-moz-keyframes flowouttoright {
0% { -moz-transform: translateX(0) scale(1); }
60%, 70% { -moz-transform: translateX(0) scale(.7); }
100% { -moz-transform: translateX(100%) scale(.7); }
}
@keyframes flowouttoright {
0% { transform: translateX(0) scale(1); }
60%, 70% { transform: translateX(0) scale(.7); }
100% { transform: translateX(100%) scale(.7); }
}
@-webkit-keyframes flowinfromleft {
0% { -webkit-transform: translateX(-100%) scale(.7); }
30%, 40% { -webkit-transform: translateX(0) scale(.7); }
100% { -webkit-transform: translateX(0) scale(1); }
}
@-moz-keyframes flowinfromleft {
0% { -moz-transform: translateX(-100%) scale(.7); }
30%, 40% { -moz-transform: translateX(0) scale(.7); }
100% { -moz-transform: translateX(0) scale(1); }
}
@keyframes flowinfromleft {
0% { transform: translateX(-100%) scale(.7); }
30%, 40% { transform: translateX(0) scale(.7); }
100% { transform: translateX(0) scale(1); }
}
@-webkit-keyframes flowinfromright {
0% { -webkit-transform: translateX(100%) scale(.7); }
30%, 40% { -webkit-transform: translateX(0) scale(.7); }
100% { -webkit-transform: translateX(0) scale(1); }
}
@-moz-keyframes flowinfromright {
0% { -moz-transform: translateX(100%) scale(.7); }
30%, 40% { -moz-transform: translateX(0) scale(.7); }
100% { -moz-transform: translateX(0) scale(1); }
}
@keyframes flowinfromright {
0% { transform: translateX(100%) scale(.7); }
30%, 40% { transform: translateX(0) scale(.7); }
100% { transform: translateX(0) scale(1); }
}
.ui-field-contain,
.ui-mobile fieldset.ui-field-contain {
display: block;
position: relative;
overflow: visible;
clear: both;
padding: .8em 0;
}
.ui-field-contain > label ~ [class*="ui-"],
.ui-field-contain .ui-controlgroup-controls {
margin: 0;
}
.ui-field-contain:last-child {
border-bottom-width: 0;
}
@media (min-width: 28em) {
.ui-field-contain,
.ui-mobile fieldset.ui-field-contain {
padding: 0;
margin: 1em 0;
border-bottom-width: 0;
}
.ui-field-contain:before,
.ui-field-contain:after {
content: "";
display: table;
}
.ui-field-contain:after {
clear: both;
}
.ui-field-contain > label,
.ui-field-contain .ui-controlgroup-label,
.ui-field-contain > .ui-rangeslider > label {
float: left;
width: 20%;
margin: .5em 2% 0 0;
}
.ui-popup .ui-field-contain > label,
.ui-popup .ui-field-contain .ui-controlgroup-label,
.ui-popup .ui-field-contain > .ui-rangeslider > label {
float: none;
width: auto;
margin: 0 0 .4em;
}
.ui-field-contain > label ~ [class*="ui-"],
.ui-field-contain .ui-controlgroup-controls {
float: left;
width: 78%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* ui-hide-label deprecated in 1.4. TODO: Remove in 1.5 */
.ui-hide-label > label ~ [class*="ui-"],
.ui-hide-label .ui-controlgroup-controls,
.ui-popup .ui-field-contain > label ~ [class*="ui-"],
.ui-popup .ui-field-contain .ui-controlgroup-controls {
float: none;
width: 100%;
}
.ui-field-contain > label ~ .ui-btn-inline {
width: auto;
margin-right: .625em;
}
.ui-field-contain > label ~ .ui-btn-inline.ui-btn-icon-notext {
width: 1.75em;
}
}
/* content configurations. */
.ui-grid-a,
.ui-grid-b,
.ui-grid-c,
.ui-grid-d,
.ui-grid-solo {
overflow: hidden;
}
.ui-block-a,
.ui-block-b,
.ui-block-c,
.ui-block-d,
.ui-block-e {
margin: 0;
padding: 0;
border: 0;
float: left;
min-height: 1px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* force new row */
.ui-block-a {
clear: left;
}
ul.ui-grid-a,
ul.ui-grid-b,
ul.ui-grid-c,
ul.ui-grid-d,
ul.ui-grid-solo,
li.ui-block-a,
li.ui-block-b,
li.ui-block-c,
li.ui-block-d,
li.ui-block-e {
margin-left: 0;
margin-right: 0;
padding: 0;
list-style: none;
}
/* No margin in grids for 100% width button elements until we can use max-width: fill-available; */
[class*="ui-block-"] > button.ui-btn {
margin-right: 0;
margin-left: 0;
}
[class*="ui-block-"] > .ui-btn,
[class*="ui-block-"] > .ui-select,
[class*="ui-block-"] > .ui-checkbox,
[class*="ui-block-"] > .ui-radio,
[class*="ui-block-"] > button.ui-btn-inline,
[class*="ui-block-"] > button.ui-btn-icon-notext,
.ui-header [class*="ui-block-"] > button.ui-btn,
.ui-footer [class*="ui-block-"] > button.ui-btn {
margin-right: .3125em;
margin-left: .3125em;
}
.ui-grid-a > .ui-block-a,
.ui-grid-a > .ui-block-b {
/* width: 49.95%; IE7 */
/* margin-right: -.5px; BB5 */
width: 50%;
}
.ui-grid-b > .ui-block-a,
.ui-grid-b > .ui-block-b,
.ui-grid-b > .ui-block-c {
/* width: 33.25%; IE7 */
/* margin-right: -.5px; BB5 */
width: 33.333%;
}
.ui-grid-c > .ui-block-a,
.ui-grid-c > .ui-block-b,
.ui-grid-c > .ui-block-c,
.ui-grid-c > .ui-block-d {
/* width: 24.925%; IE7 */
/* margin-right: -.5px; BB5 */
width: 25%;
}
.ui-grid-d > .ui-block-a,
.ui-grid-d > .ui-block-b,
.ui-grid-d > .ui-block-c,
.ui-grid-d > .ui-block-d,
.ui-grid-d > .ui-block-e {
/* width: 19.925%; IE7 */
width: 20%;
}
.ui-grid-solo > .ui-block-a {
width: 100%;
float: none;
}
/* preset breakpoint to switch to stacked grid styles below 35em (560px) */
@media (max-width: 35em) {
.ui-responsive > .ui-block-a,
.ui-responsive > .ui-block-b,
.ui-responsive > .ui-block-c,
.ui-responsive > .ui-block-d,
.ui-responsive > .ui-block-e {
width: 100%;
float: none;
}
}
/* fixed page header & footer configuration */
.ui-header-fixed,
.ui-footer-fixed {
left: 0;
right: 0;
width: 100%;
position: fixed;
z-index: 1000;
}
.ui-header-fixed {
top: -1px;
padding-top: 1px;
}
.ui-header-fixed.ui-fixed-hidden {
top: 0;
padding-top: 0;
}
.ui-header-fixed .ui-btn-left,
.ui-header-fixed .ui-btn-right {
margin-top: 1px;
}
.ui-header-fixed.ui-fixed-hidden .ui-btn-left,
.ui-header-fixed.ui-fixed-hidden .ui-btn-right {
margin-top: 0;
}
.ui-footer-fixed {
bottom: -1px;
padding-bottom: 1px;
}
.ui-footer-fixed.ui-fixed-hidden {
bottom: 0;
padding-bottom: 0;
}
.ui-header-fullscreen,
.ui-footer-fullscreen {
filter: Alpha(Opacity=90);
opacity: .9;
}
/* updatePagePadding() will update the padding to actual height of header and footer. */
.ui-page-header-fixed {
padding-top: 2.8125em;
}
.ui-page-footer-fixed {
padding-bottom: 2.8125em;
}
.ui-page-header-fullscreen > .ui-content,
.ui-page-footer-fullscreen > .ui-content {
padding: 0;
}
.ui-fixed-hidden {
position: absolute;
}
/* Tap toggle: hide external fixed footer. See issue #6604 */
.ui-footer-fixed.ui-fixed-hidden {
display: none;
}
.ui-page .ui-footer-fixed.ui-fixed-hidden {
display: block
}
.ui-page-header-fullscreen .ui-fixed-hidden,
.ui-page-footer-fullscreen .ui-fixed-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
.ui-header-fixed .ui-btn,
.ui-footer-fixed .ui-btn {
z-index: 10;
}
/* workarounds for other widgets */
.ui-android-2x-fixed .ui-li-has-thumb {
-webkit-transform: translate3d(0,0,0);
}
.ui-navbar {
max-width: 100%;
}
.ui-navbar ul:before,
.ui-navbar ul:after {
content: "";
display: table;
}
.ui-navbar ul:after {
clear: both;
}
.ui-navbar ul {
list-style: none;
margin: 0;
padding: 0;
position: relative;
display: block;
border: 0;
max-width: 100%;
overflow: visible;
}
.ui-navbar li .ui-btn {
font-size: 12.5px;
display: block;
margin: 0;
border-right-width: 0;
}
.ui-header .ui-navbar li button.ui-btn,
.ui-footer .ui-navbar li button.ui-btn {
margin: 0;
width: 100%;
}
.ui-navbar .ui-btn:focus {
z-index: 1;
}
/* fixes gaps caused by subpixel problem */
.ui-navbar li:last-child .ui-btn {
margin-right: -4px;
}
.ui-navbar li:last-child .ui-btn:after {
margin-right: 4px;
}
.ui-content .ui-navbar li:last-child .ui-btn,
.ui-content .ui-navbar .ui-grid-duo .ui-block-b .ui-btn {
border-right-width: 1px;
margin-right: 0;
}
.ui-content .ui-navbar li:last-child .ui-btn:after,
.ui-content .ui-navbar .ui-grid-duo .ui-block-b .ui-btn:after {
margin-right: 0;
}
.ui-navbar .ui-grid-duo .ui-block-a:last-child .ui-btn {
border-right-width: 1px;
margin-right: -1px;
}
.ui-navbar .ui-grid-duo .ui-block-a:last-child .ui-btn:after {
margin-right: 1px;
}
.ui-navbar .ui-grid-duo .ui-btn {
border-top-width: 0;
}
.ui-navbar .ui-grid-duo .ui-block-a:first-child .ui-btn,
.ui-navbar .ui-grid-duo .ui-block-a:first-child + .ui-block-b .ui-btn {
border-top-width: 1px;
}
.ui-header .ui-navbar .ui-btn,
.ui-footer .ui-navbar .ui-btn {
border-top-width: 0;
border-bottom-width: 0;
}
.ui-header .ui-navbar .ui-grid-duo .ui-block-a:first-child .ui-btn,
.ui-footer .ui-navbar .ui-grid-duo .ui-block-a:first-child .ui-btn,
.ui-header .ui-navbar .ui-grid-duo .ui-block-a:first-child + .ui-block-b .ui-btn,
.ui-footer .ui-navbar .ui-grid-duo .ui-block-a:first-child + .ui-block-b .ui-btn {
border-top-width: 0;
}
.ui-header .ui-title ~ .ui-navbar .ui-btn,
.ui-footer .ui-title ~ .ui-navbar .ui-btn,
.ui-header .ui-navbar .ui-grid-duo .ui-btn,
.ui-footer .ui-navbar .ui-grid-duo .ui-btn,
.ui-header .ui-title ~ .ui-navbar .ui-grid-duo .ui-block-a:first-child .ui-btn,
.ui-footer .ui-title ~ .ui-navbar .ui-grid-duo .ui-block-a:first-child .ui-btn,
.ui-header .ui-title ~ .ui-navbar .ui-grid-duo .ui-block-a:first-child + .ui-block-b .ui-btn,
.ui-footer .ui-title ~ .ui-navbar .ui-grid-duo .ui-block-a:first-child + .ui-block-b .ui-btn {
border-top-width: 1px;
}
/* Hide the native input element */
.ui-input-btn input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 0;
border: 0;
outline: 0;
-webkit-border-radius: inherit;
border-radius: inherit;
-webkit-appearance: none;
-moz-appearance: none;
cursor: pointer;
background: #fff;
background: rgba(255,255,255,0);
filter: Alpha(Opacity=0);
opacity: .1;
font-size: 1px;
text-indent: -9999px;
z-index: 2;
}
/* Fixes IE/WP filter alpha opacity bugs */
.ui-input-btn.ui-state-disabled input {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
.ui-collapsible {
margin: 0 -1em;
}
.ui-collapsible-inset,
.ui-collapsible-set {
margin: .5em 0;
}
.ui-collapsible-heading {
display: block;
margin: 0;
padding: 0;
position: relative;
}
.ui-collapsible-heading .ui-btn {
text-align: left;
margin: 0;
border-left-width: 0;
border-right-width: 0;
}
.ui-collapsible-heading .ui-btn-icon-top,
.ui-collapsible-heading .ui-btn-icon-bottom {
text-align: center;
}
.ui-collapsible-inset .ui-collapsible-heading .ui-btn {
border-right-width: 1px;
border-left-width: 1px;
}
.ui-collapsible-collapsed + .ui-collapsible:not(.ui-collapsible-inset) > .ui-collapsible-heading .ui-btn {
border-top-width: 0;
}
.ui-collapsible-set .ui-collapsible:not(.ui-collapsible-inset) .ui-collapsible-heading .ui-btn {
border-top-width: 1px;
}
.ui-collapsible-heading-status {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
.ui-collapsible-content {
display: block;
margin: 0;
padding: .5em 1em;
}
.ui-collapsible-themed-content .ui-collapsible-content {
border-left-width: 0;
border-right-width: 0;
border-top-width: 0;
border-bottom-width: 1px;
border-style: solid;
}
.ui-collapsible-inset.ui-collapsible-themed-content .ui-collapsible-content {
border-left-width: 1px;
border-right-width: 1px;
}
.ui-collapsible-inset .ui-collapsible-content {
margin: 0;
}
.ui-collapsible-content-collapsed {
display: none;
}
.ui-collapsible-set > .ui-collapsible.ui-corner-all {
-webkit-border-radius: 0;
border-radius: 0;
}
.ui-collapsible-heading,
.ui-collapsible-heading > .ui-btn {
-webkit-border-radius: inherit;
border-radius: inherit;
}
.ui-collapsible-set .ui-collapsible.ui-first-child {
-webkit-border-top-right-radius: inherit;
border-top-right-radius: inherit;
-webkit-border-top-left-radius: inherit;
border-top-left-radius: inherit;
}
.ui-collapsible-content,
.ui-collapsible-set .ui-collapsible.ui-last-child {
-webkit-border-bottom-right-radius: inherit;
border-bottom-right-radius: inherit;
-webkit-border-bottom-left-radius: inherit;
border-bottom-left-radius: inherit;
}
.ui-collapsible-themed-content:not(.ui-collapsible-collapsed) > .ui-collapsible-heading {
-webkit-border-bottom-right-radius: 0;
border-bottom-right-radius: 0;
-webkit-border-bottom-left-radius: 0;
border-bottom-left-radius: 0;
}
.ui-collapsible-set .ui-collapsible {
margin: -1px -1em 0;
}
.ui-collapsible-set .ui-collapsible-inset {
margin: -1px 0 0;
}
.ui-collapsible-set .ui-collapsible.ui-first-child {
margin-top: 0;
}
.ui-controlgroup,
fieldset.ui-controlgroup {
padding: 0;
margin: .5em 0;
}
.ui-field-contain .ui-controlgroup,
.ui-field-contain fieldset.ui-controlgroup {
margin: 0;
}
.ui-mini .ui-controlgroup-label {
font-size: 16px;
}
.ui-controlgroup.ui-mini .ui-btn-icon-notext,
.ui-controlgroup .ui-mini.ui-btn-icon-notext {
font-size: inherit;
}
.ui-controlgroup-controls .ui-btn,
.ui-controlgroup-controls .ui-checkbox,
.ui-controlgroup-controls .ui-radio,
.ui-controlgroup-controls .ui-select {
margin: 0;
}
.ui-controlgroup-controls .ui-btn:focus,
.ui-controlgroup-controls .ui-btn.ui-focus {
z-index: 1;
}
.ui-controlgroup-controls li {
list-style: none;
}
.ui-controlgroup-horizontal .ui-controlgroup-controls {
display: inline-block;
vertical-align: middle;
}
.ui-controlgroup-horizontal .ui-controlgroup-controls:before,
.ui-controlgroup-horizontal .ui-controlgroup-controls:after {
content: "";
display: table;
}
.ui-controlgroup-horizontal .ui-controlgroup-controls:after {
clear: both;
}
.ui-controlgroup-horizontal .ui-controlgroup-controls > .ui-btn,
.ui-controlgroup-horizontal .ui-controlgroup-controls li > .ui-btn,
.ui-controlgroup-horizontal .ui-controlgroup-controls .ui-checkbox,
.ui-controlgroup-horizontal .ui-controlgroup-controls .ui-radio,
.ui-controlgroup-horizontal .ui-controlgroup-controls .ui-select {
float: left;
clear: none;
}
.ui-controlgroup-horizontal .ui-controlgroup-controls button.ui-btn,
.ui-controlgroup-controls .ui-btn-icon-notext {
width: auto;
}
.ui-controlgroup-horizontal .ui-controlgroup-controls .ui-btn-icon-notext,
.ui-controlgroup-horizontal .ui-controlgroup-controls button.ui-btn-icon-notext {
width: 1.5em;
}
.ui-controlgroup-controls .ui-btn-icon-notext {
height: auto;
padding: .7em 1em;
}
.ui-controlgroup-vertical .ui-controlgroup-controls .ui-btn {
border-bottom-width: 0;
}
.ui-controlgroup-vertical .ui-controlgroup-controls .ui-btn.ui-last-child {
border-bottom-width: 1px;
}
.ui-controlgroup-horizontal .ui-controlgroup-controls .ui-btn {
border-right-width: 0;
}
.ui-controlgroup-horizontal .ui-controlgroup-controls .ui-btn.ui-last-child {
border-right-width: 1px;
}
.ui-controlgroup-controls .ui-btn-corner-all,
.ui-controlgroup-controls .ui-btn.ui-corner-all {
-webkit-border-radius: 0;
border-radius: 0;
}
.ui-controlgroup-controls,
.ui-controlgroup-controls .ui-radio,
.ui-controlgroup-controls .ui-checkbox,
.ui-controlgroup-controls .ui-select,
.ui-controlgroup-controls li {
-webkit-border-radius: inherit;
border-radius: inherit;
}
.ui-controlgroup-vertical .ui-btn.ui-first-child {
-webkit-border-top-left-radius: inherit;
border-top-left-radius: inherit;
-webkit-border-top-right-radius: inherit;
border-top-right-radius: inherit;
}
.ui-controlgroup-vertical .ui-btn.ui-last-child {
-webkit-border-bottom-left-radius: inherit;
border-bottom-left-radius: inherit;
-webkit-border-bottom-right-radius: inherit;
border-bottom-right-radius: inherit;
}
.ui-controlgroup-horizontal .ui-btn.ui-first-child {
-webkit-border-top-left-radius: inherit;
border-top-left-radius: inherit;
-webkit-border-bottom-left-radius: inherit;
border-bottom-left-radius: inherit;
}
.ui-controlgroup-horizontal .ui-btn.ui-last-child {
-webkit-border-top-right-radius: inherit;
border-top-right-radius: inherit;
-webkit-border-bottom-right-radius: inherit;
border-bottom-right-radius: inherit;
}
.ui-controlgroup-controls a.ui-shadow:not(:focus),
.ui-controlgroup-controls button.ui-shadow:not(:focus),
.ui-controlgroup-controls div.ui-shadow:not(.ui-focus) {
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
/* Fixes legend not wrapping on IE10 */
.ui-controlgroup-label legend {
max-width: 100%;
}
.ui-controlgroup-controls > label {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
.ui-dialog {
background: none !important; /* this is to ensure that dialog theming does not apply (by default at least) on the page div */
}
.ui-dialog-contain {
width: 92.5%;
max-width: 500px;
margin: 10% auto 1em auto;
padding: 0;
position: relative;
top: -1em;
}
.ui-dialog-contain > .ui-header,
.ui-dialog-contain > .ui-content,
.ui-dialog-contain > .ui-footer {
display: block;
position: relative;
width: auto;
margin: 0;
}
.ui-dialog-contain > .ui-header {
overflow: hidden;
z-index: 10;
padding: 0;
border-top-width: 0;
}
.ui-dialog-contain > .ui-footer {
z-index: 10;
padding: 0 1em;
border-bottom-width: 0;
}
.ui-popup-open .ui-header-fixed,
.ui-popup-open .ui-footer-fixed {
position: absolute !important; /* See issues #4816, #4844 and #4874 and popup.js */
}
.ui-popup-screen {
background-image: url("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="); /* Necessary to set some form of background to ensure element is clickable in IE6/7. While legacy IE won't understand the data-URI'd image, it ensures no additional requests occur in all other browsers with little overhead. */
top: 0;
left: 0;
right: 0;
bottom: 1px;
position: absolute;
filter: Alpha(Opacity=0);
opacity: 0;
z-index: 1099;
}
.ui-popup-screen.in {
opacity: 0.5;
filter: Alpha(Opacity=50);
}
.ui-popup-screen.out {
opacity: 0;
filter: Alpha(Opacity=0);
}
.ui-popup-container {
z-index: 1100;
display: inline-block;
position: absolute;
padding: 0;
outline: 0;
}
.ui-popup {
position: relative;
}
.ui-popup.ui-body-inherit {
border-width: 1px;
border-style: solid;
}
.ui-popup-hidden {
left: 0;
top: 0;
position: absolute !important;
visibility: hidden;
}
.ui-popup-truncate {
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
.ui-popup.ui-content,
.ui-popup .ui-content {
overflow: visible;
}
.ui-popup > .ui-header {
border-top-width: 0;
}
.ui-popup > .ui-footer {
border-bottom-width: 0;
}
.ui-popup > p,
.ui-popup > h1,
.ui-popup > h2,
.ui-popup > h3,
.ui-popup > h4,
.ui-popup > h5,
.ui-popup > h6 {
margin: .5em .4375em;
}
.ui-popup > span {
display: block;
margin: .5em .4375em;
}
.ui-popup-container .ui-content > p,
.ui-popup-container .ui-content > h1,
.ui-popup-container .ui-content > h2,
.ui-popup-container .ui-content > h3,
.ui-popup-container .ui-content > h4,
.ui-popup-container .ui-content > h5,
.ui-popup-container .ui-content > h6 {
margin: .5em 0;
}
.ui-popup-container .ui-content > span {
margin: 0;
}
.ui-popup-container .ui-content > p:first-child,
.ui-popup-container .ui-content > h1:first-child,
.ui-popup-container .ui-content > h2:first-child,
.ui-popup-container .ui-content > h3:first-child,
.ui-popup-container .ui-content > h4:first-child,
.ui-popup-container .ui-content > h5:first-child,
.ui-popup-container .ui-content > h6:first-child {
margin-top: 0;
}
.ui-popup-container .ui-content > p:last-child,
.ui-popup-container .ui-content > h1:last-child,
.ui-popup-container .ui-content > h2:last-child,
.ui-popup-container .ui-content > h3:last-child,
.ui-popup-container .ui-content > h4:last-child,
.ui-popup-container .ui-content > h5:last-child,
.ui-popup-container .ui-content > h6:last-child {
margin-bottom: 0;
}
.ui-popup > img {
max-width: 100%;
max-height: 100%;
vertical-align: middle;
}
.ui-popup:not(.ui-content) > img:only-child,
.ui-popup:not(.ui-content) > .ui-btn-left:first-child + img:last-child,
.ui-popup:not(.ui-content) > .ui-btn-right:first-child + img:last-child {
-webkit-border-radius: inherit;
border-radius: inherit;
}
.ui-popup iframe {
vertical-align: middle;
}
.ui-popup > .ui-btn-left,
.ui-popup > .ui-btn-right {
position: absolute;
top: -11px;
margin: 0;
z-index: 1101;
}
.ui-popup > .ui-btn-left {
left: -11px;
}
.ui-popup > .ui-btn-right {
right: -11px;
}
/* Dimensions related to the popup arrow
-----------------------------------------------------------------------------------------------------------*/
/* desired triangle height: 10px */
/**
* guide for the arrow - its width, height, and offset are theme-dependent and
* should be expessed as left, right, top, bottom, so that the element bearing
* such a class becomes stretched inside its parent position: relative element.
* The left/top/right/bottom specified below should reflect the corresponding
* border radii and so it leaves room for the shadow:
* ..--------------------..
* ." ^ top ".
* / v \
* | +------------------+ |
* | | | |
* | left| |right|
* |<--->| |<--->|
* | +------------------+ |
* \ ^ /
* `. v bottom .'
* ""--------------------""
* The idea is that the top/left of the arrow container box does not move to a
* coordinate smaller than the top/left of the guide and the right/bottom of
* the arrow container box does not move to a coordinate larger than the
* bottom/right of the guide. This will help us avoid the following situation:
* ..--------------------..
* ." ^ top ".
* /|/ v \
* / | +------------------+ |
* \ | | | |
* \| left| |right|
* |<--->| |<--->|
* | +------------------+ |
* \ ^ /
* `. v bottom .'
* ""--------------------""
* The arrow should not receive a top/left coordinate such that it is too close
* to one of the corners, because then at first the shadow of the arrow and,
* given a coordinate even closer to the corner, even the body of the arrow will
* "stick out" of the corner of the popup. The guide provides a hint to the
* arrow positioning code as to which range of values is acceptable for the
* arrow container's top/left coordinate.
**/
.ui-popup-arrow-container {
width: 20px;
height: 20px;
}
/* aside from the "infinities" (-1000,2000), triangle height is used */
.ui-popup-arrow-container.ui-popup-arrow-l {
left: -10px;
clip: rect(-1000px,10px,2000px,-1000px);
}
.ui-popup-arrow-container.ui-popup-arrow-t {
top: -10px;
clip: rect(-1000px,2000px,10px,-1000px);
}
.ui-popup-arrow-container.ui-popup-arrow-r {
right: -10px;
clip: rect(-1000px,2000px,2000px,10px);
}
.ui-popup-arrow-container.ui-popup-arrow-b {
bottom: -10px;
clip: rect(10px,2000px,1000px,-1000px);
}
/**
* For each side, the arrow is twice the desired size and its corner is aligned
* with the edge of the container:
*
* /\ /\ +----+ /\
* / \ / \ | /\ |top / \
* +----+ \ / +----+ +-->|/ \| / \
* left| / | \ / | \ |right | | | / \
* |/ | \ / | \| | /| |\ / \
* |\ | / \ | /| | / +----+ \ \ +----+ /
* | \ | / \ | / | | \ / \| |/
* +----+ / \ +----+ | \ / | |
* ^ \ / \ / ^ | \ / +->|\ /|
* | \/ \/ | | \ / | | \/ |bottom
* | | | \/ | +----+
* +-------------------+--------+-----------+
* |
* arrow container
* (clips arrow)
**/
.ui-popup-arrow-container .ui-popup-arrow {
/* (4*desired triangle height)/sqrt(2) - does not account for border - centred within the outer rectangle */
width: 28.284271247px;
height: 28.284271247px;
border-width: 1px;
border-style: solid;
}
.ui-popup-arrow-container.ui-popup-arrow-t .ui-popup-arrow {
left: -4.142135623px;
top: 5.857864376px;
}
.ui-popup-arrow-container.ui-popup-arrow-b .ui-popup-arrow {
left: -4.142135623px;
top: -14.142135623px;
}
.ui-popup-arrow-container.ui-popup-arrow-l .ui-popup-arrow {
left: 5.857864376px;
top: -4.142135623px;
}
.ui-popup-arrow-container.ui-popup-arrow-r .ui-popup-arrow {
left: -14.142135623px;
top: -4.142135623px;
}
/* Fix rotation center for oldIE - see http://www.useragentman.com/IETransformsTranslator/ */
.ui-popup-arrow-container.ui-popup-arrow-t.ie .ui-popup-arrow {
margin-left: -5.857864376269049px;
margin-top: -7.0710678118654755px;
}
.ui-popup-arrow-container.ui-popup-arrow-b.ie .ui-popup-arrow {
margin-left: -5.857864376269049px;
margin-top: -4.142135623730951px;
}
.ui-popup-arrow-container.ui-popup-arrow-l.ie .ui-popup-arrow {
margin-left: -7.0710678118654755px;
margin-top: -5.857864376269049px;
}
.ui-popup-arrow-container.ui-popup-arrow-r.ie .ui-popup-arrow {
margin-left: -4.142135623730951px;
margin-top: -5.857864376269049px;
}
/* structure */
.ui-popup > .ui-popup-arrow-guide {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
visibility: hidden;
}
.ui-popup-arrow-container {
position: absolute;
}
.ui-popup-arrow {
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
position: absolute;
overflow: hidden;
box-sizing: border-box;
}
.ui-popup-arrow-container.ie .ui-popup-arrow {
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.7071067811865474, M12=-0.7071067811865477, M21=0.7071067811865477, M22=0.7071067811865474, SizingMethod='auto expand')";
filter: progid:DXImageTransform.Microsoft.Matrix(
M11=0.7071067811865474,
M12=-0.7071067811865477,
M21=0.7071067811865477,
M22=0.7071067811865474,
SizingMethod='auto expand');
}
.ui-checkbox,
.ui-radio {
margin: .5em 0;
position: relative;
}
.ui-checkbox .ui-btn,
.ui-radio .ui-btn {
margin: 0;
text-align: left;
white-space: normal; /* Nowrap + ellipsis doesn't work on label. Issue #1419. */
z-index: 2;
}
.ui-controlgroup .ui-checkbox .ui-btn.ui-focus,
.ui-controlgroup .ui-radio .ui-btn.ui-focus {
z-index: 3;
}
.ui-checkbox .ui-btn-icon-top,
.ui-radio .ui-btn-icon-top,
.ui-checkbox .ui-btn-icon-bottom,
.ui-radio .ui-btn-icon-bottom {
text-align: center;
}
.ui-controlgroup-horizontal .ui-checkbox .ui-btn:after,
.ui-controlgroup-horizontal .ui-radio .ui-btn:after {
content: none;
display: none;
}
/* Native input positioning */
.ui-checkbox input,
.ui-radio input {
position: absolute;
left: .466em;
top: 50%;
width: 22px;
height: 22px;
margin: -11px 0 0 0;
outline: 0 !important;
z-index: 1;
}
.ui-controlgroup-horizontal .ui-checkbox input,
.ui-controlgroup-horizontal .ui-radio input {
left: 50%;
margin-left: -9px;
}
.ui-checkbox input:disabled,
.ui-radio input:disabled {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
.ui-select {
margin-top: .5em;
margin-bottom: .5em; /* no shorthand for margin because it would override margin-right for inline selects */
position: relative;
}
.ui-select > select {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
.ui-select .ui-btn {
margin: 0;
opacity: 1; /* Fixes #2588: When Windows Phone 7.5 (Mango) tries to calculate a numeric opacity for a select (including "inherit") without explicitly specifying an opacity on the parent to give it context, a bug appears where clicking elsewhere on the page after opening the select will open the select again. */
}
.ui-select .ui-btn select {
position: absolute;
top: 0;
left: 0;
width: 100%;
min-height: 1.5em;
min-height: 100%;
height: 3em;
max-height: 100%;
outline: 0;
-webkit-border-radius: inherit;
border-radius: inherit;
-webkit-appearance: none;
-moz-appearance: none;
cursor: pointer;
filter: Alpha(Opacity=0);
opacity: 0;
z-index: 2;
}
@-moz-document url-prefix() {
.ui-select .ui-btn select {
opacity: 0.0001;
}
}
/* Display none because of issues with IE/WP's filter alpha opacity */
.ui-select .ui-state-disabled select {
display: none;
}
/* Because we add all classes of the select and option elements to the span... */
.ui-select span.ui-state-disabled {
filter: Alpha(Opacity=100);
opacity: 1;
}
.ui-select .ui-btn.ui-select-nativeonly {
border-radius: 0;
border: 0;
}
.ui-select .ui-btn.ui-select-nativeonly select {
opacity: 1;
text-indent: 0;
display: block;
}
/* ui-li-count is styled in the listview CSS. We set padding and offset here because select supports icon position while listview doesn't. */
.ui-select .ui-li-has-count.ui-btn {
padding-right: 2.8125em;
}
.ui-select .ui-li-has-count.ui-btn-icon-right {
padding-right: 4.6875em;
}
.ui-select .ui-btn-icon-right .ui-li-count {
right: 3.2em;
}
/* We set the rules for the span as well to fix an issue on Chrome with text-overflow ellipsis for the button in combination with text-align center. */
.ui-select .ui-btn > span:not(.ui-li-count) {
display: block;
text-overflow: ellipsis;
overflow: hidden !important;
white-space: nowrap;
}
.ui-selectmenu.ui-popup {
min-width: 11em;
}
.ui-selectmenu .ui-dialog-contain {
overflow: hidden;
}
.ui-selectmenu .ui-header {
margin: 0;
padding: 0;
border-width: 0;
}
.ui-selectmenu.ui-dialog .ui-header {
z-index: 1;
position: relative;
}
.ui-selectmenu.ui-popup .ui-header {
-webkit-border-bottom-right-radius: 0;
border-bottom-right-radius: 0;
-webkit-border-bottom-left-radius: 0;
border-bottom-left-radius: 0;
}
/* when no placeholder is defined in a multiple select, the header height doesn't even extend past the close button. this shim's content in there */
.ui-selectmenu.ui-popup .ui-header h1:after {
content: '.';
visibility: hidden;
}
.ui-selectmenu .ui-header .ui-title {
margin: 0 2.875em;
}
.ui-selectmenu.ui-dialog .ui-content {
overflow: visible;
z-index: 1;
}
.ui-selectmenu .ui-selectmenu-list {
margin: 0;
-webkit-border-radius: inherit;
border-radius: inherit;
}
.ui-header:not(.ui-screen-hidden) + .ui-selectmenu-list {
-webkit-border-top-right-radius: 0;
border-top-right-radius: 0;
-webkit-border-top-left-radius: 0;
border-top-left-radius: 0;
}
.ui-header.ui-screen-hidden + .ui-selectmenu-list li.ui-first-child .ui-btn {
border-top-width: 0;
}
.ui-selectmenu .ui-selectmenu-list li.ui-last-child .ui-btn {
border-bottom-width: 0;
}
.ui-selectmenu .ui-btn.ui-li-divider {
cursor: default;
}
.ui-selectmenu .ui-selectmenu-placeholder {
display: none;
}
.ui-listview,
.ui-listview > li {
margin: 0;
padding: 0;
list-style: none;
}
.ui-content .ui-listview,
.ui-panel-inner > .ui-listview {
margin: -1em;
}
.ui-content .ui-listview-inset,
.ui-panel-inner > .ui-listview-inset {
margin: 1em 0;
}
.ui-collapsible-content > .ui-listview {
margin: -.5em -1em;
}
.ui-collapsible-content > .ui-listview-inset {
margin: .5em 0;
}
.ui-listview > li {
display: block;
position: relative;
overflow: visible;
}
.ui-listview > .ui-li-static,
.ui-listview > .ui-li-divider,
.ui-listview > li > a.ui-btn {
margin: 0;
display: block;
position: relative;
text-align: left;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.ui-listview > li > .ui-btn:focus {
z-index: 1;
}
.ui-listview > .ui-li-static,
.ui-listview > .ui-li-divider,
.ui-listview > li > a.ui-btn {
border-width: 1px 0 0 0;
border-style: solid;
}
.ui-listview-inset > .ui-li-static,
.ui-listview-inset > .ui-li-divider,
.ui-listview-inset > li > a.ui-btn {
border-right-width: 1px;
border-left-width: 1px;
}
.ui-listview > .ui-li-static.ui-last-child,
.ui-listview > .ui-li-divider.ui-last-child,
.ui-listview > li.ui-last-child > a.ui-btn {
border-bottom-width: 1px;
}
.ui-collapsible-content > .ui-listview:not(.ui-listview-inset) > li.ui-first-child,
.ui-collapsible-content > .ui-listview:not(.ui-listview-inset) > li.ui-first-child > a.ui-btn {
border-top-width: 0;
}
.ui-collapsible-themed-content .ui-listview:not(.ui-listview-inset) > li.ui-last-child,
.ui-collapsible-themed-content .ui-listview:not(.ui-listview-inset) > li.ui-last-child > a.ui-btn {
border-bottom-width: 0;
}
.ui-listview > li.ui-first-child,
.ui-listview > li.ui-first-child > a.ui-btn {
-webkit-border-top-right-radius: inherit;
border-top-right-radius: inherit;
-webkit-border-top-left-radius: inherit;
border-top-left-radius: inherit;
}
.ui-listview > li.ui-last-child,
.ui-listview > li.ui-last-child > a.ui-btn {
-webkit-border-bottom-right-radius: inherit;
border-bottom-right-radius: inherit;
-webkit-border-bottom-left-radius: inherit;
border-bottom-left-radius: inherit;
}
.ui-listview > li.ui-li-has-alt > a.ui-btn {
-webkit-border-top-right-radius: 0;
border-top-right-radius: 0;
-webkit-border-bottom-right-radius: 0;
border-bottom-right-radius: 0;
}
.ui-listview > li.ui-first-child > a.ui-btn + a.ui-btn {
-webkit-border-top-left-radius: 0;
border-top-left-radius: 0;
-webkit-border-top-right-radius: inherit;
border-top-right-radius: inherit;
}
.ui-listview > li.ui-last-child > a.ui-btn + a.ui-btn {
-webkit-border-bottom-left-radius: 0;
border-bottom-left-radius: 0;
-webkit-border-bottom-right-radius: inherit;
border-bottom-right-radius: inherit;
}
.ui-listview > li.ui-first-child img:first-child:not(.ui-li-icon) {
-webkit-border-top-left-radius: inherit;
border-top-left-radius: inherit;
}
.ui-listview > li.ui-last-child img:first-child:not(.ui-li-icon) {
-webkit-border-bottom-left-radius: inherit;
border-bottom-left-radius: inherit;
}
.ui-collapsible-content > .ui-listview:not(.ui-listview-inset) {
-webkit-border-radius: inherit;
border-radius: inherit;
}
.ui-listview > .ui-li-static {
padding: .7em 1em;
}
.ui-listview > .ui-li-divider {
padding: .5em 1.143em;
font-size: 14px;
font-weight: bold;
cursor: default;
outline: 0; /* Dividers in custom selectmenus have tabindex */
}
.ui-listview > .ui-li-has-count > .ui-btn,
.ui-listview > .ui-li-static.ui-li-has-count,
.ui-listview > .ui-li-divider.ui-li-has-count {
padding-right: 2.8125em;
}
.ui-listview > .ui-li-has-count > .ui-btn-icon-right {
padding-right: 4.6875em;
}
.ui-listview > .ui-li-has-thumb > .ui-btn,
.ui-listview > .ui-li-static.ui-li-has-thumb {
min-height: 3.625em;
padding-left: 6.25em;
}
/* ui-li-has-icon deprecated in 1.4. TODO: remove in 1.5 */
.ui-listview > .ui-li-has-icon > .ui-btn,
.ui-listview > .ui-li-static.ui-li-has-icon {
min-height: 1.25em;
padding-left: 2.5em;
}
/* Used by both listview and custom multiple select button */
.ui-li-count {
position: absolute;
font-size: 12.5px;
font-weight: bold;
text-align: center;
border-width: 1px;
border-style: solid;
padding: 0 .48em;
line-height: 1.6em;
min-height: 1.6em;
min-width: .64em;
right: .8em;
top: 50%;
margin-top: -.88em;
}
.ui-listview .ui-btn-icon-right .ui-li-count {
right: 3.2em;
}
.ui-listview .ui-li-has-thumb > img:first-child,
.ui-listview .ui-li-has-thumb > .ui-btn > img:first-child,
.ui-listview .ui-li-has-thumb .ui-li-thumb {
position: absolute;
left: 0;
top: 0;
max-height: 5em;
max-width: 5em;
}
/* ui-li-has-icon deprecated in 1.4. TODO: remove in 1.5 */
.ui-listview > .ui-li-has-icon > img:first-child,
.ui-listview > .ui-li-has-icon > .ui-btn > img:first-child {
position: absolute;
left: .625em;
top: .9em;
max-height: 1em;
max-width: 1em;
}
.ui-listview > li h1,
.ui-listview > li h2,
.ui-listview > li h3,
.ui-listview > li h4,
.ui-listview > li h5,
.ui-listview > li h6 {
font-size: 1em;
font-weight: bold;
display: block;
margin: .45em 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.ui-listview > li p {
font-size: .75em;
font-weight: normal;
display: block;
margin: .6em 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.ui-listview .ui-li-aside {
position: absolute;
top: 1em;
right: 3.333em;
margin: 0;
text-align: right;
}
.ui-listview > li.ui-li-has-alt > .ui-btn {
margin-right: 2.5em;
border-right-width: 0;
}
.ui-listview > li.ui-li-has-alt > .ui-btn + .ui-btn {
position: absolute;
width: 2.5em;
height: 100%;
min-height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
border-left-width: 1px;
top: 0;
right: 0;
margin: 0;
padding: 0;
z-index: 2;
}
.ui-listview-inset > li.ui-li-has-alt > .ui-btn + .ui-btn {
border-right-width: 1px;
}
.ui-listview > li.ui-li-has-alt > .ui-btn + .ui-btn:focus {
z-index: 3;
}
ol.ui-listview,
ol.ui-listview > .ui-li-divider {
counter-reset: listnumbering;
}
ol.ui-listview > li > .ui-btn,
ol.ui-listview > li.ui-li-static {
vertical-align: middle;
}
ol.ui-listview > li > .ui-btn:first-child:before,
ol.ui-listview > li.ui-li-static:before,
ol.ui-listview > li.ui-field-contain > label:before,
ol.ui-listview > li.ui-field-contain > .ui-controlgroup-label:before {
display: inline-block;
font-size: .9em;
font-weight: normal;
padding-right: .3em;
min-width: 1.4em;
line-height: 1.5;
vertical-align: middle;
counter-increment: listnumbering;
content: counter(listnumbering) ".";
}
ol.ui-listview > li.ui-field-contain:before {
content: none;
display: none;
}
ol.ui-listview > li h1:first-child,
ol.ui-listview > li h2:first-child,
ol.ui-listview > li h3:first-child,
ol.ui-listview > li h4:first-child,
ol.ui-listview > li h5:first-child,
ol.ui-listview > li h6:first-child,
ol.ui-listview > li p:first-child,
ol.ui-listview > li img:first-child + * {
display: inline-block;
vertical-align: middle;
}
ol.ui-listview > li h1:first-child ~ *,
ol.ui-listview > li h2:first-child ~ *,
ol.ui-listview > li h3:first-child ~ *,
ol.ui-listview > li h4:first-child ~ *,
ol.ui-listview > li h5:first-child ~ *,
ol.ui-listview > li h6:first-child ~ *,
ol.ui-listview > li p:first-child ~ *,
ol.ui-listview > li img:first-child + * ~ * {
margin-top: 0;
text-indent: 2.04em; /* (1.4em + .3em) * .9em / .75em */
}
html .ui-filterable + .ui-listview,
html .ui-filterable.ui-listview {
margin-top: .5em;
}
.ui-collapsible-content > form.ui-filterable {
margin-top: -.5em;
}
.ui-collapsible-content > .ui-input-search.ui-filterable {
margin-top: 0;
}
.ui-collapsible-content > .ui-filterable + .ui-listview:not(.ui-listview-inset) > li.ui-first-child,
.ui-collapsible-content > .ui-filterable + .ui-listview:not(.ui-listview-inset) > li.ui-first-child > a.ui-btn,
.ui-collapsible-content > .ui-filterable.ui-listview:not(.ui-listview-inset) > li.ui-first-child,
.ui-collapsible-content > .ui-filterable.ui-listview:not(.ui-listview-inset) > li.ui-first-child > a.ui-btn {
border-top-width: 1px;
}
div.ui-slider {
height: 30px;
margin: .5em 0;
padding: 0;
-ms-touch-action: pan-y pinch-zoom double-tap-zoom;
}
div.ui-slider:before,
div.ui-slider:after {
content: "";
display: table;
}
div.ui-slider:after {
clear: both;
}
input.ui-slider-input {
display: block;
float: left;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 4px;
width: 40px;
height: 20px;
line-height: 20px;
border-width: 1px;
border-style: solid;
outline: 0;
text-align: center;
vertical-align: text-bottom;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.ui-slider-input::-webkit-outer-spin-button,
.ui-slider-input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.ui-slider-track {
position: relative;
overflow: visible;
border-width: 1px;
border-style: solid;
height: 15px;
margin: 0 15px 0 68px;
top: 6px;
}
.ui-slider-track.ui-mini {
height: 12px;
top: 8px;
}
.ui-slider-track .ui-slider-bg {
height: 100%;
}
/* High level of specificity to override button margins in grids */
.ui-slider-track .ui-btn.ui-slider-handle {
position: absolute;
z-index: 1;
top: 50%;
width: 28px;
height: 28px;
margin: -15px 0 0 -15px;
outline: 0;
padding: 0;
}
.ui-slider-track.ui-mini .ui-slider-handle {
height: 14px;
width: 14px;
margin: -8px 0 0 -8px;
}
select.ui-slider-switch {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
div.ui-slider-switch {
display: inline-block;
height: 32px;
width: 5.8em;
top: 0;
}
/* reset the clearfix */
div.ui-slider-switch:before,
div.ui-slider-switch:after {
display: none;
clear: none;
}
div.ui-slider-switch.ui-mini {
height: 29px;
top: 0;
}
.ui-slider-inneroffset {
margin: 0 16px;
position: relative;
z-index: 1;
}
.ui-slider-switch.ui-mini .ui-slider-inneroffset {
margin: 0 15px 0 14px;
}
.ui-slider-switch .ui-btn.ui-slider-handle {
margin: 1px 0 0 -15px;
}
.ui-slider-switch.ui-mini .ui-slider-handle {
width: 25px;
height: 25px;
margin: 1px 0 0 -13px;
padding: 0;
}
.ui-slider-handle-snapping {
-webkit-transition: left 70ms linear;
-moz-transition: left 70ms linear;
transition: left 70ms linear;
}
.ui-slider-switch .ui-slider-label {
position: absolute;
text-align: center;
width: 100%;
overflow: hidden;
font-size: 16px;
top: 0;
line-height: 2;
min-height: 100%;
white-space: nowrap;
cursor: pointer;
}
.ui-slider-switch.ui-mini .ui-slider-label {
font-size: 14px;
}
.ui-slider-switch .ui-slider-label-a {
z-index: 1;
left: 0;
text-indent: -1.5em;
}
.ui-slider-switch .ui-slider-label-b {
z-index: 0;
right: 0;
text-indent: 1.5em;
}
/* The corner radii for ui-slider-switch/track can be specified in theme CSS. The bg and handle inherits. */
.ui-slider-track .ui-slider-bg,
.ui-slider-switch .ui-slider-label,
.ui-slider-switch .ui-slider-inneroffset,
.ui-slider-handle {
-webkit-border-radius: inherit;
border-radius: inherit;
}
.ui-field-contain div.ui-slider-switch {
margin: 0;
}
/* ui-hide-label deprecated in 1.4. TODO: Remove in 1.5 */
.ui-field-contain div.ui-slider-switch,
.ui-field-contain.ui-hide-label div.ui-slider-switch,
html .ui-popup .ui-field-contain div.ui-slider-switch {
display: inline-block;
width: 5.8em;
}
/* slider tooltip
-----------------------------------------------------------------------------------------------------------*/
.ui-slider-popup {
width: 64px;
height: 64px;
font-size: 36px;
padding-top: 14px;
opacity: 0.8;
}
.ui-slider-popup {
position: absolute !important;
text-align: center;
z-index: 100;
}
.ui-slider-track .ui-btn.ui-slider-handle {
font-size: .9em;
line-height: 30px;
}
.ui-rangeslider {
margin: .5em 0;
}
.ui-rangeslider:before,
.ui-rangeslider:after {
content: "";
display: table;
}
.ui-rangeslider:after {
clear: both;
}
.ui-rangeslider .ui-slider-input.ui-rangeslider-last {
float: right;
}
.ui-rangeslider .ui-rangeslider-sliders {
position: relative;
overflow: visible;
height: 30px;
margin: 0 68px;
}
.ui-rangeslider .ui-rangeslider-sliders .ui-slider-track {
position: absolute;
top: 6px;
right: 0;
left: 0;
margin: 0;
}
.ui-rangeslider.ui-mini .ui-rangeslider-sliders .ui-slider-track {
top: 8px;
}
.ui-rangeslider .ui-slider-track:first-child .ui-slider-bg {
display: none;
}
.ui-rangeslider .ui-rangeslider-sliders .ui-slider-track:first-child {
background-color: transparent;
background: none;
border-width: 0;
height: 0;
}
/* this makes ie6 and ie7 set height to 0 to fix z-index problem */
html >/**/body .ui-rangeslider .ui-rangeslider-sliders .ui-slider-track:first-child {
height: 15px;
border-width: 1px;
}
html >/**/body .ui-rangeslider.ui-mini .ui-rangeslider-sliders .ui-slider-track:first-child {
height: 12px;
}
/* Hide the second label (the first is moved outside the div) */
div.ui-rangeslider label {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
}
.ui-field-contain .ui-rangeslider input.ui-slider-input,
.ui-field-contain .ui-rangeslider.ui-mini input.ui-slider-input,
.ui-field-contain .ui-rangeslider .ui-rangeslider-sliders,
.ui-field-contain .ui-rangeslider.ui-mini .ui-rangeslider-sliders {
margin-top: 0;
margin-bottom: 0;
}
.ui-input-text,
.ui-input-search {
margin: .5em 0;
border-width: 1px;
border-style: solid;
}
.ui-mini {
margin: .446em;
}
.ui-input-text input,
.ui-input-search input,
textarea.ui-input-text {
padding: .4em;
line-height: 1.4em;
display: block;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
}
.ui-input-text input,
.ui-input-search input {
margin: 0;
min-height: 2.2em;
text-align: left; /* Opera aligns type="date" right by default */
border: 0;
background: transparent none;
-webkit-appearance: none;
-webkit-border-radius: inherit;
border-radius: inherit;
}
textarea.ui-input-text {
overflow: auto;
resize: vertical;
}
.ui-mini .ui-input-text input,
.ui-mini .ui-input-search input,
.ui-input-text.ui-mini input,
.ui-input-search.ui-mini input,
.ui-mini textarea.ui-input-text,
textarea.ui-mini {
font-size: 14px;
}
/* Same margin for mini textareas as other mini sized widgets (12.5/14 * 0.5em) */
.ui-mini textarea.ui-input-text,
textarea.ui-mini {
margin: .446em 0;
}
.ui-input-has-clear,
.ui-input-search {
position: relative;
}
/* Padding on the div instead of input because of browser spinners etc. */
.ui-input-has-clear {
padding-right: 2.375em;
}
.ui-mini.ui-input-has-clear {
padding-right: 2.923em;
}
.ui-input-has-clear input {
padding-right: 0;
/* Autofill on Chrome has bg color so we unset corners right as well. */
-webkit-border-top-right-radius: 0;
border-top-right-radius: 0;
-webkit-border-bottom-right-radius: 0;
border-bottom-right-radius: 0;
}
/* Search icon */
.ui-input-search input {
padding-left: 1.75em;
}
.ui-input-search:after {
position: absolute;
left: .3125em;
top: 50%;
margin-top: -7px;
content: "";
background-position: center center;
background-repeat: no-repeat;
width: 14px;
height: 14px;
filter: Alpha(Opacity=50);
opacity: .5;
}
.ui-input-search.ui-input-has-clear .ui-btn.ui-input-clear,
.ui-input-text.ui-input-has-clear .ui-btn.ui-input-clear {
position: absolute;
right: 0;
top: 50%;
margin: -14px .3125em 0;
border: 0;
background-color: transparent;
}
.ui-input-search .ui-input-clear-hidden,
.ui-input-text .ui-input-clear-hidden {
display: none;
}
/* Resolves issue #5166: Added to support issue introduced in Firefox 15. We can likely remove this in the future. */
.ui-input-text input::-moz-placeholder,
.ui-input-search input::-moz-placeholder,
textarea.ui-input-text::-moz-placeholder {
color: #aaa;
}
/* Same for IE10 */
.ui-input-text input:-ms-input-placeholder,
.ui-input-search input:-ms-input-placeholder,
textarea.ui-input-text:-ms-input-placeholder {
color: #aaa;
}
/* Resolves issue #5131: Width of textinput depends on its type,
for Android 4.1 */
.ui-input-text input[type=number]::-webkit-outer-spin-button {
margin: 0;
}
/* Resolves issue #5756: Textinput in IE10 has a default clear button */
.ui-input-text input::-ms-clear,
.ui-input-search input::-ms-clear {
display: none;
}
.ui-input-text input:focus,
.ui-input-search input:focus {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
textarea.ui-input-text.ui-textinput-autogrow {
overflow: hidden;
}
.ui-textinput-autogrow-resize {
-webkit-transition: height 0.25s;
-o-transition: height 0.25s;
-moz-transition: height 0.25s;
transition: height 0.25s;
}
.ui-flipswitch {
display: inline-block;
vertical-align: middle;
width: 5.875em; /* Override this and padding-left in next rule if you use labels other than "on/off" and need more space */
height: 1.875em;
border-width: 1px;
border-style: solid;
margin: .5em 0;
overflow: hidden;
-webkit-transition-property: padding, width, background-color, color, border-color;
-moz-transition-property: padding, width, background-color, color, border-color;
-o-transition-property: padding, width, background-color, color, border-color;
transition-property: padding, width, background-color, color, border-color;
-webkit-transition-duration: 100ms;
-moz-transition-duration: 100ms;
-o-transition-duration: 100ms;
transition-duration: 100ms;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
}
.ui-flipswitch.ui-flipswitch-active {
padding-left: 4em; /* Override this and width in previous rule if you use labels other than "on/off" and need more space */
width: 1.875em;
}
.ui-flipswitch-input {
position: absolute;
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
border: 0;
outline: 0;
filter: Alpha(Opacity=0);
opacity: 0;
}
.ui-flipswitch .ui-btn.ui-flipswitch-on,
.ui-flipswitch .ui-flipswitch-off {
float: left;
height: 1.75em;
margin: .0625em;
line-height: 1.65em;
}
.ui-flipswitch .ui-btn.ui-flipswitch-on {
width: 1.75em;
padding: 0;
text-indent: -2.6em; /* Override this to center text if you use a label other than "on" */
text-align: left;
border-width: 1px;
border-style: solid;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
border-radius: inherit;
overflow: visible;
color: inherit;
text-shadow: inherit;
}
.ui-flipswitch .ui-flipswitch-off {
padding: 1px;
text-indent: 1em; /* Override this to center text if you use a label other than "off" */
}
/* Override field container CSS to prevent the flipswitch from becomming full width */
html .ui-field-contain > label + .ui-flipswitch,
html .ui-popup .ui-field-contain > label + .ui-flipswitch {
display: inline-block;
width: 5.875em; /* If you override the width for .ui-flipswitch you should repeat the same value here */
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.ui-field-contain .ui-flipswitch.ui-flipswitch-active,
.ui-popup .ui-field-contain .ui-flipswitch.ui-flipswitch-active {
width: 1.875em;
}
.ui-table {
border: 0;
border-collapse: collapse;
padding: 0;
width: 100%;
}
.ui-table th,
.ui-table td {
line-height: 1.5em;
text-align: left;
padding: .4em .5em;
vertical-align:top;
}
.ui-table th .ui-btn,
.ui-table td .ui-btn {
line-height: normal;
}
.ui-table th {
font-weight: bold;
}
.ui-table caption {
text-align: left;
margin-bottom: 1.4em;
opacity: .5;
}
/*
Styles for the table columntoggle mode
*/
.ui-table-columntoggle-btn {
float: right;
margin-bottom: .8em;
}
/* Remove top/bottom margins around the fieldcontain on check list */
.ui-table-columntoggle-popup fieldset {
margin:0;
}
.ui-table-columntoggle {
clear: both;
}
/* Hide all prioritized columns by default */
@media only all {
th.ui-table-priority-6,
td.ui-table-priority-6,
th.ui-table-priority-5,
td.ui-table-priority-5,
th.ui-table-priority-4,
td.ui-table-priority-4,
th.ui-table-priority-3,
td.ui-table-priority-3,
th.ui-table-priority-2,
td.ui-table-priority-2,
th.ui-table-priority-1,
td.ui-table-priority-1 {
display: none;
}
}
/* Preset breakpoints if ".ui-responsive" class added to table */
/* Show priority 1 at 320px (20em x 16px) */
@media screen and (min-width: 20em) {
.ui-table-columntoggle.ui-responsive th.ui-table-priority-1,
.ui-table-columntoggle.ui-responsive td.ui-table-priority-1 {
display: table-cell;
}
}
/* Show priority 2 at 480px (30em x 16px) */
@media screen and (min-width: 30em) {
.ui-table-columntoggle.ui-responsive th.ui-table-priority-2,
.ui-table-columntoggle.ui-responsive td.ui-table-priority-2 {
display: table-cell;
}
}
/* Show priority 3 at 640px (40em x 16px) */
@media screen and (min-width: 40em) {
.ui-table-columntoggle.ui-responsive th.ui-table-priority-3,
.ui-table-columntoggle.ui-responsive td.ui-table-priority-3 {
display: table-cell;
}
}
/* Show priority 4 at 800px (50em x 16px) */
@media screen and (min-width: 50em) {
.ui-table-columntoggle.ui-responsive th.ui-table-priority-4,
.ui-table-columntoggle.ui-responsive td.ui-table-priority-4 {
display: table-cell;
}
}
/* Show priority 5 at 960px (60em x 16px) */
@media screen and (min-width: 60em) {
.ui-table-columntoggle.ui-responsive th.ui-table-priority-5,
.ui-table-columntoggle.ui-responsive td.ui-table-priority-5 {
display: table-cell;
}
}
/* Show priority 6 at 1,120px (70em x 16px) */
@media screen and (min-width: 70em) {
.ui-table-columntoggle.ui-responsive th.ui-table-priority-6,
.ui-table-columntoggle.ui-responsive td.ui-table-priority-6 {
display: table-cell;
}
}
/* Unchecked manually: Always hide */
.ui-table-columntoggle th.ui-table-cell-hidden,
.ui-table-columntoggle td.ui-table-cell-hidden,
.ui-table-columntoggle.ui-responsive th.ui-table-cell-hidden,
.ui-table-columntoggle.ui-responsive td.ui-table-cell-hidden {
display: none;
}
/* Checked manually: Always show */
.ui-table-columntoggle th.ui-table-cell-visible,
.ui-table-columntoggle td.ui-table-cell-visible,
.ui-table-columntoggle.ui-responsive th.ui-table-cell-visible,
.ui-table-columntoggle.ui-responsive td.ui-table-cell-visible {
display: table-cell;
}
/*
Styles for the table columntoggle mode
*/
.ui-table-reflow td .ui-table-cell-label,
.ui-table-reflow th .ui-table-cell-label {
display: none;
}
/* Mobile first styles: Begin with the stacked presentation at narrow widths */
@media only all {
/* Hide the table headers */
.ui-table-reflow thead td,
.ui-table-reflow thead th {
display: none;
}
/* Show the table cells as a block level element */
.ui-table-reflow td,
.ui-table-reflow th {
text-align: left;
display: block;
}
/* Add a fair amount of top margin to visually separate each row when stacked */
.ui-table-reflow tbody th {
margin-top: 3em;
}
/* Make the label elements a percentage width */
.ui-table-reflow td .ui-table-cell-label,
.ui-table-reflow th .ui-table-cell-label {
padding: .4em;
min-width: 30%;
display: inline-block;
margin: -.4em 1em -.4em -.4em;
}
/* For grouped headers, have a different style to visually separate the levels by classing the first label in each col group */
.ui-table-reflow th .ui-table-cell-label-top,
.ui-table-reflow td .ui-table-cell-label-top {
display: block;
padding: .4em 0;
margin: .4em 0;
text-transform: uppercase;
font-size: .9em;
font-weight: normal;
}
}
/* Breakpoint to show as a standard table at 560px (35em x 16px) or wider */
@media ( min-width: 35em ) {
/* Show the table header rows */
.ui-table-reflow.ui-responsive td,
.ui-table-reflow.ui-responsive th,
.ui-table-reflow.ui-responsive tbody th,
.ui-table-reflow.ui-responsive tbody td,
.ui-table-reflow.ui-responsive thead td,
.ui-table-reflow.ui-responsive thead th {
display: table-cell;
margin: 0;
}
/* Hide the labels in each cell */
.ui-table-reflow.ui-responsive td .ui-table-cell-label,
.ui-table-reflow.ui-responsive th .ui-table-cell-label {
display: none;
}
}
/* Hack to make IE9 and WP7.5 treat cells like block level elements, scoped to ui-responsive class */
/* Applied in a max-width media query up to the table layout breakpoint so we don't need to negate this*/
@media ( max-width: 35em ) {
.ui-table-reflow.ui-responsive td,
.ui-table-reflow.ui-responsive th {
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
float: left;
clear: left;
}
}
/* Panel */
.ui-panel {
width: 17em;
min-height: 100%;
max-height: none;
border-width: 0;
position: absolute;
top: 0;
display: block;
}
.ui-panel-closed {
width: 0;
max-height: 100%;
overflow: hidden;
visibility: hidden;
left: 0;
clip: rect(1px,1px,1px,1px);
}
.ui-panel-fixed {
position: fixed;
bottom: -1px; /* Fixes gap on Chrome for Android */
padding-bottom: 1px;
}
.ui-panel-display-reveal {
z-index: 1;
}
.ui-panel-display-push {
z-index: 999;
}
.ui-panel-display-overlay {
z-index: 1001; /* Fixed toolbars have z-index 1000 */
}
.ui-panel-inner {
padding: 1em;
}
/* Container, page and wrapper */
.ui-panel-page-container {
overflow-x: visible;
}
.ui-panel-page-container-themed .ui-page-active {
background: none;
}
.ui-panel-wrapper {
position: relative;
min-height: inherit;
border: 0;
overflow-x: hidden;
z-index: 999;
}
/* Fixed toolbars */
.ui-panel-fixed-toolbar {
overflow-x: hidden;
}
/* Dismiss */
.ui-panel-dismiss {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 100%;
z-index: 1002;
display: none;
}
.ui-panel-dismiss-open {
display: block;
}
/* Animate class is added to panel, wrapper and fixed toolbars */
.ui-panel-animate {
-webkit-transition: -webkit-transform 300ms ease;
-webkit-transition-duration: 300ms;
-moz-transition: -moz-transform 300ms ease;
transition: transform 300ms ease;
}
/* Fix for Windows Phone issue #6349: unset the transition for transforms in case of fixed toolbars. */
@media screen and ( max-device-width: 768px ) {
.ui-page-header-fixed .ui-panel-animate.ui-panel-wrapper,
.ui-page-footer-fixed .ui-panel-animate.ui-panel-wrapper,
.ui-panel-animate.ui-panel-fixed-toolbar {
-ms-transition: none;
}
/* We need a transitionend event ... */
.ui-panel-animate.ui-panel-fixed-toolbar {
-ms-transition: -ms-transform 1ms;
-ms-transform: rotate(0deg);
}
}
/* Hardware acceleration for smoother transitions on WebKit browsers */
.ui-panel-animate.ui-panel:not(.ui-panel-display-reveal) {
-webkit-backface-visibility: hidden;
-webkit-transform: translate3d(0,0,0);
}
/* Panel positioning (for overlay and push) */
/* Panel left closed */
.ui-panel-position-left {
left: -17em;
}
/* Panel left closed animated */
.ui-panel-animate.ui-panel-position-left.ui-panel-display-overlay,
.ui-panel-animate.ui-panel-position-left.ui-panel-display-push {
left: 0;
-webkit-transform: translate3d(-17em,0,0);
-moz-transform: translate3d(-17em,0,0);
transform: translate3d(-17em,0,0);
}
/* Panel left open */
.ui-panel-position-left.ui-panel-display-reveal, /* Unset "panel left closed" for reveal */
.ui-panel-open.ui-panel-position-left {
left: 0;
}
/* Panel left open animated */
.ui-panel-animate.ui-panel-open.ui-panel-position-left.ui-panel-display-overlay,
.ui-panel-animate.ui-panel-open.ui-panel-position-left.ui-panel-display-push {
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
-moz-transform: none;
}
/* Panel right closed */
.ui-panel-position-right {
right: -17em;
}
/* Panel right closed animated */
.ui-panel-animate.ui-panel-position-right.ui-panel-display-overlay,
.ui-panel-animate.ui-panel-position-right.ui-panel-display-push {
right: 0;
-webkit-transform: translate3d(17em,0,0);
-moz-transform: translate3d(17em,0,0);
transform: translate3d(17em,0,0);
}
/* Panel right open */
.ui-panel-position-right.ui-panel-display-reveal, /* Unset "panel right closed" for reveal */
.ui-panel-position-right.ui-panel-open {
right: 0;
}
/* Panel right open animated */
.ui-panel-animate.ui-panel-open.ui-panel-position-right.ui-panel-display-overlay,
.ui-panel-animate.ui-panel-open.ui-panel-position-right.ui-panel-display-push {
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
-moz-transform: none;
}
/* Wrapper and fixed toolbars positioning (for reveal and push) */
/* Panel left open */
.ui-panel-page-content-position-left {
left: 17em;
right: -17em;
}
/* Panel left open animated */
.ui-panel-animate.ui-panel-page-content-position-left {
left: 0;
right: 0;
-webkit-transform: translate3d(17em,0,0);
-moz-transform: translate3d(17em,0,0);
transform: translate3d(17em,0,0);
}
/* Panel right open */
.ui-panel-page-content-position-right {
left: -17em;
right: 17em;
}
/* Panel right open animated */
.ui-panel-animate.ui-panel-page-content-position-right {
left: 0;
right: 0;
-webkit-transform: translate3d(-17em,0,0);
-moz-transform: translate3d(-17em,0,0);
transform: translate3d(-17em,0,0);
}
/* Dismiss model open */
.ui-panel-dismiss-open.ui-panel-dismiss-position-left {
left: 17em;
}
.ui-panel-dismiss-open.ui-panel-dismiss-position-right {
right: 17em;
}
/* Shadows and borders */
.ui-panel-display-reveal {
-webkit-box-shadow: inset -5px 0 5px rgba(0,0,0,.15);
-moz-box-shadow: inset -5px 0 5px rgba(0,0,0,.15);
box-shadow: inset -5px 0 5px rgba(0,0,0,.15);
}
.ui-panel-position-right.ui-panel-display-reveal {
-webkit-box-shadow: inset 5px 0 5px rgba(0,0,0,.15);
-moz-box-shadow: inset 5px 0 5px rgba(0,0,0,.15);
box-shadow: inset 5px 0 5px rgba(0,0,0,.15);
}
.ui-panel-display-overlay {
-webkit-box-shadow: 5px 0 5px rgba(0,0,0,.15);
-moz-box-shadow: 5px 0 5px rgba(0,0,0,.15);
box-shadow: 5px 0 5px rgba(0,0,0,.15);
}
.ui-panel-position-right.ui-panel-display-overlay {
-webkit-box-shadow: -5px 0 5px rgba(0,0,0,.15);
-moz-box-shadow: -5px 0 5px rgba(0,0,0,.15);
box-shadow: -5px 0 5px rgba(0,0,0,.15);
}
.ui-panel-open.ui-panel-position-left.ui-panel-display-push {
border-right-width: 1px;
margin-right: -1px;
}
.ui-panel-page-content-position-left.ui-panel-page-content-display-push {
margin-left: 1px;
width: auto;
}
.ui-panel-open.ui-panel-position-right.ui-panel-display-push {
border-left-width: 1px;
margin-left: -1px;
}
.ui-panel-page-content-position-right.ui-panel-page-content-display-push {
margin-right: 1px;
width: auto;
}
/* Responsive: wrap on wide viewports once open */
@media (min-width:55em) {
.ui-responsive-panel .ui-panel-page-content-open.ui-panel-page-content-position-left {
margin-right: 17em;
}
.ui-responsive-panel .ui-panel-page-content-open.ui-panel-page-content-position-right {
margin-left: 17em;
}
.ui-responsive-panel .ui-panel-page-content-open {
width: auto;
}
.ui-responsive-panel .ui-panel-dismiss-display-push,
.ui-responsive-panel.ui-page-active ~ .ui-panel-dismiss-display-push {
display: none;
}
}
.ui-tabs {
position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
padding: .2em;
}
================================================
FILE: stenogotchi/ui/web/static/js/jquery.mobile/jquery.mobile-1.4.5.js
================================================
/*!
* jQuery Mobile 1.4.5
* Git HEAD hash: 68e55e78b292634d3991c795f06f5e37a512decc <> Date: Fri Oct 31 2014 17:33:30 UTC
* http://jquerymobile.com
*
* Copyright 2010, 2014 jQuery Foundation, Inc. and othercontributors
* Released under the MIT license.
* http://jquery.org/license
*
*/
(function ( root, doc, factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( [ "jquery" ], function ( $ ) {
factory( $, root, doc );
return $.mobile;
});
} else {
// Browser globals
factory( root.jQuery, root, doc );
}
}( this, document, function ( jQuery, window, document, undefined ) {
(function( $ ) {
$.mobile = {};
}( jQuery ));
/*!
* jQuery UI Core c0ab71056b936627e8a7821f03c044aec6280a40
* http://jqueryui.com
*
* Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/ui-core/
*/
(function( $, undefined ) {
var uuid = 0,
runiqueId = /^ui-id-\d+$/;
// $.ui might exist from components with no dependencies, e.g., $.ui.position
$.ui = $.ui || {};
$.extend( $.ui, {
version: "c0ab71056b936627e8a7821f03c044aec6280a40",
keyCode: {
BACKSPACE: 8,
COMMA: 188,
DELETE: 46,
DOWN: 40,
END: 35,
ENTER: 13,
ESCAPE: 27,
HOME: 36,
LEFT: 37,
PAGE_DOWN: 34,
PAGE_UP: 33,
PERIOD: 190,
RIGHT: 39,
SPACE: 32,
TAB: 9,
UP: 38
}
});
// plugins
$.fn.extend({
focus: (function( orig ) {
return function( delay, fn ) {
return typeof delay === "number" ?
this.each(function() {
var elem = this;
setTimeout(function() {
$( elem ).focus();
if ( fn ) {
fn.call( elem );
}
}, delay );
}) :
orig.apply( this, arguments );
};
})( $.fn.focus ),
scrollParent: function() {
var scrollParent;
if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
scrollParent = this.parents().filter(function() {
return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
}).eq(0);
} else {
scrollParent = this.parents().filter(function() {
return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
}).eq(0);
}
return ( /fixed/ ).test( this.css( "position") ) || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent;
},
uniqueId: function() {
return this.each(function() {
if ( !this.id ) {
this.id = "ui-id-" + (++uuid);
}
});
},
removeUniqueId: function() {
return this.each(function() {
if ( runiqueId.test( this.id ) ) {
$( this ).removeAttr( "id" );
}
});
}
});
// selectors
function focusable( element, isTabIndexNotNaN ) {
var map, mapName, img,
nodeName = element.nodeName.toLowerCase();
if ( "area" === nodeName ) {
map = element.parentNode;
mapName = map.name;
if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
return false;
}
img = $( "img[usemap=#" + mapName + "]" )[0];
return !!img && visible( img );
}
return ( /input|select|textarea|button|object/.test( nodeName ) ?
!element.disabled :
"a" === nodeName ?
element.href || isTabIndexNotNaN :
isTabIndexNotNaN) &&
// the element and all of its ancestors must be visible
visible( element );
}
function visible( element ) {
return $.expr.filters.visible( element ) &&
!$( element ).parents().addBack().filter(function() {
return $.css( this, "visibility" ) === "hidden";
}).length;
}
$.extend( $.expr[ ":" ], {
data: $.expr.createPseudo ?
$.expr.createPseudo(function( dataName ) {
return function( elem ) {
return !!$.data( elem, dataName );
};
}) :
// support: jQuery <1.8
function( elem, i, match ) {
return !!$.data( elem, match[ 3 ] );
},
focusable: function( element ) {
return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
},
tabbable: function( element ) {
var tabIndex = $.attr( element, "tabindex" ),
isTabIndexNaN = isNaN( tabIndex );
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
}
});
// support: jQuery <1.8
if ( !$( "" ).outerWidth( 1 ).jquery ) {
$.each( [ "Width", "Height" ], function( i, name ) {
var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
type = name.toLowerCase(),
orig = {
innerWidth: $.fn.innerWidth,
innerHeight: $.fn.innerHeight,
outerWidth: $.fn.outerWidth,
outerHeight: $.fn.outerHeight
};
function reduce( elem, size, border, margin ) {
$.each( side, function() {
size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
if ( border ) {
size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
}
if ( margin ) {
size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
}
});
return size;
}
$.fn[ "inner" + name ] = function( size ) {
if ( size === undefined ) {
return orig[ "inner" + name ].call( this );
}
return this.each(function() {
$( this ).css( type, reduce( this, size ) + "px" );
});
};
$.fn[ "outer" + name] = function( size, margin ) {
if ( typeof size !== "number" ) {
return orig[ "outer" + name ].call( this, size );
}
return this.each(function() {
$( this).css( type, reduce( this, size, true, margin ) + "px" );
});
};
});
}
// support: jQuery <1.8
if ( !$.fn.addBack ) {
$.fn.addBack = function( selector ) {
return this.add( selector == null ?
this.prevObject : this.prevObject.filter( selector )
);
};
}
// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
$.fn.removeData = (function( removeData ) {
return function( key ) {
if ( arguments.length ) {
return removeData.call( this, $.camelCase( key ) );
} else {
return removeData.call( this );
}
};
})( $.fn.removeData );
}
// deprecated
$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
$.support.selectstart = "onselectstart" in document.createElement( "div" );
$.fn.extend({
disableSelection: function() {
return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
".ui-disableSelection", function( event ) {
event.preventDefault();
});
},
enableSelection: function() {
return this.unbind( ".ui-disableSelection" );
},
zIndex: function( zIndex ) {
if ( zIndex !== undefined ) {
return this.css( "zIndex", zIndex );
}
if ( this.length ) {
var elem = $( this[ 0 ] ), position, value;
while ( elem.length && elem[ 0 ] !== document ) {
// Ignore z-index if position is set to a value where z-index is ignored by the browser
// This makes behavior of this function consistent across browsers
// WebKit always returns auto if the element is positioned
position = elem.css( "position" );
if ( position === "absolute" || position === "relative" || position === "fixed" ) {
// IE returns 0 when zIndex is not specified
// other browsers return a string
// we ignore the case of nested elements with an explicit value of 0
//
",
// For non-fixed supportin browsers. Position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top
fakeFixLoader: function() {
var activeBtn = $( "." + $.mobile.activeBtnClass ).first();
this.element
.css({
top: $.support.scrollTop && this.window.scrollTop() + this.window.height() / 2 ||
activeBtn.length && activeBtn.offset().top || 100
});
},
// check position of loader to see if it appears to be "fixed" to center
// if not, use abs positioning
checkLoaderPosition: function() {
var offset = this.element.offset(),
scrollTop = this.window.scrollTop(),
screenHeight = $.mobile.getScreenHeight();
if ( offset.top < scrollTop || ( offset.top - scrollTop ) > screenHeight ) {
this.element.addClass( "ui-loader-fakefix" );
this.fakeFixLoader();
this.window
.unbind( "scroll", this.checkLoaderPosition )
.bind( "scroll", $.proxy( this.fakeFixLoader, this ) );
}
},
resetHtml: function() {
this.element.html( $( this.defaultHtml ).html() );
},
// Turn on/off page loading message. Theme doubles as an object argument
// with the following shape: { theme: '', text: '', html: '', textVisible: '' }
// NOTE that the $.mobile.loading* settings and params past the first are deprecated
// TODO sweet jesus we need to break some of this out
show: function( theme, msgText, textonly ) {
var textVisible, message, loadSettings;
this.resetHtml();
// use the prototype options so that people can set them globally at
// mobile init. Consistency, it's what's for dinner
if ( $.type( theme ) === "object" ) {
loadSettings = $.extend( {}, this.options, theme );
theme = loadSettings.theme;
} else {
loadSettings = this.options;
// here we prefer the theme value passed as a string argument, then
// we prefer the global option because we can't use undefined default
// prototype options, then the prototype option
theme = theme || loadSettings.theme;
}
// set the message text, prefer the param, then the settings object
// then loading message
message = msgText || ( loadSettings.text === false ? "" : loadSettings.text );
// prepare the dom
$html.addClass( "ui-loading" );
textVisible = loadSettings.textVisible;
// add the proper css given the options (theme, text, etc)
// Force text visibility if the second argument was supplied, or
// if the text was explicitly set in the object args
this.element.attr("class", loaderClass +
" ui-corner-all ui-body-" + theme +
" ui-loader-" + ( textVisible || msgText || theme.text ? "verbose" : "default" ) +
( loadSettings.textonly || textonly ? " ui-loader-textonly" : "" ) );
// TODO verify that jquery.fn.html is ok to use in both cases here
// this might be overly defensive in preventing unknowing xss
// if the html attribute is defined on the loading settings, use that
// otherwise use the fallbacks from above
if ( loadSettings.html ) {
this.element.html( loadSettings.html );
} else {
this.element.find( "h1" ).text( message );
}
// If the pagecontainer widget has been defined we may use the :mobile-pagecontainer
// and attach to the element on which the pagecontainer widget has been defined. If not,
// we attach to the body.
this.element.appendTo( $.mobile.pagecontainer ?
$( ":mobile-pagecontainer" ) : $( "body" ) );
// check that the loader is visible
this.checkLoaderPosition();
// on scroll check the loader position
this.window.bind( "scroll", $.proxy( this.checkLoaderPosition, this ) );
},
hide: function() {
$html.removeClass( "ui-loading" );
if ( this.options.text ) {
this.element.removeClass( "ui-loader-fakefix" );
}
this.window.unbind( "scroll", this.fakeFixLoader );
this.window.unbind( "scroll", this.checkLoaderPosition );
}
});
})(jQuery, this);
/*!
* jQuery hashchange event - v1.3 - 7/21/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
// Script: jQuery hashchange event
//
// *Version: 1.3, Last updated: 7/21/2010*
//
// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
// GitHub - http://github.com/cowboy/jquery-hashchange/
// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
//
// About: License
//
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
//
// About: Examples
//
// These working examples, complete with fully commented code, illustrate a few
// ways in which this plugin can be used.
//
// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
//
// About: Support and Testing
//
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
//
// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
//
// About: Known issues
//
// While this jQuery hashchange event implementation is quite stable and
// robust, there are a few unfortunate browser bugs surrounding expected
// hashchange event-based behaviors, independent of any JavaScript
// window.onhashchange abstraction. See the following examples for more
// information:
//
// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
//
// Also note that should a browser natively support the window.onhashchange
// event, but not report that it does, the fallback polling loop will be used.
//
// About: Release History
//
// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
// "removable" for mobile-only development. Added IE6/7 document.title
// support. Attempted to make Iframe as hidden as possible by using
// techniques from http://www.paciellogroup.com/blog/?p=604. Added
// support for the "shortcut" format $(window).hashchange( fn ) and
// $(window).hashchange() like jQuery provides for built-in events.
// Renamed jQuery.hashchangeDelay to and
// lowered its default value to 50. Added
// and properties plus document-domain.html
// file to address access denied issues when setting document.domain in
// IE6/7.
// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
// from a page on another domain would cause an error in Safari 4. Also,
// IE6/7 Iframe is now inserted after the body (this actually works),
// which prevents the page from scrolling when the event is first bound.
// Event can also now be bound before DOM ready, but it won't be usable
// before then in IE6/7.
// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
// where browser version is incorrectly reported as 8.0, despite
// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
// window.onhashchange functionality into a separate plugin for users
// who want just the basic event & back button support, without all the
// extra awesomeness that BBQ provides. This plugin will be included as
// part of jQuery BBQ, but also be available separately.
(function($,window,undefined){
'$:nomunge'; // Used by YUI compressor.
// Reused string.
var str_hashchange = 'hashchange',
// Method / object references.
doc = document,
fake_onhashchange,
special = $.event.special,
// Does the browser support window.onhashchange? Note that IE8 running in
// IE7 compatibility mode reports true for 'onhashchange' in window, even
// though the event isn't supported, so also test document.documentMode.
doc_mode = doc.documentMode,
supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
// Get location.hash (or what you'd expect location.hash to be) sans any
// leading #. Thanks for making this necessary, Firefox!
function get_fragment( url ) {
url = url || location.href;
return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
};
// Method: jQuery.fn.hashchange
//
// Bind a handler to the window.onhashchange event or trigger all bound
// window.onhashchange event handlers. This behavior is consistent with
// jQuery's built-in event handlers.
//
// Usage:
//
// > jQuery(window).hashchange( [ handler ] );
//
// Arguments:
//
// handler - (Function) Optional handler to be bound to the hashchange
// event. This is a "shortcut" for the more verbose form:
// jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
// all bound window.onhashchange event handlers will be triggered. This
// is a shortcut for the more verbose
// jQuery(window).trigger( 'hashchange' ). These forms are described in
// the section.
//
// Returns:
//
// (jQuery) The initial jQuery collection of elements.
// Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
// $(elem).hashchange() for triggering, like jQuery does for built-in events.
$.fn[ str_hashchange ] = function( fn ) {
return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
};
// Property: jQuery.fn.hashchange.delay
//
// The numeric interval (in milliseconds) at which the
// polling loop executes. Defaults to 50.
// Property: jQuery.fn.hashchange.domain
//
// If you're setting document.domain in your JavaScript, and you want hash
// history to work in IE6/7, not only must this property be set, but you must
// also set document.domain BEFORE jQuery is loaded into the page. This
// property is only applicable if you are supporting IE6/7 (or IE8 operating
// in "IE7 compatibility" mode).
//
// In addition, the property must be set to the
// path of the included "document-domain.html" file, which can be renamed or
// modified if necessary (note that the document.domain specified must be the
// same in both your main JavaScript as well as in this file).
//
// Usage:
//
// jQuery.fn.hashchange.domain = document.domain;
// Property: jQuery.fn.hashchange.src
//
// If, for some reason, you need to specify an Iframe src file (for example,
// when setting document.domain as in ), you can
// do so using this property. Note that when using this property, history
// won't be recorded in IE6/7 until the Iframe src file loads. This property
// is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
// compatibility" mode).
//
// Usage:
//
// jQuery.fn.hashchange.src = 'path/to/file.html';
$.fn[ str_hashchange ].delay = 50;
/*
$.fn[ str_hashchange ].domain = null;
$.fn[ str_hashchange ].src = null;
*/
// Event: hashchange event
//
// Fired when location.hash changes. In browsers that support it, the native
// HTML5 window.onhashchange event is used, otherwise a polling loop is
// initialized, running every milliseconds to
// see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
// compatibility" mode), a hidden Iframe is created to allow the back button
// and hash-based history to work.
//
// Usage as described in :
//
// > // Bind an event handler.
// > jQuery(window).hashchange( function(e) {
// > var hash = location.hash;
// > ...
// > });
// >
// > // Manually trigger the event handler.
// > jQuery(window).hashchange();
//
// A more verbose usage that allows for event namespacing:
//
// > // Bind an event handler.
// > jQuery(window).bind( 'hashchange', function(e) {
// > var hash = location.hash;
// > ...
// > });
// >
// > // Manually trigger the event handler.
// > jQuery(window).trigger( 'hashchange' );
//
// Additional Notes:
//
// * The polling loop and Iframe are not created until at least one handler
// is actually bound to the 'hashchange' event.
// * If you need the bound handler(s) to execute immediately, in cases where
// a location.hash exists on page load, via bookmark or page refresh for
// example, use jQuery(window).hashchange() or the more verbose
// jQuery(window).trigger( 'hashchange' ).
// * The event can be bound before DOM ready, but since it won't be usable
// before then in IE6/7 (due to the necessary Iframe), recommended usage is
// to bind it inside a DOM ready handler.
// Override existing $.event.special.hashchange methods (allowing this plugin
// to be defined after jQuery BBQ in BBQ's source code).
special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
// Called only when the first 'hashchange' event is bound to window.
setup: function() {
// If window.onhashchange is supported natively, there's nothing to do..
if ( supports_onhashchange ) { return false; }
// Otherwise, we need to create our own. And we don't want to call this
// until the user binds to the event, just in case they never do, since it
// will create a polling loop and possibly even a hidden Iframe.
$( fake_onhashchange.start );
},
// Called only when the last 'hashchange' event is unbound from window.
teardown: function() {
// If window.onhashchange is supported natively, there's nothing to do..
if ( supports_onhashchange ) { return false; }
// Otherwise, we need to stop ours (if possible).
$( fake_onhashchange.stop );
}
});
// fake_onhashchange does all the work of triggering the window.onhashchange
// event for browsers that don't natively support it, including creating a
// polling loop to watch for hash changes and in IE 6/7 creating a hidden
// Iframe to enable back and forward.
fake_onhashchange = (function(){
var self = {},
timeout_id,
// Remember the initial hash so it doesn't get triggered immediately.
last_hash = get_fragment(),
fn_retval = function(val){ return val; },
history_set = fn_retval,
history_get = fn_retval;
// Start the polling loop.
self.start = function() {
timeout_id || poll();
};
// Stop the polling loop.
self.stop = function() {
timeout_id && clearTimeout( timeout_id );
timeout_id = undefined;
};
// This polling loop checks every $.fn.hashchange.delay milliseconds to see
// if location.hash has changed, and triggers the 'hashchange' event on
// window when necessary.
function poll() {
var hash = get_fragment(),
history_hash = history_get( last_hash );
if ( hash !== last_hash ) {
history_set( last_hash = hash, history_hash );
$(window).trigger( str_hashchange );
} else if ( history_hash !== last_hash ) {
location.href = location.href.replace( /#.*/, '' ) + history_hash;
}
timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
};
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
window.attachEvent && !window.addEventListener && !supports_onhashchange && (function(){
// Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
// when running in "IE7 compatibility" mode.
var iframe,
iframe_src;
// When the event is bound and polling starts in IE 6/7, create a hidden
// Iframe for history handling.
self.start = function(){
if ( !iframe ) {
iframe_src = $.fn[ str_hashchange ].src;
iframe_src = iframe_src && iframe_src + get_fragment();
// Create hidden Iframe. Attempt to make Iframe as hidden as possible
// by using techniques from http://www.paciellogroup.com/blog/?p=604.
iframe = $('').hide()
// When Iframe has completely loaded, initialize the history and
// start polling.
.one( 'load', function(){
iframe_src || history_set( get_fragment() );
poll();
})
// Load Iframe src if specified, otherwise nothing.
.attr( 'src', iframe_src || 'javascript:0' )
// Append Iframe after the end of the body to prevent unnecessary
// initial page scrolling (yes, this works).
.insertAfter( 'body' )[0].contentWindow;
// Whenever `document.title` changes, update the Iframe's title to
// prettify the back/next history menu entries. Since IE sometimes
// errors with "Unspecified error" the very first time this is set
// (yes, very useful) wrap this with a try/catch block.
doc.onpropertychange = function(){
try {
if ( event.propertyName === 'title' ) {
iframe.document.title = doc.title;
}
} catch(e) {}
};
}
};
// Override the "stop" method since an IE6/7 Iframe was created. Even
// if there are no longer any bound event handlers, the polling loop
// is still necessary for back/next to work at all!
self.stop = fn_retval;
// Get history by looking at the hidden Iframe's location.hash.
history_get = function() {
return get_fragment( iframe.location.href );
};
// Set a new history item by opening and then closing the Iframe
// document, *then* setting its location.hash. If document.domain has
// been set, update that as well.
history_set = function( hash, history_hash ) {
var iframe_doc = iframe.document,
domain = $.fn[ str_hashchange ].domain;
if ( hash !== history_hash ) {
// Update Iframe with any initial `document.title` that might be set.
iframe_doc.title = doc.title;
// Opening the Iframe's document after it has been closed is what
// actually adds a history entry.
iframe_doc.open();
// Set document.domain for the Iframe document as well, if necessary.
domain && iframe_doc.write( '\x3cscript>document.domain="' + domain + '"\x3c/script>' );
iframe_doc.close();
// Update the Iframe's hash, for great justice.
iframe.location.hash = hash;
}
};
})();
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return self;
})();
})(jQuery,this);
(function( $, undefined ) {
/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
window.matchMedia = window.matchMedia || (function( doc, undefined ) {
var bool,
docElem = doc.documentElement,
refNode = docElem.firstElementChild || docElem.firstChild,
// fakeBody required for
fakeBody = doc.createElement( "body" ),
div = doc.createElement( "div" );
div.id = "mq-test-1";
div.style.cssText = "position:absolute;top:-100em";
fakeBody.style.background = "none";
fakeBody.appendChild(div);
return function(q){
div.innerHTML = "";
docElem.insertBefore( fakeBody, refNode );
bool = div.offsetWidth === 42;
docElem.removeChild( fakeBody );
return {
matches: bool,
media: q
};
};
}( document ));
// $.mobile.media uses matchMedia to return a boolean.
$.mobile.media = function( q ) {
return window.matchMedia( q ).matches;
};
})(jQuery);
(function( $, undefined ) {
var support = {
touch: "ontouchend" in document
};
$.mobile.support = $.mobile.support || {};
$.extend( $.support, support );
$.extend( $.mobile.support, support );
}( jQuery ));
(function( $, undefined ) {
$.extend( $.support, {
orientation: "orientation" in window && "onorientationchange" in window
});
}( jQuery ));
(function( $, undefined ) {
// thx Modernizr
function propExists( prop ) {
var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ),
v;
for ( v in props ) {
if ( fbCSS[ props[ v ] ] !== undefined ) {
return true;
}
}
}
var fakeBody = $( "" ).prependTo( "html" ),
fbCSS = fakeBody[ 0 ].style,
vendors = [ "Webkit", "Moz", "O" ],
webos = "palmGetResource" in window, //only used to rule out scrollTop
operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]",
bb = window.blackberry && !propExists( "-webkit-transform" ), //only used to rule out box shadow, as it's filled opaque on BB 5 and lower
nokiaLTE7_3;
// inline SVG support test
function inlineSVG() {
// Thanks Modernizr & Erik Dahlstrom
var w = window,
svg = !!w.document.createElementNS && !!w.document.createElementNS( "http://www.w3.org/2000/svg", "svg" ).createSVGRect && !( w.opera && navigator.userAgent.indexOf( "Chrome" ) === -1 ),
support = function( data ) {
if ( !( data && svg ) ) {
$( "html" ).addClass( "ui-nosvg" );
}
},
img = new w.Image();
img.onerror = function() {
support( false );
};
img.onload = function() {
support( img.width === 1 && img.height === 1 );
};
img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
}
function transform3dTest() {
var mqProp = "transform-3d",
// Because the `translate3d` test below throws false positives in Android:
ret = $.mobile.media( "(-" + vendors.join( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" ),
el, transforms, t;
if ( ret ) {
return !!ret;
}
el = document.createElement( "div" );
transforms = {
// We’re omitting Opera for the time being; MS uses unprefixed.
"MozTransform": "-moz-transform",
"transform": "transform"
};
fakeBody.append( el );
for ( t in transforms ) {
if ( el.style[ t ] !== undefined ) {
el.style[ t ] = "translate3d( 100px, 1px, 1px )";
ret = window.getComputedStyle( el ).getPropertyValue( transforms[ t ] );
}
}
return ( !!ret && ret !== "none" );
}
// Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting )
function baseTagTest() {
var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/",
base = $( "head base" ),
fauxEle = null,
href = "",
link, rebase;
if ( !base.length ) {
base = fauxEle = $( "", { "href": fauxBase }).appendTo( "head" );
} else {
href = base.attr( "href" );
}
link = $( "" ).prependTo( fakeBody );
rebase = link[ 0 ].href;
base[ 0 ].href = href || location.pathname;
if ( fauxEle ) {
fauxEle.remove();
}
return rebase.indexOf( fauxBase ) === 0;
}
// Thanks Modernizr
function cssPointerEventsTest() {
var element = document.createElement( "x" ),
documentElement = document.documentElement,
getComputedStyle = window.getComputedStyle,
supports;
if ( !( "pointerEvents" in element.style ) ) {
return false;
}
element.style.pointerEvents = "auto";
element.style.pointerEvents = "x";
documentElement.appendChild( element );
supports = getComputedStyle &&
getComputedStyle( element, "" ).pointerEvents === "auto";
documentElement.removeChild( element );
return !!supports;
}
function boundingRect() {
var div = document.createElement( "div" );
return typeof div.getBoundingClientRect !== "undefined";
}
// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683
// allows for inclusion of IE 6+, including Windows Mobile 7
$.extend( $.mobile, { browser: {} } );
$.mobile.browser.oldIE = (function() {
var v = 3,
div = document.createElement( "div" ),
a = div.all || [];
do {
div.innerHTML = "";
} while( a[0] );
return v > 4 ? v : !v;
})();
function fixedPosition() {
var w = window,
ua = navigator.userAgent,
platform = navigator.platform,
// Rendering engine is Webkit, and capture major version
wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
wkversion = !!wkmatch && wkmatch[ 1 ],
ffmatch = ua.match( /Fennec\/([0-9]+)/ ),
ffversion = !!ffmatch && ffmatch[ 1 ],
operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ),
omversion = !!operammobilematch && operammobilematch[ 1 ];
if (
// iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 ) ||
// Opera Mini
( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ) ||
( operammobilematch && omversion < 7458 ) ||
//Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 ) ||
// Firefox Mobile before 6.0 -
( ffversion && ffversion < 6 ) ||
// WebOS less than 3
( "palmGetResource" in window && wkversion && wkversion < 534 ) ||
// MeeGo
( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 ) ) {
return false;
}
return true;
}
$.extend( $.support, {
// Note, Chrome for iOS has an extremely quirky implementation of popstate.
// We've chosen to take the shortest path to a bug fix here for issue #5426
// See the following link for information about the regex chosen
// https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent
pushState: "pushState" in history &&
"replaceState" in history &&
// When running inside a FF iframe, calling replaceState causes an error
!( window.navigator.userAgent.indexOf( "Firefox" ) >= 0 && window.top !== window ) &&
( window.navigator.userAgent.search(/CriOS/) === -1 ),
mediaquery: $.mobile.media( "only all" ),
cssPseudoElement: !!propExists( "content" ),
touchOverflow: !!propExists( "overflowScrolling" ),
cssTransform3d: transform3dTest(),
boxShadow: !!propExists( "boxShadow" ) && !bb,
fixedPosition: fixedPosition(),
scrollTop: ("pageXOffset" in window ||
"scrollTop" in document.documentElement ||
"scrollTop" in fakeBody[ 0 ]) && !webos && !operamini,
dynamicBaseTag: baseTagTest(),
cssPointerEvents: cssPointerEventsTest(),
boundingRect: boundingRect(),
inlineSVG: inlineSVG
});
fakeBody.remove();
// $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian)
// or that generally work better browsing in regular http for full page refreshes (Opera Mini)
// Note: This detection below is used as a last resort.
// We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible
nokiaLTE7_3 = (function() {
var ua = window.navigator.userAgent;
//The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older
return ua.indexOf( "Nokia" ) > -1 &&
( ua.indexOf( "Symbian/3" ) > -1 || ua.indexOf( "Series60/5" ) > -1 ) &&
ua.indexOf( "AppleWebKit" ) > -1 &&
ua.match( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ );
})();
// Support conditions that must be met in order to proceed
// default enhanced qualifications are media query support OR IE 7+
$.mobile.gradeA = function() {
return ( ( $.support.mediaquery && $.support.cssPseudoElement ) || $.mobile.browser.oldIE && $.mobile.browser.oldIE >= 8 ) && ( $.support.boundingRect || $.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/) !== null );
};
$.mobile.ajaxBlacklist =
// BlackBerry browsers, pre-webkit
window.blackberry && !window.WebKitPoint ||
// Opera Mini
operamini ||
// Symbian webkits pre 7.3
nokiaLTE7_3;
// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices
// to render the stylesheets when they're referenced before this script, as we'd recommend doing.
// This simply reappends the CSS in place, which for some reason makes it apply
if ( nokiaLTE7_3 ) {
$(function() {
$( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" );
});
}
// For ruling out shadows via css
if ( !$.support.boxShadow ) {
$( "html" ).addClass( "ui-noboxshadow" );
}
})( jQuery );
(function( $, undefined ) {
var $win = $.mobile.window, self,
dummyFnToInitNavigate = function() {
};
$.event.special.beforenavigate = {
setup: function() {
$win.on( "navigate", dummyFnToInitNavigate );
},
teardown: function() {
$win.off( "navigate", dummyFnToInitNavigate );
}
};
$.event.special.navigate = self = {
bound: false,
pushStateEnabled: true,
originalEventName: undefined,
// If pushstate support is present and push state support is defined to
// be true on the mobile namespace.
isPushStateEnabled: function() {
return $.support.pushState &&
$.mobile.pushStateEnabled === true &&
this.isHashChangeEnabled();
},
// !! assumes mobile namespace is present
isHashChangeEnabled: function() {
return $.mobile.hashListeningEnabled === true;
},
// TODO a lot of duplication between popstate and hashchange
popstate: function( event ) {
var newEvent = new $.Event( "navigate" ),
beforeNavigate = new $.Event( "beforenavigate" ),
state = event.originalEvent.state || {};
beforeNavigate.originalEvent = event;
$win.trigger( beforeNavigate );
if ( beforeNavigate.isDefaultPrevented() ) {
return;
}
if ( event.historyState ) {
$.extend(state, event.historyState);
}
// Make sure the original event is tracked for the end
// user to inspect incase they want to do something special
newEvent.originalEvent = event;
// NOTE we let the current stack unwind because any assignment to
// location.hash will stop the world and run this event handler. By
// doing this we create a similar behavior to hashchange on hash
// assignment
setTimeout(function() {
$win.trigger( newEvent, {
state: state
});
}, 0);
},
hashchange: function( event /*, data */ ) {
var newEvent = new $.Event( "navigate" ),
beforeNavigate = new $.Event( "beforenavigate" );
beforeNavigate.originalEvent = event;
$win.trigger( beforeNavigate );
if ( beforeNavigate.isDefaultPrevented() ) {
return;
}
// Make sure the original event is tracked for the end
// user to inspect incase they want to do something special
newEvent.originalEvent = event;
// Trigger the hashchange with state provided by the user
// that altered the hash
$win.trigger( newEvent, {
// Users that want to fully normalize the two events
// will need to do history management down the stack and
// add the state to the event before this binding is fired
// TODO consider allowing for the explicit addition of callbacks
// to be fired before this value is set to avoid event timing issues
state: event.hashchangeState || {}
});
},
// TODO We really only want to set this up once
// but I'm not clear if there's a beter way to achieve
// this with the jQuery special event structure
setup: function( /* data, namespaces */ ) {
if ( self.bound ) {
return;
}
self.bound = true;
if ( self.isPushStateEnabled() ) {
self.originalEventName = "popstate";
$win.bind( "popstate.navigate", self.popstate );
} else if ( self.isHashChangeEnabled() ) {
self.originalEventName = "hashchange";
$win.bind( "hashchange.navigate", self.hashchange );
}
}
};
})( jQuery );
(function( $, undefined ) {
var path, $base, dialogHashKey = "&ui-state=dialog";
$.mobile.path = path = {
uiStateKey: "&ui-state",
// This scary looking regular expression parses an absolute URL or its relative
// variants (protocol, site, document, query, and hash), into the various
// components (protocol, host, path, query, fragment, etc that make up the
// URL as well as some other commonly used sub-parts. When used with RegExp.exec()
// or String.match, it parses the URL into a results array that looks like this:
//
// [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
// [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
// [2]: http://jblas:password@mycompany.com:8080/mail/inbox
// [3]: http://jblas:password@mycompany.com:8080
// [4]: http:
// [5]: //
// [6]: jblas:password@mycompany.com:8080
// [7]: jblas:password
// [8]: jblas
// [9]: password
// [10]: mycompany.com:8080
// [11]: mycompany.com
// [12]: 8080
// [13]: /mail/inbox
// [14]: /mail/
// [15]: inbox
// [16]: ?msg=1234&type=unread
// [17]: #msg-content
//
urlParseRE: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
// Abstraction to address xss (Issue #4787) by removing the authority in
// browsers that auto-decode it. All references to location.href should be
// replaced with a call to this method so that it can be dealt with properly here
getLocation: function( url ) {
var parsedUrl = this.parseUrl( url || location.href ),
uri = url ? parsedUrl : location,
// Make sure to parse the url or the location object for the hash because using
// location.hash is autodecoded in firefox, the rest of the url should be from
// the object (location unless we're testing) to avoid the inclusion of the
// authority
hash = parsedUrl.hash;
// mimic the browser with an empty string when the hash is empty
hash = hash === "#" ? "" : hash;
return uri.protocol +
parsedUrl.doubleSlash +
uri.host +
// The pathname must start with a slash if there's a protocol, because you
// can't have a protocol followed by a relative path. Also, it's impossible to
// calculate absolute URLs from relative ones if the absolute one doesn't have
// a leading "/".
( ( uri.protocol !== "" && uri.pathname.substring( 0, 1 ) !== "/" ) ?
"/" : "" ) +
uri.pathname +
uri.search +
hash;
},
//return the original document url
getDocumentUrl: function( asParsedObject ) {
return asParsedObject ? $.extend( {}, path.documentUrl ) : path.documentUrl.href;
},
parseLocation: function() {
return this.parseUrl( this.getLocation() );
},
//Parse a URL into a structure that allows easy access to
//all of the URL components by name.
parseUrl: function( url ) {
// If we're passed an object, we'll assume that it is
// a parsed url object and just return it back to the caller.
if ( $.type( url ) === "object" ) {
return url;
}
var matches = path.urlParseRE.exec( url || "" ) || [];
// Create an object that allows the caller to access the sub-matches
// by name. Note that IE returns an empty string instead of undefined,
// like all other browsers do, so we normalize everything so its consistent
// no matter what browser we're running on.
return {
href: matches[ 0 ] || "",
hrefNoHash: matches[ 1 ] || "",
hrefNoSearch: matches[ 2 ] || "",
domain: matches[ 3 ] || "",
protocol: matches[ 4 ] || "",
doubleSlash: matches[ 5 ] || "",
authority: matches[ 6 ] || "",
username: matches[ 8 ] || "",
password: matches[ 9 ] || "",
host: matches[ 10 ] || "",
hostname: matches[ 11 ] || "",
port: matches[ 12 ] || "",
pathname: matches[ 13 ] || "",
directory: matches[ 14 ] || "",
filename: matches[ 15 ] || "",
search: matches[ 16 ] || "",
hash: matches[ 17 ] || ""
};
},
//Turn relPath into an asbolute path. absPath is
//an optional absolute path which describes what
//relPath is relative to.
makePathAbsolute: function( relPath, absPath ) {
var absStack,
relStack,
i, d;
if ( relPath && relPath.charAt( 0 ) === "/" ) {
return relPath;
}
relPath = relPath || "";
absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
absStack = absPath ? absPath.split( "/" ) : [];
relStack = relPath.split( "/" );
for ( i = 0; i < relStack.length; i++ ) {
d = relStack[ i ];
switch ( d ) {
case ".":
break;
case "..":
if ( absStack.length ) {
absStack.pop();
}
break;
default:
absStack.push( d );
break;
}
}
return "/" + absStack.join( "/" );
},
//Returns true if both urls have the same domain.
isSameDomain: function( absUrl1, absUrl2 ) {
return path.parseUrl( absUrl1 ).domain.toLowerCase() ===
path.parseUrl( absUrl2 ).domain.toLowerCase();
},
//Returns true for any relative variant.
isRelativeUrl: function( url ) {
// All relative Url variants have one thing in common, no protocol.
return path.parseUrl( url ).protocol === "";
},
//Returns true for an absolute url.
isAbsoluteUrl: function( url ) {
return path.parseUrl( url ).protocol !== "";
},
//Turn the specified realtive URL into an absolute one. This function
//can handle all relative variants (protocol, site, document, query, fragment).
makeUrlAbsolute: function( relUrl, absUrl ) {
if ( !path.isRelativeUrl( relUrl ) ) {
return relUrl;
}
if ( absUrl === undefined ) {
absUrl = this.documentBase;
}
var relObj = path.parseUrl( relUrl ),
absObj = path.parseUrl( absUrl ),
protocol = relObj.protocol || absObj.protocol,
doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
authority = relObj.authority || absObj.authority,
hasPath = relObj.pathname !== "",
pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
search = relObj.search || ( !hasPath && absObj.search ) || "",
hash = relObj.hash;
return protocol + doubleSlash + authority + pathname + search + hash;
},
//Add search (aka query) params to the specified url.
addSearchParams: function( url, params ) {
var u = path.parseUrl( url ),
p = ( typeof params === "object" ) ? $.param( params ) : params,
s = u.search || "?";
return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" );
},
convertUrlToDataUrl: function( absUrl ) {
var result = absUrl,
u = path.parseUrl( absUrl );
if ( path.isEmbeddedPage( u ) ) {
// For embedded pages, remove the dialog hash key as in getFilePath(),
// and remove otherwise the Data Url won't match the id of the embedded Page.
result = u.hash
.split( dialogHashKey )[0]
.replace( /^#/, "" )
.replace( /\?.*$/, "" );
} else if ( path.isSameDomain( u, this.documentBase ) ) {
result = u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0];
}
return window.decodeURIComponent( result );
},
//get path from current hash, or from a file path
get: function( newPath ) {
if ( newPath === undefined ) {
newPath = path.parseLocation().hash;
}
return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, "" );
},
//set location hash to path
set: function( path ) {
location.hash = path;
},
//test if a given url (string) is a path
//NOTE might be exceptionally naive
isPath: function( url ) {
return ( /\// ).test( url );
},
//return a url path with the window's location protocol/hostname/pathname removed
clean: function( url ) {
return url.replace( this.documentBase.domain, "" );
},
//just return the url without an initial #
stripHash: function( url ) {
return url.replace( /^#/, "" );
},
stripQueryParams: function( url ) {
return url.replace( /\?.*$/, "" );
},
//remove the preceding hash, any query params, and dialog notations
cleanHash: function( hash ) {
return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
},
isHashValid: function( hash ) {
return ( /^#[^#]+$/ ).test( hash );
},
//check whether a url is referencing the same domain, or an external domain or different protocol
//could be mailto, etc
isExternal: function( url ) {
var u = path.parseUrl( url );
return !!( u.protocol &&
( u.domain.toLowerCase() !== this.documentUrl.domain.toLowerCase() ) );
},
hasProtocol: function( url ) {
return ( /^(:?\w+:)/ ).test( url );
},
isEmbeddedPage: function( url ) {
var u = path.parseUrl( url );
//if the path is absolute, then we need to compare the url against
//both the this.documentUrl and the documentBase. The main reason for this
//is that links embedded within external documents will refer to the
//application document, whereas links embedded within the application
//document will be resolved against the document base.
if ( u.protocol !== "" ) {
return ( !this.isPath(u.hash) && u.hash && ( u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ) ) );
}
return ( /^#/ ).test( u.href );
},
squash: function( url, resolutionUrl ) {
var href, cleanedUrl, search, stateIndex, docUrl,
isPath = this.isPath( url ),
uri = this.parseUrl( url ),
preservedHash = uri.hash,
uiState = "";
// produce a url against which we can resolve the provided path
if ( !resolutionUrl ) {
if ( isPath ) {
resolutionUrl = path.getLocation();
} else {
docUrl = path.getDocumentUrl( true );
if ( path.isPath( docUrl.hash ) ) {
resolutionUrl = path.squash( docUrl.href );
} else {
resolutionUrl = docUrl.href;
}
}
}
// If the url is anything but a simple string, remove any preceding hash
// eg #foo/bar -> foo/bar
// #foo -> #foo
cleanedUrl = isPath ? path.stripHash( url ) : url;
// If the url is a full url with a hash check if the parsed hash is a path
// if it is, strip the #, and use it otherwise continue without change
cleanedUrl = path.isPath( uri.hash ) ? path.stripHash( uri.hash ) : cleanedUrl;
// Split the UI State keys off the href
stateIndex = cleanedUrl.indexOf( this.uiStateKey );
// store the ui state keys for use
if ( stateIndex > -1 ) {
uiState = cleanedUrl.slice( stateIndex );
cleanedUrl = cleanedUrl.slice( 0, stateIndex );
}
// make the cleanedUrl absolute relative to the resolution url
href = path.makeUrlAbsolute( cleanedUrl, resolutionUrl );
// grab the search from the resolved url since parsing from
// the passed url may not yield the correct result
search = this.parseUrl( href ).search;
// TODO all this crap is terrible, clean it up
if ( isPath ) {
// reject the hash if it's a path or it's just a dialog key
if ( path.isPath( preservedHash ) || preservedHash.replace("#", "").indexOf( this.uiStateKey ) === 0) {
preservedHash = "";
}
// Append the UI State keys where it exists and it's been removed
// from the url
if ( uiState && preservedHash.indexOf( this.uiStateKey ) === -1) {
preservedHash += uiState;
}
// make sure that pound is on the front of the hash
if ( preservedHash.indexOf( "#" ) === -1 && preservedHash !== "" ) {
preservedHash = "#" + preservedHash;
}
// reconstruct each of the pieces with the new search string and hash
href = path.parseUrl( href );
href = href.protocol + href.doubleSlash + href.host + href.pathname + search +
preservedHash;
} else {
href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState;
}
return href;
},
isPreservableHash: function( hash ) {
return hash.replace( "#", "" ).indexOf( this.uiStateKey ) === 0;
},
// Escape weird characters in the hash if it is to be used as a selector
hashToSelector: function( hash ) {
var hasHash = ( hash.substring( 0, 1 ) === "#" );
if ( hasHash ) {
hash = hash.substring( 1 );
}
return ( hasHash ? "#" : "" ) + hash.replace( /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, "\\$1" );
},
// return the substring of a filepath before the dialogHashKey, for making a server
// request
getFilePath: function( path ) {
return path && path.split( dialogHashKey )[0];
},
// check if the specified url refers to the first page in the main
// application document.
isFirstPageUrl: function( url ) {
// We only deal with absolute paths.
var u = path.parseUrl( path.makeUrlAbsolute( url, this.documentBase ) ),
// Does the url have the same path as the document?
samePath = u.hrefNoHash === this.documentUrl.hrefNoHash ||
( this.documentBaseDiffers &&
u.hrefNoHash === this.documentBase.hrefNoHash ),
// Get the first page element.
fp = $.mobile.firstPage,
// Get the id of the first page element if it has one.
fpId = fp && fp[0] ? fp[0].id : undefined;
// The url refers to the first page if the path matches the document and
// it either has no hash value, or the hash is exactly equal to the id
// of the first page element.
return samePath &&
( !u.hash ||
u.hash === "#" ||
( fpId && u.hash.replace( /^#/, "" ) === fpId ) );
},
// Some embedded browsers, like the web view in Phone Gap, allow
// cross-domain XHR requests if the document doing the request was loaded
// via the file:// protocol. This is usually to allow the application to
// "phone home" and fetch app specific data. We normally let the browser
// handle external/cross-domain urls, but if the allowCrossDomainPages
// option is true, we will allow cross-domain http/https requests to go
// through our page loading logic.
isPermittedCrossDomainRequest: function( docUrl, reqUrl ) {
return $.mobile.allowCrossDomainPages &&
(docUrl.protocol === "file:" || docUrl.protocol === "content:") &&
reqUrl.search( /^https?:/ ) !== -1;
}
};
path.documentUrl = path.parseLocation();
$base = $( "head" ).find( "base" );
path.documentBase = $base.length ?
path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), path.documentUrl.href ) ) :
path.documentUrl;
path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash);
//return the original document base url
path.getDocumentBase = function( asParsedObject ) {
return asParsedObject ? $.extend( {}, path.documentBase ) : path.documentBase.href;
};
// DEPRECATED as of 1.4.0 - remove in 1.5.0
$.extend( $.mobile, {
//return the original document url
getDocumentUrl: path.getDocumentUrl,
//return the original document base url
getDocumentBase: path.getDocumentBase
});
})( jQuery );
(function( $, undefined ) {
$.mobile.History = function( stack, index ) {
this.stack = stack || [];
this.activeIndex = index || 0;
};
$.extend($.mobile.History.prototype, {
getActive: function() {
return this.stack[ this.activeIndex ];
},
getLast: function() {
return this.stack[ this.previousIndex ];
},
getNext: function() {
return this.stack[ this.activeIndex + 1 ];
},
getPrev: function() {
return this.stack[ this.activeIndex - 1 ];
},
// addNew is used whenever a new page is added
add: function( url, data ) {
data = data || {};
//if there's forward history, wipe it
if ( this.getNext() ) {
this.clearForward();
}
// if the hash is included in the data make sure the shape
// is consistent for comparison
if ( data.hash && data.hash.indexOf( "#" ) === -1) {
data.hash = "#" + data.hash;
}
data.url = url;
this.stack.push( data );
this.activeIndex = this.stack.length - 1;
},
//wipe urls ahead of active index
clearForward: function() {
this.stack = this.stack.slice( 0, this.activeIndex + 1 );
},
find: function( url, stack, earlyReturn ) {
stack = stack || this.stack;
var entry, i, length = stack.length, index;
for ( i = 0; i < length; i++ ) {
entry = stack[i];
if ( decodeURIComponent(url) === decodeURIComponent(entry.url) ||
decodeURIComponent(url) === decodeURIComponent(entry.hash) ) {
index = i;
if ( earlyReturn ) {
return index;
}
}
}
return index;
},
closest: function( url ) {
var closest, a = this.activeIndex;
// First, take the slice of the history stack before the current index and search
// for a url match. If one is found, we'll avoid avoid looking through forward history
// NOTE the preference for backward history movement is driven by the fact that
// most mobile browsers only have a dedicated back button, and users rarely use
// the forward button in desktop browser anyhow
closest = this.find( url, this.stack.slice(0, a) );
// If nothing was found in backward history check forward. The `true`
// value passed as the third parameter causes the find method to break
// on the first match in the forward history slice. The starting index
// of the slice must then be added to the result to get the element index
// in the original history stack :( :(
//
// TODO this is hyper confusing and should be cleaned up (ugh so bad)
if ( closest === undefined ) {
closest = this.find( url, this.stack.slice(a), true );
closest = closest === undefined ? closest : closest + a;
}
return closest;
},
direct: function( opts ) {
var newActiveIndex = this.closest( opts.url ), a = this.activeIndex;
// save new page index, null check to prevent falsey 0 result
// record the previous index for reference
if ( newActiveIndex !== undefined ) {
this.activeIndex = newActiveIndex;
this.previousIndex = a;
}
// invoke callbacks where appropriate
//
// TODO this is also convoluted and confusing
if ( newActiveIndex < a ) {
( opts.present || opts.back || $.noop )( this.getActive(), "back" );
} else if ( newActiveIndex > a ) {
( opts.present || opts.forward || $.noop )( this.getActive(), "forward" );
} else if ( newActiveIndex === undefined && opts.missing ) {
opts.missing( this.getActive() );
}
}
});
})( jQuery );
(function( $, undefined ) {
var path = $.mobile.path,
initialHref = location.href;
$.mobile.Navigator = function( history ) {
this.history = history;
this.ignoreInitialHashChange = true;
$.mobile.window.bind({
"popstate.history": $.proxy( this.popstate, this ),
"hashchange.history": $.proxy( this.hashchange, this )
});
};
$.extend($.mobile.Navigator.prototype, {
squash: function( url, data ) {
var state, href, hash = path.isPath(url) ? path.stripHash(url) : url;
href = path.squash( url );
// make sure to provide this information when it isn't explicitly set in the
// data object that was passed to the squash method
state = $.extend({
hash: hash,
url: href
}, data);
// replace the current url with the new href and store the state
// Note that in some cases we might be replacing an url with the
// same url. We do this anyways because we need to make sure that
// all of our history entries have a state object associated with
// them. This allows us to work around the case where $.mobile.back()
// is called to transition from an external page to an embedded page.
// In that particular case, a hashchange event is *NOT* generated by the browser.
// Ensuring each history entry has a state object means that onPopState()
// will always trigger our hashchange callback even when a hashchange event
// is not fired.
window.history.replaceState( state, state.title || document.title, href );
return state;
},
hash: function( url, href ) {
var parsed, loc, hash, resolved;
// Grab the hash for recording. If the passed url is a path
// we used the parsed version of the squashed url to reconstruct,
// otherwise we assume it's a hash and store it directly
parsed = path.parseUrl( url );
loc = path.parseLocation();
if ( loc.pathname + loc.search === parsed.pathname + parsed.search ) {
// If the pathname and search of the passed url is identical to the current loc
// then we must use the hash. Otherwise there will be no event
// eg, url = "/foo/bar?baz#bang", location.href = "http://example.com/foo/bar?baz"
hash = parsed.hash ? parsed.hash : parsed.pathname + parsed.search;
} else if ( path.isPath(url) ) {
resolved = path.parseUrl( href );
// If the passed url is a path, make it domain relative and remove any trailing hash
hash = resolved.pathname + resolved.search + (path.isPreservableHash( resolved.hash )? resolved.hash.replace( "#", "" ) : "");
} else {
hash = url;
}
return hash;
},
// TODO reconsider name
go: function( url, data, noEvents ) {
var state, href, hash, popstateEvent,
isPopStateEvent = $.event.special.navigate.isPushStateEnabled();
// Get the url as it would look squashed on to the current resolution url
href = path.squash( url );
// sort out what the hash sould be from the url
hash = this.hash( url, href );
// Here we prevent the next hash change or popstate event from doing any
// history management. In the case of hashchange we don't swallow it
// if there will be no hashchange fired (since that won't reset the value)
// and will swallow the following hashchange
if ( noEvents && hash !== path.stripHash(path.parseLocation().hash) ) {
this.preventNextHashChange = noEvents;
}
// IMPORTANT in the case where popstate is supported the event will be triggered
// directly, stopping further execution - ie, interupting the flow of this
// method call to fire bindings at this expression. Below the navigate method
// there is a binding to catch this event and stop its propagation.
//
// We then trigger a new popstate event on the window with a null state
// so that the navigate events can conclude their work properly
//
// if the url is a path we want to preserve the query params that are available on
// the current url.
this.preventHashAssignPopState = true;
window.location.hash = hash;
// If popstate is enabled and the browser triggers `popstate` events when the hash
// is set (this often happens immediately in browsers like Chrome), then the
// this flag will be set to false already. If it's a browser that does not trigger
// a `popstate` on hash assignement or `replaceState` then we need avoid the branch
// that swallows the event created by the popstate generated by the hash assignment
// At the time of this writing this happens with Opera 12 and some version of IE
this.preventHashAssignPopState = false;
state = $.extend({
url: href,
hash: hash,
title: document.title
}, data);
if ( isPopStateEvent ) {
popstateEvent = new $.Event( "popstate" );
popstateEvent.originalEvent = {
type: "popstate",
state: null
};
this.squash( url, state );
// Trigger a new faux popstate event to replace the one that we
// caught that was triggered by the hash setting above.
if ( !noEvents ) {
this.ignorePopState = true;
$.mobile.window.trigger( popstateEvent );
}
}
// record the history entry so that the information can be included
// in hashchange event driven navigate events in a similar fashion to
// the state that's provided by popstate
this.history.add( state.url, state );
},
// This binding is intended to catch the popstate events that are fired
// when execution of the `$.navigate` method stops at window.location.hash = url;
// and completely prevent them from propagating. The popstate event will then be
// retriggered after execution resumes
//
// TODO grab the original event here and use it for the synthetic event in the
// second half of the navigate execution that will follow this binding
popstate: function( event ) {
var hash, state;
// Partly to support our test suite which manually alters the support
// value to test hashchange. Partly to prevent all around weirdness
if ( !$.event.special.navigate.isPushStateEnabled() ) {
return;
}
// If this is the popstate triggered by the actual alteration of the hash
// prevent it completely. History is tracked manually
if ( this.preventHashAssignPopState ) {
this.preventHashAssignPopState = false;
event.stopImmediatePropagation();
return;
}
// if this is the popstate triggered after the `replaceState` call in the go
// method, then simply ignore it. The history entry has already been captured
if ( this.ignorePopState ) {
this.ignorePopState = false;
return;
}
// If there is no state, and the history stack length is one were
// probably getting the page load popstate fired by browsers like chrome
// avoid it and set the one time flag to false.
// TODO: Do we really need all these conditions? Comparing location hrefs
// should be sufficient.
if ( !event.originalEvent.state &&
this.history.stack.length === 1 &&
this.ignoreInitialHashChange ) {
this.ignoreInitialHashChange = false;
if ( location.href === initialHref ) {
event.preventDefault();
return;
}
}
// account for direct manipulation of the hash. That is, we will receive a popstate
// when the hash is changed by assignment, and it won't have a state associated. We
// then need to squash the hash. See below for handling of hash assignment that
// matches an existing history entry
// TODO it might be better to only add to the history stack
// when the hash is adjacent to the active history entry
hash = path.parseLocation().hash;
if ( !event.originalEvent.state && hash ) {
// squash the hash that's been assigned on the URL with replaceState
// also grab the resulting state object for storage
state = this.squash( hash );
// record the new hash as an additional history entry
// to match the browser's treatment of hash assignment
this.history.add( state.url, state );
// pass the newly created state information
// along with the event
event.historyState = state;
// do not alter history, we've added a new history entry
// so we know where we are
return;
}
// If all else fails this is a popstate that comes from the back or forward buttons
// make sure to set the state of our history stack properly, and record the directionality
this.history.direct({
url: (event.originalEvent.state || {}).url || hash,
// When the url is either forward or backward in history include the entry
// as data on the event object for merging as data in the navigate event
present: function( historyEntry, direction ) {
// make sure to create a new object to pass down as the navigate event data
event.historyState = $.extend({}, historyEntry);
event.historyState.direction = direction;
}
});
},
// NOTE must bind before `navigate` special event hashchange binding otherwise the
// navigation data won't be attached to the hashchange event in time for those
// bindings to attach it to the `navigate` special event
// TODO add a check here that `hashchange.navigate` is bound already otherwise it's
// broken (exception?)
hashchange: function( event ) {
var history, hash;
// If hashchange listening is explicitly disabled or pushstate is supported
// avoid making use of the hashchange handler.
if (!$.event.special.navigate.isHashChangeEnabled() ||
$.event.special.navigate.isPushStateEnabled() ) {
return;
}
// On occasion explicitly want to prevent the next hash from propogating because we only
// with to alter the url to represent the new state do so here
if ( this.preventNextHashChange ) {
this.preventNextHashChange = false;
event.stopImmediatePropagation();
return;
}
history = this.history;
hash = path.parseLocation().hash;
// If this is a hashchange caused by the back or forward button
// make sure to set the state of our history stack properly
this.history.direct({
url: hash,
// When the url is either forward or backward in history include the entry
// as data on the event object for merging as data in the navigate event
present: function( historyEntry, direction ) {
// make sure to create a new object to pass down as the navigate event data
event.hashchangeState = $.extend({}, historyEntry);
event.hashchangeState.direction = direction;
},
// When we don't find a hash in our history clearly we're aiming to go there
// record the entry as new for future traversal
//
// NOTE it's not entirely clear that this is the right thing to do given that we
// can't know the users intention. It might be better to explicitly _not_
// support location.hash assignment in preference to $.navigate calls
// TODO first arg to add should be the href, but it causes issues in identifying
// embeded pages
missing: function() {
history.add( hash, {
hash: hash,
title: document.title
});
}
});
}
});
})( jQuery );
(function( $, undefined ) {
// TODO consider queueing navigation activity until previous activities have completed
// so that end users don't have to think about it. Punting for now
// TODO !! move the event bindings into callbacks on the navigate event
$.mobile.navigate = function( url, data, noEvents ) {
$.mobile.navigate.navigator.go( url, data, noEvents );
};
// expose the history on the navigate method in anticipation of full integration with
// existing navigation functionalty that is tightly coupled to the history information
$.mobile.navigate.history = new $.mobile.History();
// instantiate an instance of the navigator for use within the $.navigate method
$.mobile.navigate.navigator = new $.mobile.Navigator( $.mobile.navigate.history );
var loc = $.mobile.path.parseLocation();
$.mobile.navigate.history.add( loc.href, {hash: loc.hash} );
})( jQuery );
(function( $, undefined ) {
var props = {
"animation": {},
"transition": {}
},
testElement = document.createElement( "a" ),
vendorPrefixes = [ "", "webkit-", "moz-", "o-" ];
$.each( [ "animation", "transition" ], function( i, test ) {
// Get correct name for test
var testName = ( i === 0 ) ? test + "-" + "name" : test;
$.each( vendorPrefixes, function( j, prefix ) {
if ( testElement.style[ $.camelCase( prefix + testName ) ] !== undefined ) {
props[ test ][ "prefix" ] = prefix;
return false;
}
});
// Set event and duration names for later use
props[ test ][ "duration" ] =
$.camelCase( props[ test ][ "prefix" ] + test + "-" + "duration" );
props[ test ][ "event" ] =
$.camelCase( props[ test ][ "prefix" ] + test + "-" + "end" );
// All lower case if not a vendor prop
if ( props[ test ][ "prefix" ] === "" ) {
props[ test ][ "event" ] = props[ test ][ "event" ].toLowerCase();
}
});
// If a valid prefix was found then the it is supported by the browser
$.support.cssTransitions = ( props[ "transition" ][ "prefix" ] !== undefined );
$.support.cssAnimations = ( props[ "animation" ][ "prefix" ] !== undefined );
// Remove the testElement
$( testElement ).remove();
// Animation complete callback
$.fn.animationComplete = function( callback, type, fallbackTime ) {
var timer, duration,
that = this,
eventBinding = function() {
// Clear the timer so we don't call callback twice
clearTimeout( timer );
callback.apply( this, arguments );
},
animationType = ( !type || type === "animation" ) ? "animation" : "transition";
// Make sure selected type is supported by browser
if ( ( $.support.cssTransitions && animationType === "transition" ) ||
( $.support.cssAnimations && animationType === "animation" ) ) {
// If a fallback time was not passed set one
if ( fallbackTime === undefined ) {
// Make sure the was not bound to document before checking .css
if ( $( this ).context !== document ) {
// Parse the durration since its in second multiple by 1000 for milliseconds
// Multiply by 3 to make sure we give the animation plenty of time.
duration = parseFloat(
$( this ).css( props[ animationType ].duration )
) * 3000;
}
// If we could not read a duration use the default
if ( duration === 0 || duration === undefined || isNaN( duration ) ) {
duration = $.fn.animationComplete.defaultDuration;
}
}
// Sets up the fallback if event never comes
timer = setTimeout( function() {
$( that ).off( props[ animationType ].event, eventBinding );
callback.apply( that );
}, duration );
// Bind the event
return $( this ).one( props[ animationType ].event, eventBinding );
} else {
// CSS animation / transitions not supported
// Defer execution for consistency between webkit/non webkit
setTimeout( $.proxy( callback, this ), 0 );
return $( this );
}
};
// Allow default callback to be configured on mobileInit
$.fn.animationComplete.defaultDuration = 1000;
})( jQuery );
// This plugin is an experiment for abstracting away the touch and mouse
// events so that developers don't have to worry about which method of input
// the device their document is loaded on supports.
//
// The idea here is to allow the developer to register listeners for the
// basic mouse events, such as mousedown, mousemove, mouseup, and click,
// and the plugin will take care of registering the correct listeners
// behind the scenes to invoke the listener at the fastest possible time
// for that device, while still retaining the order of event firing in
// the traditional mouse environment, should multiple handlers be registered
// on the same element for different events.
//
// The current version exposes the following virtual events to jQuery bind methods:
// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
(function( $, window, document, undefined ) {
var dataPropertyName = "virtualMouseBindings",
touchTargetPropertyName = "virtualTouchID",
virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [],
mouseEventProps = $.event.props.concat( mouseHookProps ),
activeDocHandlers = {},
resetTimerID = 0,
startX = 0,
startY = 0,
didScroll = false,
clickBlockList = [],
blockMouseTriggers = false,
blockTouchTriggers = false,
eventCaptureSupported = "addEventListener" in document,
$document = $( document ),
nextTouchID = 1,
lastTouchID = 0, threshold,
i;
$.vmouse = {
moveDistanceThreshold: 10,
clickDistanceThreshold: 10,
resetTimerDuration: 1500
};
function getNativeEvent( event ) {
while ( event && typeof event.originalEvent !== "undefined" ) {
event = event.originalEvent;
}
return event;
}
function createVirtualEvent( event, eventType ) {
var t = event.type,
oe, props, ne, prop, ct, touch, i, j, len;
event = $.Event( event );
event.type = eventType;
oe = event.originalEvent;
props = $.event.props;
// addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
// https://github.com/jquery/jquery-mobile/issues/3280
if ( t.search( /^(mouse|click)/ ) > -1 ) {
props = mouseEventProps;
}
// copy original event properties over to the new event
// this would happen if we could call $.event.fix instead of $.Event
// but we don't have a way to force an event to be fixed multiple times
if ( oe ) {
for ( i = props.length, prop; i; ) {
prop = props[ --i ];
event[ prop ] = oe[ prop ];
}
}
// make sure that if the mouse and click virtual events are generated
// without a .which one is defined
if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) {
event.which = 1;
}
if ( t.search(/^touch/) !== -1 ) {
ne = getNativeEvent( oe );
t = ne.touches;
ct = ne.changedTouches;
touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined );
if ( touch ) {
for ( j = 0, len = touchEventProps.length; j < len; j++) {
prop = touchEventProps[ j ];
event[ prop ] = touch[ prop ];
}
}
}
return event;
}
function getVirtualBindingFlags( element ) {
var flags = {},
b, k;
while ( element ) {
b = $.data( element, dataPropertyName );
for ( k in b ) {
if ( b[ k ] ) {
flags[ k ] = flags.hasVirtualBinding = true;
}
}
element = element.parentNode;
}
return flags;
}
function getClosestElementWithVirtualBinding( element, eventType ) {
var b;
while ( element ) {
b = $.data( element, dataPropertyName );
if ( b && ( !eventType || b[ eventType ] ) ) {
return element;
}
element = element.parentNode;
}
return null;
}
function enableTouchBindings() {
blockTouchTriggers = false;
}
function disableTouchBindings() {
blockTouchTriggers = true;
}
function enableMouseBindings() {
lastTouchID = 0;
clickBlockList.length = 0;
blockMouseTriggers = false;
// When mouse bindings are enabled, our
// touch bindings are disabled.
disableTouchBindings();
}
function disableMouseBindings() {
// When mouse bindings are disabled, our
// touch bindings are enabled.
enableTouchBindings();
}
function startResetTimer() {
clearResetTimer();
resetTimerID = setTimeout( function() {
resetTimerID = 0;
enableMouseBindings();
}, $.vmouse.resetTimerDuration );
}
function clearResetTimer() {
if ( resetTimerID ) {
clearTimeout( resetTimerID );
resetTimerID = 0;
}
}
function triggerVirtualEvent( eventType, event, flags ) {
var ve;
if ( ( flags && flags[ eventType ] ) ||
( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) {
ve = createVirtualEvent( event, eventType );
$( event.target).trigger( ve );
}
return ve;
}
function mouseEventCallback( event ) {
var touchID = $.data( event.target, touchTargetPropertyName ),
ve;
if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) {
ve = triggerVirtualEvent( "v" + event.type, event );
if ( ve ) {
if ( ve.isDefaultPrevented() ) {
event.preventDefault();
}
if ( ve.isPropagationStopped() ) {
event.stopPropagation();
}
if ( ve.isImmediatePropagationStopped() ) {
event.stopImmediatePropagation();
}
}
}
}
function handleTouchStart( event ) {
var touches = getNativeEvent( event ).touches,
target, flags, t;
if ( touches && touches.length === 1 ) {
target = event.target;
flags = getVirtualBindingFlags( target );
if ( flags.hasVirtualBinding ) {
lastTouchID = nextTouchID++;
$.data( target, touchTargetPropertyName, lastTouchID );
clearResetTimer();
disableMouseBindings();
didScroll = false;
t = getNativeEvent( event ).touches[ 0 ];
startX = t.pageX;
startY = t.pageY;
triggerVirtualEvent( "vmouseover", event, flags );
triggerVirtualEvent( "vmousedown", event, flags );
}
}
}
function handleScroll( event ) {
if ( blockTouchTriggers ) {
return;
}
if ( !didScroll ) {
triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) );
}
didScroll = true;
startResetTimer();
}
function handleTouchMove( event ) {
if ( blockTouchTriggers ) {
return;
}
var t = getNativeEvent( event ).touches[ 0 ],
didCancel = didScroll,
moveThreshold = $.vmouse.moveDistanceThreshold,
flags = getVirtualBindingFlags( event.target );
didScroll = didScroll ||
( Math.abs( t.pageX - startX ) > moveThreshold ||
Math.abs( t.pageY - startY ) > moveThreshold );
if ( didScroll && !didCancel ) {
triggerVirtualEvent( "vmousecancel", event, flags );
}
triggerVirtualEvent( "vmousemove", event, flags );
startResetTimer();
}
function handleTouchEnd( event ) {
if ( blockTouchTriggers ) {
return;
}
disableTouchBindings();
var flags = getVirtualBindingFlags( event.target ),
ve, t;
triggerVirtualEvent( "vmouseup", event, flags );
if ( !didScroll ) {
ve = triggerVirtualEvent( "vclick", event, flags );
if ( ve && ve.isDefaultPrevented() ) {
// The target of the mouse events that follow the touchend
// event don't necessarily match the target used during the
// touch. This means we need to rely on coordinates for blocking
// any click that is generated.
t = getNativeEvent( event ).changedTouches[ 0 ];
clickBlockList.push({
touchID: lastTouchID,
x: t.clientX,
y: t.clientY
});
// Prevent any mouse events that follow from triggering
// virtual event notifications.
blockMouseTriggers = true;
}
}
triggerVirtualEvent( "vmouseout", event, flags);
didScroll = false;
startResetTimer();
}
function hasVirtualBindings( ele ) {
var bindings = $.data( ele, dataPropertyName ),
k;
if ( bindings ) {
for ( k in bindings ) {
if ( bindings[ k ] ) {
return true;
}
}
}
return false;
}
function dummyMouseHandler() {}
function getSpecialEventObject( eventType ) {
var realType = eventType.substr( 1 );
return {
setup: function(/* data, namespace */) {
// If this is the first virtual mouse binding for this element,
// add a bindings object to its data.
if ( !hasVirtualBindings( this ) ) {
$.data( this, dataPropertyName, {} );
}
// If setup is called, we know it is the first binding for this
// eventType, so initialize the count for the eventType to zero.
var bindings = $.data( this, dataPropertyName );
bindings[ eventType ] = true;
// If this is the first virtual mouse event for this type,
// register a global handler on the document.
activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1;
if ( activeDocHandlers[ eventType ] === 1 ) {
$document.bind( realType, mouseEventCallback );
}
// Some browsers, like Opera Mini, won't dispatch mouse/click events
// for elements unless they actually have handlers registered on them.
// To get around this, we register dummy handlers on the elements.
$( this ).bind( realType, dummyMouseHandler );
// For now, if event capture is not supported, we rely on mouse handlers.
if ( eventCaptureSupported ) {
// If this is the first virtual mouse binding for the document,
// register our touchstart handler on the document.
activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;
if ( activeDocHandlers[ "touchstart" ] === 1 ) {
$document.bind( "touchstart", handleTouchStart )
.bind( "touchend", handleTouchEnd )
// On touch platforms, touching the screen and then dragging your finger
// causes the window content to scroll after some distance threshold is
// exceeded. On these platforms, a scroll prevents a click event from being
// dispatched, and on some platforms, even the touchend is suppressed. To
// mimic the suppression of the click event, we need to watch for a scroll
// event. Unfortunately, some platforms like iOS don't dispatch scroll
// events until *AFTER* the user lifts their finger (touchend). This means
// we need to watch both scroll and touchmove events to figure out whether
// or not a scroll happenens before the touchend event is fired.
.bind( "touchmove", handleTouchMove )
.bind( "scroll", handleScroll );
}
}
},
teardown: function(/* data, namespace */) {
// If this is the last virtual binding for this eventType,
// remove its global handler from the document.
--activeDocHandlers[ eventType ];
if ( !activeDocHandlers[ eventType ] ) {
$document.unbind( realType, mouseEventCallback );
}
if ( eventCaptureSupported ) {
// If this is the last virtual mouse binding in existence,
// remove our document touchstart listener.
--activeDocHandlers[ "touchstart" ];
if ( !activeDocHandlers[ "touchstart" ] ) {
$document.unbind( "touchstart", handleTouchStart )
.unbind( "touchmove", handleTouchMove )
.unbind( "touchend", handleTouchEnd )
.unbind( "scroll", handleScroll );
}
}
var $this = $( this ),
bindings = $.data( this, dataPropertyName );
// teardown may be called when an element was
// removed from the DOM. If this is the case,
// jQuery core may have already stripped the element
// of any data bindings so we need to check it before
// using it.
if ( bindings ) {
bindings[ eventType ] = false;
}
// Unregister the dummy event handler.
$this.unbind( realType, dummyMouseHandler );
// If this is the last virtual mouse binding on the
// element, remove the binding data from the element.
if ( !hasVirtualBindings( this ) ) {
$this.removeData( dataPropertyName );
}
}
};
}
// Expose our custom events to the jQuery bind/unbind mechanism.
for ( i = 0; i < virtualEventNames.length; i++ ) {
$.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
}
// Add a capture click handler to block clicks.
// Note that we require event capture support for this so if the device
// doesn't support it, we punt for now and rely solely on mouse events.
if ( eventCaptureSupported ) {
document.addEventListener( "click", function( e ) {
var cnt = clickBlockList.length,
target = e.target,
x, y, ele, i, o, touchID;
if ( cnt ) {
x = e.clientX;
y = e.clientY;
threshold = $.vmouse.clickDistanceThreshold;
// The idea here is to run through the clickBlockList to see if
// the current click event is in the proximity of one of our
// vclick events that had preventDefault() called on it. If we find
// one, then we block the click.
//
// Why do we have to rely on proximity?
//
// Because the target of the touch event that triggered the vclick
// can be different from the target of the click event synthesized
// by the browser. The target of a mouse/click event that is synthesized
// from a touch event seems to be implementation specific. For example,
// some browsers will fire mouse/click events for a link that is near
// a touch event, even though the target of the touchstart/touchend event
// says the user touched outside the link. Also, it seems that with most
// browsers, the target of the mouse/click event is not calculated until the
// time it is dispatched, so if you replace an element that you touched
// with another element, the target of the mouse/click will be the new
// element underneath that point.
//
// Aside from proximity, we also check to see if the target and any
// of its ancestors were the ones that blocked a click. This is necessary
// because of the strange mouse/click target calculation done in the
// Android 2.1 browser, where if you click on an element, and there is a
// mouse/click handler on one of its ancestors, the target will be the
// innermost child of the touched element, even if that child is no where
// near the point of touch.
ele = target;
while ( ele ) {
for ( i = 0; i < cnt; i++ ) {
o = clickBlockList[ i ];
touchID = 0;
if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
$.data( ele, touchTargetPropertyName ) === o.touchID ) {
// XXX: We may want to consider removing matches from the block list
// instead of waiting for the reset timer to fire.
e.preventDefault();
e.stopPropagation();
return;
}
}
ele = ele.parentNode;
}
}
}, true);
}
})( jQuery, window, document );
(function( $, window, undefined ) {
var $document = $( document ),
supportTouch = $.mobile.support.touch,
scrollEvent = "touchmove scroll",
touchStartEvent = supportTouch ? "touchstart" : "mousedown",
touchStopEvent = supportTouch ? "touchend" : "mouseup",
touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
// setup new event shortcuts
$.each( ( "touchstart touchmove touchend " +
"tap taphold " +
"swipe swipeleft swiperight " +
"scrollstart scrollstop" ).split( " " ), function( i, name ) {
$.fn[ name ] = function( fn ) {
return fn ? this.bind( name, fn ) : this.trigger( name );
};
// jQuery < 1.8
if ( $.attrFn ) {
$.attrFn[ name ] = true;
}
});
function triggerCustomEvent( obj, eventType, event, bubble ) {
var originalType = event.type;
event.type = eventType;
if ( bubble ) {
$.event.trigger( event, undefined, obj );
} else {
$.event.dispatch.call( obj, event );
}
event.type = originalType;
}
// also handles scrollstop
$.event.special.scrollstart = {
enabled: true,
setup: function() {
var thisObject = this,
$this = $( thisObject ),
scrolling,
timer;
function trigger( event, state ) {
scrolling = state;
triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
}
// iPhone triggers scroll after a small delay; use touchmove instead
$this.bind( scrollEvent, function( event ) {
if ( !$.event.special.scrollstart.enabled ) {
return;
}
if ( !scrolling ) {
trigger( event, true );
}
clearTimeout( timer );
timer = setTimeout( function() {
trigger( event, false );
}, 50 );
});
},
teardown: function() {
$( this ).unbind( scrollEvent );
}
};
// also handles taphold
$.event.special.tap = {
tapholdThreshold: 750,
emitTapOnTaphold: true,
setup: function() {
var thisObject = this,
$this = $( thisObject ),
isTaphold = false;
$this.bind( "vmousedown", function( event ) {
isTaphold = false;
if ( event.which && event.which !== 1 ) {
return false;
}
var origTarget = event.target,
timer;
function clearTapTimer() {
clearTimeout( timer );
}
function clearTapHandlers() {
clearTapTimer();
$this.unbind( "vclick", clickHandler )
.unbind( "vmouseup", clearTapTimer );
$document.unbind( "vmousecancel", clearTapHandlers );
}
function clickHandler( event ) {
clearTapHandlers();
// ONLY trigger a 'tap' event if the start target is
// the same as the stop target.
if ( !isTaphold && origTarget === event.target ) {
triggerCustomEvent( thisObject, "tap", event );
} else if ( isTaphold ) {
event.preventDefault();
}
}
$this.bind( "vmouseup", clearTapTimer )
.bind( "vclick", clickHandler );
$document.bind( "vmousecancel", clearTapHandlers );
timer = setTimeout( function() {
if ( !$.event.special.tap.emitTapOnTaphold ) {
isTaphold = true;
}
triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
}, $.event.special.tap.tapholdThreshold );
});
},
teardown: function() {
$( this ).unbind( "vmousedown" ).unbind( "vclick" ).unbind( "vmouseup" );
$document.unbind( "vmousecancel" );
}
};
// Also handles swipeleft, swiperight
$.event.special.swipe = {
// More than this horizontal displacement, and we will suppress scrolling.
scrollSupressionThreshold: 30,
// More time than this, and it isn't a swipe.
durationThreshold: 1000,
// Swipe horizontal displacement must be more than this.
horizontalDistanceThreshold: 30,
// Swipe vertical displacement must be less than this.
verticalDistanceThreshold: 30,
getLocation: function ( event ) {
var winPageX = window.pageXOffset,
winPageY = window.pageYOffset,
x = event.clientX,
y = event.clientY;
if ( event.pageY === 0 && Math.floor( y ) > Math.floor( event.pageY ) ||
event.pageX === 0 && Math.floor( x ) > Math.floor( event.pageX ) ) {
// iOS4 clientX/clientY have the value that should have been
// in pageX/pageY. While pageX/page/ have the value 0
x = x - winPageX;
y = y - winPageY;
} else if ( y < ( event.pageY - winPageY) || x < ( event.pageX - winPageX ) ) {
// Some Android browsers have totally bogus values for clientX/Y
// when scrolling/zooming a page. Detectable since clientX/clientY
// should never be smaller than pageX/pageY minus page scroll
x = event.pageX - winPageX;
y = event.pageY - winPageY;
}
return {
x: x,
y: y
};
},
start: function( event ) {
var data = event.originalEvent.touches ?
event.originalEvent.touches[ 0 ] : event,
location = $.event.special.swipe.getLocation( data );
return {
time: ( new Date() ).getTime(),
coords: [ location.x, location.y ],
origin: $( event.target )
};
},
stop: function( event ) {
var data = event.originalEvent.touches ?
event.originalEvent.touches[ 0 ] : event,
location = $.event.special.swipe.getLocation( data );
return {
time: ( new Date() ).getTime(),
coords: [ location.x, location.y ]
};
},
handleSwipe: function( start, stop, thisObject, origTarget ) {
if ( stop.time - start.time < $.event.special.swipe.durationThreshold &&
Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold &&
Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) {
var direction = start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight";
triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop }), true );
triggerCustomEvent( thisObject, direction,$.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ), true );
return true;
}
return false;
},
// This serves as a flag to ensure that at most one swipe event event is
// in work at any given time
eventInProgress: false,
setup: function() {
var events,
thisObject = this,
$this = $( thisObject ),
context = {};
// Retrieve the events data for this element and add the swipe context
events = $.data( this, "mobile-events" );
if ( !events ) {
events = { length: 0 };
$.data( this, "mobile-events", events );
}
events.length++;
events.swipe = context;
context.start = function( event ) {
// Bail if we're already working on a swipe event
if ( $.event.special.swipe.eventInProgress ) {
return;
}
$.event.special.swipe.eventInProgress = true;
var stop,
start = $.event.special.swipe.start( event ),
origTarget = event.target,
emitted = false;
context.move = function( event ) {
if ( !start || event.isDefaultPrevented() ) {
return;
}
stop = $.event.special.swipe.stop( event );
if ( !emitted ) {
emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget );
if ( emitted ) {
// Reset the context to make way for the next swipe event
$.event.special.swipe.eventInProgress = false;
}
}
// prevent scrolling
if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
event.preventDefault();
}
};
context.stop = function() {
emitted = true;
// Reset the context to make way for the next swipe event
$.event.special.swipe.eventInProgress = false;
$document.off( touchMoveEvent, context.move );
context.move = null;
};
$document.on( touchMoveEvent, context.move )
.one( touchStopEvent, context.stop );
};
$this.on( touchStartEvent, context.start );
},
teardown: function() {
var events, context;
events = $.data( this, "mobile-events" );
if ( events ) {
context = events.swipe;
delete events.swipe;
events.length--;
if ( events.length === 0 ) {
$.removeData( this, "mobile-events" );
}
}
if ( context ) {
if ( context.start ) {
$( this ).off( touchStartEvent, context.start );
}
if ( context.move ) {
$document.off( touchMoveEvent, context.move );
}
if ( context.stop ) {
$document.off( touchStopEvent, context.stop );
}
}
}
};
$.each({
scrollstop: "scrollstart",
taphold: "tap",
swipeleft: "swipe.left",
swiperight: "swipe.right"
}, function( event, sourceEvent ) {
$.event.special[ event ] = {
setup: function() {
$( this ).bind( sourceEvent, $.noop );
},
teardown: function() {
$( this ).unbind( sourceEvent );
}
};
});
})( jQuery, this );
// throttled resize event
(function( $ ) {
$.event.special.throttledresize = {
setup: function() {
$( this ).bind( "resize", handler );
},
teardown: function() {
$( this ).unbind( "resize", handler );
}
};
var throttle = 250,
handler = function() {
curr = ( new Date() ).getTime();
diff = curr - lastCall;
if ( diff >= throttle ) {
lastCall = curr;
$( this ).trigger( "throttledresize" );
} else {
if ( heldCall ) {
clearTimeout( heldCall );
}
// Promise a held call will still execute
heldCall = setTimeout( handler, throttle - diff );
}
},
lastCall = 0,
heldCall,
curr,
diff;
})( jQuery );
(function( $, window ) {
var win = $( window ),
event_name = "orientationchange",
get_orientation,
last_orientation,
initial_orientation_is_landscape,
initial_orientation_is_default,
portrait_map = { "0": true, "180": true },
ww, wh, landscape_threshold;
// It seems that some device/browser vendors use window.orientation values 0 and 180 to
// denote the "default" orientation. For iOS devices, and most other smart-phones tested,
// the default orientation is always "portrait", but in some Android and RIM based tablets,
// the default orientation is "landscape". The following code attempts to use the window
// dimensions to figure out what the current orientation is, and then makes adjustments
// to the to the portrait_map if necessary, so that we can properly decode the
// window.orientation value whenever get_orientation() is called.
//
// Note that we used to use a media query to figure out what the orientation the browser
// thinks it is in:
//
// initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)");
//
// but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1,
// where the browser *ALWAYS* applied the landscape media query. This bug does not
// happen on iPad.
if ( $.support.orientation ) {
// Check the window width and height to figure out what the current orientation
// of the device is at this moment. Note that we've initialized the portrait map
// values to 0 and 180, *AND* we purposely check for landscape so that if we guess
// wrong, , we default to the assumption that portrait is the default orientation.
// We use a threshold check below because on some platforms like iOS, the iPhone
// form-factor can report a larger width than height if the user turns on the
// developer console. The actual threshold value is somewhat arbitrary, we just
// need to make sure it is large enough to exclude the developer console case.
ww = window.innerWidth || win.width();
wh = window.innerHeight || win.height();
landscape_threshold = 50;
initial_orientation_is_landscape = ww > wh && ( ww - wh ) > landscape_threshold;
// Now check to see if the current window.orientation is 0 or 180.
initial_orientation_is_default = portrait_map[ window.orientation ];
// If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR*
// if the initial orientation is portrait, but window.orientation reports 90 or -90, we
// need to flip our portrait_map values because landscape is the default orientation for
// this device/browser.
if ( ( initial_orientation_is_landscape && initial_orientation_is_default ) || ( !initial_orientation_is_landscape && !initial_orientation_is_default ) ) {
portrait_map = { "-90": true, "90": true };
}
}
$.event.special.orientationchange = $.extend( {}, $.event.special.orientationchange, {
setup: function() {
// If the event is supported natively, return false so that jQuery
// will bind to the event using DOM methods.
if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
return false;
}
// Get the current orientation to avoid initial double-triggering.
last_orientation = get_orientation();
// Because the orientationchange event doesn't exist, simulate the
// event by testing window dimensions on resize.
win.bind( "throttledresize", handler );
},
teardown: function() {
// If the event is not supported natively, return false so that
// jQuery will unbind the event using DOM methods.
if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
return false;
}
// Because the orientationchange event doesn't exist, unbind the
// resize event handler.
win.unbind( "throttledresize", handler );
},
add: function( handleObj ) {
// Save a reference to the bound event handler.
var old_handler = handleObj.handler;
handleObj.handler = function( event ) {
// Modify event object, adding the .orientation property.
event.orientation = get_orientation();
// Call the originally-bound event handler and return its result.
return old_handler.apply( this, arguments );
};
}
});
// If the event is not supported natively, this handler will be bound to
// the window resize event to simulate the orientationchange event.
function handler() {
// Get the current orientation.
var orientation = get_orientation();
if ( orientation !== last_orientation ) {
// The orientation has changed, so trigger the orientationchange event.
last_orientation = orientation;
win.trigger( event_name );
}
}
// Get the current page orientation. This method is exposed publicly, should it
// be needed, as jQuery.event.special.orientationchange.orientation()
$.event.special.orientationchange.orientation = get_orientation = function() {
var isPortrait = true, elem = document.documentElement;
// prefer window orientation to the calculation based on screensize as
// the actual screen resize takes place before or after the orientation change event
// has been fired depending on implementation (eg android 2.3 is before, iphone after).
// More testing is required to determine if a more reliable method of determining the new screensize
// is possible when orientationchange is fired. (eg, use media queries + element + opacity)
if ( $.support.orientation ) {
// if the window orientation registers as 0 or 180 degrees report
// portrait, otherwise landscape
isPortrait = portrait_map[ window.orientation ];
} else {
isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1;
}
return isPortrait ? "portrait" : "landscape";
};
$.fn[ event_name ] = function( fn ) {
return fn ? this.bind( event_name, fn ) : this.trigger( event_name );
};
// jQuery < 1.8
if ( $.attrFn ) {
$.attrFn[ event_name ] = true;
}
}( jQuery, this ));
(function( $, undefined ) {
// existing base tag?
var baseElement = $( "head" ).children( "base" ),
// base element management, defined depending on dynamic base tag support
// TODO move to external widget
base = {
// define base element, for use in routing asset urls that are referenced
// in Ajax-requested markup
element: ( baseElement.length ? baseElement :
$( "", { href: $.mobile.path.documentBase.hrefNoHash } ).prependTo( $( "head" ) ) ),
linkSelector: "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]",
// set the generated BASE element's href to a new page's base path
set: function( href ) {
// we should do nothing if the user wants to manage their url base
// manually
if ( !$.mobile.dynamicBaseEnabled ) {
return;
}
// we should use the base tag if we can manipulate it dynamically
if ( $.support.dynamicBaseTag ) {
base.element.attr( "href",
$.mobile.path.makeUrlAbsolute( href, $.mobile.path.documentBase ) );
}
},
rewrite: function( href, page ) {
var newPath = $.mobile.path.get( href );
page.find( base.linkSelector ).each(function( i, link ) {
var thisAttr = $( link ).is( "[href]" ) ? "href" :
$( link ).is( "[src]" ) ? "src" : "action",
theLocation = $.mobile.path.parseLocation(),
thisUrl = $( link ).attr( thisAttr );
// XXX_jblas: We need to fix this so that it removes the document
// base URL, and then prepends with the new page URL.
// if full path exists and is same, chop it - helps IE out
thisUrl = thisUrl.replace( theLocation.protocol + theLocation.doubleSlash +
theLocation.host + theLocation.pathname, "" );
if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
$( link ).attr( thisAttr, newPath + thisUrl );
}
});
},
// set the generated BASE element's href to a new page's base path
reset: function(/* href */) {
base.element.attr( "href", $.mobile.path.documentBase.hrefNoSearch );
}
};
$.mobile.base = base;
})( jQuery );
(function( $, undefined ) {
$.mobile.widgets = {};
var originalWidget = $.widget,
// Record the original, non-mobileinit-modified version of $.mobile.keepNative
// so we can later determine whether someone has modified $.mobile.keepNative
keepNativeFactoryDefault = $.mobile.keepNative;
$.widget = (function( orig ) {
return function() {
var constructor = orig.apply( this, arguments ),
name = constructor.prototype.widgetName;
constructor.initSelector = ( ( constructor.prototype.initSelector !== undefined ) ?
constructor.prototype.initSelector : ":jqmData(role='" + name + "')" );
$.mobile.widgets[ name ] = constructor;
return constructor;
};
})( $.widget );
// Make sure $.widget still has bridge and extend methods
$.extend( $.widget, originalWidget );
// For backcompat remove in 1.5
$.mobile.document.on( "create", function( event ) {
$( event.target ).enhanceWithin();
});
$.widget( "mobile.page", {
options: {
theme: "a",
domCache: false,
// Deprecated in 1.4 remove in 1.5
keepNativeDefault: $.mobile.keepNative,
// Deprecated in 1.4 remove in 1.5
contentTheme: null,
enhanced: false
},
// DEPRECATED for > 1.4
// TODO remove at 1.5
_createWidget: function() {
$.Widget.prototype._createWidget.apply( this, arguments );
this._trigger( "init" );
},
_create: function() {
// If false is returned by the callbacks do not create the page
if ( this._trigger( "beforecreate" ) === false ) {
return false;
}
if ( !this.options.enhanced ) {
this._enhance();
}
this._on( this.element, {
pagebeforehide: "removeContainerBackground",
pagebeforeshow: "_handlePageBeforeShow"
});
this.element.enhanceWithin();
// Dialog widget is deprecated in 1.4 remove this in 1.5
if ( $.mobile.getAttribute( this.element[0], "role" ) === "dialog" && $.mobile.dialog ) {
this.element.dialog();
}
},
_enhance: function () {
var attrPrefix = "data-" + $.mobile.ns,
self = this;
if ( this.options.role ) {
this.element.attr( "data-" + $.mobile.ns + "role", this.options.role );
}
this.element
.attr( "tabindex", "0" )
.addClass( "ui-page ui-page-theme-" + this.options.theme );
// Manipulation of content os Deprecated as of 1.4 remove in 1.5
this.element.find( "[" + attrPrefix + "role='content']" ).each( function() {
var $this = $( this ),
theme = this.getAttribute( attrPrefix + "theme" ) || undefined;
self.options.contentTheme = theme || self.options.contentTheme || ( self.options.dialog && self.options.theme ) || ( self.element.jqmData("role") === "dialog" && self.options.theme );
$this.addClass( "ui-content" );
if ( self.options.contentTheme ) {
$this.addClass( "ui-body-" + ( self.options.contentTheme ) );
}
// Add ARIA role
$this.attr( "role", "main" ).addClass( "ui-content" );
});
},
bindRemove: function( callback ) {
var page = this.element;
// when dom caching is not enabled or the page is embedded bind to remove the page on hide
if ( !page.data( "mobile-page" ).options.domCache &&
page.is( ":jqmData(external-page='true')" ) ) {
// TODO use _on - that is, sort out why it doesn't work in this case
page.bind( "pagehide.remove", callback || function( e, data ) {
//check if this is a same page transition and if so don't remove the page
if( !data.samePage ){
var $this = $( this ),
prEvent = new $.Event( "pageremove" );
$this.trigger( prEvent );
if ( !prEvent.isDefaultPrevented() ) {
$this.removeWithDependents();
}
}
});
}
},
_setOptions: function( o ) {
if ( o.theme !== undefined ) {
this.element.removeClass( "ui-page-theme-" + this.options.theme ).addClass( "ui-page-theme-" + o.theme );
}
if ( o.contentTheme !== undefined ) {
this.element.find( "[data-" + $.mobile.ns + "='content']" ).removeClass( "ui-body-" + this.options.contentTheme )
.addClass( "ui-body-" + o.contentTheme );
}
},
_handlePageBeforeShow: function(/* e */) {
this.setContainerBackground();
},
// Deprecated in 1.4 remove in 1.5
removeContainerBackground: function() {
this.element.closest( ":mobile-pagecontainer" ).pagecontainer({ "theme": "none" });
},
// Deprecated in 1.4 remove in 1.5
// set the page container background to the page theme
setContainerBackground: function( theme ) {
this.element.parent().pagecontainer( { "theme": theme || this.options.theme } );
},
// Deprecated in 1.4 remove in 1.5
keepNativeSelector: function() {
var options = this.options,
keepNative = $.trim( options.keepNative || "" ),
globalValue = $.trim( $.mobile.keepNative ),
optionValue = $.trim( options.keepNativeDefault ),
// Check if $.mobile.keepNative has changed from the factory default
newDefault = ( keepNativeFactoryDefault === globalValue ?
"" : globalValue ),
// If $.mobile.keepNative has not changed, use options.keepNativeDefault
oldDefault = ( newDefault === "" ? optionValue : "" );
// Concatenate keepNative selectors from all sources where the value has
// changed or, if nothing has changed, return the default
return ( ( keepNative ? [ keepNative ] : [] )
.concat( newDefault ? [ newDefault ] : [] )
.concat( oldDefault ? [ oldDefault ] : [] )
.join( ", " ) );
}
});
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.pagecontainer", {
options: {
theme: "a"
},
initSelector: false,
_create: function() {
this._trigger( "beforecreate" );
this.setLastScrollEnabled = true;
this._on( this.window, {
// disable an scroll setting when a hashchange has been fired,
// this only works because the recording of the scroll position
// is delayed for 100ms after the browser might have changed the
// position because of the hashchange
navigate: "_disableRecordScroll",
// bind to scrollstop for the first page, "pagechange" won't be
// fired in that case
scrollstop: "_delayedRecordScroll"
});
// TODO consider moving the navigation handler OUT of widget into
// some other object as glue between the navigate event and the
// content widget load and change methods
this._on( this.window, { navigate: "_filterNavigateEvents" });
// TODO move from page* events to content* events
this._on({ pagechange: "_afterContentChange" });
// handle initial hashchange from chrome :(
this.window.one( "navigate", $.proxy(function() {
this.setLastScrollEnabled = true;
}, this));
},
_setOptions: function( options ) {
if ( options.theme !== undefined && options.theme !== "none" ) {
this.element.removeClass( "ui-overlay-" + this.options.theme )
.addClass( "ui-overlay-" + options.theme );
} else if ( options.theme !== undefined ) {
this.element.removeClass( "ui-overlay-" + this.options.theme );
}
this._super( options );
},
_disableRecordScroll: function() {
this.setLastScrollEnabled = false;
},
_enableRecordScroll: function() {
this.setLastScrollEnabled = true;
},
// TODO consider the name here, since it's purpose specific
_afterContentChange: function() {
// once the page has changed, re-enable the scroll recording
this.setLastScrollEnabled = true;
// remove any binding that previously existed on the get scroll
// which may or may not be different than the scroll element
// determined for this page previously
this._off( this.window, "scrollstop" );
// determine and bind to the current scoll element which may be the
// window or in the case of touch overflow the element touch overflow
this._on( this.window, { scrollstop: "_delayedRecordScroll" });
},
_recordScroll: function() {
// this barrier prevents setting the scroll value based on
// the browser scrolling the window based on a hashchange
if ( !this.setLastScrollEnabled ) {
return;
}
var active = this._getActiveHistory(),
currentScroll, minScroll, defaultScroll;
if ( active ) {
currentScroll = this._getScroll();
minScroll = this._getMinScroll();
defaultScroll = this._getDefaultScroll();
// Set active page's lastScroll prop. If the location we're
// scrolling to is less than minScrollBack, let it go.
active.lastScroll = currentScroll < minScroll ? defaultScroll : currentScroll;
}
},
_delayedRecordScroll: function() {
setTimeout( $.proxy(this, "_recordScroll"), 100 );
},
_getScroll: function() {
return this.window.scrollTop();
},
_getMinScroll: function() {
return $.mobile.minScrollBack;
},
_getDefaultScroll: function() {
return $.mobile.defaultHomeScroll;
},
_filterNavigateEvents: function( e, data ) {
var url;
if ( e.originalEvent && e.originalEvent.isDefaultPrevented() ) {
return;
}
url = e.originalEvent.type.indexOf( "hashchange" ) > -1 ? data.state.hash : data.state.url;
if ( !url ) {
url = this._getHash();
}
if ( !url || url === "#" || url.indexOf( "#" + $.mobile.path.uiStateKey ) === 0 ) {
url = location.href;
}
this._handleNavigate( url, data.state );
},
_getHash: function() {
return $.mobile.path.parseLocation().hash;
},
// TODO active page should be managed by the container (ie, it should be a property)
getActivePage: function() {
return this.activePage;
},
// TODO the first page should be a property set during _create using the logic
// that currently resides in init
_getInitialContent: function() {
return $.mobile.firstPage;
},
// TODO each content container should have a history object
_getHistory: function() {
return $.mobile.navigate.history;
},
_getActiveHistory: function() {
return this._getHistory().getActive();
},
// TODO the document base should be determined at creation
_getDocumentBase: function() {
return $.mobile.path.documentBase;
},
back: function() {
this.go( -1 );
},
forward: function() {
this.go( 1 );
},
go: function( steps ) {
//if hashlistening is enabled use native history method
if ( $.mobile.hashListeningEnabled ) {
window.history.go( steps );
} else {
//we are not listening to the hash so handle history internally
var activeIndex = $.mobile.navigate.history.activeIndex,
index = activeIndex + parseInt( steps, 10 ),
url = $.mobile.navigate.history.stack[ index ].url,
direction = ( steps >= 1 )? "forward" : "back";
//update the history object
$.mobile.navigate.history.activeIndex = index;
$.mobile.navigate.history.previousIndex = activeIndex;
//change to the new page
this.change( url, { direction: direction, changeHash: false, fromHashChange: true } );
}
},
// TODO rename _handleDestination
_handleDestination: function( to ) {
var history;
// clean the hash for comparison if it's a url
if ( $.type(to) === "string" ) {
to = $.mobile.path.stripHash( to );
}
if ( to ) {
history = this._getHistory();
// At this point, 'to' can be one of 3 things, a cached page
// element from a history stack entry, an id, or site-relative /
// absolute URL. If 'to' is an id, we need to resolve it against
// the documentBase, not the location.href, since the hashchange
// could've been the result of a forward/backward navigation
// that crosses from an external page/dialog to an internal
// page/dialog.
//
// TODO move check to history object or path object?
to = !$.mobile.path.isPath( to ) ? ( $.mobile.path.makeUrlAbsolute( "#" + to, this._getDocumentBase() ) ) : to;
}
return to || this._getInitialContent();
},
_transitionFromHistory: function( direction, defaultTransition ) {
var history = this._getHistory(),
entry = ( direction === "back" ? history.getLast() : history.getActive() );
return ( entry && entry.transition ) || defaultTransition;
},
_handleDialog: function( changePageOptions, data ) {
var to, active, activeContent = this.getActivePage();
// If current active page is not a dialog skip the dialog and continue
// in the same direction
// Note: The dialog widget is deprecated as of 1.4.0 and will be removed in 1.5.0.
// Thus, as of 1.5.0 activeContent.data( "mobile-dialog" ) will always evaluate to
// falsy, so the second condition in the if-statement below can be removed altogether.
if ( activeContent && !activeContent.data( "mobile-dialog" ) ) {
// determine if we're heading forward or backward and continue
// accordingly past the current dialog
if ( data.direction === "back" ) {
this.back();
} else {
this.forward();
}
// prevent changePage call
return false;
} else {
// if the current active page is a dialog and we're navigating
// to a dialog use the dialog objected saved in the stack
to = data.pageUrl;
active = this._getActiveHistory();
// make sure to set the role, transition and reversal
// as most of this is lost by the domCache cleaning
$.extend( changePageOptions, {
role: active.role,
transition: this._transitionFromHistory(
data.direction,
changePageOptions.transition ),
reverse: data.direction === "back"
});
}
return to;
},
_handleNavigate: function( url, data ) {
//find first page via hash
// TODO stripping the hash twice with handleUrl
var to = $.mobile.path.stripHash( url ), history = this._getHistory(),
// transition is false if it's the first page, undefined
// otherwise (and may be overridden by default)
transition = history.stack.length === 0 ? "none" :
this._transitionFromHistory( data.direction ),
// default options for the changPage calls made after examining
// the current state of the page and the hash, NOTE that the
// transition is derived from the previous history entry
changePageOptions = {
changeHash: false,
fromHashChange: true,
reverse: data.direction === "back"
};
$.extend( changePageOptions, data, {
transition: transition
});
// TODO move to _handleDestination ?
// If this isn't the first page, if the current url is a dialog hash
// key, and the initial destination isn't equal to the current target
// page, use the special dialog handling
if ( history.activeIndex > 0 &&
to.indexOf( $.mobile.dialogHashKey ) > -1 ) {
to = this._handleDialog( changePageOptions, data );
if ( to === false ) {
return;
}
}
this._changeContent( this._handleDestination( to ), changePageOptions );
},
_changeContent: function( to, opts ) {
$.mobile.changePage( to, opts );
},
_getBase: function() {
return $.mobile.base;
},
_getNs: function() {
return $.mobile.ns;
},
_enhance: function( content, role ) {
// TODO consider supporting a custom callback, and passing in
// the settings which includes the role
return content.page({ role: role });
},
_include: function( page, settings ) {
// append to page and enhance
page.appendTo( this.element );
// use the page widget to enhance
this._enhance( page, settings.role );
// remove page on hide
page.page( "bindRemove" );
},
_find: function( absUrl ) {
// TODO consider supporting a custom callback
var fileUrl = this._createFileUrl( absUrl ),
dataUrl = this._createDataUrl( absUrl ),
page, initialContent = this._getInitialContent();
// Check to see if the page already exists in the DOM.
// NOTE do _not_ use the :jqmData pseudo selector because parenthesis
// are a valid url char and it breaks on the first occurence
page = this.element
.children( "[data-" + this._getNs() +
"url='" + $.mobile.path.hashToSelector( dataUrl ) + "']" );
// If we failed to find the page, check to see if the url is a
// reference to an embedded page. If so, it may have been dynamically
// injected by a developer, in which case it would be lacking a
// data-url attribute and in need of enhancement.
if ( page.length === 0 && dataUrl && !$.mobile.path.isPath( dataUrl ) ) {
page = this.element.children( $.mobile.path.hashToSelector("#" + dataUrl) )
.attr( "data-" + this._getNs() + "url", dataUrl )
.jqmData( "url", dataUrl );
}
// If we failed to find a page in the DOM, check the URL to see if it
// refers to the first page in the application. Also check to make sure
// our cached-first-page is actually in the DOM. Some user deployed
// apps are pruning the first page from the DOM for various reasons.
// We check for this case here because we don't want a first-page with
// an id falling through to the non-existent embedded page error case.
if ( page.length === 0 &&
$.mobile.path.isFirstPageUrl( fileUrl ) &&
initialContent &&
initialContent.parent().length ) {
page = $( initialContent );
}
return page;
},
_getLoader: function() {
return $.mobile.loading();
},
_showLoading: function( delay, theme, msg, textonly ) {
// This configurable timeout allows cached pages a brief
// delay to load without showing a message
if ( this._loadMsg ) {
return;
}
this._loadMsg = setTimeout($.proxy(function() {
this._getLoader().loader( "show", theme, msg, textonly );
this._loadMsg = 0;
}, this), delay );
},
_hideLoading: function() {
// Stop message show timer
clearTimeout( this._loadMsg );
this._loadMsg = 0;
// Hide loading message
this._getLoader().loader( "hide" );
},
_showError: function() {
// make sure to remove the current loading message
this._hideLoading();
// show the error message
this._showLoading( 0, $.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true );
// hide the error message after a delay
// TODO configuration
setTimeout( $.proxy(this, "_hideLoading"), 1500 );
},
_parse: function( html, fileUrl ) {
// TODO consider allowing customization of this method. It's very JQM specific
var page, all = $( "" );
//workaround to allow scripts to execute when included in page divs
all.get( 0 ).innerHTML = html;
page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();
//if page elem couldn't be found, create one and insert the body element's contents
if ( !page.length ) {
page = $( "
" );
}
// TODO tagging a page with external to make sure that embedded pages aren't
// removed by the various page handling code is bad. Having page handling code
// in many places is bad. Solutions post 1.0
page.attr( "data-" + this._getNs() + "url", this._createDataUrl( fileUrl ) )
.attr( "data-" + this._getNs() + "external-page", true );
return page;
},
_setLoadedTitle: function( page, html ) {
//page title regexp
var newPageTitle = html.match( /]*>([^<]*)/ ) && RegExp.$1;
if ( newPageTitle && !page.jqmData("title") ) {
newPageTitle = $( "
" + newPageTitle + "
" ).text();
page.jqmData( "title", newPageTitle );
}
},
_isRewritableBaseTag: function() {
return $.mobile.dynamicBaseEnabled && !$.support.dynamicBaseTag;
},
_createDataUrl: function( absoluteUrl ) {
return $.mobile.path.convertUrlToDataUrl( absoluteUrl );
},
_createFileUrl: function( absoluteUrl ) {
return $.mobile.path.getFilePath( absoluteUrl );
},
_triggerWithDeprecated: function( name, data, page ) {
var deprecatedEvent = $.Event( "page" + name ),
newEvent = $.Event( this.widgetName + name );
// DEPRECATED
// trigger the old deprecated event on the page if it's provided
( page || this.element ).trigger( deprecatedEvent, data );
// use the widget trigger method for the new content* event
this._trigger( name, newEvent, data );
return {
deprecatedEvent: deprecatedEvent,
event: newEvent
};
},
// TODO it would be nice to split this up more but everything appears to be "one off"
// or require ordering such that other bits are sprinkled in between parts that
// could be abstracted out as a group
_loadSuccess: function( absUrl, triggerData, settings, deferred ) {
var fileUrl = this._createFileUrl( absUrl );
return $.proxy(function( html, textStatus, xhr ) {
//pre-parse html to check for a data-url,
//use it as the new fileUrl, base path, etc
var content,
// TODO handle dialogs again
pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + this._getNs() + "role=[\"']?page[\"']?[^>]*>)" ),
dataUrlRegex = new RegExp( "\\bdata-" + this._getNs() + "url=[\"']?([^\"'>]*)[\"']?" );
// data-url must be provided for the base tag so resource requests
// can be directed to the correct url. loading into a temprorary
// element makes these requests immediately
if ( pageElemRegex.test( html ) &&
RegExp.$1 &&
dataUrlRegex.test( RegExp.$1 ) &&
RegExp.$1 ) {
fileUrl = $.mobile.path.getFilePath( $("
" + RegExp.$1 + "
").text() );
// We specify that, if a data-url attribute is given on the page div, its value
// must be given non-URL-encoded. However, in this part of the code, fileUrl is
// assumed to be URL-encoded, so we URL-encode the retrieved value here
fileUrl = this.window[ 0 ].encodeURIComponent( fileUrl );
}
//dont update the base tag if we are prefetching
if ( settings.prefetch === undefined ) {
this._getBase().set( fileUrl );
}
content = this._parse( html, fileUrl );
this._setLoadedTitle( content, html );
// Add the content reference and xhr to our triggerData.
triggerData.xhr = xhr;
triggerData.textStatus = textStatus;
// DEPRECATED
triggerData.page = content;
triggerData.content = content;
triggerData.toPage = content;
// If the default behavior is prevented, stop here!
// Note that it is the responsibility of the listener/handler
// that called preventDefault(), to resolve/reject the
// deferred object within the triggerData.
if ( this._triggerWithDeprecated( "load", triggerData ).event.isDefaultPrevented() ) {
return;
}
// rewrite src and href attrs to use a base url if the base tag won't work
if ( this._isRewritableBaseTag() && content ) {
this._getBase().rewrite( fileUrl, content );
}
this._include( content, settings );
// Remove loading message.
if ( settings.showLoadMsg ) {
this._hideLoading();
}
deferred.resolve( absUrl, settings, content );
}, this);
},
_loadDefaults: {
type: "get",
data: undefined,
// DEPRECATED
reloadPage: false,
reload: false,
// By default we rely on the role defined by the @data-role attribute.
role: undefined,
showLoadMsg: false,
// This delay allows loads that pull from browser cache to
// occur without showing the loading message.
loadMsgDelay: 50
},
load: function( url, options ) {
// This function uses deferred notifications to let callers
// know when the content is done loading, or if an error has occurred.
var deferred = ( options && options.deferred ) || $.Deferred(),
// Examining the option "reloadPage" passed by the user is deprecated as of 1.4.0
// and will be removed in 1.5.0.
// Copy option "reloadPage" to "reload", but only if option "reload" is not present
reloadOptionExtension =
( ( options && options.reload === undefined &&
options.reloadPage !== undefined ) ?
{ reload: options.reloadPage } : {} ),
// The default load options with overrides specified by the caller.
settings = $.extend( {}, this._loadDefaults, options, reloadOptionExtension ),
// The DOM element for the content after it has been loaded.
content = null,
// The absolute version of the URL passed into the function. This
// version of the URL may contain dialog/subcontent params in it.
absUrl = $.mobile.path.makeUrlAbsolute( url, this._findBaseWithDefault() ),
fileUrl, dataUrl, pblEvent, triggerData;
// If the caller provided data, and we're using "get" request,
// append the data to the URL.
if ( settings.data && settings.type === "get" ) {
absUrl = $.mobile.path.addSearchParams( absUrl, settings.data );
settings.data = undefined;
}
// If the caller is using a "post" request, reload must be true
if ( settings.data && settings.type === "post" ) {
settings.reload = true;
}
// The absolute version of the URL minus any dialog/subcontent params.
// In otherwords the real URL of the content to be loaded.
fileUrl = this._createFileUrl( absUrl );
// The version of the Url actually stored in the data-url attribute of
// the content. For embedded content, it is just the id of the page. For
// content within the same domain as the document base, it is the site
// relative path. For cross-domain content (Phone Gap only) the entire
// absolute Url is used to load the content.
dataUrl = this._createDataUrl( absUrl );
content = this._find( absUrl );
// If it isn't a reference to the first content and refers to missing
// embedded content reject the deferred and return
if ( content.length === 0 &&
$.mobile.path.isEmbeddedPage(fileUrl) &&
!$.mobile.path.isFirstPageUrl(fileUrl) ) {
deferred.reject( absUrl, settings );
return deferred.promise();
}
// Reset base to the default document base
// TODO figure out why we doe this
this._getBase().reset();
// If the content we are interested in is already in the DOM,
// and the caller did not indicate that we should force a
// reload of the file, we are done. Resolve the deferrred so that
// users can bind to .done on the promise
if ( content.length && !settings.reload ) {
this._enhance( content, settings.role );
deferred.resolve( absUrl, settings, content );
//if we are reloading the content make sure we update
// the base if its not a prefetch
if ( !settings.prefetch ) {
this._getBase().set(url);
}
return deferred.promise();
}
triggerData = {
url: url,
absUrl: absUrl,
toPage: url,
prevPage: options ? options.fromPage : undefined,
dataUrl: dataUrl,
deferred: deferred,
options: settings
};
// Let listeners know we're about to load content.
pblEvent = this._triggerWithDeprecated( "beforeload", triggerData );
// If the default behavior is prevented, stop here!
if ( pblEvent.deprecatedEvent.isDefaultPrevented() ||
pblEvent.event.isDefaultPrevented() ) {
return deferred.promise();
}
if ( settings.showLoadMsg ) {
this._showLoading( settings.loadMsgDelay );
}
// Reset base to the default document base.
// only reset if we are not prefetching
if ( settings.prefetch === undefined ) {
this._getBase().reset();
}
if ( !( $.mobile.allowCrossDomainPages ||
$.mobile.path.isSameDomain($.mobile.path.documentUrl, absUrl ) ) ) {
deferred.reject( absUrl, settings );
return deferred.promise();
}
// Load the new content.
$.ajax({
url: fileUrl,
type: settings.type,
data: settings.data,
contentType: settings.contentType,
dataType: "html",
success: this._loadSuccess( absUrl, triggerData, settings, deferred ),
error: this._loadError( absUrl, triggerData, settings, deferred )
});
return deferred.promise();
},
_loadError: function( absUrl, triggerData, settings, deferred ) {
return $.proxy(function( xhr, textStatus, errorThrown ) {
//set base back to current path
this._getBase().set( $.mobile.path.get() );
// Add error info to our triggerData.
triggerData.xhr = xhr;
triggerData.textStatus = textStatus;
triggerData.errorThrown = errorThrown;
// Let listeners know the page load failed.
var plfEvent = this._triggerWithDeprecated( "loadfailed", triggerData );
// If the default behavior is prevented, stop here!
// Note that it is the responsibility of the listener/handler
// that called preventDefault(), to resolve/reject the
// deferred object within the triggerData.
if ( plfEvent.deprecatedEvent.isDefaultPrevented() ||
plfEvent.event.isDefaultPrevented() ) {
return;
}
// Remove loading message.
if ( settings.showLoadMsg ) {
this._showError();
}
deferred.reject( absUrl, settings );
}, this);
},
_getTransitionHandler: function( transition ) {
transition = $.mobile._maybeDegradeTransition( transition );
//find the transition handler for the specified transition. If there
//isn't one in our transitionHandlers dictionary, use the default one.
//call the handler immediately to kick-off the transition.
return $.mobile.transitionHandlers[ transition ] || $.mobile.defaultTransitionHandler;
},
// TODO move into transition handlers?
_triggerCssTransitionEvents: function( to, from, prefix ) {
var samePage = false;
prefix = prefix || "";
// TODO decide if these events should in fact be triggered on the container
if ( from ) {
//Check if this is a same page transition and tell the handler in page
if( to[0] === from[0] ){
samePage = true;
}
//trigger before show/hide events
// TODO deprecate nextPage in favor of next
this._triggerWithDeprecated( prefix + "hide", {
// Deprecated in 1.4 remove in 1.5
nextPage: to,
toPage: to,
prevPage: from,
samePage: samePage
}, from );
}
// TODO deprecate prevPage in favor of previous
this._triggerWithDeprecated( prefix + "show", {
prevPage: from || $( "" ),
toPage: to
}, to );
},
// TODO make private once change has been defined in the widget
_cssTransition: function( to, from, options ) {
var transition = options.transition,
reverse = options.reverse,
deferred = options.deferred,
TransitionHandler,
promise;
this._triggerCssTransitionEvents( to, from, "before" );
// TODO put this in a binding to events *outside* the widget
this._hideLoading();
TransitionHandler = this._getTransitionHandler( transition );
promise = ( new TransitionHandler( transition, reverse, to, from ) ).transition();
promise.done( $.proxy( function() {
this._triggerCssTransitionEvents( to, from );
}, this ));
// TODO temporary accomodation of argument deferred
promise.done(function() {
deferred.resolve.apply( deferred, arguments );
});
},
_releaseTransitionLock: function() {
//release transition lock so navigation is free again
isPageTransitioning = false;
if ( pageTransitionQueue.length > 0 ) {
$.mobile.changePage.apply( null, pageTransitionQueue.pop() );
}
},
_removeActiveLinkClass: function( force ) {
//clear out the active button state
$.mobile.removeActiveLinkClass( force );
},
_loadUrl: function( to, triggerData, settings ) {
// preserve the original target as the dataUrl value will be
// simplified eg, removing ui-state, and removing query params
// from the hash this is so that users who want to use query
// params have access to them in the event bindings for the page
// life cycle See issue #5085
settings.target = to;
settings.deferred = $.Deferred();
this.load( to, settings );
settings.deferred.done($.proxy(function( url, options, content ) {
isPageTransitioning = false;
// store the original absolute url so that it can be provided
// to events in the triggerData of the subsequent changePage call
options.absUrl = triggerData.absUrl;
this.transition( content, triggerData, options );
}, this));
settings.deferred.fail($.proxy(function(/* url, options */) {
this._removeActiveLinkClass( true );
this._releaseTransitionLock();
this._triggerWithDeprecated( "changefailed", triggerData );
}, this));
},
_triggerPageBeforeChange: function( to, triggerData, settings ) {
var returnEvents;
triggerData.prevPage = this.activePage;
$.extend( triggerData, {
toPage: to,
options: settings
});
// NOTE: preserve the original target as the dataUrl value will be
// simplified eg, removing ui-state, and removing query params from
// the hash this is so that users who want to use query params have
// access to them in the event bindings for the page life cycle
// See issue #5085
if ( $.type(to) === "string" ) {
// if the toPage is a string simply convert it
triggerData.absUrl = $.mobile.path.makeUrlAbsolute( to, this._findBaseWithDefault() );
} else {
// if the toPage is a jQuery object grab the absolute url stored
// in the loadPage callback where it exists
triggerData.absUrl = settings.absUrl;
}
// Let listeners know we're about to change the current page.
returnEvents = this._triggerWithDeprecated( "beforechange", triggerData );
// If the default behavior is prevented, stop here!
if ( returnEvents.event.isDefaultPrevented() ||
returnEvents.deprecatedEvent.isDefaultPrevented() ) {
return false;
}
return true;
},
change: function( to, options ) {
// If we are in the midst of a transition, queue the current request.
// We'll call changePage() once we're done with the current transition
// to service the request.
if ( isPageTransitioning ) {
pageTransitionQueue.unshift( arguments );
return;
}
var settings = $.extend( {}, $.mobile.changePage.defaults, options ),
triggerData = {};
// Make sure we have a fromPage.
settings.fromPage = settings.fromPage || this.activePage;
// if the page beforechange default is prevented return early
if ( !this._triggerPageBeforeChange(to, triggerData, settings) ) {
return;
}
// We allow "pagebeforechange" observers to modify the to in
// the trigger data to allow for redirects. Make sure our to is
// updated. We also need to re-evaluate whether it is a string,
// because an object can also be replaced by a string
to = triggerData.toPage;
// If the caller passed us a url, call loadPage()
// to make sure it is loaded into the DOM. We'll listen
// to the promise object it returns so we know when
// it is done loading or if an error ocurred.
if ( $.type(to) === "string" ) {
// Set the isPageTransitioning flag to prevent any requests from
// entering this method while we are in the midst of loading a page
// or transitioning.
isPageTransitioning = true;
this._loadUrl( to, triggerData, settings );
} else {
this.transition( to, triggerData, settings );
}
},
transition: function( toPage, triggerData, settings ) {
var fromPage, url, pageUrl, fileUrl,
active, activeIsInitialPage,
historyDir, pageTitle, isDialog,
alreadyThere, newPageTitle,
params, cssTransitionDeferred,
beforeTransition;
// If we are in the midst of a transition, queue the current request.
// We'll call changePage() once we're done with the current transition
// to service the request.
if ( isPageTransitioning ) {
// make sure to only queue the to and settings values so the arguments
// work with a call to the change method
pageTransitionQueue.unshift( [toPage, settings] );
return;
}
// DEPRECATED - this call only, in favor of the before transition
// if the page beforechange default is prevented return early
if ( !this._triggerPageBeforeChange(toPage, triggerData, settings) ) {
return;
}
triggerData.prevPage = settings.fromPage;
// if the (content|page)beforetransition default is prevented return early
// Note, we have to check for both the deprecated and new events
beforeTransition = this._triggerWithDeprecated( "beforetransition", triggerData );
if (beforeTransition.deprecatedEvent.isDefaultPrevented() ||
beforeTransition.event.isDefaultPrevented() ) {
return;
}
// Set the isPageTransitioning flag to prevent any requests from
// entering this method while we are in the midst of loading a page
// or transitioning.
isPageTransitioning = true;
// If we are going to the first-page of the application, we need to make
// sure settings.dataUrl is set to the application document url. This allows
// us to avoid generating a document url with an id hash in the case where the
// first-page of the document has an id attribute specified.
if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) {
settings.dataUrl = $.mobile.path.documentUrl.hrefNoHash;
}
// The caller passed us a real page DOM element. Update our
// internal state and then trigger a transition to the page.
fromPage = settings.fromPage;
url = ( settings.dataUrl && $.mobile.path.convertUrlToDataUrl(settings.dataUrl) ) ||
toPage.jqmData( "url" );
// The pageUrl var is usually the same as url, except when url is obscured
// as a dialog url. pageUrl always contains the file path
pageUrl = url;
fileUrl = $.mobile.path.getFilePath( url );
active = $.mobile.navigate.history.getActive();
activeIsInitialPage = $.mobile.navigate.history.activeIndex === 0;
historyDir = 0;
pageTitle = document.title;
isDialog = ( settings.role === "dialog" ||
toPage.jqmData( "role" ) === "dialog" ) &&
toPage.jqmData( "dialog" ) !== true;
// By default, we prevent changePage requests when the fromPage and toPage
// are the same element, but folks that generate content
// manually/dynamically and reuse pages want to be able to transition to
// the same page. To allow this, they will need to change the default
// value of allowSamePageTransition to true, *OR*, pass it in as an
// option when they manually call changePage(). It should be noted that
// our default transition animations assume that the formPage and toPage
// are different elements, so they may behave unexpectedly. It is up to
// the developer that turns on the allowSamePageTransitiona option to
// either turn off transition animations, or make sure that an appropriate
// animation transition is used.
if ( fromPage && fromPage[0] === toPage[0] &&
!settings.allowSamePageTransition ) {
isPageTransitioning = false;
this._triggerWithDeprecated( "transition", triggerData );
this._triggerWithDeprecated( "change", triggerData );
// Even if there is no page change to be done, we should keep the
// urlHistory in sync with the hash changes
if ( settings.fromHashChange ) {
$.mobile.navigate.history.direct({ url: url });
}
return;
}
// We need to make sure the page we are given has already been enhanced.
toPage.page({ role: settings.role });
// If the changePage request was sent from a hashChange event, check to
// see if the page is already within the urlHistory stack. If so, we'll
// assume the user hit the forward/back button and will try to match the
// transition accordingly.
if ( settings.fromHashChange ) {
historyDir = settings.direction === "back" ? -1 : 1;
}
// Kill the keyboard.
// XXX_jblas: We need to stop crawling the entire document to kill focus.
// Instead, we should be tracking focus with a delegate()
// handler so we already have the element in hand at this
// point.
// Wrap this in a try/catch block since IE9 throw "Unspecified error" if
// document.activeElement is undefined when we are in an IFrame.
try {
if ( document.activeElement &&
document.activeElement.nodeName.toLowerCase() !== "body" ) {
$( document.activeElement ).blur();
} else {
$( "input:focus, textarea:focus, select:focus" ).blur();
}
} catch( e ) {}
// Record whether we are at a place in history where a dialog used to be -
// if so, do not add a new history entry and do not change the hash either
alreadyThere = false;
// If we're displaying the page as a dialog, we don't want the url
// for the dialog content to be used in the hash. Instead, we want
// to append the dialogHashKey to the url of the current page.
if ( isDialog && active ) {
// on the initial page load active.url is undefined and in that case
// should be an empty string. Moving the undefined -> empty string back
// into urlHistory.addNew seemed imprudent given undefined better
// represents the url state
// If we are at a place in history that once belonged to a dialog, reuse
// this state without adding to urlHistory and without modifying the
// hash. However, if a dialog is already displayed at this point, and
// we're about to display another dialog, then we must add another hash
// and history entry on top so that one may navigate back to the
// original dialog
if ( active.url &&
active.url.indexOf( $.mobile.dialogHashKey ) > -1 &&
this.activePage &&
!this.activePage.hasClass( "ui-dialog" ) &&
$.mobile.navigate.history.activeIndex > 0 ) {
settings.changeHash = false;
alreadyThere = true;
}
// Normally, we tack on a dialog hash key, but if this is the location
// of a stale dialog, we reuse the URL from the entry
url = ( active.url || "" );
// account for absolute urls instead of just relative urls use as hashes
if ( !alreadyThere && url.indexOf("#") > -1 ) {
url += $.mobile.dialogHashKey;
} else {
url += "#" + $.mobile.dialogHashKey;
}
}
// if title element wasn't found, try the page div data attr too
// If this is a deep-link or a reload ( active === undefined ) then just
// use pageTitle
newPageTitle = ( !active ) ? pageTitle : toPage.jqmData( "title" ) ||
toPage.children( ":jqmData(role='header')" ).find( ".ui-title" ).text();
if ( !!newPageTitle && pageTitle === document.title ) {
pageTitle = newPageTitle;
}
if ( !toPage.jqmData( "title" ) ) {
toPage.jqmData( "title", pageTitle );
}
// Make sure we have a transition defined.
settings.transition = settings.transition ||
( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined ) ||
( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition );
//add page to history stack if it's not back or forward
if ( !historyDir && alreadyThere ) {
$.mobile.navigate.history.getActive().pageUrl = pageUrl;
}
// Set the location hash.
if ( url && !settings.fromHashChange ) {
// rebuilding the hash here since we loose it earlier on
// TODO preserve the originally passed in path
if ( !$.mobile.path.isPath( url ) && url.indexOf( "#" ) < 0 ) {
url = "#" + url;
}
// TODO the property names here are just silly
params = {
transition: settings.transition,
title: pageTitle,
pageUrl: pageUrl,
role: settings.role
};
if ( settings.changeHash !== false && $.mobile.hashListeningEnabled ) {
$.mobile.navigate( this.window[ 0 ].encodeURI( url ), params, true);
} else if ( toPage[ 0 ] !== $.mobile.firstPage[ 0 ] ) {
$.mobile.navigate.history.add( url, params );
}
}
//set page title
document.title = pageTitle;
//set "toPage" as activePage deprecated in 1.4 remove in 1.5
$.mobile.activePage = toPage;
//new way to handle activePage
this.activePage = toPage;
// If we're navigating back in the URL history, set reverse accordingly.
settings.reverse = settings.reverse || historyDir < 0;
cssTransitionDeferred = $.Deferred();
this._cssTransition(toPage, fromPage, {
transition: settings.transition,
reverse: settings.reverse,
deferred: cssTransitionDeferred
});
cssTransitionDeferred.done($.proxy(function( name, reverse, $to, $from, alreadyFocused ) {
$.mobile.removeActiveLinkClass();
//if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
if ( settings.duplicateCachedPage ) {
settings.duplicateCachedPage.remove();
}
// despite visibility: hidden addresses issue #2965
// https://github.com/jquery/jquery-mobile/issues/2965
if ( !alreadyFocused ) {
$.mobile.focusPage( toPage );
}
this._releaseTransitionLock();
this._triggerWithDeprecated( "transition", triggerData );
this._triggerWithDeprecated( "change", triggerData );
}, this));
},
// determine the current base url
_findBaseWithDefault: function() {
var closestBase = ( this.activePage &&
$.mobile.getClosestBaseUrl( this.activePage ) );
return closestBase || $.mobile.path.documentBase.hrefNoHash;
}
});
// The following handlers should be bound after mobileinit has been triggered
// the following deferred is resolved in the init file
$.mobile.navreadyDeferred = $.Deferred();
//these variables make all page containers use the same queue and only navigate one at a time
// queue to hold simultanious page transitions
var pageTransitionQueue = [],
// indicates whether or not page is in process of transitioning
isPageTransitioning = false;
})( jQuery );
(function( $, undefined ) {
// resolved on domready
var domreadyDeferred = $.Deferred(),
// resolved and nulled on window.load()
loadDeferred = $.Deferred(),
// function that resolves the above deferred
pageIsFullyLoaded = function() {
// Resolve and null the deferred
loadDeferred.resolve();
loadDeferred = null;
},
documentUrl = $.mobile.path.documentUrl,
// used to track last vclicked element to make sure its value is added to form data
$lastVClicked = null;
/* Event Bindings - hashchange, submit, and click */
function findClosestLink( ele ) {
while ( ele ) {
// Look for the closest element with a nodeName of "a".
// Note that we are checking if we have a valid nodeName
// before attempting to access it. This is because the
// node we get called with could have originated from within
// an embedded SVG document where some symbol instance elements
// don't have nodeName defined on them, or strings are of type
// SVGAnimatedString.
if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) {
break;
}
ele = ele.parentNode;
}
return ele;
}
$.mobile.loadPage = function( url, opts ) {
var container;
opts = opts || {};
container = ( opts.pageContainer || $.mobile.pageContainer );
// create the deferred that will be supplied to loadPage callers
// and resolved by the content widget's load method
opts.deferred = $.Deferred();
// Preferring to allow exceptions for uninitialized opts.pageContainer
// widgets so we know if we need to force init here for users
container.pagecontainer( "load", url, opts );
// provide the deferred
return opts.deferred.promise();
};
//define vars for interal use
/* internal utility functions */
// NOTE Issue #4950 Android phonegap doesn't navigate back properly
// when a full page refresh has taken place. It appears that hashchange
// and replacestate history alterations work fine but we need to support
// both forms of history traversal in our code that uses backward history
// movement
$.mobile.back = function() {
var nav = window.navigator;
// if the setting is on and the navigator object is
// available use the phonegap navigation capability
if ( this.phonegapNavigationEnabled &&
nav &&
nav.app &&
nav.app.backHistory ) {
nav.app.backHistory();
} else {
$.mobile.pageContainer.pagecontainer( "back" );
}
};
// Direct focus to the page title, or otherwise first focusable element
$.mobile.focusPage = function ( page ) {
var autofocus = page.find( "[autofocus]" ),
pageTitle = page.find( ".ui-title:eq(0)" );
if ( autofocus.length ) {
autofocus.focus();
return;
}
if ( pageTitle.length ) {
pageTitle.focus();
} else{
page.focus();
}
};
// No-op implementation of transition degradation
$.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) {
return transition;
};
// Exposed $.mobile methods
$.mobile.changePage = function( to, options ) {
$.mobile.pageContainer.pagecontainer( "change", to, options );
};
$.mobile.changePage.defaults = {
transition: undefined,
reverse: false,
changeHash: true,
fromHashChange: false,
role: undefined, // By default we rely on the role defined by the @data-role attribute.
duplicateCachedPage: undefined,
pageContainer: undefined,
showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage
dataUrl: undefined,
fromPage: undefined,
allowSamePageTransition: false
};
$.mobile._registerInternalEvents = function() {
var getAjaxFormData = function( $form, calculateOnly ) {
var url, ret = true, formData, vclickedName, method;
if ( !$.mobile.ajaxEnabled ||
// test that the form is, itself, ajax false
$form.is( ":jqmData(ajax='false')" ) ||
// test that $.mobile.ignoreContentEnabled is set and
// the form or one of it's parents is ajax=false
!$form.jqmHijackable().length ||
$form.attr( "target" ) ) {
return false;
}
url = ( $lastVClicked && $lastVClicked.attr( "formaction" ) ) ||
$form.attr( "action" );
method = ( $form.attr( "method" ) || "get" ).toLowerCase();
// If no action is specified, browsers default to using the
// URL of the document containing the form. Since we dynamically
// pull in pages from external documents, the form should submit
// to the URL for the source document of the page containing
// the form.
if ( !url ) {
// Get the @data-url for the page containing the form.
url = $.mobile.getClosestBaseUrl( $form );
// NOTE: If the method is "get", we need to strip off the query string
// because it will get replaced with the new form data. See issue #5710.
if ( method === "get" ) {
url = $.mobile.path.parseUrl( url ).hrefNoSearch;
}
if ( url === $.mobile.path.documentBase.hrefNoHash ) {
// The url we got back matches the document base,
// which means the page must be an internal/embedded page,
// so default to using the actual document url as a browser
// would.
url = documentUrl.hrefNoSearch;
}
}
url = $.mobile.path.makeUrlAbsolute( url, $.mobile.getClosestBaseUrl( $form ) );
if ( ( $.mobile.path.isExternal( url ) && !$.mobile.path.isPermittedCrossDomainRequest( documentUrl, url ) ) ) {
return false;
}
if ( !calculateOnly ) {
formData = $form.serializeArray();
if ( $lastVClicked && $lastVClicked[ 0 ].form === $form[ 0 ] ) {
vclickedName = $lastVClicked.attr( "name" );
if ( vclickedName ) {
// Make sure the last clicked element is included in the form
$.each( formData, function( key, value ) {
if ( value.name === vclickedName ) {
// Unset vclickedName - we've found it in the serialized data already
vclickedName = "";
return false;
}
});
if ( vclickedName ) {
formData.push( { name: vclickedName, value: $lastVClicked.attr( "value" ) } );
}
}
}
ret = {
url: url,
options: {
type: method,
data: $.param( formData ),
transition: $form.jqmData( "transition" ),
reverse: $form.jqmData( "direction" ) === "reverse",
reloadPage: true
}
};
}
return ret;
};
//bind to form submit events, handle with Ajax
$.mobile.document.delegate( "form", "submit", function( event ) {
var formData;
if ( !event.isDefaultPrevented() ) {
formData = getAjaxFormData( $( this ) );
if ( formData ) {
$.mobile.changePage( formData.url, formData.options );
event.preventDefault();
}
}
});
//add active state on vclick
$.mobile.document.bind( "vclick", function( event ) {
var $btn, btnEls, target = event.target, needClosest = false;
// if this isn't a left click we don't care. Its important to note
// that when the virtual event is generated it will create the which attr
if ( event.which > 1 || !$.mobile.linkBindingEnabled ) {
return;
}
// Record that this element was clicked, in case we need it for correct
// form submission during the "submit" handler above
$lastVClicked = $( target );
// Try to find a target element to which the active class will be applied
if ( $.data( target, "mobile-button" ) ) {
// If the form will not be submitted via AJAX, do not add active class
if ( !getAjaxFormData( $( target ).closest( "form" ), true ) ) {
return;
}
// We will apply the active state to this button widget - the parent
// of the input that was clicked will have the associated data
if ( target.parentNode ) {
target = target.parentNode;
}
} else {
target = findClosestLink( target );
if ( !( target && $.mobile.path.parseUrl( target.getAttribute( "href" ) || "#" ).hash !== "#" ) ) {
return;
}
// TODO teach $.mobile.hijackable to operate on raw dom elements so the
// link wrapping can be avoided
if ( !$( target ).jqmHijackable().length ) {
return;
}
}
// Avoid calling .closest by using the data set during .buttonMarkup()
// List items have the button data in the parent of the element clicked
if ( !!~target.className.indexOf( "ui-link-inherit" ) ) {
if ( target.parentNode ) {
btnEls = $.data( target.parentNode, "buttonElements" );
}
// Otherwise, look for the data on the target itself
} else {
btnEls = $.data( target, "buttonElements" );
}
// If found, grab the button's outer element
if ( btnEls ) {
target = btnEls.outer;
} else {
needClosest = true;
}
$btn = $( target );
// If the outer element wasn't found by the our heuristics, use .closest()
if ( needClosest ) {
$btn = $btn.closest( ".ui-btn" );
}
if ( $btn.length > 0 &&
!( $btn.hasClass( "ui-state-disabled" ||
// DEPRECATED as of 1.4.0 - remove after 1.4.0 release
// only ui-state-disabled should be present thereafter
$btn.hasClass( "ui-disabled" ) ) ) ) {
$.mobile.removeActiveLinkClass( true );
$.mobile.activeClickedLink = $btn;
$.mobile.activeClickedLink.addClass( $.mobile.activeBtnClass );
}
});
// click routing - direct to HTTP or Ajax, accordingly
$.mobile.document.bind( "click", function( event ) {
if ( !$.mobile.linkBindingEnabled || event.isDefaultPrevented() ) {
return;
}
var link = findClosestLink( event.target ),
$link = $( link ),
//remove active link class if external (then it won't be there if you come back)
httpCleanup = function() {
window.setTimeout(function() { $.mobile.removeActiveLinkClass( true ); }, 200 );
},
baseUrl, href,
useDefaultUrlHandling, isExternal,
transition, reverse, role;
// If a button was clicked, clean up the active class added by vclick above
if ( $.mobile.activeClickedLink &&
$.mobile.activeClickedLink[ 0 ] === event.target.parentNode ) {
httpCleanup();
}
// If there is no link associated with the click or its not a left
// click we want to ignore the click
// TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping
// can be avoided
if ( !link || event.which > 1 || !$link.jqmHijackable().length ) {
return;
}
//if there's a data-rel=back attr, go back in history
if ( $link.is( ":jqmData(rel='back')" ) ) {
$.mobile.back();
return false;
}
baseUrl = $.mobile.getClosestBaseUrl( $link );
//get href, if defined, otherwise default to empty hash
href = $.mobile.path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
//if ajax is disabled, exit early
if ( !$.mobile.ajaxEnabled && !$.mobile.path.isEmbeddedPage( href ) ) {
httpCleanup();
//use default click handling
return;
}
// XXX_jblas: Ideally links to application pages should be specified as
// an url to the application document with a hash that is either
// the site relative path or id to the page. But some of the
// internal code that dynamically generates sub-pages for nested
// lists and select dialogs, just write a hash in the link they
// create. This means the actual URL path is based on whatever
// the current value of the base tag is at the time this code
// is called.
if ( href.search( "#" ) !== -1 &&
!( $.mobile.path.isExternal( href ) && $.mobile.path.isAbsoluteUrl( href ) ) ) {
href = href.replace( /[^#]*#/, "" );
if ( !href ) {
//link was an empty hash meant purely
//for interaction, so we ignore it.
event.preventDefault();
return;
} else if ( $.mobile.path.isPath( href ) ) {
//we have apath so make it the href we want to load.
href = $.mobile.path.makeUrlAbsolute( href, baseUrl );
} else {
//we have a simple id so use the documentUrl as its base.
href = $.mobile.path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash );
}
}
// Should we handle this link, or let the browser deal with it?
useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" );
// Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
// requests if the document doing the request was loaded via the file:// protocol.
// This is usually to allow the application to "phone home" and fetch app specific
// data. We normally let the browser handle external/cross-domain urls, but if the
// allowCrossDomainPages option is true, we will allow cross-domain http/https
// requests to go through our page loading logic.
//check for protocol or rel and its not an embedded page
//TODO overlap in logic from isExternal, rel=external check should be
// moved into more comprehensive isExternalLink
isExternal = useDefaultUrlHandling || ( $.mobile.path.isExternal( href ) && !$.mobile.path.isPermittedCrossDomainRequest( documentUrl, href ) );
if ( isExternal ) {
httpCleanup();
//use default click handling
return;
}
//use ajax
transition = $link.jqmData( "transition" );
reverse = $link.jqmData( "direction" ) === "reverse" ||
// deprecated - remove by 1.0
$link.jqmData( "back" );
//this may need to be more specific as we use data-rel more
role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
$.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, link: $link } );
event.preventDefault();
});
//prefetch pages when anchors with data-prefetch are encountered
$.mobile.document.delegate( ".ui-page", "pageshow.prefetch", function() {
var urls = [];
$( this ).find( "a:jqmData(prefetch)" ).each(function() {
var $link = $( this ),
url = $link.attr( "href" );
if ( url && $.inArray( url, urls ) === -1 ) {
urls.push( url );
$.mobile.loadPage( url, { role: $link.attr( "data-" + $.mobile.ns + "rel" ),prefetch: true } );
}
});
});
// TODO ensure that the navigate binding in the content widget happens at the right time
$.mobile.pageContainer.pagecontainer();
//set page min-heights to be device specific
$.mobile.document.bind( "pageshow", function() {
// We need to wait for window.load to make sure that styles have already been rendered,
// otherwise heights of external toolbars will have the wrong value
if ( loadDeferred ) {
loadDeferred.done( $.mobile.resetActivePageHeight );
} else {
$.mobile.resetActivePageHeight();
}
});
$.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight );
};//navreadyDeferred done callback
$( function() { domreadyDeferred.resolve(); } );
// Account for the possibility that the load event has already fired
if ( document.readyState === "complete" ) {
pageIsFullyLoaded();
} else {
$.mobile.window.load( pageIsFullyLoaded );
}
$.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() { $.mobile._registerInternalEvents(); } );
})( jQuery );
(function( $, window, undefined ) {
// TODO remove direct references to $.mobile and properties, we should
// favor injection with params to the constructor
$.mobile.Transition = function() {
this.init.apply( this, arguments );
};
$.extend($.mobile.Transition.prototype, {
toPreClass: " ui-page-pre-in",
init: function( name, reverse, $to, $from ) {
$.extend(this, {
name: name,
reverse: reverse,
$to: $to,
$from: $from,
deferred: new $.Deferred()
});
},
cleanFrom: function() {
this.$from
.removeClass( $.mobile.activePageClass + " out in reverse " + this.name )
.height( "" );
},
// NOTE overridden by child object prototypes, noop'd here as defaults
beforeDoneIn: function() {},
beforeDoneOut: function() {},
beforeStartOut: function() {},
doneIn: function() {
this.beforeDoneIn();
this.$to.removeClass( "out in reverse " + this.name ).height( "" );
this.toggleViewportClass();
// In some browsers (iOS5), 3D transitions block the ability to scroll to the desired location during transition
// This ensures we jump to that spot after the fact, if we aren't there already.
if ( $.mobile.window.scrollTop() !== this.toScroll ) {
this.scrollPage();
}
if ( !this.sequential ) {
this.$to.addClass( $.mobile.activePageClass );
}
this.deferred.resolve( this.name, this.reverse, this.$to, this.$from, true );
},
doneOut: function( screenHeight, reverseClass, none, preventFocus ) {
this.beforeDoneOut();
this.startIn( screenHeight, reverseClass, none, preventFocus );
},
hideIn: function( callback ) {
// Prevent flickering in phonegap container: see comments at #4024 regarding iOS
this.$to.css( "z-index", -10 );
callback.call( this );
this.$to.css( "z-index", "" );
},
scrollPage: function() {
// By using scrollTo instead of silentScroll, we can keep things better in order
// Just to be precautios, disable scrollstart listening like silentScroll would
$.event.special.scrollstart.enabled = false;
//if we are hiding the url bar or the page was previously scrolled scroll to hide or return to position
if ( $.mobile.hideUrlBar || this.toScroll !== $.mobile.defaultHomeScroll ) {
window.scrollTo( 0, this.toScroll );
}
// reenable scrollstart listening like silentScroll would
setTimeout( function() {
$.event.special.scrollstart.enabled = true;
}, 150 );
},
startIn: function( screenHeight, reverseClass, none, preventFocus ) {
this.hideIn(function() {
this.$to.addClass( $.mobile.activePageClass + this.toPreClass );
// Send focus to page as it is now display: block
if ( !preventFocus ) {
$.mobile.focusPage( this.$to );
}
// Set to page height
this.$to.height( screenHeight + this.toScroll );
if ( !none ) {
this.scrollPage();
}
});
this.$to
.removeClass( this.toPreClass )
.addClass( this.name + " in " + reverseClass );
if ( !none ) {
this.$to.animationComplete( $.proxy(function() {
this.doneIn();
}, this ));
} else {
this.doneIn();
}
},
startOut: function( screenHeight, reverseClass, none ) {
this.beforeStartOut( screenHeight, reverseClass, none );
// Set the from page's height and start it transitioning out
// Note: setting an explicit height helps eliminate tiling in the transitions
this.$from
.height( screenHeight + $.mobile.window.scrollTop() )
.addClass( this.name + " out" + reverseClass );
},
toggleViewportClass: function() {
$.mobile.pageContainer.toggleClass( "ui-mobile-viewport-transitioning viewport-" + this.name );
},
transition: function() {
// NOTE many of these could be calculated/recorded in the constructor, it's my
// opinion that binding them as late as possible has value with regards to
// better transitions with fewer bugs. Ie, it's not guaranteed that the
// object will be created and transition will be run immediately after as
// it is today. So we wait until transition is invoked to gather the following
var none,
reverseClass = this.reverse ? " reverse" : "",
screenHeight = $.mobile.getScreenHeight(),
maxTransitionOverride = $.mobile.maxTransitionWidth !== false &&
$.mobile.window.width() > $.mobile.maxTransitionWidth;
this.toScroll = $.mobile.navigate.history.getActive().lastScroll || $.mobile.defaultHomeScroll;
none = !$.support.cssTransitions || !$.support.cssAnimations ||
maxTransitionOverride || !this.name || this.name === "none" ||
Math.max( $.mobile.window.scrollTop(), this.toScroll ) >
$.mobile.getMaxScrollForTransition();
this.toggleViewportClass();
if ( this.$from && !none ) {
this.startOut( screenHeight, reverseClass, none );
} else {
this.doneOut( screenHeight, reverseClass, none, true );
}
return this.deferred.promise();
}
});
})( jQuery, this );
(function( $ ) {
$.mobile.SerialTransition = function() {
this.init.apply(this, arguments);
};
$.extend($.mobile.SerialTransition.prototype, $.mobile.Transition.prototype, {
sequential: true,
beforeDoneOut: function() {
if ( this.$from ) {
this.cleanFrom();
}
},
beforeStartOut: function( screenHeight, reverseClass, none ) {
this.$from.animationComplete($.proxy(function() {
this.doneOut( screenHeight, reverseClass, none );
}, this ));
}
});
})( jQuery );
(function( $ ) {
$.mobile.ConcurrentTransition = function() {
this.init.apply(this, arguments);
};
$.extend($.mobile.ConcurrentTransition.prototype, $.mobile.Transition.prototype, {
sequential: false,
beforeDoneIn: function() {
if ( this.$from ) {
this.cleanFrom();
}
},
beforeStartOut: function( screenHeight, reverseClass, none ) {
this.doneOut( screenHeight, reverseClass, none );
}
});
})( jQuery );
(function( $ ) {
// generate the handlers from the above
var defaultGetMaxScrollForTransition = function() {
return $.mobile.getScreenHeight() * 3;
};
//transition handler dictionary for 3rd party transitions
$.mobile.transitionHandlers = {
"sequential": $.mobile.SerialTransition,
"simultaneous": $.mobile.ConcurrentTransition
};
// Make our transition handler the public default.
$.mobile.defaultTransitionHandler = $.mobile.transitionHandlers.sequential;
$.mobile.transitionFallbacks = {};
// If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified
$.mobile._maybeDegradeTransition = function( transition ) {
if ( transition && !$.support.cssTransform3d && $.mobile.transitionFallbacks[ transition ] ) {
transition = $.mobile.transitionFallbacks[ transition ];
}
return transition;
};
// Set the getMaxScrollForTransition to default if no implementation was set by user
$.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition;
})( jQuery );
/*
* fallback transition for flip in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
$.mobile.transitionFallbacks.flip = "fade";
})( jQuery, this );
/*
* fallback transition for flow in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
$.mobile.transitionFallbacks.flow = "fade";
})( jQuery, this );
/*
* fallback transition for pop in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
$.mobile.transitionFallbacks.pop = "fade";
})( jQuery, this );
/*
* fallback transition for slide in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
// Use the simultaneous transitions handler for slide transitions
$.mobile.transitionHandlers.slide = $.mobile.transitionHandlers.simultaneous;
// Set the slide transitions's fallback to "fade"
$.mobile.transitionFallbacks.slide = "fade";
})( jQuery, this );
/*
* fallback transition for slidedown in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
$.mobile.transitionFallbacks.slidedown = "fade";
})( jQuery, this );
/*
* fallback transition for slidefade in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
// Set the slide transitions's fallback to "fade"
$.mobile.transitionFallbacks.slidefade = "fade";
})( jQuery, this );
/*
* fallback transition for slideup in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
$.mobile.transitionFallbacks.slideup = "fade";
})( jQuery, this );
/*
* fallback transition for turn in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
$.mobile.transitionFallbacks.turn = "fade";
})( jQuery, this );
(function( $, undefined ) {
$.mobile.degradeInputs = {
color: false,
date: false,
datetime: false,
"datetime-local": false,
email: false,
month: false,
number: false,
range: "number",
search: "text",
tel: false,
time: false,
url: false,
week: false
};
// Backcompat remove in 1.5
$.mobile.page.prototype.options.degradeInputs = $.mobile.degradeInputs;
// Auto self-init widgets
$.mobile.degradeInputsWithin = function( target ) {
target = $( target );
// Degrade inputs to avoid poorly implemented native functionality
target.find( "input" ).not( $.mobile.page.prototype.keepNativeSelector() ).each(function() {
var element = $( this ),
type = this.getAttribute( "type" ),
optType = $.mobile.degradeInputs[ type ] || "text",
html, hasType, findstr, repstr;
if ( $.mobile.degradeInputs[ type ] ) {
html = $( "
" ).html( element.clone() ).html();
// In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead
hasType = html.indexOf( " type=" ) > -1;
findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/;
repstr = " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\"" + ( hasType ? "" : ">" );
element.replaceWith( html.replace( findstr, repstr ) );
}
});
};
})( jQuery );
(function( $, window, undefined ) {
$.widget( "mobile.page", $.mobile.page, {
options: {
// Accepts left, right and none
closeBtn: "left",
closeBtnText: "Close",
overlayTheme: "a",
corners: true,
dialog: false
},
_create: function() {
this._super();
if ( this.options.dialog ) {
$.extend( this, {
_inner: this.element.children(),
_headerCloseButton: null
});
if ( !this.options.enhanced ) {
this._setCloseBtn( this.options.closeBtn );
}
}
},
_enhance: function() {
this._super();
// Class the markup for dialog styling and wrap interior
if ( this.options.dialog ) {
this.element.addClass( "ui-dialog" )
.wrapInner( $( "", {
// ARIA role
"role" : "dialog",
"class" : "ui-dialog-contain ui-overlay-shadow" +
( this.options.corners ? " ui-corner-all" : "" )
}));
}
},
_setOptions: function( options ) {
var closeButtonLocation, closeButtonText,
currentOpts = this.options;
if ( options.corners !== undefined ) {
this._inner.toggleClass( "ui-corner-all", !!options.corners );
}
if ( options.overlayTheme !== undefined ) {
if ( $.mobile.activePage[ 0 ] === this.element[ 0 ] ) {
currentOpts.overlayTheme = options.overlayTheme;
this._handlePageBeforeShow();
}
}
if ( options.closeBtnText !== undefined ) {
closeButtonLocation = currentOpts.closeBtn;
closeButtonText = options.closeBtnText;
}
if ( options.closeBtn !== undefined ) {
closeButtonLocation = options.closeBtn;
}
if ( closeButtonLocation ) {
this._setCloseBtn( closeButtonLocation, closeButtonText );
}
this._super( options );
},
_handlePageBeforeShow: function () {
if ( this.options.overlayTheme && this.options.dialog ) {
this.removeContainerBackground();
this.setContainerBackground( this.options.overlayTheme );
} else {
this._super();
}
},
_setCloseBtn: function( location, text ) {
var dst,
btn = this._headerCloseButton;
// Sanitize value
location = "left" === location ? "left" : "right" === location ? "right" : "none";
if ( "none" === location ) {
if ( btn ) {
btn.remove();
btn = null;
}
} else if ( btn ) {
btn.removeClass( "ui-btn-left ui-btn-right" ).addClass( "ui-btn-" + location );
if ( text ) {
btn.text( text );
}
} else {
dst = this._inner.find( ":jqmData(role='header')" ).first();
btn = $( "", {
"href": "#",
"class": "ui-btn ui-corner-all ui-icon-delete ui-btn-icon-notext ui-btn-" + location
})
.attr( "data-" + $.mobile.ns + "rel", "back" )
.text( text || this.options.closeBtnText || "" )
.prependTo( dst );
}
this._headerCloseButton = btn;
}
});
})( jQuery, this );
(function( $, window, undefined ) {
$.widget( "mobile.dialog", {
options: {
// Accepts left, right and none
closeBtn: "left",
closeBtnText: "Close",
overlayTheme: "a",
corners: true
},
// Override the theme set by the page plugin on pageshow
_handlePageBeforeShow: function() {
this._isCloseable = true;
if ( this.options.overlayTheme ) {
this.element
.page( "removeContainerBackground" )
.page( "setContainerBackground", this.options.overlayTheme );
}
},
_handlePageBeforeHide: function() {
this._isCloseable = false;
},
// click and submit events:
// - clicks and submits should use the closing transition that the dialog
// opened with unless a data-transition is specified on the link/form
// - if the click was on the close button, or the link has a data-rel="back"
// it'll go back in history naturally
_handleVClickSubmit: function( event ) {
var attrs,
$target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" );
if ( $target.length && !$target.jqmData( "transition" ) ) {
attrs = {};
attrs[ "data-" + $.mobile.ns + "transition" ] =
( $.mobile.navigate.history.getActive() || {} )[ "transition" ] ||
$.mobile.defaultDialogTransition;
attrs[ "data-" + $.mobile.ns + "direction" ] = "reverse";
$target.attr( attrs );
}
},
_create: function() {
var elem = this.element,
opts = this.options;
// Class the markup for dialog styling and wrap interior
elem.addClass( "ui-dialog" )
.wrapInner( $( "", {
// ARIA role
"role" : "dialog",
"class" : "ui-dialog-contain ui-overlay-shadow" +
( !!opts.corners ? " ui-corner-all" : "" )
}));
$.extend( this, {
_isCloseable: false,
_inner: elem.children(),
_headerCloseButton: null
});
this._on( elem, {
vclick: "_handleVClickSubmit",
submit: "_handleVClickSubmit",
pagebeforeshow: "_handlePageBeforeShow",
pagebeforehide: "_handlePageBeforeHide"
});
this._setCloseBtn( opts.closeBtn );
},
_setOptions: function( options ) {
var closeButtonLocation, closeButtonText,
currentOpts = this.options;
if ( options.corners !== undefined ) {
this._inner.toggleClass( "ui-corner-all", !!options.corners );
}
if ( options.overlayTheme !== undefined ) {
if ( $.mobile.activePage[ 0 ] === this.element[ 0 ] ) {
currentOpts.overlayTheme = options.overlayTheme;
this._handlePageBeforeShow();
}
}
if ( options.closeBtnText !== undefined ) {
closeButtonLocation = currentOpts.closeBtn;
closeButtonText = options.closeBtnText;
}
if ( options.closeBtn !== undefined ) {
closeButtonLocation = options.closeBtn;
}
if ( closeButtonLocation ) {
this._setCloseBtn( closeButtonLocation, closeButtonText );
}
this._super( options );
},
_setCloseBtn: function( location, text ) {
var dst,
btn = this._headerCloseButton;
// Sanitize value
location = "left" === location ? "left" : "right" === location ? "right" : "none";
if ( "none" === location ) {
if ( btn ) {
btn.remove();
btn = null;
}
} else if ( btn ) {
btn.removeClass( "ui-btn-left ui-btn-right" ).addClass( "ui-btn-" + location );
if ( text ) {
btn.text( text );
}
} else {
dst = this._inner.find( ":jqmData(role='header')" ).first();
btn = $( "", {
"role": "button",
"href": "#",
"class": "ui-btn ui-corner-all ui-icon-delete ui-btn-icon-notext ui-btn-" + location
})
.text( text || this.options.closeBtnText || "" )
.prependTo( dst );
this._on( btn, { click: "close" } );
}
this._headerCloseButton = btn;
},
// Close method goes back in history
close: function() {
var hist = $.mobile.navigate.history;
if ( this._isCloseable ) {
this._isCloseable = false;
// If the hash listening is enabled and there is at least one preceding history
// entry it's ok to go back. Initial pages with the dialog hash state are an example
// where the stack check is necessary
if ( $.mobile.hashListeningEnabled && hist.activeIndex > 0 ) {
$.mobile.back();
} else {
$.mobile.pageContainer.pagecontainer( "back" );
}
}
}
});
})( jQuery, this );
(function( $, undefined ) {
var rInitialLetter = /([A-Z])/g,
// Construct iconpos class from iconpos value
iconposClass = function( iconpos ) {
return ( "ui-btn-icon-" + ( iconpos === null ? "left" : iconpos ) );
};
$.widget( "mobile.collapsible", {
options: {
enhanced: false,
expandCueText: null,
collapseCueText: null,
collapsed: true,
heading: "h1,h2,h3,h4,h5,h6,legend",
collapsedIcon: null,
expandedIcon: null,
iconpos: null,
theme: null,
contentTheme: null,
inset: null,
corners: null,
mini: null
},
_create: function() {
var elem = this.element,
ui = {
accordion: elem
.closest( ":jqmData(role='collapsible-set')," +
":jqmData(role='collapsibleset')" +
( $.mobile.collapsibleset ? ", :mobile-collapsibleset" :
"" ) )
.addClass( "ui-collapsible-set" )
};
this._ui = ui;
this._renderedOptions = this._getOptions( this.options );
if ( this.options.enhanced ) {
ui.heading = this.element.children( ".ui-collapsible-heading" );
ui.content = ui.heading.next();
ui.anchor = ui.heading.children();
ui.status = ui.anchor.children( ".ui-collapsible-heading-status" );
} else {
this._enhance( elem, ui );
}
this._on( ui.heading, {
"tap": function() {
ui.heading.find( "a" ).first().addClass( $.mobile.activeBtnClass );
},
"click": function( event ) {
this._handleExpandCollapse( !ui.heading.hasClass( "ui-collapsible-heading-collapsed" ) );
event.preventDefault();
event.stopPropagation();
}
});
},
// Adjust the keys inside options for inherited values
_getOptions: function( options ) {
var key,
accordion = this._ui.accordion,
accordionWidget = this._ui.accordionWidget;
// Copy options
options = $.extend( {}, options );
if ( accordion.length && !accordionWidget ) {
this._ui.accordionWidget =
accordionWidget = accordion.data( "mobile-collapsibleset" );
}
for ( key in options ) {
// Retrieve the option value first from the options object passed in and, if
// null, from the parent accordion or, if that's null too, or if there's no
// parent accordion, then from the defaults.
options[ key ] =
( options[ key ] != null ) ? options[ key ] :
( accordionWidget ) ? accordionWidget.options[ key ] :
accordion.length ? $.mobile.getAttribute( accordion[ 0 ],
key.replace( rInitialLetter, "-$1" ).toLowerCase() ):
null;
if ( null == options[ key ] ) {
options[ key ] = $.mobile.collapsible.defaults[ key ];
}
}
return options;
},
_themeClassFromOption: function( prefix, value ) {
return ( value ? ( value === "none" ? "" : prefix + value ) : "" );
},
_enhance: function( elem, ui ) {
var iconclass,
opts = this._renderedOptions,
contentThemeClass = this._themeClassFromOption( "ui-body-", opts.contentTheme );
elem.addClass( "ui-collapsible " +
( opts.inset ? "ui-collapsible-inset " : "" ) +
( opts.inset && opts.corners ? "ui-corner-all " : "" ) +
( contentThemeClass ? "ui-collapsible-themed-content " : "" ) );
ui.originalHeading = elem.children( this.options.heading ).first(),
ui.content = elem
.wrapInner( "" )
.children( ".ui-collapsible-content" ),
ui.heading = ui.originalHeading;
// Replace collapsibleHeading if it's a legend
if ( ui.heading.is( "legend" ) ) {
ui.heading = $( "
"+ ui.heading.html() +"
" );
ui.placeholder = $( "" ).insertBefore( ui.originalHeading );
ui.originalHeading.remove();
}
iconclass = ( opts.collapsed ? ( opts.collapsedIcon ? "ui-icon-" + opts.collapsedIcon : "" ):
( opts.expandedIcon ? "ui-icon-" + opts.expandedIcon : "" ) );
ui.status = $( "" );
ui.anchor = ui.heading
.detach()
//modify markup & attributes
.addClass( "ui-collapsible-heading" )
.append( ui.status )
.wrapInner( "" )
.find( "a" )
.first()
.addClass( "ui-btn " +
( iconclass ? iconclass + " " : "" ) +
( iconclass ? iconposClass( opts.iconpos ) +
" " : "" ) +
this._themeClassFromOption( "ui-btn-", opts.theme ) + " " +
( opts.mini ? "ui-mini " : "" ) );
//drop heading in before content
ui.heading.insertBefore( ui.content );
this._handleExpandCollapse( this.options.collapsed );
return ui;
},
refresh: function() {
this._applyOptions( this.options );
this._renderedOptions = this._getOptions( this.options );
},
_applyOptions: function( options ) {
var isCollapsed, newTheme, oldTheme, hasCorners, hasIcon,
elem = this.element,
currentOpts = this._renderedOptions,
ui = this._ui,
anchor = ui.anchor,
status = ui.status,
opts = this._getOptions( options );
// First and foremost we need to make sure the collapsible is in the proper
// state, in case somebody decided to change the collapsed option at the
// same time as another option
if ( options.collapsed !== undefined ) {
this._handleExpandCollapse( options.collapsed );
}
isCollapsed = elem.hasClass( "ui-collapsible-collapsed" );
// We only need to apply the cue text for the current state right away.
// The cue text for the alternate state will be stored in the options
// and applied the next time the collapsible's state is toggled
if ( isCollapsed ) {
if ( opts.expandCueText !== undefined ) {
status.text( opts.expandCueText );
}
} else {
if ( opts.collapseCueText !== undefined ) {
status.text( opts.collapseCueText );
}
}
// Update icon
// Is it supposed to have an icon?
hasIcon =
// If the collapsedIcon is being set, consult that
( opts.collapsedIcon !== undefined ? opts.collapsedIcon !== false :
// Otherwise consult the existing option value
currentOpts.collapsedIcon !== false );
// If any icon-related options have changed, make sure the new icon
// state is reflected by first removing all icon-related classes
// reflecting the current state and then adding all icon-related
// classes for the new state
if ( !( opts.iconpos === undefined &&
opts.collapsedIcon === undefined &&
opts.expandedIcon === undefined ) ) {
// Remove all current icon-related classes
anchor.removeClass( [ iconposClass( currentOpts.iconpos ) ]
.concat( ( currentOpts.expandedIcon ?
[ "ui-icon-" + currentOpts.expandedIcon ] : [] ) )
.concat( ( currentOpts.collapsedIcon ?
[ "ui-icon-" + currentOpts.collapsedIcon ] : [] ) )
.join( " " ) );
// Add new classes if an icon is supposed to be present
if ( hasIcon ) {
anchor.addClass(
[ iconposClass( opts.iconpos !== undefined ?
opts.iconpos : currentOpts.iconpos ) ]
.concat( isCollapsed ?
[ "ui-icon-" + ( opts.collapsedIcon !== undefined ?
opts.collapsedIcon :
currentOpts.collapsedIcon ) ] :
[ "ui-icon-" + ( opts.expandedIcon !== undefined ?
opts.expandedIcon :
currentOpts.expandedIcon ) ] )
.join( " " ) );
}
}
if ( opts.theme !== undefined ) {
oldTheme = this._themeClassFromOption( "ui-btn-", currentOpts.theme );
newTheme = this._themeClassFromOption( "ui-btn-", opts.theme );
anchor.removeClass( oldTheme ).addClass( newTheme );
}
if ( opts.contentTheme !== undefined ) {
oldTheme = this._themeClassFromOption( "ui-body-",
currentOpts.contentTheme );
newTheme = this._themeClassFromOption( "ui-body-",
opts.contentTheme );
ui.content.removeClass( oldTheme ).addClass( newTheme );
}
if ( opts.inset !== undefined ) {
elem.toggleClass( "ui-collapsible-inset", opts.inset );
hasCorners = !!( opts.inset && ( opts.corners || currentOpts.corners ) );
}
if ( opts.corners !== undefined ) {
hasCorners = !!( opts.corners && ( opts.inset || currentOpts.inset ) );
}
if ( hasCorners !== undefined ) {
elem.toggleClass( "ui-corner-all", hasCorners );
}
if ( opts.mini !== undefined ) {
anchor.toggleClass( "ui-mini", opts.mini );
}
},
_setOptions: function( options ) {
this._applyOptions( options );
this._super( options );
this._renderedOptions = this._getOptions( this.options );
},
_handleExpandCollapse: function( isCollapse ) {
var opts = this._renderedOptions,
ui = this._ui;
ui.status.text( isCollapse ? opts.expandCueText : opts.collapseCueText );
ui.heading
.toggleClass( "ui-collapsible-heading-collapsed", isCollapse )
.find( "a" ).first()
.toggleClass( "ui-icon-" + opts.expandedIcon, !isCollapse )
// logic or cause same icon for expanded/collapsed state would remove the ui-icon-class
.toggleClass( "ui-icon-" + opts.collapsedIcon, ( isCollapse || opts.expandedIcon === opts.collapsedIcon ) )
.removeClass( $.mobile.activeBtnClass );
this.element.toggleClass( "ui-collapsible-collapsed", isCollapse );
ui.content
.toggleClass( "ui-collapsible-content-collapsed", isCollapse )
.attr( "aria-hidden", isCollapse )
.trigger( "updatelayout" );
this.options.collapsed = isCollapse;
this._trigger( isCollapse ? "collapse" : "expand" );
},
expand: function() {
this._handleExpandCollapse( false );
},
collapse: function() {
this._handleExpandCollapse( true );
},
_destroy: function() {
var ui = this._ui,
opts = this.options;
if ( opts.enhanced ) {
return;
}
if ( ui.placeholder ) {
ui.originalHeading.insertBefore( ui.placeholder );
ui.placeholder.remove();
ui.heading.remove();
} else {
ui.status.remove();
ui.heading
.removeClass( "ui-collapsible-heading ui-collapsible-heading-collapsed" )
.children()
.contents()
.unwrap();
}
ui.anchor.contents().unwrap();
ui.content.contents().unwrap();
this.element
.removeClass( "ui-collapsible ui-collapsible-collapsed " +
"ui-collapsible-themed-content ui-collapsible-inset ui-corner-all" );
}
});
// Defaults to be used by all instances of collapsible if per-instance values
// are unset or if nothing is specified by way of inheritance from an accordion.
// Note that this hash does not contain options "collapsed" or "heading",
// because those are not inheritable.
$.mobile.collapsible.defaults = {
expandCueText: " click to expand contents",
collapseCueText: " click to collapse contents",
collapsedIcon: "plus",
contentTheme: "inherit",
expandedIcon: "minus",
iconpos: "left",
inset: true,
corners: true,
theme: "inherit",
mini: false
};
})( jQuery );
(function( $, undefined ) {
var uiScreenHiddenRegex = /\bui-screen-hidden\b/;
function noHiddenClass( elements ) {
var index,
length = elements.length,
result = [];
for ( index = 0; index < length; index++ ) {
if ( !elements[ index ].className.match( uiScreenHiddenRegex ) ) {
result.push( elements[ index ] );
}
}
return $( result );
}
$.mobile.behaviors.addFirstLastClasses = {
_getVisibles: function( $els, create ) {
var visibles;
if ( create ) {
visibles = noHiddenClass( $els );
} else {
visibles = $els.filter( ":visible" );
if ( visibles.length === 0 ) {
visibles = noHiddenClass( $els );
}
}
return visibles;
},
_addFirstLastClasses: function( $els, $visibles, create ) {
$els.removeClass( "ui-first-child ui-last-child" );
$visibles.eq( 0 ).addClass( "ui-first-child" ).end().last().addClass( "ui-last-child" );
if ( !create ) {
this.element.trigger( "updatelayout" );
}
},
_removeFirstLastClasses: function( $els ) {
$els.removeClass( "ui-first-child ui-last-child" );
}
};
})( jQuery );
(function( $, undefined ) {
var childCollapsiblesSelector = ":mobile-collapsible, " + $.mobile.collapsible.initSelector;
$.widget( "mobile.collapsibleset", $.extend( {
// The initSelector is deprecated as of 1.4.0. In 1.5.0 we will use
// :jqmData(role='collapsibleset') which will allow us to get rid of the line
// below altogether, because the autoinit will generate such an initSelector
initSelector: ":jqmData(role='collapsible-set'),:jqmData(role='collapsibleset')",
options: $.extend( {
enhanced: false
}, $.mobile.collapsible.defaults ),
_handleCollapsibleExpand: function( event ) {
var closestCollapsible = $( event.target ).closest( ".ui-collapsible" );
if ( closestCollapsible.parent().is( ":mobile-collapsibleset, :jqmData(role='collapsible-set')" ) ) {
closestCollapsible
.siblings( ".ui-collapsible:not(.ui-collapsible-collapsed)" )
.collapsible( "collapse" );
}
},
_create: function() {
var elem = this.element,
opts = this.options;
$.extend( this, {
_classes: ""
});
if ( !opts.enhanced ) {
elem.addClass( "ui-collapsible-set " +
this._themeClassFromOption( "ui-group-theme-", opts.theme ) + " " +
( opts.corners && opts.inset ? "ui-corner-all " : "" ) );
this.element.find( $.mobile.collapsible.initSelector ).collapsible();
}
this._on( elem, { collapsibleexpand: "_handleCollapsibleExpand" } );
},
_themeClassFromOption: function( prefix, value ) {
return ( value ? ( value === "none" ? "" : prefix + value ) : "" );
},
_init: function() {
this._refresh( true );
// Because the corners are handled by the collapsible itself and the default state is collapsed
// That was causing https://github.com/jquery/jquery-mobile/issues/4116
this.element
.children( childCollapsiblesSelector )
.filter( ":jqmData(collapsed='false')" )
.collapsible( "expand" );
},
_setOptions: function( options ) {
var ret, hasCorners,
elem = this.element,
themeClass = this._themeClassFromOption( "ui-group-theme-", options.theme );
if ( themeClass ) {
elem
.removeClass( this._themeClassFromOption( "ui-group-theme-", this.options.theme ) )
.addClass( themeClass );
}
if ( options.inset !== undefined ) {
hasCorners = !!( options.inset && ( options.corners || this.options.corners ) );
}
if ( options.corners !== undefined ) {
hasCorners = !!( options.corners && ( options.inset || this.options.inset ) );
}
if ( hasCorners !== undefined ) {
elem.toggleClass( "ui-corner-all", hasCorners );
}
ret = this._super( options );
this.element.children( ":mobile-collapsible" ).collapsible( "refresh" );
return ret;
},
_destroy: function() {
var el = this.element;
this._removeFirstLastClasses( el.children( childCollapsiblesSelector ) );
el
.removeClass( "ui-collapsible-set ui-corner-all " +
this._themeClassFromOption( "ui-group-theme-", this.options.theme ) )
.children( ":mobile-collapsible" )
.collapsible( "destroy" );
},
_refresh: function( create ) {
var collapsiblesInSet = this.element.children( childCollapsiblesSelector );
this.element.find( $.mobile.collapsible.initSelector ).not( ".ui-collapsible" ).collapsible();
this._addFirstLastClasses( collapsiblesInSet, this._getVisibles( collapsiblesInSet, create ), create );
},
refresh: function() {
this._refresh( false );
}
}, $.mobile.behaviors.addFirstLastClasses ) );
})( jQuery );
(function( $, undefined ) {
// Deprecated in 1.4
$.fn.fieldcontain = function(/* options */) {
return this.addClass( "ui-field-contain" );
};
})( jQuery );
(function( $, undefined ) {
$.fn.grid = function( options ) {
return this.each(function() {
var $this = $( this ),
o = $.extend({
grid: null
}, options ),
$kids = $this.children(),
gridCols = { solo:1, a:2, b:3, c:4, d:5 },
grid = o.grid,
iterator,
letter;
if ( !grid ) {
if ( $kids.length <= 5 ) {
for ( letter in gridCols ) {
if ( gridCols[ letter ] === $kids.length ) {
grid = letter;
}
}
} else {
grid = "a";
$this.addClass( "ui-grid-duo" );
}
}
iterator = gridCols[grid];
$this.addClass( "ui-grid-" + grid );
$kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" );
if ( iterator > 1 ) {
$kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" );
}
if ( iterator > 2 ) {
$kids.filter( ":nth-child(" + iterator + "n+3)" ).addClass( "ui-block-c" );
}
if ( iterator > 3 ) {
$kids.filter( ":nth-child(" + iterator + "n+4)" ).addClass( "ui-block-d" );
}
if ( iterator > 4 ) {
$kids.filter( ":nth-child(" + iterator + "n+5)" ).addClass( "ui-block-e" );
}
});
};
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.navbar", {
options: {
iconpos: "top",
grid: null
},
_create: function() {
var $navbar = this.element,
$navbtns = $navbar.find( "a, button" ),
iconpos = $navbtns.filter( ":jqmData(icon)" ).length ? this.options.iconpos : undefined;
$navbar.addClass( "ui-navbar" )
.attr( "role", "navigation" )
.find( "ul" )
.jqmEnhanceable()
.grid({ grid: this.options.grid });
$navbtns
.each( function() {
var icon = $.mobile.getAttribute( this, "icon" ),
theme = $.mobile.getAttribute( this, "theme" ),
classes = "ui-btn";
if ( theme ) {
classes += " ui-btn-" + theme;
}
if ( icon ) {
classes += " ui-icon-" + icon + " ui-btn-icon-" + iconpos;
}
$( this ).addClass( classes );
});
$navbar.delegate( "a", "vclick", function( /* event */ ) {
var activeBtn = $( this );
if ( !( activeBtn.hasClass( "ui-state-disabled" ) ||
// DEPRECATED as of 1.4.0 - remove after 1.4.0 release
// only ui-state-disabled should be present thereafter
activeBtn.hasClass( "ui-disabled" ) ||
activeBtn.hasClass( $.mobile.activeBtnClass ) ) ) {
$navbtns.removeClass( $.mobile.activeBtnClass );
activeBtn.addClass( $.mobile.activeBtnClass );
// The code below is a workaround to fix #1181
$( document ).one( "pagehide", function() {
activeBtn.removeClass( $.mobile.activeBtnClass );
});
}
});
// Buttons in the navbar with ui-state-persist class should regain their active state before page show
$navbar.closest( ".ui-page" ).bind( "pagebeforeshow", function() {
$navbtns.filter( ".ui-state-persist" ).addClass( $.mobile.activeBtnClass );
});
}
});
})( jQuery );
(function( $, undefined ) {
var getAttr = $.mobile.getAttribute;
$.widget( "mobile.listview", $.extend( {
options: {
theme: null,
countTheme: null, /* Deprecated in 1.4 */
dividerTheme: null,
icon: "carat-r",
splitIcon: "carat-r",
splitTheme: null,
corners: true,
shadow: true,
inset: false
},
_create: function() {
var t = this,
listviewClasses = "";
listviewClasses += t.options.inset ? " ui-listview-inset" : "";
if ( !!t.options.inset ) {
listviewClasses += t.options.corners ? " ui-corner-all" : "";
listviewClasses += t.options.shadow ? " ui-shadow" : "";
}
// create listview markup
t.element.addClass( " ui-listview" + listviewClasses );
t.refresh( true );
},
// TODO: Remove in 1.5
_findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) {
var dict = {};
dict[ lcName ] = dict[ ucName ] = true;
while ( ele ) {
if ( dict[ ele.nodeName ] ) {
return ele;
}
ele = ele[ nextProp ];
}
return null;
},
// TODO: Remove in 1.5
_addThumbClasses: function( containers ) {
var i, img, len = containers.length;
for ( i = 0; i < len; i++ ) {
img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) );
if ( img.length ) {
$( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.hasClass( "ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
}
}
},
_getChildrenByTagName: function( ele, lcName, ucName ) {
var results = [],
dict = {};
dict[ lcName ] = dict[ ucName ] = true;
ele = ele.firstChild;
while ( ele ) {
if ( dict[ ele.nodeName ] ) {
results.push( ele );
}
ele = ele.nextSibling;
}
return $( results );
},
_beforeListviewRefresh: $.noop,
_afterListviewRefresh: $.noop,
refresh: function( create ) {
var buttonClass, pos, numli, item, itemClass, itemTheme, itemIcon, icon, a,
isDivider, startCount, newStartCount, value, last, splittheme, splitThemeClass, spliticon,
altButtonClass, dividerTheme, li,
o = this.options,
$list = this.element,
ol = !!$.nodeName( $list[ 0 ], "ol" ),
start = $list.attr( "start" ),
itemClassDict = {},
countBubbles = $list.find( ".ui-li-count" ),
countTheme = getAttr( $list[ 0 ], "counttheme" ) || this.options.countTheme,
countThemeClass = countTheme ? "ui-body-" + countTheme : "ui-body-inherit";
if ( o.theme ) {
$list.addClass( "ui-group-theme-" + o.theme );
}
// Check if a start attribute has been set while taking a value of 0 into account
if ( ol && ( start || start === 0 ) ) {
startCount = parseInt( start, 10 ) - 1;
$list.css( "counter-reset", "listnumbering " + startCount );
}
this._beforeListviewRefresh();
li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" );
for ( pos = 0, numli = li.length; pos < numli; pos++ ) {
item = li.eq( pos );
itemClass = "";
if ( create || item[ 0 ].className.search( /\bui-li-static\b|\bui-li-divider\b/ ) < 0 ) {
a = this._getChildrenByTagName( item[ 0 ], "a", "A" );
isDivider = ( getAttr( item[ 0 ], "role" ) === "list-divider" );
value = item.attr( "value" );
itemTheme = getAttr( item[ 0 ], "theme" );
if ( a.length && a[ 0 ].className.search( /\bui-btn\b/ ) < 0 && !isDivider ) {
itemIcon = getAttr( item[ 0 ], "icon" );
icon = ( itemIcon === false ) ? false : ( itemIcon || o.icon );
// TODO: Remove in 1.5 together with links.js (links.js / .ui-link deprecated in 1.4)
a.removeClass( "ui-link" );
buttonClass = "ui-btn";
if ( itemTheme ) {
buttonClass += " ui-btn-" + itemTheme;
}
if ( a.length > 1 ) {
itemClass = "ui-li-has-alt";
last = a.last();
splittheme = getAttr( last[ 0 ], "theme" ) || o.splitTheme || getAttr( item[ 0 ], "theme", true );
splitThemeClass = splittheme ? " ui-btn-" + splittheme : "";
spliticon = getAttr( last[ 0 ], "icon" ) || getAttr( item[ 0 ], "icon" ) || o.splitIcon;
altButtonClass = "ui-btn ui-btn-icon-notext ui-icon-" + spliticon + splitThemeClass;
last
.attr( "title", $.trim( last.getEncodedText() ) )
.addClass( altButtonClass )
.empty();
// Reduce to the first anchor, because only the first gets the buttonClass
a = a.first();
} else if ( icon ) {
buttonClass += " ui-btn-icon-right ui-icon-" + icon;
}
// Apply buttonClass to the (first) anchor
a.addClass( buttonClass );
} else if ( isDivider ) {
dividerTheme = ( getAttr( item[ 0 ], "theme" ) || o.dividerTheme || o.theme );
itemClass = "ui-li-divider ui-bar-" + ( dividerTheme ? dividerTheme : "inherit" );
item.attr( "role", "heading" );
} else if ( a.length <= 0 ) {
itemClass = "ui-li-static ui-body-" + ( itemTheme ? itemTheme : "inherit" );
}
if ( ol && value ) {
newStartCount = parseInt( value , 10 ) - 1;
item.css( "counter-reset", "listnumbering " + newStartCount );
}
}
// Instead of setting item class directly on the list item
// at this point in time, push the item into a dictionary
// that tells us what class to set on it so we can do this after this
// processing loop is finished.
if ( !itemClassDict[ itemClass ] ) {
itemClassDict[ itemClass ] = [];
}
itemClassDict[ itemClass ].push( item[ 0 ] );
}
// Set the appropriate listview item classes on each list item.
// The main reason we didn't do this
// in the for-loop above is because we can eliminate per-item function overhead
// by calling addClass() and children() once or twice afterwards. This
// can give us a significant boost on platforms like WP7.5.
for ( itemClass in itemClassDict ) {
$( itemClassDict[ itemClass ] ).addClass( itemClass );
}
countBubbles.each( function() {
$( this ).closest( "li" ).addClass( "ui-li-has-count" );
});
if ( countThemeClass ) {
countBubbles.not( "[class*='ui-body-']" ).addClass( countThemeClass );
}
// Deprecated in 1.4. From 1.5 you have to add class ui-li-has-thumb or ui-li-has-icon to the LI.
this._addThumbClasses( li );
this._addThumbClasses( li.find( ".ui-btn" ) );
this._afterListviewRefresh();
this._addFirstLastClasses( li, this._getVisibles( li, create ), create );
}
}, $.mobile.behaviors.addFirstLastClasses ) );
})( jQuery );
(function( $, undefined ) {
function defaultAutodividersSelector( elt ) {
// look for the text in the given element
var text = $.trim( elt.text() ) || null;
if ( !text ) {
return null;
}
// create the text for the divider (first uppercased letter)
text = text.slice( 0, 1 ).toUpperCase();
return text;
}
$.widget( "mobile.listview", $.mobile.listview, {
options: {
autodividers: false,
autodividersSelector: defaultAutodividersSelector
},
_beforeListviewRefresh: function() {
if ( this.options.autodividers ) {
this._replaceDividers();
this._superApply( arguments );
}
},
_replaceDividers: function() {
var i, lis, li, dividerText,
lastDividerText = null,
list = this.element,
divider;
list.children( "li:jqmData(role='list-divider')" ).remove();
lis = list.children( "li" );
for ( i = 0; i < lis.length ; i++ ) {
li = lis[ i ];
dividerText = this.options.autodividersSelector( $( li ) );
if ( dividerText && lastDividerText !== dividerText ) {
divider = document.createElement( "li" );
divider.appendChild( document.createTextNode( dividerText ) );
divider.setAttribute( "data-" + $.mobile.ns + "role", "list-divider" );
li.parentNode.insertBefore( divider, li );
}
lastDividerText = dividerText;
}
}
});
})( jQuery );
(function( $, undefined ) {
var rdivider = /(^|\s)ui-li-divider($|\s)/,
rhidden = /(^|\s)ui-screen-hidden($|\s)/;
$.widget( "mobile.listview", $.mobile.listview, {
options: {
hideDividers: false
},
_afterListviewRefresh: function() {
var items, idx, item, hideDivider = true;
this._superApply( arguments );
if ( this.options.hideDividers ) {
items = this._getChildrenByTagName( this.element[ 0 ], "li", "LI" );
for ( idx = items.length - 1 ; idx > -1 ; idx-- ) {
item = items[ idx ];
if ( item.className.match( rdivider ) ) {
if ( hideDivider ) {
item.className = item.className + " ui-screen-hidden";
}
hideDivider = true;
} else {
if ( !item.className.match( rhidden ) ) {
hideDivider = false;
}
}
}
}
}
});
})( jQuery );
(function( $, undefined ) {
$.mobile.nojs = function( target ) {
$( ":jqmData(role='nojs')", target ).addClass( "ui-nojs" );
};
})( jQuery );
(function( $, undefined ) {
$.mobile.behaviors.formReset = {
_handleFormReset: function() {
this._on( this.element.closest( "form" ), {
reset: function() {
this._delay( "_reset" );
}
});
}
};
})( jQuery );
/*
* "checkboxradio" plugin
*/
(function( $, undefined ) {
var escapeId = $.mobile.path.hashToSelector;
$.widget( "mobile.checkboxradio", $.extend( {
initSelector: "input:not( :jqmData(role='flipswitch' ) )[type='checkbox'],input[type='radio']:not( :jqmData(role='flipswitch' ))",
options: {
theme: "inherit",
mini: false,
wrapperClass: null,
enhanced: false,
iconpos: "left"
},
_create: function() {
var input = this.element,
o = this.options,
inheritAttr = function( input, dataAttr ) {
return input.jqmData( dataAttr ) ||
input.closest( "form, fieldset" ).jqmData( dataAttr );
},
label = this.options.enhanced ?
{
element: this.element.siblings( "label" ),
isParent: false
} :
this._findLabel(),
inputtype = input[0].type,
checkedClass = "ui-" + inputtype + "-on",
uncheckedClass = "ui-" + inputtype + "-off";
if ( inputtype !== "checkbox" && inputtype !== "radio" ) {
return;
}
if ( this.element[0].disabled ) {
this.options.disabled = true;
}
o.iconpos = inheritAttr( input, "iconpos" ) ||
label.element.attr( "data-" + $.mobile.ns + "iconpos" ) || o.iconpos,
// Establish options
o.mini = inheritAttr( input, "mini" ) || o.mini;
// Expose for other methods
$.extend( this, {
input: input,
label: label.element,
labelIsParent: label.isParent,
inputtype: inputtype,
checkedClass: checkedClass,
uncheckedClass: uncheckedClass
});
if ( !this.options.enhanced ) {
this._enhance();
}
this._on( label.element, {
vmouseover: "_handleLabelVMouseOver",
vclick: "_handleLabelVClick"
});
this._on( input, {
vmousedown: "_cacheVals",
vclick: "_handleInputVClick",
focus: "_handleInputFocus",
blur: "_handleInputBlur"
});
this._handleFormReset();
this.refresh();
},
_findLabel: function() {
var parentLabel, label, isParent,
input = this.element,
labelsList = input[ 0 ].labels;
if( labelsList && labelsList.length > 0 ) {
label = $( labelsList[ 0 ] );
isParent = $.contains( label[ 0 ], input[ 0 ] );
} else {
parentLabel = input.closest( "label" );
isParent = ( parentLabel.length > 0 );
// NOTE: Windows Phone could not find the label through a selector
// filter works though.
label = isParent ? parentLabel :
$( this.document[ 0 ].getElementsByTagName( "label" ) )
.filter( "[for='" + escapeId( input[ 0 ].id ) + "']" )
.first();
}
return {
element: label,
isParent: isParent
};
},
_enhance: function() {
this.label.addClass( "ui-btn ui-corner-all");
if ( this.labelIsParent ) {
this.input.add( this.label ).wrapAll( this._wrapper() );
} else {
//this.element.replaceWith( this.input.add( this.label ).wrapAll( this._wrapper() ) );
this.element.wrap( this._wrapper() );
this.element.parent().prepend( this.label );
}
// Wrap the input + label in a div
this._setOptions({
"theme": this.options.theme,
"iconpos": this.options.iconpos,
"mini": this.options.mini
});
},
_wrapper: function() {
return $( "" );
},
_handleInputFocus: function() {
this.label.addClass( $.mobile.focusClass );
},
_handleInputBlur: function() {
this.label.removeClass( $.mobile.focusClass );
},
_handleInputVClick: function() {
// Adds checked attribute to checked input when keyboard is used
this.element.prop( "checked", this.element.is( ":checked" ) );
this._getInputSet().not( this.element ).prop( "checked", false );
this._updateAll( true );
},
_handleLabelVMouseOver: function( event ) {
if ( this.label.parent().hasClass( "ui-state-disabled" ) ) {
event.stopPropagation();
}
},
_handleLabelVClick: function( event ) {
var input = this.element;
if ( input.is( ":disabled" ) ) {
event.preventDefault();
return;
}
this._cacheVals();
input.prop( "checked", this.inputtype === "radio" && true || !input.prop( "checked" ) );
// trigger click handler's bound directly to the input as a substitute for
// how label clicks behave normally in the browsers
// TODO: it would be nice to let the browser's handle the clicks and pass them
// through to the associate input. we can swallow that click at the parent
// wrapper element level
input.triggerHandler( "click" );
// Input set for common radio buttons will contain all the radio
// buttons, but will not for checkboxes. clearing the checked status
// of other radios ensures the active button state is applied properly
this._getInputSet().not( input ).prop( "checked", false );
this._updateAll();
return false;
},
_cacheVals: function() {
this._getInputSet().each( function() {
$( this ).attr("data-" + $.mobile.ns + "cacheVal", this.checked );
});
},
// Returns those radio buttons that are supposed to be in the same group as
// this radio button. In the case of a checkbox or a radio lacking a name
// attribute, it returns this.element.
_getInputSet: function() {
var selector, formId,
radio = this.element[ 0 ],
name = radio.name,
form = radio.form,
doc = this.element.parents().last().get( 0 ),
// A radio is always a member of its own group
radios = this.element;
// Only start running selectors if this is an attached radio button with a name
if ( name && this.inputtype === "radio" && doc ) {
selector = "input[type='radio'][name='" + escapeId( name ) + "']";
// If we're inside a form
if ( form ) {
formId = form.getAttribute( "id" );
// If the form has an ID, collect radios scattered throught the document which
// nevertheless are part of the form by way of the value of their form attribute
if ( formId ) {
radios = $( selector + "[form='" + escapeId( formId ) + "']", doc );
}
// Also add to those the radios in the form itself
radios = $( form ).find( selector ).filter( function() {
// Some radios inside the form may belong to some other form by virtue of
// having a form attribute defined on them, so we must filter them out here
return ( this.form === form );
}).add( radios );
// If we're outside a form
} else {
// Collect all those radios which are also outside of a form and match our name
radios = $( selector, doc ).filter( function() {
return !this.form;
});
}
}
return radios;
},
_updateAll: function( changeTriggered ) {
var self = this;
this._getInputSet().each( function() {
var $this = $( this );
if ( ( this.checked || self.inputtype === "checkbox" ) && !changeTriggered ) {
$this.trigger( "change" );
}
})
.checkboxradio( "refresh" );
},
_reset: function() {
this.refresh();
},
// Is the widget supposed to display an icon?
_hasIcon: function() {
var controlgroup, controlgroupWidget,
controlgroupConstructor = $.mobile.controlgroup;
// If the controlgroup widget is defined ...
if ( controlgroupConstructor ) {
controlgroup = this.element.closest(
":mobile-controlgroup," +
controlgroupConstructor.prototype.initSelector );
// ... and the checkbox is in a controlgroup ...
if ( controlgroup.length > 0 ) {
// ... look for a controlgroup widget instance, and ...
controlgroupWidget = $.data( controlgroup[ 0 ], "mobile-controlgroup" );
// ... if found, decide based on the option value, ...
return ( ( controlgroupWidget ? controlgroupWidget.options.type :
// ... otherwise decide based on the "type" data attribute.
controlgroup.attr( "data-" + $.mobile.ns + "type" ) ) !== "horizontal" );
}
}
// Normally, the widget displays an icon.
return true;
},
refresh: function() {
var isChecked = this.element[ 0 ].checked,
active = $.mobile.activeBtnClass,
iconposClass = "ui-btn-icon-" + this.options.iconpos,
addClasses = [],
removeClasses = [];
if ( this._hasIcon() ) {
removeClasses.push( active );
addClasses.push( iconposClass );
} else {
removeClasses.push( iconposClass );
( isChecked ? addClasses : removeClasses ).push( active );
}
if ( isChecked ) {
addClasses.push( this.checkedClass );
removeClasses.push( this.uncheckedClass );
} else {
addClasses.push( this.uncheckedClass );
removeClasses.push( this.checkedClass );
}
this.widget().toggleClass( "ui-state-disabled", this.element.prop( "disabled" ) );
this.label
.addClass( addClasses.join( " " ) )
.removeClass( removeClasses.join( " " ) );
},
widget: function() {
return this.label.parent();
},
_setOptions: function( options ) {
var label = this.label,
currentOptions = this.options,
outer = this.widget(),
hasIcon = this._hasIcon();
if ( options.disabled !== undefined ) {
this.input.prop( "disabled", !!options.disabled );
outer.toggleClass( "ui-state-disabled", !!options.disabled );
}
if ( options.mini !== undefined ) {
outer.toggleClass( "ui-mini", !!options.mini );
}
if ( options.theme !== undefined ) {
label
.removeClass( "ui-btn-" + currentOptions.theme )
.addClass( "ui-btn-" + options.theme );
}
if ( options.wrapperClass !== undefined ) {
outer
.removeClass( currentOptions.wrapperClass )
.addClass( options.wrapperClass );
}
if ( options.iconpos !== undefined && hasIcon ) {
label.removeClass( "ui-btn-icon-" + currentOptions.iconpos ).addClass( "ui-btn-icon-" + options.iconpos );
} else if ( !hasIcon ) {
label.removeClass( "ui-btn-icon-" + currentOptions.iconpos );
}
this._super( options );
}
}, $.mobile.behaviors.formReset ) );
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.button", {
initSelector: "input[type='button'], input[type='submit'], input[type='reset']",
options: {
theme: null,
icon: null,
iconpos: "left",
iconshadow: false, /* TODO: Deprecated in 1.4, remove in 1.5. */
corners: true,
shadow: true,
inline: null,
mini: null,
wrapperClass: null,
enhanced: false
},
_create: function() {
if ( this.element.is( ":disabled" ) ) {
this.options.disabled = true;
}
if ( !this.options.enhanced ) {
this._enhance();
}
$.extend( this, {
wrapper: this.element.parent()
});
this._on( {
focus: function() {
this.widget().addClass( $.mobile.focusClass );
},
blur: function() {
this.widget().removeClass( $.mobile.focusClass );
}
});
this.refresh( true );
},
_enhance: function() {
this.element.wrap( this._button() );
},
_button: function() {
var options = this.options,
iconClasses = this._getIconClasses( this.options );
return $("
" + this.element.val() + "
" );
},
widget: function() {
return this.wrapper;
},
_destroy: function() {
this.element.insertBefore( this.wrapper );
this.wrapper.remove();
},
_getIconClasses: function( options ) {
return ( options.icon ? ( "ui-icon-" + options.icon +
( options.iconshadow ? " ui-shadow-icon" : "" ) + /* TODO: Deprecated in 1.4, remove in 1.5. */
" ui-btn-icon-" + options.iconpos ) : "" );
},
_setOptions: function( options ) {
var outer = this.widget();
if ( options.theme !== undefined ) {
outer
.removeClass( this.options.theme )
.addClass( "ui-btn-" + options.theme );
}
if ( options.corners !== undefined ) {
outer.toggleClass( "ui-corner-all", options.corners );
}
if ( options.shadow !== undefined ) {
outer.toggleClass( "ui-shadow", options.shadow );
}
if ( options.inline !== undefined ) {
outer.toggleClass( "ui-btn-inline", options.inline );
}
if ( options.mini !== undefined ) {
outer.toggleClass( "ui-mini", options.mini );
}
if ( options.disabled !== undefined ) {
this.element.prop( "disabled", options.disabled );
outer.toggleClass( "ui-state-disabled", options.disabled );
}
if ( options.icon !== undefined ||
options.iconshadow !== undefined || /* TODO: Deprecated in 1.4, remove in 1.5. */
options.iconpos !== undefined ) {
outer
.removeClass( this._getIconClasses( this.options ) )
.addClass( this._getIconClasses(
$.extend( {}, this.options, options ) ) );
}
this._super( options );
},
refresh: function( create ) {
var originalElement,
isDisabled = this.element.prop( "disabled" );
if ( this.options.icon && this.options.iconpos === "notext" && this.element.attr( "title" ) ) {
this.element.attr( "title", this.element.val() );
}
if ( !create ) {
originalElement = this.element.detach();
$( this.wrapper ).text( this.element.val() ).append( originalElement );
}
if ( this.options.disabled !== isDisabled ) {
this._setOptions({ disabled: isDisabled });
}
}
});
})( jQuery );
(function( $ ) {
var meta = $( "meta[name=viewport]" ),
initialContent = meta.attr( "content" ),
disabledZoom = initialContent + ",maximum-scale=1, user-scalable=no",
enabledZoom = initialContent + ",maximum-scale=10, user-scalable=yes",
disabledInitially = /(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test( initialContent );
$.mobile.zoom = $.extend( {}, {
enabled: !disabledInitially,
locked: false,
disable: function( lock ) {
if ( !disabledInitially && !$.mobile.zoom.locked ) {
meta.attr( "content", disabledZoom );
$.mobile.zoom.enabled = false;
$.mobile.zoom.locked = lock || false;
}
},
enable: function( unlock ) {
if ( !disabledInitially && ( !$.mobile.zoom.locked || unlock === true ) ) {
meta.attr( "content", enabledZoom );
$.mobile.zoom.enabled = true;
$.mobile.zoom.locked = false;
}
},
restore: function() {
if ( !disabledInitially ) {
meta.attr( "content", initialContent );
$.mobile.zoom.enabled = true;
}
}
});
}( jQuery ));
(function( $, undefined ) {
$.widget( "mobile.textinput", {
initSelector: "input[type='text']," +
"input[type='search']," +
":jqmData(type='search')," +
"input[type='number']," +
":jqmData(type='number')," +
"input[type='password']," +
"input[type='email']," +
"input[type='url']," +
"input[type='tel']," +
"textarea," +
"input[type='time']," +
"input[type='date']," +
"input[type='month']," +
"input[type='week']," +
"input[type='datetime']," +
"input[type='datetime-local']," +
"input[type='color']," +
"input:not([type])," +
"input[type='file']",
options: {
theme: null,
corners: true,
mini: false,
// This option defaults to true on iOS devices.
preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1,
wrapperClass: "",
enhanced: false
},
_create: function() {
var options = this.options,
isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ),
isTextarea = this.element[ 0 ].tagName === "TEXTAREA",
isRange = this.element.is( "[data-" + ( $.mobile.ns || "" ) + "type='range']" ),
inputNeedsWrap = ( (this.element.is( "input" ) ||
this.element.is( "[data-" + ( $.mobile.ns || "" ) + "type='search']" ) ) &&
!isRange );
if ( this.element.prop( "disabled" ) ) {
options.disabled = true;
}
$.extend( this, {
classes: this._classesFromOptions(),
isSearch: isSearch,
isTextarea: isTextarea,
isRange: isRange,
inputNeedsWrap: inputNeedsWrap
});
this._autoCorrect();
if ( !options.enhanced ) {
this._enhance();
}
this._on( {
"focus": "_handleFocus",
"blur": "_handleBlur"
});
},
refresh: function() {
this.setOptions({
"disabled" : this.element.is( ":disabled" )
});
},
_enhance: function() {
var elementClasses = [];
if ( this.isTextarea ) {
elementClasses.push( "ui-input-text" );
}
if ( this.isTextarea || this.isRange ) {
elementClasses.push( "ui-shadow-inset" );
}
//"search" and "text" input widgets
if ( this.inputNeedsWrap ) {
this.element.wrap( this._wrap() );
} else {
elementClasses = elementClasses.concat( this.classes );
}
this.element.addClass( elementClasses.join( " " ) );
},
widget: function() {
return ( this.inputNeedsWrap ) ? this.element.parent() : this.element;
},
_classesFromOptions: function() {
var options = this.options,
classes = [];
classes.push( "ui-body-" + ( ( options.theme === null ) ? "inherit" : options.theme ) );
if ( options.corners ) {
classes.push( "ui-corner-all" );
}
if ( options.mini ) {
classes.push( "ui-mini" );
}
if ( options.disabled ) {
classes.push( "ui-state-disabled" );
}
if ( options.wrapperClass ) {
classes.push( options.wrapperClass );
}
return classes;
},
_wrap: function() {
return $( "" );
},
_autoCorrect: function() {
// XXX: Temporary workaround for issue 785 (Apple bug 8910589).
// Turn off autocorrect and autocomplete on non-iOS 5 devices
// since the popup they use can't be dismissed by the user. Note
// that we test for the presence of the feature by looking for
// the autocorrect property on the input element. We currently
// have no test for iOS 5 or newer so we're temporarily using
// the touchOverflow support flag for jQM 1.0. Yes, I feel dirty.
// - jblas
if ( typeof this.element[0].autocorrect !== "undefined" &&
!$.support.touchOverflow ) {
// Set the attribute instead of the property just in case there
// is code that attempts to make modifications via HTML.
this.element[0].setAttribute( "autocorrect", "off" );
this.element[0].setAttribute( "autocomplete", "off" );
}
},
_handleBlur: function() {
this.widget().removeClass( $.mobile.focusClass );
if ( this.options.preventFocusZoom ) {
$.mobile.zoom.enable( true );
}
},
_handleFocus: function() {
// In many situations, iOS will zoom into the input upon tap, this
// prevents that from happening
if ( this.options.preventFocusZoom ) {
$.mobile.zoom.disable( true );
}
this.widget().addClass( $.mobile.focusClass );
},
_setOptions: function ( options ) {
var outer = this.widget();
this._super( options );
if ( !( options.disabled === undefined &&
options.mini === undefined &&
options.corners === undefined &&
options.theme === undefined &&
options.wrapperClass === undefined ) ) {
outer.removeClass( this.classes.join( " " ) );
this.classes = this._classesFromOptions();
outer.addClass( this.classes.join( " " ) );
}
if ( options.disabled !== undefined ) {
this.element.prop( "disabled", !!options.disabled );
}
},
_destroy: function() {
if ( this.options.enhanced ) {
return;
}
if ( this.inputNeedsWrap ) {
this.element.unwrap();
}
this.element.removeClass( "ui-input-text " + this.classes.join( " " ) );
}
});
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.slider", $.extend( {
initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",
widgetEventPrefix: "slide",
options: {
theme: null,
trackTheme: null,
corners: true,
mini: false,
highlight: false
},
_create: function() {
// TODO: Each of these should have comments explain what they're for
var self = this,
control = this.element,
trackTheme = this.options.trackTheme || $.mobile.getAttribute( control[ 0 ], "theme" ),
trackThemeClass = trackTheme ? " ui-bar-" + trackTheme : " ui-bar-inherit",
cornerClass = ( this.options.corners || control.jqmData( "corners" ) ) ? " ui-corner-all" : "",
miniClass = ( this.options.mini || control.jqmData( "mini" ) ) ? " ui-mini" : "",
cType = control[ 0 ].nodeName.toLowerCase(),
isToggleSwitch = ( cType === "select" ),
isRangeslider = control.parent().is( ":jqmData(role='rangeslider')" ),
selectClass = ( isToggleSwitch ) ? "ui-slider-switch" : "",
controlID = control.attr( "id" ),
$label = $( "[for='" + controlID + "']" ),
labelID = $label.attr( "id" ) || controlID + "-label",
min = !isToggleSwitch ? parseFloat( control.attr( "min" ) ) : 0,
max = !isToggleSwitch ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1,
step = window.parseFloat( control.attr( "step" ) || 1 ),
domHandle = document.createElement( "a" ),
handle = $( domHandle ),
domSlider = document.createElement( "div" ),
slider = $( domSlider ),
valuebg = this.options.highlight && !isToggleSwitch ? (function() {
var bg = document.createElement( "div" );
bg.className = "ui-slider-bg " + $.mobile.activeBtnClass;
return $( bg ).prependTo( slider );
})() : false,
options,
wrapper,
j, length,
i, optionsCount, origTabIndex,
side, activeClass, sliderImg;
$label.attr( "id", labelID );
this.isToggleSwitch = isToggleSwitch;
domHandle.setAttribute( "href", "#" );
domSlider.setAttribute( "role", "application" );
domSlider.className = [ this.isToggleSwitch ? "ui-slider ui-slider-track ui-shadow-inset " : "ui-slider-track ui-shadow-inset ", selectClass, trackThemeClass, cornerClass, miniClass ].join( "" );
domHandle.className = "ui-slider-handle";
domSlider.appendChild( domHandle );
handle.attr({
"role": "slider",
"aria-valuemin": min,
"aria-valuemax": max,
"aria-valuenow": this._value(),
"aria-valuetext": this._value(),
"title": this._value(),
"aria-labelledby": labelID
});
$.extend( this, {
slider: slider,
handle: handle,
control: control,
type: cType,
step: step,
max: max,
min: min,
valuebg: valuebg,
isRangeslider: isRangeslider,
dragging: false,
beforeStart: null,
userModified: false,
mouseMoved: false
});
if ( isToggleSwitch ) {
// TODO: restore original tabindex (if any) in a destroy method
origTabIndex = control.attr( "tabindex" );
if ( origTabIndex ) {
handle.attr( "tabindex", origTabIndex );
}
control.attr( "tabindex", "-1" ).focus(function() {
$( this ).blur();
handle.focus();
});
wrapper = document.createElement( "div" );
wrapper.className = "ui-slider-inneroffset";
for ( j = 0, length = domSlider.childNodes.length; j < length; j++ ) {
wrapper.appendChild( domSlider.childNodes[j] );
}
domSlider.appendChild( wrapper );
// slider.wrapInner( "" );
// make the handle move with a smooth transition
handle.addClass( "ui-slider-handle-snapping" );
options = control.find( "option" );
for ( i = 0, optionsCount = options.length; i < optionsCount; i++ ) {
side = !i ? "b" : "a";
activeClass = !i ? "" : " " + $.mobile.activeBtnClass;
sliderImg = document.createElement( "span" );
sliderImg.className = [ "ui-slider-label ui-slider-label-", side, activeClass ].join( "" );
sliderImg.setAttribute( "role", "img" );
sliderImg.appendChild( document.createTextNode( options[i].innerHTML ) );
$( sliderImg ).prependTo( slider );
}
self._labels = $( ".ui-slider-label", slider );
}
// monitor the input for updated values
control.addClass( isToggleSwitch ? "ui-slider-switch" : "ui-slider-input" );
this._on( control, {
"change": "_controlChange",
"keyup": "_controlKeyup",
"blur": "_controlBlur",
"vmouseup": "_controlVMouseUp"
});
slider.bind( "vmousedown", $.proxy( this._sliderVMouseDown, this ) )
.bind( "vclick", false );
// We have to instantiate a new function object for the unbind to work properly
// since the method itself is defined in the prototype (causing it to unbind everything)
this._on( document, { "vmousemove": "_preventDocumentDrag" });
this._on( slider.add( document ), { "vmouseup": "_sliderVMouseUp" });
slider.insertAfter( control );
// wrap in a div for styling purposes
if ( !isToggleSwitch && !isRangeslider ) {
wrapper = this.options.mini ? "
" : "
";
control.add( slider ).wrapAll( wrapper );
}
// bind the handle event callbacks and set the context to the widget instance
this._on( this.handle, {
"vmousedown": "_handleVMouseDown",
"keydown": "_handleKeydown",
"keyup": "_handleKeyup"
});
this.handle.bind( "vclick", false );
this._handleFormReset();
this.refresh( undefined, undefined, true );
},
_setOptions: function( options ) {
if ( options.theme !== undefined ) {
this._setTheme( options.theme );
}
if ( options.trackTheme !== undefined ) {
this._setTrackTheme( options.trackTheme );
}
if ( options.corners !== undefined ) {
this._setCorners( options.corners );
}
if ( options.mini !== undefined ) {
this._setMini( options.mini );
}
if ( options.highlight !== undefined ) {
this._setHighlight( options.highlight );
}
if ( options.disabled !== undefined ) {
this._setDisabled( options.disabled );
}
this._super( options );
},
_controlChange: function( event ) {
// if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again
if ( this._trigger( "controlchange", event ) === false ) {
return false;
}
if ( !this.mouseMoved ) {
this.refresh( this._value(), true );
}
},
_controlKeyup: function(/* event */) { // necessary?
this.refresh( this._value(), true, true );
},
_controlBlur: function(/* event */) {
this.refresh( this._value(), true );
},
// it appears the clicking the up and down buttons in chrome on
// range/number inputs doesn't trigger a change until the field is
// blurred. Here we check thif the value has changed and refresh
_controlVMouseUp: function(/* event */) {
this._checkedRefresh();
},
// NOTE force focus on handle
_handleVMouseDown: function(/* event */) {
this.handle.focus();
},
_handleKeydown: function( event ) {
var index = this._value();
if ( this.options.disabled ) {
return;
}
// In all cases prevent the default and mark the handle as active
switch ( event.keyCode ) {
case $.mobile.keyCode.HOME:
case $.mobile.keyCode.END:
case $.mobile.keyCode.PAGE_UP:
case $.mobile.keyCode.PAGE_DOWN:
case $.mobile.keyCode.UP:
case $.mobile.keyCode.RIGHT:
case $.mobile.keyCode.DOWN:
case $.mobile.keyCode.LEFT:
event.preventDefault();
if ( !this._keySliding ) {
this._keySliding = true;
this.handle.addClass( "ui-state-active" ); /* TODO: We don't use this class for styling. Do we need to add it? */
}
break;
}
// move the slider according to the keypress
switch ( event.keyCode ) {
case $.mobile.keyCode.HOME:
this.refresh( this.min );
break;
case $.mobile.keyCode.END:
this.refresh( this.max );
break;
case $.mobile.keyCode.PAGE_UP:
case $.mobile.keyCode.UP:
case $.mobile.keyCode.RIGHT:
this.refresh( index + this.step );
break;
case $.mobile.keyCode.PAGE_DOWN:
case $.mobile.keyCode.DOWN:
case $.mobile.keyCode.LEFT:
this.refresh( index - this.step );
break;
}
}, // remove active mark
_handleKeyup: function(/* event */) {
if ( this._keySliding ) {
this._keySliding = false;
this.handle.removeClass( "ui-state-active" ); /* See comment above. */
}
},
_sliderVMouseDown: function( event ) {
// NOTE: we don't do this in refresh because we still want to
// support programmatic alteration of disabled inputs
if ( this.options.disabled || !( event.which === 1 || event.which === 0 || event.which === undefined ) ) {
return false;
}
if ( this._trigger( "beforestart", event ) === false ) {
return false;
}
this.dragging = true;
this.userModified = false;
this.mouseMoved = false;
if ( this.isToggleSwitch ) {
this.beforeStart = this.element[0].selectedIndex;
}
this.refresh( event );
this._trigger( "start" );
return false;
},
_sliderVMouseUp: function() {
if ( this.dragging ) {
this.dragging = false;
if ( this.isToggleSwitch ) {
// make the handle move with a smooth transition
this.handle.addClass( "ui-slider-handle-snapping" );
if ( this.mouseMoved ) {
// this is a drag, change the value only if user dragged enough
if ( this.userModified ) {
this.refresh( this.beforeStart === 0 ? 1 : 0 );
} else {
this.refresh( this.beforeStart );
}
} else {
// this is just a click, change the value
this.refresh( this.beforeStart === 0 ? 1 : 0 );
}
}
this.mouseMoved = false;
this._trigger( "stop" );
return false;
}
},
_preventDocumentDrag: function( event ) {
// NOTE: we don't do this in refresh because we still want to
// support programmatic alteration of disabled inputs
if ( this._trigger( "drag", event ) === false) {
return false;
}
if ( this.dragging && !this.options.disabled ) {
// this.mouseMoved must be updated before refresh() because it will be used in the control "change" event
this.mouseMoved = true;
if ( this.isToggleSwitch ) {
// make the handle move in sync with the mouse
this.handle.removeClass( "ui-slider-handle-snapping" );
}
this.refresh( event );
// only after refresh() you can calculate this.userModified
this.userModified = this.beforeStart !== this.element[0].selectedIndex;
return false;
}
},
_checkedRefresh: function() {
if ( this.value !== this._value() ) {
this.refresh( this._value() );
}
},
_value: function() {
return this.isToggleSwitch ? this.element[0].selectedIndex : parseFloat( this.element.val() ) ;
},
_reset: function() {
this.refresh( undefined, false, true );
},
refresh: function( val, isfromControl, preventInputUpdate ) {
// NOTE: we don't return here because we want to support programmatic
// alteration of the input value, which should still update the slider
var self = this,
parentTheme = $.mobile.getAttribute( this.element[ 0 ], "theme" ),
theme = this.options.theme || parentTheme,
themeClass = theme ? " ui-btn-" + theme : "",
trackTheme = this.options.trackTheme || parentTheme,
trackThemeClass = trackTheme ? " ui-bar-" + trackTheme : " ui-bar-inherit",
cornerClass = this.options.corners ? " ui-corner-all" : "",
miniClass = this.options.mini ? " ui-mini" : "",
left, width, data, tol,
pxStep, percent,
control, isInput, optionElements, min, max, step,
newval, valModStep, alignValue, percentPerStep,
handlePercent, aPercent, bPercent,
valueChanged;
self.slider[0].className = [ this.isToggleSwitch ? "ui-slider ui-slider-switch ui-slider-track ui-shadow-inset" : "ui-slider-track ui-shadow-inset", trackThemeClass, cornerClass, miniClass ].join( "" );
if ( this.options.disabled || this.element.prop( "disabled" ) ) {
this.disable();
}
// set the stored value for comparison later
this.value = this._value();
if ( this.options.highlight && !this.isToggleSwitch && this.slider.find( ".ui-slider-bg" ).length === 0 ) {
this.valuebg = (function() {
var bg = document.createElement( "div" );
bg.className = "ui-slider-bg " + $.mobile.activeBtnClass;
return $( bg ).prependTo( self.slider );
})();
}
this.handle.addClass( "ui-btn" + themeClass + " ui-shadow" );
control = this.element;
isInput = !this.isToggleSwitch;
optionElements = isInput ? [] : control.find( "option" );
min = isInput ? parseFloat( control.attr( "min" ) ) : 0;
max = isInput ? parseFloat( control.attr( "max" ) ) : optionElements.length - 1;
step = ( isInput && parseFloat( control.attr( "step" ) ) > 0 ) ? parseFloat( control.attr( "step" ) ) : 1;
if ( typeof val === "object" ) {
data = val;
// a slight tolerance helped get to the ends of the slider
tol = 8;
left = this.slider.offset().left;
width = this.slider.width();
pxStep = width/((max-min)/step);
if ( !this.dragging ||
data.pageX < left - tol ||
data.pageX > left + width + tol ) {
return;
}
if ( pxStep > 1 ) {
percent = ( ( data.pageX - left ) / width ) * 100;
} else {
percent = Math.round( ( ( data.pageX - left ) / width ) * 100 );
}
} else {
if ( val == null ) {
val = isInput ? parseFloat( control.val() || 0 ) : control[0].selectedIndex;
}
percent = ( parseFloat( val ) - min ) / ( max - min ) * 100;
}
if ( isNaN( percent ) ) {
return;
}
newval = ( percent / 100 ) * ( max - min ) + min;
//from jQuery UI slider, the following source will round to the nearest step
valModStep = ( newval - min ) % step;
alignValue = newval - valModStep;
if ( Math.abs( valModStep ) * 2 >= step ) {
alignValue += ( valModStep > 0 ) ? step : ( -step );
}
percentPerStep = 100/((max-min)/step);
// Since JavaScript has problems with large floats, round
// the final value to 5 digits after the decimal point (see jQueryUI: #4124)
newval = parseFloat( alignValue.toFixed(5) );
if ( typeof pxStep === "undefined" ) {
pxStep = width / ( (max-min) / step );
}
if ( pxStep > 1 && isInput ) {
percent = ( newval - min ) * percentPerStep * ( 1 / step );
}
if ( percent < 0 ) {
percent = 0;
}
if ( percent > 100 ) {
percent = 100;
}
if ( newval < min ) {
newval = min;
}
if ( newval > max ) {
newval = max;
}
this.handle.css( "left", percent + "%" );
this.handle[0].setAttribute( "aria-valuenow", isInput ? newval : optionElements.eq( newval ).attr( "value" ) );
this.handle[0].setAttribute( "aria-valuetext", isInput ? newval : optionElements.eq( newval ).getEncodedText() );
this.handle[0].setAttribute( "title", isInput ? newval : optionElements.eq( newval ).getEncodedText() );
if ( this.valuebg ) {
this.valuebg.css( "width", percent + "%" );
}
// drag the label widths
if ( this._labels ) {
handlePercent = this.handle.width() / this.slider.width() * 100;
aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100;
bPercent = percent === 100 ? 0 : Math.min( handlePercent + 100 - aPercent, 100 );
this._labels.each(function() {
var ab = $( this ).hasClass( "ui-slider-label-a" );
$( this ).width( ( ab ? aPercent : bPercent ) + "%" );
});
}
if ( !preventInputUpdate ) {
valueChanged = false;
// update control"s value
if ( isInput ) {
valueChanged = parseFloat( control.val() ) !== newval;
control.val( newval );
} else {
valueChanged = control[ 0 ].selectedIndex !== newval;
control[ 0 ].selectedIndex = newval;
}
if ( this._trigger( "beforechange", val ) === false) {
return false;
}
if ( !isfromControl && valueChanged ) {
control.trigger( "change" );
}
}
},
_setHighlight: function( value ) {
value = !!value;
if ( value ) {
this.options.highlight = !!value;
this.refresh();
} else if ( this.valuebg ) {
this.valuebg.remove();
this.valuebg = false;
}
},
_setTheme: function( value ) {
this.handle
.removeClass( "ui-btn-" + this.options.theme )
.addClass( "ui-btn-" + value );
var currentTheme = this.options.theme ? this.options.theme : "inherit",
newTheme = value ? value : "inherit";
this.control
.removeClass( "ui-body-" + currentTheme )
.addClass( "ui-body-" + newTheme );
},
_setTrackTheme: function( value ) {
var currentTrackTheme = this.options.trackTheme ? this.options.trackTheme : "inherit",
newTrackTheme = value ? value : "inherit";
this.slider
.removeClass( "ui-body-" + currentTrackTheme )
.addClass( "ui-body-" + newTrackTheme );
},
_setMini: function( value ) {
value = !!value;
if ( !this.isToggleSwitch && !this.isRangeslider ) {
this.slider.parent().toggleClass( "ui-mini", value );
this.element.toggleClass( "ui-mini", value );
}
this.slider.toggleClass( "ui-mini", value );
},
_setCorners: function( value ) {
this.slider.toggleClass( "ui-corner-all", value );
if ( !this.isToggleSwitch ) {
this.control.toggleClass( "ui-corner-all", value );
}
},
_setDisabled: function( value ) {
value = !!value;
this.element.prop( "disabled", value );
this.slider
.toggleClass( "ui-state-disabled", value )
.attr( "aria-disabled", value );
this.element.toggleClass( "ui-state-disabled", value );
}
}, $.mobile.behaviors.formReset ) );
})( jQuery );
(function( $, undefined ) {
var popup;
function getPopup() {
if ( !popup ) {
popup = $( "", {
"class": "ui-slider-popup ui-shadow ui-corner-all"
});
}
return popup.clone();
}
$.widget( "mobile.slider", $.mobile.slider, {
options: {
popupEnabled: false,
showValue: false
},
_create: function() {
this._super();
$.extend( this, {
_currentValue: null,
_popup: null,
_popupVisible: false
});
this._setOption( "popupEnabled", this.options.popupEnabled );
this._on( this.handle, { "vmousedown" : "_showPopup" } );
this._on( this.slider.add( this.document ), { "vmouseup" : "_hidePopup" } );
this._refresh();
},
// position the popup centered 5px above the handle
_positionPopup: function() {
var dstOffset = this.handle.offset();
this._popup.offset( {
left: dstOffset.left + ( this.handle.width() - this._popup.width() ) / 2,
top: dstOffset.top - this._popup.outerHeight() - 5
});
},
_setOption: function( key, value ) {
this._super( key, value );
if ( key === "showValue" ) {
this.handle.html( value && !this.options.mini ? this._value() : "" );
} else if ( key === "popupEnabled" ) {
if ( value && !this._popup ) {
this._popup = getPopup()
.addClass( "ui-body-" + ( this.options.theme || "a" ) )
.hide()
.insertBefore( this.element );
}
}
},
// show value on the handle and in popup
refresh: function() {
this._super.apply( this, arguments );
this._refresh();
},
_refresh: function() {
var o = this.options, newValue;
if ( o.popupEnabled ) {
// remove the title attribute from the handle (which is
// responsible for the annoying tooltip); NB we have
// to do it here as the jqm slider sets it every time
// the slider's value changes :(
this.handle.removeAttr( "title" );
}
newValue = this._value();
if ( newValue === this._currentValue ) {
return;
}
this._currentValue = newValue;
if ( o.popupEnabled && this._popup ) {
this._positionPopup();
this._popup.html( newValue );
}
if ( o.showValue && !this.options.mini ) {
this.handle.html( newValue );
}
},
_showPopup: function() {
if ( this.options.popupEnabled && !this._popupVisible ) {
this.handle.html( "" );
this._popup.show();
this._positionPopup();
this._popupVisible = true;
}
},
_hidePopup: function() {
var o = this.options;
if ( o.popupEnabled && this._popupVisible ) {
if ( o.showValue && !o.mini ) {
this.handle.html( this._value() );
}
this._popup.hide();
this._popupVisible = false;
}
}
});
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.flipswitch", $.extend({
options: {
onText: "On",
offText: "Off",
theme: null,
enhanced: false,
wrapperClass: null,
corners: true,
mini: false
},
_create: function() {
if ( !this.options.enhanced ) {
this._enhance();
} else {
$.extend( this, {
flipswitch: this.element.parent(),
on: this.element.find( ".ui-flipswitch-on" ).eq( 0 ),
off: this.element.find( ".ui-flipswitch-off" ).eq(0),
type: this.element.get( 0 ).tagName
});
}
this._handleFormReset();
// Transfer tabindex to "on" element and make input unfocusable
this._originalTabIndex = this.element.attr( "tabindex" );
if ( this._originalTabIndex != null ) {
this.on.attr( "tabindex", this._originalTabIndex );
}
this.element.attr( "tabindex", "-1" );
this._on({
"focus" : "_handleInputFocus"
});
if ( this.element.is( ":disabled" ) ) {
this._setOptions({
"disabled": true
});
}
this._on( this.flipswitch, {
"click": "_toggle",
"swipeleft": "_left",
"swiperight": "_right"
});
this._on( this.on, {
"keydown": "_keydown"
});
this._on( {
"change": "refresh"
});
},
_handleInputFocus: function() {
this.on.focus();
},
widget: function() {
return this.flipswitch;
},
_left: function() {
this.flipswitch.removeClass( "ui-flipswitch-active" );
if ( this.type === "SELECT" ) {
this.element.get( 0 ).selectedIndex = 0;
} else {
this.element.prop( "checked", false );
}
this.element.trigger( "change" );
},
_right: function() {
this.flipswitch.addClass( "ui-flipswitch-active" );
if ( this.type === "SELECT" ) {
this.element.get( 0 ).selectedIndex = 1;
} else {
this.element.prop( "checked", true );
}
this.element.trigger( "change" );
},
_enhance: function() {
var flipswitch = $( "
" ),
options = this.options,
element = this.element,
theme = options.theme ? options.theme : "inherit",
// The "on" button is an anchor so it's focusable
on = $( "", {
"href": "#"
}),
off = $( "" ),
type = element.get( 0 ).tagName,
onText = ( type === "INPUT" ) ?
options.onText : element.find( "option" ).eq( 1 ).text(),
offText = ( type === "INPUT" ) ?
options.offText : element.find( "option" ).eq( 0 ).text();
on
.addClass( "ui-flipswitch-on ui-btn ui-shadow ui-btn-inherit" )
.text( onText );
off
.addClass( "ui-flipswitch-off" )
.text( offText );
flipswitch
.addClass( "ui-flipswitch ui-shadow-inset " +
"ui-bar-" + theme + " " +
( options.wrapperClass ? options.wrapperClass : "" ) + " " +
( ( element.is( ":checked" ) ||
element
.find( "option" )
.eq( 1 )
.is( ":selected" ) ) ? "ui-flipswitch-active" : "" ) +
( element.is(":disabled") ? " ui-state-disabled": "") +
( options.corners ? " ui-corner-all": "" ) +
( options.mini ? " ui-mini": "" ) )
.append( on, off );
element
.addClass( "ui-flipswitch-input" )
.after( flipswitch )
.appendTo( flipswitch );
$.extend( this, {
flipswitch: flipswitch,
on: on,
off: off,
type: type
});
},
_reset: function() {
this.refresh();
},
refresh: function() {
var direction,
existingDirection = this.flipswitch.hasClass( "ui-flipswitch-active" ) ? "_right" : "_left";
if ( this.type === "SELECT" ) {
direction = ( this.element.get( 0 ).selectedIndex > 0 ) ? "_right": "_left";
} else {
direction = this.element.prop( "checked" ) ? "_right": "_left";
}
if ( direction !== existingDirection ) {
this[ direction ]();
}
},
_toggle: function() {
var direction = this.flipswitch.hasClass( "ui-flipswitch-active" ) ? "_left" : "_right";
this[ direction ]();
},
_keydown: function( e ) {
if ( e.which === $.mobile.keyCode.LEFT ) {
this._left();
} else if ( e.which === $.mobile.keyCode.RIGHT ) {
this._right();
} else if ( e.which === $.mobile.keyCode.SPACE ) {
this._toggle();
e.preventDefault();
}
},
_setOptions: function( options ) {
if ( options.theme !== undefined ) {
var currentTheme = options.theme ? options.theme : "inherit",
newTheme = options.theme ? options.theme : "inherit";
this.widget()
.removeClass( "ui-bar-" + currentTheme )
.addClass( "ui-bar-" + newTheme );
}
if ( options.onText !== undefined ) {
this.on.text( options.onText );
}
if ( options.offText !== undefined ) {
this.off.text( options.offText );
}
if ( options.disabled !== undefined ) {
this.widget().toggleClass( "ui-state-disabled", options.disabled );
}
if ( options.mini !== undefined ) {
this.widget().toggleClass( "ui-mini", options.mini );
}
if ( options.corners !== undefined ) {
this.widget().toggleClass( "ui-corner-all", options.corners );
}
this._super( options );
},
_destroy: function() {
if ( this.options.enhanced ) {
return;
}
if ( this._originalTabIndex != null ) {
this.element.attr( "tabindex", this._originalTabIndex );
} else {
this.element.removeAttr( "tabindex" );
}
this.on.remove();
this.off.remove();
this.element.unwrap();
this.flipswitch.remove();
this.removeClass( "ui-flipswitch-input" );
}
}, $.mobile.behaviors.formReset ) );
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.rangeslider", $.extend( {
options: {
theme: null,
trackTheme: null,
corners: true,
mini: false,
highlight: true
},
_create: function() {
var $el = this.element,
elClass = this.options.mini ? "ui-rangeslider ui-mini" : "ui-rangeslider",
_inputFirst = $el.find( "input" ).first(),
_inputLast = $el.find( "input" ).last(),
_label = $el.find( "label" ).first(),
_sliderWidgetFirst = $.data( _inputFirst.get( 0 ), "mobile-slider" ) ||
$.data( _inputFirst.slider().get( 0 ), "mobile-slider" ),
_sliderWidgetLast = $.data( _inputLast.get(0), "mobile-slider" ) ||
$.data( _inputLast.slider().get( 0 ), "mobile-slider" ),
_sliderFirst = _sliderWidgetFirst.slider,
_sliderLast = _sliderWidgetLast.slider,
firstHandle = _sliderWidgetFirst.handle,
_sliders = $( "" ).appendTo( $el );
_inputFirst.addClass( "ui-rangeslider-first" );
_inputLast.addClass( "ui-rangeslider-last" );
$el.addClass( elClass );
_sliderFirst.appendTo( _sliders );
_sliderLast.appendTo( _sliders );
_label.insertBefore( $el );
firstHandle.prependTo( _sliderLast );
$.extend( this, {
_inputFirst: _inputFirst,
_inputLast: _inputLast,
_sliderFirst: _sliderFirst,
_sliderLast: _sliderLast,
_label: _label,
_targetVal: null,
_sliderTarget: false,
_sliders: _sliders,
_proxy: false
});
this.refresh();
this._on( this.element.find( "input.ui-slider-input" ), {
"slidebeforestart": "_slidebeforestart",
"slidestop": "_slidestop",
"slidedrag": "_slidedrag",
"slidebeforechange": "_change",
"blur": "_change",
"keyup": "_change"
});
this._on({
"mousedown":"_change"
});
this._on( this.element.closest( "form" ), {
"reset":"_handleReset"
});
this._on( firstHandle, {
"vmousedown": "_dragFirstHandle"
});
},
_handleReset: function() {
var self = this;
//we must wait for the stack to unwind before updateing other wise sliders will not have updated yet
setTimeout( function() {
self._updateHighlight();
},0);
},
_dragFirstHandle: function( event ) {
//if the first handle is dragged send the event to the first slider
$.data( this._inputFirst.get(0), "mobile-slider" ).dragging = true;
$.data( this._inputFirst.get(0), "mobile-slider" ).refresh( event );
$.data( this._inputFirst.get(0), "mobile-slider" )._trigger( "start" );
return false;
},
_slidedrag: function( event ) {
var first = $( event.target ).is( this._inputFirst ),
otherSlider = ( first ) ? this._inputLast : this._inputFirst;
this._sliderTarget = false;
//if the drag was initiated on an extreme and the other handle is focused send the events to
//the closest handle
if ( ( this._proxy === "first" && first ) || ( this._proxy === "last" && !first ) ) {
$.data( otherSlider.get(0), "mobile-slider" ).dragging = true;
$.data( otherSlider.get(0), "mobile-slider" ).refresh( event );
return false;
}
},
_slidestop: function( event ) {
var first = $( event.target ).is( this._inputFirst );
this._proxy = false;
//this stops dragging of the handle and brings the active track to the front
//this makes clicks on the track go the the last handle used
this.element.find( "input" ).trigger( "vmouseup" );
this._sliderFirst.css( "z-index", first ? 1 : "" );
},
_slidebeforestart: function( event ) {
this._sliderTarget = false;
//if the track is the target remember this and the original value
if ( $( event.originalEvent.target ).hasClass( "ui-slider-track" ) ) {
this._sliderTarget = true;
this._targetVal = $( event.target ).val();
}
},
_setOptions: function( options ) {
if ( options.theme !== undefined ) {
this._setTheme( options.theme );
}
if ( options.trackTheme !== undefined ) {
this._setTrackTheme( options.trackTheme );
}
if ( options.mini !== undefined ) {
this._setMini( options.mini );
}
if ( options.highlight !== undefined ) {
this._setHighlight( options.highlight );
}
if ( options.disabled !== undefined ) {
this._setDisabled( options.disabled );
}
this._super( options );
this.refresh();
},
refresh: function() {
var $el = this.element,
o = this.options;
if ( this._inputFirst.is( ":disabled" ) || this._inputLast.is( ":disabled" ) ) {
this.options.disabled = true;
}
$el.find( "input" ).slider({
theme: o.theme,
trackTheme: o.trackTheme,
disabled: o.disabled,
corners: o.corners,
mini: o.mini,
highlight: o.highlight
}).slider( "refresh" );
this._updateHighlight();
},
_change: function( event ) {
if ( event.type === "keyup" ) {
this._updateHighlight();
return false;
}
var self = this,
min = parseFloat( this._inputFirst.val(), 10 ),
max = parseFloat( this._inputLast.val(), 10 ),
first = $( event.target ).hasClass( "ui-rangeslider-first" ),
thisSlider = first ? this._inputFirst : this._inputLast,
otherSlider = first ? this._inputLast : this._inputFirst;
if ( ( this._inputFirst.val() > this._inputLast.val() && event.type === "mousedown" && !$(event.target).hasClass("ui-slider-handle")) ) {
thisSlider.blur();
} else if ( event.type === "mousedown" ) {
return;
}
if ( min > max && !this._sliderTarget ) {
//this prevents min from being greater then max
thisSlider.val( first ? max: min ).slider( "refresh" );
this._trigger( "normalize" );
} else if ( min > max ) {
//this makes it so clicks on the target on either extreme go to the closest handle
thisSlider.val( this._targetVal ).slider( "refresh" );
//You must wait for the stack to unwind so first slider is updated before updating second
setTimeout( function() {
otherSlider.val( first ? min: max ).slider( "refresh" );
$.data( otherSlider.get(0), "mobile-slider" ).handle.focus();
self._sliderFirst.css( "z-index", first ? "" : 1 );
self._trigger( "normalize" );
}, 0 );
this._proxy = ( first ) ? "first" : "last";
}
//fixes issue where when both _sliders are at min they cannot be adjusted
if ( min === max ) {
$.data( thisSlider.get(0), "mobile-slider" ).handle.css( "z-index", 1 );
$.data( otherSlider.get(0), "mobile-slider" ).handle.css( "z-index", 0 );
} else {
$.data( otherSlider.get(0), "mobile-slider" ).handle.css( "z-index", "" );
$.data( thisSlider.get(0), "mobile-slider" ).handle.css( "z-index", "" );
}
this._updateHighlight();
if ( min >= max ) {
return false;
}
},
_updateHighlight: function() {
var min = parseInt( $.data( this._inputFirst.get(0), "mobile-slider" ).handle.get(0).style.left, 10 ),
max = parseInt( $.data( this._inputLast.get(0), "mobile-slider" ).handle.get(0).style.left, 10 ),
width = (max - min);
this.element.find( ".ui-slider-bg" ).css({
"margin-left": min + "%",
"width": width + "%"
});
},
_setTheme: function( value ) {
this._inputFirst.slider( "option", "theme", value );
this._inputLast.slider( "option", "theme", value );
},
_setTrackTheme: function( value ) {
this._inputFirst.slider( "option", "trackTheme", value );
this._inputLast.slider( "option", "trackTheme", value );
},
_setMini: function( value ) {
this._inputFirst.slider( "option", "mini", value );
this._inputLast.slider( "option", "mini", value );
this.element.toggleClass( "ui-mini", !!value );
},
_setHighlight: function( value ) {
this._inputFirst.slider( "option", "highlight", value );
this._inputLast.slider( "option", "highlight", value );
},
_setDisabled: function( value ) {
this._inputFirst.prop( "disabled", value );
this._inputLast.prop( "disabled", value );
},
_destroy: function() {
this._label.prependTo( this.element );
this.element.removeClass( "ui-rangeslider ui-mini" );
this._inputFirst.after( this._sliderFirst );
this._inputLast.after( this._sliderLast );
this._sliders.remove();
this.element.find( "input" ).removeClass( "ui-rangeslider-first ui-rangeslider-last" ).slider( "destroy" );
}
}, $.mobile.behaviors.formReset ) );
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.textinput", $.mobile.textinput, {
options: {
clearBtn: false,
clearBtnText: "Clear text"
},
_create: function() {
this._super();
if ( this.isSearch ) {
this.options.clearBtn = true;
}
if ( !!this.options.clearBtn && this.inputNeedsWrap ) {
this._addClearBtn();
}
},
clearButton: function() {
return $( "" +
"" )
.attr( "title", this.options.clearBtnText )
.text( this.options.clearBtnText );
},
_clearBtnClick: function( event ) {
this.element.val( "" )
.focus()
.trigger( "change" );
this._clearBtn.addClass( "ui-input-clear-hidden" );
event.preventDefault();
},
_addClearBtn: function() {
if ( !this.options.enhanced ) {
this._enhanceClear();
}
$.extend( this, {
_clearBtn: this.widget().find("a.ui-input-clear")
});
this._bindClearEvents();
this._toggleClear();
},
_enhanceClear: function() {
this.clearButton().appendTo( this.widget() );
this.widget().addClass( "ui-input-has-clear" );
},
_bindClearEvents: function() {
this._on( this._clearBtn, {
"click": "_clearBtnClick"
});
this._on({
"keyup": "_toggleClear",
"change": "_toggleClear",
"input": "_toggleClear",
"focus": "_toggleClear",
"blur": "_toggleClear",
"cut": "_toggleClear",
"paste": "_toggleClear"
});
},
_unbindClear: function() {
this._off( this._clearBtn, "click");
this._off( this.element, "keyup change input focus blur cut paste" );
},
_setOptions: function( options ) {
this._super( options );
if ( options.clearBtn !== undefined &&
!this.element.is( "textarea, :jqmData(type='range')" ) ) {
if ( options.clearBtn ) {
this._addClearBtn();
} else {
this._destroyClear();
}
}
if ( options.clearBtnText !== undefined && this._clearBtn !== undefined ) {
this._clearBtn.text( options.clearBtnText )
.attr("title", options.clearBtnText);
}
},
_toggleClear: function() {
this._delay( "_toggleClearClass", 0 );
},
_toggleClearClass: function() {
this._clearBtn.toggleClass( "ui-input-clear-hidden", !this.element.val() );
},
_destroyClear: function() {
this.widget().removeClass( "ui-input-has-clear" );
this._unbindClear();
this._clearBtn.remove();
},
_destroy: function() {
this._super();
if ( this.options.clearBtn ) {
this._destroyClear();
}
}
});
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.textinput", $.mobile.textinput, {
options: {
autogrow:true,
keyupTimeoutBuffer: 100
},
_create: function() {
this._super();
if ( this.options.autogrow && this.isTextarea ) {
this._autogrow();
}
},
_autogrow: function() {
this.element.addClass( "ui-textinput-autogrow" );
this._on({
"keyup": "_timeout",
"change": "_timeout",
"input": "_timeout",
"paste": "_timeout"
});
// Attach to the various you-have-become-visible notifications that the
// various framework elements emit.
// TODO: Remove all but the updatelayout handler once #6426 is fixed.
this._on( true, this.document, {
// TODO: Move to non-deprecated event
"pageshow": "_handleShow",
"popupbeforeposition": "_handleShow",
"updatelayout": "_handleShow",
"panelopen": "_handleShow"
});
},
// Synchronously fix the widget height if this widget's parents are such
// that they show/hide content at runtime. We still need to check whether
// the widget is actually visible in case it is contained inside multiple
// such containers. For example: panel contains collapsible contains
// autogrow textinput. The panel may emit "panelopen" indicating that its
// content has become visible, but the collapsible is still collapsed, so
// the autogrow textarea is still not visible.
_handleShow: function( event ) {
if ( $.contains( event.target, this.element[ 0 ] ) &&
this.element.is( ":visible" ) ) {
if ( event.type !== "popupbeforeposition" ) {
this.element
.addClass( "ui-textinput-autogrow-resize" )
.animationComplete(
$.proxy( function() {
this.element.removeClass( "ui-textinput-autogrow-resize" );
}, this ),
"transition" );
}
this._prepareHeightUpdate();
}
},
_unbindAutogrow: function() {
this.element.removeClass( "ui-textinput-autogrow" );
this._off( this.element, "keyup change input paste" );
this._off( this.document,
"pageshow popupbeforeposition updatelayout panelopen" );
},
keyupTimeout: null,
_prepareHeightUpdate: function( delay ) {
if ( this.keyupTimeout ) {
clearTimeout( this.keyupTimeout );
}
if ( delay === undefined ) {
this._updateHeight();
} else {
this.keyupTimeout = this._delay( "_updateHeight", delay );
}
},
_timeout: function() {
this._prepareHeightUpdate( this.options.keyupTimeoutBuffer );
},
_updateHeight: function() {
var paddingTop, paddingBottom, paddingHeight, scrollHeight, clientHeight,
borderTop, borderBottom, borderHeight, height,
scrollTop = this.window.scrollTop();
this.keyupTimeout = 0;
// IE8 textareas have the onpage property - others do not
if ( !( "onpage" in this.element[ 0 ] ) ) {
this.element.css({
"height": 0,
"min-height": 0,
"max-height": 0
});
}
scrollHeight = this.element[ 0 ].scrollHeight;
clientHeight = this.element[ 0 ].clientHeight;
borderTop = parseFloat( this.element.css( "border-top-width" ) );
borderBottom = parseFloat( this.element.css( "border-bottom-width" ) );
borderHeight = borderTop + borderBottom;
height = scrollHeight + borderHeight + 15;
// Issue 6179: Padding is not included in scrollHeight and
// clientHeight by Firefox if no scrollbar is visible. Because
// textareas use the border-box box-sizing model, padding should be
// included in the new (assigned) height. Because the height is set
// to 0, clientHeight == 0 in Firefox. Therefore, we can use this to
// check if padding must be added.
if ( clientHeight === 0 ) {
paddingTop = parseFloat( this.element.css( "padding-top" ) );
paddingBottom = parseFloat( this.element.css( "padding-bottom" ) );
paddingHeight = paddingTop + paddingBottom;
height += paddingHeight;
}
this.element.css({
"height": height,
"min-height": "",
"max-height": ""
});
this.window.scrollTop( scrollTop );
},
refresh: function() {
if ( this.options.autogrow && this.isTextarea ) {
this._updateHeight();
}
},
_setOptions: function( options ) {
this._super( options );
if ( options.autogrow !== undefined && this.isTextarea ) {
if ( options.autogrow ) {
this._autogrow();
} else {
this._unbindAutogrow();
}
}
}
});
})( jQuery );
(function( $, undefined ) {
$.widget( "mobile.selectmenu", $.extend( {
initSelector: "select:not( :jqmData(role='slider')):not( :jqmData(role='flipswitch') )",
options: {
theme: null,
icon: "carat-d",
iconpos: "right",
inline: false,
corners: true,
shadow: true,
iconshadow: false, /* TODO: Deprecated in 1.4, remove in 1.5. */
overlayTheme: null,
dividerTheme: null,
hidePlaceholderMenuItems: true,
closeText: "Close",
nativeMenu: true,
// This option defaults to true on iOS devices.
preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1,
mini: false
},
_button: function() {
return $( "" );
},
_setDisabled: function( value ) {
this.element.attr( "disabled", value );
this.button.attr( "aria-disabled", value );
return this._setOption( "disabled", value );
},
_focusButton : function() {
var self = this;
setTimeout( function() {
self.button.focus();
}, 40);
},
_selectOptions: function() {
return this.select.find( "option" );
},
// setup items that are generally necessary for select menu extension
_preExtension: function() {
var inline = this.options.inline || this.element.jqmData( "inline" ),
mini = this.options.mini || this.element.jqmData( "mini" ),
classes = "";
// TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577
/* if ( $el[0].className.length ) {
classes = $el[0].className;
} */
if ( !!~this.element[0].className.indexOf( "ui-btn-left" ) ) {
classes = " ui-btn-left";
}
if ( !!~this.element[0].className.indexOf( "ui-btn-right" ) ) {
classes = " ui-btn-right";
}
if ( inline ) {
classes += " ui-btn-inline";
}
if ( mini ) {
classes += " ui-mini";
}
this.select = this.element.removeClass( "ui-btn-left ui-btn-right" ).wrap( "
" );
this.selectId = this.select.attr( "id" ) || ( "select-" + this.uuid );
this.buttonId = this.selectId + "-button";
this.label = $( "label[for='"+ this.selectId +"']" );
this.isMultiple = this.select[ 0 ].multiple;
},
_destroy: function() {
var wrapper = this.element.parents( ".ui-select" );
if ( wrapper.length > 0 ) {
if ( wrapper.is( ".ui-btn-left, .ui-btn-right" ) ) {
this.element.addClass( wrapper.hasClass( "ui-btn-left" ) ? "ui-btn-left" : "ui-btn-right" );
}
this.element.insertAfter( wrapper );
wrapper.remove();
}
},
_create: function() {
this._preExtension();
this.button = this._button();
var self = this,
options = this.options,
iconpos = options.icon ? ( options.iconpos || this.select.jqmData( "iconpos" ) ) : false,
button = this.button
.insertBefore( this.select )
.attr( "id", this.buttonId )
.addClass( "ui-btn" +
( options.icon ? ( " ui-icon-" + options.icon + " ui-btn-icon-" + iconpos +
( options.iconshadow ? " ui-shadow-icon" : "" ) ) : "" ) + /* TODO: Remove in 1.5. */
( options.theme ? " ui-btn-" + options.theme : "" ) +
( options.corners ? " ui-corner-all" : "" ) +
( options.shadow ? " ui-shadow" : "" ) );
this.setButtonText();
// Opera does not properly support opacity on select elements
// In Mini, it hides the element, but not its text
// On the desktop,it seems to do the opposite
// for these reasons, using the nativeMenu option results in a full native select in Opera
if ( options.nativeMenu && window.opera && window.opera.version ) {
button.addClass( "ui-select-nativeonly" );
}
// Add counter for multi selects
if ( this.isMultiple ) {
this.buttonCount = $( "" )
.addClass( "ui-li-count ui-body-inherit" )
.hide()
.appendTo( button.addClass( "ui-li-has-count" ) );
}
// Disable if specified
if ( options.disabled || this.element.attr( "disabled" )) {
this.disable();
}
// Events on native select
this.select.change(function() {
self.refresh();
if ( !!options.nativeMenu ) {
self._delay( function() {
self.select.blur();
});
}
});
this._handleFormReset();
this._on( this.button, {
keydown: "_handleKeydown"
});
this.build();
},
build: function() {
var self = this;
this.select
.appendTo( self.button )
.bind( "vmousedown", function() {
// Add active class to button
self.button.addClass( $.mobile.activeBtnClass );
})
.bind( "focus", function() {
self.button.addClass( $.mobile.focusClass );
})
.bind( "blur", function() {
self.button.removeClass( $.mobile.focusClass );
})
.bind( "focus vmouseover", function() {
self.button.trigger( "vmouseover" );
})
.bind( "vmousemove", function() {
// Remove active class on scroll/touchmove
self.button.removeClass( $.mobile.activeBtnClass );
})
.bind( "change blur vmouseout", function() {
self.button.trigger( "vmouseout" )
.removeClass( $.mobile.activeBtnClass );
});
// In many situations, iOS will zoom into the select upon tap, this prevents that from happening
self.button.bind( "vmousedown", function() {
if ( self.options.preventFocusZoom ) {
$.mobile.zoom.disable( true );
}
});
self.label.bind( "click focus", function() {
if ( self.options.preventFocusZoom ) {
$.mobile.zoom.disable( true );
}
});
self.select.bind( "focus", function() {
if ( self.options.preventFocusZoom ) {
$.mobile.zoom.disable( true );
}
});
self.button.bind( "mouseup", function() {
if ( self.options.preventFocusZoom ) {
setTimeout(function() {
$.mobile.zoom.enable( true );
}, 0 );
}
});
self.select.bind( "blur", function() {
if ( self.options.preventFocusZoom ) {
$.mobile.zoom.enable( true );
}
});
},
selected: function() {
return this._selectOptions().filter( ":selected" );
},
selectedIndices: function() {
var self = this;
return this.selected().map(function() {
return self._selectOptions().index( this );
}).get();
},
setButtonText: function() {
var self = this,
selected = this.selected(),
text = this.placeholder,
span = $( document.createElement( "span" ) );
this.button.children( "span" ).not( ".ui-li-count" ).remove().end().end().prepend( (function() {
if ( selected.length ) {
text = selected.map(function() {
return $( this ).text();
}).get().join( ", " );
} else {
text = self.placeholder;
}
if ( text ) {
span.text( text );
} else {
// Set the contents to which we write as to be XHTML compliant - see gh-6699
span.html( " " );
}
// TODO possibly aggregate multiple select option classes
return span
.addClass( self.select.attr( "class" ) )
.addClass( selected.attr( "class" ) )
.removeClass( "ui-screen-hidden" );
})());
},
setButtonCount: function() {
var selected = this.selected();
// multiple count inside button
if ( this.isMultiple ) {
this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length );
}
},
_handleKeydown: function( /* event */ ) {
this._delay( "_refreshButton" );
},
_reset: function() {
this.refresh();
},
_refreshButton: function() {
this.setButtonText();
this.setButtonCount();
},
refresh: function() {
this._refreshButton();
},
// open and close preserved in native selects
// to simplify users code when looping over selects
open: $.noop,
close: $.noop,
disable: function() {
this._setDisabled( true );
this.button.addClass( "ui-state-disabled" );
},
enable: function() {
this._setDisabled( false );
this.button.removeClass( "ui-state-disabled" );
}
}, $.mobile.behaviors.formReset ) );
})( jQuery );
(function( $, undefined ) {
$.mobile.links = function( target ) {
//links within content areas, tests included with page
$( target )
.find( "a" )
.jqmEnhanceable()
.filter( ":jqmData(rel='popup')[href][href!='']" )
.each( function() {
// Accessibility info for popups
var element = this,
idref = element.getAttribute( "href" ).substring( 1 );
if ( idref ) {
element.setAttribute( "aria-haspopup", true );
element.setAttribute( "aria-owns", idref );
element.setAttribute( "aria-expanded", false );
}
})
.end()
.not( ".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" )
.addClass( "ui-link" );
};
})( jQuery );
(function( $, undefined ) {
function fitSegmentInsideSegment( windowSize, segmentSize, offset, desired ) {
var returnValue = desired;
if ( windowSize < segmentSize ) {
// Center segment if it's bigger than the window
returnValue = offset + ( windowSize - segmentSize ) / 2;
} else {
// Otherwise center it at the desired coordinate while keeping it completely inside the window
returnValue = Math.min( Math.max( offset, desired - segmentSize / 2 ), offset + windowSize - segmentSize );
}
return returnValue;
}
function getWindowCoordinates( theWindow ) {
return {
x: theWindow.scrollLeft(),
y: theWindow.scrollTop(),
cx: ( theWindow[ 0 ].innerWidth || theWindow.width() ),
cy: ( theWindow[ 0 ].innerHeight || theWindow.height() )
};
}
$.widget( "mobile.popup", {
options: {
wrapperClass: null,
theme: null,
overlayTheme: null,
shadow: true,
corners: true,
transition: "none",
positionTo: "origin",
tolerance: null,
closeLinkSelector: "a:jqmData(rel='back')",
closeLinkEvents: "click.popup",
navigateEvents: "navigate.popup",
closeEvents: "navigate.popup pagebeforechange.popup",
dismissible: true,
enhanced: false,
// NOTE Windows Phone 7 has a scroll position caching issue that
// requires us to disable popup history management by default
// https://github.com/jquery/jquery-mobile/issues/4784
//
// NOTE this option is modified in _create!
history: !$.mobile.browser.oldIE
},
// When the user depresses the mouse/finger on an element inside the popup while the popup is
// open, we ignore resize events for a short while. This prevents #6961.
_handleDocumentVmousedown: function( theEvent ) {
if ( this._isOpen && $.contains( this._ui.container[ 0 ], theEvent.target ) ) {
this._ignoreResizeEvents();
}
},
_create: function() {
var theElement = this.element,
myId = theElement.attr( "id" ),
currentOptions = this.options;
// We need to adjust the history option to be false if there's no AJAX nav.
// We can't do it in the option declarations because those are run before
// it is determined whether there shall be AJAX nav.
currentOptions.history = currentOptions.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
this._on( this.document, {
"vmousedown": "_handleDocumentVmousedown"
});
// Define instance variables
$.extend( this, {
_scrollTop: 0,
_page: theElement.closest( ".ui-page" ),
_ui: null,
_fallbackTransition: "",
_currentTransition: false,
_prerequisites: null,
_isOpen: false,
_tolerance: null,
_resizeData: null,
_ignoreResizeTo: 0,
_orientationchangeInProgress: false
});
if ( this._page.length === 0 ) {
this._page = $( "body" );
}
if ( currentOptions.enhanced ) {
this._ui = {
container: theElement.parent(),
screen: theElement.parent().prev(),
placeholder: $( this.document[ 0 ].getElementById( myId + "-placeholder" ) )
};
} else {
this._ui = this._enhance( theElement, myId );
this._applyTransition( currentOptions.transition );
}
this
._setTolerance( currentOptions.tolerance )
._ui.focusElement = this._ui.container;
// Event handlers
this._on( this._ui.screen, { "vclick": "_eatEventAndClose" } );
this._on( this.window, {
orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
resize: $.proxy( this, "_handleWindowResize" ),
keyup: $.proxy( this, "_handleWindowKeyUp" )
});
this._on( this.document, { "focusin": "_handleDocumentFocusIn" } );
},
_enhance: function( theElement, myId ) {
var currentOptions = this.options,
wrapperClass = currentOptions.wrapperClass,
ui = {
screen: $( "" ),
placeholder: $( "" ),
container: $( "" )
},
fragment = this.document[ 0 ].createDocumentFragment();
fragment.appendChild( ui.screen[ 0 ] );
fragment.appendChild( ui.container[ 0 ] );
if ( myId ) {
ui.screen.attr( "id", myId + "-screen" );
ui.container.attr( "id", myId + "-popup" );
ui.placeholder
.attr( "id", myId + "-placeholder" )
.html( "" );
}
// Apply the proto
this._page[ 0 ].appendChild( fragment );
// Leave a placeholder where the element used to be
ui.placeholder.insertAfter( theElement );
theElement
.detach()
.addClass( "ui-popup " +
this._themeClassFromOption( "ui-body-", currentOptions.theme ) + " " +
( currentOptions.shadow ? "ui-overlay-shadow " : "" ) +
( currentOptions.corners ? "ui-corner-all " : "" ) )
.appendTo( ui.container );
return ui;
},
_eatEventAndClose: function( theEvent ) {
theEvent.preventDefault();
theEvent.stopImmediatePropagation();
if ( this.options.dismissible ) {
this.close();
}
return false;
},
// Make sure the screen covers the entire document - CSS is sometimes not
// enough to accomplish this.
_resizeScreen: function() {
var screen = this._ui.screen,
popupHeight = this._ui.container.outerHeight( true ),
screenHeight = screen.removeAttr( "style" ).height(),
// Subtracting 1 here is necessary for an obscure Andrdoid 4.0 bug where
// the browser hangs if the screen covers the entire document :/
documentHeight = this.document.height() - 1;
if ( screenHeight < documentHeight ) {
screen.height( documentHeight );
} else if ( popupHeight > screenHeight ) {
screen.height( popupHeight );
}
},
_handleWindowKeyUp: function( theEvent ) {
if ( this._isOpen && theEvent.keyCode === $.mobile.keyCode.ESCAPE ) {
return this._eatEventAndClose( theEvent );
}
},
_expectResizeEvent: function() {
var windowCoordinates = getWindowCoordinates( this.window );
if ( this._resizeData ) {
if ( windowCoordinates.x === this._resizeData.windowCoordinates.x &&
windowCoordinates.y === this._resizeData.windowCoordinates.y &&
windowCoordinates.cx === this._resizeData.windowCoordinates.cx &&
windowCoordinates.cy === this._resizeData.windowCoordinates.cy ) {
// timeout not refreshed
return false;
} else {
// clear existing timeout - it will be refreshed below
clearTimeout( this._resizeData.timeoutId );
}
}
this._resizeData = {
timeoutId: this._delay( "_resizeTimeout", 200 ),
windowCoordinates: windowCoordinates
};
return true;
},
_resizeTimeout: function() {
if ( this._isOpen ) {
if ( !this._expectResizeEvent() ) {
if ( this._ui.container.hasClass( "ui-popup-hidden" ) ) {
// effectively rapid-open the popup while leaving the screen intact
this._ui.container.removeClass( "ui-popup-hidden ui-popup-truncate" );
this.reposition( { positionTo: "window" } );
this._ignoreResizeEvents();
}
this._resizeScreen();
this._resizeData = null;
this._orientationchangeInProgress = false;
}
} else {
this._resizeData = null;
this._orientationchangeInProgress = false;
}
},
_stopIgnoringResizeEvents: function() {
this._ignoreResizeTo = 0;
},
_ignoreResizeEvents: function() {
if ( this._ignoreResizeTo ) {
clearTimeout( this._ignoreResizeTo );
}
this._ignoreResizeTo = this._delay( "_stopIgnoringResizeEvents", 1000 );
},
_handleWindowResize: function(/* theEvent */) {
if ( this._isOpen && this._ignoreResizeTo === 0 ) {
if ( ( this._expectResizeEvent() || this._orientationchangeInProgress ) &&
!this._ui.container.hasClass( "ui-popup-hidden" ) ) {
// effectively rapid-close the popup while leaving the screen intact
this._ui.container
.addClass( "ui-popup-hidden ui-popup-truncate" )
.removeAttr( "style" );
}
}
},
_handleWindowOrientationchange: function(/* theEvent */) {
if ( !this._orientationchangeInProgress && this._isOpen && this._ignoreResizeTo === 0 ) {
this._expectResizeEvent();
this._orientationchangeInProgress = true;
}
},
// When the popup is open, attempting to focus on an element that is not a
// child of the popup will redirect focus to the popup
_handleDocumentFocusIn: function( theEvent ) {
var target,
targetElement = theEvent.target,
ui = this._ui;
if ( !this._isOpen ) {
return;
}
if ( targetElement !== ui.container[ 0 ] ) {
target = $( targetElement );
if ( !$.contains( ui.container[ 0 ], targetElement ) ) {
$( this.document[ 0 ].activeElement ).one( "focus", $.proxy( function() {
this._safelyBlur( targetElement );
}, this ) );
ui.focusElement.focus();
theEvent.preventDefault();
theEvent.stopImmediatePropagation();
return false;
} else if ( ui.focusElement[ 0 ] === ui.container[ 0 ] ) {
ui.focusElement = target;
}
}
this._ignoreResizeEvents();
},
_themeClassFromOption: function( prefix, value ) {
return ( value ? ( value === "none" ? "" : ( prefix + value ) ) : ( prefix + "inherit" ) );
},
_applyTransition: function( value ) {
if ( value ) {
this._ui.container.removeClass( this._fallbackTransition );
if ( value !== "none" ) {
this._fallbackTransition = $.mobile._maybeDegradeTransition( value );
if ( this._fallbackTransition === "none" ) {
this._fallbackTransition = "";
}
this._ui.container.addClass( this._fallbackTransition );
}
}
return this;
},
_setOptions: function( newOptions ) {
var currentOptions = this.options,
theElement = this.element,
screen = this._ui.screen;
if ( newOptions.wrapperClass !== undefined ) {
this._ui.container
.removeClass( currentOptions.wrapperClass )
.addClass( newOptions.wrapperClass );
}
if ( newOptions.theme !== undefined ) {
theElement
.removeClass( this._themeClassFromOption( "ui-body-", currentOptions.theme ) )
.addClass( this._themeClassFromOption( "ui-body-", newOptions.theme ) );
}
if ( newOptions.overlayTheme !== undefined ) {
screen
.removeClass( this._themeClassFromOption( "ui-overlay-", currentOptions.overlayTheme ) )
.addClass( this._themeClassFromOption( "ui-overlay-", newOptions.overlayTheme ) );
if ( this._isOpen ) {
screen.addClass( "in" );
}
}
if ( newOptions.shadow !== undefined ) {
theElement.toggleClass( "ui-overlay-shadow", newOptions.shadow );
}
if ( newOptions.corners !== undefined ) {
theElement.toggleClass( "ui-corner-all", newOptions.corners );
}
if ( newOptions.transition !== undefined ) {
if ( !this._currentTransition ) {
this._applyTransition( newOptions.transition );
}
}
if ( newOptions.tolerance !== undefined ) {
this._setTolerance( newOptions.tolerance );
}
if ( newOptions.disabled !== undefined ) {
if ( newOptions.disabled ) {
this.close();
}
}
return this._super( newOptions );
},
_setTolerance: function( value ) {
var tol = { t: 30, r: 15, b: 30, l: 15 },
ar;
if ( value !== undefined ) {
ar = String( value ).split( "," );
$.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
switch( ar.length ) {
// All values are to be the same
case 1:
if ( !isNaN( ar[ 0 ] ) ) {
tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
}
break;
// The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
case 2:
if ( !isNaN( ar[ 0 ] ) ) {
tol.t = tol.b = ar[ 0 ];
}
if ( !isNaN( ar[ 1 ] ) ) {
tol.l = tol.r = ar[ 1 ];
}
break;
// The array contains values in the order top, right, bottom, left
case 4:
if ( !isNaN( ar[ 0 ] ) ) {
tol.t = ar[ 0 ];
}
if ( !isNaN( ar[ 1 ] ) ) {
tol.r = ar[ 1 ];
}
if ( !isNaN( ar[ 2 ] ) ) {
tol.b = ar[ 2 ];
}
if ( !isNaN( ar[ 3 ] ) ) {
tol.l = ar[ 3 ];
}
break;
default:
break;
}
}
this._tolerance = tol;
return this;
},
_clampPopupWidth: function( infoOnly ) {
var menuSize,
windowCoordinates = getWindowCoordinates( this.window ),
// rectangle within which the popup must fit
rectangle = {
x: this._tolerance.l,
y: windowCoordinates.y + this._tolerance.t,
cx: windowCoordinates.cx - this._tolerance.l - this._tolerance.r,
cy: windowCoordinates.cy - this._tolerance.t - this._tolerance.b
};
if ( !infoOnly ) {
// Clamp the width of the menu before grabbing its size
this._ui.container.css( "max-width", rectangle.cx );
}
menuSize = {
cx: this._ui.container.outerWidth( true ),
cy: this._ui.container.outerHeight( true )
};
return { rc: rectangle, menuSize: menuSize };
},
_calculateFinalLocation: function( desired, clampInfo ) {
var returnValue,
rectangle = clampInfo.rc,
menuSize = clampInfo.menuSize;
// Center the menu over the desired coordinates, while not going outside
// the window tolerances. This will center wrt. the window if the popup is
// too large.
returnValue = {
left: fitSegmentInsideSegment( rectangle.cx, menuSize.cx, rectangle.x, desired.x ),
top: fitSegmentInsideSegment( rectangle.cy, menuSize.cy, rectangle.y, desired.y )
};
// Make sure the top of the menu is visible
returnValue.top = Math.max( 0, returnValue.top );
// If the height of the menu is smaller than the height of the document
// align the bottom with the bottom of the document
returnValue.top -= Math.min( returnValue.top,
Math.max( 0, returnValue.top + menuSize.cy - this.document.height() ) );
return returnValue;
},
// Try and center the overlay over the given coordinates
_placementCoords: function( desired ) {
return this._calculateFinalLocation( desired, this._clampPopupWidth() );
},
_createPrerequisites: function( screenPrerequisite, containerPrerequisite, whenDone ) {
var prerequisites,
self = this;
// It is important to maintain both the local variable prerequisites and
// self._prerequisites. The local variable remains in the closure of the
// functions which call the callbacks passed in. The comparison between the
// local variable and self._prerequisites is necessary, because once a
// function has been passed to .animationComplete() it will be called next
// time an animation completes, even if that's not the animation whose end
// the function was supposed to catch (for example, if an abort happens
// during the opening animation, the .animationComplete handler is not
// called for that animation anymore, but the handler remains attached, so
// it is called the next time the popup is opened - making it stale.
// Comparing the local variable prerequisites to the widget-level variable
// self._prerequisites ensures that callbacks triggered by a stale
// .animationComplete will be ignored.
prerequisites = {
screen: $.Deferred(),
container: $.Deferred()
};
prerequisites.screen.then( function() {
if ( prerequisites === self._prerequisites ) {
screenPrerequisite();
}
});
prerequisites.container.then( function() {
if ( prerequisites === self._prerequisites ) {
containerPrerequisite();
}
});
$.when( prerequisites.screen, prerequisites.container ).done( function() {
if ( prerequisites === self._prerequisites ) {
self._prerequisites = null;
whenDone();
}
});
self._prerequisites = prerequisites;
},
_animate: function( args ) {
// NOTE before removing the default animation of the screen
// this had an animate callback that would resolve the deferred
// now the deferred is resolved immediately
// TODO remove the dependency on the screen deferred
this._ui.screen
.removeClass( args.classToRemove )
.addClass( args.screenClassToAdd );
args.prerequisites.screen.resolve();
if ( args.transition && args.transition !== "none" ) {
if ( args.applyTransition ) {
this._applyTransition( args.transition );
}
if ( this._fallbackTransition ) {
this._ui.container
.addClass( args.containerClassToAdd )
.removeClass( args.classToRemove )
.animationComplete( $.proxy( args.prerequisites.container, "resolve" ) );
return;
}
}
this._ui.container.removeClass( args.classToRemove );
args.prerequisites.container.resolve();
},
// The desired coordinates passed in will be returned untouched if no reference element can be identified via
// desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid
// x and y coordinates by specifying the center middle of the window if the coordinates are absent.
// options: { x: coordinate, y: coordinate, positionTo: string: "origin", "window", or jQuery selector
_desiredCoords: function( openOptions ) {
var offset,
dst = null,
windowCoordinates = getWindowCoordinates( this.window ),
x = openOptions.x,
y = openOptions.y,
pTo = openOptions.positionTo;
// Establish which element will serve as the reference
if ( pTo && pTo !== "origin" ) {
if ( pTo === "window" ) {
x = windowCoordinates.cx / 2 + windowCoordinates.x;
y = windowCoordinates.cy / 2 + windowCoordinates.y;
} else {
try {
dst = $( pTo );
} catch( err ) {
dst = null;
}
if ( dst ) {
dst.filter( ":visible" );
if ( dst.length === 0 ) {
dst = null;
}
}
}
}
// If an element was found, center over it
if ( dst ) {
offset = dst.offset();
x = offset.left + dst.outerWidth() / 2;
y = offset.top + dst.outerHeight() / 2;
}
// Make sure x and y are valid numbers - center over the window
if ( $.type( x ) !== "number" || isNaN( x ) ) {
x = windowCoordinates.cx / 2 + windowCoordinates.x;
}
if ( $.type( y ) !== "number" || isNaN( y ) ) {
y = windowCoordinates.cy / 2 + windowCoordinates.y;
}
return { x: x, y: y };
},
_reposition: function( openOptions ) {
// We only care about position-related parameters for repositioning
openOptions = {
x: openOptions.x,
y: openOptions.y,
positionTo: openOptions.positionTo
};
this._trigger( "beforeposition", undefined, openOptions );
this._ui.container.offset( this._placementCoords( this._desiredCoords( openOptions ) ) );
},
reposition: function( openOptions ) {
if ( this._isOpen ) {
this._reposition( openOptions );
}
},
_safelyBlur: function( currentElement ){
if ( currentElement !== this.window[ 0 ] &&
currentElement.nodeName.toLowerCase() !== "body" ) {
$( currentElement ).blur();
}
},
_openPrerequisitesComplete: function() {
var id = this.element.attr( "id" ),
firstFocus = this._ui.container.find( ":focusable" ).first();
this._ui.container.addClass( "ui-popup-active" );
this._isOpen = true;
this._resizeScreen();
// Check to see if currElement is not a child of the container. If it's not, blur
if ( !$.contains( this._ui.container[ 0 ], this.document[ 0 ].activeElement ) ) {
this._safelyBlur( this.document[ 0 ].activeElement );
}
if ( firstFocus.length > 0 ) {
this._ui.focusElement = firstFocus;
}
this._ignoreResizeEvents();
if ( id ) {
this.document.find( "[aria-haspopup='true'][aria-owns='" + id + "']" ).attr( "aria-expanded", true );
}
this._trigger( "afteropen" );
},
_open: function( options ) {
var openOptions = $.extend( {}, this.options, options ),
// TODO move blacklist to private method
androidBlacklist = ( function() {
var ua = navigator.userAgent,
// Rendering engine is Webkit, and capture major version
wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ),
wkversion = !!wkmatch && wkmatch[ 1 ],
androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ),
andversion = !!androidmatch && androidmatch[ 1 ],
chromematch = ua.indexOf( "Chrome" ) > -1;
// Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome.
if ( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) {
return true;
}
return false;
}());
// Count down to triggering "popupafteropen" - we have two prerequisites:
// 1. The popup window animation completes (container())
// 2. The screen opacity animation completes (screen())
this._createPrerequisites(
$.noop,
$.noop,
$.proxy( this, "_openPrerequisitesComplete" ) );
this._currentTransition = openOptions.transition;
this._applyTransition( openOptions.transition );
this._ui.screen.removeClass( "ui-screen-hidden" );
this._ui.container.removeClass( "ui-popup-truncate" );
// Give applications a chance to modify the contents of the container before it appears
this._reposition( openOptions );
this._ui.container.removeClass( "ui-popup-hidden" );
if ( this.options.overlayTheme && androidBlacklist ) {
/* TODO: The native browser on Android 4.0.X ("Ice Cream Sandwich") suffers from an issue where the popup overlay appears to be z-indexed above the popup itself when certain other styles exist on the same page -- namely, any element set to `position: fixed` and certain types of input. These issues are reminiscent of previously uncovered bugs in older versions of Android's native browser: https://github.com/scottjehl/Device-Bugs/issues/3
This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
https://github.com/jquery/jquery-mobile/issues/4816
https://github.com/jquery/jquery-mobile/issues/4844
https://github.com/jquery/jquery-mobile/issues/4874
*/
// TODO sort out why this._page isn't working
this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
}
this._animate({
additionalCondition: true,
transition: openOptions.transition,
classToRemove: "",
screenClassToAdd: "in",
containerClassToAdd: "in",
applyTransition: false,
prerequisites: this._prerequisites
});
},
_closePrerequisiteScreen: function() {
this._ui.screen
.removeClass( "out" )
.addClass( "ui-screen-hidden" );
},
_closePrerequisiteContainer: function() {
this._ui.container
.removeClass( "reverse out" )
.addClass( "ui-popup-hidden ui-popup-truncate" )
.removeAttr( "style" );
},
_closePrerequisitesDone: function() {
var container = this._ui.container,
id = this.element.attr( "id" );
// remove the global mutex for popups
$.mobile.popup.active = undefined;
// Blur elements inside the container, including the container
$( ":focus", container[ 0 ] ).add( container[ 0 ] ).blur();
if ( id ) {
this.document.find( "[aria-haspopup='true'][aria-owns='" + id + "']" ).attr( "aria-expanded", false );
}
// alert users that the popup is closed
this._trigger( "afterclose" );
},
_close: function( immediate ) {
this._ui.container.removeClass( "ui-popup-active" );
this._page.removeClass( "ui-popup-open" );
this._isOpen = false;
// Count down to triggering "popupafterclose" - we have two prerequisites:
// 1. The popup window reverse animation completes (container())
// 2. The screen opacity animation completes (screen())
this._createPrerequisites(
$.proxy( this, "_closePrerequisiteScreen" ),
$.proxy( this, "_closePrerequisiteContainer" ),
$.proxy( this, "_closePrerequisitesDone" ) );
this._animate( {
additionalCondition: this._ui.screen.hasClass( "in" ),
transition: ( immediate ? "none" : ( this._currentTransition ) ),
classToRemove: "in",
screenClassToAdd: "out",
containerClassToAdd: "reverse out",
applyTransition: true,
prerequisites: this._prerequisites
});
},
_unenhance: function() {
if ( this.options.enhanced ) {
return;
}
// Put the element back to where the placeholder was and remove the "ui-popup" class
this._setOptions( { theme: $.mobile.popup.prototype.options.theme } );
this.element
// Cannot directly insertAfter() - we need to detach() first, because
// insertAfter() will do nothing if the payload div was not attached
// to the DOM at the time the widget was created, and so the payload
// will remain inside the container even after we call insertAfter().
// If that happens and we remove the container a few lines below, we
// will cause an infinite recursion - #5244
.detach()
.insertAfter( this._ui.placeholder )
.removeClass( "ui-popup ui-overlay-shadow ui-corner-all ui-body-inherit" );
this._ui.screen.remove();
this._ui.container.remove();
this._ui.placeholder.remove();
},
_destroy: function() {
if ( $.mobile.popup.active === this ) {
this.element.one( "popupafterclose", $.proxy( this, "_unenhance" ) );
this.close();
} else {
this._unenhance();
}
return this;
},
_closePopup: function( theEvent, data ) {
var parsedDst, toUrl,
currentOptions = this.options,
immediate = false;
if ( ( theEvent && theEvent.isDefaultPrevented() ) || $.mobile.popup.active !== this ) {
return;
}
// restore location on screen
window.scrollTo( 0, this._scrollTop );
if ( theEvent && theEvent.type === "pagebeforechange" && data ) {
// Determine whether we need to rapid-close the popup, or whether we can
// take the time to run the closing transition
if ( typeof data.toPage === "string" ) {
parsedDst = data.toPage;
} else {
parsedDst = data.toPage.jqmData( "url" );
}
parsedDst = $.mobile.path.parseUrl( parsedDst );
toUrl = parsedDst.pathname + parsedDst.search + parsedDst.hash;
if ( this._myUrl !== $.mobile.path.makeUrlAbsolute( toUrl ) ) {
// Going to a different page - close immediately
immediate = true;
} else {
theEvent.preventDefault();
}
}
// remove nav bindings
this.window.off( currentOptions.closeEvents );
// unbind click handlers added when history is disabled
this.element.undelegate( currentOptions.closeLinkSelector, currentOptions.closeLinkEvents );
this._close( immediate );
},
// any navigation event after a popup is opened should close the popup
// NOTE the pagebeforechange is bound to catch navigation events that don't
// alter the url (eg, dialogs from popups)
_bindContainerClose: function() {
this.window
.on( this.options.closeEvents, $.proxy( this, "_closePopup" ) );
},
widget: function() {
return this._ui.container;
},
// TODO no clear deliniation of what should be here and
// what should be in _open. Seems to be "visual" vs "history" for now
open: function( options ) {
var url, hashkey, activePage, currentIsDialog, hasHash, urlHistory,
self = this,
currentOptions = this.options;
// make sure open is idempotent
if ( $.mobile.popup.active || currentOptions.disabled ) {
return this;
}
// set the global popup mutex
$.mobile.popup.active = this;
this._scrollTop = this.window.scrollTop();
// if history alteration is disabled close on navigate events
// and leave the url as is
if ( !( currentOptions.history ) ) {
self._open( options );
self._bindContainerClose();
// When histoy is disabled we have to grab the data-rel
// back link clicks so we can close the popup instead of
// relying on history to do it for us
self.element
.delegate( currentOptions.closeLinkSelector, currentOptions.closeLinkEvents, function( theEvent ) {
self.close();
theEvent.preventDefault();
});
return this;
}
// cache some values for min/readability
urlHistory = $.mobile.navigate.history;
hashkey = $.mobile.dialogHashKey;
activePage = $.mobile.activePage;
currentIsDialog = ( activePage ? activePage.hasClass( "ui-dialog" ) : false );
this._myUrl = url = urlHistory.getActive().url;
hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog && ( urlHistory.activeIndex > 0 );
if ( hasHash ) {
self._open( options );
self._bindContainerClose();
return this;
}
// if the current url has no dialog hash key proceed as normal
// otherwise, if the page is a dialog simply tack on the hash key
if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ) {
url = url + (url.indexOf( "#" ) > -1 ? hashkey : "#" + hashkey);
} else {
url = $.mobile.path.parseLocation().hash + hashkey;
}
// swallow the the initial navigation event, and bind for the next
this.window.one( "beforenavigate", function( theEvent ) {
theEvent.preventDefault();
self._open( options );
self._bindContainerClose();
});
this.urlAltered = true;
$.mobile.navigate( url, { role: "dialog" } );
return this;
},
close: function() {
// make sure close is idempotent
if ( $.mobile.popup.active !== this ) {
return this;
}
this._scrollTop = this.window.scrollTop();
if ( this.options.history && this.urlAltered ) {
$.mobile.back();
this.urlAltered = false;
} else {
// simulate the nav bindings having fired
this._closePopup();
}
return this;
}
});
// TODO this can be moved inside the widget
$.mobile.popup.handleLink = function( $link ) {
var offset,
path = $.mobile.path,
// NOTE make sure to get only the hash from the href because ie7 (wp7)
// returns the absolute href in this case ruining the element selection
popup = $( path.hashToSelector( path.parseUrl( $link.attr( "href" ) ).hash ) ).first();
if ( popup.length > 0 && popup.data( "mobile-popup" ) ) {
offset = $link.offset();
popup.popup( "open", {
x: offset.left + $link.outerWidth() / 2,
y: offset.top + $link.outerHeight() / 2,
transition: $link.jqmData( "transition" ),
positionTo: $link.jqmData( "position-to" )
});
}
//remove after delay
setTimeout( function() {
$link.removeClass( $.mobile.activeBtnClass );
}, 300 );
};
// TODO move inside _create
$.mobile.document.on( "pagebeforechange", function( theEvent, data ) {
if ( data.options.role === "popup" ) {
$.mobile.popup.handleLink( data.options.link );
theEvent.preventDefault();
}
});
})( jQuery );
/*
* custom "selectmenu" plugin
*/
(function( $, undefined ) {
var unfocusableItemSelector = ".ui-disabled,.ui-state-disabled,.ui-li-divider,.ui-screen-hidden,:jqmData(role='placeholder')",
goToAdjacentItem = function( item, target, direction ) {
var adjacent = item[ direction + "All" ]()
.not( unfocusableItemSelector )
.first();
// if there's a previous option, focus it
if ( adjacent.length ) {
target
.blur()
.attr( "tabindex", "-1" );
adjacent.find( "a" ).first().focus();
}
};
$.widget( "mobile.selectmenu", $.mobile.selectmenu, {
_create: function() {
var o = this.options;
// Custom selects cannot exist inside popups, so revert the "nativeMenu"
// option to true if a parent is a popup
o.nativeMenu = o.nativeMenu || ( this.element.parents( ":jqmData(role='popup'),:mobile-popup" ).length > 0 );
return this._super();
},
_handleSelectFocus: function() {
this.element.blur();
this.button.focus();
},
_handleKeydown: function( event ) {
this._super( event );
this._handleButtonVclickKeydown( event );
},
_handleButtonVclickKeydown: function( event ) {
if ( this.options.disabled || this.isOpen || this.options.nativeMenu ) {
return;
}
if (event.type === "vclick" ||
event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE)) {
this._decideFormat();
if ( this.menuType === "overlay" ) {
this.button.attr( "href", "#" + this.popupId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
} else {
this.button.attr( "href", "#" + this.dialogId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
}
this.isOpen = true;
// Do not prevent default, so the navigation may have a chance to actually open the chosen format
}
},
_handleListFocus: function( e ) {
var params = ( e.type === "focusin" ) ?
{ tabindex: "0", event: "vmouseover" }:
{ tabindex: "-1", event: "vmouseout" };
$( e.target )
.attr( "tabindex", params.tabindex )
.trigger( params.event );
},
_handleListKeydown: function( event ) {
var target = $( event.target ),
li = target.closest( "li" );
// switch logic based on which key was pressed
switch ( event.keyCode ) {
// up or left arrow keys
case 38:
goToAdjacentItem( li, target, "prev" );
return false;
// down or right arrow keys
case 40:
goToAdjacentItem( li, target, "next" );
return false;
// If enter or space is pressed, trigger click
case 13:
case 32:
target.trigger( "click" );
return false;
}
},
_handleMenuPageHide: function() {
// After the dialog's done, we may want to trigger change if the value has actually changed
this._delayedTrigger();
// TODO centralize page removal binding / handling in the page plugin.
// Suggestion from @jblas to do refcounting
//
// TODO extremely confusing dependency on the open method where the pagehide.remove
// bindings are stripped to prevent the parent page from disappearing. The way
// we're keeping pages in the DOM right now sucks
//
// rebind the page remove that was unbound in the open function
// to allow for the parent page removal from actions other than the use
// of a dialog sized custom select
//
// doing this here provides for the back button on the custom select dialog
this.thisPage.page( "bindRemove" );
},
_handleHeaderCloseClick: function() {
if ( this.menuType === "overlay" ) {
this.close();
return false;
}
},
_handleListItemClick: function( event ) {
var listItem = $( event.target ).closest( "li" ),
// Index of option tag to be selected
oldIndex = this.select[ 0 ].selectedIndex,
newIndex = $.mobile.getAttribute( listItem, "option-index" ),
option = this._selectOptions().eq( newIndex )[ 0 ];
// Toggle selected status on the tag for multi selects
option.selected = this.isMultiple ? !option.selected : true;
// Toggle checkbox class for multiple selects
if ( this.isMultiple ) {
listItem.find( "a" )
.toggleClass( "ui-checkbox-on", option.selected )
.toggleClass( "ui-checkbox-off", !option.selected );
}
// If it's not a multiple select, trigger change after it has finished closing
if ( !this.isMultiple && oldIndex !== newIndex ) {
this._triggerChange = true;
}
// Trigger change if it's a multiple select
// Hide custom select for single selects only - otherwise focus clicked item
// We need to grab the clicked item the hard way, because the list may have been rebuilt
if ( this.isMultiple ) {
this.select.trigger( "change" );
this.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
.find( "a" ).first().focus();
}
else {
this.close();
}
event.preventDefault();
},
build: function() {
var selectId, popupId, dialogId, label, thisPage, isMultiple, menuId,
themeAttr, overlayTheme, overlayThemeAttr, dividerThemeAttr,
menuPage, listbox, list, header, headerTitle, menuPageContent,
menuPageClose, headerClose,
o = this.options;
if ( o.nativeMenu ) {
return this._super();
}
selectId = this.selectId;
popupId = selectId + "-listbox";
dialogId = selectId + "-dialog";
label = this.label;
thisPage = this.element.closest( ".ui-page" );
isMultiple = this.element[ 0 ].multiple;
menuId = selectId + "-menu";
themeAttr = o.theme ? ( " data-" + $.mobile.ns + "theme='" + o.theme + "'" ) : "";
overlayTheme = o.overlayTheme || o.theme || null;
overlayThemeAttr = overlayTheme ? ( " data-" + $.mobile.ns +
"overlay-theme='" + overlayTheme + "'" ) : "";
dividerThemeAttr = ( o.dividerTheme && isMultiple ) ? ( " data-" + $.mobile.ns + "divider-theme='" + o.dividerTheme + "'" ) : "";
menuPage = $( "
');
if (this.escapeHtml) {
tel.text(t);
}
else {
tel.html(t);
}
this.canvas._elem.append(tel);
var h = $(tel).outerHeight();
var w = $(tel).outerWidth();
var top = gd[1] - 0.5*h;
var left = gd[0] - 0.5*w;
tel.css({top: top, left: left});
this.labels[idx] = $(tel);
}
}
};
$.jqplot.DivCanvas = function() {
$.jqplot.ElemContainer.call(this);
this._ctx;
};
$.jqplot.DivCanvas.prototype = new $.jqplot.ElemContainer();
$.jqplot.DivCanvas.prototype.constructor = $.jqplot.DivCanvas;
$.jqplot.DivCanvas.prototype.createElement = function(offsets, clss, plotDimensions) {
this._offsets = offsets;
var klass = 'jqplot-DivCanvas';
if (clss != undefined) {
klass = clss;
}
var elem;
// if this canvas already has a dom element, don't make a new one.
if (this._elem) {
elem = this._elem.get(0);
}
else {
elem = document.createElement('div');
}
// if new plotDimensions supplied, use them.
if (plotDimensions != undefined) {
this._plotDimensions = plotDimensions;
}
var w = this._plotDimensions.width - this._offsets.left - this._offsets.right + 'px';
var h = this._plotDimensions.height - this._offsets.top - this._offsets.bottom + 'px';
this._elem = $(elem);
this._elem.css({ position: 'absolute', width:w, height:h, left: this._offsets.left, top: this._offsets.top });
this._elem.addClass(klass);
return this._elem;
};
$.jqplot.DivCanvas.prototype.setContext = function() {
this._ctx = {
canvas:{
width:0,
height:0
},
clearRect:function(){return null;}
};
return this._ctx;
};
$.jqplot.BubbleCanvas = function() {
$.jqplot.ElemContainer.call(this);
this._ctx;
};
$.jqplot.BubbleCanvas.prototype = new $.jqplot.ElemContainer();
$.jqplot.BubbleCanvas.prototype.constructor = $.jqplot.BubbleCanvas;
// initialize with the x,y pont of bubble center and the bubble radius.
$.jqplot.BubbleCanvas.prototype.createElement = function(x, y, r) {
var klass = 'jqplot-bubble-point';
var elem;
// if this canvas already has a dom element, don't make a new one.
if (this._elem) {
elem = this._elem.get(0);
}
else {
elem = document.createElement('canvas');
}
elem.width = (r != null) ? 2*r : elem.width;
elem.height = (r != null) ? 2*r : elem.height;
this._elem = $(elem);
var l = (x != null && r != null) ? x - r : this._elem.css('left');
var t = (y != null && r != null) ? y - r : this._elem.css('top');
this._elem.css({ position: 'absolute', left: l, top: t });
this._elem.addClass(klass);
if ($.jqplot.use_excanvas) {
window.G_vmlCanvasManager.init_(document);
elem = window.G_vmlCanvasManager.initElement(elem);
}
return this._elem;
};
$.jqplot.BubbleCanvas.prototype.draw = function(r, color, gradients, angle) {
var ctx = this._ctx;
// r = Math.floor(r*1.04);
// var x = Math.round(ctx.canvas.width/2);
// var y = Math.round(ctx.canvas.height/2);
var x = ctx.canvas.width/2;
var y = ctx.canvas.height/2;
ctx.save();
if (gradients && !$.jqplot.use_excanvas) {
r = r*1.04;
var comps = $.jqplot.getColorComponents(color);
var colorinner = 'rgba('+Math.round(comps[0]+0.8*(255-comps[0]))+', '+Math.round(comps[1]+0.8*(255-comps[1]))+', '+Math.round(comps[2]+0.8*(255-comps[2]))+', '+comps[3]+')';
var colorend = 'rgba('+comps[0]+', '+comps[1]+', '+comps[2]+', 0)';
// var rinner = Math.round(0.35 * r);
// var xinner = Math.round(x - Math.cos(angle) * 0.33 * r);
// var yinner = Math.round(y - Math.sin(angle) * 0.33 * r);
var rinner = 0.35 * r;
var xinner = x - Math.cos(angle) * 0.33 * r;
var yinner = y - Math.sin(angle) * 0.33 * r;
var radgrad = ctx.createRadialGradient(xinner, yinner, rinner, x, y, r);
radgrad.addColorStop(0, colorinner);
radgrad.addColorStop(0.93, color);
radgrad.addColorStop(0.96, colorend);
radgrad.addColorStop(1, colorend);
// radgrad.addColorStop(.98, colorend);
ctx.fillStyle = radgrad;
ctx.fillRect(0,0, ctx.canvas.width, ctx.canvas.height);
}
else {
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.beginPath();
var ang = 2*Math.PI;
ctx.arc(x, y, r, 0, ang, 0);
ctx.closePath();
ctx.fill();
}
ctx.restore();
};
$.jqplot.BubbleCanvas.prototype.setContext = function() {
this._ctx = this._elem.get(0).getContext("2d");
return this._ctx;
};
$.jqplot.BubbleAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
};
$.jqplot.BubbleAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.BubbleAxisRenderer.prototype.constructor = $.jqplot.BubbleAxisRenderer;
// called with scope of axis object.
$.jqplot.BubbleAxisRenderer.prototype.init = function(options){
$.extend(true, this, options);
var db = this._dataBounds;
var minsidx = 0,
minpidx = 0,
maxsidx = 0,
maxpidx = 0,
maxr = 0,
minr = 0,
minMaxRadius = 0,
maxMaxRadius = 0,
maxMult = 0,
minMult = 0;
// Go through all the series attached to this axis and find
// the min/max bounds for this axis.
for (var i=0; i db.max || db.max == null) {
db.max = d[j][0];
maxsidx=i;
maxpidx=j;
maxr = d[j][2];
maxMaxRadius = s.maxRadius;
maxMult = s.autoscaleMultiplier;
}
}
else {
if (d[j][1] < db.min || db.min == null) {
db.min = d[j][1];
minsidx=i;
minpidx=j;
minr = d[j][2];
minMaxRadius = s.maxRadius;
minMult = s.autoscaleMultiplier;
}
if (d[j][1] > db.max || db.max == null) {
db.max = d[j][1];
maxsidx=i;
maxpidx=j;
maxr = d[j][2];
maxMaxRadius = s.maxRadius;
maxMult = s.autoscaleMultiplier;
}
}
}
}
var minRatio = minr/minMaxRadius;
var maxRatio = maxr/maxMaxRadius;
// need to estimate the effect of the radius on total axis span and adjust axis accordingly.
var span = db.max - db.min;
// var dim = (this.name == 'xaxis' || this.name == 'x2axis') ? this._plotDimensions.width : this._plotDimensions.height;
var dim = Math.min(this._plotDimensions.width, this._plotDimensions.height);
var minfact = minRatio * minMult/3 * span;
var maxfact = maxRatio * maxMult/3 * span;
db.max += maxfact;
db.min -= minfact;
};
function highlight (plot, sidx, pidx) {
plot.plugins.bubbleRenderer.highlightLabelCanvas.empty();
var s = plot.series[sidx];
var canvas = plot.plugins.bubbleRenderer.highlightCanvas;
var ctx = canvas._ctx;
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
s._highlightedPoint = pidx;
plot.plugins.bubbleRenderer.highlightedSeriesIndex = sidx;
var color = s.highlightColorGenerator.get(pidx);
var x = s.gridData[pidx][0],
y = s.gridData[pidx][1],
r = s.gridData[pidx][2];
ctx.save();
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(x, y, r, 0, 2*Math.PI, 0);
ctx.closePath();
ctx.fill();
ctx.restore();
// bring label to front
if (s.labels[pidx]) {
plot.plugins.bubbleRenderer.highlightLabel = s.labels[pidx].clone();
plot.plugins.bubbleRenderer.highlightLabel.appendTo(plot.plugins.bubbleRenderer.highlightLabelCanvas);
plot.plugins.bubbleRenderer.highlightLabel.addClass('jqplot-bubble-label-highlight');
}
}
function unhighlight (plot) {
var canvas = plot.plugins.bubbleRenderer.highlightCanvas;
var sidx = plot.plugins.bubbleRenderer.highlightedSeriesIndex;
plot.plugins.bubbleRenderer.highlightLabelCanvas.empty();
canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
for (var i=0; i
');
var top = this._gridPadding.top;
var left = this._gridPadding.left;
var width = this._plotDimensions.width - this._gridPadding.left - this._gridPadding.right;
var height = this._plotDimensions.height - this._gridPadding.top - this._gridPadding.bottom;
this.plugins.bubbleRenderer.highlightLabelCanvas.css({top:top, left:left, width:width+'px', height:height+'px'});
this.eventCanvas._elem.before(this.plugins.bubbleRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-bubbleRenderer-highlight-canvas', this._plotDimensions, this));
this.eventCanvas._elem.before(this.plugins.bubbleRenderer.highlightLabelCanvas);
var hctx = this.plugins.bubbleRenderer.highlightCanvas.setContext();
}
// setup default renderers for axes and legend so user doesn't have to
// called with scope of plot
function preInit(target, data, options) {
options = options || {};
options.axesDefaults = options.axesDefaults || {};
options.seriesDefaults = options.seriesDefaults || {};
// only set these if there is a Bubble series
var setopts = false;
if (options.seriesDefaults.renderer == $.jqplot.BubbleRenderer) {
setopts = true;
}
else if (options.series) {
for (var i=0; i < options.series.length; i++) {
if (options.series[i].renderer == $.jqplot.BubbleRenderer) {
setopts = true;
}
}
}
if (setopts) {
options.axesDefaults.renderer = $.jqplot.BubbleAxisRenderer;
options.sortData = false;
}
}
$.jqplot.preInitHooks.push(preInit);
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.canvasAxisLabelRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.CanvasAxisLabelRenderer
* Renderer to draw axis labels with a canvas element to support advanced
* featrues such as rotated text. This renderer uses a separate rendering engine
* to draw the text on the canvas. Two modes of rendering the text are available.
* If the browser has native font support for canvas fonts (currently Mozila 3.5
* and Safari 4), you can enable text rendering with the canvas fillText method.
* You do so by setting the "enableFontSupport" option to true.
*
* Browsers lacking native font support will have the text drawn on the canvas
* using the Hershey font metrics. Even if the "enableFontSupport" option is true
* non-supporting browsers will still render with the Hershey font.
*
*/
$.jqplot.CanvasAxisLabelRenderer = function(options) {
// Group: Properties
// prop: angle
// angle of text, measured clockwise from x axis.
this.angle = 0;
// name of the axis associated with this tick
this.axis;
// prop: show
// whether or not to show the tick (mark and label).
this.show = true;
// prop: showLabel
// whether or not to show the label.
this.showLabel = true;
// prop: label
// label for the axis.
this.label = '';
// prop: fontFamily
// CSS spec for the font-family css attribute.
// Applies only to browsers supporting native font rendering in the
// canvas tag. Currently Mozilla 3.5 and Safari 4.
this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif';
// prop: fontSize
// CSS spec for font size.
this.fontSize = '11pt';
// prop: fontWeight
// CSS spec for fontWeight: normal, bold, bolder, lighter or a number 100 - 900
this.fontWeight = 'normal';
// prop: fontStretch
// Multiplier to condense or expand font width.
// Applies only to browsers which don't support canvas native font rendering.
this.fontStretch = 1.0;
// prop: textColor
// css spec for the color attribute.
this.textColor = '#666666';
// prop: enableFontSupport
// true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+.
// If true, label will be drawn with canvas tag native support for fonts.
// If false, label will be drawn with Hershey font metrics.
this.enableFontSupport = true;
// prop: pt2px
// Point to pixel scaling factor, used for computing height of bounding box
// around a label. The labels text renderer has a default setting of 1.4, which
// should be suitable for most fonts. Leave as null to use default. If tops of
// letters appear clipped, increase this. If bounding box seems too big, decrease.
// This is an issue only with the native font renderering capabilities of Mozilla
// 3.5 and Safari 4 since they do not provide a method to determine the font height.
this.pt2px = null;
this._elem;
this._ctx;
this._plotWidth;
this._plotHeight;
this._plotDimensions = {height:null, width:null};
$.extend(true, this, options);
if (options.angle == null && this.axis != 'xaxis' && this.axis != 'x2axis') {
this.angle = -90;
}
var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily};
if (this.pt2px) {
ropts.pt2px = this.pt2px;
}
if (this.enableFontSupport) {
if ($.jqplot.support_canvas_text()) {
this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts);
}
else {
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
}
}
else {
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
}
};
$.jqplot.CanvasAxisLabelRenderer.prototype.init = function(options) {
$.extend(true, this, options);
this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily});
};
// return width along the x axis
// will check first to see if an element exists.
// if not, will return the computed text box width.
$.jqplot.CanvasAxisLabelRenderer.prototype.getWidth = function(ctx) {
if (this._elem) {
return this._elem.outerWidth(true);
}
else {
var tr = this._textRenderer;
var l = tr.getWidth(ctx);
var h = tr.getHeight(ctx);
var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l);
return w;
}
};
// return height along the y axis.
$.jqplot.CanvasAxisLabelRenderer.prototype.getHeight = function(ctx) {
if (this._elem) {
return this._elem.outerHeight(true);
}
else {
var tr = this._textRenderer;
var l = tr.getWidth(ctx);
var h = tr.getHeight(ctx);
var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l);
return w;
}
};
$.jqplot.CanvasAxisLabelRenderer.prototype.getAngleRad = function() {
var a = this.angle * Math.PI/180;
return a;
};
$.jqplot.CanvasAxisLabelRenderer.prototype.draw = function(ctx, plot) {
// Memory Leaks patch
if (this._elem) {
if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
}
this._elem.emptyForce();
this._elem = null;
}
// create a canvas here, but can't draw on it untill it is appended
// to dom for IE compatability.
var elem = plot.canvasManager.getCanvas();
this._textRenderer.setText(this.label, ctx);
var w = this.getWidth(ctx);
var h = this.getHeight(ctx);
elem.width = w;
elem.height = h;
elem.style.width = w;
elem.style.height = h;
elem = plot.canvasManager.initCanvas(elem);
this._elem = $(elem);
this._elem.css({ position: 'absolute'});
this._elem.addClass('jqplot-'+this.axis+'-label');
elem = null;
return this._elem;
};
$.jqplot.CanvasAxisLabelRenderer.prototype.pack = function() {
this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label);
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.canvasAxisTickRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.CanvasAxisTickRenderer
* Renderer to draw axis ticks with a canvas element to support advanced
* featrues such as rotated text. This renderer uses a separate rendering engine
* to draw the text on the canvas. Two modes of rendering the text are available.
* If the browser has native font support for canvas fonts (currently Mozila 3.5
* and Safari 4), you can enable text rendering with the canvas fillText method.
* You do so by setting the "enableFontSupport" option to true.
*
* Browsers lacking native font support will have the text drawn on the canvas
* using the Hershey font metrics. Even if the "enableFontSupport" option is true
* non-supporting browsers will still render with the Hershey font.
*/
$.jqplot.CanvasAxisTickRenderer = function(options) {
// Group: Properties
// prop: mark
// tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null.
this.mark = 'outside';
// prop: showMark
// whether or not to show the mark on the axis.
this.showMark = true;
// prop: showGridline
// whether or not to draw the gridline on the grid at this tick.
this.showGridline = true;
// prop: isMinorTick
// if this is a minor tick.
this.isMinorTick = false;
// prop: angle
// angle of text, measured clockwise from x axis.
this.angle = 0;
// prop: markSize
// Length of the tick marks in pixels. For 'cross' style, length
// will be stoked above and below axis, so total length will be twice this.
this.markSize = 4;
// prop: show
// whether or not to show the tick (mark and label).
this.show = true;
// prop: showLabel
// whether or not to show the label.
this.showLabel = true;
// prop: labelPosition
// 'auto', 'start', 'middle' or 'end'.
// Whether tick label should be positioned so the start, middle, or end
// of the tick mark.
this.labelPosition = 'auto';
this.label = '';
this.value = null;
this._styles = {};
// prop: formatter
// A class of a formatter for the tick text.
// The default $.jqplot.DefaultTickFormatter uses sprintf.
this.formatter = $.jqplot.DefaultTickFormatter;
// prop: formatString
// string passed to the formatter.
this.formatString = '';
// prop: prefix
// String to prepend to the tick label.
// Prefix is prepended to the formatted tick label.
this.prefix = '';
// prop: fontFamily
// css spec for the font-family css attribute.
this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif';
// prop: fontSize
// CSS spec for font size.
this.fontSize = '10pt';
// prop: fontWeight
// CSS spec for fontWeight
this.fontWeight = 'normal';
// prop: fontStretch
// Multiplier to condense or expand font width.
// Applies only to browsers which don't support canvas native font rendering.
this.fontStretch = 1.0;
// prop: textColor
// css spec for the color attribute.
this.textColor = '#666666';
// prop: enableFontSupport
// true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+.
// If true, tick label will be drawn with canvas tag native support for fonts.
// If false, tick label will be drawn with Hershey font metrics.
this.enableFontSupport = true;
// prop: pt2px
// Point to pixel scaling factor, used for computing height of bounding box
// around a label. The labels text renderer has a default setting of 1.4, which
// should be suitable for most fonts. Leave as null to use default. If tops of
// letters appear clipped, increase this. If bounding box seems too big, decrease.
// This is an issue only with the native font renderering capabilities of Mozilla
// 3.5 and Safari 4 since they do not provide a method to determine the font height.
this.pt2px = null;
this._elem;
this._ctx;
this._plotWidth;
this._plotHeight;
this._plotDimensions = {height:null, width:null};
$.extend(true, this, options);
var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily};
if (this.pt2px) {
ropts.pt2px = this.pt2px;
}
if (this.enableFontSupport) {
if ($.jqplot.support_canvas_text()) {
this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts);
}
else {
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
}
}
else {
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
}
};
$.jqplot.CanvasAxisTickRenderer.prototype.init = function(options) {
$.extend(true, this, options);
this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily});
};
// return width along the x axis
// will check first to see if an element exists.
// if not, will return the computed text box width.
$.jqplot.CanvasAxisTickRenderer.prototype.getWidth = function(ctx) {
if (this._elem) {
return this._elem.outerWidth(true);
}
else {
var tr = this._textRenderer;
var l = tr.getWidth(ctx);
var h = tr.getHeight(ctx);
var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l);
return w;
}
};
// return height along the y axis.
$.jqplot.CanvasAxisTickRenderer.prototype.getHeight = function(ctx) {
if (this._elem) {
return this._elem.outerHeight(true);
}
else {
var tr = this._textRenderer;
var l = tr.getWidth(ctx);
var h = tr.getHeight(ctx);
var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l);
return w;
}
};
// return top.
$.jqplot.CanvasAxisTickRenderer.prototype.getTop = function(ctx) {
if (this._elem) {
return this._elem.position().top;
}
else {
return null;
}
};
$.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad = function() {
var a = this.angle * Math.PI/180;
return a;
};
$.jqplot.CanvasAxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
this.value = value;
if (isMinor) {
this.isMinorTick = true;
}
return this;
};
$.jqplot.CanvasAxisTickRenderer.prototype.draw = function(ctx, plot) {
if (!this.label) {
this.label = this.prefix + this.formatter(this.formatString, this.value);
}
// Memory Leaks patch
if (this._elem) {
if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
}
this._elem.emptyForce();
this._elem = null;
}
// create a canvas here, but can't draw on it untill it is appended
// to dom for IE compatability.
var elem = plot.canvasManager.getCanvas();
this._textRenderer.setText(this.label, ctx);
var w = this.getWidth(ctx);
var h = this.getHeight(ctx);
// canvases seem to need to have width and heigh attributes directly set.
elem.width = w;
elem.height = h;
elem.style.width = w;
elem.style.height = h;
elem.style.textAlign = 'left';
elem.style.position = 'absolute';
elem = plot.canvasManager.initCanvas(elem);
this._elem = $(elem);
this._elem.css(this._styles);
this._elem.addClass('jqplot-'+this.axis+'-tick');
elem = null;
return this._elem;
};
$.jqplot.CanvasAxisTickRenderer.prototype.pack = function() {
this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label);
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.canvasOverlay.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
var objCounter = 0;
// class: $.jqplot.CanvasOverlay
$.jqplot.CanvasOverlay = function(opts){
var options = opts || {};
this.options = {
show: $.jqplot.config.enablePlugins,
deferDraw: false
};
// prop: objects
this.objects = [];
this.objectNames = [];
this.canvas = null;
this.markerRenderer = new $.jqplot.MarkerRenderer({style:'line'});
this.markerRenderer.init();
this.highlightObjectIndex = null;
if (options.objects) {
var objs = options.objects,
obj;
for (var i=0; i qx) { temp = px; px = qx; qx = temp; }
if (py > qy) { temp = py; py = qy; qy = temp; }
var ret = (rx >= px && rx <= qx && ry >= py && ry <= qy);
return ret;
}
function handleMove(ev, gridpos, datapos, neighbor, plot) {
var co = plot.plugins.canvasOverlay;
var objs = co.objects;
var l = objs.length;
var obj, haveHighlight=false;
var elem;
for (var i=0; i -1) {
return n/this.pt2px;
}
else if (sz.indexOf('pt') > -1) {
return n;
}
else if (sz.indexOf('em') > -1) {
return n*12;
}
else if (sz.indexOf('%') > -1) {
return n*12/100;
}
// default to pixels;
else {
return n/this.pt2px;
}
};
$.jqplot.CanvasTextRenderer.prototype.fontWeight2Float = function(w) {
// w = normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
// return values adjusted for Hershey font.
if (Number(w)) {
return w/400;
}
else {
switch (w) {
case 'normal':
return 1;
break;
case 'bold':
return 1.75;
break;
case 'bolder':
return 2.25;
break;
case 'lighter':
return 0.75;
break;
default:
return 1;
break;
}
}
};
$.jqplot.CanvasTextRenderer.prototype.getText = function() {
return this.text;
};
$.jqplot.CanvasTextRenderer.prototype.setText = function(t, ctx) {
this.text = t;
this.setWidth(ctx);
return this;
};
$.jqplot.CanvasTextRenderer.prototype.getWidth = function(ctx) {
return this.width;
};
$.jqplot.CanvasTextRenderer.prototype.setWidth = function(ctx, w) {
if (!w) {
this.width = this.measure(ctx, this.text);
}
else {
this.width = w;
}
return this;
};
// return height in pixels.
$.jqplot.CanvasTextRenderer.prototype.getHeight = function(ctx) {
return this.height;
};
// w - height in pt
// set heigh in px
$.jqplot.CanvasTextRenderer.prototype.setHeight = function(w) {
if (!w) {
//height = this.fontSize /0.75;
this.height = this.normalizedFontSize * this.pt2px;
}
else {
this.height = w;
}
return this;
};
$.jqplot.CanvasTextRenderer.prototype.letter = function (ch)
{
return this.letters[ch];
};
$.jqplot.CanvasTextRenderer.prototype.ascent = function()
{
return this.normalizedFontSize;
};
$.jqplot.CanvasTextRenderer.prototype.descent = function()
{
return 7.0*this.normalizedFontSize/25.0;
};
$.jqplot.CanvasTextRenderer.prototype.measure = function(ctx, str)
{
var total = 0;
var len = str.length;
for (var i = 0; i < len; i++) {
var c = this.letter(str.charAt(i));
if (c) {
total += c.width * this.normalizedFontSize / 25.0 * this.fontStretch;
}
}
return total;
};
$.jqplot.CanvasTextRenderer.prototype.draw = function(ctx,str)
{
var x = 0;
// leave room at bottom for descenders.
var y = this.height*0.72;
var total = 0;
var len = str.length;
var mag = this.normalizedFontSize / 25.0;
ctx.save();
var tx, ty;
// 1st quadrant
if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
tx = 0;
ty = -Math.sin(this.angle) * this.width;
}
// 4th quadrant
else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
tx = Math.sin(this.angle) * this.height;
ty = 0;
}
// 2nd quadrant
else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
tx = -Math.cos(this.angle) * this.width;
ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
}
// 3rd quadrant
else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
ty = -Math.cos(this.angle) * this.height;
}
ctx.strokeStyle = this.fillStyle;
ctx.fillStyle = this.fillStyle;
ctx.translate(tx, ty);
ctx.rotate(this.angle);
ctx.lineCap = "round";
// multiplier was 2.0
var fact = (this.normalizedFontSize > 30) ? 2.0 : 2 + (30 - this.normalizedFontSize)/20;
ctx.lineWidth = fact * mag * this.fontWeight2Float(this.fontWeight);
for ( var i = 0; i < len; i++) {
var c = this.letter( str.charAt(i));
if ( !c) {
continue;
}
ctx.beginPath();
var penUp = 1;
var needStroke = 0;
for ( var j = 0; j < c.points.length; j++) {
var a = c.points[j];
if ( a[0] == -1 && a[1] == -1) {
penUp = 1;
continue;
}
if ( penUp) {
ctx.moveTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
penUp = false;
} else {
ctx.lineTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
}
}
ctx.stroke();
x += c.width*mag*this.fontStretch;
}
ctx.restore();
return total;
};
$.jqplot.CanvasTextRenderer.prototype.letters = {
' ': { width: 16, points: [] },
'!': { width: 10, points: [[5,21],[5,7],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
'"': { width: 16, points: [[4,21],[4,14],[-1,-1],[12,21],[12,14]] },
'#': { width: 21, points: [[11,25],[4,-7],[-1,-1],[17,25],[10,-7],[-1,-1],[4,12],[18,12],[-1,-1],[3,6],[17,6]] },
'$': { width: 20, points: [[8,25],[8,-4],[-1,-1],[12,25],[12,-4],[-1,-1],[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
'%': { width: 24, points: [[21,21],[3,0],[-1,-1],[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],[10,20],[13,19],[16,19],[19,20],[21,21],[-1,-1],[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },
'&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] },
'\'': { width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },
'(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },
')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },
'*': { width: 16, points: [[8,21],[8,9],[-1,-1],[3,18],[13,12],[-1,-1],[13,18],[3,12]] },
'+': { width: 26, points: [[13,18],[13,0],[-1,-1],[4,9],[22,9]] },
',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
'-': { width: 18, points: [[6,9],[12,9]] },
'.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },
'/': { width: 22, points: [[20,25],[2,-7]] },
'0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },
'1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },
'2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },
'3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
'4': { width: 20, points: [[13,21],[3,7],[18,7],[-1,-1],[13,21],[13,0]] },
'5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
'6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },
'7': { width: 20, points: [[17,21],[7,0],[-1,-1],[3,21],[17,21]] },
'8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },
'9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },
':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
'<': { width: 24, points: [[20,18],[4,9],[20,0]] },
'=': { width: 26, points: [[4,12],[22,12],[-1,-1],[4,6],[22,6]] },
'>': { width: 24, points: [[4,18],[20,9],[4,0]] },
'?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],[-1,-1],[9,2],[8,1],[9,0],[10,1],[9,2]] },
'@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],[-1,-1],[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],[-1,-1],[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],[-1,-1],[19,16],[18,8],[18,6],[19,5]] },
'A': { width: 18, points: [[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]] },
'B': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[-1,-1],[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },
'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },
'D': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },
'E': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11],[-1,-1],[4,0],[17,0]] },
'F': { width: 18, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]] },
'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],[-1,-1],[13,8],[18,8]] },
'H': { width: 22, points: [[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]] },
'I': { width: 8, points: [[4,21],[4,0]] },
'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },
'K': { width: 21, points: [[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]] },
'L': { width: 17, points: [[4,21],[4,0],[-1,-1],[4,0],[16,0]] },
'M': { width: 24, points: [[4,21],[4,0],[-1,-1],[4,21],[12,0],[-1,-1],[20,21],[12,0],[-1,-1],[20,21],[20,0]] },
'N': { width: 22, points: [[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]] },
'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },
'P': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },
'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],[-1,-1],[12,4],[18,-2]] },
'R': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],[-1,-1],[11,11],[18,0]] },
'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
'T': { width: 16, points: [[8,21],[8,0],[-1,-1],[1,21],[15,21]] },
'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },
'V': { width: 18, points: [[1,21],[9,0],[-1,-1],[17,21],[9,0]] },
'W': { width: 24, points: [[2,21],[7,0],[-1,-1],[12,21],[7,0],[-1,-1],[12,21],[17,0],[-1,-1],[22,21],[17,0]] },
'X': { width: 20, points: [[3,21],[17,0],[-1,-1],[17,21],[3,0]] },
'Y': { width: 18, points: [[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]] },
'Z': { width: 20, points: [[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]] },
'[': { width: 14, points: [[4,25],[4,-7],[-1,-1],[5,25],[5,-7],[-1,-1],[4,25],[11,25],[-1,-1],[4,-7],[11,-7]] },
'\\': { width: 14, points: [[0,21],[14,-3]] },
']': { width: 14, points: [[9,25],[9,-7],[-1,-1],[10,25],[10,-7],[-1,-1],[3,25],[10,25],[-1,-1],[3,-7],[10,-7]] },
'^': { width: 16, points: [[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]] },
'_': { width: 16, points: [[0,-2],[16,-2]] },
'`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },
'a': { width: 19, points: [[15,14],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'b': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'd': { width: 19, points: [[15,21],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]] },
'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'h': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]] },
'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],[-1,-1],[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },
'k': { width: 17, points: [[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]] },
'l': { width: 8, points: [[4,21],[4,0]] },
'm': { width: 30, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],[-1,-1],[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },
'n': { width: 19, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },
'p': { width: 19, points: [[4,14],[4,-7],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
'q': { width: 19, points: [[15,14],[15,-7],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'r': { width: 13, points: [[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]] },
's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },
't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]] },
'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]] },
'v': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0]] },
'w': { width: 22, points: [[3,14],[7,0],[-1,-1],[11,14],[7,0],[-1,-1],[11,14],[15,0],[-1,-1],[19,14],[15,0]] },
'x': { width: 17, points: [[3,14],[14,0],[-1,-1],[14,14],[3,0]] },
'y': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },
'z': { width: 17, points: [[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]] },
'{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],[-1,-1],[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],[-1,-1],[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },
'|': { width: 8, points: [[4,25],[4,-7]] },
'}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],[-1,-1],[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],[-1,-1],[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },
'~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],[-1,-1],[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }
};
$.jqplot.CanvasFontRenderer = function(options) {
options = options || {};
if (!options.pt2px) {
options.pt2px = 1.5;
}
$.jqplot.CanvasTextRenderer.call(this, options);
};
$.jqplot.CanvasFontRenderer.prototype = new $.jqplot.CanvasTextRenderer({});
$.jqplot.CanvasFontRenderer.prototype.constructor = $.jqplot.CanvasFontRenderer;
$.jqplot.CanvasFontRenderer.prototype.measure = function(ctx, str)
{
// var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
var fstyle = this.fontSize+' '+this.fontFamily;
ctx.save();
ctx.font = fstyle;
var w = ctx.measureText(str).width;
ctx.restore();
return w;
};
$.jqplot.CanvasFontRenderer.prototype.draw = function(ctx, str)
{
var x = 0;
// leave room at bottom for descenders.
var y = this.height*0.72;
//var y = 12;
ctx.save();
var tx, ty;
// 1st quadrant
if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
tx = 0;
ty = -Math.sin(this.angle) * this.width;
}
// 4th quadrant
else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
tx = Math.sin(this.angle) * this.height;
ty = 0;
}
// 2nd quadrant
else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
tx = -Math.cos(this.angle) * this.width;
ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
}
// 3rd quadrant
else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
ty = -Math.cos(this.angle) * this.height;
}
ctx.strokeStyle = this.fillStyle;
ctx.fillStyle = this.fillStyle;
// var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
var fstyle = this.fontSize+' '+this.fontFamily;
ctx.font = fstyle;
ctx.translate(tx, ty);
ctx.rotate(this.angle);
ctx.fillText(str, x, y);
// ctx.strokeText(str, x, y);
ctx.restore();
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.categoryAxisRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* class: $.jqplot.CategoryAxisRenderer
* A plugin for jqPlot to render a category style axis, with equal pixel spacing between y data values of a series.
*
* To use this renderer, include the plugin in your source
* >
*
* and supply the appropriate options to your plot
*
* > {axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer}}}
**/
$.jqplot.CategoryAxisRenderer = function(options) {
$.jqplot.LinearAxisRenderer.call(this);
// prop: sortMergedLabels
// True to sort tick labels when labels are created by merging
// x axis values from multiple series. That is, say you have
// two series like:
// > line1 = [[2006, 4], [2008, 9], [2009, 16]];
// > line2 = [[2006, 3], [2007, 7], [2008, 6]];
// If no label array is specified, tick labels will be collected
// from the x values of the series. With sortMergedLabels
// set to true, tick labels will be:
// > [2006, 2007, 2008, 2009]
// With sortMergedLabels set to false, tick labels will be:
// > [2006, 2008, 2009, 2007]
//
// Note, this property is specified on the renderOptions for the
// axes when creating a plot:
// > axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer, rendererOptions:{sortMergedLabels:true}}}
this.sortMergedLabels = false;
};
$.jqplot.CategoryAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.CategoryAxisRenderer.prototype.constructor = $.jqplot.CategoryAxisRenderer;
$.jqplot.CategoryAxisRenderer.prototype.init = function(options){
this.groups = 1;
this.groupLabels = [];
this._groupLabels = [];
this._grouped = false;
this._barsPerGroup = null;
this.reverse = false;
// prop: tickRenderer
// A class of a rendering engine for creating the ticks labels displayed on the plot,
// See <$.jqplot.AxisTickRenderer>.
// this.tickRenderer = $.jqplot.AxisTickRenderer;
// this.labelRenderer = $.jqplot.AxisLabelRenderer;
$.extend(true, this, {tickOptions:{formatString:'%d'}}, options);
var db = this._dataBounds;
// Go through all the series attached to this axis and find
// the min/max bounds for this axis.
for (var i=0; i db.max || db.max == null) {
db.max = d[j][0];
}
}
else {
if (d[j][1] < db.min || db.min == null) {
db.min = d[j][1];
}
if (d[j][1] > db.max || db.max == null) {
db.max = d[j][1];
}
}
}
}
if (this.groupLabels.length) {
this.groups = this.groupLabels.length;
}
};
$.jqplot.CategoryAxisRenderer.prototype.createTicks = function() {
// we're are operating on an axis here
var ticks = this._ticks;
var userTicks = this.ticks;
var name = this.name;
// databounds were set on axis initialization.
var db = this._dataBounds;
var dim, interval;
var min, max;
var pos1, pos2;
var tt, i;
// if we already have ticks, use them.
if (userTicks.length) {
// adjust with blanks if we have groups
if (this.groups > 1 && !this._grouped) {
var l = userTicks.length;
var skip = parseInt(l/this.groups, 10);
var count = 0;
for (var i=skip; i 1 && !this._grouped) {
var l = labels.length;
var skip = parseInt(l/this.groups, 10);
var count = 0;
for (var i=skip; i0 && track
');
if (this.name == 'xaxis' || this.name == 'x2axis') {
this._elem.width(this._plotDimensions.width);
}
else {
this._elem.height(this._plotDimensions.height);
}
// create a _label object.
this.labelOptions.axis = this.name;
this._label = new this.labelRenderer(this.labelOptions);
if (this._label.show) {
var elem = this._label.draw(ctx, plot);
elem.appendTo(this._elem);
}
var t = this._ticks;
for (var i=0; i
');
elem.html(this.groupLabels[i]);
this._groupLabels.push(elem);
elem.appendTo(this._elem);
}
}
return this._elem;
};
// called with scope of axis
$.jqplot.CategoryAxisRenderer.prototype.set = function() {
var dim = 0;
var temp;
var w = 0;
var h = 0;
var lshow = (this._label == null) ? false : this._label.show;
if (this.show) {
var t = this._ticks;
for (var i=0; i dim) {
dim = temp;
}
}
}
var dim2 = 0;
for (var i=0; i dim2) {
dim2 = temp;
}
}
if (lshow) {
w = this._label._elem.outerWidth(true);
h = this._label._elem.outerHeight(true);
}
if (this.name == 'xaxis') {
dim += dim2 + h;
this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
}
else if (this.name == 'x2axis') {
dim += dim2 + h;
this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
}
else if (this.name == 'yaxis') {
dim += dim2 + w;
this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
this._label._elem.css('width', w+'px');
}
}
else {
dim += dim2 + w;
this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
this._label._elem.css('width', w+'px');
}
}
}
};
// called with scope of axis
$.jqplot.CategoryAxisRenderer.prototype.pack = function(pos, offsets) {
var ticks = this._ticks;
var max = this.max;
var min = this.min;
var offmax = offsets.max;
var offmin = offsets.min;
var lshow = (this._label == null) ? false : this._label.show;
var i;
for (var p in pos) {
this._elem.css(p, pos[p]);
}
this._offsets = offsets;
// pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
var pixellength = offmax - offmin;
var unitlength = max - min;
if (!this.reverse) {
// point to unit and unit to point conversions references to Plot DOM element top left corner.
this.u2p = function(u){
return (u - min) * pixellength / unitlength + offmin;
};
this.p2u = function(p){
return (p - offmin) * unitlength / pixellength + min;
};
if (this.name == 'xaxis' || this.name == 'x2axis'){
this.series_u2p = function(u){
return (u - min) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + min;
};
}
else {
this.series_u2p = function(u){
return (u - max) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + max;
};
}
}
else {
// point to unit and unit to point conversions references to Plot DOM element top left corner.
this.u2p = function(u){
return offmin + (max - u) * pixellength / unitlength;
};
this.p2u = function(p){
return min + (p - offmin) * unitlength / pixellength;
};
if (this.name == 'xaxis' || this.name == 'x2axis'){
this.series_u2p = function(u){
return (max - u) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + max;
};
}
else {
this.series_u2p = function(u){
return (min - u) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + min;
};
}
}
if (this.show) {
if (this.name == 'xaxis' || this.name == 'x2axis') {
for (i=0; i= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one.
if (this._ticks[j]._elem && this._ticks[j].label != " ") {
var t = this._ticks[j]._elem;
var p = t.position();
mid += p.left + t.outerWidth(true)/2;
count++;
}
}
mid = mid/count;
this._groupLabels[i].css({'left':(mid - this._groupLabels[i].outerWidth(true)/2)});
this._groupLabels[i].css(labeledge[0], labeledge[1]);
}
}
else {
for (i=0; i 0) {
shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
}
else {
shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
}
break;
case 'middle':
// if (t.angle > 0) {
// shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
// }
// else {
// shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
// }
shim = -t.getHeight()/2;
break;
default:
shim = -t.getHeight()/2;
break;
}
}
else {
shim = -t.getHeight()/2;
}
var val = this.u2p(t.value) + shim + 'px';
t._elem.css('top', val);
t.pack();
}
}
var labeledge=['left', 0];
if (lshow) {
var h = this._label._elem.outerHeight(true);
this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
if (this.name == 'yaxis') {
this._label._elem.css('left', '0px');
labeledge = ['left', this._label._elem.outerWidth(true)];
}
else {
this._label._elem.css('right', '0px');
labeledge = ['right', this._label._elem.outerWidth(true)];
}
this._label.pack();
}
// draw the group labels, position top here, do left after label position.
var step = parseInt(this._ticks.length/this.groups, 10) + 1; // step is one more than before as we don't want to have overlaps in loops
for (i=0; i= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one.
if (this._ticks[j]._elem && this._ticks[j].label != " ") {
var t = this._ticks[j]._elem;
var p = t.position();
mid += p.top + t.outerHeight()/2;
count++;
}
}
mid = mid/count;
this._groupLabels[i].css({'top':mid - this._groupLabels[i].outerHeight()/2});
this._groupLabels[i].css(labeledge[0], labeledge[1]);
}
}
}
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.ciParser.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.ciParser
* Data Renderer function which converts a custom JSON data object into jqPlot data format.
* Set this as a callable on the jqplot dataRenderer plot option:
*
* > plot = $.jqplot('mychart', [data], { dataRenderer: $.jqplot.ciParser, ... });
*
* Where data is an object in JSON format or a JSON encoded string conforming to the
* City Index API spec.
*
* Note that calling the renderer function is handled internally by jqPlot. The
* user does not have to call the function. The parameters described below will
* automatically be passed to the ciParser function.
*
* Parameters:
* data - JSON encoded string or object.
* plot - reference to jqPlot Plot object.
*
* Returns:
* data array in jqPlot format.
*
*/
$.jqplot.ciParser = function (data, plot) {
var ret = [],
line,
temp,
i, j, k, kk;
if (typeof(data) == "string") {
data = $.jqplot.JSON.parse(data, handleStrings);
}
else if (typeof(data) == "object") {
for (k in data) {
for (i=0; i= 0) {
//here we will try to extract the ticks from the Date string in the "value" fields of JSON returned data
a = /^\/Date\((-?[0-9]+)\)\/$/.exec(value);
if (a) {
return parseInt(a[1], 10);
}
}
return value;
}
}
for (var prop in data) {
line = [];
temp = data[prop];
switch (prop) {
case "PriceTicks":
for (i=0; i= 1 or will often miss point intersections.
this.intersectionThreshold = 2;
// prop: showCursorLegend
// Replace the plot legend with an enhanced legend displaying intersection information.
this.showCursorLegend = false;
// prop: cursorLegendFormatString
// Format string used in the cursor legend. If showTooltipDataPosition is true,
// this will also be the default format string used by tooltipFormatString.
this.cursorLegendFormatString = $.jqplot.Cursor.cursorLegendFormatString;
// whether the cursor is over the grid or not.
this._oldHandlers = {onselectstart: null, ondrag: null, onmousedown: null};
// prop: constrainOutsideZoom
// True to limit actual zoom area to edges of grid, even when zooming
// outside of plot area. That is, can't zoom out by mousing outside plot.
this.constrainOutsideZoom = true;
// prop: showTooltipOutsideZoom
// True will keep updating the tooltip when zooming of the grid.
this.showTooltipOutsideZoom = false;
// true if mouse is over grid, false if not.
this.onGrid = false;
$.extend(true, this, options);
};
$.jqplot.Cursor.cursorLegendFormatString = '%s x:%s, y:%s';
// called with scope of plot
$.jqplot.Cursor.init = function (target, data, opts){
// add a cursor attribute to the plot
var options = opts || {};
this.plugins.cursor = new $.jqplot.Cursor(options.cursor);
var c = this.plugins.cursor;
if (c.show) {
$.jqplot.eventListenerHooks.push(['jqplotMouseEnter', handleMouseEnter]);
$.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
$.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
if (c.showCursorLegend) {
opts.legend = opts.legend || {};
opts.legend.renderer = $.jqplot.CursorLegendRenderer;
opts.legend.formatString = this.plugins.cursor.cursorLegendFormatString;
opts.legend.show = true;
}
if (c.zoom) {
$.jqplot.eventListenerHooks.push(['jqplotMouseDown', handleMouseDown]);
if (c.clickReset) {
$.jqplot.eventListenerHooks.push(['jqplotClick', handleClick]);
}
if (c.dblClickReset) {
$.jqplot.eventListenerHooks.push(['jqplotDblClick', handleDblClick]);
}
}
this.resetZoom = function() {
var axes = this.axes;
if (!c.zoomProxy) {
for (var ax in axes) {
axes[ax].reset();
axes[ax]._ticks = [];
// fake out tick creation algorithm to make sure original auto
// computed format string is used if _overrideFormatString is true
if (c._zoom.axes[ax] !== undefined) {
axes[ax]._autoFormatString = c._zoom.axes[ax].tickFormatString;
}
}
this.redraw();
}
else {
var ctx = this.plugins.cursor.zoomCanvas._ctx;
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
ctx = null;
}
this.plugins.cursor._zoom.isZoomed = false;
this.target.trigger('jqplotResetZoom', [this, this.plugins.cursor]);
};
if (c.showTooltipDataPosition) {
c.showTooltipUnitPosition = false;
c.showTooltipGridPosition = false;
if (options.cursor.tooltipFormatString == undefined) {
c.tooltipFormatString = $.jqplot.Cursor.cursorLegendFormatString;
}
}
}
};
// called with context of plot
$.jqplot.Cursor.postDraw = function() {
var c = this.plugins.cursor;
// Memory Leaks patch
if (c.zoomCanvas) {
c.zoomCanvas.resetCanvas();
c.zoomCanvas = null;
}
if (c.cursorCanvas) {
c.cursorCanvas.resetCanvas();
c.cursorCanvas = null;
}
if (c._tooltipElem) {
c._tooltipElem.emptyForce();
c._tooltipElem = null;
}
if (c.zoom) {
c.zoomCanvas = new $.jqplot.GenericCanvas();
this.eventCanvas._elem.before(c.zoomCanvas.createElement(this._gridPadding, 'jqplot-zoom-canvas', this._plotDimensions, this));
c.zoomCanvas.setContext();
}
var elem = document.createElement('div');
c._tooltipElem = $(elem);
elem = null;
c._tooltipElem.addClass('jqplot-cursor-tooltip');
c._tooltipElem.css({position:'absolute', display:'none'});
if (c.zoomCanvas) {
c.zoomCanvas._elem.before(c._tooltipElem);
}
else {
this.eventCanvas._elem.before(c._tooltipElem);
}
if (c.showVerticalLine || c.showHorizontalLine) {
c.cursorCanvas = new $.jqplot.GenericCanvas();
this.eventCanvas._elem.before(c.cursorCanvas.createElement(this._gridPadding, 'jqplot-cursor-canvas', this._plotDimensions, this));
c.cursorCanvas.setContext();
}
// if we are showing the positions in unit coordinates, and no axes groups
// were specified, create a default set.
if (c.showTooltipUnitPosition){
if (c.tooltipAxisGroups.length === 0) {
var series = this.series;
var s;
var temp = [];
for (var i=0; i 6 && Math.abs(gridpos.y - c._zoom.start[1]) > 6) || (c.constrainZoomTo == 'x' && Math.abs(gridpos.x - c._zoom.start[0]) > 6) || (c.constrainZoomTo == 'y' && Math.abs(gridpos.y - c._zoom.start[1]) > 6)) {
if (!plot.plugins.cursor.zoomProxy) {
for (var ax in datapos) {
// make a copy of the original axes to revert back.
if (c._zoom.axes[ax] == undefined) {
c._zoom.axes[ax] = {};
c._zoom.axes[ax].numberTicks = axes[ax].numberTicks;
c._zoom.axes[ax].tickInterval = axes[ax].tickInterval;
// for date axes...
c._zoom.axes[ax].daTickInterval = axes[ax].daTickInterval;
c._zoom.axes[ax].min = axes[ax].min;
c._zoom.axes[ax].max = axes[ax].max;
c._zoom.axes[ax].tickFormatString = (axes[ax].tickOptions != null) ? axes[ax].tickOptions.formatString : '';
}
if ((c.constrainZoomTo == 'none') || (c.constrainZoomTo == 'x' && ax.charAt(0) == 'x') || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'y')) {
dp = datapos[ax];
if (dp != null) {
if (dp > start[ax]) {
newmin = start[ax];
newmax = dp;
}
else {
span = start[ax] - dp;
newmin = dp;
newmax = start[ax];
}
curax = axes[ax];
_numberTicks = null;
// if aligning this axis, use number of ticks from previous axis.
// Do I need to reset somehow if alignTicks is changed and then graph is replotted??
if (curax.alignTicks) {
if (curax.name === 'x2axis' && plot.axes.xaxis.show) {
_numberTicks = plot.axes.xaxis.numberTicks;
}
else if (curax.name.charAt(0) === 'y' && curax.name !== 'yaxis' && curax.name !== 'yMidAxis' && plot.axes.yaxis.show) {
_numberTicks = plot.axes.yaxis.numberTicks;
}
}
if (this.looseZoom && (axes[ax].renderer.constructor === $.jqplot.LinearAxisRenderer || axes[ax].renderer.constructor === $.jqplot.LogAxisRenderer )) { //} || axes[ax].renderer.constructor === $.jqplot.DateAxisRenderer)) {
ret = $.jqplot.LinearTickGenerator(newmin, newmax, curax._scalefact, _numberTicks);
// if new minimum is less than "true" minimum of axis display, adjust it
if (axes[ax].tickInset && ret[0] < axes[ax].min + axes[ax].tickInset * axes[ax].tickInterval) {
ret[0] += ret[4];
ret[2] -= 1;
}
// if new maximum is greater than "true" max of axis display, adjust it
if (axes[ax].tickInset && ret[1] > axes[ax].max - axes[ax].tickInset * axes[ax].tickInterval) {
ret[1] -= ret[4];
ret[2] -= 1;
}
// for log axes, don't fall below current minimum, this will look bad and can't have 0 in range anyway.
if (axes[ax].renderer.constructor === $.jqplot.LogAxisRenderer && ret[0] < axes[ax].min) {
// remove a tick and shift min up
ret[0] += ret[4];
ret[2] -= 1;
}
axes[ax].min = ret[0];
axes[ax].max = ret[1];
axes[ax]._autoFormatString = ret[3];
axes[ax].numberTicks = ret[2];
axes[ax].tickInterval = ret[4];
// for date axes...
axes[ax].daTickInterval = [ret[4]/1000, 'seconds'];
}
else {
axes[ax].min = newmin;
axes[ax].max = newmax;
axes[ax].tickInterval = null;
axes[ax].numberTicks = null;
// for date axes...
axes[ax].daTickInterval = null;
}
axes[ax]._ticks = [];
}
}
// if ((c.constrainZoomTo == 'x' && ax.charAt(0) == 'y' && c.autoscaleConstraint) || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'x' && c.autoscaleConstraint)) {
// dp = datapos[ax];
// if (dp != null) {
// axes[ax].max == null;
// axes[ax].min = null;
// }
// }
}
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
plot.redraw();
c._zoom.isZoomed = true;
ctx = null;
}
plot.target.trigger('jqplotZoom', [gridpos, datapos, plot, cursor]);
}
};
$.jqplot.preInitHooks.push($.jqplot.Cursor.init);
$.jqplot.postDrawHooks.push($.jqplot.Cursor.postDraw);
function updateTooltip(gridpos, datapos, plot) {
var c = plot.plugins.cursor;
var s = '';
var addbr = false;
if (c.showTooltipGridPosition) {
s = gridpos.x+', '+gridpos.y;
addbr = true;
}
if (c.showTooltipUnitPosition) {
var g;
for (var i=0; i';
}
if (c.useAxesFormatters) {
for (var j=0; j';
}
s += $.jqplot.sprintf(c.tooltipFormatString, label, sx, sy);
addbr = true;
}
}
}
}
c._tooltipElem.html(s);
}
function moveLine(gridpos, plot) {
var c = plot.plugins.cursor;
var ctx = c.cursorCanvas._ctx;
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
if (c.showVerticalLine) {
c.shapeRenderer.draw(ctx, [[gridpos.x, 0], [gridpos.x, ctx.canvas.height]]);
}
if (c.showHorizontalLine) {
c.shapeRenderer.draw(ctx, [[0, gridpos.y], [ctx.canvas.width, gridpos.y]]);
}
var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
if (c.showCursorLegend) {
var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
for (var i=0; i0; n--) {
axis = an[n-1];
if (ax[axis].show) {
dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
}
}
return {offsets:go, gridPos:gridPos, dataPos:dataPos};
}
function handleZoomMove(ev) {
var plot = ev.data.plot;
var c = plot.plugins.cursor;
// don't do anything if not on grid.
if (c.show && c.zoom && c._zoom.started && !c.zoomTarget) {
ev.preventDefault();
var ctx = c.zoomCanvas._ctx;
var positions = getEventPosition(ev);
var gridpos = positions.gridPos;
var datapos = positions.dataPos;
c._zoom.gridpos = gridpos;
c._zoom.datapos = datapos;
c._zoom.zooming = true;
var xpos = gridpos.x;
var ypos = gridpos.y;
var height = ctx.canvas.height;
var width = ctx.canvas.width;
if (c.showTooltip && !c.onGrid && c.showTooltipOutsideZoom) {
updateTooltip(gridpos, datapos, plot);
if (c.followMouse) {
moveTooltip(gridpos, plot);
}
}
if (c.constrainZoomTo == 'x') {
c._zoom.end = [xpos, height];
}
else if (c.constrainZoomTo == 'y') {
c._zoom.end = [width, ypos];
}
else {
c._zoom.end = [xpos, ypos];
}
var sel = window.getSelection;
if (document.selection && document.selection.empty)
{
document.selection.empty();
}
else if (sel && !sel().isCollapsed) {
sel().collapse();
}
drawZoomBox.call(c);
ctx = null;
}
}
function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
var c = plot.plugins.cursor;
if(plot.plugins.mobile){
$(document).one('vmouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
} else {
$(document).one('mouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
}
var axes = plot.axes;
if (document.onselectstart != undefined) {
c._oldHandlers.onselectstart = document.onselectstart;
document.onselectstart = function () { return false; };
}
if (document.ondrag != undefined) {
c._oldHandlers.ondrag = document.ondrag;
document.ondrag = function () { return false; };
}
if (document.onmousedown != undefined) {
c._oldHandlers.onmousedown = document.onmousedown;
document.onmousedown = function () { return false; };
}
if (c.zoom) {
if (!c.zoomProxy) {
var ctx = c.zoomCanvas._ctx;
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
ctx = null;
}
if (c.constrainZoomTo == 'x') {
c._zoom.start = [gridpos.x, 0];
}
else if (c.constrainZoomTo == 'y') {
c._zoom.start = [0, gridpos.y];
}
else {
c._zoom.start = [gridpos.x, gridpos.y];
}
c._zoom.started = true;
for (var ax in datapos) {
// get zoom starting position.
c._zoom.axes.start[ax] = datapos[ax];
}
if(plot.plugins.mobile){
$(document).bind('vmousemove.jqplotCursor', {plot:plot}, handleZoomMove);
} else {
$(document).bind('mousemove.jqplotCursor', {plot:plot}, handleZoomMove);
}
}
}
function handleMouseUp(ev) {
var plot = ev.data.plot;
var c = plot.plugins.cursor;
if (c.zoom && c._zoom.zooming && !c.zoomTarget) {
var xpos = c._zoom.gridpos.x;
var ypos = c._zoom.gridpos.y;
var datapos = c._zoom.datapos;
var height = c.zoomCanvas._ctx.canvas.height;
var width = c.zoomCanvas._ctx.canvas.width;
var axes = plot.axes;
if (c.constrainOutsideZoom && !c.onGrid) {
if (xpos < 0) { xpos = 0; }
else if (xpos > width) { xpos = width; }
if (ypos < 0) { ypos = 0; }
else if (ypos > height) { ypos = height; }
for (var axis in datapos) {
if (datapos[axis]) {
if (axis.charAt(0) == 'x') {
datapos[axis] = axes[axis].series_p2u(xpos);
}
else {
datapos[axis] = axes[axis].series_p2u(ypos);
}
}
}
}
if (c.constrainZoomTo == 'x') {
ypos = height;
}
else if (c.constrainZoomTo == 'y') {
xpos = width;
}
c._zoom.end = [xpos, ypos];
c._zoom.gridpos = {x:xpos, y:ypos};
c.doZoom(c._zoom.gridpos, datapos, plot, c);
}
c._zoom.started = false;
c._zoom.zooming = false;
$(document).unbind('mousemove.jqplotCursor', handleZoomMove);
if (document.onselectstart != undefined && c._oldHandlers.onselectstart != null){
document.onselectstart = c._oldHandlers.onselectstart;
c._oldHandlers.onselectstart = null;
}
if (document.ondrag != undefined && c._oldHandlers.ondrag != null){
document.ondrag = c._oldHandlers.ondrag;
c._oldHandlers.ondrag = null;
}
if (document.onmousedown != undefined && c._oldHandlers.onmousedown != null){
document.onmousedown = c._oldHandlers.onmousedown;
c._oldHandlers.onmousedown = null;
}
}
function drawZoomBox() {
var start = this._zoom.start;
var end = this._zoom.end;
var ctx = this.zoomCanvas._ctx;
var l, t, h, w;
if (end[0] > start[0]) {
l = start[0];
w = end[0] - start[0];
}
else {
l = end[0];
w = start[0] - end[0];
}
if (end[1] > start[1]) {
t = start[1];
h = end[1] - start[1];
}
else {
t = end[1];
h = start[1] - end[1];
}
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.strokeStyle = '#999999';
ctx.lineWidth = 1.0;
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
ctx.fillRect(0,0,ctx.canvas.width, ctx.canvas.height);
ctx.clearRect(l, t, w, h);
// IE won't show transparent fill rect, so stroke a rect also.
ctx.strokeRect(l,t,w,h);
ctx = null;
}
$.jqplot.CursorLegendRenderer = function(options) {
$.jqplot.TableLegendRenderer.call(this, options);
this.formatString = '%s';
};
$.jqplot.CursorLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
$.jqplot.CursorLegendRenderer.prototype.constructor = $.jqplot.CursorLegendRenderer;
// called in context of a Legend
$.jqplot.CursorLegendRenderer.prototype.draw = function() {
if (this._elem) {
this._elem.emptyForce();
this._elem = null;
}
if (this.show) {
var series = this._series, s;
// make a table. one line label per row.
var elem = document.createElement('table');
this._elem = $(elem);
elem = null;
this._elem.addClass('jqplot-legend jqplot-cursor-legend');
this._elem.css('position', 'absolute');
var pad = false;
for (var i = 0; i< series.length; i++) {
s = series[i];
if (s.show && s.showLabel) {
var lt = $.jqplot.sprintf(this.formatString, s.label.toString());
if (lt) {
var color = s.color;
if (s._stack && !s.fill) {
color = '';
}
addrow.call(this, lt, color, pad, i);
pad = true;
}
// let plugins add more rows to legend. Used by trend line plugin.
for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
var item = $.jqplot.addLegendRowHooks[j].call(this, s);
if (item) {
addrow.call(this, item.label, item.color, pad);
pad = true;
}
}
}
}
series = s = null;
delete series;
delete s;
}
function addrow(label, color, pad, idx) {
var rs = (pad) ? this.rowSpacing : '0';
var tr = $('
');
td.appendTo(tr);
td.data('seriesIndex', idx);
if (this.escapeHtml) {
td.text(label);
}
else {
td.html(label);
}
tr = null;
td = null;
}
return this._elem;
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.dateAxisRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.DateAxisRenderer
* A plugin for a jqPlot to render an axis as a series of date values.
* This renderer has no options beyond those supplied by the class.
* It supplies its own tick formatter, so the tickOptions.formatter option
* should not be overridden.
*
* Thanks to Ken Synder for his enhanced Date instance methods which are
* included with this code .
*
* To use this renderer, include the plugin in your source
* >
*
* and supply the appropriate options to your plot
*
* > {axes:{xaxis:{renderer:$.jqplot.DateAxisRenderer}}}
*
* Dates can be passed into the axis in almost any recognizable value and
* will be parsed. They will be rendered on the axis in the format
* specified by tickOptions.formatString. e.g. tickOptions.formatString = '%Y-%m-%d'.
*
* Accecptable format codes
* are:
*
* > Code Result Description
* > == Years ==
* > %Y 2008 Four-digit year
* > %y 08 Two-digit year
* > == Months ==
* > %m 09 Two-digit month
* > %#m 9 One or two-digit month
* > %B September Full month name
* > %b Sep Abbreviated month name
* > == Days ==
* > %d 05 Two-digit day of month
* > %#d 5 One or two-digit day of month
* > %e 5 One or two-digit day of month
* > %A Sunday Full name of the day of the week
* > %a Sun Abbreviated name of the day of the week
* > %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday)
* > %o th The ordinal suffix string following the day of the month
* > == Hours ==
* > %H 23 Hours in 24-hour format (two digits)
* > %#H 3 Hours in 24-hour integer format (one or two digits)
* > %I 11 Hours in 12-hour format (two digits)
* > %#I 3 Hours in 12-hour integer format (one or two digits)
* > %p PM AM or PM
* > == Minutes ==
* > %M 09 Minutes (two digits)
* > %#M 9 Minutes (one or two digits)
* > == Seconds ==
* > %S 02 Seconds (two digits)
* > %#S 2 Seconds (one or two digits)
* > %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00)
* > == Milliseconds ==
* > %N 008 Milliseconds (three digits)
* > %#N 8 Milliseconds (one to three digits)
* > == Timezone ==
* > %O 360 difference in minutes between local time and GMT
* > %Z Mountain Standard Time Name of timezone as reported by browser
* > %G -06:00 Hours and minutes between GMT
* > == Shortcuts ==
* > %F 2008-03-26 %Y-%m-%d
* > %T 05:06:30 %H:%M:%S
* > %X 05:06:30 %H:%M:%S
* > %x 03/26/08 %m/%d/%y
* > %D 03/26/08 %m/%d/%y
* > %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y
* > %v 3-Sep-2008 %e-%b-%Y
* > %R 15:31 %H:%M
* > %r 3:31:00 PM %I:%M:%S %p
* > == Characters ==
* > %n \n Newline
* > %t \t Tab
* > %% % Percent Symbol
*/
$.jqplot.DateAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
this.date = new $.jsDate();
};
var second = 1000;
var minute = 60 * second;
var hour = 60 * minute;
var day = 24 * hour;
var week = 7 * day;
// these are less definitive
var month = 30.4368499 * day;
var year = 365.242199 * day;
var daysInMonths = [31,28,31,30,31,30,31,30,31,30,31,30];
// array of consistent nice intervals. Longer intervals
// will depend on days in month, days in year, etc.
var niceFormatStrings = ['%M:%S.%#N', '%M:%S.%#N', '%M:%S.%#N', '%M:%S', '%M:%S', '%M:%S', '%M:%S', '%H:%M:%S', '%H:%M:%S', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%a %H:%M', '%a %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%v', '%v', '%v', '%v', '%v', '%v', '%v'];
var niceIntervals = [0.1*second, 0.2*second, 0.5*second, second, 2*second, 5*second, 10*second, 15*second, 30*second, minute, 2*minute, 5*minute, 10*minute, 15*minute, 30*minute, hour, 2*hour, 4*hour, 6*hour, 8*hour, 12*hour, day, 2*day, 3*day, 4*day, 5*day, week, 2*week];
var niceMonthlyIntervals = [];
function bestDateInterval(min, max, titarget) {
// iterate through niceIntervals to find one closest to titarget
var badness = Number.MAX_VALUE;
var temp, bestTi, bestfmt;
for (var i=0, l=niceIntervals.length; i < l; i++) {
temp = Math.abs(titarget - niceIntervals[i]);
if (temp < badness) {
badness = temp;
bestTi = niceIntervals[i];
bestfmt = niceFormatStrings[i];
}
}
return [bestTi, bestfmt];
}
$.jqplot.DateAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.DateAxisRenderer.prototype.constructor = $.jqplot.DateAxisRenderer;
$.jqplot.DateTickFormatter = function(format, val) {
if (!format) {
format = '%Y/%m/%d';
}
return $.jsDate.strftime(val, format);
};
$.jqplot.DateAxisRenderer.prototype.init = function(options){
// prop: tickRenderer
// A class of a rendering engine for creating the ticks labels displayed on the plot,
// See <$.jqplot.AxisTickRenderer>.
// this.tickRenderer = $.jqplot.AxisTickRenderer;
// this.labelRenderer = $.jqplot.AxisLabelRenderer;
this.tickOptions.formatter = $.jqplot.DateTickFormatter;
// prop: tickInset
// Controls the amount to inset the first and last ticks from
// the edges of the grid, in multiples of the tick interval.
// 0 is no inset, 0.5 is one half a tick interval, 1 is a full
// tick interval, etc.
this.tickInset = 0;
// prop: drawBaseline
// True to draw the axis baseline.
this.drawBaseline = true;
// prop: baselineWidth
// width of the baseline in pixels.
this.baselineWidth = null;
// prop: baselineColor
// CSS color spec for the baseline.
this.baselineColor = null;
this.daTickInterval = null;
this._daTickInterval = null;
$.extend(true, this, options);
var db = this._dataBounds,
stats,
sum,
s,
d,
pd,
sd,
intv;
// Go through all the series attached to this axis and find
// the min/max bounds for this axis.
for (var i=0; i db.max) || db.max == null) {
db.max = d[j][0];
}
if (j>0) {
intv = Math.abs(d[j][0] - d[j-1][0]);
stats.intervals.push(intv);
if (stats.frequencies.hasOwnProperty(intv)) {
stats.frequencies[intv] += 1;
}
else {
stats.frequencies[intv] = 1;
}
}
sum += intv;
}
else {
d[j][1] = new $.jsDate(d[j][1]).getTime();
pd[j][1] = new $.jsDate(d[j][1]).getTime();
sd[j][1] = new $.jsDate(d[j][1]).getTime();
if ((d[j][1] != null && d[j][1] < db.min) || db.min == null) {
db.min = d[j][1];
}
if ((d[j][1] != null && d[j][1] > db.max) || db.max == null) {
db.max = d[j][1];
}
if (j>0) {
intv = Math.abs(d[j][1] - d[j-1][1]);
stats.intervals.push(intv);
if (stats.frequencies.hasOwnProperty(intv)) {
stats.frequencies[intv] += 1;
}
else {
stats.frequencies[intv] = 1;
}
}
}
sum += intv;
}
if (s.renderer.bands) {
if (s.renderer.bands.hiData.length) {
var bd = s.renderer.bands.hiData;
for (var j=0, l=bd.length; j < l; j++) {
if (this.name === 'xaxis' || this.name === 'x2axis') {
bd[j][0] = new $.jsDate(bd[j][0]).getTime();
if ((bd[j][0] != null && bd[j][0] > db.max) || db.max == null) {
db.max = bd[j][0];
}
}
else {
bd[j][1] = new $.jsDate(bd[j][1]).getTime();
if ((bd[j][1] != null && bd[j][1] > db.max) || db.max == null) {
db.max = bd[j][1];
}
}
}
}
if (s.renderer.bands.lowData.length) {
var bd = s.renderer.bands.lowData;
for (var j=0, l=bd.length; j < l; j++) {
if (this.name === 'xaxis' || this.name === 'x2axis') {
bd[j][0] = new $.jsDate(bd[j][0]).getTime();
if ((bd[j][0] != null && bd[j][0] < db.min) || db.min == null) {
db.min = bd[j][0];
}
}
else {
bd[j][1] = new $.jsDate(bd[j][1]).getTime();
if ((bd[j][1] != null && bd[j][1] < db.min) || db.min == null) {
db.min = bd[j][1];
}
}
}
}
}
var tempf = 0,
tempn=0;
for (var n in stats.frequencies) {
stats.sortedIntervals.push({interval:n, frequency:stats.frequencies[n]});
}
stats.sortedIntervals.sort(function(a, b){
return b.frequency - a.frequency;
});
stats.min = $.jqplot.arrayMin(stats.intervals);
stats.max = $.jqplot.arrayMax(stats.intervals);
stats.mean = sum/d.length;
this._intervalStats.push(stats);
stats = sum = s = d = pd = sd = null;
}
db = null;
};
// called with scope of an axis
$.jqplot.DateAxisRenderer.prototype.reset = function() {
this.min = this._options.min;
this.max = this._options.max;
this.tickInterval = this._options.tickInterval;
this.numberTicks = this._options.numberTicks;
this._autoFormatString = '';
if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
this.tickOptions.formatString = '';
}
this.daTickInterval = this._daTickInterval;
// this._ticks = this.__ticks;
};
$.jqplot.DateAxisRenderer.prototype.createTicks = function(plot) {
// we're are operating on an axis here
var ticks = this._ticks;
var userTicks = this.ticks;
var name = this.name;
// databounds were set on axis initialization.
var db = this._dataBounds;
var iv = this._intervalStats;
var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
var interval;
var min, max;
var pos1, pos2;
var tt, i;
var threshold = 30;
var insetMult = 1;
var daTickInterval = null;
// if user specified a tick interval, convert to usable.
if (this.tickInterval != null)
{
// if interval is a number or can be converted to one, use it.
// Assume it is in SECONDS!!!
if (Number(this.tickInterval)) {
daTickInterval = [Number(this.tickInterval), 'seconds'];
}
// else, parse out something we can build from.
else if (typeof this.tickInterval == "string") {
var parts = this.tickInterval.split(' ');
if (parts.length == 1) {
daTickInterval = [1, parts[0]];
}
else if (parts.length == 2) {
daTickInterval = [parts[0], parts[1]];
}
}
}
var tickInterval = this.tickInterval;
// if we already have ticks, use them.
// ticks must be in order of increasing value.
min = new $.jsDate((this.min != null) ? this.min : db.min).getTime();
max = new $.jsDate((this.max != null) ? this.max : db.max).getTime();
// see if we're zooming. if we are, don't use the min and max we're given,
// but compute some nice ones. They will be reset later.
var cursor = plot.plugins.cursor;
if (cursor && cursor._zoom && cursor._zoom.zooming) {
this.min = null;
this.max = null;
}
var range = max - min;
if (this.tickOptions == null || !this.tickOptions.formatString) {
this._overrideFormatString = true;
}
if (userTicks.length) {
// ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
for (i=0; i 6) {
intv = 6;
}
// figure out the starting month and ending month.
var mstart = new $.jsDate(min).setDate(1).setHours(0,0,0,0);
// See if max ends exactly on a month
var tempmend = new $.jsDate(max);
var mend = new $.jsDate(max).setDate(1).setHours(0,0,0,0);
if (tempmend.getTime() !== mend.getTime()) {
mend = mend.add(1, 'month');
}
var nmonths = mend.diff(mstart, 'month');
nttarget = Math.ceil(nmonths/intv) + 1;
this.min = mstart.getTime();
this.max = mstart.clone().add((nttarget - 1) * intv, 'month').getTime();
this.numberTicks = nttarget;
for (var i=0; i 200) {
this.numberTicks = parseInt(3+(dim-200)/100, 10);
}
else {
this.numberTicks = 2;
}
}
insetMult = range / (this.numberTicks-1)/1000;
if (this.daTickInterval == null) {
this.daTickInterval = [insetMult, 'seconds'];
}
for (var i=0; i
*
* Properties described here are passed into the $.jqplot function
* as options on the series renderer. For example:
*
* > plot2 = $.jqplot('chart2', [s1, s2], {
* > seriesDefaults: {
* > renderer:$.jqplot.DonutRenderer,
* > rendererOptions:{
* > sliceMargin: 2,
* > innerDiameter: 110,
* > startAngle: -90
* > }
* > }
* > });
*
* A donut plot will trigger events on the plot target
* according to user interaction. All events return the event object,
* the series index, the point (slice) index, and the point data for
* the appropriate slice.
*
* 'jqplotDataMouseOver' - triggered when user mouseing over a slice.
* 'jqplotDataHighlight' - triggered the first time user mouses over a slice,
* if highlighting is enabled.
* 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
* a highlighted slice.
* 'jqplotDataClick' - triggered when the user clicks on a slice.
* 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if
* the "captureRightClick" option is set to true on the plot.
*/
$.jqplot.DonutRenderer = function(){
$.jqplot.LineRenderer.call(this);
};
$.jqplot.DonutRenderer.prototype = new $.jqplot.LineRenderer();
$.jqplot.DonutRenderer.prototype.constructor = $.jqplot.DonutRenderer;
// called with scope of a series
$.jqplot.DonutRenderer.prototype.init = function(options, plot) {
// Group: Properties
//
// prop: diameter
// Outer diameter of the donut, auto computed by default
this.diameter = null;
// prop: innerDiameter
// Inner diameter of the donut, auto calculated by default.
// If specified will override thickness value.
this.innerDiameter = null;
// prop: thickness
// thickness of the donut, auto computed by default
// Overridden by if innerDiameter is specified.
this.thickness = null;
// prop: padding
// padding between the donut and plot edges, legend, etc.
this.padding = 20;
// prop: sliceMargin
// angular spacing between donut slices in degrees.
this.sliceMargin = 0;
// prop: ringMargin
// pixel distance between rings, or multiple series in a donut plot.
// null will compute ringMargin based on sliceMargin.
this.ringMargin = null;
// prop: fill
// true or false, whether to fil the slices.
this.fill = true;
// prop: shadowOffset
// offset of the shadow from the slice and offset of
// each succesive stroke of the shadow from the last.
this.shadowOffset = 2;
// prop: shadowAlpha
// transparency of the shadow (0 = transparent, 1 = opaque)
this.shadowAlpha = 0.07;
// prop: shadowDepth
// number of strokes to apply to the shadow,
// each stroke offset shadowOffset from the last.
this.shadowDepth = 5;
// prop: highlightMouseOver
// True to highlight slice when moused over.
// This must be false to enable highlightMouseDown to highlight when clicking on a slice.
this.highlightMouseOver = true;
// prop: highlightMouseDown
// True to highlight when a mouse button is pressed over a slice.
// This will be disabled if highlightMouseOver is true.
this.highlightMouseDown = false;
// prop: highlightColors
// an array of colors to use when highlighting a slice.
this.highlightColors = [];
// prop: dataLabels
// Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
// Defaults to percentage of each pie slice.
this.dataLabels = 'percent';
// prop: showDataLabels
// true to show data labels on slices.
this.showDataLabels = false;
// prop: totalLabel
// true to show total label in the centre
this.totalLabel = false;
// prop: dataLabelFormatString
// Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
this.dataLabelFormatString = null;
// prop: dataLabelThreshold
// Threshhold in percentage (0 - 100) of pie area, below which no label will be displayed.
// This applies to all label types, not just to percentage labels.
this.dataLabelThreshold = 3;
// prop: dataLabelPositionFactor
// A Multiplier (0-1) of the pie radius which controls position of label on slice.
// Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
this.dataLabelPositionFactor = 0.4;
// prop: dataLabelNudge
// Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
this.dataLabelNudge = 0;
// prop: startAngle
// Angle to start drawing donut in degrees.
// According to orientation of canvas coordinate system:
// 0 = on the positive x axis
// -90 = on the positive y axis.
// 90 = on the negaive y axis.
// 180 or - 180 = on the negative x axis.
this.startAngle = 0;
this.tickRenderer = $.jqplot.DonutTickRenderer;
// Used as check for conditions where donut shouldn't be drawn.
this._drawData = true;
this._type = 'donut';
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
if (options.highlightMouseDown && options.highlightMouseOver == null) {
options.highlightMouseOver = false;
}
$.extend(true, this, options);
if (this.diameter != null) {
this.diameter = this.diameter - this.sliceMargin;
}
this._diameter = null;
this._innerDiameter = null;
this._radius = null;
this._innerRadius = null;
this._thickness = null;
// references to the previous series in the plot to properly calculate diameters
// and thicknesses of nested rings.
this._previousSeries = [];
this._numberSeries = 1;
// array of [start,end] angles arrays, one for each slice. In radians.
this._sliceAngles = [];
// index of the currenty highlighted point, if any
this._highlightedPoint = null;
// set highlight colors if none provided
if (this.highlightColors.length == 0) {
for (var i=0; i 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
newrgb[j] = parseInt(newrgb[j], 10);
}
this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
}
}
plot.postParseOptionsHooks.addOnce(postParseOptions);
plot.postInitHooks.addOnce(postInit);
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
plot.postDrawHooks.addOnce(postPlotDraw);
};
$.jqplot.DonutRenderer.prototype.setGridData = function(plot) {
// set gridData property. This will hold angle in radians of each data point.
var stack = [];
var td = [];
var sa = this.startAngle/180*Math.PI;
var tot = 0;
// don't know if we have any valid data yet, so set plot to not draw.
this._drawData = false;
for (var i=0; i0) {
stack[i] += stack[i-1];
}
tot += this.data[i][1];
}
var fact = Math.PI*2/stack[stack.length - 1];
for (var i=0; i0) {
stack[i] += stack[i-1];
}
tot += data[i][1];
}
var fact = Math.PI*2/stack[stack.length - 1];
for (var i=0; i 6.282 + this.startAngle) {
ang2 = 6.282 + this.startAngle;
if (ang1 > ang2) {
ang1 = 6.281 + this.startAngle;
}
}
// Fix for IE, where it can't seem to handle 0 degree angles. Also avoids
// ugly line on unfilled donuts.
if (ang1 >= ang2) {
return;
}
ctx.beginPath();
ctx.fillStyle = color;
ctx.strokeStyle = color;
// ctx.lineWidth = lineWidth;
ctx.arc(0, 0, r, ang1, ang2, false);
ctx.lineTo(ri*Math.cos(ang2), ri*Math.sin(ang2));
ctx.arc(0,0, ri, ang2, ang1, true);
ctx.closePath();
if (fill) {
ctx.fill();
}
else {
ctx.stroke();
}
}
if (isShadow) {
for (var i=0; i 1 && this.index > 0) ? this._previousSeries[0]._diameter : this._diameter;
this._thickness = this.thickness || (od - this.innerDiameter - 2.0*ringmargin*this._numberSeries) / this._numberSeries/2.0;
}
else {
this._thickness = this.thickness || mindim / 2 / (this._numberSeries + 1) * 0.85;
}
if (this._diameter < 6) {
$.jqplot.log("Diameter of donut too small, not rendering.");
return;
}
var r = this._radius = this._diameter/2;
this._innerRadius = this._radius - this._thickness;
var sa = this.startAngle / 180 * Math.PI;
this._center = [(cw - trans * offx)/2 + trans * offx, (ch - trans*offy)/2 + trans * offy];
if (this.shadow) {
var shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
for (var i=0; i= this.dataLabelThreshold) {
var fstr, avgang = (ang1+ang2)/2, label;
if (this.dataLabels == 'label') {
fstr = this.dataLabelFormatString || '%s';
label = $.jqplot.sprintf(fstr, gd[i][0]);
}
else if (this.dataLabels == 'value') {
fstr = this.dataLabelFormatString || '%d';
label = $.jqplot.sprintf(fstr, this.data[i][1]);
}
else if (this.dataLabels == 'percent') {
fstr = this.dataLabelFormatString || '%d%%';
label = $.jqplot.sprintf(fstr, gd[i][2]*100);
}
else if (this.dataLabels.constructor == Array) {
fstr = this.dataLabelFormatString || '%s';
label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
}
var fact = this._innerRadius + this._thickness * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
var labelelem = $('' + label + '').insertBefore(plot.eventCanvas._elem);
x -= labelelem.width()/2;
y -= labelelem.height()/2;
x = Math.round(x);
y = Math.round(y);
labelelem.css({left: x, top: y});
}
}
if (this.totalLabel) {
var totalLabel = $('
' + this._totalAmount + '
').insertAfter(plot.eventCanvas._elem);
totalLabel.css({left: this._center[0], top: this._center[1]});
}
};
$.jqplot.DonutAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
};
$.jqplot.DonutAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.DonutAxisRenderer.prototype.constructor = $.jqplot.DonutAxisRenderer;
// There are no traditional axes on a donut chart. We just need to provide
// dummy objects with properties so the plot will render.
// called with scope of axis object.
$.jqplot.DonutAxisRenderer.prototype.init = function(options){
//
this.tickRenderer = $.jqplot.DonutTickRenderer;
$.extend(true, this, options);
// I don't think I'm going to need _dataBounds here.
// have to go Axis scaling in a way to fit chart onto plot area
// and provide u2p and p2u functionality for mouse cursor, etc.
// for convienence set _dataBounds to 0 and 100 and
// set min/max to 0 and 100.
this._dataBounds = {min:0, max:100};
this.min = 0;
this.max = 100;
this.showTicks = false;
this.ticks = [];
this.showMark = false;
this.show = false;
};
$.jqplot.DonutLegendRenderer = function(){
$.jqplot.TableLegendRenderer.call(this);
};
$.jqplot.DonutLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
$.jqplot.DonutLegendRenderer.prototype.constructor = $.jqplot.DonutLegendRenderer;
/**
* Class: $.jqplot.DonutLegendRenderer
* Legend Renderer specific to donut plots. Set by default
* when user creates a donut plot.
*/
$.jqplot.DonutLegendRenderer.prototype.init = function(options) {
// Group: Properties
//
// prop: numberRows
// Maximum number of rows in the legend. 0 or null for unlimited.
this.numberRows = null;
// prop: numberColumns
// Maximum number of columns in the legend. 0 or null for unlimited.
this.numberColumns = null;
$.extend(true, this, options);
};
// called with context of legend
$.jqplot.DonutLegendRenderer.prototype.draw = function() {
var legend = this;
if (this.show) {
var series = this._series;
var ss = 'position:absolute;';
ss += (this.background) ? 'background:'+this.background+';' : '';
ss += (this.border) ? 'border:'+this.border+';' : '';
ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
this._elem = $('
');
// Donut charts legends don't go by number of series, but by number of data points
// in the series. Refactor things here for that.
var pad = false,
reverse = false,
nr, nc;
var s = series[0];
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
if (s.show) {
var pd = s.data;
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(pd.length/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(pd.length/this.numberColumns);
}
else {
nr = pd.length;
nc = 1;
}
var i, j, tr, td1, td2, lt, rs, color;
var idx = 0;
for (i=0; i').prependTo(this._elem);
}
else{
tr = $('
').appendTo(this._elem);
}
for (j=0; j0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $('
'+
'
'+
'
');
td2 = $('
');
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html(lt);
}
if (reverse) {
td2.prependTo(tr);
td1.prependTo(tr);
}
else {
td1.appendTo(tr);
td2.appendTo(tr);
}
pad = true;
}
idx++;
}
}
}
}
return this._elem;
};
// setup default renderers for axes and legend so user doesn't have to
// called with scope of plot
function preInit(target, data, options) {
options = options || {};
options.axesDefaults = options.axesDefaults || {};
options.legend = options.legend || {};
options.seriesDefaults = options.seriesDefaults || {};
// only set these if there is a donut series
var setopts = false;
if (options.seriesDefaults.renderer == $.jqplot.DonutRenderer) {
setopts = true;
}
else if (options.series) {
for (var i=0; i < options.series.length; i++) {
if (options.series[i].renderer == $.jqplot.DonutRenderer) {
setopts = true;
}
}
}
if (setopts) {
options.axesDefaults.renderer = $.jqplot.DonutAxisRenderer;
options.legend.renderer = $.jqplot.DonutLegendRenderer;
options.legend.preDraw = true;
options.seriesDefaults.pointLabels = {show: false};
}
}
// called with scope of plot.
function postInit(target, data, options) {
// if multiple series, add a reference to the previous one so that
// donut rings can nest.
for (var i=1; i= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
drag.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
}
mr.color = drag.color;
mr.init();
var start = (neighbor.pointIndex > 0) ? neighbor.pointIndex - 1 : 0;
var end = neighbor.pointIndex+2;
drag._gridData = s.gridData.slice(start, end);
}
function handleMove(ev, gridpos, datapos, neighbor, plot) {
if (plot.plugins.dragable.dragCanvas.isDragging) {
var dc = plot.plugins.dragable.dragCanvas;
var dp = dc._neighbor;
var s = plot.series[dp.seriesIndex];
var drag = s.plugins.dragable;
var gd = s.gridData;
// compute the new grid position with any constraints.
var x = (drag.constrainTo == 'y') ? dp.gridData[0] : gridpos.x;
var y = (drag.constrainTo == 'x') ? dp.gridData[1] : gridpos.y;
// compute data values for any listeners.
var xu = s._xaxis.series_p2u(x);
var yu = s._yaxis.series_p2u(y);
// clear the canvas then redraw effect at new position.
var ctx = dc._ctx;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// adjust our gridData for the new mouse position
if (dp.pointIndex > 0) {
drag._gridData[1] = [x, y];
}
else {
drag._gridData[0] = [x, y];
}
plot.series[dp.seriesIndex].draw(dc._ctx, {gridData:drag._gridData, shadow:false, preventJqPlotSeriesDrawTrigger:true, color:drag.color, markerOptions:{color:drag.color, shadow:false}, trendline:{show:false}});
plot.target.trigger('jqplotSeriesPointChange', [dp.seriesIndex, dp.pointIndex, [xu,yu], [x,y]]);
}
else if (neighbor != null) {
var series = plot.series[neighbor.seriesIndex];
if (series.isDragable) {
var dc = plot.plugins.dragable.dragCanvas;
if (!dc.isOver) {
dc._cursors.push(ev.target.style.cursor);
ev.target.style.cursor = "pointer";
}
dc.isOver = true;
}
}
else if (neighbor == null) {
var dc = plot.plugins.dragable.dragCanvas;
if (dc.isOver) {
ev.target.style.cursor = dc._cursors.pop();
dc.isOver = false;
}
}
}
function handleDown(ev, gridpos, datapos, neighbor, plot) {
var dc = plot.plugins.dragable.dragCanvas;
dc._cursors.push(ev.target.style.cursor);
if (neighbor != null) {
var s = plot.series[neighbor.seriesIndex];
var drag = s.plugins.dragable;
if (s.isDragable && !dc.isDragging) {
dc._neighbor = neighbor;
dc.isDragging = true;
initDragPoint(plot, neighbor);
drag.markerRenderer.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], dc._ctx);
ev.target.style.cursor = "move";
plot.target.trigger('jqplotDragStart', [neighbor.seriesIndex, neighbor.pointIndex, gridpos, datapos]);
}
}
// Just in case of a hickup, we'll clear the drag canvas and reset.
else {
var ctx = dc._ctx;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
dc.isDragging = false;
}
}
function handleUp(ev, gridpos, datapos, neighbor, plot) {
if (plot.plugins.dragable.dragCanvas.isDragging) {
var dc = plot.plugins.dragable.dragCanvas;
// clear the canvas
var ctx = dc._ctx;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
dc.isDragging = false;
// redraw the series canvas at the new point.
var dp = dc._neighbor;
var s = plot.series[dp.seriesIndex];
var drag = s.plugins.dragable;
// compute the new grid position with any constraints.
var x = (drag.constrainTo == 'y') ? dp.data[0] : datapos[s.xaxis];
var y = (drag.constrainTo == 'x') ? dp.data[1] : datapos[s.yaxis];
// var x = datapos[s.xaxis];
// var y = datapos[s.yaxis];
s.data[dp.pointIndex][0] = x;
s.data[dp.pointIndex][1] = y;
plot.drawSeries({preventJqPlotSeriesDrawTrigger:true}, dp.seriesIndex);
dc._neighbor = null;
ev.target.style.cursor = dc._cursors.pop();
plot.target.trigger('jqplotDragStop', [gridpos, datapos]);
}
}
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.enhancedLegendRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
// class $.jqplot.EnhancedLegendRenderer
// Legend renderer which can specify the number of rows and/or columns in the legend.
$.jqplot.EnhancedLegendRenderer = function(){
$.jqplot.TableLegendRenderer.call(this);
};
$.jqplot.EnhancedLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
$.jqplot.EnhancedLegendRenderer.prototype.constructor = $.jqplot.EnhancedLegendRenderer;
// called with scope of legend.
$.jqplot.EnhancedLegendRenderer.prototype.init = function(options) {
// prop: numberRows
// Maximum number of rows in the legend. 0 or null for unlimited.
this.numberRows = null;
// prop: numberColumns
// Maximum number of columns in the legend. 0 or null for unlimited.
this.numberColumns = null;
// prop: seriesToggle
// false to not enable series on/off toggling on the legend.
// true or a fadein/fadeout speed (number of milliseconds or 'fast', 'normal', 'slow')
// to enable show/hide of series on click of legend item.
this.seriesToggle = 'normal';
// prop: seriesToggleReplot
// True to replot the chart after toggling series on/off.
// This will set the series show property to false.
// This allows for rescaling or other maniplation of chart.
// Set to an options object (e.g. {resetAxes: true}) for replot options.
this.seriesToggleReplot = false;
// prop: disableIEFading
// true to toggle series with a show/hide method only and not allow fading in/out.
// This is to overcome poor performance of fade in some versions of IE.
this.disableIEFading = true;
$.extend(true, this, options);
if (this.seriesToggle) {
$.jqplot.postDrawHooks.push(postDraw);
}
};
// called with scope of legend
$.jqplot.EnhancedLegendRenderer.prototype.draw = function(offsets, plot) {
var legend = this;
if (this.show) {
var series = this._series;
var s;
var ss = 'position:absolute;';
ss += (this.background) ? 'background:'+this.background+';' : '';
ss += (this.border) ? 'border:'+this.border+';' : '';
ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
this._elem = $('
');
if (this.seriesToggle) {
this._elem.css('z-index', '3');
}
var pad = false,
reverse = false,
nr, nc;
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(series.length/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(series.length/this.numberColumns);
}
else {
nr = series.length;
nc = 1;
}
var i, j, tr, td1, td2, lt, rs, div, div0, div1;
var idx = 0;
// check to see if we need to reverse
for (i=series.length-1; i>=0; i--) {
if (nc == 1 && series[i]._stack || series[i].renderer.constructor == $.jqplot.BezierCurveRenderer){
reverse = true;
}
}
for (i=0; i0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $(document.createElement('td'));
td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
td1.css({textAlign: 'center', paddingTop: rs});
div0 = $(document.createElement('div'));
div0.addClass('jqplot-table-legend-swatch-outline');
div1 = $(document.createElement('div'));
div1.addClass('jqplot-table-legend-swatch');
div1.css({backgroundColor: color, borderColor: color});
td1.append(div0.append(div1));
td2 = $(document.createElement('td'));
td2.addClass('jqplot-table-legend jqplot-table-legend-label');
td2.css('paddingTop', rs);
// td1 = $('
'+
// '
'+
// '
');
// td2 = $('
');
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html(lt);
}
if (reverse) {
if (this.showLabels) {td2.prependTo(tr);}
if (this.showSwatches) {td1.prependTo(tr);}
}
else {
if (this.showSwatches) {td1.appendTo(tr);}
if (this.showLabels) {td2.appendTo(tr);}
}
if (this.seriesToggle) {
// add an overlay for clicking series on/off
// div0 = $(document.createElement('div'));
// div0.addClass('jqplot-table-legend-overlay');
// div0.css({position:'relative', left:0, top:0, height:'100%', width:'100%'});
// tr.append(div0);
var speed;
if (typeof(this.seriesToggle) === 'string' || typeof(this.seriesToggle) === 'number') {
if (!$.jqplot.use_excanvas || !this.disableIEFading) {
speed = this.seriesToggle;
}
}
if (this.showSwatches) {
td1.bind('click', {series:s, speed:speed, plot: plot, replot:this.seriesToggleReplot}, handleToggle);
td1.addClass('jqplot-seriesToggle');
}
if (this.showLabels) {
td2.bind('click', {series:s, speed:speed, plot: plot, replot:this.seriesToggleReplot}, handleToggle);
td2.addClass('jqplot-seriesToggle');
}
// for series that are already hidden, add the hidden class
if (!s.show && s.showLabel) {
td1.addClass('jqplot-series-hidden');
td2.addClass('jqplot-series-hidden');
}
}
pad = true;
}
}
idx++;
}
td1 = td2 = div0 = div1 = null;
}
}
return this._elem;
};
var handleToggle = function (ev) {
var d = ev.data,
s = d.series,
replot = d.replot,
plot = d.plot,
speed = d.speed,
sidx = s.index,
showing = false;
if (s.canvas._elem.is(':hidden') || !s.show) {
showing = true;
}
var doLegendToggle = function() {
if (replot) {
var opts = {};
if ($.isPlainObject(replot)) {
$.extend(true, opts, replot);
}
plot.replot(opts);
// if showing, there was no canvas element to fade in, so hide here
// and then do a fade in.
if (showing && speed) {
var s = plot.series[sidx];
if (s.shadowCanvas._elem) {
s.shadowCanvas._elem.hide().fadeIn(speed);
}
s.canvas._elem.hide().fadeIn(speed);
s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide().fadeIn(speed);
}
}
else {
var s = plot.series[sidx];
if (s.canvas._elem.is(':hidden') || !s.show) {
// Not sure if there is a better way to check for showSwatches and showLabels === true.
// Test for "undefined" since default values for both showSwatches and showLables is true.
if (typeof plot.options.legend.showSwatches === 'undefined' || plot.options.legend.showSwatches === true) {
plot.legend._elem.find('td').eq(sidx * 2).addClass('jqplot-series-hidden');
}
if (typeof plot.options.legend.showLabels === 'undefined' || plot.options.legend.showLabels === true) {
plot.legend._elem.find('td').eq((sidx * 2) + 1).addClass('jqplot-series-hidden');
}
}
else {
if (typeof plot.options.legend.showSwatches === 'undefined' || plot.options.legend.showSwatches === true) {
plot.legend._elem.find('td').eq(sidx * 2).removeClass('jqplot-series-hidden');
}
if (typeof plot.options.legend.showLabels === 'undefined' || plot.options.legend.showLabels === true) {
plot.legend._elem.find('td').eq((sidx * 2) + 1).removeClass('jqplot-series-hidden');
}
}
}
};
s.toggleDisplay(ev, doLegendToggle);
};
// called with scope of plot.
var postDraw = function () {
if (this.legend.renderer.constructor == $.jqplot.EnhancedLegendRenderer && this.legend.seriesToggle){
var e = this.legend._elem.detach();
this.eventCanvas._elem.after(e);
}
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.enhancedPieLegendRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
// class $.jqplot.EnhancedPieLegendRenderer
// Legend renderer which can specify the number of rows and/or columns in the legend
// Similar to EnhancedLegendRenderer, but for pie charts
$.jqplot.EnhancedPieLegendRenderer = function(){
$.jqplot.TableLegendRenderer.call(this);
};
$.jqplot.EnhancedPieLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
$.jqplot.EnhancedPieLegendRenderer.prototype.constructor = $.jqplot.EnhancedPieLegendRenderer;
// called with scope of legend.
$.jqplot.EnhancedPieLegendRenderer.prototype.init = function(options) {
// prop: numberRows
// Maximum number of rows in the legend. 0 or null for unlimited.
this.numberRows = null;
// prop: numberColumns
// Maximum number of columns in the legend. 0 or null for unlimited.
this.numberColumns = null;
// prop: seriesToggle
// false to not enable series on/off toggling on the legend.
// true or a fadein/fadeout speed (number of milliseconds or 'fast', 'normal', 'slow')
// to enable show/hide of series on click of legend item.
this.seriesToggle = 'normal';
// prop: seriesToggleReplot
// True to replot the chart after toggling series on/off.
// This will set the series show property to false.
// This allows for rescaling or other maniplation of chart.
// Set to an options object (e.g. {resetAxes: true}) for replot options.
this.seriesToggleReplot = false;
// prop: disableIEFading
// true to toggle series with a show/hide method only and not allow fading in/out.
// This is to overcome poor performance of fade in some versions of IE.
this.disableIEFading = true;
// prop: toolTips
// optional array of toolTip text corresponding to each pie slice
this.toolTips = [];
$.extend(true, this, options);
if (this.seriesToggle) {
$.jqplot.postDrawHooks.push(postDraw);
}
};
// called with scope of legend
$.jqplot.EnhancedPieLegendRenderer.prototype.draw = function(offsets, plot) {
var legend = this;
if (this.show) {
var series = this._series;
var s;
var ss = 'position:absolute;';
ss += (this.background) ? 'background:'+this.background+';' : '';
ss += (this.border) ? 'border:'+this.border+';' : '';
ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
this._elem = $('
');
if (this.seriesToggle) {
this._elem.css('z-index', '3');
}
var pad = false,
reverse = false,
nr, nc;
var s = series[0];
var slen = s.data.length;
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(slen/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(slen/this.numberColumns);
}
else {
nr = slen;
nc = 1;
}
var i, j, tr, td1, td2, lt, rs, div, div0, div1;
var idx = 0;
// check to see if we need to reverse
for (i=series.length-1; i>=0; i--) {
if (nc == 1 && series[i]._stack || series[i].renderer.constructor == $.jqplot.BezierCurveRenderer){
reverse = true;
}
}
for (i=0; i0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $(document.createElement('td'));
td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
td1.css({textAlign: 'center', paddingTop: rs});
div0 = $(document.createElement('div'));
div0.addClass('jqplot-table-legend-swatch-outline');
if (tt !== undefined) {
div0.attr("title", tt);
}
div1 = $(document.createElement('div'));
div1.addClass('jqplot-table-legend-swatch');
div1.css({backgroundColor: color, borderColor: color});
td1.append(div0.append(div1));
td2 = $(document.createElement('td'));
td2.addClass('jqplot-table-legend jqplot-table-legend-label');
td2.css('paddingTop', rs);
if (tt !== undefined) {
td2.attr("title", tt);
}
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html(lt);
}
if (reverse) {
if (this.showLabels) {td2.prependTo(tr);}
if (this.showSwatches) {td1.prependTo(tr);}
}
else {
if (this.showSwatches) {td1.appendTo(tr);}
if (this.showLabels) {td2.appendTo(tr);}
}
if (this.seriesToggle) {
var speed;
if (typeof(this.seriesToggle) === 'string' || typeof(this.seriesToggle) === 'number') {
if (!$.jqplot.use_excanvas || !this.disableIEFading) {
speed = this.seriesToggle;
}
}
if (this.showSwatches) {
td1.bind('click', {series:s, index:idx, speed:speed, plot: plot, replot:this.seriesToggleReplot}, handleToggle);
td1.addClass('jqplot-seriesToggle');
}
if (this.showLabels) {
td2.bind('click', {series:s, index:idx, speed:speed, plot: plot, replot:this.seriesToggleReplot}, handleToggle);
td2.addClass('jqplot-seriesToggle');
}
// for slices that are already hidden, add the hidden class
if (s.showSlice[idx] === false && s.showLabel) {
td1.addClass('jqplot-series-hidden');
td2.addClass('jqplot-series-hidden');
}
}
pad = true;
}
}
idx++;
}
td1 = td2 = div0 = div1 = null;
}
}
return this._elem;
};
var handleToggle = function (ev) {
var d = ev.data,
replot = d.replot,
plot = d.plot,
idx = d.index;
d.series.showSlice[idx] = (d.series.showSlice[idx] === false) ? true : false;
var opts = {};
if ($.isPlainObject(replot)) {
$.extend(true, opts, replot);
}
plot.replot(opts);
};
// called with scope of plot.
var postDraw = function () {
if (this.legend.renderer.constructor == $.jqplot.EnhancedPieLegendRenderer && this.legend.seriesToggle) {
var e = this.legend._elem.detach();
this.eventCanvas._elem.after(e);
}
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.funnelRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.FunnelRenderer
* Plugin renderer to draw a funnel chart.
* x values, if present, will be used as labels.
* y values give area size.
*
* Funnel charts will draw a single series
* only.
*
* To use this renderer, you need to include the
* funnel renderer plugin, for example:
*
* >
*
* Properties described here are passed into the $.jqplot function
* as options on the series renderer. For example:
*
* > plot2 = $.jqplot('chart2', [s1, s2], {
* > seriesDefaults: {
* > renderer:$.jqplot.FunnelRenderer,
* > rendererOptions:{
* > sectionMargin: 12,
* > widthRatio: 0.3
* > }
* > }
* > });
*
* IMPORTANT
*
* *The funnel renderer will reorder data in descending order* so the largest value in
* the data set is first and displayed on top of the funnel. Data will then
* be displayed in descending order down the funnel. The area of each funnel
* section will correspond to the value of each data point relative to the sum
* of all values. That is section area is proportional to section value divided by
* sum of all section values.
*
* If your data is not in descending order when passed into the plot, *it will be
* reordered* when stored in the series.data property. A copy of the unordered
* data is kept in the series._unorderedData property.
*
* A funnel plot will trigger events on the plot target
* according to user interaction. All events return the event object,
* the series index, the point (section) index, and the point data for
* the appropriate section. *Note* the point index will referr to the ordered
* data, not the original unordered data.
*
* 'jqplotDataMouseOver' - triggered when mousing over a section.
* 'jqplotDataHighlight' - triggered the first time user mouses over a section,
* if highlighting is enabled.
* 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
* a highlighted section.
* 'jqplotDataClick' - triggered when the user clicks on a section.
* 'jqplotDataRightClick' - tiggered when the user right clicks on a section if
* the "captureRightClick" option is set to true on the plot.
*/
$.jqplot.FunnelRenderer = function(){
$.jqplot.LineRenderer.call(this);
};
$.jqplot.FunnelRenderer.prototype = new $.jqplot.LineRenderer();
$.jqplot.FunnelRenderer.prototype.constructor = $.jqplot.FunnelRenderer;
// called with scope of a series
$.jqplot.FunnelRenderer.prototype.init = function(options, plot) {
// Group: Properties
//
// prop: padding
// padding between the funnel and plot edges, legend, etc.
this.padding = {top: 20, right: 20, bottom: 20, left: 20};
// prop: sectionMargin
// spacing between funnel sections in pixels.
this.sectionMargin = 6;
// prop: fill
// true or false, whether to fill the areas.
this.fill = true;
// prop: shadowOffset
// offset of the shadow from the area and offset of
// each succesive stroke of the shadow from the last.
this.shadowOffset = 2;
// prop: shadowAlpha
// transparency of the shadow (0 = transparent, 1 = opaque)
this.shadowAlpha = 0.07;
// prop: shadowDepth
// number of strokes to apply to the shadow,
// each stroke offset shadowOffset from the last.
this.shadowDepth = 5;
// prop: highlightMouseOver
// True to highlight area when moused over.
// This must be false to enable highlightMouseDown to highlight when clicking on a area.
this.highlightMouseOver = true;
// prop: highlightMouseDown
// True to highlight when a mouse button is pressed over a area.
// This will be disabled if highlightMouseOver is true.
this.highlightMouseDown = false;
// prop: highlightColors
// array of colors to use when highlighting an area.
this.highlightColors = [];
// prop: widthRatio
// The ratio of the width of the top of the funnel to the bottom.
// a ratio of 0 will make an upside down pyramid.
this.widthRatio = 0.2;
// prop: lineWidth
// width of line if areas are stroked and not filled.
this.lineWidth = 2;
// prop: dataLabels
// Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
// Defaults to percentage of each pie slice.
this.dataLabels = 'percent';
// prop: showDataLabels
// true to show data labels on slices.
this.showDataLabels = false;
// prop: dataLabelFormatString
// Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
this.dataLabelFormatString = null;
// prop: dataLabelThreshold
// Threshhold in percentage (0 - 100) of pie area, below which no label will be displayed.
// This applies to all label types, not just to percentage labels.
this.dataLabelThreshold = 3;
this._type = 'funnel';
this.tickRenderer = $.jqplot.FunnelTickRenderer;
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
if (options.highlightMouseDown && options.highlightMouseOver == null) {
options.highlightMouseOver = false;
}
$.extend(true, this, options);
// index of the currenty highlighted point, if any
this._highlightedPoint = null;
// lengths of bases, or horizontal sides of areas of trapezoid.
this._bases = [];
// total area
this._atot;
// areas of segments.
this._areas = [];
// vertical lengths of segments.
this._lengths = [];
// angle of the funnel to vertical.
this._angle;
this._dataIndices = [];
// sort data
this._unorderedData = $.extend(true, [], this.data);
var idxs = $.extend(true, [], this.data);
for (var i=0; i 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.4 * (255 - newrgb[j]);
newrgb[j] = parseInt(newrgb[j], 10);
}
this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
}
}
plot.postParseOptionsHooks.addOnce(postParseOptions);
plot.postInitHooks.addOnce(postInit);
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
plot.postDrawHooks.addOnce(postPlotDraw);
};
// gridData will be of form [label, percentage of total]
$.jqplot.FunnelRenderer.prototype.setGridData = function(plot) {
// set gridData property. This will hold angle in radians of each data point.
var sum = 0;
var td = [];
for (var i=0; i this._lengths[i]*tolerance && count < 100) {
this._lengths[i] = this._areas[i]/(this._bases[i] - this._lengths[i] * Math.tan(this._angle));
err = Math.abs(this._lengths[i] - guess);
this._bases[i+1] = this._bases[i] - (2*this._lengths[i]*Math.tan(this._angle));
guess = this._lengths[i];
count++;
}
lsum += this._lengths[i];
}
// figure out vertices of each section
this._vertices = new Array(gd.length);
// these are 4 coners of entire trapezoid
var p0 = [loff, toff],
p1 = [loff+this._bases[0], toff],
p2 = [loff + (this._bases[0] - this._bases[this._bases.length-1])/2, toff + this._length],
p3 = [p2[0] + this._bases[this._bases.length-1], p2[1]];
// equations of right and left sides, returns x, y values given height of section (y value)
function findleft (l) {
var m = (p0[1] - p2[1])/(p0[0] - p2[0]);
var b = p0[1] - m*p0[0];
var y = l + p0[1];
return [(y - b)/m, y];
}
function findright (l) {
var m = (p1[1] - p3[1])/(p1[0] - p3[0]);
var b = p1[1] - m*p1[0];
var y = l + p1[1];
return [(y - b)/m, y];
}
var x = offx, y = offy;
var h=0, adj=0;
for (i=0; i 0 && i < gd.length-1) {
adj = sm/2;
}
else if (i == gd.length -1) {
adj = 2*sm/3;
}
v.push(findleft(h+adj));
v.push(findright(h+adj));
h += this._lengths[i];
if (i == 0) {
adj = -2*sm/3;
}
else if (i > 0 && i < gd.length-1) {
adj = -sm/2;
}
else if (i == gd.length - 1) {
adj = 0;
}
v.push(findright(h+adj));
v.push(findleft(h+adj));
}
if (this.shadow) {
var shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
for (var i=0; i= this.dataLabelThreshold) {
var fstr, label;
if (this.dataLabels == 'label') {
fstr = this.dataLabelFormatString || '%s';
label = $.jqplot.sprintf(fstr, gd[i][0]);
}
else if (this.dataLabels == 'value') {
fstr = this.dataLabelFormatString || '%d';
label = $.jqplot.sprintf(fstr, this.data[i][1]);
}
else if (this.dataLabels == 'percent') {
fstr = this.dataLabelFormatString || '%d%%';
label = $.jqplot.sprintf(fstr, gd[i][1]*100);
}
else if (this.dataLabels.constructor == Array) {
fstr = this.dataLabelFormatString || '%s';
label = $.jqplot.sprintf(fstr, this.dataLabels[this._dataIndices[i]]);
}
var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
var x = (v[0][0] + v[1][0])/2 + this.canvas._offsets.left;
var y = (v[1][1] + v[2][1])/2 + this.canvas._offsets.top;
var labelelem = $('' + label + '').insertBefore(plot.eventCanvas._elem);
x -= labelelem.width()/2;
y -= labelelem.height()/2;
x = Math.round(x);
y = Math.round(y);
labelelem.css({left: x, top: y});
}
}
};
$.jqplot.FunnelAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
};
$.jqplot.FunnelAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.FunnelAxisRenderer.prototype.constructor = $.jqplot.FunnelAxisRenderer;
// There are no traditional axes on a funnel chart. We just need to provide
// dummy objects with properties so the plot will render.
// called with scope of axis object.
$.jqplot.FunnelAxisRenderer.prototype.init = function(options){
//
this.tickRenderer = $.jqplot.FunnelTickRenderer;
$.extend(true, this, options);
// I don't think I'm going to need _dataBounds here.
// have to go Axis scaling in a way to fit chart onto plot area
// and provide u2p and p2u functionality for mouse cursor, etc.
// for convienence set _dataBounds to 0 and 100 and
// set min/max to 0 and 100.
this._dataBounds = {min:0, max:100};
this.min = 0;
this.max = 100;
this.showTicks = false;
this.ticks = [];
this.showMark = false;
this.show = false;
};
/**
* Class: $.jqplot.FunnelLegendRenderer
* Legend Renderer specific to funnel plots. Set by default
* when the user creates a funnel plot.
*/
$.jqplot.FunnelLegendRenderer = function(){
$.jqplot.TableLegendRenderer.call(this);
};
$.jqplot.FunnelLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
$.jqplot.FunnelLegendRenderer.prototype.constructor = $.jqplot.FunnelLegendRenderer;
$.jqplot.FunnelLegendRenderer.prototype.init = function(options) {
// Group: Properties
//
// prop: numberRows
// Maximum number of rows in the legend. 0 or null for unlimited.
this.numberRows = null;
// prop: numberColumns
// Maximum number of columns in the legend. 0 or null for unlimited.
this.numberColumns = null;
$.extend(true, this, options);
};
// called with context of legend
$.jqplot.FunnelLegendRenderer.prototype.draw = function() {
var legend = this;
if (this.show) {
var series = this._series;
var ss = 'position:absolute;';
ss += (this.background) ? 'background:'+this.background+';' : '';
ss += (this.border) ? 'border:'+this.border+';' : '';
ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
this._elem = $('
');
// Funnel charts legends don't go by number of series, but by number of data points
// in the series. Refactor things here for that.
var pad = false,
reverse = false,
nr, nc;
var s = series[0];
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
if (s.show) {
var pd = s.data;
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(pd.length/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(pd.length/this.numberColumns);
}
else {
nr = pd.length;
nc = 1;
}
var i, j, tr, td1, td2, lt, rs, color;
var idx = 0;
for (i=0; i').prependTo(this._elem);
}
else{
tr = $('
').appendTo(this._elem);
}
for (j=0; j0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $('
'+
'
'+
'
');
td2 = $('
');
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html(lt);
}
if (reverse) {
td2.prependTo(tr);
td1.prependTo(tr);
}
else {
td1.appendTo(tr);
td2.appendTo(tr);
}
pad = true;
}
idx++;
}
}
}
}
return this._elem;
};
// $.jqplot.FunnelLegendRenderer.prototype.pack = function(offsets) {
// if (this.show) {
// // fake a grid for positioning
// var grid = {_top:offsets.top, _left:offsets.left, _right:offsets.right, _bottom:this._plotDimensions.height - offsets.bottom};
// if (this.placement == 'insideGrid') {
// switch (this.location) {
// case 'nw':
// var a = grid._left + this.xoffset;
// var b = grid._top + this.yoffset;
// this._elem.css('left', a);
// this._elem.css('top', b);
// break;
// case 'n':
// var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
// var b = grid._top + this.yoffset;
// this._elem.css('left', a);
// this._elem.css('top', b);
// break;
// case 'ne':
// var a = offsets.right + this.xoffset;
// var b = grid._top + this.yoffset;
// this._elem.css({right:a, top:b});
// break;
// case 'e':
// var a = offsets.right + this.xoffset;
// var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
// this._elem.css({right:a, top:b});
// break;
// case 'se':
// var a = offsets.right + this.xoffset;
// var b = offsets.bottom + this.yoffset;
// this._elem.css({right:a, bottom:b});
// break;
// case 's':
// var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
// var b = offsets.bottom + this.yoffset;
// this._elem.css({left:a, bottom:b});
// break;
// case 'sw':
// var a = grid._left + this.xoffset;
// var b = offsets.bottom + this.yoffset;
// this._elem.css({left:a, bottom:b});
// break;
// case 'w':
// var a = grid._left + this.xoffset;
// var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
// this._elem.css({left:a, top:b});
// break;
// default: // same as 'se'
// var a = grid._right - this.xoffset;
// var b = grid._bottom + this.yoffset;
// this._elem.css({right:a, bottom:b});
// break;
// }
//
// }
// else {
// switch (this.location) {
// case 'nw':
// var a = this._plotDimensions.width - grid._left + this.xoffset;
// var b = grid._top + this.yoffset;
// this._elem.css('right', a);
// this._elem.css('top', b);
// break;
// case 'n':
// var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
// var b = this._plotDimensions.height - grid._top + this.yoffset;
// this._elem.css('left', a);
// this._elem.css('bottom', b);
// break;
// case 'ne':
// var a = this._plotDimensions.width - offsets.right + this.xoffset;
// var b = grid._top + this.yoffset;
// this._elem.css({left:a, top:b});
// break;
// case 'e':
// var a = this._plotDimensions.width - offsets.right + this.xoffset;
// var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
// this._elem.css({left:a, top:b});
// break;
// case 'se':
// var a = this._plotDimensions.width - offsets.right + this.xoffset;
// var b = offsets.bottom + this.yoffset;
// this._elem.css({left:a, bottom:b});
// break;
// case 's':
// var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
// var b = this._plotDimensions.height - offsets.bottom + this.yoffset;
// this._elem.css({left:a, top:b});
// break;
// case 'sw':
// var a = this._plotDimensions.width - grid._left + this.xoffset;
// var b = offsets.bottom + this.yoffset;
// this._elem.css({right:a, bottom:b});
// break;
// case 'w':
// var a = this._plotDimensions.width - grid._left + this.xoffset;
// var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
// this._elem.css({right:a, top:b});
// break;
// default: // same as 'se'
// var a = grid._right - this.xoffset;
// var b = grid._bottom + this.yoffset;
// this._elem.css({right:a, bottom:b});
// break;
// }
// }
// }
// };
// setup default renderers for axes and legend so user doesn't have to
// called with scope of plot
function preInit(target, data, options) {
options = options || {};
options.axesDefaults = options.axesDefaults || {};
options.legend = options.legend || {};
options.seriesDefaults = options.seriesDefaults || {};
// only set these if there is a funnel series
var setopts = false;
if (options.seriesDefaults.renderer == $.jqplot.FunnelRenderer) {
setopts = true;
}
else if (options.series) {
for (var i=0; i < options.series.length; i++) {
if (options.series[i].renderer == $.jqplot.FunnelRenderer) {
setopts = true;
}
}
}
if (setopts) {
options.axesDefaults.renderer = $.jqplot.FunnelAxisRenderer;
options.legend.renderer = $.jqplot.FunnelLegendRenderer;
options.legend.preDraw = true;
options.sortData = false;
options.seriesDefaults.pointLabels = {show: false};
}
}
function postInit(target, data, options) {
// if multiple series, add a reference to the previous one so that
// funnel rings can nest.
for (var i=0; i
*
* A tooltip providing information about the data point is enabled by default.
* To disable the tooltip, set "showTooltip" to false.
*
* You can control what data is displayed in the tooltip with various
* options. The "tooltipAxes" option controls whether the x, y or both
* data values are displayed.
*
* Some chart types (e.g. hi-low-close) have more than one y value per
* data point. To display the additional values in the tooltip, set the
* "yvalues" option to the desired number of y values present (3 for a hlc chart).
*
* By default, data values will be formatted with the same formatting
* specifiers as used to format the axis ticks. A custom format code
* can be supplied with the tooltipFormatString option. This will apply
* to all values in the tooltip.
*
* For more complete control, the "formatString" option can be set. This
* Allows conplete control over tooltip formatting. Values are passed to
* the format string in an order determined by the "tooltipAxes" and "yvalues"
* options. So, if you have a hi-low-close chart and you just want to display
* the hi-low-close values in the tooltip, you could set a formatString like:
*
* > highlighter: {
* > tooltipAxes: 'y',
* > yvalues: 3,
* > formatString:'
* >
hi:
%s
* >
low:
%s
* >
close:
%s
'
* > }
*
*/
$.jqplot.Highlighter = function(options) {
// Group: Properties
//
//prop: show
// true to show the highlight.
this.show = $.jqplot.config.enablePlugins;
// prop: markerRenderer
// Renderer used to draw the marker of the highlighted point.
// Renderer will assimilate attributes from the data point being highlighted,
// so no attributes need set on the renderer directly.
// Default is to turn off shadow drawing on the highlighted point.
this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false});
// prop: showMarker
// true to show the marker
this.showMarker = true;
// prop: lineWidthAdjust
// Pixels to add to the lineWidth of the highlight.
this.lineWidthAdjust = 2.5;
// prop: sizeAdjust
// Pixels to add to the overall size of the highlight.
this.sizeAdjust = 5;
// prop: showTooltip
// Show a tooltip with data point values.
this.showTooltip = true;
// prop: tooltipLocation
// Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
this.tooltipLocation = 'nw';
// prop: fadeTooltip
// true = fade in/out tooltip, flase = show/hide tooltip
this.fadeTooltip = true;
// prop: tooltipFadeSpeed
// 'slow', 'def', 'fast', or number of milliseconds.
this.tooltipFadeSpeed = "fast";
// prop: tooltipOffset
// Pixel offset of tooltip from the highlight.
this.tooltipOffset = 2;
// prop: tooltipAxes
// Which axes to display in tooltip, 'x', 'y' or 'both', 'xy' or 'yx'
// 'both' and 'xy' are equivalent, 'yx' reverses order of labels.
this.tooltipAxes = 'both';
// prop; tooltipSeparator
// String to use to separate x and y axes in tooltip.
this.tooltipSeparator = ', ';
// prop; tooltipContentEditor
// Function used to edit/augment/replace the formatted tooltip contents.
// Called as str = tooltipContentEditor(str, seriesIndex, pointIndex)
// where str is the generated tooltip html and seriesIndex and pointIndex identify
// the data point being highlighted. Should return the html for the tooltip contents.
this.tooltipContentEditor = null;
// prop: useAxesFormatters
// Use the x and y axes formatters to format the text in the tooltip.
this.useAxesFormatters = true;
// prop: tooltipFormatString
// sprintf format string for the tooltip.
// Uses Ash Searle's javascript sprintf implementation
// found here: http://hexmen.com/blog/2007/03/printf-sprintf/
// See http://perldoc.perl.org/functions/sprintf.html for reference.
// Additional "p" and "P" format specifiers added by Chris Leonello.
this.tooltipFormatString = '%.5P';
// prop: formatString
// alternative to tooltipFormatString
// will format the whole tooltip text, populating with x, y values as
// indicated by tooltipAxes option. So, you could have a tooltip like:
// 'Date: %s, number of cats: %d' to format the whole tooltip at one go.
// If useAxesFormatters is true, values will be formatted according to
// Axes formatters and you can populate your tooltip string with
// %s placeholders.
this.formatString = null;
// prop: yvalues
// Number of y values to expect in the data point array.
// Typically this is 1. Certain plots, like OHLC, will
// have more y values in each data point array.
this.yvalues = 1;
// prop: bringSeriesToFront
// This option requires jQuery 1.4+
// True to bring the series of the highlighted point to the front
// of other series.
this.bringSeriesToFront = false;
this._tooltipElem;
this.isHighlighting = false;
this.currentNeighbor = null;
$.extend(true, this, options);
};
var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
// axis.renderer.tickrenderer.formatter
// called with scope of plot
$.jqplot.Highlighter.init = function (target, data, opts){
var options = opts || {};
// add a highlighter attribute to the plot
this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter);
};
// called within scope of series
$.jqplot.Highlighter.parseOptions = function (defaults, options) {
// Add a showHighlight option to the series
// and set it to true by default.
this.showHighlight = true;
};
// called within context of plot
// create a canvas which we can draw on.
// insert it before the eventCanvas, so eventCanvas will still capture events.
$.jqplot.Highlighter.postPlotDraw = function() {
// Memory Leaks patch
if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) {
this.plugins.highlighter.highlightCanvas.resetCanvas();
this.plugins.highlighter.highlightCanvas = null;
}
if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) {
this.plugins.highlighter._tooltipElem.emptyForce();
this.plugins.highlighter._tooltipElem = null;
}
this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas();
this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this));
this.plugins.highlighter.highlightCanvas.setContext();
var elem = document.createElement('div');
this.plugins.highlighter._tooltipElem = $(elem);
elem = null;
this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip');
this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'});
this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem);
};
$.jqplot.preInitHooks.push($.jqplot.Highlighter.init);
$.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions);
$.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw);
function draw(plot, neighbor) {
var hl = plot.plugins.highlighter;
var s = plot.series[neighbor.seriesIndex];
var smr = s.markerRenderer;
var mr = hl.markerRenderer;
mr.style = smr.style;
mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust;
mr.size = smr.size + hl.sizeAdjust;
var rgba = $.jqplot.getColorComponents(smr.color);
var newrgb = [rgba[0], rgba[1], rgba[2]];
var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
mr.init();
mr.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], hl.highlightCanvas._ctx);
}
function showTooltip(plot, series, neighbor) {
// neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}
// gridData should be x,y pixel coords on the grid.
// add the plot._gridPadding to that to get x,y in the target.
var hl = plot.plugins.highlighter;
var elem = hl._tooltipElem;
var serieshl = series.highlighter || {};
var opts = $.extend(true, {}, hl, serieshl);
if (opts.useAxesFormatters) {
var xf = series._xaxis._ticks[0].formatter;
var yf = series._yaxis._ticks[0].formatter;
var xfstr = series._xaxis._ticks[0].formatString;
var yfstr = series._yaxis._ticks[0].formatString;
var str;
var xstr = xf(xfstr, neighbor.data[0]);
var ystrs = [];
for (var i=1; i
*
* and supply the appropriate options to your plot
*
* > {axes:{xaxis:{renderer:$.jqplot.LogAxisRenderer}}}
**/
$.jqplot.LogAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
// prop: axisDefaults
// Default properties which will be applied directly to the series.
//
// Group: Properties
//
// Properties
//
// base - the logarithmic base, commonly 2, 10 or Math.E
// tickDistribution - Deprecated. "power" distribution of ticks
// always used. Option has no effect.
this.axisDefaults = {
base : 10,
tickDistribution :'power'
};
};
$.jqplot.LogAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.LogAxisRenderer.prototype.constructor = $.jqplot.LogAxisRenderer;
$.jqplot.LogAxisRenderer.prototype.init = function(options) {
// prop: drawBaseline
// True to draw the axis baseline.
this.drawBaseline = true;
// prop: minorTicks
// Number of ticks to add between "major" ticks.
// Major ticks are ticks supplied by user or auto computed.
// Minor ticks cannot be created by user.
this.minorTicks = 'auto';
this._scalefact = 1.0;
$.extend(true, this, options);
this._autoFormatString = '%d';
this._overrideFormatString = false;
for (var d in this.renderer.axisDefaults) {
if (this[d] == null) {
this[d] = this.renderer.axisDefaults[d];
}
}
this.resetDataBounds();
};
$.jqplot.LogAxisRenderer.prototype.createTicks = function(plot) {
// we're are operating on an axis here
var ticks = this._ticks;
var userTicks = this.ticks;
var name = this.name;
var db = this._dataBounds;
var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
var interval;
var min, max;
var pos1, pos2;
var tt, i;
var threshold = 30;
// For some reason scalefactor is screwing up ticks.
this._scalefact = (Math.max(dim, threshold+1) - threshold)/300;
// if we already have ticks, use them.
// ticks must be in order of increasing value.
if (userTicks.length) {
// ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
for (i=0; i 140) {
numberTicks = Math.round(Math.log(this.max/this.min)/Math.log(this.base) + 1);
if (numberTicks < 2) {
numberTicks = 2;
}
if (minorTicks === 0) {
var temp = dim/(numberTicks - 1);
if (temp < 100) {
minorTicks = 0;
}
else if (temp < 190) {
minorTicks = 1;
}
else if (temp < 250) {
minorTicks = 3;
}
else if (temp < 600) {
minorTicks = 4;
}
else {
minorTicks = 9;
}
}
}
else {
numberTicks = 2;
if (minorTicks === 0) {
minorTicks = 1;
}
minorTicks = 0;
}
}
else {
numberTicks = this.numberTicks;
}
if (order >= 0 && minorTicks !== 3) {
this._autoFormatString = '%d';
}
// Adjust format string for case with 3 ticks where we'll have like 1, 2.5, 5, 7.5, 10
else if (order <= 0 && minorTicks === 3) {
var temp = -(order - 1);
this._autoFormatString = '%.'+ Math.abs(order-1) + 'f';
}
// Adjust format string for values less than 1.
else if (order < 0) {
var temp = -order;
this._autoFormatString = '%.'+ Math.abs(order) + 'f';
}
else {
this._autoFormatString = '%d';
}
var to, t, val, tt1, spread, interval;
for (var i=0; i=0; j--) {
val = tt1-interval*(j+1);
t = new this.tickRenderer(this.tickOptions);
if (this._overrideFormatString && this._autoFormatString != '') {
t.formatString = this._autoFormatString;
}
if (!this.showTicks) {
t.showLabel = false;
t.showMark = false;
}
else if (!this.showTickMarks) {
t.showMark = false;
}
t.setTick(val, this.name);
this._ticks.push(t);
}
}
}
}
// min and max are set as would be the case with zooming
else if (this.min != null && this.max != null) {
var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
var nt, ti;
// don't have an interval yet, pick one that gives the most
// "round" ticks we can get.
if (this.numberTicks == null && this.tickInterval == null) {
// var threshold = 30;
var tdim = Math.max(dim, threshold+1);
var nttarget = Math.ceil((tdim-threshold)/35 + 1);
var ret = $.jqplot.LinearTickGenerator.bestConstrainedInterval(this.min, this.max, nttarget);
this._autoFormatString = ret[3];
nt = ret[2];
ti = ret[4];
for (var i=0; i 0) {
shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
}
else {
shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
}
break;
case 'middle':
// if (t.angle > 0) {
// shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
// }
// else {
// shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
// }
shim = -t.getHeight()/2;
break;
default:
shim = -t.getHeight()/2;
break;
}
}
else {
shim = -t.getHeight()/2;
}
var val = this.u2p(t.value) + shim + 'px';
t._elem.css('top', val);
t.pack();
}
}
if (lshow) {
var h = this._label._elem.outerHeight(true);
this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
if (this.name == 'yaxis') {
this._label._elem.css('left', '0px');
}
else {
this._label._elem.css('right', '0px');
}
this._label.pack();
}
}
}
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.mekkoAxisRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
// class: $.jqplot.MekkoAxisRenderer
// An axis renderer for a Mekko chart.
// Should be used with a Mekko chart where the mekkoRenderer is used on the series.
// Displays the Y axis as a range from 0 to 1 (0 to 100%) and the x axis with a tick
// for each series scaled to the sum of all the y values.
$.jqplot.MekkoAxisRenderer = function() {
};
// called with scope of axis object.
$.jqplot.MekkoAxisRenderer.prototype.init = function(options){
// prop: tickMode
// How to space the ticks on the axis.
// 'bar' will place a tick at the width of each bar.
// This is the default for the x axis.
// 'even' will place ticks at even intervals. This is
// the default for x2 axis and y axis. y axis cannot be changed.
this.tickMode;
// prop: barLabelRenderer
// renderer to use to draw labels under each bar.
this.barLabelRenderer = $.jqplot.AxisLabelRenderer;
// prop: barLabels
// array of labels to put under each bar.
this.barLabels = this.barLabels || [];
// prop: barLabelOptions
// options object to pass to the bar label renderer.
this.barLabelOptions = {};
this.tickOptions = $.extend(true, {showGridline:false}, this.tickOptions);
this._barLabels = [];
$.extend(true, this, options);
if (this.name == 'yaxis') {
this.tickOptions.formatString = this.tickOptions.formatString || "%d\%";
}
var db = this._dataBounds;
db.min = 0;
// for y axes, scale always go from 0 to 1 (0 to 100%)
if (this.name == 'yaxis' || this.name == 'y2axis') {
db.max = 100;
this.tickMode = 'even';
}
// For x axes, scale goes from 0 to sum of all y values.
else if (this.name == 'xaxis'){
this.tickMode = (this.tickMode == null) ? 'bar' : this.tickMode;
for (var i=0; i dim) {
dim = temp;
}
}
}
if (lshow) {
w = this._label._elem.outerWidth(true);
h = this._label._elem.outerHeight(true);
}
if (this.name == 'xaxis') {
dim = dim + h;
this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
}
else if (this.name == 'x2axis') {
dim = dim + h;
this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
}
else if (this.name == 'yaxis') {
dim = dim + w;
this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
this._label._elem.css('width', w+'px');
}
}
else {
dim = dim + w;
this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
this._label._elem.css('width', w+'px');
}
}
}
};
// called with scope of axis
$.jqplot.MekkoAxisRenderer.prototype.createTicks = function() {
// we're are operating on an axis here
var ticks = this._ticks;
var userTicks = this.ticks;
var name = this.name;
// databounds were set on axis initialization.
var db = this._dataBounds;
var dim, interval;
var min, max;
var pos1, pos2;
var t, tt, i, j;
// if we already have ticks, use them.
// ticks must be in order of increasing value.
if (userTicks.length) {
// ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
for (i=0; i 0) {
adj = Math.max(Math.log(min)/Math.LN10, 0.05);
}
min -= adj;
max += adj;
}
var range = max - min;
var rmin, rmax;
var temp, prev, curr;
var ynumticks = [3,5,6,11,21];
// yaxis divide ticks in nice intervals from 0 to 1.
if (this.name == 'yaxis' || this.name == 'y2axis') {
this.min = 0;
this.max = 100;
// user didn't specify number of ticks.
if (!this.numberTicks){
if (this.tickInterval) {
this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
}
else {
temp = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
for (i=0; i 1) {
prev = curr;
continue;
}
else if (curr < 1) {
// was prev or is curr closer to one?
if (Math.abs(prev - 1) < Math.abs(curr - 1)) {
this.numberTicks = ynumticks[i-1];
break;
}
else {
this.numberTicks = ynumticks[i];
break;
}
}
else if (i == ynumticks.length -1) {
this.numberTicks = ynumticks[i];
}
}
this.tickInterval = range / (this.numberTicks - 1);
}
}
// user did specify number of ticks.
else {
this.tickInterval = range / (this.numberTicks - 1);
}
for (var i=0; i temp) {
t = new this.tickRenderer(this.tickOptions);
if (!this.showTicks) {
t.showLabel = false;
t.showMark = false;
}
else if (!this.showTickMarks) {
t.showMark = false;
}
t.setTick(this.max, this.name);
this._ticks.push(t);
}
}
else if (this.tickMode == 'even') {
this.min = 0;
this.max = this.max || db.max;
// get a desired number of ticks
var nt = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
range = this.max - this.min;
this.numberTicks = nt;
this.tickInterval = range / (this.numberTicks - 1);
for (i=0; i 0) {
shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
}
else {
shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
}
break;
case 'middle':
shim = -t.getHeight()/2;
break;
default:
shim = -t.getHeight()/2;
break;
}
}
else {
shim = -t.getHeight()/2;
}
var val = this.u2p(t.value) + shim + 'px';
t._elem.css('top', val);
t.pack();
}
}
if (lshow) {
var h = this._label._elem.outerHeight(true);
this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
if (this.name == 'yaxis') {
this._label._elem.css('left', '0px');
}
else {
this._label._elem.css('right', '0px');
}
this._label.pack();
}
}
}
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.mekkoRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.MekkoRenderer
* Draws a Mekko style chart which shows 3 dimensional data on a 2 dimensional graph.
* the <$.jqplot.MekkoAxisRenderer> should be used with mekko charts. The mekko renderer
* overrides the default legend renderer with its own $.jqplot.MekkoLegendRenderer
* which allows more flexibility to specify number of rows and columns in the legend.
*
* Data is specified per bar in the chart. You can specify data as an array of y values, or as
* an array of [label, value] pairs. Note that labels are used only on the first series.
* Labels on subsequent series are ignored:
*
* > bar1 = [['shirts', 8],['hats', 14],['shoes', 6],['gloves', 16],['dolls', 12]];
* > bar2 = [15,6,9,13,6];
* > bar3 = [['grumpy',4],['sneezy',2],['happy',7],['sleepy',9],['doc',7]];
*
* If you want to place labels for each bar under the axis, you use the barLabels option on
* the axes. The bar labels can be styled with the ".jqplot-mekko-barLabel" css class.
*
* > barLabels = ['Mickey Mouse', 'Donald Duck', 'Goofy'];
* > axes:{xaxis:{barLabels:barLabels}}
*
*/
$.jqplot.MekkoRenderer = function(){
this.shapeRenderer = new $.jqplot.ShapeRenderer();
// prop: borderColor
// color of the borders between areas on the chart
this.borderColor = null;
// prop: showBorders
// True to draw borders lines between areas on the chart.
// False will draw borders lines with the same color as the area.
this.showBorders = true;
};
// called with scope of series.
$.jqplot.MekkoRenderer.prototype.init = function(options, plot) {
this.fill = false;
this.fillRect = true;
this.strokeRect = true;
this.shadow = false;
// width of bar on x axis.
this._xwidth = 0;
this._xstart = 0;
$.extend(true, this.renderer, options);
// set the shape renderer options
var opts = {lineJoin:'miter', lineCap:'butt', isarc:false, fillRect:this.fillRect, strokeRect:this.strokeRect};
this.renderer.shapeRenderer.init(opts);
plot.axes.x2axis._series.push(this);
this._type = 'mekko';
};
// Method: setGridData
// converts the user data values to grid coordinates and stores them
// in the gridData array. Will convert user data into appropriate
// rectangles.
// Called with scope of a series.
$.jqplot.MekkoRenderer.prototype.setGridData = function(plot) {
// recalculate the grid data
var xp = this._xaxis.series_u2p;
var yp = this._yaxis.series_u2p;
var data = this._plotData;
this.gridData = [];
// figure out width on x axis.
// this._xwidth = this._sumy / plot._sumy * this.canvas.getWidth();
this._xwidth = xp(this._sumy) - xp(0);
if (this.index>0) {
this._xstart = plot.series[this.index-1]._xstart + plot.series[this.index-1]._xwidth;
}
var totheight = this.canvas.getHeight();
var sumy = 0;
var cury;
var curheight;
for (var i=0; i');
// Mekko charts legends don't go by number of series, but by number of data points
// in the series. Refactor things here for that.
var pad = false,
reverse = true, // mekko charts are always stacked, so reverse
nr, nc;
var s = series[0];
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
if (s.show) {
var pd = s.data;
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(pd.length/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(pd.length/this.numberColumns);
}
else {
nr = pd.length;
nc = 1;
}
var i, j, tr, td1, td2, lt, rs, color;
var idx = 0;
for (i=0; i').prependTo(this._elem);
}
else{
tr = $('
').appendTo(this._elem);
}
for (j=0; j0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $('
'+
'
'+
'
');
td2 = $('
');
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html(lt);
}
if (reverse) {
td2.prependTo(tr);
td1.prependTo(tr);
}
else {
td1.appendTo(tr);
td2.appendTo(tr);
}
pad = true;
}
idx++;
}
}
tr = null;
td1 = null;
td2 = null;
}
}
return this._elem;
};
$.jqplot.MekkoLegendRenderer.prototype.pack = function(offsets) {
if (this.show) {
// fake a grid for positioning
var grid = {_top:offsets.top, _left:offsets.left, _right:offsets.right, _bottom:this._plotDimensions.height - offsets.bottom};
if (this.placement == 'insideGrid') {
switch (this.location) {
case 'nw':
var a = grid._left + this.xoffset;
var b = grid._top + this.yoffset;
this._elem.css('left', a);
this._elem.css('top', b);
break;
case 'n':
var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
var b = grid._top + this.yoffset;
this._elem.css('left', a);
this._elem.css('top', b);
break;
case 'ne':
var a = offsets.right + this.xoffset;
var b = grid._top + this.yoffset;
this._elem.css({right:a, top:b});
break;
case 'e':
var a = offsets.right + this.xoffset;
var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
this._elem.css({right:a, top:b});
break;
case 'se':
var a = offsets.right + this.xoffset;
var b = offsets.bottom + this.yoffset;
this._elem.css({right:a, bottom:b});
break;
case 's':
var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
var b = offsets.bottom + this.yoffset;
this._elem.css({left:a, bottom:b});
break;
case 'sw':
var a = grid._left + this.xoffset;
var b = offsets.bottom + this.yoffset;
this._elem.css({left:a, bottom:b});
break;
case 'w':
var a = grid._left + this.xoffset;
var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
this._elem.css({left:a, top:b});
break;
default: // same as 'se'
var a = grid._right - this.xoffset;
var b = grid._bottom + this.yoffset;
this._elem.css({right:a, bottom:b});
break;
}
}
else {
switch (this.location) {
case 'nw':
var a = this._plotDimensions.width - grid._left + this.xoffset;
var b = grid._top + this.yoffset;
this._elem.css('right', a);
this._elem.css('top', b);
break;
case 'n':
var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
var b = this._plotDimensions.height - grid._top + this.yoffset;
this._elem.css('left', a);
this._elem.css('bottom', b);
break;
case 'ne':
var a = this._plotDimensions.width - offsets.right + this.xoffset;
var b = grid._top + this.yoffset;
this._elem.css({left:a, top:b});
break;
case 'e':
var a = this._plotDimensions.width - offsets.right + this.xoffset;
var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
this._elem.css({left:a, top:b});
break;
case 'se':
var a = this._plotDimensions.width - offsets.right + this.xoffset;
var b = offsets.bottom + this.yoffset;
this._elem.css({left:a, bottom:b});
break;
case 's':
var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
var b = this._plotDimensions.height - offsets.bottom + this.yoffset;
this._elem.css({left:a, top:b});
break;
case 'sw':
var a = this._plotDimensions.width - grid._left + this.xoffset;
var b = offsets.bottom + this.yoffset;
this._elem.css({right:a, bottom:b});
break;
case 'w':
var a = this._plotDimensions.width - grid._left + this.xoffset;
var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
this._elem.css({right:a, top:b});
break;
default: // same as 'se'
var a = grid._right - this.xoffset;
var b = grid._bottom + this.yoffset;
this._elem.css({right:a, bottom:b});
break;
}
}
}
};
// setup default renderers for axes and legend so user doesn't have to
// called with scope of plot
function preInit(target, data, options) {
options = options || {};
options.axesDefaults = options.axesDefaults || {};
options.legend = options.legend || {};
options.seriesDefaults = options.seriesDefaults || {};
var setopts = false;
if (options.seriesDefaults.renderer == $.jqplot.MekkoRenderer) {
setopts = true;
}
else if (options.series) {
for (var i=0; i < options.series.length; i++) {
if (options.series[i].renderer == $.jqplot.MekkoRenderer) {
setopts = true;
}
}
}
if (setopts) {
options.axesDefaults.renderer = $.jqplot.MekkoAxisRenderer;
options.legend.renderer = $.jqplot.MekkoLegendRenderer;
options.legend.preDraw = true;
}
}
$.jqplot.preInitHooks.push(preInit);
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.meterGaugeRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.MeterGaugeRenderer
* Plugin renderer to draw a meter gauge chart.
*
* Data consists of a single series with 1 data point to position the gauge needle.
*
* To use this renderer, you need to include the
* meter gauge renderer plugin, for example:
*
* >
*
* Properties described here are passed into the $.jqplot function
* as options on the series renderer. For example:
*
* > plot0 = $.jqplot('chart0',[[18]],{
* > title: 'Network Speed',
* > seriesDefaults: {
* > renderer: $.jqplot.MeterGaugeRenderer,
* > rendererOptions: {
* > label: 'MB/s'
* > }
* > }
* > });
*
* A meterGauge plot does not support events.
*/
$.jqplot.MeterGaugeRenderer = function(){
$.jqplot.LineRenderer.call(this);
};
$.jqplot.MeterGaugeRenderer.prototype = new $.jqplot.LineRenderer();
$.jqplot.MeterGaugeRenderer.prototype.constructor = $.jqplot.MeterGaugeRenderer;
// called with scope of a series
$.jqplot.MeterGaugeRenderer.prototype.init = function(options) {
// Group: Properties
//
// prop: diameter
// Outer diameter of the meterGauge, auto computed by default
this.diameter = null;
// prop: padding
// padding between the meterGauge and plot edges, auto
// calculated by default.
this.padding = null;
// prop: shadowOffset
// offset of the shadow from the gauge ring and offset of
// each succesive stroke of the shadow from the last.
this.shadowOffset = 2;
// prop: shadowAlpha
// transparency of the shadow (0 = transparent, 1 = opaque)
this.shadowAlpha = 0.07;
// prop: shadowDepth
// number of strokes to apply to the shadow,
// each stroke offset shadowOffset from the last.
this.shadowDepth = 4;
// prop: background
// background color of the inside of the gauge.
this.background = "#efefef";
// prop: ringColor
// color of the outer ring, hub, and needle of the gauge.
this.ringColor = "#BBC6D0";
// needle color not implemented yet.
this.needleColor = "#C3D3E5";
// prop: tickColor
// color of the tick marks around the gauge.
this.tickColor = "#989898";
// prop: ringWidth
// width of the ring around the gauge. Auto computed by default.
this.ringWidth = null;
// prop: min
// Minimum value on the gauge. Auto computed by default
this.min;
// prop: max
// Maximum value on the gauge. Auto computed by default
this.max;
// prop: ticks
// Array of tick values. Auto computed by default.
this.ticks = [];
// prop: showTicks
// true to show ticks around gauge.
this.showTicks = true;
// prop: showTickLabels
// true to show tick labels next to ticks.
this.showTickLabels = true;
// prop: label
// A gauge label like 'kph' or 'Volts'
this.label = null;
// prop: labelHeightAdjust
// Number of Pixels to offset the label up (-) or down (+) from its default position.
this.labelHeightAdjust = 0;
// prop: labelPosition
// Where to position the label, either 'inside' or 'bottom'.
this.labelPosition = 'inside';
// prop: intervals
// Array of ranges to be drawn around the gauge.
// Array of form:
// > [value1, value2, ...]
// indicating the values for the first, second, ... intervals.
this.intervals = [];
// prop: intervalColors
// Array of colors to use for the intervals.
this.intervalColors = [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"];
// prop: intervalInnerRadius
// Radius of the inner circle of the interval ring.
this.intervalInnerRadius = null;
// prop: intervalOuterRadius
// Radius of the outer circle of the interval ring.
this.intervalOuterRadius = null;
this.tickRenderer = $.jqplot.MeterGaugeTickRenderer;
// ticks spaced every 1, 2, 2.5, 5, 10, 20, .1, .2, .25, .5, etc.
this.tickPositions = [1, 2, 2.5, 5, 10];
// prop: tickSpacing
// Degrees between ticks. This is a target number, if
// incompatible span and ticks are supplied, a suitable
// spacing close to this value will be computed.
this.tickSpacing = 30;
this.numberMinorTicks = null;
// prop: hubRadius
// Radius of the hub at the bottom center of gauge which the needle attaches to.
// Auto computed by default
this.hubRadius = null;
// prop: tickPadding
// padding of the tick marks to the outer ring and the tick labels to marks.
// Auto computed by default.
this.tickPadding = null;
// prop: needleThickness
// Maximum thickness the needle. Auto computed by default.
this.needleThickness = null;
// prop: needlePad
// Padding between needle and inner edge of the ring when the needle is at the min or max gauge value.
this.needlePad = 6;
// prop: pegNeedle
// True will stop needle just below/above the min/max values if data is below/above min/max,
// as if the meter is "pegged".
this.pegNeedle = true;
this._type = 'meterGauge';
$.extend(true, this, options);
this.type = null;
this.numberTicks = null;
this.tickInterval = null;
// span, the sweep (in degrees) from min to max. This gauge is
// a semi-circle.
this.span = 180;
// get rid of this nonsense
// this.innerSpan = this.span;
if (this.type == 'circular') {
this.semiCircular = false;
}
else if (this.type != 'circular') {
this.semiCircular = true;
}
else {
this.semiCircular = (this.span <= 180) ? true : false;
}
this._tickPoints = [];
// reference to label element.
this._labelElem = null;
// start the gauge at the beginning of the span
this.startAngle = (90 + (360 - this.span)/2) * Math.PI/180;
this.endAngle = (90 - (360 - this.span)/2) * Math.PI/180;
this.setmin = !!(this.min == null);
this.setmax = !!(this.max == null);
// if given intervals and is an array of values, create labels and colors.
if (this.intervals.length) {
if (this.intervals[0].length == null || this.intervals.length == 1) {
for (var i=0; i= this.data[0][1]) {
this.max = this.intervals[this.intervals.length-1][0];
this.setmax = false;
}
}
else {
this.setmax = false;
}
}
else {
// no ticks and no intervals supplied, put needle in middle
this.min = (this.min == null) ? 0 : this.min;
this.setmin = false;
if (this.max == null) {
this.max = this.data[0][1] * 1.25;
this.setmax = true;
}
else {
this.setmax = false;
}
}
};
$.jqplot.MeterGaugeRenderer.prototype.setGridData = function(plot) {
// set gridData property. This will hold angle in radians of each data point.
var stack = [];
var td = [];
var sa = this.startAngle;
for (var i=0; i0) {
stack[i] += stack[i-1];
}
}
var fact = Math.PI*2/stack[stack.length - 1];
for (var i=0; i0) {
stack[i] += stack[i-1];
}
}
var fact = Math.PI*2/stack[stack.length - 1];
for (var i=0; i=0; i--) {
temp = interval/(pos[i] * Math.pow(10, fact));
if (temp == 4 || temp == 5) {
return temp - 1;
}
}
return null;
}
// called with scope of series
$.jqplot.MeterGaugeRenderer.prototype.draw = function (ctx, gd, options) {
var i;
var opts = (options != undefined) ? options : {};
// offset and direction of offset due to legend placement
var offx = 0;
var offy = 0;
var trans = 1;
if (options.legendInfo && options.legendInfo.placement == 'inside') {
var li = options.legendInfo;
switch (li.location) {
case 'nw':
offx = li.width + li.xoffset;
break;
case 'w':
offx = li.width + li.xoffset;
break;
case 'sw':
offx = li.width + li.xoffset;
break;
case 'ne':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'e':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'se':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'n':
offy = li.height + li.yoffset;
break;
case 's':
offy = li.height + li.yoffset;
trans = -1;
break;
default:
break;
}
}
// pre-draw so can get its dimensions.
if (this.label) {
this._labelElem = $('
'+this.label+'
');
this.canvas._elem.after(this._labelElem);
}
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
var cw = ctx.canvas.width;
var ch = ctx.canvas.height;
if (this.padding == null) {
this.padding = Math.round(Math.min(cw, ch)/30);
}
var w = cw - offx - 2 * this.padding;
var h = ch - offy - 2 * this.padding;
if (this.labelPosition == 'bottom' && this.label) {
h -= this._labelElem.outerHeight(true);
}
var mindim = Math.min(w,h);
var d = mindim;
if (!this.diameter) {
if (this.semiCircular) {
if ( w >= 2*h) {
if (!this.ringWidth) {
this.ringWidth = 2*h/35;
}
this.needleThickness = this.needleThickness || 2+Math.pow(this.ringWidth, 0.8);
this.innerPad = this.ringWidth/2 + this.needleThickness/2 + this.needlePad;
this.diameter = 2 * (h - 2*this.innerPad);
}
else {
if (!this.ringWidth) {
this.ringWidth = w/35;
}
this.needleThickness = this.needleThickness || 2+Math.pow(this.ringWidth, 0.8);
this.innerPad = this.ringWidth/2 + this.needleThickness/2 + this.needlePad;
this.diameter = w - 2*this.innerPad - this.ringWidth - this.padding;
}
// center taking into account legend and over draw for gauge bottom below hub.
// this will be center of hub.
this._center = [(cw - trans * offx)/2 + trans * offx, (ch + trans*offy - this.padding - this.ringWidth - this.innerPad)];
}
else {
if (!this.ringWidth) {
this.ringWidth = d/35;
}
this.needleThickness = this.needleThickness || 2+Math.pow(this.ringWidth, 0.8);
this.innerPad = 0;
this.diameter = d - this.ringWidth;
// center in middle of canvas taking into account legend.
// will be center of hub.
this._center = [(cw-trans*offx)/2 + trans * offx, (ch-trans*offy)/2 + trans * offy];
}
if (this._labelElem && this.labelPosition == 'bottom') {
this._center[1] -= this._labelElem.outerHeight(true);
}
}
this._radius = this.diameter/2;
this.tickSpacing = 6000/this.diameter;
if (!this.hubRadius) {
this.hubRadius = this.diameter/18;
}
this.shadowOffset = 0.5 + this.ringWidth/9;
this.shadowWidth = this.ringWidth*1;
this.tickPadding = 3 + Math.pow(this.diameter/20, 0.7);
this.tickOuterRadius = this._radius - this.ringWidth/2 - this.tickPadding;
this.tickLength = (this.showTicks) ? this._radius/13 : 0;
if (this.ticks.length == 0) {
// no ticks, lets make some.
var max = this.max,
min = this.min,
setmax = this.setmax,
setmin = this.setmin,
ti = (max - min) * this.tickSpacing / this.span;
var tf = Math.floor(parseFloat((Math.log(ti)/Math.log(10)).toFixed(11)));
var tp = (ti/Math.pow(10, tf));
(tp > 2 && tp <= 2.5) ? tp = 2.5 : tp = Math.ceil(tp);
var t = this.tickPositions;
var tpindex, nt;
for (i=0; i 0) ? min - min % ti : min - min % ti - ti;
if (!this.forceZero) {
var diff = Math.min(min - tmin, 0.8*ti);
var ntp = Math.floor(diff/t[tpindex]);
if (ntp > 1) {
tmin = tmin + t[tpindex] * (ntp-1);
if (parseInt(tmin, 10) != tmin && parseInt(tmin-t[tpindex], 10) == tmin-t[tpindex]) {
tmin = tmin - t[tpindex];
}
}
}
if (min == tmin) {
min -= ti;
}
else {
// tmin should always be lower than dataMin
if (min - tmin > 0.23*ti) {
min = tmin;
}
else {
min = tmin -ti;
nt += 1;
}
}
nt += 1;
var tmax = min + (nt - 1) * ti;
if (max >= tmax) {
tmax += ti;
nt += 1;
}
// now tmax should always be mroe than dataMax
if (tmax - max < 0.23*ti) {
tmax += ti;
nt += 1;
}
this.max = max = tmax;
this.min = min;
this.tickInterval = ti;
this.numberTicks = nt;
var it;
for (i=0; i= tmax) {
max = tmax + ti;
nt += 1;
}
else {
max = tmax;
}
this.tickInterval = this.tickInterval || ti;
this.numberTicks = this.numberTicks || nt;
var it;
for (i=0; i 1) {
var rstr = String(range);
if (rstr.search(/\./) == -1) {
var pos = rstr.search(/0+$/);
nonSigDigits = (pos > 0) ? rstr.length - pos - 1 : 0;
}
}
sigRange = range/Math.pow(10, nonSigDigits);
for (i=0; i'+this.ticks[i][1]+'
');
this.canvas._elem.after(elem);
ew = elem.outerWidth(true);
eh = elem.outerHeight(true);
l = this._tickPoints[i][0] - ew * (this._tickPoints[i][2]-Math.PI)/Math.PI - tp * Math.cos(this._tickPoints[i][2]);
t = this._tickPoints[i][1] - eh/2 + eh/2 * Math.pow(Math.abs((Math.sin(this._tickPoints[i][2]))), 0.5) + tp/3 * Math.pow(Math.abs((Math.sin(this._tickPoints[i][2]))), 0.5) ;
// t = this._tickPoints[i][1] - eh/2 - eh/2 * Math.sin(this._tickPoints[i][2]) - tp/2 * Math.sin(this._tickPoints[i][2]);
elem.css({left:l, top:t, color: this.tickColor});
dim = ew*Math.cos(this._tickPoints[i][2]) + eh*Math.sin(Math.PI/2+this._tickPoints[i][2]/2);
maxdim = (dim > maxdim) ? dim : maxdim;
}
}
// draw the gauge label
if (this.label && this.labelPosition == 'inside') {
var l = this._center[0] + this.canvas._offsets.left;
var tp = this.tickPadding * (1 - 1/(this.diameter/80+1));
var t = 0.5*(this._center[1] + this.canvas._offsets.top - this.hubRadius) + 0.5*(this._center[1] + this.canvas._offsets.top - this.tickOuterRadius + this.tickLength + tp) + this.labelHeightAdjust;
// this._labelElem = $('
'+this.label+'
');
// this.canvas._elem.after(this._labelElem);
l -= this._labelElem.outerWidth(true)/2;
t -= this._labelElem.outerHeight(true)/2;
this._labelElem.css({left:l, top:t});
}
else if (this.label && this.labelPosition == 'bottom') {
var l = this._center[0] + this.canvas._offsets.left - this._labelElem.outerWidth(true)/2;
var t = this._center[1] + this.canvas._offsets.top + this.innerPad + this.ringWidth + this.padding + this.labelHeightAdjust;
this._labelElem.css({left:l, top:t});
}
// draw the intervals
ctx.save();
var inner = this.intervalInnerRadius || this.hubRadius * 1.5;
if (this.intervalOuterRadius == null) {
if (this.showTickLabels) {
var outer = (this.tickOuterRadius - this.tickLength - this.tickPadding - this.diameter/8);
}
else {
var outer = (this.tickOuterRadius - this.tickLength - this.diameter/16);
}
}
else {
var outer = this.intervalOuterRadius;
}
var range = this.max - this.min;
var intrange = this.intervals[this.intervals.length-1] - this.min;
var start, end, span = this.span*Math.PI/180;
for (i=0; i this.max + dataspan*3/this.span) {
datapoint = this.max + dataspan*3/this.span;
}
if (this.data[0][1] < this.min - dataspan*3/this.span) {
datapoint = this.min - dataspan*3/this.span;
}
}
var dataang = (datapoint - this.min)/dataspan * this.span * Math.PI/180 + this.startAngle;
ctx.save();
ctx.beginPath();
ctx.fillStyle = this.ringColor;
ctx.strokeStyle = this.ringColor;
this.needleLength = (this.tickOuterRadius - this.tickLength) * 0.85;
this.needleThickness = (this.needleThickness < 2) ? 2 : this.needleThickness;
var endwidth = this.needleThickness * 0.4;
var dl = this.needleLength/10;
var dt = (this.needleThickness - endwidth)/10;
var templ;
for (var i=0; i<10; i++) {
templ = this.needleThickness - i*dt;
ctx.moveTo(dl*i*Math.cos(dataang), dl*i*Math.sin(dataang));
ctx.lineWidth = templ;
ctx.lineTo(dl*(i+1)*Math.cos(dataang), dl*(i+1)*Math.sin(dataang));
ctx.stroke();
}
ctx.restore();
}
else {
this._center = [(cw - trans * offx)/2 + trans * offx, (ch - trans*offy)/2 + trans * offy];
}
};
$.jqplot.MeterGaugeAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
};
$.jqplot.MeterGaugeAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.MeterGaugeAxisRenderer.prototype.constructor = $.jqplot.MeterGaugeAxisRenderer;
// There are no traditional axes on a gauge chart. We just need to provide
// dummy objects with properties so the plot will render.
// called with scope of axis object.
$.jqplot.MeterGaugeAxisRenderer.prototype.init = function(options){
//
this.tickRenderer = $.jqplot.MeterGaugeTickRenderer;
$.extend(true, this, options);
// I don't think I'm going to need _dataBounds here.
// have to go Axis scaling in a way to fit chart onto plot area
// and provide u2p and p2u functionality for mouse cursor, etc.
// for convienence set _dataBounds to 0 and 100 and
// set min/max to 0 and 100.
this._dataBounds = {min:0, max:100};
this.min = 0;
this.max = 100;
this.showTicks = false;
this.ticks = [];
this.showMark = false;
this.show = false;
};
$.jqplot.MeterGaugeLegendRenderer = function(){
$.jqplot.TableLegendRenderer.call(this);
};
$.jqplot.MeterGaugeLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
$.jqplot.MeterGaugeLegendRenderer.prototype.constructor = $.jqplot.MeterGaugeLegendRenderer;
/**
* Class: $.jqplot.MeterGaugeLegendRenderer
*Meter gauges don't typically have a legend, this overrides the default legend renderer.
*/
$.jqplot.MeterGaugeLegendRenderer.prototype.init = function(options) {
// Maximum number of rows in the legend. 0 or null for unlimited.
this.numberRows = null;
// Maximum number of columns in the legend. 0 or null for unlimited.
this.numberColumns = null;
$.extend(true, this, options);
};
// called with context of legend
$.jqplot.MeterGaugeLegendRenderer.prototype.draw = function() {
if (this.show) {
var series = this._series;
var ss = 'position:absolute;';
ss += (this.background) ? 'background:'+this.background+';' : '';
ss += (this.border) ? 'border:'+this.border+';' : '';
ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
this._elem = $('
');
// MeterGauge charts legends don't go by number of series, but by number of data points
// in the series. Refactor things here for that.
var pad = false,
reverse = false,
nr, nc;
var s = series[0];
if (s.show) {
var pd = s.data;
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(pd.length/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(pd.length/this.numberColumns);
}
else {
nr = pd.length;
nc = 1;
}
var i, j, tr, td1, td2, lt, rs, color;
var idx = 0;
for (i=0; i').prependTo(this._elem);
}
else{
tr = $('
').appendTo(this._elem);
}
for (j=0; j0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $('
'+
'
'+
'
');
td2 = $('
');
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html(lt);
}
if (reverse) {
td2.prependTo(tr);
td1.prependTo(tr);
}
else {
td1.appendTo(tr);
td2.appendTo(tr);
}
pad = true;
}
idx++;
}
}
}
}
return this._elem;
};
// setup default renderers for axes and legend so user doesn't have to
// called with scope of plot
function preInit(target, data, options) {
// debugger
options = options || {};
options.axesDefaults = options.axesDefaults || {};
options.legend = options.legend || {};
options.seriesDefaults = options.seriesDefaults || {};
options.grid = options.grid || {};
// only set these if there is a gauge series
var setopts = false;
if (options.seriesDefaults.renderer == $.jqplot.MeterGaugeRenderer) {
setopts = true;
}
else if (options.series) {
for (var i=0; i < options.series.length; i++) {
if (options.series[i].renderer == $.jqplot.MeterGaugeRenderer) {
setopts = true;
}
}
}
if (setopts) {
options.axesDefaults.renderer = $.jqplot.MeterGaugeAxisRenderer;
options.legend.renderer = $.jqplot.MeterGaugeLegendRenderer;
options.legend.preDraw = true;
options.grid.background = options.grid.background || 'white';
options.grid.drawGridlines = false;
options.grid.borderWidth = (options.grid.borderWidth != null) ? options.grid.borderWidth : 0;
options.grid.shadow = (options.grid.shadow != null) ? options.grid.shadow : false;
}
}
// called with scope of plot
function postParseOptions(options) {
//
}
$.jqplot.preInitHooks.push(preInit);
$.jqplot.postParseOptionsHooks.push(postParseOptions);
$.jqplot.MeterGaugeTickRenderer = function() {
$.jqplot.AxisTickRenderer.call(this);
};
$.jqplot.MeterGaugeTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
$.jqplot.MeterGaugeTickRenderer.prototype.constructor = $.jqplot.MeterGaugeTickRenderer;
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.mobile.js
================================================
/**
* jqplot.jquerymobile plugin
* jQuery Mobile virtual event support.
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2011 Takashi Okamoto
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
*/
(function($) {
function postInit(target, data, options){
this.bindCustomEvents = function() {
this.eventCanvas._elem.bind('vclick', {plot:this}, this.onClick);
this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick);
this.eventCanvas._elem.bind('taphold', {plot:this}, this.onDblClick);
this.eventCanvas._elem.bind('vmousedown', {plot:this}, this.onMouseDown);
this.eventCanvas._elem.bind('vmousemove', {plot:this}, this.onMouseMove);
this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter);
this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave);
if (this.captureRightClick) {
this.eventCanvas._elem.bind('vmouseup', {plot:this}, this.onRightClick);
this.eventCanvas._elem.get(0).oncontextmenu = function() {
return false;
};
}
else {
this.eventCanvas._elem.bind('vmouseup', {plot:this}, this.onMouseUp);
}
};
this.plugins.mobile = true;
}
$.jqplot.postInitHooks.push(postInit);
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.ohlcRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.OHLCRenderer
* jqPlot Plugin to draw Open Hi Low Close, Candlestick and Hi Low Close charts.
*
* To use this plugin, include the renderer js file in
* your source:
*
* >
*
* You will most likely want to use a date axis renderer
* for the x axis also, so include the date axis render js file also:
*
* >
*
* Then you set the renderer in the series options on your plot:
*
* > series: [{renderer:$.jqplot.OHLCRenderer}]
*
* For OHLC and candlestick charts, data should be specified
* like so:
*
* > dat = [['07/06/2009',138.7,139.68,135.18,135.4], ['06/29/2009',143.46,144.66,139.79,140.02], ...]
*
* If the data array has only 4 values per point instead of 5,
* the renderer will create a Hi Low Close chart instead. In that case,
* data should be supplied like:
*
* > dat = [['07/06/2009',139.68,135.18,135.4], ['06/29/2009',144.66,139.79,140.02], ...]
*
* To generate a candlestick chart instead of an OHLC chart,
* set the "candlestick" option to true:
*
* > series: [{renderer:$.jqplot.OHLCRenderer, rendererOptions:{candleStick:true}}],
*
*/
$.jqplot.OHLCRenderer = function(){
// subclass line renderer to make use of some of its methods.
$.jqplot.LineRenderer.call(this);
// prop: candleStick
// true to render chart as candleStick.
// Must have an open price, cannot be a hlc chart.
this.candleStick = false;
// prop: tickLength
// length of the line in pixels indicating open and close price.
// Default will auto calculate based on plot width and
// number of points displayed.
this.tickLength = 'auto';
// prop: bodyWidth
// width of the candlestick body in pixels. Default will auto calculate
// based on plot width and number of candlesticks displayed.
this.bodyWidth = 'auto';
// prop: openColor
// color of the open price tick mark. Default is series color.
this.openColor = null;
// prop: closeColor
// color of the close price tick mark. Default is series color.
this.closeColor = null;
// prop: wickColor
// color of the hi-lo line thorugh the candlestick body.
// Default is the series color.
this.wickColor = null;
// prop: fillUpBody
// true to render an "up" day (close price greater than open price)
// with a filled candlestick body.
this.fillUpBody = false;
// prop: fillDownBody
// true to render a "down" day (close price lower than open price)
// with a filled candlestick body.
this.fillDownBody = true;
// prop: upBodyColor
// Color of candlestick body of an "up" day. Default is series color.
this.upBodyColor = null;
// prop: downBodyColor
// Color of candlestick body on a "down" day. Default is series color.
this.downBodyColor = null;
// prop: hlc
// true if is a hi-low-close chart (no open price).
// This is determined automatically from the series data.
this.hlc = false;
// prop: lineWidth
// Width of the hi-low line and open/close ticks.
// Must be set in the rendererOptions for the series.
this.lineWidth = 1.5;
this._tickLength;
this._bodyWidth;
};
$.jqplot.OHLCRenderer.prototype = new $.jqplot.LineRenderer();
$.jqplot.OHLCRenderer.prototype.constructor = $.jqplot.OHLCRenderer;
// called with scope of series.
$.jqplot.OHLCRenderer.prototype.init = function(options) {
options = options || {};
// lineWidth has to be set on the series, changes in renderer
// constructor have no effect. set the default here
// if no renderer option for lineWidth is specified.
this.lineWidth = options.lineWidth || 1.5;
$.jqplot.LineRenderer.prototype.init.call(this, options);
this._type = 'ohlc';
// set the yaxis data bounds here to account for hi and low values
var db = this._yaxis._dataBounds;
var d = this._plotData;
// if data points have less than 5 values, force a hlc chart.
if (d[0].length < 5) {
this.renderer.hlc = true;
for (var j=0; j db.max || db.max == null) {
db.max = d[j][1];
}
}
}
else {
for (var j=0; j db.max || db.max == null) {
db.max = d[j][2];
}
}
}
};
// called within scope of series.
$.jqplot.OHLCRenderer.prototype.draw = function(ctx, gd, options) {
var d = this.data;
var xmin = this._xaxis.min;
var xmax = this._xaxis.max;
// index of last value below range of plot.
var xminidx = 0;
// index of first value above range of plot.
var xmaxidx = d.length;
var xp = this._xaxis.series_u2p;
var yp = this._yaxis.series_u2p;
var i, prevColor, ops, b, h, w, a, points;
var o;
var r = this.renderer;
var opts = (options != undefined) ? options : {};
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
r.bodyWidth = (opts.bodyWidth != undefined) ? opts.bodyWidth : r.bodyWidth;
r.tickLength = (opts.tickLength != undefined) ? opts.tickLength : r.tickLength;
ctx.save();
if (this.show) {
var x, open, hi, low, close;
// need to get widths based on number of points shown,
// not on total number of points. Use the results
// to speed up drawing in next step.
for (var i=0; i open) {
// draw wick
if (r.wickColor) {
o.color = r.wickColor;
}
else if (r.downBodyColor) {
o.color = r.downBodyColor;
}
ops = $.extend(true, {}, opts, o);
r.shapeRenderer.draw(ctx, [[x, hi], [x, open]], ops);
r.shapeRenderer.draw(ctx, [[x, close], [x, low]], ops);
o = {};
b = open;
h = close - open;
// if color specified, use it
if (r.fillDownBody) {
o.fillRect = true;
}
else {
o.strokeRect = true;
w = w - this.lineWidth;
a = x - w/2;
}
if (r.downBodyColor) {
o.color = r.downBodyColor;
o.fillStyle = r.downBodyColor;
}
points = [a, b, w, h];
}
// even, open = close
else {
// draw wick
if (r.wickColor) {
o.color = r.wickColor;
}
ops = $.extend(true, {}, opts, o);
r.shapeRenderer.draw(ctx, [[x, hi], [x, low]], ops);
o = {};
o.fillRect = false;
o.strokeRect = false;
a = [x - w/2, open];
b = [x + w/2, close];
w = null;
h = null;
points = [a, b];
}
ops = $.extend(true, {}, opts, o);
r.shapeRenderer.draw(ctx, points, ops);
}
else {
prevColor = opts.color;
if (r.openColor) {
opts.color = r.openColor;
}
// draw open tick
if (!r.hlc) {
r.shapeRenderer.draw(ctx, [[x-r._tickLength, open], [x, open]], opts);
}
opts.color = prevColor;
// draw wick
if (r.wickColor) {
opts.color = r.wickColor;
}
r.shapeRenderer.draw(ctx, [[x, hi], [x, low]], opts);
opts.color = prevColor;
// draw close tick
if (r.closeColor) {
opts.color = r.closeColor;
}
r.shapeRenderer.draw(ctx, [[x, close], [x+r._tickLength, close]], opts);
opts.color = prevColor;
}
}
}
ctx.restore();
};
$.jqplot.OHLCRenderer.prototype.drawShadow = function(ctx, gd, options) {
// This is a no-op, shadows drawn with lines.
};
// called with scope of plot.
$.jqplot.OHLCRenderer.checkOptions = function(target, data, options) {
// provide some sensible highlighter options by default
// These aren't good for hlc, only for ohlc or candlestick
if (!options.highlighter) {
options.highlighter = {
showMarker:false,
tooltipAxes: 'y',
yvalues: 4,
formatString:'
date:
%s
open:
%s
hi:
%s
low:
%s
close:
%s
'
};
}
};
//$.jqplot.preInitHooks.push($.jqplot.OHLCRenderer.checkOptions);
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.pieRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.PieRenderer
* Plugin renderer to draw a pie chart.
* x values, if present, will be used as slice labels.
* y values give slice size.
*
* To use this renderer, you need to include the
* pie renderer plugin, for example:
*
* >
*
* Properties described here are passed into the $.jqplot function
* as options on the series renderer. For example:
*
* > plot2 = $.jqplot('chart2', [s1, s2], {
* > seriesDefaults: {
* > renderer:$.jqplot.PieRenderer,
* > rendererOptions:{
* > sliceMargin: 2,
* > startAngle: -90
* > }
* > }
* > });
*
* A pie plot will trigger events on the plot target
* according to user interaction. All events return the event object,
* the series index, the point (slice) index, and the point data for
* the appropriate slice.
*
* 'jqplotDataMouseOver' - triggered when user mouseing over a slice.
* 'jqplotDataHighlight' - triggered the first time user mouses over a slice,
* if highlighting is enabled.
* 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
* a highlighted slice.
* 'jqplotLegendHighlight' - triggered the first time user mouses over a legend,
* if highlighting is enabled.
* 'jqplotLegendUnhighlight' - triggered when a user moves the mouse out of
* a highlighted legend.
* 'jqplotDataClick' - triggered when the user clicks on a slice.
* 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if
* the "captureRightClick" option is set to true on the plot.
*/
$.jqplot.PieRenderer = function(){
$.jqplot.LineRenderer.call(this);
};
$.jqplot.PieRenderer.prototype = new $.jqplot.LineRenderer();
$.jqplot.PieRenderer.prototype.constructor = $.jqplot.PieRenderer;
// called with scope of a series
$.jqplot.PieRenderer.prototype.init = function(options, plot) {
// Group: Properties
//
// prop: diameter
// Outer diameter of the pie, auto computed by default
this.diameter = null;
// prop: padding
// padding between the pie and plot edges, legend, etc.
this.padding = 20;
// prop: sliceMargin
// angular spacing between pie slices in degrees.
this.sliceMargin = 0;
// prop: fill
// true or false, whether to fil the slices.
this.fill = true;
// prop: shadowOffset
// offset of the shadow from the slice and offset of
// each succesive stroke of the shadow from the last.
this.shadowOffset = 2;
// prop: shadowAlpha
// transparency of the shadow (0 = transparent, 1 = opaque)
this.shadowAlpha = 0.07;
// prop: shadowDepth
// number of strokes to apply to the shadow,
// each stroke offset shadowOffset from the last.
this.shadowDepth = 5;
// prop: highlightMouseOver
// True to highlight slice when moused over.
// This must be false to enable highlightMouseDown to highlight when clicking on a slice.
this.highlightMouseOver = true;
// prop: highlightMouseDown
// True to highlight when a mouse button is pressed over a slice.
// This will be disabled if highlightMouseOver is true.
this.highlightMouseDown = false;
// prop: highlightColors
// an array of colors to use when highlighting a slice.
this.highlightColors = [];
// prop: dataLabels
// Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
// Defaults to percentage of each pie slice.
this.dataLabels = 'percent';
// prop: showDataLabels
// true to show data labels on slices.
this.showDataLabels = false;
// prop: dataLabelFormatString
// Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
this.dataLabelFormatString = null;
// prop: dataLabelThreshold
// Threshhold in percentage (0-100) of pie area, below which no label will be displayed.
// This applies to all label types, not just to percentage labels.
this.dataLabelThreshold = 3;
// prop: dataLabelPositionFactor
// A Multiplier (0-1) of the pie radius which controls position of label on slice.
// Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
this.dataLabelPositionFactor = 0.52;
// prop: dataLabelNudge
// Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
this.dataLabelNudge = 2;
// prop: dataLabelCenterOn
// True to center the data label at its position.
// False to set the inside facing edge of the label at its position.
this.dataLabelCenterOn = true;
// prop: startAngle
// Angle to start drawing pie in degrees.
// According to orientation of canvas coordinate system:
// 0 = on the positive x axis
// -90 = on the positive y axis.
// 90 = on the negaive y axis.
// 180 or - 180 = on the negative x axis.
this.startAngle = 0;
this.tickRenderer = $.jqplot.PieTickRenderer;
// prop: showSlice
// Array for whether the pie chart slice for a data element should be displayed.
// Containsg true or false for each data element. If not specified, defaults to true.
this.showSlice = [];
// Used as check for conditions where pie shouldn't be drawn.
this._drawData = true;
this._type = 'pie';
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
if (options.highlightMouseDown && options.highlightMouseOver == null) {
options.highlightMouseOver = false;
}
$.extend(true, this, options);
if (this.sliceMargin < 0) {
this.sliceMargin = 0;
}
this._diameter = null;
this._radius = null;
// array of [start,end] angles arrays, one for each slice. In radians.
this._sliceAngles = [];
// index of the currenty highlighted point, if any
this._highlightedPoint = null;
// set highlight colors if none provided
if (this.highlightColors.length == 0) {
for (var i=0; i 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
newrgb[j] = parseInt(newrgb[j], 10);
}
this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
}
}
this.highlightColorGenerator = new $.jqplot.ColorGenerator(this.highlightColors);
plot.postParseOptionsHooks.addOnce(postParseOptions);
plot.postInitHooks.addOnce(postInit);
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
plot.postDrawHooks.addOnce(postPlotDraw);
};
$.jqplot.PieRenderer.prototype.setGridData = function(plot) {
// set gridData property. This will hold angle in radians of each data point.
var stack = [];
var td = [];
var sa = this.startAngle/180*Math.PI;
var tot = 0;
// don't know if we have any valid data yet, so set plot to not draw.
this._drawData = false;
for (var i=0; i0) {
stack[i] += stack[i-1];
}
tot += this.data[i][1];
}
var fact = Math.PI*2/stack[stack.length - 1];
for (var i=0; i0) {
stack[i] += stack[i-1];
}
tot += data[i][1];
}
var fact = Math.PI*2/stack[stack.length - 1];
for (var i=0; i 0 && absang > 0.01 && absang < 6.282) {
rprime = parseFloat(sm) / 2.0 / calcRadiusAdjustment(ang);
}
return rprime;
}
$.jqplot.PieRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) {
if (this._drawData) {
var r = this._radius;
var fill = this.fill;
var lineWidth = this.lineWidth;
var sm = this.sliceMargin;
if (this.fill == false) {
sm += this.lineWidth;
}
ctx.save();
ctx.translate(this._center[0], this._center[1]);
var rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
var transx = rprime * Math.cos((ang1 + ang2) / 2.0);
var transy = rprime * Math.sin((ang1 + ang2) / 2.0);
if ((ang2 - ang1) <= Math.PI) {
r -= rprime;
}
else {
r += rprime;
}
ctx.translate(transx, transy);
if (isShadow) {
for (var i=0, l=this.shadowDepth; i 6.282 + this.startAngle) {
ang2 = 6.282 + this.startAngle;
if (ang1 > ang2) {
ang1 = 6.281 + this.startAngle;
}
}
// Fix for IE, where it can't seem to handle 0 degree angles. Also avoids
// ugly line on unfilled pies.
if (ang1 >= ang2) {
return;
}
ctx.beginPath();
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.arc(0, 0, rad, ang1, ang2, false);
ctx.lineTo(0,0);
ctx.closePath();
if (fill) {
ctx.fill();
}
else {
ctx.stroke();
}
}
};
// called with scope of series
$.jqplot.PieRenderer.prototype.draw = function (ctx, gd, options, plot) {
var i;
var opts = (options != undefined) ? options : {};
// offset and direction of offset due to legend placement
var offx = 0;
var offy = 0;
var trans = 1;
var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
var sliceColor;
if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
var li = options.legendInfo;
switch (li.location) {
case 'nw':
offx = li.width + li.xoffset;
break;
case 'w':
offx = li.width + li.xoffset;
break;
case 'sw':
offx = li.width + li.xoffset;
break;
case 'ne':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'e':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'se':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'n':
offy = li.height + li.yoffset;
break;
case 's':
offy = li.height + li.yoffset;
trans = -1;
break;
default:
break;
}
}
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
//see http://stackoverflow.com/questions/20221461/hidpi-retina-plot-drawing
var cw = parseInt(ctx.canvas.style.width);
var ch = parseInt(ctx.canvas.style.height);
//
var w = cw - offx - 2 * this.padding;
var h = ch - offy - 2 * this.padding;
var mindim = Math.min(w,h);
var d = mindim;
// Fixes issue #272. Thanks hugwijst!
// reset slice angles array.
this._sliceAngles = [];
var sm = this.sliceMargin;
if (this.fill == false) {
sm += this.lineWidth;
}
var rprime;
var maxrprime = 0;
var ang, ang1, ang2, shadowColor;
var sa = this.startAngle / 180 * Math.PI;
// have to pre-draw shadows, so loop throgh here and calculate some values also.
for (var i=0, l=gd.length; i Math.PI) {
maxrprime = Math.max(rprime, maxrprime);
}
}
if (this.diameter != null && this.diameter > 0) {
this._diameter = this.diameter - 2*maxrprime;
}
else {
this._diameter = d - 2*maxrprime;
}
// Need to check for undersized pie. This can happen if
// plot area too small and legend is too big.
if (this._diameter < 6) {
$.jqplot.log('Diameter of pie too small, not rendering.');
return;
}
var r = this._radius = this._diameter/2;
this._center = [(cw - trans * offx)/2 + trans * offx + maxrprime * Math.cos(sa), (ch - trans*offy)/2 + trans * offy + maxrprime * Math.sin(sa)];
if (this.shadow) {
for (var i=0, l=gd.length; i= this.dataLabelThreshold) {
var fstr, avgang = (this._sliceAngles[i][0] + this._sliceAngles[i][1])/2, label;
if (this.dataLabels == 'label') {
fstr = this.dataLabelFormatString || '%s';
label = $.jqplot.sprintf(fstr, gd[i][0]);
}
else if (this.dataLabels == 'value') {
fstr = this.dataLabelFormatString || '%d';
label = $.jqplot.sprintf(fstr, this.data[i][1]);
}
else if (this.dataLabels == 'percent') {
fstr = this.dataLabelFormatString || '%d%%';
label = $.jqplot.sprintf(fstr, gd[i][2]*100);
}
else if (this.dataLabels.constructor == Array) {
fstr = this.dataLabelFormatString || '%s';
label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
}
var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
var labelelem = $('
' + label + '
').insertBefore(plot.eventCanvas._elem);
if (this.dataLabelCenterOn) {
x -= labelelem.width()/2;
y -= labelelem.height()/2;
}
else {
x -= labelelem.width() * Math.sin(avgang/2);
y -= labelelem.height()/2;
}
x = Math.round(x);
y = Math.round(y);
labelelem.css({left: x, top: y});
}
}
}
};
$.jqplot.PieAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
};
$.jqplot.PieAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.PieAxisRenderer.prototype.constructor = $.jqplot.PieAxisRenderer;
// There are no traditional axes on a pie chart. We just need to provide
// dummy objects with properties so the plot will render.
// called with scope of axis object.
$.jqplot.PieAxisRenderer.prototype.init = function(options){
//
this.tickRenderer = $.jqplot.PieTickRenderer;
$.extend(true, this, options);
// I don't think I'm going to need _dataBounds here.
// have to go Axis scaling in a way to fit chart onto plot area
// and provide u2p and p2u functionality for mouse cursor, etc.
// for convienence set _dataBounds to 0 and 100 and
// set min/max to 0 and 100.
this._dataBounds = {min:0, max:100};
this.min = 0;
this.max = 100;
this.showTicks = false;
this.ticks = [];
this.showMark = false;
this.show = false;
};
$.jqplot.PieLegendRenderer = function(){
$.jqplot.TableLegendRenderer.call(this);
};
$.jqplot.PieLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
$.jqplot.PieLegendRenderer.prototype.constructor = $.jqplot.PieLegendRenderer;
/**
* Class: $.jqplot.PieLegendRenderer
* Legend Renderer specific to pie plots. Set by default
* when user creates a pie plot.
*/
$.jqplot.PieLegendRenderer.prototype.init = function(options) {
// Group: Properties
//
// prop: numberRows
// Maximum number of rows in the legend. 0 or null for unlimited.
this.numberRows = null;
// prop: numberColumns
// Maximum number of columns in the legend. 0 or null for unlimited.
this.numberColumns = null;
// prop: width
// Fixed with of legend. 0 or null for auto size
this.width = null;
$.extend(true, this, options);
};
// called with context of legend
$.jqplot.PieLegendRenderer.prototype.draw = function() {
var legend = this;
if (this.show) {
var series = this._series;
this._elem = $(document.createElement('table'));
this._elem.addClass('jqplot-table-legend');
var ss = {position:'absolute'};
if (this.background) {
ss['background'] = this.background;
}
if (this.border) {
ss['border'] = this.border;
}
if (this.fontSize) {
ss['fontSize'] = this.fontSize;
}
if (this.fontFamily) {
ss['fontFamily'] = this.fontFamily;
}
if (this.textColor) {
ss['textColor'] = this.textColor;
}
if (this.marginTop != null) {
ss['marginTop'] = this.marginTop;
}
if (this.marginBottom != null) {
ss['marginBottom'] = this.marginBottom;
}
if (this.marginLeft != null) {
ss['marginLeft'] = this.marginLeft;
}
if (this.marginRight != null) {
ss['marginRight'] = this.marginRight;
}
this._elem.css(ss);
// Pie charts legends don't go by number of series, but by number of data points
// in the series. Refactor things here for that.
var pad = false,
reverse = false,
nr,
nc;
var s = series[0];
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
if (s.show) {
var pd = s.data;
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(pd.length/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(pd.length/this.numberColumns);
}
else {
nr = pd.length;
nc = 1;
}
var i, j;
var tr, td1, td2;
var lt, tt, rs, color;
var idx = 0;
var div0, div1;
for (i=0; i0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $(document.createElement('td'));
td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
td1.css({textAlign: 'center', paddingTop: rs});
div0 = $(document.createElement('div'));
div0.addClass('jqplot-table-legend-swatch-outline');
if (tt !== '') {
div0.attr("title", tt);
}
div1 = $(document.createElement('div'));
div1.addClass('jqplot-table-legend-swatch');
div1.css({backgroundColor: color, borderColor: color});
td1.append(div0.append(div1));
td2 = $(document.createElement('td'));
td2.addClass('jqplot-table-legend jqplot-table-legend-label');
td2.css('paddingTop', rs);
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html('' + lt + "");
}
if (reverse) {
td2.prependTo(tr);
td1.prependTo(tr);
}
else {
td1.appendTo(tr);
td2.appendTo(tr);
}
pad = true;
}
idx++;
}
}
}
}
return this._elem;
};
$.jqplot.PieRenderer.prototype.handleMove = function(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
plot.target.trigger('jqplotDataMouseOver', ins);
if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
plot.target.trigger('jqplotDataHighlight', ins);
highlight (plot, ins[0], ins[1]);
}
}
else if (neighbor == null) {
unhighlight (plot);
}
};
// this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
// setup default renderers for axes and legend so user doesn't have to
// called with scope of plot
function preInit(target, data, options) {
options = options || {};
options.axesDefaults = options.axesDefaults || {};
options.legend = options.legend || {};
options.seriesDefaults = options.seriesDefaults || {};
// only set these if there is a pie series
var setopts = false;
if (options.seriesDefaults.renderer == $.jqplot.PieRenderer) {
setopts = true;
}
else if (options.series) {
for (var i=0; i < options.series.length; i++) {
if (options.series[i].renderer == $.jqplot.PieRenderer) {
setopts = true;
}
}
}
if (setopts) {
options.axesDefaults.renderer = $.jqplot.PieAxisRenderer;
options.legend.renderer = options.legend.renderer || $.jqplot.PieLegendRenderer;
options.legend.preDraw = true;
options.seriesDefaults.pointLabels = {show: false};
}
}
function postInit(target, data, options) {
for (var i=0; i
*
* By default, the last value in the data ponit array in the data series is used
* for the label. For most series renderers, extra data can be added to the
* data point arrays and the last value will be used as the label.
*
* For instance,
* this series:
*
* > [[1,4], [3,5], [7,2]]
*
* Would, by default, use the y values in the labels.
* Extra data can be added to the series like so:
*
* > [[1,4,'mid'], [3 5,'hi'], [7,2,'low']]
*
* And now the point labels would be 'mid', 'low', and 'hi'.
*
* Options to the point labels and a custom labels array can be passed into the
* "pointLabels" option on the series option like so:
*
* > series:[{pointLabels:{
* > labels:['mid', 'hi', 'low'],
* > location:'se',
* > ypadding: 12
* > }
* > }]
*
* A custom labels array in the options takes precendence over any labels
* in the series data. If you have a custom labels array in the options,
* but still want to use values from the series array as labels, set the
* "labelsFromSeries" option to true.
*
* By default, html entities (<, >, etc.) are escaped in point labels.
* If you want to include actual html markup in the labels,
* set the "escapeHTML" option to false.
*
*/
$.jqplot.PointLabels = function(options) {
// Group: Properties
//
// prop: show
// show the labels or not.
this.show = $.jqplot.config.enablePlugins;
// prop: location
// compass location where to position the label around the point.
// 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
this.location = 'n';
// prop: labelsFromSeries
// true to use labels within data point arrays.
this.labelsFromSeries = false;
// prop: seriesLabelIndex
// array index for location of labels within data point arrays.
// if null, will use the last element of the data point array.
this.seriesLabelIndex = null;
// prop: labels
// array of arrays of labels, one array for each series.
this.labels = [];
// actual labels that will get displayed.
// needed to preserve user specified labels in labels array.
this._labels = [];
// prop: stackedValue
// true to display value as stacked in a stacked plot.
// no effect if labels is specified.
this.stackedValue = false;
// prop: ypadding
// vertical padding in pixels between point and label
this.ypadding = 6;
// prop: xpadding
// horizontal padding in pixels between point and label
this.xpadding = 6;
// prop: escapeHTML
// true to escape html entities in the labels.
// If you want to include markup in the labels, set to false.
this.escapeHTML = true;
// prop: edgeTolerance
// Number of pixels that the label must be away from an axis
// boundary in order to be drawn. Negative values will allow overlap
// with the grid boundaries.
this.edgeTolerance = -5;
// prop: formatter
// A class of a formatter for the tick text. sprintf by default.
this.formatter = $.jqplot.DefaultTickFormatter;
// prop: formatString
// string passed to the formatter.
this.formatString = '';
// prop: hideZeros
// true to not show a label for a value which is 0.
this.hideZeros = false;
this._elems = [];
$.extend(true, this, options);
};
var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
// called with scope of a series
$.jqplot.PointLabels.init = function (target, data, seriesDefaults, opts, plot){
var options = $.extend(true, {}, seriesDefaults, opts);
options.pointLabels = options.pointLabels || {};
if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal' && !options.pointLabels.location) {
options.pointLabels.location = 'e';
}
// add a pointLabels attribute to the series plugins
this.plugins.pointLabels = new $.jqplot.PointLabels(options.pointLabels);
this.plugins.pointLabels.setLabels.call(this);
};
// called with scope of series
$.jqplot.PointLabels.prototype.setLabels = function() {
var p = this.plugins.pointLabels;
var labelIdx;
if (p.seriesLabelIndex != null) {
labelIdx = p.seriesLabelIndex;
}
else if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal') {
labelIdx = (this._plotData[0].length < 3) ? 0 : this._plotData[0].length -1;
}
else {
labelIdx = (this._plotData.length === 0) ? 0 : this._plotData[0].length -1;
}
p._labels = [];
if (p.labels.length === 0 || p.labelsFromSeries) {
if (p.stackedValue) {
if (this._plotData.length && this._plotData[0].length){
// var idx = p.seriesLabelIndex || this._plotData[0].length -1;
for (var i=0; i scr || elb + et > scb) {
elem.remove();
}
elem = null;
helem = null;
}
// finally, animate them if the series is animated
// if (this.renderer.animation && this.renderer.animation._supported && this.renderer.animation.show && plot._drawCount < 2) {
// var sel = '.jqplot-point-label.jqplot-series-'+this.index;
// $(sel).hide();
// $(sel).fadeIn(1000);
// }
}
};
$.jqplot.postSeriesInitHooks.push($.jqplot.PointLabels.init);
$.jqplot.postDrawSeriesHooks.push($.jqplot.PointLabels.draw);
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.pyramidAxisRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
$.jqplot.PyramidAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
};
$.jqplot.PyramidAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.PyramidAxisRenderer.prototype.constructor = $.jqplot.PyramidAxisRenderer;
// called with scope of axis
$.jqplot.PyramidAxisRenderer.prototype.init = function(options){
// Group: Properties
//
// prop: position
// Position of axis. Values are: top, bottom , left, center, right.
// By default, x and x2 axes are bottom, y axis is center.
this.position = null;
// prop: drawBaseline
// True to draw the axis baseline.
this.drawBaseline = true;
// prop: baselineWidth
// width of the baseline in pixels.
this.baselineWidth = null;
// prop: baselineColor
// CSS color spec for the baseline.
this.baselineColor = null;
this.tickSpacingFactor = 25;
this._type = 'pyramid';
this._splitAxis = false;
this._splitLength = null;
this.category = false;
this._autoFormatString = '';
this._overrideFormatString = false;
$.extend(true, this, options);
this.renderer.options = options;
this.resetDataBounds = this.renderer.resetDataBounds;
this.resetDataBounds();
};
$.jqplot.PyramidAxisRenderer.prototype.resetDataBounds = function() {
// Go through all the series attached to this axis and find
// the min/max bounds for this axis.
var db = this._dataBounds;
db.min = null;
db.max = null;
var temp;
for (var i=0; i db.max) || db.max === null) {
db.max = temp;
}
}
else {
temp = d[j][0];
if ((temp !== null && temp < db.min) || db.min === null) {
db.min = temp;
}
if ((temp !== null && temp > db.max) || db.max === null) {
db.max = temp;
}
}
}
}
};
// called with scope of axis
$.jqplot.PyramidAxisRenderer.prototype.draw = function(ctx, plot) {
if (this.show) {
// populate the axis label and value properties.
// createTicks is a method on the renderer, but
// call it within the scope of the axis.
this.renderer.createTicks.call(this, plot);
// fill a div with axes labels in the right direction.
// Need to pregenerate each axis to get its bounds and
// position it and the labels correctly on the plot.
var dim=0;
var temp;
// Added for theming.
if (this._elem) {
// Memory Leaks patch
//this._elem.empty();
this._elem.emptyForce();
this._elem = null;
}
this._elem = $(document.createElement('div'));
this._elem.addClass('jqplot-axis jqplot-'+this.name);
this._elem.css('position', 'absolute');
if (this.name == 'xaxis' || this.name == 'x2axis') {
this._elem.width(this._plotDimensions.width);
}
else {
this._elem.height(this._plotDimensions.height);
}
// create a _label object.
this.labelOptions.axis = this.name;
this._label = new this.labelRenderer(this.labelOptions);
if (this._label.show) {
var elem = this._label.draw(ctx, plot);
elem.appendTo(this._elem);
elem = null;
}
var t = this._ticks;
var tick;
for (var i=0; i maxVisibleTicks) {
// check for number of ticks we can skip
temp = this.numberTicks - 1;
for (i=2; i0; i--) {
t = new this.tickRenderer(this.tickOptions);
t.value = this._ticks[i-1].value + this.tickInterval/2.0;
t.label = '';
t.showLabel = false;
t.axis = this.name;
this._ticks[i].showGridline = false;
this._ticks[i].showMark = false;
this._ticks.splice(i, 0, t);
// temp.push(t);
}
// merge in the new ticks
// for (i=1, l=temp.length; i tumax) {
tumin = min - range*(this.padMin - 1);
tumax = max + range*(this.padMax - 1);
ret = $.jqplot.LinearTickGenerator(tumin, tumax, scalefact);
}
this.min = ret[0];
this.max = ret[1];
this.numberTicks = ret[2];
this._autoFormatString = ret[3];
this.tickInterval = ret[4];
}
else {
dim = this._plotDimensions.height;
// ticks will be on whole integers like 1, 2, 3, ... or 1, 4, 7, ...
min = db.min;
max = db.max;
s = this._series[0];
this._ticks = [];
range = max - min;
// if range is a prime, will get only 2 ticks, expand range in that case.
if (_primesHash[range]) {
range += 1;
max += 1;
}
this.max = max;
this.min = min;
maxVisibleTicks = Math.round(2.0 + dim/this.tickSpacingFactor);
if (range + 1 <= maxVisibleTicks) {
this.numberTicks = range + 1;
this.tickInterval = 1.0;
}
else {
// figure out a round number of ticks to skip in every interval
// range / ti + 1 = nt
// ti = range / (nt - 1)
for (var i=maxVisibleTicks; i>1; i--) {
if (range/(i - 1) === Math.round(range/(i - 1))) {
this.numberTicks = i;
this.tickInterval = range/(i - 1);
break;
}
}
}
}
if (this._overrideFormatString && this._autoFormatString != '') {
this.tickOptions = this.tickOptions || {};
this.tickOptions.formatString = this._autoFormatString;
}
var labelval;
for (i=0; i dim) {
dim = temp;
}
}
}
if (this.name === 'yMidAxis') {
for (i=0; i w) ? dim : w;
var temp = dim/2.0 - w/2.0;
this._elem.css({'width':dim+'px', top:'0px'});
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
this._label._elem.css({width: w, left: temp, top: 0});
}
}
else {
dim = dim + w;
this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
this._label._elem.css('width', w+'px');
}
}
}
};
$.jqplot.PyramidAxisRenderer.prototype.pack = function(pos, offsets) {
// Add defaults for repacking from resetTickValues function.
pos = pos || {};
offsets = offsets || this._offsets;
var ticks = this._ticks;
var max = this.max;
var min = this.min;
var offmax = offsets.max;
var offmin = offsets.min;
var lshow = (this._label == null) ? false : this._label.show;
for (var p in pos) {
this._elem.css(p, pos[p]);
}
this._offsets = offsets;
// pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
var pixellength = offmax - offmin;
var unitlength = max - min;
var sl = this._splitLength;
// point to unit and unit to point conversions references to Plot DOM element top left corner.
if (this._splitAxis) {
pixellength -= this._splitLength;
// don't know that this one is correct.
this.p2u = function(p){
return (p - offmin) * unitlength / pixellength + min;
};
this.u2p = function(u){
if (u <= 0) {
return (u - min) * pixellength / unitlength + offmin;
}
else {
return (u - min) * pixellength / unitlength + offmin + sl;
}
};
this.series_u2p = function(u){
if (u <= 0) {
return (u - min) * pixellength / unitlength;
}
else {
return (u - min) * pixellength / unitlength + sl;
}
};
// don't know that this one is correct.
this.series_p2u = function(p){
return p * unitlength / pixellength + min;
};
}
else {
this.p2u = function(p){
return (p - offmin) * unitlength / pixellength + min;
};
this.u2p = function(u){
return (u - min) * pixellength / unitlength + offmin;
};
if (this.name.charAt(0) === 'x'){
this.series_u2p = function(u){
return (u - min) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + min;
};
}
else {
this.series_u2p = function(u){
return (u - max) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + max;
};
}
}
if (this.show) {
if (this.name.charAt(0) === 'x') {
for (var i=0; i 0) {
shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
}
else {
shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
}
break;
case 'middle':
// if (t.angle > 0) {
// shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
// }
// else {
// shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
// }
shim = -t.getHeight()/2;
break;
default:
shim = -t.getHeight()/2;
break;
}
}
else {
shim = -t.getHeight()/2;
}
var val = this.u2p(t.value) + shim + 'px';
t._elem.css('top', val);
t.pack();
}
}
if (lshow) {
var h = this._label._elem.outerHeight(true);
if (this.name !== 'yMidAxis') {
this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
}
if (this.name == 'yaxis') {
this._label._elem.css('left', '0px');
}
else if (this.name !== 'yMidAxis') {
this._label._elem.css('right', '0px');
}
this._label.pack();
}
}
}
ticks = null;
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.pyramidGridRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
// Class: $.jqplot.CanvasGridRenderer
// The default jqPlot grid renderer, creating a grid on a canvas element.
// The renderer has no additional options beyond the class.
$.jqplot.PyramidGridRenderer = function(){
$.jqplot.CanvasGridRenderer.call(this);
};
$.jqplot.PyramidGridRenderer.prototype = new $.jqplot.CanvasGridRenderer();
$.jqplot.PyramidGridRenderer.prototype.constructor = $.jqplot.PyramidGridRenderer;
// called with context of Grid object
$.jqplot.CanvasGridRenderer.prototype.init = function(options) {
this._ctx;
this.plotBands = {
show: false,
color: 'rgb(230, 219, 179)',
axis: 'y',
start: null,
interval: 10
};
$.extend(true, this, options);
// set the shadow renderer options
var sopts = {lineJoin:'miter', lineCap:'round', fill:false, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.shadowWidth, closePath:false, strokeStyle:this.shadowColor};
this.renderer.shadowRenderer.init(sopts);
};
$.jqplot.PyramidGridRenderer.prototype.draw = function() {
this._ctx = this._elem.get(0).getContext("2d");
var ctx = this._ctx;
var axes = this._axes;
var xp = axes.xaxis.u2p;
var yp = axes.yMidAxis.u2p;
var xnudge = axes.xaxis.max/1000.0;
var xp0 = xp(0);
var xpn = xp(xnudge);
var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis','yMidAxis'];
// Add the grid onto the grid canvas. This is the bottom most layer.
ctx.save();
ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height);
ctx.fillStyle = this.backgroundColor || this.background;
ctx.fillRect(this._left, this._top, this._width, this._height);
if (this.plotBands.show) {
ctx.save();
var pb = this.plotBands;
ctx.fillStyle = pb.color;
var axis;
var x, y, w, h;
// find axis to work with
if (pb.axis.charAt(0) === 'x') {
if (axes.xaxis.show) {
axis = axes.xaxis;
}
}
else if (pb.axis.charAt(0) === 'y') {
if (axes.yaxis.show) {
axis = axes.yaxis;
}
else if (axes.y2axis.show) {
axis = axes.y2axis;
}
else if (axes.yMidAxis.show) {
axis = axes.yMidAxis;
}
}
if (axis !== undefined) {
// draw some rectangles
var start = pb.start;
if (start === null) {
start = axis.min;
}
for (var i = start; i < axis.max; i += 2 * pb.interval) {
if (axis.name.charAt(0) === 'y') {
x = this._left;
if ((i + pb.interval) < axis.max) {
y = axis.series_u2p(i + pb.interval) + this._top;
}
else {
y = axis.series_u2p(axis.max) + this._top;
}
w = this._right - this._left;
h = axis.series_u2p(start) - axis.series_u2p(start + pb.interval);
ctx.fillRect(x, y, w, h);
}
// else {
// y = 0;
// x = axis.series_u2p(i);
// h = this._height;
// w = axis.series_u2p(start + pb.interval) - axis.series_u2p(start);
// }
}
}
ctx.restore();
}
ctx.save();
ctx.lineJoin = 'miter';
ctx.lineCap = 'butt';
ctx.lineWidth = this.gridLineWidth;
ctx.strokeStyle = this.gridLineColor;
var b, e, s, m;
for (var i=5; i>0; i--) {
var name = ax[i-1];
var axis = axes[name];
var ticks = axis._ticks;
var numticks = ticks.length;
if (axis.show) {
if (axis.drawBaseline) {
var bopts = {};
if (axis.baselineWidth !== null) {
bopts.lineWidth = axis.baselineWidth;
}
if (axis.baselineColor !== null) {
bopts.strokeStyle = axis.baselineColor;
}
switch (name) {
case 'xaxis':
if (axes.yMidAxis.show) {
drawLine (this._left, this._bottom, xp0, this._bottom, bopts);
drawLine (xpn, this._bottom, this._right, this._bottom, bopts);
}
else {
drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
}
break;
case 'yaxis':
drawLine (this._left, this._bottom, this._left, this._top, bopts);
break;
case 'yMidAxis':
drawLine(xp0, this._bottom, xp0, this._top, bopts);
drawLine(xpn, this._bottom, xpn, this._top, bopts);
break;
case 'x2axis':
if (axes.yMidAxis.show) {
drawLine (this._left, this._top, xp0, this._top, bopts);
drawLine (xpn, this._top, this._right, this._top, bopts);
}
else {
drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
}
break;
case 'y2axis':
drawLine (this._right, this._bottom, this._right, this._top, bopts);
break;
}
}
for (var j=numticks; j>0; j--) {
var t = ticks[j-1];
if (t.show) {
var pos = Math.round(axis.u2p(t.value)) + 0.5;
switch (name) {
case 'xaxis':
// draw the grid line if we should
if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
drawLine(pos, this._top, pos, this._bottom);
}
// draw the mark
if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
s = t.markSize;
m = t.mark;
var pos = Math.round(axis.u2p(t.value)) + 0.5;
switch (m) {
case 'outside':
b = this._bottom;
e = this._bottom+s;
break;
case 'inside':
b = this._bottom-s;
e = this._bottom;
break;
case 'cross':
b = this._bottom-s;
e = this._bottom+s;
break;
default:
b = this._bottom;
e = this._bottom+s;
break;
}
// draw the shadow
if (this.shadow) {
this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
}
// draw the line
drawLine(pos, b, pos, e);
}
break;
case 'yaxis':
// draw the grid line
if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
drawLine(this._right, pos, this._left, pos);
}
// draw the mark
if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
s = t.markSize;
m = t.mark;
var pos = Math.round(axis.u2p(t.value)) + 0.5;
switch (m) {
case 'outside':
b = this._left-s;
e = this._left;
break;
case 'inside':
b = this._left;
e = this._left+s;
break;
case 'cross':
b = this._left-s;
e = this._left+s;
break;
default:
b = this._left-s;
e = this._left;
break;
}
// draw the shadow
if (this.shadow) {
this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
}
drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
}
break;
case 'yMidAxis':
// draw the grid line
if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
drawLine(this._left, pos, xp0, pos);
drawLine(xpn, pos, this._right, pos);
}
// draw the mark
if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
s = t.markSize;
m = t.mark;
var pos = Math.round(axis.u2p(t.value)) + 0.5;
b = xp0;
e = xp0 + s;
// draw the shadow
if (this.shadow) {
this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
}
drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
b = xpn - s;
e = xpn;
// draw the shadow
if (this.shadow) {
this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
}
drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
}
break;
case 'x2axis':
// draw the grid line
if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
drawLine(pos, this._bottom, pos, this._top);
}
// draw the mark
if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
s = t.markSize;
m = t.mark;
var pos = Math.round(axis.u2p(t.value)) + 0.5;
switch (m) {
case 'outside':
b = this._top-s;
e = this._top;
break;
case 'inside':
b = this._top;
e = this._top+s;
break;
case 'cross':
b = this._top-s;
e = this._top+s;
break;
default:
b = this._top-s;
e = this._top;
break;
}
// draw the shadow
if (this.shadow) {
this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
}
drawLine(pos, b, pos, e);
}
break;
case 'y2axis':
// draw the grid line
if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
drawLine(this._left, pos, this._right, pos);
}
// draw the mark
if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
s = t.markSize;
m = t.mark;
var pos = Math.round(axis.u2p(t.value)) + 0.5;
switch (m) {
case 'outside':
b = this._right;
e = this._right+s;
break;
case 'inside':
b = this._right-s;
e = this._right;
break;
case 'cross':
b = this._right-s;
e = this._right+s;
break;
default:
b = this._right;
e = this._right+s;
break;
}
// draw the shadow
if (this.shadow) {
this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
}
drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
}
break;
default:
break;
}
}
}
t = null;
}
axis = null;
ticks = null;
}
ctx.restore();
function drawLine(bx, by, ex, ey, opts) {
ctx.save();
opts = opts || {};
if (opts.lineWidth == null || opts.lineWidth != 0){
$.extend(true, ctx, opts);
ctx.beginPath();
ctx.moveTo(bx, by);
ctx.lineTo(ex, ey);
ctx.stroke();
}
ctx.restore();
}
if (this.shadow) {
if (axes.yMidAxis.show) {
var points = [[this._left, this._bottom], [xp0, this._bottom]];
this.renderer.shadowRenderer.draw(ctx, points);
var points = [[xpn, this._bottom], [this._right, this._bottom], [this._right, this._top]];
this.renderer.shadowRenderer.draw(ctx, points);
var points = [[xp0, this._bottom], [xp0, this._top]];
this.renderer.shadowRenderer.draw(ctx, points);
}
else {
var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]];
this.renderer.shadowRenderer.draw(ctx, points);
}
}
// Now draw border around grid. Use axis border definitions. start at
// upper left and go clockwise.
if (this.borderWidth != 0 && this.drawBorder) {
if (axes.yMidAxis.show) {
drawLine (this._left, this._top, xp0, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
drawLine (xpn, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
drawLine (this._right, this._bottom, xpn, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
drawLine (xp0, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
drawLine (xp0, this._bottom, xp0, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
drawLine (xpn, this._bottom, xpn, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
}
else {
drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
}
}
// ctx.lineWidth = this.borderWidth;
// ctx.strokeStyle = this.borderColor;
// ctx.strokeRect(this._left, this._top, this._width, this._height);
ctx.restore();
ctx = null;
axes = null;
};
})(jQuery);
================================================
FILE: stenogotchi/ui/web/static/js/plugins/jqplot.pyramidRenderer.js
================================================
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
// Need to ensure pyramid axis and grid renderers are loaded.
// You should load these with script tags in the html head, that is more efficient
// as the browser will cache the request.
// Note, have to block with synchronous request in order to execute bar renderer code.
if ($.jqplot.PyramidAxisRenderer === undefined) {
$.ajax({
url: $.jqplot.pluginLocation + 'jqplot.pyramidAxisRenderer.js',
dataType: "script",
async: false
});
}
if ($.jqplot.PyramidGridRenderer === undefined) {
$.ajax({
url: $.jqplot.pluginLocation + 'jqplot.pyramidGridRenderer.js',
dataType: "script",
async: false
});
}
$.jqplot.PyramidRenderer = function(){
$.jqplot.LineRenderer.call(this);
};
$.jqplot.PyramidRenderer.prototype = new $.jqplot.LineRenderer();
$.jqplot.PyramidRenderer.prototype.constructor = $.jqplot.PyramidRenderer;
// called with scope of a series
$.jqplot.PyramidRenderer.prototype.init = function(options, plot) {
options = options || {};
this._type = 'pyramid';
// Group: Properties
//
// prop: barPadding
this.barPadding = 10;
this.barWidth = null;
// prop: fill
// True to fill the bars.
this.fill = true;
// prop: highlightMouseOver
// True to highlight slice when moused over.
// This must be false to enable highlightMouseDown to highlight when clicking on a slice.
this.highlightMouseOver = true;
// prop: highlightMouseDown
// True to highlight when a mouse button is pressed over a slice.
// This will be disabled if highlightMouseOver is true.
this.highlightMouseDown = false;
// prop: highlightColors
// an array of colors to use when highlighting a slice.
this.highlightColors = [];
// prop highlightThreshold
// Expand the highlightable region in the x direction.
// E.g. a value of 3 will highlight a bar when the mouse is
// within 3 pixels of the bar in the x direction.
this.highlightThreshold = 2;
// prop: synchronizeHighlight
// Index of another series to highlight when this series is highlighted.
// null or false to not synchronize.
this.synchronizeHighlight = false;
// prop: offsetBars
// False will center bars on their y value.
// True will push bars up by 1/2 bar width to fill between their y values.
// If true, there needs to be 1 more tick than there are bars.
this.offsetBars = false;
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
if (options.highlightMouseDown && options.highlightMouseOver == null) {
options.highlightMouseOver = false;
}
this.side = 'right';
$.extend(true, this, options);
// if (this.fill === false) {
// this.shadow = false;
// }
if (this.side === 'left') {
this._highlightThreshold = [[-this.highlightThreshold, 0], [-this.highlightThreshold, 0], [0,0], [0,0]];
}
else {
this._highlightThreshold = [[0,0], [0,0], [this.highlightThreshold, 0], [this.highlightThreshold, 0]];
}
this.renderer.options = options;
// index of the currenty highlighted point, if any
this._highlightedPoint = null;
// Array of actual data colors used for each data point.
this._dataColors = [];
this._barPoints = [];
this.fillAxis = 'y';
this._primaryAxis = '_yaxis';
this._xnudge = 0;
// set the shape renderer options
var opts = {lineJoin:'miter', lineCap:'butt', fill:this.fill, fillRect:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.color, closePath:this.fill, lineWidth: this.lineWidth};
this.renderer.shapeRenderer.init(opts);
// set the shadow renderer options
var shadow_offset = options.shadowOffset;
// set the shadow renderer options
if (shadow_offset == null) {
// scale the shadowOffset to the width of the line.
if (this.lineWidth > 2.5) {
shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6);
// var shadow_offset = this.shadowOffset;
}
// for skinny lines, don't make such a big shadow.
else {
shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163;
}
}
var sopts = {lineJoin:'miter', lineCap:'butt', fill:this.fill, fillRect:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, closePath:this.fill, lineWidth: this.lineWidth};
this.renderer.shadowRenderer.init(sopts);
plot.postDrawHooks.addOnce(postPlotDraw);
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
// if this is the left side of pyramid, set y values to negative.
if (this.side === 'left') {
for (var i=0, l=this.data.length; i= 0) {
// xstart = this._xaxis.series_u2p(this._xnudge);
w = gridData[i][0] - xstart;
h = this.barWidth;
points = [xstart, base - bw2 - yadj, w, h];
}
else {
// xstart = this._xaxis.series_u2p(0);
w = xstart - gridData[i][0];
h = this.barWidth;
points = [gridData[i][0], base - bw2 - yadj, w, h];
}
this._barPoints.push([[points[0], points[1] + h], [points[0], points[1]], [points[0] + w, points[1]], [points[0] + w, points[1] + h]]);
if (shadow) {
this.renderer.shadowRenderer.draw(ctx, points);
}
var clr = opts.fillStyle || this.color;
this._dataColors.push(clr);
this.renderer.shapeRenderer.draw(ctx, points, opts);
}
else {
if (i === 0) {
points =[[xstart, ystart], [gridData[i][0], ystart], [gridData[i][0], gridData[i][1] - bw2 - yadj]];
}
else if (i < l-1) {
points = points.concat([[gridData[i-1][0], gridData[i-1][1] - bw2 - yadj], [gridData[i][0], gridData[i][1] + bw2 - yadj], [gridData[i][0], gridData[i][1] - bw2 - yadj]]);
}
// finally, draw the line
else {
points = points.concat([[gridData[i-1][0], gridData[i-1][1] - bw2 - yadj], [gridData[i][0], gridData[i][1] + bw2 - yadj], [gridData[i][0], yend], [xstart, yend]]);
if (shadow) {
this.renderer.shadowRenderer.draw(ctx, points);
}
var clr = opts.fillStyle || this.color;
this._dataColors.push(clr);
this.renderer.shapeRenderer.draw(ctx, points, opts);
}
}
}
}
if (this.highlightColors.length == 0) {
this.highlightColors = $.jqplot.computeHighlightColors(this._dataColors);
}
else if (typeof(this.highlightColors) == 'string') {
this.highlightColors = [];
for (var i=0; i= s.synchronizeHighlight && s.synchronizeHighlight !== sidx) {
s = plot.series[s.synchronizeHighlight];
opts = {fillStyle: s.highlightColors[pidx], fillRect: false};
s.renderer.shapeRenderer.draw(canvas._ctx, s._barPoints[pidx], opts);
}
canvas = null;
}
function unhighlight (plot) {
var canvas = plot.plugins.pyramidRenderer.highlightCanvas;
canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
for (var i=0; i
{% block head %}
{% block meta %}
{% endblock %}
{% block title %}
{% endblock %}
{% block styles %}
{% endblock %}
{% endblock %}
{% block body %}
{% if has_info %}
{{ loaded[name].__description__ }}
{% else %}
Description can't be loaded yet.
{% endif %}
{% endfor %}
{% endblock %}
================================================
FILE: stenogotchi/ui/web/templates/status.html
================================================
{{ title }}
{{ message }}
================================================
FILE: stenogotchi/utils.py
================================================
import logging
import glob
import os
import sys
import time
import json
import shutil
import toml
import sys
import re
from toml.encoder import TomlEncoder, _dump_str
from zipfile import ZipFile
from datetime import datetime
class DottedTomlEncoder(TomlEncoder):
"""
Dumps the toml into the dotted-key format
"""
def __init__(self, _dict=dict):
super(DottedTomlEncoder, self).__init__(_dict)
def dump_list(self, v):
retval = "["
# 1 line if its just 1 item; therefore no newline
if len(v) > 1:
retval += "\n"
for u in v:
retval += " " + str(self.dump_value(u)) + ",\n"
# 1 line if its just 1 item; remove newline
if len(v) <= 1:
retval = retval.rstrip("\n")
retval += "]"
return retval
def dump_sections(self, o, sup):
retstr = ""
pre = ""
if sup:
pre = sup + "."
for section, value in o.items():
section = str(section)
qsection = section
if not re.match(r'^[A-Za-z0-9_-]+$', section):
qsection = _dump_str(section)
if value is not None:
if isinstance(value, dict):
toadd, _ = self.dump_sections(value, pre + qsection)
retstr += toadd
# separte sections
if not retstr.endswith('\n\n'):
retstr += '\n'
else:
retstr += (pre + qsection + " = " +
str(self.dump_value(value)) + '\n')
return (retstr, self._dict())
def parse_version(version):
"""
Converts a version str to tuple, so that versions can be compared
"""
return tuple(version.split('.'))
def download_file(url, destination, chunk_size=128):
import requests
resp = requests.get(url)
resp.raise_for_status()
with open(destination, 'wb') as fd:
for chunk in resp.iter_content(chunk_size):
fd.write(chunk)
def unzip(file, destination, strip_dirs=0):
os.makedirs(destination, exist_ok=True)
with ZipFile(file, 'r') as zip:
if strip_dirs:
for info in zip.infolist():
new_filename = info.filename.split('/', maxsplit=strip_dirs)[strip_dirs]
if new_filename:
info.filename = new_filename
zip.extract(info, destination)
else:
zip.extractall(destination)
# https://stackoverflow.com/questions/823196/yaml-merge-in-python
def merge_config(user, default):
if isinstance(user, dict) and isinstance(default, dict):
for k, v in default.items():
if k not in user:
user[k] = v
else:
user[k] = merge_config(user[k], v)
return user
def load_configOLD(args):
import stenogotchi
config_file = os.path.join(os.path.dirname(stenogotchi.__file__), 'config.toml')
config = None
# load the config
with open(config_file) as fp:
config = toml.load(fp)
# check if display is supported
if config['ui']['display']['type'] in ('waveshare2in13v2', 'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
config['ui']['display']['type'] = 'waveshare_2'
else:
print("unsupported display type %s" % config['ui']['display']['type'])
sys.exit(1)
return config
def save_config(config, target):
with open(target, 'wt') as fp:
fp.write(toml.dumps(config, encoder=DottedTomlEncoder()))
return True
def load_config(args):
default_config_path = os.path.dirname(args.config)
if not os.path.exists(default_config_path):
os.makedirs(default_config_path)
import stenogotchi
ref_defaults_file = os.path.join(os.path.dirname(stenogotchi.__file__), 'defaults.toml')
ref_defaults_data = None
# check for a config.toml file on /boot/
boot_conf = '/boot/config.toml'
if os.path.exists(boot_conf):
# logging not configured here yet
print("installing %s to %s ...", boot_conf, args.user_config)
# https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link
shutil.move(boot_conf, args.user_config)
# check for an entire stenogotchi folder on /boot/
if os.path.isdir('/boot/stenogotchi'):
print("installing /boot/stenogotchi to /etc/stenogotchi ...")
shutil.rmtree('/etc/stenogotchi', ignore_errors=True)
shutil.move('/boot/stenogotchi', '/etc/')
# if not config is found, copy the defaults
if not os.path.exists(args.config):
print("copying %s to %s ..." % (ref_defaults_file, args.config))
shutil.copy(ref_defaults_file, args.config)
else:
# check if the user messed with the defaults
with open(ref_defaults_file) as fp:
ref_defaults_data = fp.read()
with open(args.config) as fp:
defaults_data = fp.read()
if ref_defaults_data != defaults_data:
print("!!! file in %s is different than release defaults, overwriting !!!" % args.config)
shutil.copy(ref_defaults_file, args.config)
# load the defaults
with open(args.config) as fp:
config = toml.load(fp)
# load the user config
try:
user_config = None
if os.path.exists(args.user_config):
with open(args.user_config) as toml_file:
user_config = toml.load(toml_file)
if user_config:
config = merge_config(user_config, config)
except Exception as ex:
logging.error("There was an error processing the configuration file:\n%s ",ex)
sys.exit(1)
# dropins
dropin = config['main']['confd']
if dropin and os.path.isdir(dropin):
dropin += '*.toml' if dropin.endswith('/') else '/*.toml'
for conf in glob.glob(dropin):
with open(conf) as toml_file:
additional_config = toml.load(toml_file)
config = merge_config(additional_config, config)
# the very first step is to normalize the display name so we don't need dozens of if/elif around
#if config['ui']['display']['type'] in ('inky', 'inkyphat'):
# config['ui']['display']['type'] = 'inky'
#elif config['ui']['display']['type'] in ('papirus', 'papi'):
# config['ui']['display']['type'] = 'papirus'
#elif config['ui']['display']['type'] in ('oledhat',):
# config['ui']['display']['type'] = 'oledhat'
#elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'):
# config['ui']['display']['type'] = 'waveshare_1'
if config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2', 'waveshare2in13v2'):
config['ui']['display']['type'] = 'waveshare_2'
#elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'):
# config['ui']['display']['type'] = 'waveshare27inch'
#elif config['ui']['display']['type'] in ('ws_29inch', 'ws29inch', 'waveshare_29inch', 'waveshare29inch'):
# config['ui']['display']['type'] = 'waveshare29inch'
#elif config['ui']['display']['type'] in ('lcdhat',):
# config['ui']['display']['type'] = 'lcdhat'
#elif config['ui']['display']['type'] in ('dfrobot_1', 'df1'):
# config['ui']['display']['type'] = 'dfrobot_1'
#elif config['ui']['display']['type'] in ('dfrobot_2', 'df2'):
# config['ui']['display']['type'] = 'dfrobot_2'
#elif config['ui']['display']['type'] in ('ws_154inch', 'ws154inch', 'waveshare_154inch', 'waveshare154inch'):
# config['ui']['display']['type'] = 'waveshare154inch'
#elif config['ui']['display']['type'] in ('waveshare144lcd', 'ws_144inch', 'ws144inch', 'waveshare_144inch', 'waveshare144inch'):
# config['ui']['display']['type'] = 'waveshare144lcd'
#elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare_213d', 'waveshare213d'):
# config['ui']['display']['type'] = 'waveshare213d'
#elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare_213bc', 'waveshare213bc'):
# config['ui']['display']['type'] = 'waveshare213bc'
#elif config['ui']['display']['type'] in ('spotpear24inch'):
# config['ui']['display']['type'] = 'spotpear24inch'
else:
print("unsupported display type %s" % config['ui']['display']['type'])
sys.exit(1)
return config
def secs_to_hhmmss(secs):
mins, secs = divmod(secs, 60)
hours, mins = divmod(mins, 60)
return '%02d:%02d:%02d' % (hours, mins, secs)
def led(on=True):
with open('/sys/class/leds/led0/brightness', 'w+t') as fp:
fp.write("%d" % (0 if on is True else 1))
def blink(times=1, delay=0.3):
for _ in range(0, times):
led(True)
time.sleep(delay)
led(False)
time.sleep(delay)
led(True)
class StatusFile(object):
def __init__(self, path, data_format='raw'):
self._path = path
self._updated = None
self._format = data_format
self.data = None
if os.path.exists(path):
self._updated = datetime.fromtimestamp(os.path.getmtime(path))
with open(path) as fp:
if data_format == 'json':
self.data = json.load(fp)
else:
self.data = fp.read()
def data_field_or(self, name, default=""):
if self.data is not None and name in self.data:
return self.data[name]
return default
def newer_then_minutes(self, minutes):
return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes
def newer_then_hours(self, hours):
return self._updated is not None and ((datetime.now() - self._updated).seconds / (60 * 60)) < hours
def newer_then_days(self, days):
return self._updated is not None and (datetime.now() - self._updated).days < days
def update(self, data=None):
from stenogotchi.fs import ensure_write
self._updated = datetime.now()
self.data = data
with ensure_write(self._path, 'w') as fp:
if data is None:
fp.write(str(self._updated))
elif self._format == 'json':
json.dump(self.data, fp)
else:
fp.write(data)
================================================
FILE: stenogotchi/voice.py
================================================
import random
import gettext
import os
class Voice:
def __init__(self, lang):
localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale')
translation = gettext.translation(
'voice', localedir,
languages=[lang],
fallback=True,
)
translation.install()
self._ = translation.gettext
def custom(self, s):
return s
def default(self):
return self._('ZzzzZZzzzzZzzz')
def on_starting(self):
return random.choice([
self._('Hi, I\'m Stenogotchi! Starting ...'),
self._('New day, new words, new chords!'),
self._('Caption the day!')])
def on_ai_ready(self):
return random.choice([
self._('AI ready.'),
self._('The neural network is ready.')])
def on_keys_generation(self):
return random.choice([
self._('Generating keys, do not turn off ...')])
def on_normal(self):
return random.choice([
self._('Wonder what we\'ll type next'),
self._('Sure is cosy in here'),
self._('We better not be writing any spam mail ...'),
self._('I bet I\'m a great swimmer.'),
self._('Used to know this Tama guy. Real piece of work.'),
self._('Did you know I\'m barely literate?'),
self._('Hi buddy'),
self._('You\'re looking sharp today!'),
self._('We\'re friends right? Best friends!'),
self._('I like hanging out with you'),
'',
'...'])
def on_reading_logs(self, lines_so_far=0):
if lines_so_far == 0:
return self._('Reading last session logs ...')
else:
return self._('Read {lines_so_far} log lines so far ...').format(lines_so_far=lines_so_far)
def on_bored(self):
return random.choice([
self._('I\'m bored ...'),
self._('Let\'s go for a walk!'),
self._('Let\'s write something!')])
def on_motivated(self, reward):
return self._('This is the best day of my life!')
def on_demotivated(self, reward):
return random.choice([
self._('This sucks ...'),
self._('Meh ...'),
self._('What\'s even the point ...'),
self._('Shitty day :/')])
def on_sad(self):
return random.choice([
self._('I\'m extremely bored ...'),
self._('I\'m very sad ...'),
self._('I\'m sad'),
'...'])
def on_angry(self):
# passive aggressive or not? :D
return random.choice([
'...',
self._('Leave me alone ...'),
self._('I\'m mad at you!')])
def on_excited(self):
return random.choice([
self._('I\'m living the life!'),
self._('I type before you think'),
self._('So many words!!!'),
self._('I\'m having so much fun!'),
self._('My crime is that of curiosity ...')])
def on_new_peer(self, peer):
if peer.first_encounter():
return random.choice([
self._('Hello {name}! Nice to meet you.').format(name=peer.name())])
else:
return random.choice([
self._('Yo {name}! Sup?').format(name=peer.name()),
self._('Hey {name} how are you doing?').format(name=peer.name()),
self._('Unit {name} is nearby!').format(name=peer.name())])
def on_lost_peer(self, peer):
return random.choice([
self._('Uhm ... goodbye {name}').format(name=peer.name()),
self._('{name} is gone ...').format(name=peer.name())])
def on_miss(self, who):
return random.choice([
self._('Whoops ... {name} is gone.').format(name=who),
self._('{name} missed!').format(name=who),
self._('Missed!')])
def on_grateful(self):
return random.choice([
self._('Good friends are a blessing!'),
self._('I love my friends!')])
def on_lonely(self):
return random.choice([
self._('Nobody wants to play with me ...'),
self._('I feel so alone ...'),
self._('Where\'s everybody?!')])
def on_napping(self, secs):
return random.choice([
#self._('Napping for {secs}s ...').format(secs=secs),
self._('Napping for a bit ...'),
self._('Zzzzz'),
self._('ZzzZzzz')])
def on_shutdown(self):
return random.choice([
self._('Good night.'),
self._('Zzz')])
def on_awakening(self):
return random.choice([
'!',
''])
def on_waiting(self, secs):
return random.choice([
self._('Waiting for {secs}s ...').format(secs=secs),
'...',
self._('Looking around ({secs}s)').format(secs=secs)])
def on_rebooting(self):
return self._("Oops, something went wrong ... Rebooting ...")
def on_last_session_data(self, last_session):
status = self._('Probably typed a lot last time buddy')
return status
def on_last_session_tweet(self, last_session):
pass
def on_bt_connected(self, bthost_name):
return random.choice([
self._(f'Grabbed hold of {bthost_name} with my bluetooth tentacle'),
self._(f'{bthost_name} is ready to receive'),
self._(f'Sinking my tooth into {bthost_name}'),
self._(f'Hi {bthost_name}, Let\'s team up!')])
def on_bt_disconnected(self):
return random.choice([
self._('Lost connection to bluetooth host'),
self._('Maybe it was a milk tooth and not bluetooth since it fell out ...'),
self._('Need a new bluetooth connection buddy')])
def on_wifi_connected(self, ssid, ip):
return random.choice([
self._(f'Found a town called {ssid}. I\'m gonna be known as {ip}'),
self._(f'Connected to: {ssid} with ip: {ip}'),
self._(f'Infiltrated {ssid} under the alias {ip}'),
self._(f'Hit me up at {ssid} under {ip}')])
def on_wifi_disconnected(self):
return random.choice([
self._('I... I just lost WiFi'),
self._('I\'m feeling disconnected'),
self._('Bruh, I was watching Netflix!')])
def on_plover_boot(self):
return random.choice([
self._('Just waiting for Plover to pick up the phone now ...'),
self._('Plover is loading dictionaries ...'),
self._('Waiting for Plover to wake up')])
def on_plover_ready(self):
return random.choice([
self._('My fingers are itching, let\'s type'),
self._('Throw some chords at me bro'),
self._('Plover is ready'),
self._('Plover is finally dressed and ready to go'),
self._('We are ready to go')])
def on_wpm_record(self, wpm_top):
if wpm_top < 100:
return random.choice([
self._('Keep up the good work!'),
self._('You are getting quick!')])
if wpm_top < 150:
return random.choice([
self._('Impressive!'),
self._('You have left most qwerty users in the dust!')])
elif wpm_top < 200:
return random.choice([
self._('Your fingers were a blur'),
self._('Wow, I barely managed to keep up'),
self._('Amazing!')])
elif wpm_top < 250:
return random.choice([
self._('I can\'t keep up with you anymore'),
self._('I can barely think that fast'),
self._('Warp drive engaged!')])
elif wpm_top < 300:
return random.choice([
self._('I\'m starting to think you\'re cheating!'),
self._('Keep that up and your fingers will fly off')])
else:
return self._('Mark Kislingbury ... Is that you?!')
def on_dict_lookup_done(self):
return random.choice([
self._('I can never remember that one either'),
self._('Welp ... I already forgot it again'),
self._('Oh ... I totally knew that one'),
self._('How often do you need that word anyway?'),
self._('Last time we\'ll need to look that one up. Right?')])
def hhmmss(self, count, fmt):
if count > 1:
# plural
if fmt == "h":
return self._("hours")
if fmt == "m":
return self._("minutes")
if fmt == "s":
return self._("seconds")
else:
# sing
if fmt == "h":
return self._("hour")
if fmt == "m":
return self._("minute")
if fmt == "s":
return self._("second")
return fmt
================================================
FILE: stenogotchi.py
================================================
#!/usr/bin/python3
import logging
import argparse
import time
import signal
import sys
import toml
import stenogotchi
from stenogotchi import utils
from stenogotchi.plugins import cmd as plugins_cmd
from stenogotchi import log
from stenogotchi import restart
from stenogotchi import fs
from stenogotchi.utils import DottedTomlEncoder
def do_clear(display):
logging.info("clearing the display ...")
display.clear()
sys.exit(0)
def do_manual_mode(agent):
logging.info("entering manual mode ...")
agent.mode = 'manual'
agent.last_session.parse(agent.view(), args.skip_session)
if not args.skip_session:
logging.info("the last session lasted %s" % (agent.last_session.duration_human))
while True:
display.on_manual_mode(agent.last_session)
time.sleep(5)
def do_auto_mode(agent):
logging.info("entering auto mode ...")
agent.mode = 'auto'
agent.start()
while True:
try:
# This is the main loop. But we don't have any main duty outside interacting with plover and acting as ui.
# Need to come up with some more dynamic way of triggering UI updates based on Plover/etc events
time.sleep(600)
agent._view.on_normal()
except Exception as e:
logging.exception("main loop exception (%s)", e)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser = plugins_cmd.add_parsers(parser)
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/stenogotchi/default.toml',
help='Main configuration file.')
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/stenogotchi/config.toml',
help='If this file exists, configuration will be merged and this will override default values.')
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False,
help="Skip last session parsing in manual mode.")
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
help="Clear the ePaper display and exit.")
parser.add_argument('--debug', dest="debug", action="store_true", default=False,
help="Enable debug logs.")
parser.add_argument('--version', dest="version", action="store_true", default=False,
help="Print the version.")
parser.add_argument('--print-config', dest="print_config", action="store_true", default=False,
help="Print the configuration.")
args = parser.parse_args()
if plugins_cmd.used_plugin_cmd(args):
config = utils.load_config(args)
log.setup_logging(args, config)
rc = plugins_cmd.handle_cmd(args, config)
sys.exit(rc)
if args.version:
print(stenogotchi.__version__)
sys.exit(0)
config = utils.load_config(args)
if args.print_config:
print(toml.dumps(config, encoder=DottedTomlEncoder()))
sys.exit(0)
from stenogotchi.agent import Agent
from stenogotchi.ui import fonts
from stenogotchi.ui.display import Display
from stenogotchi import plugins
stenogotchi.config = config
fs.setup_mounts(config)
log.setup_logging(args, config)
fonts.init(config)
stenogotchi.set_name(config['main']['name'])
plugins.load(config)
display = Display(config=config, state={'name': '%s>' % stenogotchi.name()})
if args.do_clear:
do_clear(display)
sys.exit(0)
agent = Agent(view=display, config=config)
def usr1_handler(*unused):
logging.info('Received USR1 singal. Restart process ...')
restart("MANU" if args.do_manual else "AUTO")
signal.signal(signal.SIGUSR1, usr1_handler)
if args.do_manual:
do_manual_mode(agent)
else:
do_auto_mode(agent)