diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c49849..0bdc995 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,108 +1,78 @@ project( plasma-publictransport ) # Find the required Libaries cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.6.1") set(KF5_MIN_VERSION "5.23.0") find_package(ECM 1.7.0 REQUIRED NO_MODULE) -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${ECM_MODULE_PATH} ${ECM_MODULE_DIR} ${CMAKE_MODULE_PATH}) -find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core DBus Xml Quick Test Sql Script) +set(CMAKE_MODULE_PATH + ${ECM_MODULE_DIR} + ${ECM_MODULE_PATH} + ${CMAKE_MODULE_PATH} + ${CMAKE_CURRENT_SOURCE_DIR}/cmake +) + +find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core DBus Xml Quick Test Sql) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS CoreAddons Config I18n NewStuff ThreadWeaver KDELibs4Support Plasma PlasmaQuick) -find_package(Marble QUIET CONFIG) -set_package_properties(Marble PROPERTIES DESCRIPTION "Marble is a virtual globe and world atlas" URL "http://marble.kde.org" TYPE OPTIONAL PURPOSE "Marble is used to show public transport stops in a map" ) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMOptionalAddSubdirectory) include(CheckCXXCompilerFlag) add_definitions( ${QT_DEFINITIONS} ) include_directories( ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ) # Can be used from the command line, eg. "cmake -DINSTALL_TIMETABLEMATE=ON .." # to configure with timetablemate (or use "cmake -i .." for interactive options) # Can also be set from a CMake GUI -option( INSTALL_ENGINE_OPENSTREETMAP "Note: Currently unused! Only useful if you have GPS hardware. It can then be used to get stops near your current position" OFF ) -option( INSTALL_APPLET "A plasma applet showing a departure/arrival board or journeys. Departures can be filtered and alarms can be set" ON ) -option( INSTALL_APPLET_FLIGHTS "A simple applet that shows flight departures. It uses a single service provider, that's also available for the other applets/runner" OFF ) -option( INSTALL_APPLET_GRAPHICALTIMETABLELINE "Shows a street with vehicles moving on it, representing the timetable" OFF ) -option( INSTALL_RUNNER "Can be used to quickly get departures/arrivals or journeys" ON ) -option( INSTALL_ICONS "Vehicle type icons and other icons used by PublicTransport components" ON ) +option( INSTALL_ICONS "Vehicle type icons and other icons used by PublicTransport components" OFF ) option( INSTALL_ALL "Install everything (except for unit tests). This overrides other options and is OFF by default" OFF ) option( BUILD_TESTS "Turn this option on to build unit tests for PublicTransport" OFF ) mark_as_advanced( BUILD_TESTS ) if ( INSTALL_ALL ) - set( INSTALL_ENGINE_OPENSTREETMAP TRUE ) - set( INSTALL_APPLET TRUE ) - set( INSTALL_APPLET_FLIGHTS TRUE ) - set( INSTALL_APPLET_GRAPHICALTIMETABLELINE TRUE ) - set( INSTALL_RUNNER TRUE ) set( INSTALL_ICONS TRUE ) endif ( INSTALL_ALL ) message( "******************************************************" ) message( "**** The following components are enabled: ****" ) message( "******************************************************" ) # The library and the engine are used by all applets, the runner and TimetableMate message( " - PublicTransport Helper Library" ) add_subdirectory( libpublictransporthelper ) # Include directory of publictransporthelper, to be used by other components using the library set( PUBLICTRANSPORTHELPER_INCLUDES ${CMAKE_SOURCE_DIR}/libpublictransporthelper/ ${CMAKE_BINARY_DIR}/libpublictransporthelper/ # for lib_config.h ) if ( MARBLE_FOUND ) list ( APPEND PUBLICTRANSPORTHELPER_INCLUDES ${MARBLE_INCLUDE_DIR} ) endif ( MARBLE_FOUND ) message( " - PublicTransport Data Engine" ) add_subdirectory( engine ) -#[[if ( INSTALL_ENGINE_OPENSTREETMAP ) - message( " - OpenStreetMap Data Engine" ) - add_subdirectory( engine-openstreetmap ) -endif ( INSTALL_ENGINE_OPENSTREETMAP ) - # Icons are are also needed everywhere, but may be turned off if ( INSTALL_ICONS ) message( " - PublicTransport Icons" ) add_subdirectory( icons ) endif ( INSTALL_ICONS ) -if ( INSTALL_APPLET ) - message( " - PublicTransport Applet" ) - add_subdirectory( applet ) -endif ( INSTALL_APPLET ) - -if ( INSTALL_APPLET_FLIGHTS ) - message( " - Flights Applet" ) - add_subdirectory( applet-flights ) -endif ( INSTALL_APPLET_FLIGHTS ) - -if ( INSTALL_APPLET_GRAPHICALTIMETABLELINE ) - message( " - GraphicalTimetableLine Applet" ) - add_subdirectory( applet-graphicaltimetableline) -endif ( INSTALL_APPLET_GRAPHICALTIMETABLELINE ) - -if ( INSTALL_RUNNER ) - message( " - PublicTransport Runner" ) - add_subdirectory( runner ) -endif ( INSTALL_RUNNER )]] message( "*****************************************************" ) # Show which external packages were found #macro_display_feature_log() diff --git a/applet-flights/AUTHORS b/applet-flights/AUTHORS deleted file mode 100644 index db15527..0000000 --- a/applet-flights/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Friedrich Pülz, fpuelz@gmx.de \ No newline at end of file diff --git a/applet-flights/CHANGELOG b/applet-flights/CHANGELOG deleted file mode 100644 index 88b7202..0000000 --- a/applet-flights/CHANGELOG +++ /dev/null @@ -1,5 +0,0 @@ - -Changelog of plasma-applet-flights - -0.10 - Beta 3 -- A new applet only for flights. It always uses international_flightstats as service provider and has a very simple interface. diff --git a/applet-flights/CMakeLists.txt b/applet-flights/CMakeLists.txt deleted file mode 100644 index 384f2ab..0000000 --- a/applet-flights/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Project Needs a name ofcourse -project(plasma-flights) - -# Find the required Libaries -find_package(KDE4 REQUIRED) -include(KDE4Defaults) -include( ../PublicTransportDefaults ) -add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) - -include_directories( - ${CMAKE_SOURCE_DIR} - ${CMAKE_BINARY_DIR} - ${KDE4_INCLUDES} - ${PUBLICTRANSPORTHELPER_INCLUDES} - ) - -# We add our source code here -set(flights_SRCS flightdeparturelist.cpp flights.cpp) - -# Now make sure all files get to the right place -kde4_add_plugin(plasma_applet_flights ${flights_SRCS}) - -add_dependencies( plasma_applet_flights publictransporthelper ) - -target_link_libraries(plasma_applet_flights - ${KDE4_PLASMA_LIBS} ${KDE4_KDEUI_LIBS} - publictransporthelper) - -install(TARGETS plasma_applet_flights - DESTINATION ${PLUGIN_INSTALL_DIR}) - -install(FILES plasma-applet-flights.desktop - DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/applet-flights/COPYING b/applet-flights/COPYING deleted file mode 100644 index 54c5c69..0000000 --- a/applet-flights/COPYING +++ /dev/null @@ -1,341 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - PublicTransport - This is a plasma applet that displays a departure/arrival board - for your stop. Plasma is part of KDE. - Copyright (C) 2010 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/applet-flights/Messages.sh b/applet-flights/Messages.sh deleted file mode 100644 index d83880a..0000000 --- a/applet-flights/Messages.sh +++ /dev/null @@ -1,2 +0,0 @@ -#! /usr/bin/env bash -$XGETTEXT *.cpp -o $podir/plasma_applet_flights.pot diff --git a/applet-flights/flightdeparturelist.cpp b/applet-flights/flightdeparturelist.cpp deleted file mode 100644 index 1f2ffd8..0000000 --- a/applet-flights/flightdeparturelist.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -// Header -#include "flightdeparturelist.h" - -// Plasma includes -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include -#include - -FlightDeparture::FlightDeparture( QGraphicsItem* parent ) : QGraphicsWidget( parent ) -{ - setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - setMinimumSize( 125, 45 ); - - m_icon = new Plasma::IconWidget( PublicTransport::Global::vehicleTypeToIcon(PublicTransport::Plane), - QString(), this ); - m_header = new Plasma::Label( this ); - m_info = new Plasma::Label( this ); - - m_icon->setMinimumSize( 32, 32 ); - m_icon->setMaximumSize( 32, 32 ); - - QFont headerFont = m_header->font(); - headerFont.setBold( true ); - m_header->setFont( headerFont ); - m_header->setText( headerText() ); - m_header->setToolTip( headerText() ); - m_header->setMaximumHeight( m_header->boundingRect().height() * 0.9 ); - - m_info->setText( infoText() ); - m_info->setToolTip( infoText() ); - m_info->setWordWrap( true ); - m_info->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - m_info->setMaximumHeight( boundingRect().height() - m_header->maximumHeight() - 5 ); - - QGraphicsGridLayout *mainLayout = new QGraphicsGridLayout( this ); - mainLayout->addItem( m_icon, 0, 0, 2, 1, Qt::AlignCenter ); - mainLayout->addItem( m_header, 0, 1, 1, 1, Qt::AlignBottom ); - mainLayout->addItem( m_info, 1, 1, 1, 1, Qt::AlignTop ); - mainLayout->setHorizontalSpacing( 10 ); - mainLayout->setVerticalSpacing( 0 ); - mainLayout->setContentsMargins( 0, 0, 0, 0 ); -} - -QString FlightDeparture::headerText() const -{ - return i18n("Flight %1 to %2", m_flightNumber, m_target); -} - -QString FlightDeparture::infoText() const -{ - return i18n("Departing at %1, %2, %3", - KGlobal::locale()->formatTime(m_departure.time()), m_status, m_airline); -} - -void FlightDeparture::setTarget( const QString& target ) -{ - // Set target and remove the shorthand of the airport - m_target = target; - m_target.replace( QRegExp("^[A-Z]+\\s"), QString() ); - m_header->setText( headerText() ); - m_header->setToolTip( headerText() ); -} - -void FlightDeparture::setDeparture( const QDateTime& departure ) -{ - m_departure = departure; - m_info->setText( infoText() ); - m_info->setToolTip( infoText() ); -} - -void FlightDeparture::setAirline( const QString& airline ) -{ - m_airline = airline; - m_info->setText( infoText() ); - m_info->setToolTip( infoText() ); -} - -void FlightDeparture::setFlightNumber( const QString& flightNumber ) -{ - m_flightNumber = flightNumber; - m_header->setText( headerText() ); - m_header->setToolTip( headerText() ); -} - -void FlightDeparture::setStatus( const QString& status ) -{ - m_status = status; - m_info->setText( infoText() ); - m_info->setToolTip( infoText() ); -} - -void FlightDeparture::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget ) -{ - painter->setRenderHints( QPainter::SmoothPixmapTransform | QPainter::Antialiasing ); - - QGraphicsWidget::paint( painter, option, widget ); - - QString headerText = i18n("Flight %1 to %2", m_flightNumber, m_target); - QString text = i18n("Departing at %1, %2, %3", - KGlobal::locale()->formatTime(m_departure.time()), m_status, m_airline); - - int iconSize = qMin( qMax(qCeil(option->rect.height() / 2), 16), 64 ); - QRectF iconRect( 0, (option->rect.height() - iconSize) / 2, iconSize, iconSize ); - QRectF headerRect( 5 + iconSize, 0, - option->rect.width() - 5 - iconSize, option->rect.height() / 3 ); - QRectF textRect( 5 + iconSize, headerRect.height(), - headerRect.width(), 2 * option->rect.height() / 3 ); - -// QColor textColor = option->palette.text().color(); -// bool drawHalos = qGray(textColor.rgb()) < 192; -// -// QList< QRect > fadeRects, haloRects; -// int fadeWidth = 30; -// QPixmap pixmap( textRect.size() ); -// pixmap.fill( Qt::transparent ); -// QPainter p( &pixmap ); -// p.setPen( painter->pen() ); - - Plasma::FrameSvg svg(this); - QRectF backgroundRect = option->rect.adjusted( -12, -12, 12, 12 ); - svg.setImagePath("widgets/background"); - svg.resizeFrame( backgroundRect.size() ); - svg.paintFrame( painter, backgroundRect.topLeft() ); -// painter->drawRect( option->rect ); - return; - - QFont normalFont = font(); - QFont headerFont = normalFont; - headerFont.setBold( true ); - - QPixmap icon = PublicTransport::Global::vehicleTypeToIcon( PublicTransport::Plane ) - .pixmap( iconRect.size().toSize() ); - painter->drawPixmap( iconRect.topLeft(), icon ); - - QRadialGradient headerGradient( 50.0, 50.0, qMax(headerRect.height(), qreal(100.0)) ); - QColor color = Plasma::Theme::defaultTheme()->color(Plasma::Theme::HighlightColor); - color.setAlphaF( 0.4 ); - headerGradient.setColorAt( 0, color ); - headerGradient.setColorAt( 1, Qt::transparent ); - painter->fillRect( headerRect, QBrush(headerGradient) ); - - QLinearGradient gradient( headerRect.bottomLeft(), headerRect.bottomRight() ); - gradient.setColorAt( 0, Qt::black ); - gradient.setColorAt( 1, Qt::transparent ); - painter->fillRect( headerRect.left(), headerRect.bottom(), headerRect.width(), 1, - QBrush(gradient) ); - - painter->setFont( headerFont ); - QFontMetrics fmHeader( headerFont ); - headerText = fmHeader.elidedText( headerText, Qt::ElideRight, headerRect.width() ); - painter->drawText( headerRect, headerText, QTextOption(Qt::AlignBottom) ); - - painter->setFont( normalFont ); - QFontMetrics fmText( normalFont ); - text = fmHeader.elidedText( text, Qt::ElideRight, textRect.width() * 2.1 ); - painter->drawText( textRect, text, QTextOption(Qt::AlignTop) ); -} - -FlightDepartureList::FlightDepartureList( QGraphicsItem* parent, Qt::WindowFlags wFlags ) - : QGraphicsWidget( parent, wFlags ), m_contentWidget(0) -{ - setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - - Plasma::ScrollWidget *scrollWidget = new Plasma::ScrollWidget( this ); - m_contentWidget = new QGraphicsWidget( scrollWidget ); - m_contentWidget->setContentsMargins( 10, 10, 10, 10 ); - scrollWidget->setWidget( m_contentWidget ); - - QGraphicsLinearLayout *mainLayout = new QGraphicsLinearLayout( this ); - mainLayout->addItem( scrollWidget ); - - QGraphicsLinearLayout *contentLayout = new QGraphicsLinearLayout( Qt::Vertical, m_contentWidget ); - contentLayout->setSpacing( 10 ); -} - -void FlightDepartureList::updateLayout() -{ - QGraphicsLinearLayout *contentLayout = new QGraphicsLinearLayout( Qt::Vertical, m_contentWidget ); - contentLayout->setSpacing( 10 ); - - int maxDepartures = qCeil( boundingRect().height() / 100 ); // Min 100 pixel per departure - for ( int i = 0; i < departures().count(); ++i ) { - FlightDeparture *departure = departures()[i]; - departure->setVisible( i < maxDepartures ); - if ( departure->isVisible() ) { - contentLayout->addItem( departure ); - } - } -} - -void FlightDepartureList::setTimetableData( const Plasma::DataEngine::Data& data ) -{ - QGraphicsLinearLayout *contentLayout = new QGraphicsLinearLayout( Qt::Vertical, m_contentWidget ); - contentLayout->setSpacing( 10 ); - - qDeleteAll( m_departures ); - m_departures.clear(); - - QUrl url; - QDateTime updated; - url = data["requestUrl"].toUrl(); - updated = data["updated"].toDateTime(); - QVariantList departuresData = data.contains("departures") ? data["departures"].toList() - : data["arrivals"].toList(); - kDebug() << " - " << departuresData.count() << "departures to be processed"; - int i = 0; - foreach ( const QVariant &departureVariant, departuresData ) { - if ( i >= 10 ) { - break; - } - ++i; - - const QVariantHash departureData = departureVariant.toHash(); - FlightDeparture *flightDeparture = new FlightDeparture( this ); - flightDeparture->setDeparture( departureData["DepartureDateTime"].toDateTime() ); - flightDeparture->setAirline( departureData["Operator"].toString() ); - flightDeparture->setTarget( departureData["Target"].toString() ); - flightDeparture->setFlightNumber( departureData["TransportLine"].toString() ); - flightDeparture->setStatus( departureData["Status"].toString().replace(QRegExp(" |\n"), QString()) ); - m_departures << flightDeparture; - - contentLayout->addItem( flightDeparture ); -// QList< QTime > routeTimes; -// if ( dataMap.contains( "routeTimes" ) ) { -// QVariantList times = dataMap[ "routeTimes" ].toList(); -// foreach( const QVariant &time, times ) { -// routeTimes << time.toTime(); -// } -// } -// DepartureInfo departureInfo( dataMap["operator"].toString(), dataMap["line"].toString(), -// dataMap["target"].toString(), dataMap["departure"].toDateTime(), -// static_cast( dataMap["vehicleType"].toInt() ), -// dataMap["nightline"].toBool(), dataMap["expressline"].toBool(), -// dataMap["platform"].toString(), dataMap["delay"].toInt(), -// dataMap["delayReason"].toString(), dataMap["journeyNews"].toString(), -// dataMap["routeStops"].toStringList(), routeTimes, -// dataMap["routeExactStops"].toInt() ); - } - - update(); -} diff --git a/applet-flights/flightdeparturelist.h b/applet-flights/flightdeparturelist.h deleted file mode 100644 index 1883e41..0000000 --- a/applet-flights/flightdeparturelist.h +++ /dev/null @@ -1,81 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#ifndef FLIGHTDEPARTURELIST_H -#define FLIGHTDEPARTURELIST_H - -#include -#include - -namespace Plasma -{ - class Label; - class IconWidget; -} - -class FlightDeparture : public QGraphicsWidget { -public: - FlightDeparture( QGraphicsItem* parent = 0 ); - - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - - QDateTime departure() const { return m_departure; }; - QString target() const { return m_target; }; - QString flightNumber() const { return m_flightNumber; }; - QString status() const { return m_status; }; - QString airline() const { return m_airline; }; - - void setDeparture( const QDateTime& departure ); - void setTarget( const QString &target ); - void setFlightNumber( const QString &flightNumber ); - void setStatus( const QString &status ); - void setAirline( const QString &airline ); - -private: - QString headerText() const; - QString infoText() const; - - QDateTime m_departure; - QString m_target; - QString m_flightNumber; - QString m_status; - QString m_airline; - - Plasma::IconWidget *m_icon; - Plasma::Label *m_header; - Plasma::Label *m_info; -}; - -class FlightDepartureList : public QGraphicsWidget -{ -public: - explicit FlightDepartureList( QGraphicsItem* parent = 0, Qt::WindowFlags wFlags = 0 ); - - void setTimetableData( const Plasma::DataEngine::Data &timetableData ); - void updateLayout(); - - QList departures() const { return m_departures; }; - -private: - QList m_departures; - QGraphicsWidget *m_contentWidget; -}; - -#endif // FLIGHTDEPARTURELIST_H diff --git a/applet-flights/flights.cpp b/applet-flights/flights.cpp deleted file mode 100644 index 1e5325e..0000000 --- a/applet-flights/flights.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Own includes -#include "flights.h" -#include "flightdeparturelist.h" - -// libpublictransporthelper includes -#include -#include -#include -#include - -// KDE includes -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include - -using namespace PublicTransport; - -Flights::Flights(QObject *parent, const QVariantList &args) - : Plasma::PopupApplet(parent, args), m_stopLineEdit(0), m_flightDepartureList(0), m_header(0), - m_container(0) -{ - setBackgroundHints( DefaultBackground ); -// m_svg.setImagePath("widgets/background"); - setHasConfigurationInterface( true ); - setContentsMargins( 10, 10, 10, 10 ); - setAspectRatioMode( Plasma::IgnoreAspectRatio ); - resize( 300, 200 ); - setPopupIcon( PublicTransport::Global::vehicleTypeToIcon(Plane) ); -} - -Flights::~Flights() -{ - if ( hasFailedToLaunch() ) { - // Do some cleanup here - } else { - // Save settings - } -} - -void Flights::init() -{ - m_airport = config().readEntry( QLatin1String("airport"), QString() ); - setConfigurationRequired( m_airport.isEmpty(), i18n("Please select an airport") ); - if ( !m_airport.isEmpty() ) { - dataEngine("publictransport")->connectSource( - QString("Departures international_flightstats|stop=%1|timeoffset=0").arg(m_airport), this, - 60000, Plasma::AlignToMinute ); - } -} - -void Flights::resizeEvent( QGraphicsSceneResizeEvent* event ) -{ - Plasma::Applet::resizeEvent( event ); - -// if ( m_flightDepartureList ) { -// m_flightDepartureList->updateLayout(); -// } -} - -QGraphicsWidget* Flights::graphicsWidget() -{ - if ( !m_container ) { - m_container = new QGraphicsWidget( this ); - - m_header = new Plasma::Label( m_container ); - m_header->setText( m_airport ); - QFont font = Plasma::Theme::defaultTheme()->font( Plasma::Theme::DefaultFont ); - font.setPointSize( 14 ); - m_header->setFont( font ); - m_header->setAlignment( Qt::AlignCenter ); - - m_flightDepartureList = new FlightDepartureList( m_container ); - m_flightDepartureList->setPreferredSize( 300, 200 ); - - QGraphicsLinearLayout *mainLayout = new QGraphicsLinearLayout( this ); - mainLayout->addItem( m_container ); - mainLayout->setContentsMargins( 0, 0, 0, 0 ); - - QGraphicsLinearLayout *containerLayout = new QGraphicsLinearLayout( Qt::Vertical, m_container ); - containerLayout->addItem( m_header ); - containerLayout->addItem( m_flightDepartureList ); - containerLayout->setContentsMargins( 0, 4, 0, 0 ); - containerLayout->setSpacing( 0 ); - - registerAsDragHandle( m_header ); - registerAsDragHandle( m_flightDepartureList ); - } - - return m_container; -} - -void Flights::createConfigurationInterface( KConfigDialog* parent ) -{ -// StopSettings stopSettings; -// stopSettings.set( LocationSetting, "international" ); -// stopSettings.set( ServiceProviderSetting, "international_flightstats" ); - - QWidget *airportConfig = new QWidget( parent ); - QFormLayout *airportLayout = new QFormLayout( airportConfig ); - m_stopLineEdit = new StopLineEdit( airportConfig, QLatin1String("international_flightstats") ); - m_stopLineEdit->setText( m_airport ); - airportLayout->addRow( i18n("&Airport:"), m_stopLineEdit ); - - parent->addPage( airportConfig, i18n("Airport") ); - - connect( parent, SIGNAL(applyClicked()), this, SLOT(configAccepted()) ); - connect( parent, SIGNAL(okClicked()), this, SLOT(configAccepted()) ); - - m_stopLineEdit->setFocus(); -} - -void Flights::configAccepted() -{ - m_airport = m_stopLineEdit->text(); - - setConfigurationRequired( m_airport.isEmpty(), i18n("Please select an airport") ); - if ( !m_airport.isEmpty() ) { - dataEngine("publictransport")->connectSource( - QString("Departures international_flightstats|stop=%1").arg(m_airport), this, - 60000, Plasma::AlignToMinute ); - } - - config().writeEntry( QLatin1String("airport"), m_airport ); - emit configNeedsSaving(); - configChanged(); - m_stopLineEdit = 0; - - m_header->setText( m_airport ); -} - -void Flights::dataUpdated( const QString& sourceName, const Plasma::DataEngine::Data& data ) -{ - Q_UNUSED( sourceName ); - m_flightDepartureList->setTimetableData( data ); -} - -#include "flights.moc" diff --git a/applet-flights/flights.h b/applet-flights/flights.h deleted file mode 100644 index 6c95684..0000000 --- a/applet-flights/flights.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Here we avoid loading the header multiple times -#ifndef FLIGHTS_HEADER -#define FLIGHTS_HEADER - -// KDE includes -#include -#include - -namespace Plasma { - class Label; -} -namespace PublicTransport { - class StopLineEdit; -} -class FlightDepartureList; - -// Define our plasma Applet -class Flights : public Plasma::PopupApplet -{ - Q_OBJECT -public: - // Basic Create/Destroy - Flights(QObject *parent, const QVariantList &args); - ~Flights(); - - void init(); - - virtual void createConfigurationInterface( KConfigDialog* parent ); - virtual QGraphicsWidget* graphicsWidget(); - virtual void resizeEvent( QGraphicsSceneResizeEvent* event ); - -public slots: - void configAccepted(); - - /** @brief The data from the data engine was updated. */ - void dataUpdated( const QString& sourceName, const Plasma::DataEngine::Data &data ); - -private: - PublicTransport::StopLineEdit *m_stopLineEdit; - FlightDepartureList *m_flightDepartureList; - QString m_airport; - Plasma::Label *m_header; - QGraphicsWidget *m_container; -}; - -// This is the command that links your applet to the .desktop file -K_EXPORT_PLASMA_APPLET(flights, Flights) -#endif diff --git a/applet-flights/plasma-applet-flights.desktop b/applet-flights/plasma-applet-flights.desktop deleted file mode 100644 index 169f522..0000000 --- a/applet-flights/plasma-applet-flights.desktop +++ /dev/null @@ -1,57 +0,0 @@ -[Desktop Entry] -Name=Flights -Name[bs]=Letovi -Name[ca]=Flights -Name[ca@valencia]=Flights -Name[cs]=Lety -Name[de]=Flüge -Name[es]=Vuelos -Name[et]=Lennud -Name[fr]=Vols -Name[ga]=Eitiltí -Name[gl]=Voos -Name[km]=ការ​ហោះហើរ -Name[ko]=항공편 -Name[nds]=Flaag -Name[nl]=Vluchten -Name[pl]=Loty -Name[pt]=Voos -Name[pt_BR]=Voos -Name[ro]=Zboruri -Name[sk]=Lety -Name[sv]=Flyg -Name[uk]=Рейси -Name[x-test]=xxFlightsxx -Comment=Shows flight departures/arrivals for a given airport. -Comment[bs]=Prikazuje odlaske/dolaske aviona za određeni aerodrom. -Comment[ca]=Mostra les sortides/arribades dels vols per a un aeroport indicat. -Comment[ca@valencia]=Mostra les eixides/arribades dels vols per a un aeroport indicat. -Comment[de]=Zeigt Abflüge/Ankünfte von Flügen auf einem angegebenen Flughafen an. -Comment[es]=Muestra las salidas/llegadas de vuelos para un aeropuerto dado. -Comment[et]=Määratud lennujaama saabuvate/väljuvate lendude näitamine. -Comment[fr]=Affiche les départs / les arrivées de vols depuis un aéroport donné. -Comment[gl]=Mostra as saídas dos voos e as chegadas dos aeroportos que se indiquen. -Comment[km]=បង្ហាញ​ការ​ចេញ​ដំណើរ/ការ​​មកដល់​នៃ​ជើង​ហោះហើរ​សម្រាប់​អាកាសយានដ្ឋាន​ដែល​បាន​ផ្ដល់ -Comment[ko]=지정한 공항의 항공편 출도착을 표시합니다. -Comment[nl]=Vertrektijden/aankomsten van vluchten tonen voor een gegeven luchthaven. -Comment[pl]=Pokazuje przyloty/odloty dla danego lotniska. -Comment[pt]=Mostra partidas/chegadas de voos para um dado aeroporto. -Comment[pt_BR]=Mostra partidas/chegadas de voos de um determinado aeroporto. -Comment[sk]=Zobrazí odlety/prílety pre dané letisko. -Comment[sv]=Visa flygavgångar och ankomster för en given flygplats. -Comment[uk]=Показує графік вильотів/прильотів для вказаного аеропорту. -Comment[x-test]=xxShows flight departures/arrivals for a given airport.xx -Type=Service -Icon=vehicle_type_plane -X-KDE-ServiceTypes=Plasma/Applet -X-KDE-Library=plasma_applet_flights -X-KDE-PluginInfo-Author=Friedrich Pülz -X-KDE-PluginInfo-Email=fpuelz@gmx.de -X-KDE-PluginInfo-Name=flights -X-KDE-PluginInfo-Version=0.1.1 -X-KDE-PluginInfo-Website=http://plasma.kde.org/ -X-KDE-PluginInfo-Category=Online Services -X-KDE-PluginInfo-Depends= -X-KDE-PluginInfo-License=LGPL -X-KDE-PluginInfo-EnabledByDefault=true - diff --git a/applet-graphicaltimetableline/AUTHORS b/applet-graphicaltimetableline/AUTHORS deleted file mode 100644 index db15527..0000000 --- a/applet-graphicaltimetableline/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Friedrich Pülz, fpuelz@gmx.de \ No newline at end of file diff --git a/applet-graphicaltimetableline/CHANGELOG b/applet-graphicaltimetableline/CHANGELOG deleted file mode 100644 index 2dfe2f1..0000000 --- a/applet-graphicaltimetableline/CHANGELOG +++ /dev/null @@ -1,12 +0,0 @@ - -Changelog of plasma-applet-graphicaltimetableline - -0.10 - Beta 4 -- The departure column in the mini timetable is now automatically sized to it's widest entry (just like the transport line column) -- Fixed train SVGs to correctly cut the train icons in the circles. - -0.10 - Beta 3 -- A new applet with a graphical presentation of departures, showing a street with the positions of the next departing vehicles. -- It can also show a little timetable, at the position of the stop. -- Vehicles are painted using an SVG, transport line strings are drawn if possible for local public transport. -- Completely animated (moving vehicles, departing animation). diff --git a/applet-graphicaltimetableline/CMakeLists.txt b/applet-graphicaltimetableline/CMakeLists.txt deleted file mode 100644 index 77b05fe..0000000 --- a/applet-graphicaltimetableline/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -project( plasma-graphicaltimetableline ) - -# Find the required Libaries -find_package( KDE4 REQUIRED ) -include( KDE4Defaults ) -include( ../PublicTransportDefaults ) -add_definitions( ${QT_DEFINITIONS} ${KDE4_DEFINITIONS} ) -include_directories( - ${CMAKE_SOURCE_DIR} - ${CMAKE_BINARY_DIR} - ${KDE4_INCLUDES} - ${PUBLICTRANSPORTHELPER_INCLUDES} - ) - -# We add our source code here -set( graphicaltimetableline_SRCS graphicaltimetableline.cpp ) - -# Now make sure all files get to the right place -kde4_add_plugin( plasma_applet_graphicaltimetableline - ${graphicaltimetableline_SRCS} ) - -add_dependencies( plasma_applet_graphicaltimetableline publictransporthelper ) - -target_link_libraries( plasma_applet_graphicaltimetableline - ${KDE4_PLASMA_LIBS} ${KDE4_KDEUI_LIBS} - publictransporthelper ) - -install( TARGETS plasma_applet_graphicaltimetableline - DESTINATION ${PLUGIN_INSTALL_DIR} ) - -install( FILES plasma-applet-graphicaltimetableline.desktop - DESTINATION ${SERVICES_INSTALL_DIR} ) - -install( FILES vehicles.svg - DESTINATION ${DATA_INSTALL_DIR}/plasma_applet_graphicaltimetableline ) diff --git a/applet-graphicaltimetableline/COPYING b/applet-graphicaltimetableline/COPYING deleted file mode 100644 index 54c5c69..0000000 --- a/applet-graphicaltimetableline/COPYING +++ /dev/null @@ -1,341 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - PublicTransport - This is a plasma applet that displays a departure/arrival board - for your stop. Plasma is part of KDE. - Copyright (C) 2010 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/applet-graphicaltimetableline/Messages.sh b/applet-graphicaltimetableline/Messages.sh deleted file mode 100644 index 3ea5258..0000000 --- a/applet-graphicaltimetableline/Messages.sh +++ /dev/null @@ -1,2 +0,0 @@ -#! /usr/bin/env bash -$XGETTEXT *.cpp -o $podir/plasma_applet_graphicaltimetableline.pot diff --git a/applet-graphicaltimetableline/graphicaltimetableline.cpp b/applet-graphicaltimetableline/graphicaltimetableline.cpp deleted file mode 100644 index 3fbf7e5..0000000 --- a/applet-graphicaltimetableline/graphicaltimetableline.cpp +++ /dev/null @@ -1,1063 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "graphicaltimetableline.h" - -// libpublictransporthelper includes -#include -#include -#include -#include - -// KDE includes -#include -#include -#include -#include -#include "kdeversion.h" - -// Plasma includes -#include -#include -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -GraphicalTimetableLine::GraphicalTimetableLine(QObject *parent, const QVariantList &args) - : Plasma::Applet(parent, args), m_stopWidget(0), m_vehicleTypeModel(0), - m_showTimetableCheckbox(0), m_drawTransportLineCheckbox(0), - m_zoomInButton(0), m_zoomOutButton(0), m_title(0), m_departureView(0), m_svg(this) -{ - m_animate = true; - - // this will get us the standard applet background, for free! - setBackgroundHints(DefaultBackground); - m_svg.setImagePath( KGlobal::dirs()->findResource("data", "plasma_applet_graphicaltimetableline/vehicles.svg") ); - m_svg.setContainsMultipleImages( true ); - - setAspectRatioMode(Plasma::IgnoreAspectRatio); - setHasConfigurationInterface(true); - resize( 400, 250 ); - setMinimumHeight( 125 ); - - QRectF rect = contentsRect(); - m_timelineStart = QPointF( rect.left() + 0.1 * rect.width(), rect.top() + 0.75 * rect.height() ); - m_timelineEnd = QPointF( rect.right() - 0.05 * rect.width(), rect.top() + 0.18 * rect.height() ); -} - - -GraphicalTimetableLine::~GraphicalTimetableLine() -{ - if ( hasFailedToLaunch() ) { - // Do some cleanup here - } else { - // Save settings - } -} - -void GraphicalTimetableLine::init() -{ - if ( !m_svg.hasElement("background") ) { - setFailedToLaunch( true, i18n("No 'background' element found in the SVG") ); - } - - m_zoomInButton = new Plasma::ToolButton( this ); - m_zoomOutButton = new Plasma::ToolButton( this ); - m_zoomInButton->setIcon( KIcon("zoom-in") ); - m_zoomOutButton->setIcon( KIcon("zoom-out") ); - m_zoomInButton->setZValue( 999999 ); - m_zoomOutButton->setZValue( 999999 ); - connect( m_zoomInButton, SIGNAL(clicked()), this, SLOT(zoomIn()) ); - connect( m_zoomOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()) ); - - m_title = new Plasma::Label( this ); - QFont font = Plasma::Theme::defaultTheme()->font( Plasma::Theme::DefaultFont ); - font.setPixelSize( 14 ); - font.setBold( true ); - m_title->setFont( font ); - m_title->setWordWrap( false ); - m_title->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); - m_title->setZValue( 999999 ); - - m_courtesy = new Plasma::Label; - m_courtesy->setAlignment( Qt::AlignVCenter | Qt::AlignRight ); - connect( m_courtesy, SIGNAL(linkActivated(QString)), - KToolInvocation::self(), SLOT(invokeBrowser(QString)) ); - QLabel *labelInfo = m_courtesy->nativeWidget(); - labelInfo->setOpenExternalLinks( true ); - labelInfo->setWordWrap( true ); - m_courtesy->setText( courtesyText() ); - m_courtesy->setZValue( 999999 ); - - m_departureView = new QGraphicsWidget( this ); - m_departureView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - m_departureView->translate( 0, -m_title->size().height() - 25 ); - - QGraphicsGridLayout *l = new QGraphicsGridLayout( this ); - l->addItem( m_zoomInButton, 0, 0 ); - l->addItem( m_zoomOutButton, 0, 1 ); - l->addItem( m_title, 0, 2 ); - l->addItem( m_departureView, 1, 0, 1, 3 ); - l->addItem( m_courtesy, 2, 0, 1, 3 ); - - m_stopSettings.set( ServiceProviderSetting, - config().readEntry(QLatin1String("serviceProvider"), QString()) ); - m_stopSettings.set( StopNameSetting, - config().readEntry(QLatin1String("stopName"), QString()) ); - - m_timelineLength = config().readEntry( QLatin1String("timelineLength"), 10 ); - m_showTimetable = config().readEntry( QLatin1String("showTimetable"), true ); - m_drawTransportLine = config().readEntry( QLatin1String("drawTransportLine"), true ); - - QVariantList vehicleTypes = config().readEntry( QLatin1String("vehicleTypes"), QVariantList() ); - if ( vehicleTypes.isEmpty() ) { - m_vehicleTypes << UnknownVehicleType << Tram << Bus << TrolleyBus << InterurbanTrain - << Subway << Metro << RegionalTrain << RegionalExpressTrain << InterregionalTrain - << IntercityTrain << HighSpeedTrain << Ship << Plane << Feet; - } else { - foreach ( const QVariant &vehicleType, vehicleTypes ) { - m_vehicleTypes << static_cast( vehicleType.toInt() ); - } - } - - if ( m_stopSettings.stopList().isEmpty() ) { - setConfigurationRequired( true, i18n("Please select a stop name") ); - } else if ( m_stopSettings.get(ServiceProviderSetting).isEmpty() ) { - setConfigurationRequired( true, i18n("Please select a service provider") ); - } else { - setConfigurationRequired( false ); - } - - if ( !configurationRequired() ) { - m_sourceName = QString("Departures %1|stop=%2|timeoffset=0") - .arg(m_stopSettings.get(ServiceProviderSetting)) - .arg(m_stopSettings.stop(0).nameOrId()); - dataEngine("publictransport")->connectSource( m_sourceName, - this, 60000, Plasma::AlignToMinute ); - } - - createTooltip(); -} - -void GraphicalTimetableLine::createConfigurationInterface(KConfigDialog* parent) -{ - QWidget *stopConfig = new QWidget( parent ); - QFormLayout *stopLayout = new QFormLayout( stopConfig ); - - m_stopWidget = new StopWidget( stopConfig, m_stopSettings ); - CheckCombobox *filterList = new CheckCombobox( stopConfig ); - m_vehicleTypeModel = new VehicleTypeModel( filterList ); - m_vehicleTypeModel->checkVehicleTypes( m_vehicleTypes ); - filterList->setModel( m_vehicleTypeModel ); - filterList->setAllowNoCheckedItem( false ); - m_showTimetableCheckbox = new QCheckBox( i18n("Enable"), stopConfig ); - m_drawTransportLineCheckbox = new QCheckBox( i18n("Enable"), stopConfig ); - m_showTimetableCheckbox->setChecked( m_showTimetable ); - m_drawTransportLineCheckbox->setChecked( m_drawTransportLine ); - m_drawTransportLineCheckbox->setToolTip( i18n("Draws the transport line string into the " - "vehicle type icon, for icons that are associated with a single departure.") ); - - stopLayout->addRow( m_stopWidget ); - stopLayout->addRow( i18n("Shown &Vehicles:"), filterList ); - stopLayout->addRow( i18n("Show &Timetable:"), m_showTimetableCheckbox ); - stopLayout->addRow( i18n("Draw Transport &Line:"), m_drawTransportLineCheckbox ); - parent->addPage( stopConfig, i18n("Stop") ); - - connect( parent, SIGNAL(applyClicked()), this, SLOT(configAccepted()) ); - connect( parent, SIGNAL(okClicked()), this, SLOT(configAccepted()) ); - - m_stopWidget->setFocus(); - if ( m_stopSettings.stopList().isEmpty() || m_stopSettings.stop(0).name.isEmpty() ) { - m_stopWidget->editSettings(); - } -} - -void GraphicalTimetableLine::configAccepted() -{ - m_stopSettings = m_stopWidget->stopSettings(); - m_vehicleTypes = m_vehicleTypeModel->checkedVehicleTypes(); - - qDeleteAll( m_departures ); - m_departures.clear(); - - if ( m_stopSettings.stops().isEmpty() ) { - setConfigurationRequired( true, i18n("Please select a stop name") ); - } else if ( m_stopSettings.get(ServiceProviderSetting).isEmpty() ) { - setConfigurationRequired( true, i18n("Please select a service provider") ); - } else { - setConfigurationRequired( false ); - } - - // Disconnect old source - if ( !m_sourceName.isEmpty() ) { - dataEngine("publictransport")->disconnectSource( m_sourceName, this ); - } - - if ( !configurationRequired() ) { - m_animate = false; - m_sourceName = QString("Departures %1|stop=%2|timeoffset=0") - .arg(m_stopSettings.get(ServiceProviderSetting)) - .arg(m_stopSettings.stops(StopSettings::UseStopIdIfAvailable).first()); - dataEngine("publictransport")->connectSource( m_sourceName, - this, 60000, Plasma::AlignToMinute ); - } - - m_showTimetable = m_showTimetableCheckbox->isChecked(); - m_drawTransportLine = m_drawTransportLineCheckbox->isChecked(); - - config().writeEntry( QLatin1String("serviceProvider"), - m_stopSettings.get(ServiceProviderSetting) ); - config().writeEntry( QLatin1String("stopName"), - m_stopSettings.stops(StopSettings::UseStopIdIfAvailable).first() ); - - config().writeEntry( QLatin1String("timelineLength"), m_timelineLength ); - config().writeEntry( QLatin1String("showTimetable"), m_showTimetable ); - config().writeEntry( QLatin1String("drawTransportLine"), m_drawTransportLine ); - - QVariantList vehicleTypes; - foreach ( VehicleType vehicleType, m_vehicleTypes ) { - vehicleTypes << static_cast( vehicleType ); - } - config().writeEntry( QLatin1String("vehicleTypes"), vehicleTypes ); - - emit configNeedsSaving(); - configChanged(); - - m_stopWidget = 0; - m_vehicleTypeModel = 0; - m_showTimetableCheckbox = 0; - m_drawTransportLineCheckbox = 0; -} - -void GraphicalTimetableLine::createTooltip( Departure *departure ) -{ - if ( isPopupShowing() || (formFactor() != Plasma::Horizontal - && formFactor() != Plasma::Vertical) ) { - return; - } - - Plasma::ToolTipContent tooltipData; - tooltipData.setMainText( i18nc("@info", "Public Transport") ); - if ( m_departures.isEmpty() ) { - tooltipData.setSubText( i18nc("@info", "View departures for public transport") ); - } else { - QList dataList = departure ? departure->departureData() - : m_departures.first()->departureData(); - if ( dataList.count() == 1 ) { - // Signle departure item hovered - DepartureData data = dataList.first(); - tooltipData.setSubText( i18n("Line %1 (%2) %3", - data.transportLine, data.target, KGlobal::locale()->formatTime(data.time.time())) ); - } else { - // Multiple departure items hovered - QString text; - foreach ( const DepartureData &data, dataList ) { - text.append( i18n("Line %1 (%2) %3\n", - data.transportLine, data.target, KGlobal::locale()->formatTime(data.time.time())) ); - } - if ( text.endsWith('\n') ) { - text.remove( text.length() - 1, 1 ); - } - tooltipData.setSubText( text ); - } - } - - tooltipData.setImage( KIcon("public-transport-stop").pixmap(IconSize(KIconLoader::Desktop)) ); - Plasma::ToolTipManager::self()->setContent( this, tooltipData ); -} - -QString GraphicalTimetableLine::courtesyText() -{ - QVariantHash data = dataEngine("publictransport")->query( - QString("ServiceProvider %1").arg(m_stopSettings.get(ServiceProviderSetting)) ); - QString shortUrl = data[ "shortUrl" ].toString(); - QString url = data[ "url" ].toString(); - QString sLastUpdate = m_lastSourceUpdate.toString( "hh:mm" ); - if ( sLastUpdate.isEmpty() ) { - sLastUpdate = i18nc("@info/plain This is used as 'last data update' text when there " - "hasn't been any updates yet.", "none"); - } - - // HACK: This breaks the text at one position if needed - // Plasma::Label doesn't work well will HTML formatted text and word wrap: - // It sets the height as if the label shows the HTML source. - QString textNoHtml1 = QString( "%1: %2" ).arg( i18nc( "@info/plain", "last update" ), sLastUpdate ); - QString textNoHtml2 = QString( "%1: %2" ).arg( i18nc( "@info/plain", "data by" ), shortUrl ); - QFontMetrics fm( m_courtesy->font() ); - int width1 = fm.width( textNoHtml1 ); - int width2 = fm.width( textNoHtml2 ); - int width = width1 + fm.width( ", " ) + width2; - if ( width > size().width() ) { - setMinimumWidth( qMax(150, qMax(width1, width2)) ); - return QString( "%1: %2
%3: %5
" ) - .arg( i18nc( "@info/plain", "last update" ), sLastUpdate, - i18nc( "@info/plain", "data by" ), url, shortUrl ); - } else { - return QString( "%1: %2, %3: %5" ) - .arg( i18nc( "@info/plain", "last update" ), sLastUpdate, - i18nc( "@info/plain", "data by" ), url, shortUrl ); - } -} - -void GraphicalTimetableLine::zoomIn() -{ - m_timelineLength /= 1.5; - if ( m_timelineLength <= MIN_TIMELINE_LENGTH ) { - m_timelineLength = MIN_TIMELINE_LENGTH; // Minimally MIN_TIMELINE_LENGTH minutes - m_zoomInButton->setEnabled( false ); - } - m_zoomOutButton->setEnabled( true ); - updateItemPositions( false ); - updateTitle(); - update(); -} - -void GraphicalTimetableLine::zoomOut() -{ - m_timelineLength *= 1.5; - if ( m_timelineLength >= MAX_TIMELINE_LENGTH ) { - m_timelineLength = MAX_TIMELINE_LENGTH; // Maximally MAX_TIMELINE_LENGTH minutes - m_zoomOutButton->setEnabled( false ); - } - m_zoomInButton->setEnabled( true ); - updateItemPositions( false ); - updateTitle(); - update(); -} - -QDateTime GraphicalTimetableLine::endTime() const -{ - return QDateTime::currentDateTime().addSecs( 60 * m_timelineLength ); -} - -void GraphicalTimetableLine::dataUpdated( const QString& sourceName, - const Plasma::DataEngine::Data& data ) -{ - Q_UNUSED( sourceName ); - - kDebug() << m_departures.count() << "departures at start"; - for ( int i = m_departures.count() - 1; i >= 0; --i ) { - Departure *departure = m_departures[i]; - if ( QDateTime::currentDateTime().secsTo(departure->dateTime()) <= -40 - && departure->isVisible() ) - { - // Remove old departure - kDebug() << "Animate out old departure" << i << "at" << departure->dateTime().time().toString(); - m_departures.removeAt( i ); - - departure->setZValue( 999999 ); - - QPropertyAnimation *rotateAnimation = - new QPropertyAnimation( departure, "rotation", this ); - rotateAnimation->setStartValue( 0.0 ); - rotateAnimation->setEndValue( 360.0 ); - rotateAnimation->setEasingCurve( QEasingCurve(QEasingCurve::OutInBack) ); - rotateAnimation->setDuration( 1000 ); - - Plasma::Animation *slideAnimation = - Plasma::Animator::create( Plasma::Animator::SlideAnimation, departure ); - slideAnimation->setEasingCurve( QEasingCurve(QEasingCurve::InCubic) ); - slideAnimation->setTargetWidget( departure ); - slideAnimation->setProperty( "movementDirection", Plasma::Animation::MoveRight ); - slideAnimation->setProperty( "distance", contentsRect().width() - departure->boundingRect().right() ); - slideAnimation->setProperty( "duration", 750 ); - - Plasma::Animation *fadeAnimation = - Plasma::Animator::create( Plasma::Animator::FadeAnimation, departure ); - fadeAnimation->setTargetWidget( departure ); - fadeAnimation->setProperty( "startOpacity", 1.0 ); - fadeAnimation->setProperty( "targetOpacity", 0.0 ); - fadeAnimation->setProperty( "duration", 750 ); - - QParallelAnimationGroup *parallelGroup = new QParallelAnimationGroup( departure ); - parallelGroup->addAnimation( slideAnimation ); - parallelGroup->addAnimation( fadeAnimation ); - - QSequentialAnimationGroup *sequentialGroup = new QSequentialAnimationGroup( departure ); - sequentialGroup->addAnimation( rotateAnimation ); - sequentialGroup->addAnimation( parallelGroup ); - connect( sequentialGroup, SIGNAL(finished()), departure, SLOT(deleteLater()) ); - sequentialGroup->start( QAbstractAnimation::DeleteWhenStopped ); - } - } - - QUrl url; - QDateTime updated; - url = data["requestUrl"].toUrl(); - updated = data["updated"].toDateTime(); - QVariantList departuresData = data.contains("departures") ? data["departures"].toList() - : data["arrivals"].toList(); - kDebug() << " - " << departuresData.count() << "departures to be processed"; - foreach ( const QVariant &departureVariant, departuresData ) { - const QVariantHash departureHash = departureVariant.toHash(); - VehicleType vehicleType = static_cast( departureHash["TypeOfVehicle"].toInt() ); - if ( !m_vehicleTypes.contains(vehicleType) ) { - continue; // Fitlered - } - - QDateTime dateTime = departureHash["DepartureDateTime"].toDateTime(); - if ( QDateTime::currentDateTime().secsTo(dateTime) < -60 ) { - kDebug() << "Got an old departure" << dateTime; - continue; - } - - DepartureData departureData( dateTime, departureHash["TransportLine"].toString(), - departureHash["Target"].toString(), vehicleType ); - - bool departureIsOld = false; - foreach ( Departure *departure, m_departures ) { - if ( departure->containsDeparture(departureData) ) { - departureIsOld = true; - break; - } - } - if ( departureIsOld ) { - continue; // Departure was already added in a previous dataUpdated call - } - - Departure *departure = new Departure( m_departureView, departureData ); - departure->setPos( m_timelineEnd ); - m_departures << departure; - -// QList< QTime > routeTimes; -// if ( dataMap.contains( "routeTimes" ) ) { -// QVariantList times = dataMap[ "routeTimes" ].toList(); -// foreach( const QVariant &time, times ) { -// routeTimes << time.toTime(); -// } -// } -// DepartureInfo departureInfo( dataMap["operator"].toString(), dataMap["line"].toString(), -// dataMap["target"].toString(), dataMap["departure"].toDateTime(), -// static_cast( dataMap["vehicleType"].toInt() ), -// dataMap["nightline"].toBool(), dataMap["expressline"].toBool(), -// dataMap["platform"].toString(), dataMap["delay"].toInt(), -// dataMap["delayReason"].toString(), dataMap["journeyNews"].toString(), -// dataMap["routeStops"].toStringList(), routeTimes, -// dataMap["routeExactStops"].toInt() ); - } // for ( int i = 0; i < count; ++i ) - - // Update "last update" time - if ( updated > m_lastSourceUpdate ) { - m_lastSourceUpdate = updated; - } - - updateTitle(); - m_courtesy->setText( courtesyText() ); - createTooltip(); - - kDebug() << m_departures.count() << "departures after adding new ones"; - updateItemPositions( m_animate ); - m_animate = true; - update(); -} - -void GraphicalTimetableLine::updateTitle() -{ - if ( !m_title || m_stopSettings.stopList().isEmpty() ) { - return; - } - QFontMetrics fm( m_title->font() ); - qreal maxStopNameWidth = contentsRect().width() - m_zoomOutButton->boundingRect().right() - 50 - - fm.width(" (99:99 - 99:99)"); - m_title->setText( QString("%1 (%2 - %3)") - .arg( fm.elidedText(m_stopSettings.stop(0), Qt::ElideRight, maxStopNameWidth) ) - .arg( KGlobal::locale()->formatTime(QTime::currentTime()) ) - .arg( KGlobal::locale()->formatTime(endTime().time()) ) ); -} - -Departure::Departure( QGraphicsItem* parent, const DepartureData &data, const QPointF &pos ) - : QGraphicsWidget(parent) -{ - m_size = QSizeF( DEPARTURE_SIZE, DEPARTURE_SIZE ); - - QFont f = Plasma::Theme::defaultTheme()->font( Plasma::Theme::DefaultFont ); - f.setBold( true ); - f.setPixelSize( 13 ); - setFont( f ); - - m_departures << data; - setPos( pos ); - updatePosition(); - updateDrawData(); - updateTooltip(); -} - -Departure::Departure(QGraphicsItem* parent, const QList< DepartureData >& dataList, - const QPointF &pos) - : QGraphicsWidget(parent) -{ - m_size = QSizeF( DEPARTURE_SIZE, DEPARTURE_SIZE ); - - QFont f = Plasma::Theme::defaultTheme()->font( Plasma::Theme::DefaultFont ); - f.setBold( true ); - f.setPixelSize( 13 ); - setFont( f ); - - m_departures = dataList; - setPos( pos ); - updatePosition(); - updateDrawData(); - updateTooltip(); -} - -QPointF Departure::updatePosition( bool animate ) -{ - GraphicalTimetableLine *applet = qobject_cast(parentWidget()->parentWidget()); - qreal newOpacity, zoom, z; - QPointF position = applet->positionFromTime( m_departures.first().time, &newOpacity, &zoom, &z ); - if ( position.isNull() ) { - if ( pos().isNull() ) { - setOpacity( 0.0 ); - } else if ( isVisible() && opacity() > 0.0 ) { - Plasma::Animation *fadeAnimation = - Plasma::Animator::create( Plasma::Animator::FadeAnimation, this ); - fadeAnimation->setTargetWidget( this ); - fadeAnimation->setProperty( "startOpacity", opacity() ); - fadeAnimation->setProperty( "targetOpacity", 0.0 ); - fadeAnimation->start( QAbstractAnimation::DeleteWhenStopped ); - } - } else { - int msecs = animate ? 5000 : 250; - - if ( pos().isNull() ) { - setPos( applet->newDeparturePosition() ); - setZValue( z ); - setSize( QSizeF(DEPARTURE_SIZE * zoom, DEPARTURE_SIZE * zoom) ); - } - - QPropertyAnimation *moveAnimation = new QPropertyAnimation( this, "pos" ); - moveAnimation->setDuration( msecs ); - moveAnimation->setEasingCurve( QEasingCurve(QEasingCurve::InOutQuad) ); - moveAnimation->setStartValue( pos() ); - moveAnimation->setEndValue( position ); - - Plasma::Animation *fadeAnimation = 0; - if ( opacity() != newOpacity ) { - fadeAnimation = Plasma::Animator::create( Plasma::Animator::FadeAnimation, this ); - fadeAnimation->setTargetWidget( this ); - fadeAnimation->setProperty( "duration", msecs ); - fadeAnimation->setProperty( "startOpacity", opacity() ); - fadeAnimation->setProperty( "targetOpacity", newOpacity ); - } - QPropertyAnimation *zoomAnimation = new QPropertyAnimation( this, "size" ); - zoomAnimation->setDuration( msecs ); - zoomAnimation->setStartValue( m_size ); - zoomAnimation->setEndValue( QSizeF(DEPARTURE_SIZE * zoom, DEPARTURE_SIZE * zoom) ); - - QParallelAnimationGroup *parallelGroup = new QParallelAnimationGroup( this ); - if ( fadeAnimation ) { - parallelGroup->addAnimation( fadeAnimation ); - } - parallelGroup->addAnimation( zoomAnimation ); - parallelGroup->addAnimation( moveAnimation ); - parallelGroup->start( QAbstractAnimation::DeleteWhenStopped ); - - setZValue( z ); - } - - return position; -} - -void Departure::updateTooltip() -{ - QString text = i18np("One Departure:", "%1 Departures:", m_departures.count()); - text.append( "
" ); - // Show maximally 10 departures - for ( int i = 0; i < qMin(10, m_departures.count()); ++i ) { - const DepartureData data = m_departures[i]; - // TODO KUIT - text.append( i18n("Line %1 at %2 to %3", data.transportLine, - KGlobal::locale()->formatTime(data.time.time()), data.target) ); - text.append( "
" ); - } - if ( m_departures.count() > 10 ) { - text.append( i18np("...one more departure", "...%1 more departures", m_departures.count() - 10) ); - } - if ( text.endsWith(QLatin1String("
")) ) { - text.remove( text.length() - 6, 6 ); - } - - setToolTip( text ); -} - -void Departure::updateDrawData() -{ - m_drawData.clear(); - QSet drawnVehicleTypes; - QSet doubleVehicleTypes; - QSet dontDrawTransportLineVehicleTypes; - for ( int i = 0; i < m_departures.count(); ++i ) { - DepartureData &data = m_departures[i]; - if ( drawnVehicleTypes.contains(data.vehicleType) ) { - // There is already a departure drawn with this vehicle type - if ( doubleVehicleTypes.contains(data.vehicleType) ) { - if ( !dontDrawTransportLineVehicleTypes.contains(data.vehicleType) ) { - // Don't draw the transport line string for vehicles types that have - // more than two associated departures in this departure item - dontDrawTransportLineVehicleTypes << data.vehicleType; - - for ( int i = m_departures.count() - 1; i >= 0; --i ) { - if ( dontDrawTransportLineVehicleTypes.contains(m_departures[i].vehicleType) ) { - m_departures[i].drawTransportLine = false; - } - } - } - } else { - // There is only one other departure with this vehicle type - data.drawTransportLine = true; - m_drawData << &data; - doubleVehicleTypes << data.vehicleType; - } - } else { - // First departure with this vehicle type - data.drawTransportLine = true; - m_drawData << &data; - drawnVehicleTypes << data.vehicleType; - } - } - - // Don't draw double vehicle types if there are more than four items to be drawn - if ( m_drawData.count() > 4 ) { - for ( int i = m_drawData.count() - 1; i >= 0; --i ) { - if ( doubleVehicleTypes.contains(m_drawData[i]->vehicleType) ) { - doubleVehicleTypes.remove( m_drawData[i]->vehicleType ); - m_drawData.removeAt( i ); - } - } - } -} - -void Departure::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget ) -{ - Q_UNUSED( option ); - Q_UNUSED( widget ); - - painter->setRenderHint( QPainter::SmoothPixmapTransform ); - painter->setRenderHint( QPainter::Antialiasing ); - - GraphicalTimetableLine *applet = qobject_cast(parentWidget()->parentWidget()); - QRectF rect = boundingRect(); - - QRectF vehicleRect = rect; - qreal factor = departureSizeFactor(); - vehicleRect.setWidth( vehicleRect.width() * factor ); - vehicleRect.setHeight( vehicleRect.height() * factor ); - qreal translation = departureOffset( vehicleRect.width() ); - foreach ( const DepartureData *data, m_drawData ) { - if ( data->drawTransportLine ) { - applet->paintVehicle( painter, data->vehicleType, vehicleRect, data->transportLine ); - } else { - applet->paintVehicle( painter, data->vehicleType, vehicleRect ); - } - - // Move to next vehicle type svg position - vehicleRect.translate( translation, translation ); - } - - QDateTime minTime = m_departures.first().time, maxTime = minTime; - foreach ( const DepartureData &data, m_departures ) { - if ( data.time < minTime ) { - minTime = data.time; - } else if ( data.time > maxTime ) { - maxTime = data.time; - } - } - QString text; - if ( m_departures.count() != m_drawData.count() ) { - // Show the number of departures if there are more than vehicle icons drawn - text.append( QString("%1: ").arg(m_departures.count()) ); - } -// text.append( minTime == maxTime ? KGlobal::locale()->formatTime(minTime.time()) -// : QString("%1 - %2").arg(KGlobal::locale()->formatTime(minTime.time())) -// .arg(KGlobal::locale()->formatTime(maxTime.time())) ); - - QDateTime currentTime = QDateTime::currentDateTime(); - if ( minTime == maxTime ) { - int minsToDeparture = qCeil( currentTime.secsTo(minTime) / 60.0 ); - if ( minsToDeparture < 0 ) { - text.append( i18n("leaving") ); - } else if ( minsToDeparture == 0 ) { - text.append( i18n("now") ); - } else { - text.append( i18np("in 1 min.", "in %1 min.", minsToDeparture) ); - } - } else { - int minMinsToDeparture = qCeil( currentTime.secsTo(minTime) / 60.0 ); - int maxMinsToDeparture = qCeil( currentTime.secsTo(maxTime) / 60.0 ); - if ( minMinsToDeparture < 0 && maxMinsToDeparture < 0 ) { - text.append( i18n("leaving") ); - } else if ( minMinsToDeparture == 0 && maxMinsToDeparture == 0 ) { - text.append( i18n("now") ); - } else { - text.append( i18n("in %1-%2 min.", minMinsToDeparture, maxMinsToDeparture) ); - } - } - - QFontMetrics fm( font() ); - int textWidth = fm.width( text ); - QRectF textRect = rect; - QRectF haloRect( textRect.left() + (textRect.width() - textWidth) / 2, - textRect.top() + (textRect.height() - fm.height()) / 2, - textWidth, fm.height() ); - haloRect = haloRect.intersected( textRect ); - - painter->rotate( 45 ); // Draw the text 45 degree rotated, just along the arrangement of vehicle type icons - Plasma::PaintUtils::drawHalo( painter, haloRect ); - painter->setFont( font() ); - painter->drawText( textRect, text, QTextOption(Qt::AlignCenter) ); -} - -QSizeF Departure::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const -{ - Q_UNUSED( which ); - Q_UNUSED( constraint ); - return m_size; -} - -QRectF Departure::boundingRect() const -{ - qreal padding = 20; - return QRectF( -m_size.width() / 2.0 - padding, -m_size.height() / 2.0 - padding, - m_size.width() + 2 * padding, m_size.height() + 2 * padding ); -} - -void Departure::combineWith( Departure *other ) -{ - m_departures << other->departureData(); - updateDrawData(); - updateTooltip(); -} - -Departure* Departure::splitAt( QGraphicsItem *parent, int index ) -{ - if ( m_departures.count() == 1 || index == 0 ) { - // Departure items should at least contain one departure - return 0; - } - - // Create new Departure item with departures beginning with index - Departure *departure = new Departure( parent, m_departures.mid(index), pos() ); - - // Remove departures that moved into the new Departure item - while ( m_departures.count() > index ) { - m_departures.removeLast(); - } - - updateDrawData(); - updateTooltip(); - return departure; -} - -void Departure::hoverEnterEvent( QGraphicsSceneHoverEvent* event ) -{ - GraphicalTimetableLine *applet = - qobject_cast( parentWidget()->parentWidget() ); - QGraphicsItem::hoverEnterEvent(event); - applet->createTooltip( this ); -} - -QPointF GraphicalTimetableLine::positionFromTime( const QDateTime& time, qreal *opacity, - qreal *zoom, qreal *z ) const -{ - qreal minutesToDeparture = qCeil( QDateTime::currentDateTime().secsTo(time) / 60.0 ); - if ( minutesToDeparture > m_timelineLength || minutesToDeparture < 0 ) { - return QPointF(); - } - - qreal position = minutesToDeparture / m_timelineLength; // 0 .. 1 - - if ( opacity ) { - *opacity = position < 0.5 ? 1.0 : 1.0 - 2.0 * (position - 0.5); - } - if ( zoom ) { - *zoom = 1.5 * (2.0 - position); - } - if ( z ) { - *z = 1.0 - position; - } - return QPointF( m_timelineStart.x() + position * (m_timelineEnd.x() - m_timelineStart.x()), - m_timelineStart.y() + position * (m_timelineEnd.y() - m_timelineStart.y()) ); -} - -void GraphicalTimetableLine::updateItemPositions( bool animate ) -{ - QPointF lastPos; - Departure *lastDeparture = 0; - for ( int i = 0; i < m_departures.count(); ++i ) { - Departure *departure = m_departures[i]; - QPointF newPos = departure->updatePosition( animate ); - - // Split departure items eg. after zooming out - QList departureData = departure->departureData(); - QPointF lastSubPos = positionFromTime(departureData.first().time); - for ( int n = 1; n < departureData.count(); ++n ) { - QPointF subPos = positionFromTime(departureData[n].time); - if ( (lastSubPos - subPos).manhattanLength() > MIN_DISTANCE_BETWEEN_DEPARTURES ) { - // Departure isn't too close to the last departure - // Split them to two objects - Departure *splitDeparture = departure->splitAt( m_departureView, n ); - if ( splitDeparture ) { - m_departures.insert( i + 1, splitDeparture ); - splitDeparture->updatePosition( animate ); - } - break; - } - lastSubPos = subPos; - } - - if ( lastDeparture && (lastPos - newPos).manhattanLength() < MIN_DISTANCE_BETWEEN_DEPARTURES ) { - // Departure is very close to the last departure - // Combine both into one object - lastDeparture->combineWith( departure ); - - // Delete combined departure item - m_departures.removeAt( i ); - delete departure; - --i; - } else if ( !newPos.isNull() ) { - lastDeparture = departure; - lastPos = newPos; - } - } - return; -} - -void GraphicalTimetableLine::paintVehicle( QPainter* painter, VehicleType vehicle, - const QRectF& rect, const QString &transportLine ) -{ - // Draw transport line string onto the vehicle type svg - // only if activated in the settings and a supported vehicle type - // (currently only local public transport) - bool drawTransportLine = m_drawTransportLine && !transportLine.isEmpty() - && PublicTransport::Global::generalVehicleType(vehicle) == LocalPublicTransport; - - QString vehicleKey; - switch ( vehicle ) { - case Tram: vehicleKey = "tram"; break; - case Bus: vehicleKey = "bus"; break; - case TrolleyBus: vehicleKey = "trolleybus"; break; - case Subway: vehicleKey = "subway"; break; - case Metro: vehicleKey = "metro"; break; - case InterurbanTrain: vehicleKey = "interurbantrain"; break; - case RegionalTrain: vehicleKey = "regionaltrain"; break; - case RegionalExpressTrain: vehicleKey = "regionalexpresstrain"; break; - case InterregionalTrain: vehicleKey = "interregionaltrain"; break; - case IntercityTrain: vehicleKey = "intercitytrain"; break; - case HighSpeedTrain: vehicleKey = "highspeedtrain"; break; - case Feet: vehicleKey = "feet"; break; - case Ship: vehicleKey = "ship"; break; - case Plane: vehicleKey = "plane"; break; - default: - kDebug() << "Unknown vehicle type" << vehicle; - return; // TODO: draw a simple circle or something.. or an unknown vehicle type icon - } - if ( drawTransportLine ) { - vehicleKey.append( "_empty" ); - } - if ( !m_svg.hasElement(vehicleKey) ) { - kDebug() << "SVG element" << vehicleKey << "not found"; - return; - } - - int shadowWidth = 4; - m_svg.resize( rect.width() - 2 * shadowWidth, rect.height() - 2 * shadowWidth ); - - QPixmap pixmap( (int)rect.width(), (int)rect.height() ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - m_svg.paint( &p, shadowWidth, shadowWidth, vehicleKey ); - - // Draw transport line string (only for local public transport) - if ( drawTransportLine ) { - QString text = transportLine; - text.remove( ' ' ); - - QFont f = font(); - f.setBold( true ); - if ( text.length() > 2 ) { - f.setPixelSize( qMax(8, qCeil(1.2 * rect.width() / text.length())) ); - } else { - f.setPixelSize( rect.width() * 0.55 ); - } - p.setFont( f ); - p.setPen( Qt::white ); - - QRect textRect( shadowWidth, shadowWidth, - rect.width() - 2 * shadowWidth, rect.height() - 2 * shadowWidth ); - p.drawText( textRect, text, QTextOption(Qt::AlignCenter) ); - } - - QImage shadow = pixmap.toImage(); - Plasma::PaintUtils::shadowBlur( shadow, shadowWidth - 1, Qt::black ); - painter->drawImage( rect.topLeft() + QPoint(1, 2), shadow ); - painter->drawPixmap( rect.topLeft(), pixmap ); -} - -void GraphicalTimetableLine::resizeEvent(QGraphicsSceneResizeEvent* event) -{ - Plasma::Applet::resizeEvent(event); - - QRectF rect = contentsRect(); - m_timelineStart = QPointF( rect.left() + 0.1 * rect.width(), rect.top() + 0.75 * rect.height() ); - m_timelineEnd = QPointF( rect.right() - 0.05 * rect.width(), rect.top() + 0.18 * rect.height() ); - - qreal scale = qMax( qreal(0.4), qMin(qreal(1.0), - qreal(qMin(rect.width(), rect.height()) / 250)) ); - foreach ( Departure *departure, m_departures ) { - departure->setScale( scale ); - } - updateItemPositions( false ); - updateTitle(); // New eliding -} - -void GraphicalTimetableLine::paintInterface( QPainter *p, - const QStyleOptionGraphicsItem *option, const QRect &rect ) -{ - Q_UNUSED( option ); - p->setRenderHint( QPainter::SmoothPixmapTransform ); - p->setRenderHint( QPainter::Antialiasing ); - - // Draw background - if ( !m_svg.hasElement("background") ) { - kDebug() << "Background SVG element not found"; - return; - } - m_svg.resize( (int)rect.width(), (int)rect.height() ); - m_svg.paint( p, rect, "background" ); - - // Draw text markers (every full hour) - QFont timeMarkerFont = font(); - timeMarkerFont.setBold( true ); - p->setFont( timeMarkerFont ); - p->setPen( Qt::darkGray ); - QFontMetrics fm( font() ); - QDateTime time( QDate::currentDate(), QTime(QTime::currentTime().hour() + 1, 0) ); - QPointF pos = positionFromTime( time ); - while ( !pos.isNull() ) { - QString text = KGlobal::locale()->formatTime(time.time()); - int textWidth = fm.width( text ); - QRectF textRect( pos.x() - textWidth / 2.0, pos.y() - fm.height() / 2.0, - textWidth, fm.height() ); - - Plasma::PaintUtils::drawHalo( p, textRect ); - p->drawText( textRect, text, QTextOption(Qt::AlignCenter) ); - - time = time.addSecs( 60 * 60 ); - pos = positionFromTime( time ); - } - - if ( m_showTimetable ) { - QFont timetableFont = font(); - timetableFont.setBold( false ); - timetableFont.setPixelSize( 10 ); - p->setFont( timetableFont ); -#if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - p->setPen( Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor) ); -#else - p->setPen( Plasma::Theme::defaultTheme()->color(Plasma::Theme::ViewTextColor) ); -#endif - - fm = QFontMetrics( timetableFont ); - qreal padding = 8; - QRect timetableRect( rect.left() + 5, rect.top() + m_title->boundingRect().height() + 10, - rect.width() * 0.4, rect.height() * 0.4 ); - QRect timetableContentsRect = timetableRect.adjusted( padding, padding, -padding, -padding ); - int maxLines = qFloor( timetableRect.height() / fm.lineSpacing() ) - 1; - QList departureDataList; - for ( int i = 0; i < qMin(m_departures.count(), maxLines); ++i ) { - departureDataList << m_departures[i]->departureData(); - } - - // Draw timetable background - m_svg.resize( (int)timetableRect.width(), (int)timetableRect.height() ); - m_svg.paint( p, timetableRect, "timetable" ); - - // Calculate column widths - int maxTransportLineWidth = 0; - int maxDepartureWidth = 0; - QDateTime currentTime = QDateTime::currentDateTime(); - QStringList departureTimeStrings; - for ( int i = 0; i < qMin(maxLines, departureDataList.count()); ++i ) { - int transportLineWidth = fm.width( departureDataList[i].transportLine ); - if ( transportLineWidth > maxTransportLineWidth ) { - maxTransportLineWidth = transportLineWidth; - } - - int minsToDeparture = qCeil( currentTime.secsTo(departureDataList[i].time) / 60.0 ); - QString departureTimeString = minsToDeparture == 0 ? i18n("now") - : i18np("1 min.", "%1 min.", minsToDeparture); - departureTimeStrings << departureTimeString; - - int departureWidth = fm.width( departureTimeString ); - if ( departureWidth > maxDepartureWidth ) { - maxDepartureWidth = departureWidth; - } - } - qreal columnTransportLine = qMin( (qreal)maxTransportLineWidth + 5, - timetableContentsRect.width() / 4.0 ); - qreal columnDeparture = qMin( (qreal)maxDepartureWidth + 5, - timetableContentsRect.width() / 3.5 ); - qreal columnTarget = timetableContentsRect.width() - columnTransportLine - columnDeparture; - - // Prepere text options - QTextOption textOption( Qt::AlignLeft | Qt::AlignTop ); - textOption.setWrapMode( QTextOption::NoWrap ); - textOption.setTabArray( QList() << columnTransportLine - << (columnTransportLine + columnTarget) ); // Use an example line string - - // Draw timetable text - for ( int i = 0; i < qMin(maxLines, departureDataList.count()); ++i ) { - DepartureData data = departureDataList[i]; - QString elidedLine = fm.elidedText( data.transportLine, Qt::ElideRight, columnTransportLine - 5 ); - QString elidedTarget = fm.elidedText( data.target, Qt::ElideRight, columnTarget - 5 ); - QString departureString = QString("%2\t%3\t%1").arg(departureTimeStrings[i]) - .arg(elidedLine).arg(elidedTarget); - QString timetableText( departureString ); - - QRect textRect( timetableContentsRect.left(), - timetableContentsRect.top() + i * fm.lineSpacing(), - timetableContentsRect.width(), fm.lineSpacing() ); - Plasma::PaintUtils::drawHalo( p, textRect ); - p->drawText( textRect, timetableText, textOption ); - } - } -} - -#include "graphicaltimetableline.moc" diff --git a/applet-graphicaltimetableline/graphicaltimetableline.h b/applet-graphicaltimetableline/graphicaltimetableline.h deleted file mode 100644 index de6fbf5..0000000 --- a/applet-graphicaltimetableline/graphicaltimetableline.h +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Here we avoid loading the header multiple times -#ifndef GRAPHICALTIMETABLELINE_HEADER -#define GRAPHICALTIMETABLELINE_HEADER - -// libpublictransporthelper includes -#include - -// KDE includes -#include -#include -#include - -#define DEPARTURE_SIZE 20 -#define MIN_DISTANCE_BETWEEN_DEPARTURES 50 -#define MIN_TIMELINE_LENGTH 5 // in minutes -#define MAX_TIMELINE_LENGTH 3 * 60 // in minutes - -class QPropertyAnimation; -class QCheckBox; -class CheckCombobox; -namespace Plasma { - class ToolButton; - class Label; -} -namespace PublicTransport { - class StopWidget; - class VehicleTypeModel; -} -using namespace PublicTransport; - -/** - * @brief Data for one departure/arrival. - **/ -struct DepartureData { - QDateTime time; - QString transportLine; - QString target; - VehicleType vehicleType; - bool drawTransportLine; - - DepartureData() { - vehicleType = UnknownVehicleType; - }; - - DepartureData( const QDateTime &time, const QString &transportLine, const QString &target, - VehicleType vehicleType, bool drawTransportLine = true ) { - this->time = time; - this->transportLine = transportLine; - this->target = target; - this->vehicleType = vehicleType; - this->drawTransportLine = drawTransportLine; - }; - - bool operator ==( const DepartureData &other ) const { - return time == other.time && transportLine == other.transportLine - && target == other.target && vehicleType == other.vehicleType; - }; -}; - -/** - * @brief A QGraphicsItem showing one or more departures/arrivals. - * - * One Departure can be combined with another using @ref combineWith. - **/ -class Departure : public QGraphicsWidget { - Q_OBJECT - Q_PROPERTY( QSizeF size READ size WRITE setSize ) -public: - Departure( QGraphicsItem* parent, const DepartureData &data, - const QPointF &pos = QPointF() ); - Departure( QGraphicsItem* parent, const QList &dataList, - const QPointF &pos = QPointF() ); - - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - virtual QSizeF sizeHint(Qt::SizeHint which, const QSizeF& constraint = QSizeF()) const; - virtual QRectF boundingRect() const; - QSizeF size() const { return m_size; }; - void setSize( const QSizeF &size ) { m_size = size; updateGeometry(); update(); }; - - /** - * @brief Updates the position based on the departure time. - * - * This function gets called automatically when the departure time gets changed using - * @ref setDateTime. - * - * @param animate Whether or not to animate the position change. The animation can take up to - * one minute. Default is true. - **/ - QPointF updatePosition( bool animate = true ); - - void updateTooltip(); - void updateDrawData(); - - QList departureData() const { return m_departures; }; - QDateTime dateTime() const { return m_departures.first().time; }; - QStringList transportLines() const { - QStringList ret; - foreach ( const DepartureData &data, m_departures ) { - ret << data.transportLine; - } - return ret; - }; - QStringList targets() const { - QStringList ret; - foreach ( const DepartureData &data, m_departures ) { - ret << data.target; - } - return ret; - }; - QList vehicleTypes() const { - QList ret; - foreach ( const DepartureData &data, m_departures ) { - ret << data.vehicleType; - } - return ret; - }; - - bool containsDeparture( const DepartureData &other ) const { - foreach ( const DepartureData &data, m_departures ) { - if ( data == other ) { - return true; - } - } - - return false; // Not found - }; - - void combineWith( Departure *other ); - Departure *splitAt( QGraphicsItem *parent, int index ); - -protected: - virtual void hoverEnterEvent( QGraphicsSceneHoverEvent* event ); - -private: - inline qreal departureSizeFactor() const { - return m_drawData.count() == 1 ? 1.0 : 1.0 / (0.75 * m_drawData.count()); - }; - inline qreal departureOffset( qreal vehicleSize ) const { - return m_drawData.count() == 1 ? 0.0 - : (boundingRect().width() - vehicleSize) / (m_drawData.count() - 1); - }; - - QList m_departures; // Departures visualized by this item - QList m_drawData; // Departures for which an own icon gets drawn - QSizeF m_size; -}; - -// Define our plasma Applet -class GraphicalTimetableLine : public Plasma::Applet -{ - Q_OBJECT -public: - // Basic Create/Destroy - GraphicalTimetableLine( QObject *parent, const QVariantList &args ); - ~GraphicalTimetableLine(); - - virtual void createConfigurationInterface( KConfigDialog* parent ); - - // The paintInterface procedure paints the applet to the desktop - void paintInterface( QPainter *painter, - const QStyleOptionGraphicsItem *option, - const QRect& contentsRect ); - void init(); - - QPointF positionFromTime( const QDateTime &time, qreal *opacity = 0, qreal *zoom = 0, - qreal *zValue = 0 ) const; - void paintVehicle( QPainter *painter, VehicleType vehicle, const QRectF &rect, - const QString &transportLine = QString() ); - - QString courtesyText(); - QDateTime endTime() const; - void createTooltip( Departure *departure = 0 ); - - QPointF newDeparturePosition() const { return m_timelineEnd; }; - -protected: - virtual void resizeEvent( QGraphicsSceneResizeEvent* event) ; - -public slots: - void configAccepted(); - - /** @brief The data from the data engine was updated. */ - void dataUpdated( const QString& sourceName, const Plasma::DataEngine::Data &data ); - - void updateItemPositions( bool animate = true ); - void updateTitle(); - - void zoomIn(); - void zoomOut(); - -private: - // Configuration widgets - StopWidget *m_stopWidget; - VehicleTypeModel *m_vehicleTypeModel; - QCheckBox *m_showTimetableCheckbox; - QCheckBox *m_drawTransportLineCheckbox; - - // Settings - StopSettings m_stopSettings; - QList m_vehicleTypes; // filter - qreal m_timelineLength; // in minutes - bool m_showTimetable; - bool m_drawTransportLine; - - // Graphics items - Plasma::ToolButton *m_zoomInButton; - Plasma::ToolButton *m_zoomOutButton; - Plasma::Label *m_title; - Plasma::Label *m_courtesy; - QGraphicsWidget *m_departureView; - QList m_departures; - - // Data source info - QDateTime m_lastSourceUpdate; - QString m_sourceName; - - Plasma::Svg m_svg; - QPointF m_timelineStart; - QPointF m_timelineEnd; - bool m_animate; -}; - -// This is the command that links your applet to the .desktop file -K_EXPORT_PLASMA_APPLET(graphicaltimetableline, GraphicalTimetableLine) -#endif diff --git a/applet-graphicaltimetableline/plasma-applet-graphicaltimetableline.desktop b/applet-graphicaltimetableline/plasma-applet-graphicaltimetableline.desktop deleted file mode 100644 index 3b24a14..0000000 --- a/applet-graphicaltimetableline/plasma-applet-graphicaltimetableline.desktop +++ /dev/null @@ -1,54 +0,0 @@ -[Desktop Entry] -Name=GraphicalTimetableLine -Name[bs]=GrafičkiRasporedLinija -Name[ca]=GraphicalTimetableLine -Name[ca@valencia]=GraphicalTimetableLine -Name[de]=Grafischer Fahrplan -Name[es]=GraphicalTimetableLine -Name[et]=Graafiline sõiduplaan -Name[fr]=GraphicalTimetableLine -Name[gl]=Liña gráfica da liña de horarios -Name[km]=GraphicalTimetableLine -Name[ko]=GraphicalTimetableLine -Name[nl]=GrafischeDienstregelingRegel -Name[pl]=Graficzny rozkład jazdy linii -Name[pt]=LinhaGráficaHorário -Name[pt_BR]=LinhaGráficaHorário -Name[sk]=GraphicalTimetableLine -Name[sv]=Grafisk tidtabellrad -Name[uk]=Графічний розклад авіаліній -Name[x-test]=xxGraphicalTimetableLinexx -Comment=Plasma GraphicalTimetableLine -Comment[bs]=Plazma GrafičkiRasporedLinija -Comment[ca]=GraphicalTimetableLine de Plasma -Comment[ca@valencia]=GraphicalTimetableLine de Plasma -Comment[de]=Grafischer Fahrplan für Plasma -Comment[es]=GraphicalTimetableLine de Plasma -Comment[et]=Plasma graafiline sõiduplaan -Comment[fr]=Outil graphique de calendrier pour Plasma -Comment[gl]=Liña gráfica da liña de horarios para Plasma -Comment[km]=បន្ទាត់​កាលវិភាគ​ក្រាហ្វិក​ប្លាស្មា -Comment[ko]=Plasma GraphicalTimetableLine -Comment[nl]=Plasma GrafischeDienstregelingRegel -Comment[pl]=Graficzny rozkład jazdy linii dla Plazmy -Comment[pt]=LinhaGráficaHorário do Plasma -Comment[pt_BR]=LinhaGráficaHorário do Plasma -Comment[sk]=Plasma GraphicalTimetableLine -Comment[sv]=Plasma grafisk tidtabellrad -Comment[uk]=Графічний розклад авіаліній Плазми -Comment[x-test]=xxPlasma GraphicalTimetableLinexx -Type=Service - -X-KDE-ServiceTypes=Plasma/Applet -X-KDE-Library=plasma_applet_graphicaltimetableline -X-KDE-PluginInfo-Author=Friedrich Pülz -X-KDE-PluginInfo-Email=fpuelz@gmx.de -X-KDE-PluginInfo-Name=graphicaltimetableline -X-KDE-PluginInfo-Version=0.1.1 -X-KDE-PluginInfo-Website=http://plasma.kde.org/ -X-KDE-PluginInfo-Category=Online Services -X-KDE-PluginInfo-Category= -X-KDE-PluginInfo-Depends= -X-KDE-PluginInfo-License=GPL -X-KDE-PluginInfo-EnabledByDefault=true - diff --git a/applet-graphicaltimetableline/vehicles.svg b/applet-graphicaltimetableline/vehicles.svg deleted file mode 100644 index a79f5d5..0000000 --- a/applet-graphicaltimetableline/vehicles.svg +++ /dev/null @@ -1,1568 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - Bus - - - - - - - S - - - - - - - Tram - - - - - - - U - - - - - - - Tro - - - - - - - M - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/applet/AUTHORS b/applet/AUTHORS deleted file mode 100644 index db15527..0000000 --- a/applet/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Friedrich Pülz, fpuelz@gmx.de \ No newline at end of file diff --git a/applet/CHANGELOG b/applet/CHANGELOG deleted file mode 100644 index a11e30e..0000000 --- a/applet/CHANGELOG +++ /dev/null @@ -1,567 +0,0 @@ - -Changelog of plasma-applet-publictransport - -0.10.1 -- Fix busy state not ending when departure data was received - -0.10 - Final -- Global CMakeLists.txt to build and install everything in one run -- Fix backgrounds of journey items with a positive/negative rating (they faded to black instead of transparent) -- Fix crash when resizing the widget when not in departure view -- Fix crash without route information -- Fix crash with empty journey data sources -- Fix applet collapsing to popup icon in desktop after restart -- Fix double request of departures -- Fix timetable not showing up after first configuration -- Use custom font also for route stop text items (if too small it uses KGlobalSettings::smallestReadableFont) -- Correct height of departure/journey graphics items (with bigger fonts sometimes a second line wasn't shown) -- Show popup when starting a journey search or showing the journey list (if the applet is in a panel) -- Add a journey menu, used in several places, with an entry to start a new journey search, entries for favorite/recent journeys and an entry to configure those journeys (in a dialog) -- Add journey button to the title of the applet (on the left of the filter button), which shows the new journey menu -- Add departure date filter for alarms, this fixes old autogenerated alarms not to be deleted, if the applet was not running when the alarm should have fired -- Use target for color groups if route stops are not available -- Automatically remove city names from stop names -- Make use of the apply button in the configuration dialog -- Do not show text in the filter button if no filters are active - -- Filter button without text if no filters are active -- Make use of apply button in the configuration dialog -- Fix crash with empty journey data sources -- Fix journey search menu updates when changing the current stop settings -- Fix text color of journey route items -- Fix drawing of route stop items on mouse hover: It could draw light text on a light halo -- Fix unreadable background/text color combinations in journey view. The positive/negative background colors of the current color scheme are darkened/lightened to provide enough contrast to the text color. If the background color gets darkened it's saturation gets raised to still be able to identify the journey rating easily. For neutral rating the neutral background color form the current color scheme gets now used instead of transparent. -- Fix zoom factor update of journey route items, they now properly update their geometries to zoom factor changes -- Fix enable/disable drawing of shadows/halos -- Cleanup - -0.10 - RC 2 -- Fix link errors -- Fix memory leaks -- Fix applet collapsing to popup icon in desktop -- Add the possibility to put the applet into the notification area (systemtray). -- Add monochrome vehicle type icons, used for the popup icon -- The popup icon now visualizes the time until the departure of the currently shown vehicle by fading it in radially like a clock (completely half-transparent => departure in 60 or more minutes, completely opaque => departure is now, 1/2 opaque => departure in 30 minutes, etc.) -- Use of KPixmapCache for vehicle type icons -- Improve drawing performance alot - - Don't draw a complete background gradient for departure/journey items; draw solid color background(s) and then draw fade out gradients at the left/right - - Hide route items if the containing departure/journey item isn't expanded - - Store a pointer to the DepartureModel in RouteStopTextGraphicsItem, to get it's RouteStopFlags (used for drawing) -- Don't rearrange route stop items if the route item isn't visible (not expanded) -- Better layout adjustments for small sizes -- Change applet tooltip according to current departure group in popup icon -- Shorter alarm names, alarm names in notifications -- Nicer alarm background -- Fix color groups and route time display for arrivals - -0.10 - RC 1 -Highlights: New filter/alarm UI, Context menus for journey/departure route stops, highlight stops, show intermediate departure list, alarms in journey view, colorized departure groups, quick&easy filtering by color groups, show stops in marble using OpenStreetMap, use of QStateMachine - -- Replace state flags with a QStateMachine (states for views like departures, journeys, journey search, ...; states for data status like waiting, valid, invalid; states for the network status). -- Add a context menu to route stop items. There are currently four entries: Create a filter for departures via that stop, show an intermediate departure list for that stop, highlight a stop (also highlights departures/arrivals via that stop) and copy stop name to clipboard. -- New context menu entries for journey route stops (copy stop name to clipboard, add/remove an alarm for the journey, ie. the first sub-journey) -- New context menu entries for departure items: "Route Stops", with entries for each route stop (and sub entries which are the same as shown in context menus of route stop items) and "Unhighlight All Stops" (only shown if a stop is currently highighted) -- New filter configuration UI with better space usage -- New alarm configuration UI (almost the same as the filter UI) -- Possible to choose multiple filter configurations per stop (AND combined) -- Remove "Enable Filters" setting, to disable filters, disable all filter configurations -- Connections between filters and stops get now stored with the filter settings (a list of stops affected by the filter, previously the name of the filter was stored with the stop settings) -- Filters get now stored in config groups named "filterConfig_X", not "filterConfig_[NAME]" (the names get now stored inside the filter config groups) -- Group departures by direction automatically (can be disabled in the appearance settings) -- Departure groups are visualized by different background colors (each direction always gets the same group color, colors are used from a list of selected oxygen colors) -- When departure groups are enabled, they are shown in the filter menu, to quickly filter out departures into a specific direction (or to use as a legend for the departure groups) -- Show stops in marble using updated OpenStreetMap data engine (context menu of route items, only shown if the openstreetmap data engine is installed) -- Show time in minutes from the first stop in route stops (and the actual time in it's tooltip) -- Show alarms in journey view (matching the first journey part) -- Show alarm icons in departure and journey view -- Use different icons for special route stops (home stop -> "go-home", origin -> "flag-red", target -> "flag-green", highlighted -> "flag-blue") -- Draw an arrow at the end of the route line in route items -- Ask for a name for new filter configurations -- Move departureinfo.h/.cpp, filter.h/.cpp, filterwidget.h/.cpp and some parts of global.h/.cpp from the applet to libpublictransporthelper (because class FilterSettings gets now use in StopSettingsWidget/StopSettingsDialog, instead of only a QString with the name of the filter configuration) -- New unit tests for the applet -- Don't display too many route stop items (omit some if there's not enough space) -- Show delay information for journeys again (was missing since the porting to QGraphicsWidget) -- Elide too long filter names when shown in the filter button -- Fix departures not being removed and flashing all the time by making DepartureModel::removeLeavingDepartures a slot -- Fix alarm settings writing (the number of alarms was stored per applet, but the alarm settings globally) -- Fix crashes with context menus of deleted route stop items -- Fix intermediate departure list action, didn't work when another intermediate departure list was requested while already in intermediate departure list state - -0.10 - Beta 8 -- Make leaving departures animate before being removed from the list - -0.10 - Beta 6 -- Port JourneySearchSuggestionWidget from a proxied QTreeView to a Plasma::ScrollWidget and add a class JourneySearchSuggestionItem, representing a single suggestion. -- Drop unused HtmlDelegate class, JourneySearchSuggestionWidget was the last class using it. -- Add a context menu to intermediate stops in journey route items. Currently contains one entry to search for Journeys to that stop. Can be handy to get some earlier connections to that intermediate stop, so that one has more time to change the vehicle. -- Draw the "public-transport-stop"-icons instead of a simple circles for route stop items (but not for the small intermediate stops). -- Add a QGraphicsDropShadowEffect to RouteStopMarkerGraphicsItem. -- Make padding in TimetableWidget/JourneyWidget smaller and adjust to the size factor defined in the settings dialog. -- New layout for multiple vehicle icons in the popup icon. -- Reduce number of needed preprocessor switches for KDE 4.5 support. - -0.10 - Beta 5 -- Make TimetableWidget show "from" for arrival lists and "to" for departure lists. -- Make compilable with KDE 4.5 again (tested on fedora 14). - -0.10 - Beta 4 -- Replaced the QWidget based treeviews used to show the departure/arrival/journey timetables with new QGraphicsWidgets, named TimetableWidget and JourneyTimetableWidget. These graphics widgets are derived from Plasma::ScrollWidget, to enable nice scrolling animations. The TimetableWidget/JourneyTimetableWidget classes offer the same functionality as the previous treeviews, except for headers and header column resizing (is now done automatically, the journey view doesn't use columns now) and moving/sorting (not really needed?). It allows more flexible animations, eg. a custom easing curve for the expand/collapse animation. -- Routes are now drawn as line with markers at the stop positions. For each marker the stop name is drawn rotated to fit multiple stop names (using the smallest readable font as configured in KDEs settings). Uses QPainterPath to draw the text better readable than with QPainter::drawText. This new route item takes much less space than the previous simple route stop list. The end of the exact route (if any) is visualized by adding multiple smaller stop markers after the last exact stop. The route item offers nice animations. -- Journey routes are now also represented by a new nice QGraphicsWidget, showing a vertical timeline. -- The only thing using HtmlDelegate in the applet is now the JourneySearchSuggestionWidget, still needs to be converted to a QGraphicsWidget. -- Dropped PublicTransportTreeView and some functions in the PublicTransport class, which are no longer needed, because of the switch from QWidget to QGraphicsWidget. -- The title of the applet (the name of the used stop) now gets elided if neccessary, allowing smaller applet sizes without painting outside of the applet. -- The icon shown in panels now shows a vehicle icon for the next departure(s) with the minutes until the departure. The transport line string is drawn into the vehicle type SVG for local public transport. The drawing code comes from the GraphicalTimetableLine applet. -- Scrolling the mouse wheel on the icon in a panel cycles through the first few departures and the next alarm departure, if there is one (with nice animations). -- Made the icon showing an alarm departure look consistant with the other new icon appearances. - -0.10 - Beta 3 -- Fixed mapping of new departures from the data engine to displayed departures in the applet (it didn't compare the target string). This caused filters to be run on one departure, while another one got falsely removed. -- Fixed the duration display for journey searches. -- Changed the filter widgets from IconWidget + Label to ToolButton. This means, that now the text can also be clicked. -- Changed the X-KDE-PluginInfo-Website in the .desktop file to point to the PublicTransport UserBase page. - -0.10 - Beta 1 -- The filter label now also uses the selected font from the settings. -- The title for the journey search is now correctly set to "From A to B", instead of just "Journeys". -- Removed the flag icons, now using the ones used by the locale settings in systemsettings. This also fixes packaging conflicts with other packages that install icons with the same names. -- Code cleanup: new classes TitleWidget, JourneySearchSuggestionWidget with code that was previously inside the main PublicTransport applet class -- Moved some stuff into a separate library: publictransporthelper. It contains classes that can be used by applets/runners for easy configuration dialog creation. All classes in that new library got d-pointers and are more flexible now. - -0.9 -- Fixed a crash, that occured sometimes after an update with changed data (eg. a delayed departure no longer being delayed). Cause: Items could be deleted without notifying the model. -- Show which filter is currently active if any. The filter can also be enabled/disabled or changed by clicking the new filter icon. -- Created models for locations and service providers instead of using QStandardItemModels for easier reuse (eg. by the runner) and cleaner code. -- Disabled the use of KCategoryView for the service provider combobox, because it's not really useful as there is the possibility to filter it using the location combobox. And the categorized view causes some problems. -- Added a button in the service provider info dialog to open the service provider in TimetableMate. - -0.8.3 -- Added KUIT to all i18n calls, more context strings, fixes (title capitalization, had some contexts as strings...) -- Added KUIT to .ui strings. Especially whatsthis-strings are now much easier to read. -- Fixed coloring of keywords in keyword suggestion items for the journey search view. -- BugFix: When reading the settings the first old alarm could be read again although it has been removed. -- BugFix: When no alarm is selected in the alarm settings page the alarm-type and affected-stops-combo boxes weren't set disabled and changing them caused a crash. - -0.8.2 -- BugFix: The filter job of the worker thread didn't protect the use of a member variable with the mutex. It also marked departures as newly unfiltered also if they shouldn't be visible because of the first departure settings. -- Recently used journey searches are now also suggested when only the stop name matches. Previously the stop name and all other keywords had to match. -- Fixed journey search suggestions to appear on each edit. Previously not all suggestions were added (eg. "30 minutes later") when for example a recently used stop was clicked. -- Fixed stop suggestions in journey search when the search line is emtpy (the stop name wasn't inserted). - -0.8.1 -- The departure/journey views now show a message if they don't contain any items (like "waiting for departures..."). If filters are enabled and departure data is fetched it shows a hint that filters are enabled. -- Alarms do now blink 5 times when fired (in each affected row). -- The blur effect for the overlay widget is no longer animated if the applet is too big. -- Fixed many (all?) krazy warnings. -- Error messages are no longer shown in popup dialogs but directly inside the departure view and only if the departure list is empty. -- Removed the upper limit (5) of stop settings. -- BugFix: Automatically generated alarms that get directly fired are now also removed from the alarm list. -- BugFix: Fixed a crash when changing the filter configuration while alarms are active. -- BugFix: Fixed possible crash when changing the filter configuration. If an item should be added to the model but already is in it, the model now returns a pointer to the existing item rather than NULL. - -0.8 -Highlights: Syntax highlighted journey search, easy journey search string "construction" with suggestions, nicer journey search view. - -- Added a button to start the journey search to the journey search view. -- Recent journey searches are now also shown in the journey search suggestions view (maximally five). The treeview now also uses the HTML delegate with a transparent background. -- Extended the (plasma-) HTML delgate to draw nice focus backgrounds. -- Added items to the journey search suggestions view to add/remove keywords to/from the journey search line, eg. add "in 5/15/30 minutes" / "tomorrow at 6:00" or to change them, eg. "30/60 minutes later/earlier". -- Hover effect for the departure/journey view and the journey search suggestion view. -- Fadeout for the journey search line (left and right). -- Clear button for the journey search line, without letting the cursor go under the clear button. -- Descriptions for journey search keywords (shown in the add/remove items). -- New journey search syntax highlighter. -- New line edit derived from KLineEdit, that uses the journey search syntax highlighter and draws highlighted text using QTextDocument. -- Tooltip not showing on desktop or when the popup is shown. -- Now using KInputDialog to get names for filter configurations, haven't found that class earlier. -- Less memory consumption: All widgets were created at startup, also widgets for eg. searching for journeys. They are now created when needed and deleted afterwards. -- Better fading between views, using a single animation object, that fades out a pixmap of the old appearance. -- Sorting by arrival and duration now works as expected for the journey view. -- Better column resizing behavior, to avoid having a horizontal scrollbar shown (by adjusting the size of the last column). -- Better journey rating: It's computed by duration (75%) and needed changes (25%) and has some special cases. The colors are now more visible (smaller fade out areas). -- BugFix: Correct layout at startup. Previously the layout was a bit wrong until the first departures were shown. -- Fixed install.sh. - -0.8 Beta 1 -Highlights: Alarm settings page, recurring alarms, journey rating by duration, much faster updates (by using custom models and a worker thread), better error messages. - -- New settings page for alarms: Alarms can be defined using a filter, all departures that match the filter automatically get an alarm. Recurring alarms are now also possible, others get deleted once they're fired. Alarms can still be used as before using the context menu, which now generates a one time alarm filter. Generated alarms can be removed using the context menu as long as the alarm doesn't get changed. Alarms won't get lost anymore on logout. -- New filter types for the alarm filter: Filter by departure time and by day of week. So you can now tell the applet to create an alarm for specific departures, eg. line 6, every workday at 6:00. -- Worker thread for reading data from the data engine, filtering departures and checking for alarms. Previously those stuff could take a long time when very many departures are displayed (I measured 3 seconds for 100 departures for each update, but updates are now also much faster, because departures are now only updated when they have changed). -- The applet won't show more departures than set in the settings, but the data engine now requests as much departures as possible. With enabled filters the chances are now higher to reach the maximum departure count. -- New custom model for departures. It uses a timer which is aligned to minute. Each full minute the remaining time values are updated (previously everything was updated), old departures are removed and the next alarm is checked (previously one QTimer for each alarm). New departures are correctly sorted. Departures/Journeys are accessible through a uint hash value, so finding an item in the model is now much faster. -- Journeys now get rated, currently only by it's duration. The shortest journeys get a green background, the longest get a red one (at least with the default KDE color scheme ;)). -- The info column of the journey view now shows the duration and the needed changes. The start and target stop names are now displayed in the title. -- More meaningful error messages are now displayed if eg. the server isn't available. An ok button can be pressed to retry downloading. Checks network status using the "network" data engine. -- The route item of departures is now automatically expanded. -- Obsolete AlarmTimer class removed. -- Seperate class for parsing the journey search string. -- Put OverlayWidget into a seperate file. -- Moved the custom QTreeView and QHeaderView subclasses to a seperate file. -- For unknown vehicle types the icon from the settings is now also shown in the departure list. -- BugFix: The title label's font size didn't change when the size factor was changed in the settings. -- The indentation in the departure view is now also affected by size factor changes. -- BugFix: When changing the first departure setting to "at custom time" the input field wasn't enabled. -- Fixed text color of route itmes, they were always dark gray, hardly readable in dark themes. -- Fixed not working "Filter Uses" tab. Stop settings got mapped to the wrong combo boxes to change their filter configuration. -- For KDE 4.3 the KCategoryView now isn't used for the service provider model, because that had problems. -- Moved ~2000 lines of code out of publictransport.cpp. - -0.7 -- Better line breaking for the departure column and less unneeded "regexp'ing from/to html". -- Alternating row colors for the departure view with a gradient (one brush for a departure item and all it's children). -- For departures with an alarm a special background is now drawn again, but this time also for all children. -- The first column can no longer be moved and other columns can no longer be moved to visual index 0. Otherwise the child items aren't spanned over the whole row (QTreeView currently only allows the first column to be spanned over all columns). -- Changed the background hint back from TranslucentBackground to StandardBackground. -- The text color for delayed departures or departures on schedule is now the themes text color, tinted to red/green. Previously it just used red/green/darkred/darkgreen, which wasn't working good with all themes. -- Less confusing departure time display for delayed departures: Display predicted departure (scheduled departure + delay) and show delay in format "(+X)" if any. Previously it showed this: "[predicted-departure] Minutes + [delay] Minutes". This also removes the doubled "Minutes" text. -- BugFix: When changing the number of lines per row the result wasn't always directly visible, because the size hints of the items aren't always updated. Now the complete model is rebuild with the new lines per row settings (without requesting data again from the data engine). -- BugFix: Newly added stop settings had bad defaults for settings in the details of the stop settings dialog (alarm time, time of first departure, ...). -- BugFix: The feature list of the current service provider wasn't updated, when the current stop was changed. Therefore for example "Search for Journeys..." could show up in the context menu although it's not supported by the service provider. -- BugFix: The "action buttons" overlay showed a button to display arrivals even if they aren't supported by the service provider. - -0.7 Beta 5 -- The location/service provider combo boxes now use a not plasma themed (HTML-) delegate. -- The service provider combo box now uses KCategorizedView to display the providers categorized by location. -- The departure view now doesn't draw frames for each departure. Instead it draws lines between them. Previously the drawing didn't work well with some themes and when columns were moved. -- The background of the applet is now more transparent. The departure (tree-) view has no background anymore. For light themes now halos are drawn instead of shadows. -- New appearance config option: Draw shadows or not. -- Better fade out rect calculation, removed right margin for items in the departure view. -- The header view now looks more like plasma. -- The departure view now fades out on bottom and (if the header is hidden) on top (by overriding drawRow() of QTreeView). -- Nicer context menu with separators and a modified "run associated application"-action with the appropriate browser icon. -- The "Update Timetable" action now updates the journey view if it's currently shown (not the depature view). -- Instead of saving the header section positions and sizes in seperate config keys the whole header state is now stored in one key (using saveState(), restoreState()). -- Features of service providers are now also translated in the combo box. -- BugFix: Showing/hiding the header when a journey list is shown now doesn't switch to departure view. -- BugFix: "Duration" and "Changes" items in journey view were deleted on update, "Pricing" got duplicated with each update. -- BugFix: The route item in journey view got expanded with each update (not only when it's added to the list). -- Fixed a possible crash on startup. -- Code cleanups. - -0.7 Beta 4 -- Stops and filters are now shared between all publicTransport applets. Old non-globally stored settings will be moved to the global config. -- Made the "First Departure" and "Alarm Time" settings settable independently for each stop and moved them into the details of the stop settings dialog. -- Show "Switch current stop" action only if there is more than one stop setting. -- Show "Show Arrivals" action only if that's supported by the service provider. -- BugFix: The "Show Arrival list" radio button in the settings dialog was always disabled. -- New Filter: Filter by intermediate stops (via). This can be used for example instead of multiple target filters, if you want to only show departures in one direction. Then the target column can be hidden without showing ambigous information. -- BugFix: Filter variants were overwritten with a random variant when the filter was opened. -- Column sizes and positions are now stored on exit. -- The courtesy label now gets broken into two lines if the applet is too slim. The applet's minimal width is then set so that the label fits into it. Previously the label could exceed the size of the applet (and with it all other controls, eg. the departure view). -- BugFix: When showing/hiding the header or the target column with the context menu, the settings weren't saved. -- BugFix: The hide column target was only stored if it wasn't changed. - -0.7 Beta 3 -- Fixed storing of filter configurations with only one filter and only one constraint. -- Fixed the default constraint for new vehicle type filters to have "Unknown" checked. Previously there were no checked item, which could confuse users, because the combobox doesn't allow that all items get unchecked. -- Changed the "Switch Filter Configuration" context menu item. It's now named "Filter" and contains a checkbox to enable/disable filters, the filter configurations are separated by a title item. -- Added an option to enable/disable all filters, therefore the ShowAll filter action is now removed. -- Added a new tab to the filter settings page, which lists all stop settings and the used filter configurations to quickly change them. -- Fixed journey view: all results were written into one item. Cause: The hash wasn't generated for journey items, therefore an empty string was used as hash for all journey items. -- Added a what's this text to the stop list of the stop config dialog and changed the labels of additional stops from "Additional Stop X" to "Combined Stop X". - -0.7 Beta 2 -- Fixed storing of the selected city (for service providers requiring a city value). -- Enabled word wrap for the stop settings overview. -- Saved some lines of code by putting some code of the update departure/journey item method into another method. -- Fixed "first departure at custom time" mode: It filtered out items before the current time instead of filtering them out if they're before the custom time. -- The stop settings dialog now doesn't allow to accept empty stop names. It is now also better in setting widget values from incomplete stop settings. -- Replaced the "Find Stops Near You..." button in the stop settings dialog with a KDialog user button and relabled it to "Nearby Stops...". That should make it clear to the user that that may change all values in the dialog, not only the stop name. -- The stop settings dialog now uses a details widget, which contains the filter configuration selector and the button to install additional service providers. The header labels are removed to make the dialog even smaller ;) -- Fixed a bug with the dynamic widget list widget. -- Fixed a bug that prevented the notification of errors in the data engine (mixed up two parameters..). -- Code cleanups. -- Changed the names of the service providers to the companies behind. -- Fixed the sorting of service providers (international always at the end). -- BugFix: Don't try to select the default service provider if "Show All Service Providers" is selected. Previously this selected a title element = no valid service provider. Now it just stays at the last selected service provider. - -0.7 Beta 1 -- Revamped stop selection config, more like the weather applet with stop settings in a separate dialog. Can now contain multiple stop lists. The user can then switch between those stop lists and all departures for the stops in the current list are combined. In other words: publicTransport now supports switching between multiple stops AND combining departures of near stops. -- Different filter config for each stop ('default' by default ;)). Changing the filter config in the filter config page now doesn't change the currently used filter config, but opens it for editing. No need to save filter configs, they're now saved by accepting the config dialog. -- Revamped filter config, new filters, easier setup, more flexible. -- Switching between multiple stops is now done through the main applets context menu entry "Switch Current Stop", it shows a submenu with radio items for all defined stop settings. -- New classes for settings that replace the old PublicTransportSettings class, which needed a pointer to the applet and was crowded. Those new classes are: Settings (to store settings in), SettingsIO (static methods for reading/writing Settings), SettingsUiManager (manages the config dialog), StopFinder (searches near stops using OSM, validates them with the publicTransport engine). -- Moved context menu items which aren't contextual for departure/arrival items away from the departure view's context menu to the main applet's context menu. -- The size slider in the appearance config page now looks like the one of the folder view applet. -- All stop names from openstreetmap are now send to the service provider to find a matching stop name. -- Service providers are now sorted by their names (were previously sorted by country code). -- The config dialog now uses a QSortFilterProxyModel to filter the service provider model (only show items for the currently selected location). This way the service provider model doesn't need to be reconstructed each time the location changes. -- Created a base class for widgets that can dynamically add/remove child widgets and provides add/remove buttons. That class is used for the stop settings overview, in the stop settings dialog and in the filter settings (here it has two levels: multiple filters can contain multiple constraints). -- Created a subclass of KComboBox: CheckComboBox. When no or only one item is checked it is displayed normally. When multiple items are checked all icons of those items are painted and a "checked count / total count" text. It also elides icons if they don't fit into the combo box. It's used for the filter by vehicle type. -- Fixed install with custom prefix for /accessorInfos (dataEngine) and /kbflags (applet). -- BugFix: The currently used stops weren't set when opening the config dialog again. -- BugFix: Updating departure items took too much CPU. -- BugFix: The settings dialog now uses only one HtmlDelegate to paint the items of the locations/service providers comboboxes. - -0.6.10 -- The applet now uses a new data engine (from me ;), optional) "openstreetmap" (also in playground in KDEs SVN), to get stop names near the user. The geolocation data engine is used to get values for where the user is (latitude, longitude). Initial values can be set this way (country, default accessor, city/stop) when adding another publictransport applet. There's also a new button to automatically fill in all values in the stop selection page (a dialog box is shown to choose a stop). Could be useful when you're on a journey and want to set the stop to the currently nearest stop. The new stop name is then checked at the service provider and the completer pops up (if only the city is missing it is added to the stop name in one if these ways: "stop city", "stop, city" or "city stop"). If the accuracy of the user's position is low (eg. with IP based geolocation), a larger area is searched for stop names. The data engine reports openstreetmap data while downloading. -- When changing the service provider, the stop autocompleter now pops up with stop names from the new service provider. This should make sure, that the user doesn't just change the service provider and expect the stop name to be still valid. -- Nicer icons: They're only using oxygen colors now. -- Fixed license (COPYING files) -- BugFix: Finished alarm timers crashed the applet. - -0.6.9 -- Targets/Origins can now be filtered using wildcards/regular expressions. -- The old RegExp-Accessors (without script file) can now also parse stop weights. -- BugFix: Stop suggestions didn't work for de_fahrplaner. It's now using another url for stop suggestions, like de_db (same system), with stop weights. -- BugFix: sk_imhd didn't work because the web site has changed the time format from "h.mm" to "hh:mm". The time parsing is now more flexible and tries other time formats if the one specified in the script doesn't work. - -0.6.8 -- Moved the credits label to the bottom (like in the weather applet). It's now also translatable. -- Better title layout (stop names), especially with multiple or long stop names. -- Better image quality when the applet is rotated. -- Sub-items in the departure/arrival-treeview can now be expanded/collapsed with a single/double click on the whole item (currently only useful for the route information item). -- Changed the advanced config page: The update timeout option is gone, timeouts are managed by the data engine; Better layout. -- Removed the layout hack for the info label that is now on the bottom of the applet. -- Less CPU usage when updating the timetable data in the applet. A hash is created and stored for each new timetable item to quickly find it in the model. -- BugFix: The tooltip didn't show the next departure of all currently activated stops. -- BugFix: Showing only results of the nth stop didn't work for n > 0 (all but the first stop). -- BugFix: When automatic updates are disabled the shown departures/journeys are now updated every minute based on the available timetable data (instead of requesting new data). Previously there were no updates at all (without auto updates) and the durations until departure weren't updated. -- BugFix: Fixed the main layout of the applet, it expanded over the size of the applet after some time. -- BugFix: The departure/arrival list is now trimmed to maximally contain "maximum departures" items again. This now also works for multiple stops. -- BugFix: The info label didn't show the latest update time when multiple stops were used. It just showed the last update time of the stop for that results went in from the data engine at last. -- BugFix: Fixed the used home stop for journey searches to be the first in the list of stops. - -0.6.7 -- BugFix: The applet now stores departures seperately for each stop. Previously only one departure list was used for all stops, old departures weren't cleared when new data arrived and the list of departures grew. -- The credit tooltip now isn't shown when no credit info is given in the accessor info xml ( tag). The tag should only be included in accessor info xmls if the service provider has permitted it's use in publicTransport. -- Additional information is now shown/hidden with single/double click depending on KDEs global settings. - -0.6.6 -New features: Multiple stops in one applet - -- Multiple stops can now be used in a single applet. It shows the departures/arrivals for all stops or just for one of them at a time. The currently shown stop(s) can be switched by clicking the icon in the top-left. New stops can easily be added with an unobtrusive tool button. All stop edit fields have autocompletion. -- Fixed another memory leak in the applet (not deleted widgets, when not currently in a layout). - -0.6.5 -- Fixed memory leaks in the applet: - - The HtmlDelegate used for the timetable TreeView wasn't deleted. - - The models for the timetable data in the applet (departures/arrivals and journeys) weren't deleted. - - AlarmTimer objects weren't always deleted. - - The HtmlDelegate::paint() method created new Plasma::FrameSvg objects in each call, without deleting them. - - PublicTransportSettings::m_dataSourceTester wasn't deleted. - - Some widgets weren't deleted. - - Some context menu actions weren't deleted. - -- Fixed a compile issue with KDE 4.3. - -0.6.4 -New features: Action buttons shown on click on the top-left icon, more credit infos. - -- Clicking the icon on the top-left of the applet now shows some action buttons and blurs the other widgets in the applet (for Qt < 4.6 there's no blur, but a semi-transparent background-colored overlay). The actions are currently: Search journeys and switch beween departures/arrivals (when journeys are shown it's go back to the departure/arrival list). -- Added some animations (fading between the different views). -- The info label now doesn't fade out on the right (setting word wrap to true, so that Plasma doesn't fade the text out on the right, manually setting the label's size fixed to it's size hint, because with QSizePolicy::Maximum and word wrap on and HTML formatted text in it, it's size gets too big). -- The default font size of the info label is now set to KGlobalSettings::smallestReadableFont(). -- More credit information is now shown as a tooltip of the info label (top-right corner of the applet). - -0.6.3 -New features: Filter Configurations; Recent journey searches; KCompletion for stop name completion. - -- Added a link to the used script file to the accessor info dialog. -- Recent journey searches are now stored and can be accessed via a tool button in the journey search view. -- Added "Filter Configurations". There can be multiple filter configurations, each containing another set of values for the filter settings (filtered vehicle types / lines / targets). Filter configurations can be configured in the config dialog. They can also be switched with the context menu. -- Journey results are now closed when the popup is shown/hidden. -- Improved completion using KCompletion manually (should write something like a KSyntaxCompletion). -- The link to the service provider home page in the top right edge of the applet does now work (by preventing plasma from filtering mouse press events to drag the applet, unregisterAsDragHandle() doesn't work for labels somehow..). The link now uses the url in the tag of the accessor info xml, instead of the , which is used as text for the link. -- Adjusted the layout of the applets title line, so that the info label on the right doesn't grow larger than needed. The title label that displays the current stop name doesn't get wrapped needlessly now. -- KCompletion for the stop field in the settings dialog (with stop weights). -- Removed the "journey to / from home stop" config option from the config dialog. This can be selected by using different keywords in the journey search line ("to" / "from"). -- Translation fixes. -- Some code cleanup / fixes. - -0.6.2 -- The route information item in journey mode is now automatically expanded (but the journey items still need to get expanded manually, ie. "Show Additional Information"). -- Added dates to the departure/arrival-columns if it's not today. It uses "tomorrow" or the weekday names if the departure/arrival is in <= 6 days. -- Now using KLocale::prettyFormatDuration() for duration formatting. -- Changed the journey search: The (no-plasma-widget) date/time input is removed, the date and/or time can now be given in the journey search line. It uses a special syntax after the stop name: "at", followed by the time and/or date or "in X min(s)/minute(s)". Before the "at" there can be "arr(iving)" or "dep(arting)" to search for journeys arriving/departing at the given time. You can also use "today", "tommorow" and "now". Before the stop name there can be either "from" or "to" or nothing (equals "to"), to get journeys from or to the given stop. There's also some autocompletion for this syntax and an explaining tooltip. It's also localizable. -- You can now press the Up/Down keys when the journey search line is focused to select another suggested stop. -- When journey searches aren't supported by the current service provider, the applet now shows an info message, when the user tries to search for journeys. Previously it just found nothing. - -0.6: -New features: Using associated application url (KDE 4.4). - -- The applet now sets the url that was used to request the latest timetable from the provider as associated application url (new in KDE 4.4 and only used when compiled with KDE 4.4). This means that you can just click the (new) button and your default browser opens the timetable that's currently shown in the applet. -- Added a new "vehicle type": Feet (by feet). This is used by some providers when searching for journeys. A new icon is also added. -- The sorting of stop suggestions from providers is now used as is. Previously the sorting was messed up (because the stops where only stored in a QHash as keys with it's IDs as values). - -0.5.5: -New features: Change the size of the applet (font/icon sizes), initial GHNS support, filter by line, better context menu, date and time input for journey search. - -- Added GHNS-support. A repository has been created at newstuff.kde.org (thanks to Josef Spillner). There is a new popup menu button in the config dialog. You can download (accessor info xmls for) new service providers or install them from a local file. Maybe I can add an upload-function, otherwise new entries can be sent to me, so that I can add them to the newstuff-repository. Unfortunatly the download doesn't work as expected for now and I doesn't know what's wrong.. -- Added binary translations for germany to the source archive. Will be installed with the applet. -- Added the possibility to filter by lines (in the same way as with directions, including context menu entries). -- Better layout for the context menu, filter entries are now grouped in two submenu. One to add filters and one to remove filters. -- Added a popup menu entry to switch to the journey search mode to make this feature more visible to users. Previously it was only accessible by clicking the icon on the top left of the applet. -- Added date and time input to the journey search. -- Added an option to the appearance config page to set a global size factor for the applet (using a slider). This option increases / decreases the font and icon sizes. This way you can now use the applet as a big display panel on the whole screen :) Or you can make the applet very very small if you want. I also added bigger versions of the vehicle type icons. -- Now using standard icon installation method (kde4_install_icons in cmake). You now have to take care of the installation prefix, so use "cmake -DCMAKE_INSTALL_PREFIX=`kde4-config --prefix` ." instead of just "cmake ." (see README). This way it also works with other prefixes than "/usr" and all icons are installed with one command. - -0.5.4: -Bugfix-release. - -Fixes: - - Added a signal-slot-connection in qt designer to enable the QTimeEdit for a custom first departure time, when the accompanying radio button is checked. - - Fixed strings overlapping the lower border in the departure / arrival list for some fonts. For example "DejaVu Sans" now works better with two lines per row. - - In the location combobox in the config dialog there is now an item at the bottom, showing all errornous accessor info XMLs. Previously it just showed a test error message with no connection to the data engine. - -0.5.3 -Bugfix-release. - -Fixes: - - Fixed tooltip text to use "arrival at" instead of "departure from" when an arrival board is shown. - - Fixed city selection when opening the config dialog. - - Added a stop validity check when the city changes (for correct LED display). - - Fixed the link text for the "data by"-link in the title of the applet (was wrong for service providers with a seperate city value). - - Added a handler for themeChanged signals, which sets all colors, fonts and svgs of the new plasma theme. - - Fixed the text color of items in the locations- and service providers-combobox, now using plasma theme- / global KDE-colors. - - Fixed the frame svg that is drawn as background for each departure / arrival and in the config comboboxes (location and service provider). It now only uses "widgets/frame" when it's available in the current theme (currently it's only the air theme I think). Otherwise it uses "widgets/tooltip". - -0.5.2 -New vehicle types: Metro, trolley bus, ferry, plane. -Simpler configuration. - -- Added vehicle type "ferry" with icon and enabled ferry-data on de_db. -- Added vehicle type "plane" with icon and added an accesor info xml for flightstats.com (with stop autocompletion, delay, journey news, ..) -- Added vehicle type "metro" and "trolleybus" (with icons) -- Added filters for the new vehicle types, unknown vehicle types can now also be hidden. -- Changed the config dialog again: Theres now a location field ("international", "germany", "austria", ...), to filter the available service providers. On first start the global KDE locale country is used. Some settings are moved to an advanced section in a tab view. -- Added kbflags-icons (copied from translatoid) for the new location field (somehow there are no default KDE flag icons?). - -Fixes: - - The city list is now sorted in the config dialog combo box for city input (only for service providers that need a seperate city input). - - Fixed an error with stop autocompletion. It showed always all downloaded stop names, that are stored for stop name to stop id conversion. Now it shows only the suggestions that has been downloaded for the last given stop name. So it's now more usable. - - The journey data source is now disconnected from the data engine when the user returns to the departure / arrival view. - -0.5.1 -Fixes: - - Fixed the tab order in the config dialogs. - -0.5 -- Added APIDOX documentation -- Changed "Filter out trams" to "Hide trams" (also for other vehicle types) -- Changed the layout of the configuration dialog. The time of the first departure can now be set either relative to the current time or to a custom time. In the "accessor info"-dialog there is now a link to the source XML file of the accessor. - -Fixes: - - Added missing icon files to the CMakeLists.txt of the "publictransport-icons"-directory. Train icons were missing (and "vehicletype-sbahn" was my old name for "vehicletype-train-interurban"). - - Fixed titel capitalization in the config dialogs (thanks to Panagiotis Papadopoulos) - -0.4.8 -- Added a journey search mode, where you can search for journeys from or to your "home stop". The accessors in the data engine can now have a new regExp for parsing journey sites. The db.de-accessor parses departures, arrivals, used types of vehicles, needed changes and pricing. The sbb.ch-accessor also supports this new feature (sbb.ch is very similiar to db.de). -- The journey search input has autocompletion. An autocompletion list is shown in the whole applet when typing a new target stop. -- Added an option to hide the header of the tree view or just the target / origin column. This can be useful to create a minimalistic design or if the applet has little space. -- Changed the design of the title, containing the stop name, last updated and a link to the service provider. New is an icon on the left. -- The new icon in the top left corner of the applet is clickable, to change between departure/arrival list and journey search / journey list. The icon also indicates the status of data receiving and gets painted disabled when there was an error. -- The predicted departure / arrival (with delay added) time is now shown in the departure / arrival column. It's tinted red, if it's delayed (green, if on schedule; default color, if no information is available). The original departure / arrival time is shown in the additional information. -- Changed the sub-items in the tree view to span the whole row. -- The font family can now be changed. -- Seperated all settings related code from the applet class. -- Added state flags to the applet, to make it easier to keep track of the applets current state (Valid data received? Data requested? What is currently shown in the applet (departure/arrival list, journey search, journey list)? Is the config dialog shown?). Methods are called when states get set or unset. - -Fixes: - - Fixed "resize to contents" of the columns by putting the html-formatted data into an extra data role and setting the items text to plain text. - - The "busy widget" is now shown when data was requested until an answer comes in. - -0.4.7 -- Added a "Show all vehicle types" context menu entry. It's only shown if there are filtered vehicle types. -- Added AUTHORS and INSTALL files, changed README files - -Fixes: - - "Applet needs config"-message isn't shown anymore when there are downloaded departures / arrivals and then a parsing error occured (most probably because of a lost network connection). - - The "popup-icon-information" about the next alarm is now also displayed if there is an inactive (already fired) alarm first. - - The model of the tree view is now updated without network connection (so remaining minutes get updated and old departures / arrivals are filtered out). - - The service provider link is now displayed again (wasn't in 0.4.6). - - Fixed a crash in geometryChanged() - -0.4.6 -- Added config options for "time of first departure" and "maximum number of departures". You may want to set the time of the first departure to the time you need to walk to the stop. -- Added a context menu for the treeview. Items for setting/removing alarms, adding/removing targets to/from the filter, hide by vehicle type, expand/collapse items. With this menu it's much easier to change the filter settings and you don't have to write the targets / origins exactly as they appear in the tree view. -- Added alarms, can be set with a new "set/remove alarm for departure" context menu. The alarm is shown using KNotification (couldn't get the dbus method 'scheduleMessage()' of kalarm to work, I'm just getting false as result). It's also possible to change the time when the alarm is fired in the config dialog (in minutes before the departure). -- The service provider combo box now also uses the delegate for html text that is used by the main tree view. For each service provider it's features are also displayed. -- Added sorting and group titles for countries to the service provider combo box -- Added Plasma::FrameSvg drawing to the html delegate. -- Added "What's This" entries to the config dialog. -- Added possibility to change between departures and arrivals (config options only shown if supported by service provider). -- Added icon in the tree view for departures / arrivals containing journey news. -- Further shortened the remaining time string. If the departure/arrival is "on schedule" the departure time painted in dark green. -- If an alarm is pending, the popup icon of the applet displays in how many minutes the next alarm will be fired -- The departure / arrival list now isn't cleared, when the document can't be downloaded or the downloaded document can't be parsed. Departures / arrivals in the past get filtered out. So you can set the maximum number of departures high to have departures displayed for a longer time without network connection. -- Changed "direction" to "target" / "origin". -- Code cleanups. - -Fixes: - - Sorting now includes delays. - - Delayed departures are now displayed until the predicted departure time (=departure+delay) has passed. - - Departure items can now be expanded/collapsed with a double click anywhere in a row. - - The moving buttons of the train type filter selector are now enabled if an item is selected. - -0.4.5 -- Parse and use stop ID when available instead of stop name (eliminates some ambiguities of stop names) -- Wrote a delegate to paint html-text in the treeview, added the possibility to change the lines per row, used code from the tasks plasmoid for drawing the text with fading out lines and text / icon shadow (theres a little problem with the fading out lines, I don't know how to get QTextDocument to make the last text line infinitly long, so that it can fade out. But I have some little workarounds) -- Delays are now colored red using the new html delegate -- The departure list is now updated instead of clearing and newly filling it each time. Old departures get removed, already existing ones get updated and new ones are added. This way expanded items stay expanded when updating. -- Improved the automatic column resizing -- Added an info dialog for the service providers, displaying their name and version, author, url, description and a feature list. -- Now using the favicons-dataengine to get favicons of the service providers when available -- Added other train icons - -- Fixes: - - Added recheck of the stop name when the service provider changes (for correct led) - - Wrong green led in config dialog - - The tooltip now shows the first not filtered departure (previously it showed filtered departures) - -0.4.4 -- Made strings for remaining minutes shorter -- Changed the header-resizing of the tree view. Columns "line" and "departure" stay as set by the user, "direction" will shrink/grow as needed. -- Placed a link to the service provider in the plasmoid -- Changed the UI for filtering vehicle types to use KActionSelector instead of many checkboxes - -- Fixes: - - Tree view is no longer editable. - -0.4.3 (19.06.2009) -- Added filtering possibilities for subways/trains -- Fixed filtering by line, so that trains with high numbers (e.g. "RE 28324") are not filtered. I restricted filtering by line to trams/buses/subways/interurban trains (max line number is 999). -- Added initial delay/"journey news" and platform parsing for db.de. You can see this info by double clicking on a row in the TreeView. The platform should be shown where available. The delays may show up correctly.. Need to test that (and need to find delays to test). At least it parses "on schedule"/"no info available" correctly. - -0.4.2 (18.06.2009) -- Added icons for vehicle types -- Added a CMakeList.txt in the icons folder in the sources archive. It will install the icons needed by the plasmoid. There is also a README. -- Added the possibility to only have one input field for accessors (not city and stop but one field containing both). For some accessors there is now only one field. -- Added a KLed to the config dialog that shines green when the entered stop is valid (so you will get a departure board) -- The data origin is now displayed in the plasmoid -- Added COPYING files containing the GPL license - -0.4.1 (17.06.2009) -- Fixed the text color in the TreeView to use Plasma::Theme::TextColor - -0.4 (17.06.2009) -- Now using a TreeView instead of an HTML-label. You can sort it as you want by clicking the headers and rearrange the columns by dragging the headers. -- When a stop name is ambigous the data engine parses the possible stop names. The applet uses these for autocompletion of the stop field in the config dialog. (Currently only for db.de) - -0.3.5 (16.06.2009) -- Readded filters (when creating the data engine I didn't port all functionality, and then forgot to do so.. until now) -- Improved color handling (now using plasma-theme-colors), no more "extra background-rects" - -0.3 (12.06.2009) -- The plasmoid now uses a newly created data engine. -- Added support for "Rhein-Neckar"-region (by Martin Gräßlin), bvg.de (Berlin), imhd.sk (Bratislava in Slovakia) - -0.2.2 (05.06.2009) -- Fixed an issue with the popup, the timetable was very small in the popup -- Fixed an issue with fahrplaner.de, I had some wrong characters in the URL - -0.2.1 (05.06.2009) -- Added support for vvs.de for Stuttgart and surroundings -- Improved the tooltip (now it shows the next departure) -- Made the applet draggable by clicking on the timetable (found registerAsDragHandle) -- The text is no longer drawn outside of the applet if it's too big -- Improved the look (v-align on top, transparent alternating row backgrounds) - -0.2 (04.06.2009) -- Wrote a new class "TimetableAccessor" which can be derived to add support for another site / server. -- Now I have two such accessors: One for "Niedersachen, Bremen" (through fahrplaner.de) and one for "Rhein-Main" (rmv.de). -- On rmv.de I found a Yahoo-Widget and could read it's source code. Great :) The data from the server is in XML format (which makes it smaller and easier to parse). - -0.1 (03.06.2009) -- Initial version, works for Niedersachsen/Bremen. diff --git a/applet/CMakeLists.txt b/applet/CMakeLists.txt deleted file mode 100644 index bd27476..0000000 --- a/applet/CMakeLists.txt +++ /dev/null @@ -1,75 +0,0 @@ -project( plasma-applet-publictransport ) - -# Find the required Libaries -find_package( KDE4 REQUIRED ) -include( KDE4Defaults ) -include( ../PublicTransportDefaults ) -add_definitions( ${QT_DEFINITIONS} ${KDE4_DEFINITIONS} ) - -include_directories( - ${CMAKE_SOURCE_DIR} - ${CMAKE_BINARY_DIR} - ${KDE4_INCLUDES} - ${PUBLICTRANSPORTHELPER_INCLUDES} -) - -# We add our source code here -set( publictransport_SRCS - publictransport.cpp - publictransport_p.cpp - titlewidget.cpp - popupicon.cpp - departureprocessor.cpp - departuremodel.cpp - departurepainter.cpp - journeysearchsuggestionwidget.cpp - journeysearchparser.cpp - journeysearchlineedit.cpp - journeysearchitem.cpp - journeysearchmodel.cpp - journeysearchlistview.cpp - overlaywidget.cpp - global.cpp - settings.cpp - settingsio.cpp - settingsui.cpp - datasourcetester.cpp - timetablewidget.cpp - routegraphicsitem.cpp - stopaction.cpp - colorgroups.cpp ) - -kde4_add_ui_files( publictransport_SRCS - publicTransportConfig.ui - publicTransportConfigAdvanced.ui - publicTransportAppearanceConfig.ui - publicTransportFilterConfig.ui - alarmConfig.ui ) - -# Now make sure all files get to the right place -kde4_add_plugin( plasma_applet_publictransport ${publictransport_SRCS} ) - -add_dependencies( plasma_applet_publictransport publictransporthelper ) - -target_link_libraries( plasma_applet_publictransport - ${KDE4_PLASMA_LIBS} - ${KDE4_KDEUI_LIBS} - ${KDE4_KIO_LIBS} - ${KDE4_KNEWSTUFF3_LIBS} - publictransporthelper # found in ../publictransporthelper -) - -install( TARGETS plasma_applet_publictransport - DESTINATION ${PLUGIN_INSTALL_DIR} ) - -install( FILES plasma-applet-publictransport.desktop - DESTINATION ${SERVICES_INSTALL_DIR} ) - -install( FILES vehicles.svg - DESTINATION ${DATA_INSTALL_DIR}/plasma_applet_publictransport ) - -add_subdirectory(departures) - -if ( BUILD_TESTS ) - add_subdirectory( tests ) -endif ( BUILD_TESTS ) diff --git a/applet/COPYING b/applet/COPYING deleted file mode 100644 index 54c5c69..0000000 --- a/applet/COPYING +++ /dev/null @@ -1,341 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - PublicTransport - This is a plasma applet that displays a departure/arrival board - for your stop. Plasma is part of KDE. - Copyright (C) 2010 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/applet/Messages.sh b/applet/Messages.sh deleted file mode 100644 index 5f0d6a3..0000000 --- a/applet/Messages.sh +++ /dev/null @@ -1,4 +0,0 @@ -#! /usr/bin/env bash -$EXTRACTRC *.ui >> rc.cpp -$XGETTEXT *.cpp *.h -o $podir/plasma_applet_publictransport.pot -rm -f rc.cpp diff --git a/applet/alarmConfig.ui b/applet/alarmConfig.ui deleted file mode 100644 index a99cf17..0000000 --- a/applet/alarmConfig.ui +++ /dev/null @@ -1,242 +0,0 @@ - - - alarmConfig - - - - 0 - 0 - 479 - 310 - - - - - - - &Alarm: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - alarms - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - A list of available filter configurations - - - <para>A list of all available filter configurations. Filter configurations can be added/removed/renamed using the buttons on the right of this combobox. </para> -<para>Each filter configuration consists of a name, a list of stops using the filter configuration, a filter action and a list of filters. Each filter contains a list of constraints.</para> - - - - - - - Add a new filter configuration - - - ... - - - - - - - Delete the selected filter configuration - - - ... - - - - - - - Rename the selected filter configuration - - - ... - - - - - - - - - &Used With: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - affectedStops - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - The stops that use the selected filter configuration - - - - - - - Alarm &Type: - - - alarmType - - - - - - - - Remove After First Match - - - - - Apply to New Departures - - - - - - - - - 0 - 222 - - - - Set &Alarm For Matching Departures - - - - - - true - - - - 0 - 0 - - - - QFrame::NoFrame - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 457 - 175 - - - - - QFormLayout::ExpandingFieldsGrow - - - QFormLayout::WrapLongRows - - - 0 - - - - - - 0 - 0 - - - - 1 - - - and - - - - - - - - - - - - - - - KComboBox - QComboBox -
kcombobox.h
-
- - CheckCombobox - QComboBox -
checkcombobox.h
-
- - PublicTransport::FilterWidget - QWidget -
filterwidget.h
- 1 -
-
- - -
diff --git a/applet/applet-doc.h b/applet/applet-doc.h deleted file mode 100644 index 09bd3c5..0000000 --- a/applet/applet-doc.h +++ /dev/null @@ -1,328 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -/** @mainpage Public Transport Applet -@section intro_applet_sec Introduction -This applet shows a departure / arrival board for public transport, trains, ferries and planes. -Journeys can also be searched for. It uses the public transport data engine and has some advanced -configuration possibilities like filters, alarms and a flexible appearance. - -@see models for more information about how the applet interacts with the PublicTransport data - engine and how the data is stored in models. -@see filterSystem for more information about how the filters work. - -@section install_applet_sec Installation -To install this applet type the following commands:
-\> cd /path-to-extracted-applet-sources/build
-\> cmake -DCMAKE_INSTALL_PREFIX=`kde4-config --prefix` ..
-\> make
-\> make install
-
-After installation do the following to use the applet in your plasma desktop: -Restart plasma to load the applet:
-\> kquitapp plasma-desktop
-\> plasma-desktop
-
-or test it with:
-\> plasmoidviewer publictransport
-
-You might need to run kbuildsycoca4 in order to get the .desktop file recognized. -*/ - -/** -* @defgroup models Models -@brief Data gets retrieved from data engines, processed in a thread and stored in models. - -The models used for storing public transport data are: @ref DepartureModel for departures/arrivals -and @ref JourneyModel for journeys. They are both based on @ref PublicTransportModel. - -The applet uses five data engines: publictransport, geolocation, -openstreetmap (to get stops near the user) and favicons (to get favicons from the -service providers). -The publictransport data engine expects data source names in a specific format, which is explained -in detail in it's documentation. Here are some examples of what source names the applet generates -(based on the settings): -@par -"Departures de_db|stop=Leipzig|timeOffset=5" for departures from the service provider with -the ID "de_db", a stop named "Leipzig" and an offset (from now) in minutes for the first departure -of 5. -@par -"Journeys de_db|originStop=Leipzig|targetStop=Bremen" for journeys from the service -provider with the ID "de_db", an origin stop named "Leipzig" and a target stop named "Bremen". - -@note The service provider ID "de_db" can be left away to use a default service provider - for the users country (from KDE's global settings). - -The format of the data structure returned from the data engine is again explained in detail in the -data engine's documentation (@ref pageUsage). It arrives in the slot -@ref PublicTransport::dataUpdated. From there one of the following functions is called, based on -the data returned by the data engine: -@par -@ref PublicTransport::handleDataError, if the "error" key of the data structure is true, ie. there -was an error while running the query in the data engine (eg. server not reachable or an error in -the service provider while trying to parse the document from the server), -@par -@ref PublicTransport::processStopSuggestions, if the "stops" key of the data structure contains -data, which can also happen if eg. "Departures" were queried for, but the stop name is ambigous, -@par -@ref DepartureProcessor::processJourneys, @ref DepartureProcessor::processDepartures if there's -a "journeys", "departures" or "arrivals" key respectively. A new -job is added to the background thread. The thread then reads the data and creates data structures -of type @ref DepartureInfo for departures/arrivals or @ref JourneyInfo for journeys. It also checks -for alarms and applies filters. That way complex filters and or many alarms applied to many -departures/arrivals won't freeze the applet. - -Before beginning a new departure/arrival/journey job the thread emits a signal that is connected -to @ref PublicTransport::beginDepartureProcessing / @ref PublicTransport::beginJourneyProcessing. -Once a chunk of departures/arrivals is ready @ref PublicTransport::departuresProcessed gets called -through a signal/slot connection. In that function the processed departures are cached based on -the source name (but with date and time values stripped) and then the departure/arrival model gets -filled with them in @ref PublicTransport::fillModel. If journeys are ready -@ref PublicTransport::journeysProcessed gets called by the thread, which calls -@ref PublicTransport::fillModelJourney. If filter settings are changed the thread is used again to -run filters on the current data. Once the filter job is ready it calls -@ref PublicTransport::departuresFiltered. - -The @ref PublicTransport::fillModel and @ref PublicTransport::fillModelJourney functions add, -update and/or remove items in the models. Both the departure/arrival and the journey model have -functions called @ref DepartureModel::indexFromInfo / @ref JourneyModel::indexFromInfo, which use -a hash generated from the data items (@ref DepartureInfo / @ref JourneyInfo) to quickly check, if -there already is an item in the model for a given data item. Hashes are generated automatically in -the constructors and can be retrieved using @ref PublicTransportInfo::hash. Two data items don't -have to be exactly equal to generade an equal hash. That is important to also find -departures/arrivals/journeys which data has changed since the last update, eg. departures with a -changed delay. - -@see filterSystem -*/ - -/** -* @defgroup filterSystem Filter System -@brief The applet has the possibility to filter departures/arrivals based on various constraints. - -Those constraints are combined to filters using logical AND. Filters on the other hand can be -combined to filter lists using logical OR. The filter system is also used to match alarms. - -The filtering is performed in classes described under @ref filter_classes_sec, while those filters -can be setup using widgets described under @ref filter_widgets_sec. - -@n -@section filter_classes_sec Classes That Perform Filtering -The lowest class in the hierarchy of filter classes is @ref Constraint, which describes a single -constraint which should match the departures/arrivals to be shown/hidden. One step higher -in the hierarchy comes the class @ref Filter, which is a list of constraints (combined using -logical AND). Another step higher comes @ref FilterList, which is a list of filters (combined -using logical OR). A @ref FilterList is wrapped by an object of type @ref FilterSettings together -with the @ref FilterAction (show or hide matching departures/arrivals), name and affected stops -for that filter configuration. - -Each @ref Constraint has a @ref FilterType, ie. what to filter with this constraint. For example -a constraint can filter departures/arrivals by the used vehicle type using @ref FilterByVehicleType. -Each @ref Constraint also has a @ref FilterVariant, eg. equals / doesn't equal. The used -@ref FilterVariant affects the way a constraint matches specific departures/arrivals. Last but not -least each @ref Constraint has a value. -So for example a constraint can be assembled like this: Filter by vehicle type, match -departures/arrivals that have the same value as stored in the constraint. - -Filters can be serialized using toData() / fromData() methods. -Filter widgets described in the next section can be easily created from these filter classes. - -@n -@section filter_widgets_sec Widgets for Editing Filters -There are accompanying QWidget based classes for the filter classes from the previous section: -@par -@ref ConstraintWidget for @ref Constraint, @ref FilterWidget for @ref Filter and -@ref FilterListWidget for @ref FilterList. Filter widgets can be constructed from the filter -classes of the previous section. - -For each constraint data type there is a separate constraint widget class: -@par -@ref ConstraintListWidget to select values of a given list of values (eg. a list of vehicle types), -@par -@ref ConstraintStringWidget to enter a string for matching (eg. matching intermediate stops), -@par -@ref ConstraintIntWidget to enter an integer for matching and -@par -@ref ConstraintTimeWidget to enter a time for matching (eg. a departure time, used for alarms). - -@ref FilterWidget uses @ref AbstractDynamicLabeledWidgetContainer as base class to allow dynamic -adding / removing of constraints. @ref FilterListWidget uses @ref AbstractDynamicWidgetContainer -to do the same with filters. Those base classes automatically add buttons to let the user -add / remove widgets. They are pretty flexible and will maybe end up in a library later for -reuse in other projects (like the publictransport runner). -*/ - -/** @page pageClassDiagram Class Diagram -@dot -digraph publicTransportDataEngine { - ratio="compress"; - size="10,100"; - concentrate="true"; - // rankdir="LR"; - clusterrank=local; - - node [ - shape=record - fontname=Helvetica, fontsize=10 - style=filled - fillcolor="#eeeeee" - ]; - - applet [ - fillcolor="#ffdddd" - label="{PublicTransportApplet|Shows a departure / arrival list or a list of journeys.\l|+ dataUpdated( QString, Data ) : void\l# createTooltip() : void\l# updatePopupIcon() : void\l# processData( Data ) : void\l}" - URL="\ref PublicTransport" - ]; - - subgraph clusterWidgets { - label="Widgets"; - style="rounded, filled"; - color="#cceeee"; - node [ fillcolor="#ccffff" ]; - - timetableWidget [ - label="{TimetableWidget|Represents the departure/arrial board.\l}" - URL="\ref TimetableWidget" - ]; - - departureGraphicsItem [ - label="{DepartureGraphicsItem|Represents one item in the departure/arrial board.\l}" - URL="\ref DepartureGraphicsItem" - ]; - - journeyTimetableWidget [ - label="{JourneyTimetableWidget|Represents the journey board.\l}" - URL="\ref JourneyTimetableWidget" - ]; - - journeyGraphicsItem [ - label="{JourneyGraphicsItem|Represents one item in the journey board.\l}" - URL="\ref JourneyGraphicsItem" - ]; - - titleWidget [ - label="{TitleWidget|Represents the title of the applet.\l}" - URL="\ref TitleWidget" - ]; - - routeGraphicsItem [ - label="{RouteGraphicsItem|Represents the route item in a departure/arrival item.\l}" - URL="\ref RouteGraphicsItem" - ]; - - journeyRouteGraphicsItem [ - label="{JourneyRouteGraphicsItem|Represents the route item in a journey item.\l}" - URL="\ref JourneyRouteGraphicsItem" - ]; - }; - - subgraph thread { - label="Background Thread"; - style="rounded, filled"; - color="#ffcccc"; - node [ fillcolor="#ffdfdf" ]; - - departureProcessor [ - label="{DepartureProcessor|Processes data from the data engine and applies filters/alarms.\l}" - URL="\ref DepartureProcessor" - ]; - }; - - subgraph clusterSettings { - label="Settings"; - style="rounded, filled"; - color="#ccccff"; - node [ fillcolor="#dfdfff" ]; - - settings [ - label="{PublicTransportSettings|Manages the settings of the applet.\l}" - URL="\ref PublicTransportSettings" - ]; - - dataSourceTester [ - label="{DataSourceTester|Tests a departure / arrival or journey data source \lat the public transport data engine.\l|+ setTestSource( QString ) : void\l+ testResult( TestResult, QVariant ) : void [signal] }" - URL="\ref DataSourceTester" - ]; - }; - - subgraph clusterModels { - label="Models"; - style="rounded, filled"; - color="#ccffcc"; - node [ fillcolor="#dfffdf" ]; - rank="sink"; - - departureModel [ - label="{DepartureModel|Stores information about a departures / arrivals.\l}" - URL="\ref DepartureModel" - ]; - - journeyModel [ - label="{JourneyModel|Stores information about a journeys.\l}" - URL="\ref JourneyModel" - ]; - - departureItem [ - label="{DepartureItem|Wraps DepartureInfo objects for DepartureModel.\l}" - URL="\ref DepartureItem" - ]; - - journeyItem [ - label="{JourneyItem|Wraps JourneyInfo objects for JourneyModel.\l}" - URL="\ref JourneyItem" - ]; - - departureInfo [ - label="{DepartureInfo|Stores information about a single departure / arrival.\l}" - URL="\ref DepartureInfo" - ]; - - journeyInfo [ - label="{JourneyInfo|Stores information about a single journey.\l}" - URL="\ref JourneyInfo" - ]; - }; - - edge [ dir=back, arrowhead="normal", arrowtail="none", style="dashed", fontcolor="darkgray", - taillabel="", headlabel="0..*" ]; - timetableWidget -> departureGraphicsItem [ label="uses" ]; - journeyTimetableWidget -> journeyGraphicsItem [ label="uses" ]; - departureModel -> departureItem [ label="uses" ]; - journeyModel -> journeyItem [ label="uses" ]; - - edge [ dir=forward, arrowhead="none", arrowtail="normal", style="dashed", fontcolor="darkgray", - taillabel="1", headlabel="" ]; - departureProcessor -> applet [ label="m_departureProcessor" ]; - settings -> applet [ label="m_settings" ]; - dataSourceTester -> settings [ label="m_dataSourceTester" ]; - - edge [ dir=back, arrowhead="normal", arrowtail="none", style="dashed", fontcolor="darkgray", - taillabel="", headlabel="1" ]; - applet -> timetableWidget [ label="m_timetable" ]; - applet -> journeyTimetableWidget [ label="m_journeyTimetable" ]; - applet -> titleWidget [ label="m_titleWidget" ]; - applet -> departureModel [ label="m_model" ]; - applet -> journeyModel [ label="m_modelJourneys" ]; - departureItem -> departureInfo [ label="uses" ]; - journeyItem -> journeyInfo [ label="uses" ]; - departureGraphicsItem -> routeGraphicsItem [ label="uses" ]; - journeyGraphicsItem -> journeyRouteGraphicsItem [ label="uses" ]; -} -@enddot -*/ diff --git a/applet/colorgroups.cpp b/applet/colorgroups.cpp deleted file mode 100644 index be527e4..0000000 --- a/applet/colorgroups.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "colorgroups.h" - -// Qt includes -#include - -struct TargetCounter { - QString target; - QString displayText; - int usedCount; - - TargetCounter() {}; - TargetCounter( const QString &target, const QString &displayText, int occurenceCount = 1 ) - : target(target), displayText(displayText), usedCount(occurenceCount) {}; - TargetCounter &operator ++() { ++usedCount; return *this; }; -}; - -class TargetCounterGreaterThan { -public: - inline TargetCounterGreaterThan() {}; - inline bool operator()( const TargetCounter &l, const TargetCounter &r ) const { - return l.usedCount > r.usedCount; - }; -}; - -ColorGroupSettingsList ColorGroups::generateColorGroupSettingsFrom( - const QList< DepartureInfo >& infoList, DepartureArrivalListType departureArrivalListType ) -{ - Q_UNUSED( departureArrivalListType ); - - // Maximal number of groups - const int maxGroupCount = 10; - - const int opacity = 128; - const int colors = 82; - static const QColor oxygenColors[colors] = { - QColor(56, 37, 9, opacity), //wood brown6 -// QColor(87, 64, 30, opacity), // wood brown5 - QColor(117, 81, 26, opacity), // wood brown4 - QColor(143, 107, 50, opacity), // wood brown3 - QColor(179, 146, 93, opacity), // wood brown2 -// QColor(222, 188, 133, opacity), // wood brown1 - QColor(156, 15, 15, opacity), // brick red6 -// QColor(191, 3, 3, opacity), // brick red5 - QColor(226, 8, 0, opacity), // brick red4 - QColor(232, 87, 82, opacity), // brick red3 - QColor(240, 134, 130, opacity), // brick red2 -// QColor(249, 204, 202, opacity), // brick red1 - QColor(156, 15, 86, opacity), // raspberry pink6 -// QColor(191, 3, 97, opacity), // raspberry pink5 - QColor(226, 0, 113, opacity), // raspberry pink4 - QColor(232, 82, 144, opacity), // raspberry pink3 - QColor(240, 130, 176, opacity), // raspberry pink2 -// QColor(249, 202, 222, opacity), // raspberry pink1 - QColor(106, 0, 86, opacity), // burgundy purple6 -// QColor(133, 2, 108, opacity), // burgundy purple5 - QColor(160, 39, 134, opacity), // burgundy purple4 - QColor(177, 79, 154, opacity), // burgundy purple3 - QColor(193, 115, 176, opacity), // burgundy purple2 -// QColor(232, 183, 215, opacity), // burgundy purple1 - QColor(29, 10, 85, opacity), // grape violet6 -// QColor(52, 23, 110, opacity), // grape violet5 - QColor(70, 40, 134, opacity), // grape violet4 - QColor(100, 74, 155, opacity), // grape violet3 - QColor(142, 121, 165, opacity), // grape violet2 -// QColor(195, 180, 218, opacity), // grape violet1 - QColor(0, 49, 110, opacity), // skyblue6 -// QColor(0, 67, 138, opacity), // skyblue5 - QColor(0, 87, 174, opacity), // skyblue4 - QColor(44, 114, 199, opacity), // skyblue3 - QColor(97, 147, 207, opacity), // skyblue2 -// QColor(164, 192, 228, opacity), // skyblue1 - QColor(0, 72, 77, opacity), // sea blue6 -// QColor(0, 96, 102, opacity), // sea blue5 - QColor(0, 120, 128, opacity), // sea blue4 - QColor(0, 167, 179, opacity), // sea blue3 - QColor(0, 196, 204, opacity), // sea blue2 -// QColor(168, 221, 224, opacity), // sea blue1 - QColor(0, 88, 63, opacity), // emerald green6 -// QColor(0, 115, 77, opacity), // emerald green5 - QColor(0, 153, 102, opacity), // emerald green4 - QColor(0, 179, 119, opacity), // emerald green3 - QColor(0, 204, 136, opacity), // emerald green2 -// QColor(153, 220, 198, opacity), // emerald green1 - QColor(0, 110, 41, opacity), // forest green6 -// QColor(0, 137, 44, opacity), // forest green5 - QColor(55, 164, 44, opacity), // forest green4 - QColor(119, 183, 83, opacity), // forest green3 - QColor(177, 210, 143, opacity), // forest green2 -// QColor(216, 232, 194, opacity), // forest green1 - QColor(227, 173, 0, opacity), // sun yellow6 -// QColor(243, 195, 0, opacity), // sun yellow5 - QColor(255, 221, 0, opacity), // sun yellow4 - QColor(255, 235, 85, opacity), // sun yellow3 - QColor(255, 242, 153, opacity), // sun yellow2 -// QColor(255, 246, 200, opacity), // sun yellow1 - QColor(172, 67, 17, opacity), // hot orange6 -// QColor(207, 73, 19, opacity), // hot orange5 - QColor(235, 115, 49, opacity), // hot orange4 - QColor(242, 155, 104, opacity), // hot orange3 - QColor(242, 187, 136, opacity), // hot orange2 -// QColor(255, 217, 176, opacity), // hot orange1 - QColor(46, 52, 54, opacity), // aluminum gray6 -// QColor(85, 87, 83, opacity), // aluminum gray5 - QColor(136, 138, 133, opacity), // aluminum gray4 - QColor(186, 189, 182, opacity), // aluminum gray3 - QColor(211, 215, 207, opacity), // aluminum gray2 -// QColor(238, 238, 236, opacity), // aluminum gray1 - QColor(77, 38, 0, opacity), // brown orange6 -// QColor(128, 63, 0, opacity), // brown orange5 - QColor(191, 94, 0, opacity), // brown orange4 - QColor(255, 126, 0, opacity), // brown orange3 - QColor(255, 191, 128, opacity), // brown orange2 -// QColor(255, 223, 191, opacity), // brown orange1 - QColor(89, 0, 0, opacity), // red6 -// QColor(140, 0, 0, opacity), // red5 - QColor(191, 0, 0, opacity), // red4 - QColor(255, 0, 0, opacity), // red3 - QColor(255, 128, 128, opacity), // red2 -// QColor(255, 191, 191, opacity), // red1 - QColor(115, 0, 85, opacity), // pink6 -// QColor(163, 0, 123, opacity), // pink5 - QColor(204, 0, 154, opacity), // pink4 - QColor(255, 0, 191, opacity), // pink3 - QColor(255, 128, 223, opacity), // pink2 -// QColor(255, 191, 240, opacity), // pink1 - QColor(44, 0, 89, opacity), // purple6 -// QColor(64, 0, 128, opacity), // purple5 - QColor(90, 0, 179, opacity), // purple4 - QColor(128, 0, 255, opacity), // purple3 - QColor(192, 128, 255, opacity), // purple2 -// QColor(223, 191, 255, opacity), // purple1 - QColor(0, 0, 128, opacity), // blue6 -// QColor(0, 0, 191, opacity), // blue5 - QColor(0, 0, 255, opacity), // blue4 - QColor(0, 102, 255, opacity), // blue3 - QColor(128, 179, 255, opacity), // blue2 -// QColor(191, 217, 255, opacity), // blue1 - QColor(0, 77, 0, opacity), // green6 -// QColor(0, 140, 0, opacity), // green5 - QColor(0, 191, 0, opacity), // green4 - QColor(0, 255, 0, opacity), // green3 - QColor(128, 255, 128, opacity), // green2 -// QColor(191, 255, 191, opacity), // green1 - QColor(99, 128, 0, opacity), // lime6 -// QColor(139, 179, 0, opacity), // lime5 - QColor(191, 245, 0, opacity), // lime4 - QColor(229, 255, 0, opacity), // lime3 - QColor(240, 255, 128, opacity), // lime2 -// QColor(248, 255, 191, opacity), // lime1 - QColor(255, 170, 0, opacity), // yellow6 -// QColor(255, 191, 0, opacity), // yellow5 - QColor(255, 213, 0, opacity), // yellow4 - QColor(255, 255, 0, opacity), // yellow3 - QColor(255, 255, 153, opacity), // yellow2 -// QColor(255, 255, 191, opacity), // yellow1 - QColor(50, 50, 50, opacity), // gray6 -// QColor(85, 85, 85, opacity) // gray5 - QColor(136, 136, 136, opacity), // gray4 -// QColor(187, 187, 187, opacity), // gray3 -// QColor(221, 221, 221, opacity), // gray2 -// QColor(238, 238, 238, opacity) // gray1 - }; - - // Count target usages - QHash< QString, TargetCounter > targetUsedInformation; - foreach ( const DepartureInfo &info, infoList ) { - const QString target = info.target(); - const QString displayText = info.targetShortened(); - - // Check if the target was already counted - if ( targetUsedInformation.contains(target) ) { - // Increment the used count of the target by one - ++targetUsedInformation[target]; - } else { - // Add new target with count 1 - targetUsedInformation.insert( target, TargetCounter(target, displayText) ); - } - } - - // Sort list of targets by used count - QList< TargetCounter > targetCount = targetUsedInformation.values(); - qStableSort( targetCount.begin(), targetCount.end(), TargetCounterGreaterThan() ); - - // Create the color groups - ColorGroupSettingsList colorGroups; - for ( int i = 0; i < maxGroupCount && i < targetCount.count(); ++i ) { - const TargetCounter &routeCount = targetCount[i]; - QString hashString = routeCount.target; - while ( hashString.length() < 3 ) { - hashString += 'z'; - } - const int color = qHash(hashString) % colors; - - // Create filter for the new color group - Filter groupFilter; - groupFilter << Constraint( FilterByTarget, FilterEquals, routeCount.target ); - - // Create the new color group - ColorGroupSettings group; - group.filters << groupFilter; - group.color = oxygenColors[color]; - group.target = routeCount.target; - group.displayText = routeCount.displayText; - - colorGroups << group; - } - - return colorGroups; -} diff --git a/applet/colorgroups.h b/applet/colorgroups.h deleted file mode 100644 index 1577da3..0000000 --- a/applet/colorgroups.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file - * @brief This file contains classes for color groups used by the applet. - * @author Friedrich Pülz */ - -#ifndef COLORGROUPS_HEADER -#define COLORGROUPS_HEADER - -// Own includes -#include "settings.h" - -// libpublictransporthelper includes -#include - -/** - * @brief This class contains a static method to generate color groups from departures. - **/ -class ColorGroups { -public: - /** - * @brief Generates a list of color group settings from the given departure @p infoList. - * - * The given departure @p infoList get grouped by direction. Each group gets a color assigned. - **/ - static ColorGroupSettingsList generateColorGroupSettingsFrom( - const QList< DepartureInfo >& infoList, - DepartureArrivalListType departureArrivalListType ); -}; - -#endif // Multiple inclusion guard diff --git a/applet/datasourcetester.cpp b/applet/datasourcetester.cpp deleted file mode 100644 index f1b6b90..0000000 --- a/applet/datasourcetester.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#include "datasourcetester.h" - - -void DataSourceTester::connectTestSource() -{ - if ( !m_testSource.isEmpty() ) { - m_publicTransportEngine->connectSource( m_testSource, this ); - } -} - -void DataSourceTester::disconnectTestSource() -{ - if ( !m_testSource.isEmpty() ) { - m_publicTransportEngine->disconnectSource( m_testSource, this ); - m_testSource = ""; - } -} - -void DataSourceTester::setTestSource( const QString& sourceName ) -{ - disconnectTestSource(); - - m_testSource = sourceName; - connectTestSource(); -} - -QString DataSourceTester::stopToStopID( const QString& stopName ) -{ - return m_mapStopToStopID.value( stopName, "" ).toString(); -} - -void DataSourceTester::clearStopToStopIdMap() -{ - m_mapStopToStopID.clear(); -} - -void DataSourceTester::dataUpdated( const QString& sourceName, const Plasma::DataEngine::Data& data ) -{ - Q_UNUSED( sourceName ); - if ( data.isEmpty() ) { - return; - } - disconnectTestSource(); - - // Check for errors from the data engine - if ( data.value( "error" ).toBool() ) { - emit testResult( Error, i18nc( "@info/plain", "The stop name is invalid." ), QVariant(), QVariant() ); - } else { - // Check if we got a possible stop list or a journey list - if ( data.contains("stops") ) { - processTestSourcePossibleStopList( data ); - } else { - // List of journeys received - disconnectTestSource(); - emit testResult( JourneyListReceived, QVariant(), QVariant(), QVariant() ); - } - } -} - -void DataSourceTester::processTestSourcePossibleStopList( const Plasma::DataEngine::Data& data ) -{ - disconnectTestSource(); - - QStringList stopNames; - QVariantHash stopToStopID; - QVariantHash stopToStopWeight; - QVariantList stops = data["stops"].toList(); - foreach ( const QVariant &stopData, stops ) { - QVariantHash stop = stopData.toHash(); - QString sStopName = stop["StopName"].toString(); - QString sStopID = stop["StopID"].toString(); - int stopWeight = stop["StopWeight"].toInt(); - stopNames.append( sStopName ); - stopToStopID.insert( sStopName, sStopID ); - stopToStopWeight.insert( sStopName, stopWeight ); - - m_mapStopToStopID.insert( sStopName, sStopID ); - } - emit testResult( PossibleStopsReceived, stopNames, stopToStopID, stopToStopWeight ); -} diff --git a/applet/datasourcetester.h b/applet/datasourcetester.h deleted file mode 100644 index aaa2bb5..0000000 --- a/applet/datasourcetester.h +++ /dev/null @@ -1,93 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#ifndef DATASOURCETESTER_HEADER -#define DATASOURCETESTER_HEADER - -/** @file - * @brief This file contains the DataSourceTester class. - * @author Friedrich Pülz */ - -#include -#include - -/** @class DataSourceTester - * @brief Tests data sources with the "publictransport"-data engine. - * - * The source with a given source name can be tested. The signal @a testResult is emitted when - * the test is complete. - **/ -class DataSourceTester : public QObject { - Q_OBJECT - -public: - /** Results of a data source test. */ - enum TestResult { - Error, /**< The used data source name is erroneous or the data couldn't be parsed correctly. - * TODO: It's also possible, that there just weren't any departures / arrivals. - * In such a case JourneyListReceived should be used (or a new EmptyJourneyListReceived?). */ - JourneyListReceived, /**< The tested data source name gets a list - * of departures / arrivals or journeys. */ - PossibleStopsReceived /**< The tested data source name gets a list of stop suggestions. - * If you requested a journey list this means that the stop name is ambiguous. - * You can try to use stop IDs, if the ambiguity can't be removed. */ - }; - - DataSourceTester( const QString &testSource, Plasma::DataEngine *publicTransportEngine, - QObject* parent = 0 ) - : QObject(parent), m_testSource(testSource), - m_publicTransportEngine(publicTransportEngine) { - }; - - ~DataSourceTester() { - disconnectTestSource(); - } - - void processTestSourcePossibleStopList( const Plasma::DataEngine::Data& data ); - - /** Source name to be tested. */ - QString testSource() { return m_testSource; }; - - /** Sets the source name to be tested and connects it to the data engine. */ - void setTestSource( const QString &sourceName ); - - QString stopToStopID( const QString &stopName ); - void clearStopToStopIdMap(); - -signals: - void testResult( DataSourceTester::TestResult testResult, - const QVariant &data, const QVariant &data2, const QVariant &data3 ); - -public slots: - void dataUpdated( const QString &sourceName, const Plasma::DataEngine::Data &data ); - -private: - /** Disconnects the test data source. */ - void disconnectTestSource(); - - /** Connects the test data source. */ - void connectTestSource(); - - QString m_testSource; /**< Source name for testing configurations. */ - QHash< QString, QVariant > m_mapStopToStopID; /**< A hash with stop names as keys - * and the corresponding stop IDs as values. */ - Plasma::DataEngine *m_publicTransportEngine; -}; - -#endif // DATASOURCETESTER_HEADER diff --git a/applet/departuremodel.cpp b/applet/departuremodel.cpp deleted file mode 100644 index 3edbb08..0000000 --- a/applet/departuremodel.cpp +++ /dev/null @@ -1,2339 +0,0 @@ -/* - * Copyright 2013 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "departuremodel.h" -#include "settings.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -// Used to sort departures in the model -class DepartureModelLessThan -{ -public: - // Compares departures by column. - inline DepartureModelLessThan( Columns column = ColumnDeparture ) { - this->column = column; - }; - - inline bool operator()( const QPair &l, - const QPair &r ) const { - return operator()( l.first->departureInfo(), r.first->departureInfo() ); - }; - - inline bool operator()( const DepartureInfo* l, const DepartureInfo* r ) const { - switch ( column ) { - case ColumnDeparture: - return l->predictedDeparture() < r->predictedDeparture(); - case ColumnTarget: - return l->target() < r->target(); - case ColumnLineString: - if ( l->lineNumber() < r->lineNumber() ) { - return true; - } else { - return l->lineString().localeAwareCompare( r->lineString() ) < 0; - } - default: - kDebug() << "Can't sort unknown column" << column; - return false; - } - }; - Columns column; -}; - -// Used to sort departures in the model -class DepartureModelGreaterThan -{ -public: - // Compares departures by column. - inline DepartureModelGreaterThan( Columns column = ColumnDeparture ) { - this->column = column; - }; - - inline bool operator()( const QPair &l, - const QPair &r ) const { - return operator()( l.first->departureInfo(), r.first->departureInfo() ); - }; - - inline bool operator()( const DepartureInfo* l, const DepartureInfo* r ) const { - switch ( column ) { - case ColumnDeparture: - return l->predictedDeparture() > r->predictedDeparture(); - case ColumnTarget: - return l->target() > r->target(); - case ColumnLineString: - if ( l->lineNumber() > r->lineNumber() ) { - return true; - } else { - return l->lineString().localeAwareCompare( r->lineString() ) > 0; - } - default: - kDebug() << "Can't sort unknown column" << column; - return false; - } - }; - Columns column; -}; - -// Used to sort journeys in the model -class JourneyModelLessThan -{ -public: - // Compares journeys by column. - inline JourneyModelLessThan( Columns column = ColumnDeparture ) { - this->column = column; - }; - - inline bool operator()( const QPair &l, - const QPair &r ) const { - return operator()( l.first->journeyInfo(), r.first->journeyInfo() ); - }; - - inline bool operator()( const JourneyInfo* l, const JourneyInfo* r ) const { - switch ( column ) { - case ColumnDeparture: - return l->departure() < r->departure(); - case ColumnArrival: - return l->arrival() < r->arrival(); - case ColumnTarget: - return l->duration() < r->duration(); - case ColumnLineString: - if ( l->vehicleTypes().count() < r->vehicleTypes().count() ) { - return true; - } - default: - kDebug() << "Can't sort unknown column" << column; - return false; - } - }; - Columns column; -}; - -// Used to sort journeys in the model -class JourneyModelGreaterThan -{ -public: - // Compares journeys by column. - inline JourneyModelGreaterThan( Columns column = ColumnDeparture ) { - this->column = column; - }; - - inline bool operator()( const QPair &l, - const QPair &r ) const { - return operator()( l.first->journeyInfo(), r.first->journeyInfo() ); - }; - - inline bool operator()( const JourneyInfo* l, const JourneyInfo* r ) const { - switch ( column ) { - case ColumnDeparture: - return l->departure() > r->departure(); - case ColumnArrival: - return l->arrival() > r->arrival(); - case ColumnTarget: - return l->duration() > r->duration(); - case ColumnLineString: - if ( l->vehicleTypes().count() > r->vehicleTypes().count() ) { - return true; - } - default: - kDebug() << "Can't sort unknown column" << column; - return false; - } - }; - Columns column; -}; - -ItemBase::ItemBase( const Info *info ) : m_parent( 0 ), m_model( 0 ), m_info( info ) -{ - Q_ASSERT_X( info, "DepartureModelItemBase::DepartureModelItemBase", - "The pointer to the Info object must be given." ); -} - -ItemBase::~ItemBase() -{ - // Cleanup - qDeleteAll( m_children ); -} - -void ItemBase::setModel( PublicTransportModel *model ) -{ - // Set the given model in this item and all it's children - m_model = model; - foreach( ChildItem *child, m_children ) { - child->setModel( model ); - } -} - -ItemBase* ItemBase::topLevelParent() const -{ - // Use the parent item as long as another parent is available to get the top level parent item - ItemBase *p = const_cast< ItemBase* >( this ); - while ( p->parent() ) { - p = p->parent(); - } - return p; -} - -QModelIndex ItemBase::index() -{ - // The index of this item can only be retrieved if associated with a model - if ( m_model ) { - return m_model->index( this ); - } else { - return QModelIndex(); - } -} - -ChildItem* ItemBase::childByType( ItemType itemType ) const -{ - // Search through all children for an item of the given type - foreach( ChildItem *child, m_children ) { - if ( child->type() == itemType ) { - // Found a child item of the given type - return child; - } - } - - // Did not find a child item of the given type - return 0; -} - -void ItemBase::removeChildren( int first, int count ) -{ - // Check arguments - if ( first == -1 ) { - kDebug() << "Not a child of this item"; - return; - } - - // Delete the child at position first, count times - for ( int i = 0; i < count; ++i ) { - ChildItem *child = m_children.takeAt( first ); - delete child; - } -} - -void ItemBase::removeChild( ChildItem* child ) -{ - if ( m_model ) { - m_model->removeRow( m_children.indexOf(child), index() ); - } else { - m_children.removeOne( child ); - } -} - -void ItemBase::appendChild( ChildItem* child ) -{ - if ( m_model ) { - m_model->appendChild( this, child ); - } else { - appendChildFromModel( child ); - } -} - -void ItemBase::appendChildFromModel( ChildItem *child ) -{ - // Add child to children list, pass the model and set the parent to this item - m_children.append( child ); - child->m_parent = this; - child->m_model = m_model; -} - -TopLevelItem::TopLevelItem( const Info* info ) - : QObject(0), ItemBase(info) -{ -} - -void DepartureItem::setLeavingSoon( bool leavingSoon ) -{ - if ( leavingSoon ) { - m_flags |= IsLeavingSoon; - } else { - m_flags &= ~IsLeavingSoon; - } - - if ( m_model ) { - // Notify model about the change - m_model->itemChanged( this, 0, 0 ); - } -} - -void TopLevelItem::setData( Columns column, const QVariant& data, int role ) -{ - m_columnData[ column ][ role ] = data; - if ( m_model ) { - // Notify model about the change - m_model->itemChanged( this, column, column ); - } -} - -ChildItem::ChildItem( ItemType itemType, const QString& formattedText, - const QIcon& icon, const Info *info ) - : ItemBase( info ) -{ - m_type = itemType; - setFormattedText( formattedText ); - setIcon( icon ); -} - -ChildItem::ChildItem( ItemType itemType, const QString& formattedText, const Info* info ) - : ItemBase( info ) -{ - m_type = itemType; - setFormattedText( formattedText ); -} - -ChildItem::ChildItem( ItemType itemType, const Info* info ) - : ItemBase( info ) -{ - m_type = itemType; -} - -QVariant ChildItem::data( int role, int ) const -{ - if ( m_data.contains(role) ) { - return m_data.value( role ); - } else if ( role == DrawAlarmBackgroundRole ) { - // The data for this role is only available at the top level parent - ItemBase *p = topLevelParent(); - return p->data( role ); - } else if ( role == FormattedTextRole ) { - return m_data.value( Qt::DisplayRole ); - } else if ( role == JourneyRatingRole && dynamic_cast(m_model) ) { - // The data for this role is only available at the top level parent - JourneyItem *topLevelJourneyItem = static_cast( topLevelParent() ); - return topLevelJourneyItem->data( JourneyRatingRole ); - } - - return QVariant(); -} - -void ChildItem::setData( const QVariant& data, int role ) -{ - m_data[ role ] = data; - if ( m_model ) { - // Notify model about the change - m_model->itemChanged( this, 0, 0 ); - } -} - - -JourneyItem::JourneyItem( const JourneyInfo& journeyInfo, const Info* info ) - : TopLevelItem( info ) -{ - m_alarm = NoAlarm; - setJourneyInfo( journeyInfo ); -} - -int JourneyItem::row() const -{ - if ( m_model ) { - return m_model->rowFromItem( const_cast( this ) ); - } else { - // No associated model - return -1; - } -} - -QHash< ItemType, ChildItem* > JourneyItem::typedChildren() const -{ - // Build a QHash with all children, sorted by their type - QHash< ItemType, ChildItem* > children; - foreach( ChildItem *child, m_children ) { - if ( child->type() != OtherItem ) { - children.insert( child->type(), child ); - } - } - - return children; -} - -QVariant JourneyItem::data( int role, int column ) const -{ - if ( column >= m_columnData.count() ) { - return QVariant(); - } - - if ( column < m_columnData.count() && m_columnData[column].contains(role) ) { - return m_columnData[column].value( role ); - } else if ( role == DrawAlarmBackgroundRole ) { - return m_alarm.testFlag( AlarmPending );// || !qFuzzyIsNull( m_alarmColorIntensity ); - } else if ( role == AlarmColorIntensityRole ) { - return m_alarm.testFlag( AlarmPending ) ? 1.0 : 0.0;//m_alarmColorIntensity; - } else if ( !m_parent ) { - switch ( role ) { - case LinesPerRowRole: - return m_info->linesPerRow; - case Qt::TextAlignmentRole: - return static_cast(( column == 0 ? Qt::AlignRight : Qt::AlignLeft ) - | Qt::AlignVCenter ); - case DecorationPositionRole: - return column == 0 ? DecorationLeft : DecorationRight; - - case FormattedTextRole: // No formatted text defined - if ( column < m_columnData.count() ) { - return m_columnData[column].value( Qt::DisplayRole ); - } - - case JourneyRatingRole: - return rating(); - - default: - return QVariant(); - } - } - - return QVariant(); -} - -qreal JourneyItem::rating() const -{ - if ( !m_model ) { - // Default rating if no model is associated - return 0.5; - } - - // The journey model keeps track of the smallest and biggest values for durations - // and changes. Journeys with less duration and/or changes are rated better than journeys - // with longer duration and/or more changes. - JourneyModel *model = static_cast( m_model ); - int durationSpan = model->biggestDuration() - model->smallestDuration(); - int changesSpan = model->biggestChanges() - model->smallestChanges(); - - // Check if this journey is the worst of the journeys in the model. - // Also check if the spans are big enough to rate the journey as worst. For example a journey - // with the most changes and longest duration is not necessarily the worst journey, because - // all other journeys may have the same values (or very close values). - if ( (journeyInfo()->changes() == model->biggestChanges() && changesSpan > 4 - && model->biggestChanges() > 3 * model->smallestChanges()) || - (journeyInfo()->duration() == model->biggestDuration() && durationSpan > 30) ) - { - // Rate the journey as worst journey - return 1.0; - } - - // Rate duration and changes independently. If all journeys have the same duration/changes - // the durationSpan/changesSpan is 0. In this case no rating is possible for that value. - qreal durationRating = durationSpan == 0 ? -1.0 - : qreal(journeyInfo()->duration() - model->smallestDuration()) / qreal(durationSpan); - qreal changesRating = changesSpan == 0 ? -1.0 - : qreal(journeyInfo()->changes() - model->smallestChanges()) / qreal(changesSpan); - - // If one rating is not available return the other one (which may also be -1.0) - if ( durationRating == -1.0 ) { - return changesRating; - } else if ( changesRating == -1.0 ) { - return durationRating; - } else { - // Both changes and duration ratings are available - if ( changesRating < 0.1 || changesRating > 0.9 ) { - return durationRating * 0.75 + changesRating * 0.25; - } else { - return durationRating; - } - } -} - -void JourneyItem::setJourneyInfo( const JourneyInfo& journeyInfo ) -{ - if ( m_journeyInfo.isValid() ) { - // Has old data, only update children - m_journeyInfo = journeyInfo; - updateValues(); - updateChildren(); - } else { - // Has no old data, create children as needed - m_journeyInfo = journeyInfo; - updateValues(); - createChildren(); - } -} - -void JourneyItem::updateValues() -{ - setIcon( ColumnLineString, Global::iconFromVehicleTypeList( - m_journeyInfo.vehicleTypes().toList(), 32 * m_info->sizeFactor ) ); - - QString sDuration = KGlobal::locale()->prettyFormatDuration( - m_journeyInfo.duration() * 60 * 1000 ); - QString text = i18ncp("@info Text of journey items in an 'info' column", - "Duration: %2, " - "
%1 change", - "Duration: %2, " - "%1 changes", - m_journeyInfo.changes(), sDuration); - setFormattedText( ColumnJourneyInfo, text ); -// setText( s.replace(QRegExp("<[^>]*>"), "") ); - if ( hasDataForChildType(JourneyNewsItem) ) { - setIcon( ColumnJourneyInfo, GlobalApplet::makeOverlayIcon( - KIcon( "view-pim-news" ), "arrow-down", QSize( 12, 12 ) ) ); - } - - updateTimeValues(); - - if ( m_model ) { - m_model->itemChanged( this, 0, 2 ); - } -} - -void JourneyItem::updateChildren() -{ - QHash< ItemType, ChildItem* > children = typedChildren(); - QList< ItemType > types; - types << DurationItem << ChangesItem << PricingItem << JourneyNewsItem << RouteItem; - foreach( ItemType type, types ) { - // Check if data is (still) available for the current item type - if ( hasDataForChildType(type) ) { - // Data is available for the current item type - if ( children.contains(type) ) { - // Child is already existent, just update it - updateChild( type, children[type] ); - } else { - // Create a new child for the current item type - appendNewChild( type ); - } - } else if ( children.contains(type) ) { - // Data for the current item type is no longer available - removeChild( children[type] ); - } - } -} - -void JourneyItem::createChildren() -{ - QList< ItemType > types; - types << DurationItem << ChangesItem << PricingItem << JourneyNewsItem << RouteItem; - foreach( ItemType type, types ) { - // Check if data is available for the current item type - if ( hasDataForChildType( type ) ) { - // Data is available for the current item type, create a new child - appendNewChild( type ); - } - } -} - -void JourneyItem::updateChild( ItemType itemType, ChildItem* child ) -{ - if ( itemType == RouteItem ) { - // Update route items by simple removing and recreating it - m_model->removeRows( child->row(), 1, child->parent()->index() ); - appendNewChild( RouteItem ); - } else { - // Update possibly changed data - int linesPerRow; - child->setFormattedText( childItemText(itemType, &linesPerRow) ); - if ( itemType == JourneyNewsItem || itemType == DelayItem ) { - child->setData( linesPerRow, LinesPerRowRole ); - } - } -} - -ChildItem* JourneyItem::appendNewChild( ItemType itemType ) -{ - ChildItem *child; - if ( itemType == RouteItem ) { - child = createRouteItem(); - } else { - int linesPerRow; - child = new ChildItem( - itemType, childItemText( itemType, &linesPerRow ), KIcon(), m_info ); - if ( itemType == JourneyNewsItem || itemType == DelayItem ) { - child->setData( linesPerRow, LinesPerRowRole ); - } - } - - appendChild( child ); - return child; -} - -void JourneyItem::updateTimeValues() -{ - // Update departure string if it has changed - const bool timeBold = m_info->departureTimeFlags.testFlag( Settings::DisplayDepartureTimeBold ); - QString depTextFormatted = m_journeyInfo.departureText( true, timeBold, true, true, - m_info->linesPerRow ); - QString oldTextFormatted = formattedText( ColumnDeparture ); - if ( oldTextFormatted != depTextFormatted ) { - setFormattedText( ColumnDeparture, depTextFormatted ); - - QString depText = m_journeyInfo.departureText( false, timeBold, true, true, - m_info->linesPerRow ); - setText( ColumnDeparture, depText ); - } - - // Update arrival string if it has changed - QString arrTextFormatted = m_journeyInfo.arrivalText( true, timeBold, true, true, - m_info->linesPerRow ); - oldTextFormatted = formattedText( ColumnArrival ); - if ( oldTextFormatted != arrTextFormatted ) { - setFormattedText( ColumnArrival, arrTextFormatted ); - - QString arrText = m_journeyInfo.departureText( false, timeBold, true, true, - m_info->linesPerRow ); - setText( ColumnDeparture, arrText ); - } - - if ( m_model ) { - // Notify model about the change - m_model->itemChanged( this, 2, 2 ); - } -} - -bool JourneyItem::hasDataForChildType( ItemType itemType ) -{ - switch ( itemType ) { - case JourneyNewsItem: - return !m_journeyInfo.journeyNews().isEmpty() || !m_journeyInfo.journeyNewsUrl().isEmpty(); - case OperatorItem: - return !m_journeyInfo.operatorName().isEmpty(); - case RouteItem: - return !m_journeyInfo.routeStops().isEmpty(); - case DurationItem: - return m_journeyInfo.duration() > 0; - case ChangesItem: - return m_journeyInfo.changes() > 0; - case PricingItem: - return !m_journeyInfo.pricing().isEmpty(); - - case OtherItem: - default: - kDebug() << "Wrong item type" << itemType; - break; - } - - return false; -} - -QString JourneyItem::childItemText( ItemType itemType, int* linesPerRow ) -{ - QString text; - if ( linesPerRow ) { - *linesPerRow = 1; - } - switch ( itemType ) { - case JourneyNewsItem: - text = m_journeyInfo.journeyNews(); - if ( text.startsWith( QLatin1String( "http://" ) ) ) { // TODO: Make the link clickable... - text = QString( "%1" ).arg( text ); - } - text = QString( "%1 %2" ).arg( i18nc( "@info/plain News for a journey with public " - "transport, like 'platform changed'", "News:" ) ).arg( text ); - if ( !m_journeyInfo.journeyNewsUrl().isEmpty() ) { - if ( !m_journeyInfo.journeyNews().isEmpty() ) { - text += "
"; - } - text += QString( "%1" ).arg( m_journeyInfo.journeyNewsUrl() ); - } - if ( linesPerRow ) { - *linesPerRow = qMin( 3, text.length() / 25 ); - } - break; - case OperatorItem: - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The company that is responsible for " - "this departure/arrival/journey", "Operator:" ) ) - .arg( m_journeyInfo.operatorName() ); - break; - case DurationItem: - if ( m_journeyInfo.duration() <= 0 ) { - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The duration of a journey", "Duration:" ) ) - .arg( 0 ); - } else { - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The duration of a journey", "Duration:" ) ) - .arg( Global::durationString( m_journeyInfo.duration() * 60 ) ); - } - break; - case ChangesItem: - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The changes of a journey", "Changes:" ) ) - .arg( m_journeyInfo.changes() ); - break; - case PricingItem: - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The pricing of a journey", "Pricing:" ) ) - .arg( m_journeyInfo.pricing() ); - break; - case RouteItem: - if ( m_journeyInfo.routeExactStops() > 0 - && m_journeyInfo.routeExactStops() < m_journeyInfo.routeStops().count() ) { - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The route of this departure/arrival/journey", "Route:" ) ) - .arg( i18nc( "@info/plain For routes of journey items, if not " - "all intermediate stops are known", "> %1 stops", - m_journeyInfo.routeStops().count() ) ); - } else { - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The route of this departure/arrival/journey", "Route:" ) ) - .arg( i18nc( "@info/plain For routes of journey items, if all " - "intermediate stops are known", "%1 stops", - m_journeyInfo.routeStops().count() ) ); - } - break; - - case OtherItem: - default: - kDebug() << "Wrong item type" << itemType; - break; - } - - return text; -} - -ChildItem* JourneyItem::createRouteItem() -{ - ChildItem *routeItem = new ChildItem( RouteItem, childItemText( RouteItem ), m_info ); - - // Add route stops as child rows - for ( int row = 0; row < m_journeyInfo.routeStops().count() - 1; ++row ) { - // Add a separator item, when the exact route ends - if ( row == m_journeyInfo.routeExactStops() && row > 0 ) { - ChildItem *separatorItem = new ChildItem( - OtherItem, i18nc( "@info/plain Marker for the first place in a list of " - "intermediate stops, where at least one stop has been omitted", - " - End of exact route - " ), m_info ); - routeItem->appendChild( separatorItem ); - } - - KIcon icon; - QString sTransportLine; - if ( row < m_journeyInfo.routeVehicleTypes().count() && - m_journeyInfo.routeVehicleTypes()[row] != UnknownVehicleType ) - { - icon = Global::vehicleTypeToIcon( m_journeyInfo.routeVehicleTypes()[row] ); - } - if ( row < m_journeyInfo.routeVehicleTypes().count() && - m_journeyInfo.routeVehicleTypes()[row] == Feet ) - { - sTransportLine = i18nc( "@info/plain", "Footway" ); - } else if ( m_journeyInfo.routeTransportLines().count() > row ) { - sTransportLine = m_journeyInfo.routeTransportLines()[row]; - } else { - icon = KIcon( "public-transport-stop" ); - if ( m_journeyInfo.routeTransportLines().count() > row ) - sTransportLine = m_journeyInfo.routeTransportLines()[row]; - } - - QString stopDep = m_journeyInfo.routeStops()[row]; - QString stopArr = m_journeyInfo.routeStops()[row + 1]; - if ( m_journeyInfo.routePlatformsDeparture().count() > row - && !m_journeyInfo.routePlatformsDeparture()[row].isEmpty() ) { - stopDep = i18nc( "@info/plain", "Platform %1", m_journeyInfo.routePlatformsDeparture()[row] ) - + " - " + stopDep; - } - if ( m_journeyInfo.routePlatformsArrival().count() > row - && !m_journeyInfo.routePlatformsArrival()[row].isEmpty() ) { - stopArr = i18nc( "@info/plain", "Platform %1", m_journeyInfo.routePlatformsArrival()[row] ) - + " - " + stopArr; - } - - QString sTimeDep = m_journeyInfo.routeTimesDeparture().value(row).toString( "hh:mm" ); - if ( m_journeyInfo.routeTimesDepartureDelay().count() > row ) { - int delay = m_journeyInfo.routeTimesDepartureDelay()[ row ]; - if ( delay > 0 ) { - sTimeDep += QString( " +%1" ) - .arg( delay ).arg( Global::textColorDelayed().name() ); - } else if ( delay == 0 ) { - sTimeDep = sTimeDep.prepend( QString( "" ) - .arg( Global::textColorOnSchedule().name() ) ) - .append( "" ); - } - } - - QString sTimeArr = m_journeyInfo.routeTimesArrival().value(row).toString( "hh:mm" ); - if ( m_journeyInfo.routeTimesArrivalDelay().count() > row ) { - int delay = m_journeyInfo.routeTimesArrivalDelay()[ row ]; - if ( delay > 0 ) { - sTimeArr += QString( " +%1" ) - .arg( delay ).arg( Global::textColorDelayed().name() ); - } else if ( delay == 0 ) { - sTimeArr = sTimeArr.prepend( QString( "" ) - .arg( Global::textColorOnSchedule().name() ) ) - .append( "" ); - } - } - - ChildItem *routeStopItem; - if ( sTransportLine.isEmpty() ) { - routeStopItem = new ChildItem( OtherItem, - i18nc("@info/plain %1 is the departure time, %2 the origin stop name, " - "%3 the arrival time, %4 the target stop name.", - "dep: %1 - %2arr: %3 - %4", - sTimeDep, stopDep, sTimeArr, stopArr), - icon, m_info ); - routeStopItem->setData( 2, LinesPerRowRole ); - } else { - routeStopItem = new ChildItem( OtherItem, - i18nc("@info/plain %1 is the departure time, %2 the origin stop name, " - "%3 the arrival time, %4 the target stop name, %5 the transport line.", - "%5dep: %1 - %2arr: %3 - %4", - sTimeDep, stopDep, sTimeArr, stopArr, sTransportLine), - icon, m_info ); - routeStopItem->setData( 3, LinesPerRowRole ); - } - - int iconExtend = 16 * m_info->sizeFactor; - routeStopItem->setData( QSize(iconExtend, iconExtend), IconSizeRole ); - - routeItem->appendChild( routeStopItem ); - } - - return routeItem; -} - -DepartureItem::DepartureItem( const DepartureInfo &departureInfo, const Info *info ) - : TopLevelItem( info ) -{ - m_flags = NoFlags; - m_alarm = NoAlarm; - m_alarmColorIntensity = 0.0; - setDepartureInfo( departureInfo ); -} - -int DepartureItem::row() const -{ - if ( m_model ) { - return m_model->rowFromItem( const_cast(this) ); - } else { - return -1; - } -} - -void DepartureItem::setAlarmColorIntensity( qreal alarmColorIntensity ) -{ - m_alarmColorIntensity = alarmColorIntensity; - if ( m_model ) { - // Notify model about the change - m_model->itemChanged( this, 0, 2 ); - } -} - -void DepartureItem::setDepartureInfo( const DepartureInfo &departureInfo ) -{ - if ( m_departureInfo == departureInfo ) { - // Timetable data is unchanged, but matchedAlarms may have changed - m_departureInfo = departureInfo; - return; - } - - // Timetable data has changed, update values and children - m_departureInfo = departureInfo; - updateValues(); - updateChildren(); -} - -void DepartureItem::updateValues() -{ - setText( ColumnLineString, m_departureInfo.lineString() ); - setFormattedText( ColumnLineString, QString("%1") - .arg(m_departureInfo.lineString()) ); - setIcon( ColumnLineString, Global::vehicleTypeToIcon(m_departureInfo.vehicleType()) ); - - setText( ColumnTarget, m_departureInfo.target() ); - if ( hasDataForChildType(JourneyNewsItem) ) { - setIcon( ColumnTarget, GlobalApplet::makeOverlayIcon(KIcon("view-pim-news"), - "arrow-down", QSize(12, 12)) ); - } - - updateTimeValues(); - - if ( m_model ) { - // Notify model about the change - m_model->itemChanged( this, 0, 2 ); - } -} - -void DepartureItem::updateChildren() -{ - // Create a list with all item types used as children for departure items - QList< ItemType > types = QList< ItemType >() - << PlatformItem << JourneyNewsItem << DelayItem << OperatorItem << RouteItem; - - // Check for updates of child items and remove children which no longer have data available - int i = 0; - while ( i < m_children.count() ) { - ChildItem *child = m_children[ i ]; - const ItemType type = child->type(); - if ( hasDataForChildType(type) ) { - // There is still data for the current child, update it - updateChild( type, child, i ); - ++i; - } else { - // No data is available for the current child any longer, remove it - removeChild( child ); - } - - // Remove processed type from children type list - types.removeOne( type ); - } - - // Append new children - foreach( ItemType type, types ) { - if ( hasDataForChildType(type) ) { - appendNewChild( type ); - } - } -} - -void DepartureItem::createChildren() -{ - QList< ItemType > types; - types << PlatformItem << JourneyNewsItem << DelayItem << OperatorItem << RouteItem; - foreach( ItemType type, types ) { - if ( hasDataForChildType(type) ) { - appendNewChild( type ); - } - } -} - -void DepartureItem::updateChild( ItemType itemType, ChildItem* child, int childIndex ) -{ - if ( itemType == RouteItem ) { - m_model->removeRows( childIndex == -1 ? child->row() : childIndex, - 1, child->parent()->index() ); - appendNewChild( RouteItem ); - } else { - int linesPerRow; - child->setFormattedText( childItemText(itemType, &linesPerRow) ); - if ( itemType == JourneyNewsItem || itemType == DelayItem ) { - child->setData( linesPerRow, LinesPerRowRole ); - } - } -} - -ChildItem* DepartureItem::appendNewChild( ItemType itemType ) -{ - ChildItem *child; - if ( itemType == RouteItem ) { - child = createRouteItem(); - } else { - int linesPerRow; - child = new ChildItem( itemType, childItemText(itemType, &linesPerRow), - KIcon(), m_info ); - if ( itemType == JourneyNewsItem || itemType == DelayItem ) { - child->setData( linesPerRow, LinesPerRowRole ); - } - } - - appendChild( child ); - return child; -} - -void DepartureItem::updateTimeValues() -{ - Settings::DepartureTimeFlags timeFlags = m_info->departureTimeFlags; - QString depTextFormatted = m_departureInfo.departureText( true, - timeFlags.testFlag(Settings::DisplayDepartureTimeBold), - timeFlags.testFlag(Settings::ShowRemainingTime), - timeFlags.testFlag(Settings::ShowDepartureTime), m_info->linesPerRow ); - QString oldTextFormatted = formattedText( ColumnDeparture ); - if ( oldTextFormatted != depTextFormatted ) { - setFormattedText( ColumnDeparture, depTextFormatted ); - - QString depText = m_departureInfo.departureText( false, - timeFlags.testFlag(Settings::DisplayDepartureTimeBold), - timeFlags.testFlag(Settings::ShowRemainingTime), - timeFlags.testFlag(Settings::ShowDepartureTime), m_info->linesPerRow ); - setText( ColumnDeparture, depText ); - } - - if ( m_model ) { - m_model->itemChanged( this, 2, 2 ); - } -} - -bool DepartureItem::hasDataForChildType( ItemType itemType ) -{ - switch ( itemType ) { - case PlatformItem: - return !m_departureInfo.platform().isEmpty(); - case JourneyNewsItem: - return !m_departureInfo.journeyNews().isEmpty() || - !m_departureInfo.journeyNewsUrl().isEmpty(); - case DelayItem: - return true; // Also shows "no delay info available" - case OperatorItem: - return !m_departureInfo.operatorName().isEmpty(); - case RouteItem: - return !m_departureInfo.routeStops().isEmpty(); - - case OtherItem: - default: - kDebug() << "Wrong item type" << itemType; - break; - } - - return false; -} - -QString DepartureItem::childItemText( ItemType itemType, int *linesPerRow ) -{ - QString text; - if ( linesPerRow ) { - *linesPerRow = 1; - } - switch ( itemType ) { - case PlatformItem: - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The platform from which a tram/bus/train departs", "Platform:" ) ) - .arg( m_departureInfo.platform() ); - break; - case JourneyNewsItem: - text = m_departureInfo.journeyNews(); - if ( text.startsWith( QLatin1String( "http://" ) ) ) { // TODO: Make the link clickable... - text = QString( "%2" ).arg( text ) - .arg( i18nc("@info/plain Display text for a link to a website with " - "journey news for the current journey item", - "Link to journey news") ); - } - text = QString( "%1 %2" ) - .arg( i18nc("@info/plain News for a journey with public transport, " - "like 'platform changed'", "News:") ) - .arg( text ); - - if ( !m_departureInfo.journeyNewsUrl().isEmpty() ) { - if ( !m_departureInfo.journeyNews().isEmpty() ) { - text += "
"; - } - text += QString( "%1" ).arg( m_departureInfo.journeyNewsUrl() ); - } - // Try to set enough lines to show all text - if ( linesPerRow ) { - *linesPerRow = qMin( 3, text.length() / 25 ); - } - break; - case DelayItem: - text = QString( "%1 %2" ) - .arg( i18nc("@info/plain Information about delays " - "of a journey with public transport", "Delay:") ) - .arg( m_departureInfo.delayText() ); - if ( m_departureInfo.delayType() == Delayed ) { - text += "
" + ( m_info->departureArrivalListType == ArrivalList - ? i18nc( "@info/plain", "Original arrival time:" ) - : i18nc( "@info/plain", "Original departure time:" ) ) + " " + - m_departureInfo.departure().toString( "hh:mm" ); - // When there's a delay use two lines - if ( linesPerRow ) { - *linesPerRow = 2; - } - } else if ( linesPerRow ) { - *linesPerRow = 1; - } - break; - case OperatorItem: - text = QString( "%1 %2" ).arg( i18nc( "@info/plain The company that is " - "responsible for this departure/arrival/journey", "Operator:" ) ) - .arg( m_departureInfo.operatorName() ); - break; - case RouteItem: - if ( m_departureInfo.routeExactStops() > 0 - && m_departureInfo.routeExactStops() < m_departureInfo.routeStops().count() ) { - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The route of this departure/arrival/journey", "Route:" ) ) - .arg( i18nc( "@info/plain For routes of departure/arrival items, if " - "not all intermediate stops are known", "> %1 stops", - m_departureInfo.routeStops().count() ) ); - } else { - text = QString( "%1 %2" ) - .arg( i18nc( "@info/plain The route of this departure/arrival/journey", "Route:" ) ) - .arg( i18nc( "@info/plain For routes of departure/arrival items, if " - "all intermediate stops are known", "%1 stops", - m_departureInfo.routeStops().count() ) ); - } - break; - - case OtherItem: - default: - kDebug() << "Wrong item type" << itemType; - break; - } - - return text; -} - -ChildItem* DepartureItem::createRouteItem() -{ - ChildItem *routeItem = new ChildItem( RouteItem, childItemText(RouteItem), m_info ); - - // Add route stops as child rows - for ( int row = 0; row < m_departureInfo.routeStops().count(); ++row ) { - // Add a separator item, when the exact route ends - // TODO "End of exact route" is "Start of exact route" for arrivals - if ( m_info->departureArrivalListType == ArrivalList ) { - // The exact route stops number here means "number of inexact route stops"... - if ( row == m_departureInfo.routeExactStops() && row > 0 ) { - ChildItem *separatorItem = new ChildItem( OtherItem, - i18nc("@info/plain Marker for the first place in a list of " - "intermediate stops, where no stop has been omitted (for arrival lists)", - " - Start of exact route - "), - m_info ); - routeItem->appendChild( separatorItem ); - } - } else { // if ( m_info->departureArrivalListType == DepartureList ) { - if ( row == m_departureInfo.routeExactStops() && row > 0 ) { - ChildItem *separatorItem = new ChildItem( OtherItem, - i18nc("@info/plain Marker for the first place in a list of intermediate " - "stops, where at least one stop has been omitted (for departure lists)", - " - End of exact route - "), - m_info ); - routeItem->appendChild( separatorItem ); - } - } - - // Add the current route stop ("departure - stop name") - QString text = m_departureInfo.routeStops()[row]; - if ( row < m_departureInfo.routeTimes().count() ) { - // A time is available for the current route stop, prepend it before the stop name - text.prepend( m_departureInfo.routeTimes()[row].toString("hh:mm") + " - " ); - } - ChildItem *routeStopItem = new ChildItem( - OtherItem, text, KIcon( "public-transport-stop" ), m_info ); - routeItem->appendChild( routeStopItem ); - } - - return routeItem; -} - -QVariant DepartureItem::data( int role, int column ) const -{ - if ( column >= m_columnData.count() ) { - return QVariant(); - } - - if ( column < m_columnData.count() && m_columnData[column].contains(role) ) { - return m_columnData[column].value( role ); - } else if ( role == IsLeavingSoonRole ) { - return isLeavingSoon(); - } else if ( role == DrawAlarmBackgroundRole ) { - return m_alarm.testFlag( AlarmPending ) || !qFuzzyIsNull( m_alarmColorIntensity ); - } else if ( role == AlarmColorIntensityRole ) { - return m_alarm.testFlag( AlarmPending ) ? 1.0 : m_alarmColorIntensity; - } else if ( !m_parent ) { - // Top level item (m_parent should always be 0 for DepartureItems) - switch ( role ) { - case LinesPerRowRole: - return m_info->linesPerRow; - case Qt::TextAlignmentRole: - return static_cast(( column == 0 ? Qt::AlignRight : Qt::AlignLeft ) - | Qt::AlignVCenter ); - case DecorationPositionRole: - return column == 0 ? DecorationLeft : DecorationRight; - - case FormattedTextRole: // No formatted text defined - if ( column < m_columnData.count() ) { - return m_columnData[column].value( Qt::DisplayRole ); - } - break; - - case Qt::BackgroundColorRole: { - ColorGroupSettingsList colorGroups = static_cast( model() )->colorGroups(); - foreach ( const ColorGroupSettings &colorGroup, colorGroups ) { - if ( colorGroup.matches(m_departureInfo) ) { - return colorGroup.color; - } - } - return Qt::transparent; - } - - default: - return QVariant(); - } - } - - return QVariant(); -} - -void DepartureItem::setAlarm() -{ - removeAlarm(); // Remove old alarm, if any - static_cast( m_model )->addAlarm( this ); -} - -void DepartureItem::removeAlarm() -{ - if ( hasAlarm() ) { - static_cast( m_model )->removeAlarm( this ); - } -} - -void DepartureItem::setAlarmStates( AlarmStates alarmStates ) -{ // TODO - m_alarm = alarmStates; - - if ( alarmStates.testFlag(AlarmPending) ) { - if ( alarmStates.testFlag(AlarmIsRecurring) ) { - // Add alarm icon with a recurring-icon as overlay - setIcon( ColumnDeparture, KIcon("task-reminder", 0, - QStringList() << "task-recurring") ); - } else { - // Add alarm icon - setIcon( ColumnDeparture, KIcon("task-reminder") ); - } - } else if ( alarmStates == NoAlarm ) { - // Remove alarm icon - setIcon( ColumnDeparture, KIcon() ); - } else if ( alarmStates.testFlag(AlarmFired) ) { - // Add disabled alarm icon - KIconEffect iconEffect; - KIcon icon = alarmStates.testFlag( AlarmIsRecurring ) - ? KIcon( "task-reminder", 0, QStringList() << "task-recurring" ) - : KIcon( "task-reminder" ); - QPixmap pixmap = iconEffect.apply( icon.pixmap( 16 * m_info->sizeFactor ), - KIconLoader::Small, KIconLoader::DisabledState ); - KIcon disabledAlarmIcon; - disabledAlarmIcon.addPixmap( pixmap, QIcon::Normal ); - setIcon( ColumnDeparture, disabledAlarmIcon ); - } - - m_model->itemChanged( this, 0, 2 ); - m_model->childrenChanged( this ); // Children inherit the alarm background -} - -void JourneyItem::setAlarmStates(AlarmStates alarmStates) -{ // TODO - m_alarm = alarmStates; - - if ( alarmStates.testFlag(AlarmPending) ) { - if ( alarmStates.testFlag(AlarmIsRecurring) ) { - // Add alarm icon with a recurring-icon as overlay - setIcon( ColumnDeparture, KIcon("task-reminder", 0, - QStringList() << "task-recurring") ); - } else { - // Add alarm icon - setIcon( ColumnDeparture, KIcon("task-reminder") ); - } - } else if ( alarmStates == NoAlarm ) { - // Remove alarm icon - setIcon( ColumnDeparture, KIcon() ); - } else if ( alarmStates.testFlag(AlarmFired) ) { - // Add disabled alarm icon - KIconEffect iconEffect; - KIcon icon = alarmStates.testFlag( AlarmIsRecurring ) - ? KIcon( "task-reminder", 0, QStringList() << "task-recurring" ) - : KIcon( "task-reminder" ); - QPixmap pixmap = iconEffect.apply( icon.pixmap( 16 * m_info->sizeFactor ), - KIconLoader::Small, KIconLoader::DisabledState ); - KIcon disabledAlarmIcon; - disabledAlarmIcon.addPixmap( pixmap, QIcon::Normal ); - setIcon( ColumnDeparture, disabledAlarmIcon ); - } - - m_model->itemChanged( this, 0, 2 ); - m_model->childrenChanged( this ); // Children inherit the alarm background -} - -PublicTransportModel::PublicTransportModel( QObject* parent ) - : QAbstractItemModel( parent ), m_nextItem( 0 ), - m_updateTimer( new QTimer(this) ) -{ - m_updateTimer->setInterval( 60000 ); - connect( m_updateTimer, SIGNAL(timeout()), this, SLOT(update()) ); - - callAtNextFullMinute( SLOT(startUpdateTimer()) ); -} - -void PublicTransportModel::startUpdateTimer() -{ - update(); - m_updateTimer->start(); -} - -void PublicTransportModel::callAtNextFullMinute( const char* member ) -{ - // Calculate milliseconds until the next full minute - const QTime time = QTime::currentTime(); - QTimer::singleShot( (60 - time.second()) * 1000 - time.msec(), this, member ); -} - -void PublicTransportModel::setLinesPerRow( int linesPerRow ) -{ - if ( m_info.linesPerRow == linesPerRow ) { - return; - } - m_info.linesPerRow = linesPerRow; - emit dataChanged( index(0, 0), index(rowCount(), 0) ); -} - -void PublicTransportModel::setSizeFactor( float sizeFactor ) -{ - m_info.sizeFactor = sizeFactor; - // TODO: Update items? -} - -void PublicTransportModel::setDepartureColumnSettings( Settings::DepartureTimeFlags flags ) -{ - m_info.departureTimeFlags = flags; - foreach( ItemBase *item, m_items ) { - item->updateTimeValues(); - } -} - -void PublicTransportModel::setProviderFeatures( const QStringList &providerFeatures ) -{ - m_info.providerFeatures = providerFeatures; -} - -QModelIndex PublicTransportModel::index( int row, int column, - const QModelIndex& parent ) const -{ - if ( parent.isValid() ) { - if ( !hasIndex(row, column, parent) ) { - return QModelIndex(); - } - - ItemBase *parentItem = static_cast( parent.internalPointer() ); - if ( row < parentItem->childCount() ) { - return createIndex( row, column, parentItem->child(row) ); - } else { - return QModelIndex(); - } - } else { - if ( !hasIndex(row, column, QModelIndex()) ) { - return QModelIndex(); - } - - if ( row >= 0 && row < m_items.count() ) { - return createIndex( row, column, m_items[row] ); - } else { - return QModelIndex(); - } - } -} - -QModelIndex PublicTransportModel::parent( const QModelIndex& child ) const -{ - if ( !child.isValid() ) { - return QModelIndex(); - } - - ItemBase *childItem = static_cast( child.internalPointer() ); - if ( !childItem ) { - return QModelIndex(); - } - - ItemBase *parent = childItem->parent(); - if ( parent ) { - return createIndex( parent->row(), 0, parent ); - } else { - return QModelIndex(); - } -} - -int PublicTransportModel::rowCount( const QModelIndex& parent ) const -{ - if ( parent.column() > 0 ) { - return 0; - } - - if ( parent.isValid() ) { - ItemBase *parentItem = static_cast( parent.internalPointer() ); - return parentItem->childCount(); - } else { - return m_items.count(); - } -} - -ItemBase* PublicTransportModel::itemFromIndex( const QModelIndex& index ) const -{ - return static_cast( index.internalPointer() ); -} - -int PublicTransportModel::rowFromItem( ItemBase* item ) -{ - return m_items.indexOf( item ); -} - -QModelIndex PublicTransportModel::indexFromItem( ItemBase* item, int column ) const -{ - return item ? createIndex( item->row(), column, item ) : QModelIndex(); -} - -void PublicTransportModel::itemChanged( ItemBase* item, int columnLeft, int columnRight ) -{ - if ( columnLeft == columnRight ) { - QModelIndex index = indexFromItem( item, columnLeft ); - if ( !index.isValid() ) { - kDebug() << "The given item is not in the model"; - } else { - emit dataChanged( index, index ); - } - } else { - QModelIndex indexLeft = indexFromItem( item, columnLeft ); - QModelIndex indexRight = indexFromItem( item, columnRight ); - if ( !indexLeft.isValid() ) { - kDebug() << "The given item is not in the model"; - } else { - emit dataChanged( indexLeft, indexRight ); - } - } -} - -void PublicTransportModel::childrenChanged( ItemBase* parentItem ) -{ - if ( !parentItem->children().isEmpty() ) { - QModelIndex indexFirst = indexFromItem( parentItem->children().first() ); - QModelIndex indexLast = indexFromItem( parentItem->children().last() ); - emit dataChanged( indexFirst, indexLast ); - - foreach( ChildItem *child, parentItem->children() ) { - childrenChanged( child ); - } - } -} - -QVariant PublicTransportModel::data( const QModelIndex& index, int role ) const -{ - if ( !index.isValid() ) { - return QVariant(); - } - - ItemBase *item = static_cast( index.internalPointer() ); - return item->data( role, index.column() ); -} - -void PublicTransportModel::clear() -{ - emit itemsAboutToBeRemoved( m_items ); - - beginRemoveRows( QModelIndex(), 0, m_items.count() ); - m_infoToItem.clear(); - qDeleteAll( m_items ); - m_items.clear(); - m_nextItem = 0; - endRemoveRows(); -} - -JourneyModel::JourneyModel( QObject* parent ) : PublicTransportModel( parent ) -{ - m_smallestDuration = 999999; - m_biggestDuration = 0; - m_smallestChanges = 999999; - m_biggestChanges = 0; -} - -int JourneyModel::columnCount( const QModelIndex& parent ) const -{ - return parent.isValid() ? 1 : 4; -} - -QVariant JourneyModel::headerData( int section, Qt::Orientation orientation, - int role ) const -{ - if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { - switch ( section ) { - case 0: return i18nc( "@title:column A public transport line", "Line" ); - case 1: return i18nc( "@title:column Information about a journey with public transport", "Information" ); - case 2: return i18nc( "@title:column Time of departure of a tram or bus", "Departure" ); - case 3: return i18nc( "@title:column Time of arrival of a tram or bus", "Arrival" ); - } - } - - return QVariant(); -} - -void JourneyModel::setDepartureArrivalListType( DepartureArrivalListType departureArrivalListType ) -{ - m_info.departureArrivalListType = departureArrivalListType; -} - -void JourneyModel::setCurrentStopIndex( int currentStopSettingsIndex ) -{ - m_info.currentStopSettingsIndex = currentStopSettingsIndex; -} - -void JourneyModel::setAlarmSettings( const AlarmSettingsList& alarm ) -{ - m_info.alarm = alarm; - - for ( int row = 0; row < m_items.count(); ++row ) { - JourneyItem *journeyItem = static_cast( m_items[row] ); - updateItemAlarm( journeyItem ); - } -} - -void JourneyModel::updateItemAlarm( JourneyItem* journeyItem ) -{ - // Store old alarm states - AlarmStates oldAlarmStates = journeyItem->alarmStates(); - - // Use a dummy DepartureInfo that "mimics" the first journey part of the current journey - JourneyInfo journeyInfo = *journeyItem->journeyInfo(); - QString lineString = journeyInfo.routeTransportLines().isEmpty() - ? QString() : journeyInfo.routeTransportLines().first(); - VehicleType vehicleType = journeyInfo.routeVehicleTypes().isEmpty() - ? UnknownVehicleType : journeyInfo.routeVehicleTypes().first(); - DepartureInfo departureInfo( /* Data source: */ QString(), /* index: */ -1, - PublicTransport::DepartureInfo::NoDepartureFlags, - QString(), lineString, QString(), QString(), - journeyInfo.departure(), vehicleType ); - AlarmStates alarmStates = NoAlarm; - for ( int a = 0; a < m_info.alarm.count(); ++a ) { - AlarmSettings alarm = m_info.alarm[ a ]; - - // Remove target constraints from the alarm filter (because the target is unknown) - Filter alarmFilter = alarm.filter; - for ( int i = 0; i < alarmFilter.count(); ++i ) { - if ( alarmFilter[i].type == PublicTransport::FilterByTarget ) { - alarmFilter.removeAt( i ); - break; - } - } - - if ( alarm.affectedStops.contains(m_info.currentStopSettingsIndex) - && alarm.enabled && !alarmFilter.isEmpty() - && alarmFilter.match(departureInfo) ) - { - const QDateTime alarmTime = journeyItem->alarmTime(); - if ( QDateTime::currentDateTime() > alarmTime ) { - alarmStates |= AlarmFired; - } else { - alarmStates |= AlarmPending; - } - if ( alarm.autoGenerated ) { - alarmStates |= AlarmIsAutoGenerated; - } - if ( alarm.type == AlarmApplyToNewDepartures ) { - alarmStates |= AlarmIsRecurring; - } - break; - } - } - - if ( oldAlarmStates != alarmStates ) { - journeyItem->setAlarmStates( alarmStates ); - } -} - -bool JourneyModel::removeRows( int row, int count, const QModelIndex& parent ) -{ - beginRemoveRows( parent, row, row + count - 1 ); - if ( parent.isValid() ) { - ItemBase *item = itemFromIndex( parent ); - item->removeChildren( row, count ); - } else { - emit itemsAboutToBeRemoved( m_items.mid(row, count) ); - - for ( int i = 0; i < count; ++i ) { - JourneyItem *item = static_cast( m_items.takeAt(row) ); - m_infoToItem.remove( item->journeyInfo()->hash() ); - if ( m_nextItem == item ) { - m_nextItem = findNextItem(); - } -// if ( item->journeyInfo()->duration() == m_biggestDuration ) - // TODO Find new biggest duration item -// else if ( item->journeyInfo()->duration() == m_smallestDuration ) - // TODO Find new smallest duration item - delete item; - } - } - - if ( isEmpty() ) { - m_smallestDuration = 999999; - m_biggestDuration = 0; - m_smallestChanges = 999999; - m_biggestChanges = 0; - } - endRemoveRows(); - - return true; -} - -void JourneyModel::clear() -{ - PublicTransportModel::clear(); - - m_smallestDuration = 999999; - m_biggestDuration = 0; - m_smallestChanges = 999999; - m_biggestChanges = 0; -} - -void JourneyModel::update() -{ -// if ( m_info.showRemainingMinutes ) { - foreach( ItemBase *item, m_items ) { - item->updateTimeValues(); - } -// } -} - -void JourneyModel::sort( int column, Qt::SortOrder order ) -{ - if ( column < 0 || rowCount() == 0 ) { - return; - } - - emit layoutAboutToBeChanged(); - - QVector< QPair > sortable/*( m_items.count() )*/; - for ( int row = 0; row < m_items.count(); ++row ) { - sortable.append( QPair(static_cast(m_items[row]), row) ); - } - - if ( order == Qt::AscendingOrder ) { - JourneyModelLessThan lt( static_cast(column) ); - qStableSort( sortable.begin(), sortable.end(), lt ); - } else { - JourneyModelGreaterThan gt( static_cast(column) ); - qStableSort( sortable.begin(), sortable.end(), gt ); - } - - // TODO Use persistentIndexList() - QModelIndexList changedPersistentIndexesFrom, changedPersistentIndexesTo; - QList< ItemBase* > sorted_children; - for ( int newRow = 0; newRow < sortable.count(); ++newRow ) { - const QPair sortableItem = sortable[ newRow ]; - ItemBase *item = sortableItem.first; - const int oldRow = sortableItem.second; - sorted_children << item; - - for ( int column = 0; column < columnCount(); ++column ) { - changedPersistentIndexesFrom.append( createIndex(oldRow, column, item) ); - changedPersistentIndexesTo.append( createIndex(newRow, column, item) ); - } - } - - m_items = sorted_children; - changePersistentIndexList( changedPersistentIndexesFrom, changedPersistentIndexesTo ); - - emit layoutChanged(); -} - -JourneyItem* JourneyModel::addItem( const JourneyInfo& journeyInfo, - Columns sortColumn, Qt::SortOrder sortOrder ) -{ - ItemBase *existingItem = m_infoToItem.value( journeyInfo.hash(), 0 ); - if ( existingItem ) { - kDebug() << "Journey already added to the model" << journeyInfo; - return static_cast( existingItem ); - } - - // Find the row where to insert the new journey - int count = m_items.count(); - int insertBefore = count; - if ( sortOrder == Qt::AscendingOrder ) { - JourneyModelGreaterThan gt( static_cast( sortColumn ) ); - for ( int i = 0; i < count; ++i ) { - JourneyItem *item = static_cast( m_items.at( i ) ); - if ( gt.operator()( item->journeyInfo(), &journeyInfo ) ) { - insertBefore = i; - break; - } - } - } else { - JourneyModelLessThan lt( static_cast( sortColumn ) ); - for ( int i = 0; i < count; ++i ) { - JourneyItem *item = static_cast( m_items.at( i ) ); - if ( lt.operator()( item->journeyInfo(), &journeyInfo ) ) { - insertBefore = i; - break; - } - } - } - - beginInsertRows( QModelIndex(), insertBefore, insertBefore ); - JourneyItem *item = new JourneyItem( journeyInfo, &m_info ); - m_infoToItem.insert( journeyInfo.hash(), item ); - m_items.insert( insertBefore, item ); - item->setModel( this ); - endInsertRows(); - - // Update next departing journey - if ( m_nextItem ) { - if ( item->journeyInfo()->departure() - < static_cast( m_nextItem )->journeyInfo()->departure() ) { - m_nextItem = item; - } - } else { - m_nextItem = findNextItem( sortColumn == ColumnDeparture - && sortOrder == Qt::AscendingOrder ); - } - - // Update biggest/smallest duration and changes for rating of journeys - if ( item->journeyInfo()->duration() > m_biggestDuration ) { - m_biggestDuration = item->journeyInfo()->duration(); - } else if ( item->journeyInfo()->duration() < m_smallestDuration ) { - m_smallestDuration = item->journeyInfo()->duration(); - } - - if ( item->journeyInfo()->changes() > m_biggestChanges ) { - m_biggestChanges = item->journeyInfo()->changes(); - } else if ( item->journeyInfo()->changes() < m_smallestChanges ) { - m_smallestChanges = item->journeyInfo()->changes(); - } - - // Set alarm flags - updateItemAlarm( item ); - - return item; -} - -JourneyItem* JourneyModel::findNextItem( bool sortedByDepartureAscending ) const -{ - if ( m_items.isEmpty() ) { - return 0; - } - - if ( sortedByDepartureAscending ) { - return static_cast( m_items.first() ); - } else { - JourneyItem *earliest = static_cast( m_items.first() ); - for ( int i = 1; i < m_items.count(); ++i ) { - JourneyItem *item = static_cast( m_items[i] ); - if ( item->journeyInfo()->departure() < earliest->journeyInfo()->departure() ) { - earliest = item; - } - } - return earliest; - } -} - -DepartureModel::DepartureModel( QObject* parent ) : PublicTransportModel( parent ) -{ -} - -void DepartureModel::update() -{ - // Check for alarms that should now be fired - if ( !m_alarms.isEmpty() ) { - QDateTime nextAlarm = m_alarms.keys().first(); - int secs = QDateTime::currentDateTime().secsTo( nextAlarm ); - if ( secs < 10 ) { - while ( m_alarms.contains( nextAlarm ) ) { - DepartureItem *item = m_alarms.take( nextAlarm ); - fireAlarm( nextAlarm, item ); - } - } - } - - // Sort out departures in the past - int row = 0; - m_nextItem = m_items.isEmpty() ? 0 : static_cast( m_items[row] ); - QDateTime nextDeparture = m_nextItem - ? static_cast(m_nextItem)->departureInfo()->predictedDeparture() - : QDateTime(); - nextDeparture.setTime( QTime(nextDeparture.time().hour(), nextDeparture.time().minute()) ); // Set second to 0 - while ( m_nextItem && nextDeparture < QDateTime::currentDateTime() ) { - // The next departure is in the past - DepartureItem *leavingItem = static_cast( m_nextItem ); - leavingItem->setLeavingSoon( true ); - - // Go to the next item, if any - ++row; - if ( row >= m_items.count() ) { - break; - } - m_nextItem = static_cast( m_items[row] ); - nextDeparture = static_cast(m_nextItem)->departureInfo()->predictedDeparture(); - nextDeparture.setTime( QTime(nextDeparture.time().hour(), nextDeparture.time().minute()) ); // Set second to 0 - } - - // Wait 10 seconds before removing the departure. - // By having called setLeavingSoon(true) the items to be removed will animate to indicate that - // they are leaving soon. - QTimer::singleShot( 10000, this, SLOT(removeLeavingDepartures()) ); - - // Update departure column if necessary (remaining minutes) - if ( m_info.departureTimeFlags.testFlag(Settings::ShowRemainingTime) ) { - foreach( ItemBase *item, m_items ) { - item->updateTimeValues(); - } - } -} - -void DepartureModel::removeLeavingDepartures() -{ - QList leaving; - - for ( int row = 0; row < m_items.count(); ++row ) { - DepartureItem *item = static_cast( m_items[row] ); - if ( item->isLeavingSoon() ) { - leaving << *item->departureInfo(); - removeRows( row, 1 ); - --row; - } else { - break; - } - } - - if ( !leaving.isEmpty() ) { - emit departuresLeft( leaving ); - } -} - -int DepartureModel::columnCount( const QModelIndex& parent ) const -{ - // Top level items have three columns (line, target, departure), - // child items have only one stretched column - return parent.isValid() ? 1 : 3; -} - -void DepartureModel::setAlarmSettings( const AlarmSettingsList& alarm ) -{ - m_info.alarm = alarm; - - // Remove old alarms - QMultiMap< QDateTime, DepartureItem* >::iterator it; - for ( it = m_alarms.begin(); it != m_alarms.end(); ++it ) { - disconnect( *it, SIGNAL(destroyed(QObject*)), this, SLOT(alarmItemDestroyed(QObject*)) ); - (*it)->setAlarmStates( NoAlarm ); - - it = m_alarms.erase( it ); - } - - // Set new alarms (go through all alarm settings for all departures) - for ( int row = 0; row < m_items.count(); ++row ) { - for ( int a = 0; a < m_info.alarm.count(); ++a ) { - AlarmSettings alarm = m_info.alarm.at( a ); - if ( alarm.enabled - && alarm.filter.match( - *static_cast(m_items[row])->departureInfo() ) ) - { - // Current alarm is enabled and matches the current departure - DepartureItem *depItem = static_cast( m_items[row] ); - if ( !depItem->hasAlarm() ) { - addAlarm( depItem ); - } - - if ( !depItem->departureInfo()->matchedAlarms().contains(a) ) { - depItem->departureInfo()->matchedAlarms() << a; - } - if ( alarm.autoGenerated ) { - depItem->setAlarmStates( depItem->alarmStates() | AlarmIsAutoGenerated ); - } - if ( alarm.type != AlarmRemoveAfterFirstMatch ) { - depItem->setAlarmStates( depItem->alarmStates() | AlarmIsRecurring ); - } - } - } - } -} - -void DepartureModel::setDepartureArrivalListType( DepartureArrivalListType departureArrivalListType ) -{ - if ( m_info.departureArrivalListType == departureArrivalListType ) { - return; - } - m_info.departureArrivalListType = departureArrivalListType; - emit headerDataChanged( Qt::Horizontal, 1, 2 ); -} - -void DepartureModel::setCurrentStopIndex(int currentStopSettingsIndex) -{ - m_info.currentStopSettingsIndex = currentStopSettingsIndex; -} - -QVariant DepartureModel::headerData( int section, Qt::Orientation orientation, - int role ) const -{ - if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { - switch ( section ) { - case 0: return i18nc( "@title:column A public transport line", "Line" ); - case 1: - if ( m_info.departureArrivalListType == DepartureList ) { - return i18nc( "@title:column Target of a tramline or busline", "Target" ); - } else { - return i18nc( "@title:column Origin of a tramline or busline", "Origin" ); - } - case 2: - if ( m_info.departureArrivalListType == DepartureList ) { - return i18nc( "@title:column Time of departure of a tram or bus", "Departure" ); - } else { - return i18nc( "@title:column Time of arrival of a tram or bus", "Arrival" ); - } - } - } - - return QVariant(); -} - -QList< uint > DepartureModel::itemHashes() const -{ - QList< uint > hashes; - foreach( ItemBase *item, m_items ) { - hashes << static_cast( item )->departureInfo()->hash(); - } - return hashes; -} - -QList< DepartureInfo > DepartureModel::departureInfos() const -{ - QList< DepartureInfo > infoList; - foreach( ItemBase *item, m_items ) { - infoList << *static_cast( item )->departureInfo(); - } - return infoList; -} - -QStringList DepartureModel::allStopNames( int maxDepartureCount ) const -{ - QStringList stopNames; - for ( int i = 0; i < m_items.count() && (maxDepartureCount == -1 || i <= maxDepartureCount); ++i ) { - const DepartureInfo *info = static_cast( m_items[i] )->departureInfo(); - stopNames << info->target(); - stopNames << info->routeStops(); - } - stopNames.removeDuplicates(); - return stopNames; -} - -void DepartureModel::sort( int column, Qt::SortOrder order ) -{ - if ( column < 0 || rowCount() == 0 ) { - return; - } - emit layoutAboutToBeChanged(); - - // Create a vector of pairs of departure items and their positions in the item list - // to be able to update persistent model indexes - QVector< QPair > sortable; - for ( int row = 0; row < m_items.count(); ++row ) { - sortable.append( QPair(static_cast(m_items[row]), row) ); - } - - // Sort the intermediate vector - if ( order == Qt::AscendingOrder ) { - DepartureModelLessThan lt( static_cast(column) ); - qStableSort( sortable.begin(), sortable.end(), lt ); - } else { - DepartureModelGreaterThan gt( static_cast(column) ); - qStableSort( sortable.begin(), sortable.end(), gt ); - } - - // Create new list of sorted items and updated persistent model indexes - QModelIndexList changedPersistentIndexesFrom, changedPersistentIndexesTo; - QList< ItemBase* > sorted_children; - for ( int i = 0; i < m_items.count(); ++i ) { - int r = sortable.at( i ).second; - ItemBase *item = m_items[r]; - sorted_children << item; - - // Store changed persistent indices - for ( int c = 0; c < columnCount(); ++c ) { - changedPersistentIndexesFrom.append( createIndex(r, c, item) ); - changedPersistentIndexesTo.append( createIndex(i, c, item) ); - } - } - - m_items = sorted_children; - changePersistentIndexList( changedPersistentIndexesFrom, changedPersistentIndexesTo ); - emit layoutChanged(); -} - -DepartureItem* DepartureModel::findNextItem( bool sortedByDepartureAscending ) const -{ - if ( m_items.isEmpty() ) { - return 0; - } - - if ( sortedByDepartureAscending ) { - return static_cast( m_items.first() ); - } else { - // Find the next departing item - DepartureItem *earliest = static_cast( m_items.first() ); - for ( int i = 1; i < m_items.count(); ++i ) { - DepartureItem *item = static_cast( m_items[i] ); - if ( item->departureInfo()->predictedDeparture() < - earliest->departureInfo()->predictedDeparture() ) { - earliest = item; - } - } - return earliest; - } -} - -DepartureItem *DepartureModel::addItem( const DepartureInfo& departureInfo, - Columns sortColumn, Qt::SortOrder sortOrder ) -{ - // Check if the item has already been added to this model (using DepartureInfo::hash()) - ItemBase *existingItem = m_infoToItem.value( departureInfo.hash(), 0 ); - if ( existingItem ) { - kDebug() << "Departure already added to the model at index" << departureInfo; - return static_cast( existingItem ); - } - - // Find the row where to insert the new departure - int count = m_items.count(); - int insertBefore = count; - if ( sortOrder == Qt::AscendingOrder ) { - DepartureModelGreaterThan gt( static_cast( sortColumn ) ); - for ( int i = 0; i < count; ++i ) { - DepartureItem *item = static_cast( m_items.at(i) ); - if ( gt.operator()(item->departureInfo(), &departureInfo) ) { - insertBefore = i; - break; - } - } - } else { - DepartureModelLessThan lt( static_cast( sortColumn ) ); - for ( int i = 0; i < count; ++i ) { - DepartureItem *item = static_cast( m_items.at(i) ); - if ( lt.operator()(item->departureInfo(), &departureInfo) ) { - insertBefore = i; - break; - } - } - } - - // Create and insert the new DepartureItem - beginInsertRows( QModelIndex(), insertBefore, insertBefore ); - DepartureItem *newItem = new DepartureItem( departureInfo, &m_info ); - m_infoToItem.insert( departureInfo.hash(), newItem ); - m_items.insert( insertBefore, newItem ); - newItem->setModel( this ); - endInsertRows(); - - // Ensure m_nextItem points to the next departure in the list - if ( m_nextItem ) { - if ( newItem->departureInfo()->predictedDeparture() < - static_cast(m_nextItem)->departureInfo()->predictedDeparture() ) - { - m_nextItem = newItem; - } - } else { - m_nextItem = findNextItem( sortColumn == ColumnDeparture - && sortOrder == Qt::AscendingOrder ); - } - - // Handle alarms - if ( !departureInfo.matchedAlarms().isEmpty() ) { - addAlarm( newItem ); - - // Check if there's only one matching autogenerated alarm - // and/or at least one recurring alarm - if ( departureInfo.matchedAlarms().count() == 1 ) { - int matchedAlarm = departureInfo.matchedAlarms().first(); - if ( matchedAlarm < 0 || matchedAlarm >= m_info.alarm.count() ) { - kDebug() << "Matched alarm is out of range of current alarm settings" << matchedAlarm; - } else { - AlarmSettings alarm = m_info.alarm.at( matchedAlarm ); - if ( alarm.autoGenerated ) { - newItem->setAlarmStates( newItem->alarmStates() | AlarmIsAutoGenerated ); - } - if ( alarm.type != AlarmRemoveAfterFirstMatch ) { - newItem->setAlarmStates( newItem->alarmStates() | AlarmIsRecurring ); - } - } - } else { - for ( int a = 0; a < departureInfo.matchedAlarms().count(); ++a ) { - int matchedAlarm = departureInfo.matchedAlarms().at( a ); - if ( matchedAlarm < 0 || matchedAlarm >= m_info.alarm.count() ) { - kDebug() << "Matched alarm is out of range of current alarm settings" << matchedAlarm; - continue; - } - if ( m_info.alarm.at( matchedAlarm ).type != AlarmRemoveAfterFirstMatch ) { - newItem->setAlarmStates( newItem->alarmStates() | AlarmIsRecurring ); - break; - } - } - } - } - - return newItem; -} - -void PublicTransportModel::appendChild( ItemBase *parent, ChildItem *child ) -{ - const QModelIndex index = parent->index(); - beginInsertRows( index, parent->childCount(), parent->childCount() ); - parent->appendChildFromModel( child ); - endInsertRows(); -} - -bool DepartureModel::removeRows( int row, int count, const QModelIndex& parent ) -{ - beginRemoveRows( parent, row, row + count - 1 ); - if ( parent.isValid() ) { - ItemBase *item = itemFromIndex( parent ); - item->removeChildren( row, count ); - } else { - emit itemsAboutToBeRemoved( m_items.mid(row, count) ); - - for ( int i = 0; i < count; ++i ) { - DepartureItem *item = static_cast( m_items.takeAt(row) ); - item->removeChildren( 0, item->childCount() ); // Needed? - m_infoToItem.remove( item->departureInfo()->hash() ); - if ( item->hasAlarm() ) { - removeAlarm( item ); - } - if ( m_nextItem == item ) { - m_nextItem = findNextItem(); - } - delete item; - } - } - endRemoveRows(); - - return true; -} - -void DepartureModel::clear() -{ - PublicTransportModel::clear(); - m_alarms.clear(); -} - -void DepartureModel::alarmItemDestroyed( QObject* item ) -{ - DepartureItem *depItem = qobject_cast< DepartureItem* >( item ); - int index; - while ( (index = m_alarms.values().indexOf(depItem)) != -1 ) { - m_alarms.remove( m_alarms.keys().at(index), depItem ); - } -} - -void DepartureModel::addAlarm( DepartureItem* item ) -{ - const QDateTime alarmTime = item->alarmTime(); - if ( QDateTime::currentDateTime() > alarmTime ) { - fireAlarm( alarmTime, item ); - } else { - connect( item, SIGNAL(destroyed(QObject*)), this, SLOT(alarmItemDestroyed(QObject*)) ); - m_alarms.insert( alarmTime, item ); - item->setAlarmStates( (item->alarmStates() & ~AlarmFired) | AlarmPending ); - } -} - -void DepartureModel::removeAlarm( DepartureItem* item ) -{ - int index = m_alarms.values().indexOf( item ); - if ( index == -1 ) { - kDebug() << "Alarm not found!"; - return; - } - int removed = m_alarms.remove( m_alarms.keys().at( index ), item ); - if ( removed > 0 ) { - disconnect( item, SIGNAL(destroyed(QObject*)), this, SLOT(alarmItemDestroyed(QObject*)) ); - item->setAlarmStates( NoAlarm ); - } -} - -void DepartureModel::fireAlarm( const QDateTime& dateTime, DepartureItem* item ) -{ - if ( item->alarmStates().testFlag(AlarmFired) ) { - return; // Don't fire alarms more than once - } - - kDebug() << "FIRE" << dateTime << item->departureInfo()->matchedAlarms(); - bool fireAlarm = true; - AlarmSettings matchingAlarmSettings; - for ( int i = item->departureInfo()->matchedAlarms().count() - 1; i >= 0; --i ) { - int matchedAlarm = item->departureInfo()->matchedAlarms().at( i ); - if ( matchedAlarm < 0 || matchedAlarm >= m_info.alarm.count() ) { - kDebug() << "Matched alarm is out of range of current alarm settings"; - continue; - } - - matchingAlarmSettings = m_info.alarm[ matchedAlarm ]; - if ( matchingAlarmSettings.lastFired.isValid() ) { - int secs = matchingAlarmSettings.lastFired.secsTo( dateTime ); - kDebug() << "Alarm already fired?" << secs << "seconds from last fired to alarm time."; - if ( secs >= 0 ) { - fireAlarm = false; - continue; // Try other matched alarms - } - } - - // Found an alarm - break; - } - kDebug() << "Fire alarm?" << fireAlarm; - if ( !fireAlarm ) { - return; - } - - item->setAlarmStates( (item->alarmStates() & ~(AlarmPending | AlarmIsAutoGenerated)) | AlarmFired ); - emit alarmFired( item, matchingAlarmSettings ); - - QList< int > alarmsToRemove; - for ( int i = item->departureInfo()->matchedAlarms().count() - 1; i >= 0; --i ) { - int matchedAlarm = item->departureInfo()->matchedAlarms().at( i ); - if ( matchedAlarm < 0 || matchedAlarm >= m_info.alarm.count() ) { - kDebug() << "Matched alarm is out of range of current alarm settings"; - continue; - } - if ( m_info.alarm[matchedAlarm].type == AlarmRemoveAfterFirstMatch ) { - alarmsToRemove << matchedAlarm; - } - m_info.alarm[ matchedAlarm ].lastFired = QDateTime::currentDateTime(); - - QPropertyAnimation *anim = new QPropertyAnimation( item, "alarmColorIntensity", this ); - anim->setStartValue( 1.0 ); - anim->setEndValue( 0.0 ); - anim->setDuration( 1000 ); - anim->setLoopCount( 5 ); - anim->start( QAbstractAnimation::DeleteWhenStopped ); - } - kDebug() << "ALARMS TO BE REMOVED" << alarmsToRemove << item->departureInfo()->lineString() - << item->departureInfo()->target() << item->departureInfo()->departure(); - if ( !alarmsToRemove.isEmpty() ) { - foreach( int i, alarmsToRemove ) // stored in descending order... - m_info.alarm.removeAt( i ); // ...so removing is ok - emit updateAlarms( m_info.alarm, alarmsToRemove ); - } -} - -RouteItemFlags PublicTransportModel::routeItemFlags( const QString& stopName ) const -{ - RouteItemFlags flags = RouteItemDefault; - if ( m_info.highlightedStop.compare(stopName, Qt::CaseInsensitive) == 0 ) { - flags |= RouteItemHighlighted; - } - if ( m_info.homeStop.compare(stopName, Qt::CaseInsensitive) == 0 ) { - flags |= RouteItemHomeStop; - } - return flags; -} - -void PublicTransportModel::setHighlightedStop( const QString& stopName ) -{ - m_info.highlightedStop = stopName; - - if ( !m_items.isEmpty() ) { - emit dataChanged( m_items.first()->index(), m_items.last()->index() ); - } -} - -void DepartureModel::setColorGroups( const ColorGroupSettingsList& colorGroups ) -{ - if ( m_colorGroups == colorGroups ) { - return; // Unchanged - } - m_colorGroups = colorGroups; - - if ( !m_items.isEmpty() ) { - QModelIndex topLeft = m_items.first()->index(); - QModelIndex bottomRight = m_items.last()->index(); - if ( topLeft.isValid() && bottomRight.isValid() ) { - emit dataChanged( topLeft, bottomRight ); - } - } -} - -RouteStopFlags DepartureItem::routeStopFlags( int routeStopIndex, int *minsFromFirstRouteStop ) -{ - RouteStopFlags routeStopFlags; - - const QString stopName = m_departureInfo.routeStops()[ routeStopIndex ]; - if ( routeStopIndex == 0 ) { - routeStopFlags |= RouteStopIsOrigin; - } else if ( routeStopIndex == m_departureInfo.routeStops().count() - 1 ) { - routeStopFlags |= RouteStopIsTarget; - } else { - routeStopFlags |= RouteStopIsIntermediate; - } - - // Get time information - bool isFirstZeroMinuteStop = false; - if ( routeStopIndex < m_departureInfo.routeTimes().count() - && m_departureInfo.routeTimes()[routeStopIndex].isValid() ) - { - const QDateTime time = m_departureInfo.routeTimes()[routeStopIndex]; - int _minsFromFirstRouteStop = - qCeil( m_departureInfo.departure().secsTo(time) / 60.0 ); - - if ( _minsFromFirstRouteStop == 0 ) { - if ( routeStopIndex == 0 ) { - isFirstZeroMinuteStop = true; - } else { - // Check if the previous stop has the same departure time - const QDateTime previousTime = m_departureInfo.routeTimes()[routeStopIndex - 1]; - isFirstZeroMinuteStop = previousTime != time; - } - } - - if ( minsFromFirstRouteStop ) { - *minsFromFirstRouteStop = _minsFromFirstRouteStop; - } - } else if ( minsFromFirstRouteStop ) { - *minsFromFirstRouteStop = -1; - } - if ( m_model->info().homeStop == stopName || isFirstZeroMinuteStop ) { - routeStopFlags |= RouteStopIsHomeStop; - } - if ( m_model->info().highlightedStop == stopName ) { - routeStopFlags |= RouteStopIsHighlighted; - } - - return routeStopFlags; -} - -RouteStopFlags JourneyItem::departureRouteStopFlags( int routeStopIndex, int routeSubStopIndex, - int* minsFromFirstRouteStop ) -{ - return routeStopFlags( routeStopIndex, routeSubStopIndex, - minsFromFirstRouteStop, m_journeyInfo.routeTimesDeparture() ); -} - -RouteStopFlags JourneyItem::arrivalRouteStopFlags( int routeStopIndex, int routeSubStopIndex, - int* minsFromFirstRouteStop ) -{ - return routeStopFlags( routeStopIndex, routeSubStopIndex, - minsFromFirstRouteStop, m_journeyInfo.routeTimesArrival() ); -} - -RouteStopFlags JourneyItem::routeStopFlags( int routeStopIndex, int routeSubStopIndex, - int* minsFromFirstRouteStop, - const QList< QDateTime >& times ) -{ - RouteStopFlags routeStopFlags; - - if ( routeStopIndex == 0 && routeSubStopIndex == 0 ) { - routeStopFlags |= RouteStopIsOrigin; - } else if ( routeStopIndex == m_journeyInfo.routeStops().count() - 1 && - (routeStopIndex >= m_journeyInfo.routeSubJourneys().count() || - routeSubStopIndex == m_journeyInfo.routeSubJourneys()[routeStopIndex].routeStops.count() - 1) ) - { - routeStopFlags |= RouteStopIsTarget; - } else if ( routeSubStopIndex == 0 ) { - routeStopFlags |= RouteStopIsConnectingStop; // TODO | RouteStopIsIntermediate? - } else { - routeStopFlags |= RouteStopIsIntermediate; - } - - // Get time information - int _minsFromFirstRouteStop = -1; - if ( routeStopIndex < times.count() && times[routeStopIndex].isValid() ) { - QDateTime time = times[routeStopIndex]; - _minsFromFirstRouteStop = qCeil( m_journeyInfo.departure().secsTo(time) / 60 ); - } - - const QString stopName = m_journeyInfo.routeStops()[ routeStopIndex ]; - PublicTransportModel *model = qobject_cast( m_model ); - if ( model->info().homeStop == stopName || _minsFromFirstRouteStop == 0 ) { - routeStopFlags |= RouteStopIsHomeStop; - } - if ( model->info().highlightedStop == stopName ) { - routeStopFlags |= RouteStopIsHighlighted; - } - - if ( minsFromFirstRouteStop ) { - *minsFromFirstRouteStop = _minsFromFirstRouteStop; - } - return routeStopFlags; -} diff --git a/applet/departuremodel.h b/applet/departuremodel.h deleted file mode 100644 index d332e11..0000000 --- a/applet/departuremodel.h +++ /dev/null @@ -1,934 +0,0 @@ -/* -* Copyright 2013 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -/** @file - * @brief This file contains models which hold information about public transport journeys/departures/arrivals. - * @author Friedrich Pülz */ - -#ifndef DEPARTUREMODEL_HEADER -#define DEPARTUREMODEL_HEADER - -// Own includes -#include "settings.h" // AlarmSettings(List) - -// libpublictransporthelper includes -#include // Member variable - -// Qt includes -#include // Base class - -/** @brief Holds information about settings from the applet. */ -struct Info { - Info() { - departureArrivalListType = DepartureList; - linesPerRow = 2; - departureTimeFlags = Settings::DefaultDepartureTimeFlags; - sizeFactor = 1.0f; - alarmMinsBeforeDeparture = 5; - currentStopSettingsIndex = -1; - }; - - AlarmSettingsList alarm; - DepartureArrivalListType departureArrivalListType; - int linesPerRow, alarmMinsBeforeDeparture, currentStopSettingsIndex; - Settings::DepartureTimeFlags departureTimeFlags; - float sizeFactor; - QString homeStop; - QString highlightedStop; - QStringList providerFeatures; -}; - -/** - * @brief Child item types. - * - * @ingroup models */ -enum ItemType { - OtherItem, /**< For children of child items. */ - PlatformItem, /**< The item contains the platform */ - JourneyNewsItem, /**< The item contains the journey news */ - DelayItem, /**< The item contains the delay */ - OperatorItem, /**< The item contains the operator name */ - RouteItem, /**< The item contains a list of stops in the route - * (to the destination for departures / arrivals) */ - - DurationItem, /**< The items contains the duration in minutes of a journey */ - ChangesItem, /**< The item contains the number of changes of a journey */ - PricingItem /**< The item contains the pricing of a journey */ -}; - -/** - * @brief Columns for the departure/journey model. - * - * @ingroup models */ -enum Columns { - ColumnLineString = 0, /**< The column showing the line string. */ - ColumnTarget = 1, /**< The column showing the target/origin. */ - ColumnDeparture = 2, /**< The column showing the departure/arrival time. */ - - ColumnJourneyInfo = ColumnTarget, /**< The column showing information about a journey. */ - ColumnArrival = 3 /**< The column showing the arrival time of a journey. */ -}; - -class ChildItem; -class PublicTransportModel; - -/** - * @brief Base class for items of PublicTransportModel. - * - * @ingroup models - **/ -class ItemBase { - friend class PublicTransportModel; - friend class DepartureModel; - friend class JourneyModel; - -public: - ItemBase( const Info *info ); - virtual ~ItemBase(); - - /** @returns the parent item of this item, or 0 if this item is a - * toplevel item. */ - ItemBase *parent() const { return m_parent; }; - - /** @returns the toplevel parent item of this item, or a pointer to this - * item if it is a toplevel item itself. Never returns 0. */ - ItemBase *topLevelParent() const; - - const Info *info() const { return m_info; }; - - /** @returns the first child item with the given @p itemType. */ - ChildItem *childByType( ItemType itemType ) const; - - /** @returns the child item in the given @p row.. */ - ChildItem *child( int row ) const { return m_children[row]; }; - - /** @returns a list of all child items. */ - QList< ChildItem* > children() const { return m_children; }; - - /** @returns the number of child items. */ - int childCount() const { return m_children.count(); }; - - /** @brief Removes the given @p child item. */ - void removeChild( ChildItem *child ); - - /** @brief Appends the given @p child item. */ - void appendChild( ChildItem *child ); - - /** @returns the QModelIndex of this item. */ - QModelIndex index(); - - /** @returns the row of this item. */ - virtual int row() const = 0; - - /** @returns the data of this item in the given @p role at the given @p column. */ - virtual QVariant data( int role = Qt::DisplayRole, int column = 0 ) const = 0; - - /** @returns a pointer to the model of this item if any. */ - PublicTransportModel *model() const { return m_model; }; - - /** @brief Sets the model of this item to @p model. */ - void setModel( PublicTransportModel *model ); - - /** - * @brief Whether or not this item has a pending alarm. - * - * The default implementation always returns false. - **/ - virtual bool hasPendingAlarm() const { return false; }; - - /** - * @brief Updates remaining time values in the departure/arrival column. - * - * The default implementation does nothing. - **/ - virtual void updateTimeValues() {}; - -protected: - /** @brief Removes @p count child items beginning with the child item at row @p first. */ - void removeChildren( int first, int count ); - - /** @brief Appends the given @p child item, called from the model. */ - void appendChildFromModel( ChildItem *child ); - - ItemBase *m_parent; - PublicTransportModel *m_model; - QList< ChildItem* > m_children; - const Info *m_info; -}; - -/** - * @brief A child item in PublicTransportModel. - * - * @ingroup models - **/ -class ChildItem : public ItemBase { -public: - ChildItem( ItemType itemType, const QString &formattedText, const QIcon &icon, const Info *info ); - ChildItem( ItemType itemType, const QString &formattedText, const Info *info ); - ChildItem( ItemType itemType, const Info *info ); - - /** The row of this child item in it's parent. */ - virtual int row() const { - return m_parent ? m_parent->children().indexOf( const_cast< ChildItem* >(this) ) : -1; }; - - /** @brief The type of this child item. */ - ItemType type() const { return m_type; }; - - virtual QVariant data( int role = Qt::DisplayRole, int = 0 ) const; - void setData( const QVariant &data, int role = Qt::UserRole ); - - /** @brief Gets the text of this child item. */ - inline QString text() const { - return m_data.value( Qt::DisplayRole ).toString(); }; - - /** - * @brief Sets the text of this child item to @p text. - * - * Views can ignore this unformatted text if a formatted text is set. - * @see setFormattedText - **/ - inline void setText( const QString &text ) { - setData( text, Qt::DisplayRole ); }; - - /** @brief Gets the formatted text of this child item. */ - inline QString formattedText() const { - return m_data.value( FormattedTextRole ).toString(); }; - - /** - * @brief Sets the formatted text of this child item to @p text. - * - * @param text can contain HTML tags. Views can ignores the unformatted text (stored in - * Qt::DisplayRole) if a formatted text is set. - * - * @see setText - **/ - inline void setFormattedText( const QString &text ) { - setData( text, FormattedTextRole ); }; - - /** @brief Gets the icon of this child item. */ - virtual inline QIcon icon() const { - return m_data.value( Qt::DecorationRole ).value< QIcon >(); }; - - /** @brief Sets the icon of this child item to @p icon. */ - inline void setIcon( const QIcon &icon ) { - setData( icon, Qt::DecorationRole ); }; - -protected: - QHash< int, QVariant > m_data; - ItemType m_type; -}; - -/** - * @brief Base class for top level items in PublicTransportModel. - * - * @ingroup models - **/ -class TopLevelItem : public QObject, public ItemBase { - Q_OBJECT - -public: - TopLevelItem( const Info *info ); - - /** @brief Set the data in @p column to @p data for the given @p role. */ - virtual void setData( Columns column, const QVariant &data, int role = Qt::UserRole ); - - /** - * @brief Get the name of the data source in which this item is included. - * This is only useful if there are multiple stops combined into one timetable. - * The default implementation returns a default constructed QString. - **/ - virtual QString dataSource() const { return QString(); }; - - /** @brief Gets the text of the given @p column. */ - inline QString text( Columns column = ColumnLineString ) const { - return m_columnData.value( column )[ Qt::DisplayRole ].toString(); }; - - /** - * @brief Sets the text of the given @p column to @p text. - * - * Views can ignore this unformatted text if a formatted text is set. - * - * @see setFormattedText - **/ - inline void setText( Columns column, const QString &text ) { - setData( column, text, Qt::DisplayRole ); }; - - /** @brief Gets the formatted text of the given @p column. */ - inline QString formattedText( Columns column = ColumnLineString ) const { - return m_columnData.value( column )[ FormattedTextRole ].toString(); }; - - /** - * @brief Sets the formatted text of the given @p column to @p text. - * - * @param text can contain HTML tags. The @ref HtmlDelegate ignores the - * unformatted text (stored in Qt::DisplayRole) if a formatted text is set. - * - * @see setText - **/ - inline void setFormattedText( Columns column, const QString &text ) { - setData( column, text, FormattedTextRole ); }; - - /** @brief Gets the icon of the given @p column. */ - inline QIcon icon( Columns column = ColumnLineString ) const { - return m_columnData.value( column )[ Qt::DecorationRole ].value< QIcon >(); }; - - /** @brief Sets the icon of the given @p column to @p icon. */ - inline void setIcon( Columns column, const QIcon &icon ) { - setData( column, icon, Qt::DecorationRole ); }; - - /** - * @returns the current alarm states. - * - * @see AlarmStates - **/ - AlarmStates alarmStates() const { return m_alarm; }; - - /** @brief Whether or not this departure/journey has an alarm (pending or fired). */ - bool hasAlarm() const { return m_alarm.testFlag(AlarmPending) || m_alarm.testFlag(AlarmFired); }; - - /** @brief Whether or not this departure/journey has a pending alarm. */ - virtual bool hasPendingAlarm() const { return m_alarm.testFlag(AlarmPending); }; - - /** @brief Gets the date and time at which an alarm for this departure/journey should be fired. */ - virtual QDateTime alarmTime() const = 0; - -protected: - QHash< int, QHash > m_columnData; - AlarmStates m_alarm; -}; - -/** - * @brief An item which automatically creates/updates child items according to the information in @ref journeyInfo. - * - * To update this item and it's child items call @ref setJourneyInfo. - * To only update remaining time values, call @ref updateTimeValues. - * - * @ingroup models - **/ -class JourneyItem : public TopLevelItem { - Q_OBJECT - -public: - JourneyItem( const JourneyInfo &journeyInfo, const Info *info ); - - /** @brief The row of this journey item in the model. */ - virtual int row() const; - - /** @returns the information that this item currently shows. */ - const JourneyInfo *journeyInfo() const { return &m_journeyInfo; }; - - RouteStopFlags departureRouteStopFlags( int routeStopIndex = 0, int routeSubStopIndex = 0, - int *minsFromFirstRouteStop = 0 ); - RouteStopFlags arrivalRouteStopFlags( int routeStopIndex = 0, int routeSubStopIndex = 0, - int *minsFromFirstRouteStop = 0 ); - - /** @returns a hash with child items by their type. */ - QHash< ItemType, ChildItem* > typedChildren() const; - - /** @brief Gets the data for the given @p role in @p column. */ - virtual QVariant data( int role = Qt::DisplayRole, int column = 0 ) const; - - /** @brief Updates remaining time values in the departure/arrival column. */ - virtual void updateTimeValues(); - - /** @brief Updates this item and all it's child items according to the inforamtion - * in @p journeyInfo. */ - void setJourneyInfo( const JourneyInfo &journeyInfo ); - - /** @brief Sets the alarm states. */ - void setAlarmStates( AlarmStates alarmStates ); - - /** @brief Gets the date and time at which an alarm for this journey should be fired. */ - virtual QDateTime alarmTime() const { - return m_journeyInfo.departure().addSecs( // TODO predictedDeparture() with delay - -m_info->alarmMinsBeforeDeparture * 60 ); - }; - -protected: - /** @brief Updates this item. */ - void updateValues(); - - /** @brief Creates and adds all children for the current data. */ - void createChildren(); - - /** @brief Updates all child items, add/removes children if necessary. */ - void updateChildren(); - - /** @brief Adds a new child item of the given @p itemType. */ - ChildItem *appendNewChild( ItemType itemType ); - - /** @brief Updates the given @p child item which is of type @p itemType. */ - void updateChild( ItemType itemType, ChildItem *child ); - - /** @returns true, if there's data to be shown for the given @p itemType. */ - bool hasDataForChildType( ItemType itemType ); - - /** - * @param itemType The child item type to get the display text for. - * - * @param linesPerRow The number of lines for the new item is put here, if it's not 0. - * - * @returns the text to be displayed for the given @p itemType. - **/ - QString childItemText( ItemType itemType, int *linesPerRow = 0 ); - - /** @brief Creates a route item with one child item for each route stop. */ - ChildItem *createRouteItem(); - - /** - * @brief A rating value between 0 (best) and 1 (worst). - * - * Journeys are rated by their duration and the needed changes relative - * to the global maximal/minimal duration/changes of the model. - **/ - qreal rating() const; - - RouteStopFlags routeStopFlags( int routeStopIndex, int routeSubStopIndex, - int *minsFromFirstRouteStop, const QList × ); - - JourneyInfo m_journeyInfo; -}; - -/** - * @brief An item which automatically creates/updates child items according - * to the information in @ref departureInfo. - * - * To update this item and it's child items call @ref setDepartureInfo. To only update remaining - * time values, call @ref updateTimeValues. - * - * @ingroup models - **/ -class DepartureItem : public TopLevelItem { - Q_OBJECT - Q_PROPERTY( qreal alarmColorIntensity READ alarmColorIntensity WRITE setAlarmColorIntensity ) - -public: - enum Flag { - NoFlags = 0x00, - IsLeavingSoon = 0x01 - }; - Q_DECLARE_FLAGS( Flags, Flag ) - - DepartureItem( const DepartureInfo &departureInfo, const Info *info ); - - /** @brief The row of this departure item in the model. */ - virtual int row() const; - - /** @returns the information that this item currently shows. */ - const DepartureInfo *departureInfo() const { return &m_departureInfo; }; - - RouteStopFlags routeStopFlags( int routeStopIndex = 0, int *minsFromFirstRouteStop = 0 ); - - /** @brief Gets the data for the given @p role in @p column. */ - virtual QVariant data( int role = Qt::DisplayRole, int column = 0 ) const; - - virtual QString dataSource() const { return m_departureInfo.dataSource(); }; - int dataSourceIndex() const { return m_departureInfo.index(); }; - - bool isLeavingSoon() const { return m_flags.testFlag(IsLeavingSoon); }; - void setLeavingSoon( bool leavingSoon = true ); - - bool includesAdditionalData() const { return m_departureInfo.includesAdditionalData(); }; - bool isWaitingForAdditionalData() const { return m_departureInfo.isWaitingForAdditionalData(); }; - - /** @brief Sets the alarm states. */ - void setAlarmStates( AlarmStates alarmStates ); - - /** @brief Gets the date and time at which an alarm for this departure should be fired. */ - virtual QDateTime alarmTime() const { - return m_departureInfo.predictedDeparture().addSecs( - -m_info->alarmMinsBeforeDeparture * 60 ); - }; - - /** @brief Sets an alarm for this departure. */ - void setAlarm(); - - /** @brief Removes a possibly set alarm. */ - void removeAlarm(); - - qreal alarmColorIntensity() const { return m_alarmColorIntensity; }; - void setAlarmColorIntensity( qreal alarmColorIntensity ); - - /** @brief Updates remaining time values in the departure column. */ - virtual void updateTimeValues(); - - /** @brief Updates this item and all it's child items according to the inforamtion - * in @p departureInfo. */ - void setDepartureInfo( const DepartureInfo &departureInfo ); - -protected: - /** @brief Updates this item. */ - void updateValues(); - - /** @brief Creates and adds all children for the current data. */ - void createChildren(); - - /** @brief Updates all child items, add/removes children if necessary. */ - void updateChildren(); - - /** @brief Adds a new child item of the given @p itemType. */ - ChildItem *appendNewChild( ItemType itemType ); - - /** @brief Updates the given @p child item which is of type @p itemType. */ - void updateChild( ItemType itemType, ChildItem *child, int childIndex = -1 ); - - /** @returns true, if there's data to be shown for the given @p itemType. */ - bool hasDataForChildType( ItemType itemType ); - - /** - * @param itemType The child item type to get the display text for. - * - * @param linesPerRow The number of lines for the new item is put here, if it's not 0. - * - * @returns the text to be displayed for the given @p itemType. - **/ - QString childItemText( ItemType itemType, int *linesPerRow = 0 ); - - /** @brief Creates a route item with one child item for each route stop. */ - ChildItem *createRouteItem(); - - DepartureInfo m_departureInfo; - qreal m_alarmColorIntensity; - Flags m_flags; -}; - -/** - * @brief Base class for DepartureModel and JourneyModel. - * - * @ingroup models - **/ -class PublicTransportModel : public QAbstractItemModel { - Q_OBJECT - friend class ItemBase; - -public: - PublicTransportModel( QObject *parent = 0 ); - virtual ~PublicTransportModel() { qDeleteAll( m_items ); }; - - /** - * @brief Gets the QModelIndex of the item at @p row and @p column. - * - * @param row The row of the index to get. - * - * @param column The column of the index to get. - * - * @param parent The parent index of the index to be returned or an invalid QModelIndex, to get - * the index of a toplevel item. Defaults to QModelIndex(). - * - * @return The QModelIndex of the item at @p row and @p column. - **/ - virtual QModelIndex index( int row, int column, const QModelIndex& parent = QModelIndex() ) const; - - /** - * @brief Gets the QModelIndex of the given @p item. - * - * @param item The item to get the index for. - * - * @return The QModelIndex for the given @p item. - **/ - virtual QModelIndex index( ItemBase *item ) const { - return createIndex( item->row(), 0, item ); }; - - /** - * @brief Gets the number of rows for the given @p parent in this model. - * - * @param parent The parent, which row count should be returned. - * Defaults to QModelIndex(), ie. the toplevel row count. - * - * @return The number of rows for the given @p parent in this model. - **/ - virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const; - - /** - * @brief Gets the index of the parent item for the given @p child index. - * - * @param child The index of a child item of the parent to be returned - * - * @return The QModelIndex of the parent of the given @p child. - **/ - virtual QModelIndex parent( const QModelIndex& child ) const; - - /** @brief Gets the data for the given @p role in @p column. */ - virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; - - /** - * @brief Gets the toplevel item at @p row. - * - * @returns a pointer to the toplevel item at the given @p row. - **/ - ItemBase *item( int row ) const { return m_items.at(row); }; - - /** - * @brief Gets the toplevel item at the given @p index. - * - * @returns a pointer to the toplevel item at the given @p index. - **/ - ItemBase *itemFromIndex( const QModelIndex &index ) const; - - QModelIndex indexFromItem( ItemBase *item, int column = 0 ) const; - - /** - * @brief Gets the row of the given toplevel item. - * - * @param item The item which row should be returned. - * - * @return The row of the given @p item or -1, if it isn't a toplevel item of this model. - **/ - int rowFromItem( ItemBase *item ); - - /** - * @brief Gets a list of all toplevel items of this model. - * - * @return The list of toplevel items. - **/ - QList< ItemBase* > items() const { return m_items; }; - - /** - * @brief Removes the given @p item from the model. - * - * @param item The item to be removed. - * - * @return True, if the item was successfully removed. False, otherwise. - * Removing may fail, eg. if it isn't inside the model. - **/ - virtual bool removeItem( ItemBase *item ) { - return removeRows( item->row(), 1 ); }; - - /** @brief Removes all items from the model, but doesn't clear header data. */ - virtual void clear(); - - /** - * @brief Checks whether or not this model is empty. - * - * @returns True, if this model has no items, ie. is empty. False, otherwise. - **/ - inline bool isEmpty() const { return rowCount() == 0; }; - - /** @brief Gets a structure containing some settings. */ - Info info() const { return m_info; }; - - void setAlarmMinsBeforeDeparture( int alarmMinsBeforeDeparture = 5 ) { - m_info.alarmMinsBeforeDeparture = alarmMinsBeforeDeparture; }; - - void setLinesPerRow( int linesPerRow ); - - void setSizeFactor( float sizeFactor ); - - void setDepartureColumnSettings( Settings::DepartureTimeFlags flags = Settings::DefaultDepartureTimeFlags ); - - void setHomeStop( const QString &homeStop ) { - m_info.homeStop = homeStop; - }; - - void setProviderFeatures( const QStringList &providerFeatures ); - - /** - * @brief Returns the currently highlighted stop or an empty string, if no stop is - * currently highlighted. - **/ - QString highlightedStop() const { return m_info.highlightedStop; }; - - /** - * @brief Gets the flags for route stop item with the given @p stopName. - * - * @param stopName The stop name which is associated with the route stop - * item to get flags for. - * - * @returns The flags for the route stop item, which is associated with the - * given @p stopName. - **/ - RouteItemFlags routeItemFlags( const QString &stopName ) const; - - /** - * @brief Notifies the model about changes in the given @p item. - * - * @param item The item which data has changed. - * - * @param columnLeft The first changed column. Defaults to 0. - * - * @param columnRight The last changed column Defaults to 0. - **/ - void itemChanged( ItemBase *item, int columnLeft = 0, int columnRight = 0 ); - - /** - * @brief Notifies the model about changes in children of the given @p parentItem. - * - * @param parentItem The item, which children were changed. - **/ - void childrenChanged( ItemBase *parentItem ); - -signals: - /** - * @brief The @p items will get removed after this signal was emitted. - * - * @param items Can be a list of DepartureItems or JourneyItems, that will - * get removed after this signal was emitted. - **/ - void itemsAboutToBeRemoved( const QList &items ); - -public slots: - /** - * @brief Sets the route stop item to be highlighted. - * - * @param stopName The stop name associated with the route stop item - * to highlight. If this is empty (default), the currently highlighted - * stop gets unhighlighted. - **/ - void setHighlightedStop( const QString &stopName = QString() ); - -protected slots: - void startUpdateTimer(); - - /** @brief Called each full minute. */ - virtual void update() = 0; - -protected: - void callAtNextFullMinute( const char *member ); - virtual ItemBase *findNextItem( bool sortedByTimeAscending = false ) const = 0; - void appendChild( ItemBase *parent, ChildItem *child ); - - QList< ItemBase* > m_items; - QHash< uint, ItemBase* > m_infoToItem; - ItemBase *m_nextItem; - - Info m_info; - QTimer *m_updateTimer; -}; - -class QTimer; - -/** - * @brief A model for departure items. - * - * @ingroup models - **/ -class DepartureModel : public PublicTransportModel { - Q_OBJECT - friend class ItemBase; - -public: - DepartureModel( QObject *parent = 0 ); - - /** - * @brief Gets the number of columns for the given @p parent in this model. - * - * @param parent The parent, which column count should be returned. Defaults to QModelIndex(), - * ie. the toplevel column count. - * - * @return The number of columns for the given @p parent in this model. - **/ - virtual int columnCount( const QModelIndex& parent = QModelIndex() ) const; - - /** - * @brief Removes @p count items beginning at @p row from @p parent. - * - * @param row The row of the first item to be removed. - * @param count The number of items to be removed, beginning at @p row. - * @param parent The QModelIndex parent which child items should be removed or an invalid - * model index to remove toplevel items. Defaults to QModelIndex(). - * - * @return True, if the items were successfully removed from the model. False, otherwise. - **/ - virtual bool removeRows( int row, int count, const QModelIndex& parent = QModelIndex() ); - virtual QVariant headerData( int section, Qt::Orientation orientation, - int role = Qt::DisplayRole ) const; - virtual void sort( int column, Qt::SortOrder order = Qt::AscendingOrder ); - - ItemBase *itemFromInfo( const DepartureInfo &info ) const { - return m_infoToItem.contains(info.hash()) ? m_infoToItem[info.hash()] : 0; }; - QModelIndex indexFromInfo( const DepartureInfo &info ) const { - return m_infoToItem.contains(info.hash()) ? m_infoToItem[info.hash()]->index() : QModelIndex(); }; - - /** - * @brief A list of hashes of all departure items. - * - * Hashes can be retrieved using qHash or @ref DepartureInfo::hash. - **/ - QList< uint > itemHashes() const; - - QList< DepartureInfo > departureInfos() const; - - /** - * @brief Gets all stop names found in the current data of this model. - * - * If available, route stop names are also added to the returned string list. - * - * @param maxDepartureCount -1 to add all stop names found in all departures in this model. - * >= 0 to stop adding stop names of departures after the departure with the index - * @p maxDepartureCount. - **/ - QStringList allStopNames( int maxDepartureCount = -1 ) const; - - virtual DepartureItem *addItem( const DepartureInfo &departureInfo, - Columns sortColumn = ColumnDeparture, - Qt::SortOrder sortOrder = Qt::AscendingOrder ); - /** - * @brief Updates the given @p departureItem with the given @p newDepartureInfo. - * - * This simply calls setDepartureInfo() on @p deoartureItem. - **/ - virtual void updateItem( DepartureItem *departureItem, const DepartureInfo &newDepartureInfo ) { - departureItem->setDepartureInfo( newDepartureInfo ); }; - - /** @brief Removes all departures from the model, but doesn't clear header data. */ - virtual void clear(); - - /** @brief Gets a structure containing some settings. */ - Info info() const { return m_info; }; - - void setDepartureArrivalListType( DepartureArrivalListType departureArrivalListType ); - void setCurrentStopIndex( int currentStopSettingsIndex ); - void setAlarmSettings( const AlarmSettingsList &alarm ); - - /** @brief Whether or not there are pending alarms. */ - bool hasAlarms() const { return !m_alarms.isEmpty(); }; - - /** @brief The number of pending alarms. */ - int alarmCount() const { return m_alarms.count(); }; - - /** @brief The date and time of the next alarm or a null QDateTime if there's no pending alarm. */ - QDateTime nextAlarmTime() const { - return m_alarms.isEmpty() ? QDateTime() : m_alarms.keys().first(); }; - - /** @brief The departure with the next alarm or 0 if there's no pending alarm. */ - DepartureItem *nextAlarmDeparture() const { - return m_alarms.isEmpty() ? 0 : m_alarms.values().first(); }; - - /** @brief A map with all pending alarms. There can be multiple departures for each alarm time. */ - const QMultiMap< QDateTime, DepartureItem* > *alarms() const { - return &m_alarms; }; - - /** @brief Adds an alarm for the given @p item. */ - void addAlarm( DepartureItem *item ); - - /** @brief Removes an alarm from the given @p item. */ - void removeAlarm( DepartureItem *item ); - - void setColorGroups( const ColorGroupSettingsList &colorGroups ); - ColorGroupSettingsList colorGroups() const { return m_colorGroups; }; - -signals: - /** @brief The alarm for @p item has been fired. */ - void alarmFired( DepartureItem *item, const AlarmSettings &alarm ); - - void updateAlarms( const AlarmSettingsList &newAlarmSettings, const QList &removedAlarms ); - - /** - * @brief The @p departures have just left. - * - * The DepartureItems for the given @p departures were deleted before emitting this signal. - **/ - void departuresLeft( const QList &departures ); - -protected slots: - /** - * @brief Updates time values, checks for alarms and sorts out old departures. - * - * Called each full minute. */ - virtual void update(); - - void removeLeavingDepartures(); - - void alarmItemDestroyed( QObject *item ); - -private: - virtual DepartureItem *findNextItem( bool sortedByDepartureAscending = false ) const; - void fireAlarm( const QDateTime& dateTime, DepartureItem* item ); - - QMultiMap< QDateTime, DepartureItem* > m_alarms; - ColorGroupSettingsList m_colorGroups; // A list of color groups for the current stop -}; - -/** - * @brief A model for journey items. - * @ingroup models */ -class JourneyModel : public PublicTransportModel { - Q_OBJECT - friend class ItemBase; - -public: - JourneyModel( QObject *parent = 0 ); - - virtual int columnCount( const QModelIndex& parent = QModelIndex() ) const; - virtual bool removeRows( int row, int count, const QModelIndex& parent = QModelIndex() ); - - /** @brief Removes all journeys from the model, but doesn't clear header data. */ - virtual void clear(); - - virtual QVariant headerData( int section, Qt::Orientation orientation, - int role = Qt::DisplayRole ) const; - virtual void sort( int column, Qt::SortOrder order = Qt::AscendingOrder ); - - ItemBase *itemFromInfo( const JourneyInfo &info ) const { - return m_infoToItem.contains(info.hash()) ? m_infoToItem[info.hash()] : 0; }; - QModelIndex indexFromInfo( const JourneyInfo &info ) const { - return m_infoToItem.contains(info.hash()) ? m_infoToItem[info.hash()]->index() : QModelIndex(); }; - - /** - * @brief A list of hashes of all departure items. - * - * Hashes can be retrieved using qHash or @ref JourneyInfo::hash. */ - QList< uint > itemHashes() const; - - virtual JourneyItem *addItem( const JourneyInfo &journeyInfo, - Columns sortColumn = ColumnDeparture, Qt::SortOrder sortOrder = Qt::AscendingOrder ); - virtual void updateItem( JourneyItem *journeyItem, const JourneyInfo &newJourneyInfo ) { - journeyItem->setJourneyInfo( newJourneyInfo ); }; - - /** @brief Gets a structure containing some settings. */ - Info info() const { return m_info; }; - - void setDepartureArrivalListType( DepartureArrivalListType departureArrivalListType ); - void setCurrentStopIndex( int currentStopSettingsIndex ); - void setAlarmSettings( const AlarmSettingsList &alarm ); - - /** - * @brief Gets the smallest duration in minutes of a journey in this model. - * - * @return The smallest duration in minutes of a journey in this model. - **/ - int smallestDuration() const { return m_smallestDuration; }; - - /** @brief Gets the biggest duration in minutes of a journey in this model. - * @return The biggest duration in minutes of a journey in this model. - **/ - int biggestDuration() const { return m_biggestDuration; }; - - /** @brief Gets the smallest number of changes of a journey in this model. - * @return The smallest number of changes of a journey in this model. - **/ - int smallestChanges() const { return m_smallestChanges; }; - - /** @brief Gets the biggest number of changes of a journey in this model. - * @return The biggest number of changes of a journey in this model. - **/ - int biggestChanges() const { return m_biggestChanges; }; - -protected slots: - virtual void update(); - -private: - virtual JourneyItem *findNextItem( bool sortedByDepartureAscending = false ) const; - void updateItemAlarm( JourneyItem *journeyItem ); - - int m_smallestDuration, m_biggestDuration; - int m_smallestChanges, m_biggestChanges; -}; - -#endif // Multiple inclusion guard diff --git a/applet/departurepainter.cpp b/applet/departurepainter.cpp deleted file mode 100644 index c009911..0000000 --- a/applet/departurepainter.cpp +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "departurepainter.h" - -// Own includes -#include "departuremodel.h" -#include "popupicon.h" - -// libpublictransporthelper includes -#include -#include - -// KDE+Plasma includes -#include -#include -#include -#include -#include - -// Qt includes -#include -#include - -DeparturePainter::DeparturePainter( QObject *parent ) - : QObject(parent), m_pixmapCache(new KPixmapCache("DeparturePainter")), m_svg(0) -{ -} - -DeparturePainter::~DeparturePainter() -{ - delete m_pixmapCache; -} - -QString DeparturePainter::iconKey( VehicleType vehicle, DeparturePainter::VehicleIconFlags flags ) -{ - QString vehicleKey; - switch ( vehicle ) { - case Tram: vehicleKey = "tram"; break; - case Bus: vehicleKey = "bus"; break; - case TrolleyBus: vehicleKey = "trolleybus"; break; - case Subway: vehicleKey = "subway"; break; - case Metro: vehicleKey = "metro"; break; - case InterurbanTrain: vehicleKey = "interurbantrain"; break; - case RegionalTrain: vehicleKey = "regionaltrain"; break; - case RegionalExpressTrain: vehicleKey = "regionalexpresstrain"; break; - case InterregionalTrain: vehicleKey = "interregionaltrain"; break; - case IntercityTrain: vehicleKey = "intercitytrain"; break; - case HighSpeedTrain: vehicleKey = "highspeedtrain"; break; - case Feet: vehicleKey = "feet"; break; - case Ship: vehicleKey = "ship"; break; - case Plane: vehicleKey = "plane"; break; - default: - kDebug() << "Unknown vehicle type" << vehicle; - return QString(); - } - - // Use monochrome (mostly white) icons - if ( flags.testFlag(MonochromeIcon) ) { - vehicleKey.append( "_white" ); - } - if ( flags.testFlag(EmptyIcon) ) { - vehicleKey.append( "_empty" ); - } - - return vehicleKey; -} - -DeparturePainter::VehicleIconFlags DeparturePainter::iconFlagsFromIconDrawFlags( - DeparturePainter::VehicleIconDrawFlags flags ) -{ - VehicleIconFlags iconFlags = ColoredIcon; - if ( flags.testFlag(DrawMonochromeIcon) ) { - iconFlags |= MonochromeIcon; - } - if ( flags.testFlag(DrawTransportLine) ) { - iconFlags |= EmptyIcon; - } - return iconFlags; -} - -void DeparturePainter::paintVehicle( QPainter* painter, VehicleType vehicle, - const QRectF& rect, const QString& transportLine, int minutesUntilDeparture, - VehicleIconDrawFlags iconDrawFlags ) -{ - const bool drawTransportLine = iconDrawFlags.testFlag(DrawTransportLine) - && !transportLine.isEmpty() - && PublicTransport::Global::generalVehicleType(vehicle) == LocalPublicTransport; - if ( !drawTransportLine ) { - // Remove possibly set flag, if the transport line cannot be drawn - iconDrawFlags &= ~DrawTransportLine; - } - const VehicleIconFlags iconFlags = iconFlagsFromIconDrawFlags( iconDrawFlags ); - const QString vehicleKey = iconKey( vehicle, iconFlags ); - const QString vehicleCacheKey = vehicleKey + QString("%1%2%3%4") - .arg( static_cast(iconDrawFlags) ).arg( drawTransportLine ? transportLine : "" ) - .arg( int(rect.width()) ).arg( int(rect.height()) ); - const int shadowWidth = qBound( 2, int(rect.width() / 20), 4 ); - - QPixmap vehiclePixmap; - if ( !m_pixmapCache->find(vehicleCacheKey, vehiclePixmap) ) { - vehiclePixmap = QPixmap( int(rect.width()), int(rect.height()) ); - vehiclePixmap.fill( Qt::transparent ); - QPainter p( &vehiclePixmap ); - p.setRenderHint( QPainter::Antialiasing ); - - // Resize vehicle type icon SVG and draw it - m_svg->resize( rect.width() - shadowWidth, rect.height() - shadowWidth ); - m_svg->paint( &p, shadowWidth / 2, shadowWidth / 2, vehicleKey ); - - // Draw transport line string (only for local public transport) - if ( drawTransportLine ) { - QString text; - if ( transportLine.length() > 8 ) { - // The transport line string is too long to be drawn inside a vehicle icon - const QStringList words = transportLine.split( QRegExp("[ \\-_\\+&/\\\\]"), - QString::SkipEmptyParts ); - if ( words.count() == 1 ) { - // No spaces in the transport line string, remove all lower case letters - text = words[0]; - text.remove( QRegExp("[a-z]+") ); - if ( text.length() > 8 ) { - // Still more than eight characters, cut the string - text = text.left( 8 ); - } - } else { - // Multiple words in the transport line string, - // create an abbreviation by only using the first letter of each word - foreach ( const QString &word, words ) { - text += word[0]; // Empty parts are skipped, therefore word is not empty - } - } - } else { - text = transportLine; - text.replace(' ', QString()); - } - - QFont font = Plasma::Theme::defaultTheme()->font( Plasma::Theme::DefaultFont ); - font.setBold( true ); - if ( text.length() > 2 ) { - font.setPixelSize( qMax(8, qCeil(1.18 * rect.width() / text.length())) ); - } else { - font.setPixelSize( rect.width() * 0.5 ); - } - p.setFont( font ); - QFontMetrics fm( font ); - if ( iconDrawFlags.testFlag(DrawMonochromeIcon) ) { - // For monochrome icons, draw white text with a dark gray outline - QPen textOutlinePen( QColor(0, 0, 0, 100) ); - textOutlinePen.setWidthF( qMin(qreal(10.0), qreal(font.pixelSize() / 5.0)) ); - textOutlinePen.setCapStyle( Qt::RoundCap ); - textOutlinePen.setJoinStyle( Qt::RoundJoin ); - QPainterPath textPath; - textPath.addText( rect.left() + (rect.width() - fm.width(text)) / 2.0, - rect.bottom() - (rect.height() - fm.ascent() + fm.descent()) / 2.0, - font, text ); - p.setPen( textOutlinePen ); - p.drawPath( textPath ); // Takes much time - p.fillPath( textPath, Qt::white ); - } else { - // For colored icons simply draw white text - p.setPen( Qt::white ); - p.drawText( rect, text, QTextOption(Qt::AlignCenter) ); - } - } - p.end(); - - // Insert rendered vehicle icon into the pixmap cache - m_pixmapCache->insert( vehicleCacheKey, vehiclePixmap ); - } - - // Make a part 70% transparent, dependent on minsToDeparture - if ( iconDrawFlags.testFlag(DrawTimeGraphics) && minutesUntilDeparture > 0 ) { - // Draw graphical indication for the time until departure/arrival - QPixmap polygonPixmap; - const QString polygonCacheKey = QString("polygon%1%2%3") - .arg( qMin(minutesUntilDeparture, int(MAX_MINUTES_UNTIL_DEPARTURE)) ) - .arg( vehiclePixmap.width() ).arg( vehiclePixmap.height() ); - - if ( !m_pixmapCache->find(polygonCacheKey, polygonPixmap) ) { - polygonPixmap = QPixmap( vehiclePixmap.width(), vehiclePixmap.height() ); - if ( minutesUntilDeparture >= MAX_MINUTES_UNTIL_DEPARTURE ) { - // Make the whole SVG in pixmap 70% transparent - polygonPixmap.fill( QColor(0, 0, 0, 77) ); - } else { - // Construct a polygon for the transparency effect visualizing minsToDeparture - QPolygon polygon; - - // All parts begin with these two points - const qreal half = polygonPixmap.width() / 2.0; - const qreal a = (8.0 * minutesUntilDeparture) / MAX_MINUTES_UNTIL_DEPARTURE; - polygon.append( QPoint(half, half) ); // middle - polygon.append( QPoint(half, 0) ); // top middle - if ( minutesUntilDeparture > MAX_MINUTES_UNTIL_DEPARTURE / 8 ) { - // Paint at least 1/8 black (add point at the top left edge) - polygon.append( QPoint(0, 0) ); // top left - if ( minutesUntilDeparture > MAX_MINUTES_UNTIL_DEPARTURE * 3 / 8 ) { - // Paint at least 3/8 black (add point at the bottom left edge) - polygon.append( QPoint(0, polygonPixmap.height()) ); // bottom left - if ( minutesUntilDeparture > MAX_MINUTES_UNTIL_DEPARTURE * 5 / 8 ) { - // Paint at least 5/8 black (add point at the bottom right edge) - polygon.append( QPoint(polygonPixmap.width(), polygonPixmap.height()) ); - if ( minutesUntilDeparture > MAX_MINUTES_UNTIL_DEPARTURE * 7 / 8 ) { - // Paint [7/8, 8/8] black (add point on the right half of the top side) - polygon.append( QPoint(polygonPixmap.width(), 0) ); // top right - polygon.append( QPoint(half * (9.0 - a), 0) ); - } else { - // Paint [6/8, 7/8[ black (add point on the right side) - polygon.append( QPoint(polygonPixmap.width(), half * (7.0 - a)) ); - } - } else { - // Paint [3/8, 5/8[ black (add point on the bottom side) - polygon.append( QPoint(half * (a - 3.0), polygonPixmap.height()) ); - } - } else { // if ( minsToDeparture > maxMinsToDeparture / 8 ) { - // Paint [1/8, 3/8[ black (add point on the left side) - polygon.append( QPoint(0, half * (a - 1.0)) ); - } - } else { - // Paint [0/8, 1/8[ black (add point on the left half of the top side) - polygon.append( QPoint(half * (1.0 - a), 0) ); - } - - // Draw 70% transparent parts - polygonPixmap.fill( Qt::white ); - QPainter polygonPainter( &polygonPixmap ); - polygonPainter.setCompositionMode( QPainter::CompositionMode_Source ); - polygonPainter.setPen( Qt::black ); - polygonPainter.setBrush( QColor(0, 0, 0, 77) ); - polygonPainter.drawPolygon( polygon ); - polygonPainter.end(); - } - - // Insert rendered polygon pixmap into the pixmap cache - m_pixmapCache->insert( polygonCacheKey, polygonPixmap ); - } - - // Draw the polygon - QPainter p( &vehiclePixmap ); - p.setCompositionMode( QPainter::CompositionMode_DestinationIn ); - p.drawPixmap( 0, 0, polygonPixmap ); - p.end(); - } - - if ( !iconDrawFlags.testFlag(DrawMonochromeIcon) ) { - // Draw a shadow, but not if a monochrome icon is used - QImage shadow = vehiclePixmap.toImage(); - Plasma::PaintUtils::shadowBlur( shadow, shadowWidth - 1, Qt::black ); - painter->drawImage( rect.topLeft() + QPoint(1, 2), shadow ); - } - - // Draw the resulting pixmap - painter->drawPixmap( rect.topLeft(), vehiclePixmap ); -} - -QPixmap DeparturePainter::createMainIconPixmap( const QSize &size ) const -{ - QPixmap pixmap( size ); - pixmap.fill( Qt::transparent ); - - const QString mainIconKey = "stop_white"; - if ( !m_svg->hasElement(mainIconKey) ) { - kDebug() << "SVG element" << mainIconKey << "not found"; - return pixmap; - } - - QPainter painter( &pixmap ); - m_svg->resize( size ); - m_svg->paint( &painter, 0, 0, mainIconKey ); - painter.end(); - - return pixmap; -} - -QPixmap DeparturePainter::createDeparturesPixmap( DepartureItem *departure, - const QSize &size, VehicleIconDrawFlags iconDrawFlags ) -{ - Q_ASSERT( departure ); - QPixmap pixmap( size ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - p.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing - | QPainter::SmoothPixmapTransform ); - const DepartureInfo *data = departure->departureInfo(); - int minsToDeparture = qCeil( QDateTime::currentDateTime().secsTo( - data->predictedDeparture()) / 60.0 ); - - paintVehicle( &p, data->vehicleType(), pixmap.rect(), data->lineString(), - minsToDeparture, iconDrawFlags ); - - if ( iconDrawFlags.testFlag(DrawTimeText) ) { - // Add text to visualize the time until the departure (an alternative is DrawTimeGraphics) - QString text; - if ( minsToDeparture < -1 ) { - text.append( i18nc("Indicating the departure time of an already left vehicle", "left") ); - } else if ( minsToDeparture < 0 ) { - text.append( i18nc("Indicating the departure time of a currently leaving vehicle", "leaving") ); - } else if ( minsToDeparture == 0 ) { - text.append( i18nc("Indicating the departure time of a vehicle, that will leave now", "now") ); - } else if ( minsToDeparture >= 60 * 24 ) { - text.append( i18np("1 day", "%1 days", qRound(minsToDeparture / (6 * 24)) / 10.0) ); - } else if ( minsToDeparture >= 60 ) { - text.append( i18np("1 hour", "%1 hours", qRound(minsToDeparture / 6) / 10.0) ); - } else { - text.append( i18np("1 min.", "%1 min.", minsToDeparture) ); - } - - QFont font = Plasma::Theme::defaultTheme()->font( Plasma::Theme::DefaultFont ); - font.setPixelSize( qBound(KGlobalSettings::smallestReadableFont().pixelSize(), - int(pixmap.width() * 0.3), 36) ); - font.setBold( true ); - p.setFont( font ); - QFontMetrics fm( font ); - QRectF textRect( 0, 0, pixmap.width(), pixmap.height() ); - - int textWidth = fm.width( text ); - if ( textWidth > textRect.width() ) { - // Show only the number of minutes, if the whole text doesn't fit - text = QString::number( minsToDeparture ); - textWidth = fm.width( text ); - } - - text = fm.elidedText( text, Qt::ElideRight, textRect.width() * 1.05 ); - if ( iconDrawFlags.testFlag(DrawMonochromeIcon) ) { - QPen textOutlinePen( QColor(0, 0, 0, 150) ); - textOutlinePen.setWidthF( qMin(qreal(6.0), qreal(font.pixelSize() / 3.0)) ); - textOutlinePen.setCapStyle( Qt::RoundCap ); - textOutlinePen.setJoinStyle( Qt::RoundJoin ); - QPen textOutlineFinePen( QColor(0, 0, 0, 225) ); - textOutlineFinePen.setCosmetic( true ); - QPainterPath textPath; - textPath.addText( textRect.left() + (textRect.width() - textWidth) / 2.5, - textRect.bottom() - textOutlinePen.width(), font, text ); - p.setPen( textOutlinePen ); - p.drawPath( textPath ); - p.setPen( textOutlineFinePen ); - p.drawPath( textPath ); - p.fillPath( textPath, Qt::white ); - } else { - QRectF haloRect( textRect.left() + (textRect.width() - textWidth) / 2, - textRect.bottom() - fm.height(), textWidth, fm.height() ); - haloRect = haloRect.intersected( textRect ).adjusted( 3, 3, -3, -3 ); - Plasma::PaintUtils::drawHalo( &p, haloRect ); - - QTextOption option( Qt::AlignHCenter | Qt::AlignBottom ); - option.setWrapMode( QTextOption::NoWrap ); - p.drawText( textRect, text, option ); - } - } - - p.end(); - return pixmap; -} - -QPixmap DeparturePainter::createAlarmPixmap( DepartureItem* departure, const QSize &size ) -{ - QPixmap pixmap = createDeparturesPixmap( departure, size ); - int iconSize = pixmap.width() / 2; - QPixmap pixmapAlarmIcon = KIcon( "task-reminder" ).pixmap( iconSize ); - QPainter p( &pixmap ); - // Draw alarm icon in the top-right corner. - p.drawPixmap( pixmap.width() - iconSize - 1, 1, pixmapAlarmIcon ); - p.end(); - - return pixmap; -} - -QPixmap DeparturePainter::createPopupIcon( PopupIcon *popupIcon, DepartureModel* model, - const QSize &size ) -{ - int startDepartureGroupIndex = popupIcon->startDepartureGroupIndex(); - int endDepartureGroupIndex = popupIcon->endDepartureGroupIndex(); - qreal departureGroupIndex = popupIcon->departureGroupIndex(); - qreal departureIndex = popupIcon->departureIndex(); - QPixmap pixmap; - if ( qFuzzyCompare((qreal)qFloor(departureGroupIndex), departureGroupIndex) ) { - // If departureGroupIndex is an integer draw without transition between groups - departureGroupIndex = qBound( model->hasAlarms() ? -1 : 0, - qFloor(departureGroupIndex), popupIcon->departureGroups()->count() - 1 ); - - if ( departureGroupIndex < 0 ) { - pixmap = createAlarmPixmap( model->nextAlarmDeparture(), size ); - } else { - QList group = popupIcon->currentDepartureGroup(); - if ( group.isEmpty() ) { - return pixmap; - } - if ( qFuzzyCompare((qreal)qFloor(departureIndex), departureIndex) ) { - // If departureIndex is an integer draw without transition between departures - // in the current group - pixmap = createDeparturesPixmap( group[qFloor(departureIndex) % group.count()], size ); - } else { - const int startDepartureIndex = qFloor( departureIndex ) % group.count(); - int endDepartureIndex = startDepartureIndex + 1; - const qreal transition = qBound( 0.0, (departureIndex - startDepartureIndex) - / (endDepartureIndex - startDepartureIndex), 1.0 ); - endDepartureIndex %= group.count(); - DepartureItem *startDeparture = group[ startDepartureIndex ]; - DepartureItem *endDeparture = group[ endDepartureIndex ]; - - // Cache transition pixmaps. This creates lots of pixmaps (max. 100 per transition) - // but it also lowers CPU usage a lot. - // The animation uses QEasingCurve::OutQuad, therefore transition^2 is used for - // the cache key here. This should produce more different keys for values near 1, - // where the animation slows down (and therefore needs more different pixmaps). - // The pixmaps are normally only needed for one minute. - const QString fadeCacheKey = QString("%1-%2,%3-%4,%5,%6,%7x%8,%9") - .arg( static_cast(startDeparture->departureInfo()->vehicleType()) ) - .arg( static_cast(endDeparture->departureInfo()->vehicleType()) ) - .arg( startDeparture->departureInfo()->lineString() ) - .arg( endDeparture->departureInfo()->lineString() ) - .arg( transition * transition, 0, 'f', 2 ) - .arg( qMin(qCeil(QDateTime::currentDateTime().secsTo( - startDeparture->departureInfo()->predictedDeparture()) / 60.0), - int(MAX_MINUTES_UNTIL_DEPARTURE)) ) - .arg( size.width() ).arg( size.height() ) - .arg( static_cast(DefaultVehicleIconDrawFlags) ); - if ( !m_pixmapCache->find(fadeCacheKey, pixmap) ) { - // Draw transition between two departures in the current group - QPixmap startPixmap = createDeparturesPixmap( startDeparture, size ); - QPixmap endPixmap = createDeparturesPixmap( endDeparture, size ); - - // Make end pixmap transparent - QColor alpha( 0, 0, 0 ); - alpha.setAlphaF( transition * transition ); - QPainter pEnd( &endPixmap ); - pEnd.setCompositionMode( QPainter::CompositionMode_DestinationIn ); - pEnd.fillRect( startPixmap.rect(), alpha ); - pEnd.end(); - - // Mix transparent start and end pixmaps - pixmap = QPixmap( size ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - p.drawPixmap( pixmap.rect(), startPixmap ); - p.setCompositionMode( QPainter::CompositionMode_DestinationOut ); - p.fillRect( pixmap.rect(), alpha ); - p.setCompositionMode( QPainter::CompositionMode_Plus ); - p.drawPixmap( pixmap.rect(), endPixmap ); - p.end(); - - m_pixmapCache->insert( fadeCacheKey, pixmap ); - } - } - } - } else { - // Draw transition between two departure groups - const DepartureGroupList *departureGroups = popupIcon->departureGroups(); - startDepartureGroupIndex = qBound( model->hasAlarms() ? -1 : 0, - startDepartureGroupIndex, departureGroups->count() - 1 ); - endDepartureGroupIndex = qBound( model->hasAlarms() ? -1 : 0, - endDepartureGroupIndex, departureGroups->count() - 1 ); - const QList startGroup = startDepartureGroupIndex < 0 - ? QList() : departureGroups->at( startDepartureGroupIndex ); - const QList endGroup = endDepartureGroupIndex < 0 - ? QList() : departureGroups->at( endDepartureGroupIndex ); - - QPixmap startPixmap = startDepartureGroupIndex < 0 - ? createAlarmPixmap(model->nextAlarmDeparture(), size) - : createDeparturesPixmap(startGroup[qFloor(departureIndex) % startGroup.count()], size); - QPixmap endPixmap = endDepartureGroupIndex < 0 - ? createAlarmPixmap(model->nextAlarmDeparture(), size) - : createDeparturesPixmap(endGroup.first(), size); - pixmap = QPixmap( size ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - p.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - - qreal transition, startSize, endSize; - if ( endDepartureGroupIndex > startDepartureGroupIndex ) { - // Move forward to next departure - transition = qBound( 0.0, (departureGroupIndex - startDepartureGroupIndex) - / (endDepartureGroupIndex - startDepartureGroupIndex), 1.0 ); - } else { - // Mave backward to previous departure - transition = 1.0 - qBound( 0.0, (startDepartureGroupIndex - departureGroupIndex) - / (startDepartureGroupIndex - endDepartureGroupIndex), 1.0 ); - qSwap( startPixmap, endPixmap ); - } - startSize = (1.0 + 0.25 * transition) * pixmap.width(); - endSize = transition * pixmap.width(); - - p.drawPixmap( (pixmap.width() - endSize) / 2 + pixmap.width() * (1.0 - transition) / 2.0, - (pixmap.height() - endSize) / 2, endSize, endSize, endPixmap ); - - QPixmap startTransitionPixmap( pixmap.size() ); - startTransitionPixmap.fill( Qt::transparent ); - QPainter p2( &startTransitionPixmap ); - p2.drawPixmap( 0, 0, pixmap.width(), pixmap.height(), startPixmap ); - - // Make startTransitionPixmap more transparent (for fading) - p2.setCompositionMode( QPainter::CompositionMode_DestinationIn ); - p2.fillRect( startTransitionPixmap.rect(), QColor(0, 0, 0, 255 * (1.0 - transition * transition)) ); - p2.end(); - - p.setTransform( QTransform().rotate(transition * 90, Qt::YAxis) ); - p.drawPixmap( (pixmap.width() - startSize) / 2 - pixmap.width() * transition / 5.0, - (pixmap.height() - startSize) / 2, - startSize, startSize, startTransitionPixmap ); - p.end(); - } - - return pixmap; -} diff --git a/applet/departurepainter.h b/applet/departurepainter.h deleted file mode 100644 index 6264b50..0000000 --- a/applet/departurepainter.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef DEPARTUREPAINTER_HEADER -#define DEPARTUREPAINTER_HEADER - -/** @file - * @brief This file contains the DeparturePainter class. - * @author Friedrich Pülz */ - -#include - -class DepartureModel; -class DepartureItem; -class PopupIcon; -class KPixmapCache; -namespace Plasma { - class Svg; -} -class QPixmap; -class QPainter; -class QSize; -class QRectF; - -using namespace PublicTransport; - -/** - * @brief Draws icons for departures and associated stuff. - **/ -class DeparturePainter : public QObject { - Q_OBJECT - -public: - /** - * @brief Flags for vehicle icons. - * - * For each combination of these flags and for each vehicle type there is an icon in the SVG. - **/ - enum VehicleIconFlag { - ColoredIcon = 0x0, /**< The default colored vehicle icon. */ - EmptyIcon = 0x1, /**< Vehicle icon without content like "tram", "bus", "tro", etc. - * Useful to draw custom constent like the transport line string, eg. "N1". */ - MonochromeIcon = 0x2 /**< Use monochrome version of the icon. */ - }; - Q_DECLARE_FLAGS( VehicleIconFlags, VehicleIconFlag ) - - /** - * @brief Flags for drawing vehicle icons. - **/ - enum VehicleIconDrawFlag { - DrawColoredIcon = 0x00, /**< Draw a colored vehicle type icon. */ - DrawMonochromeIcon = 0x01, /**< Draw a monochrome version of the vehicle type icon. */ - DrawTransportLine = 0x02, /**< Draw the transport line string into the vehicle type icon. */ - DrawTimeText = 0x04, /**< Draw the time until departure/arrival as text. */ - DrawTimeGraphics = 0x08, /**< Draw the time until departure/arrival indicated - * using a graphical representation. */ - - DefaultVehicleIconDrawFlags = DrawMonochromeIcon | DrawTransportLine | DrawTimeGraphics - }; - Q_DECLARE_FLAGS( VehicleIconDrawFlags, VehicleIconDrawFlag ) - - DeparturePainter( QObject *parent = 0 ); - virtual ~DeparturePainter(); - - /** - * @brief The maximum number of minutes until departure that has a distinct visualization. - * - * Departures with a higher number of minutes until their departure time get the same graphical - * representation. - * If this is 60, it can be read intuitively like a clock. - **/ - static const int MAX_MINUTES_UNTIL_DEPARTURE = 60; - - void setSvg( Plasma::Svg *svg ) { m_svg = svg; }; - Plasma::Svg *svg() const { return m_svg; }; - - static QString iconKey( VehicleType vehicle, VehicleIconFlags flags = ColoredIcon ); - static VehicleIconFlags iconFlagsFromIconDrawFlags( VehicleIconDrawFlags flags = DrawColoredIcon ); - - /// @param iconDrawFlags DrawTimeText does nothing here, but DrawTimeGraphics works. - void paintVehicle( QPainter* painter, VehicleType vehicle, - const QRectF& rect, const QString &transportLine = QString(), - int minsToDeparture = 0, - VehicleIconDrawFlags iconDrawFlags = DefaultVehicleIconDrawFlags ); - - QPixmap createMainIconPixmap( const QSize &size ) const; - - /** - * @brief Creates a pixmap for the given @p departures. - * - * This pixmap is used for the popup icon for departures. Multiple departures may be drawn - * side by side, eg. if they depart at the same time. - **/ - QPixmap createDeparturesPixmap( DepartureItem *departure, const QSize &size, - VehicleIconDrawFlags iconDrawFlags = DefaultVehicleIconDrawFlags ); - - /** - * @brief Creates a pixmap for the given @p departure with an alarm. - * - * This pixmap is used for the popup icon for departures for which an alarm is set. - **/ - QPixmap createAlarmPixmap( DepartureItem *departure, const QSize &size ); - - /** - * @brief Creates a pixmap for the given @p popupIcon. - * - * This pixmap is used for the popup icon, it draws the icon using the current state of - * @p popupIcon. - **/ - QPixmap createPopupIcon( PopupIcon *popupIcon, DepartureModel *model, const QSize &size ); - -private: - KPixmapCache *m_pixmapCache; - Plasma::Svg *m_svg; -}; -Q_DECLARE_OPERATORS_FOR_FLAGS( DeparturePainter::VehicleIconFlags ) -Q_DECLARE_OPERATORS_FOR_FLAGS( DeparturePainter::VehicleIconDrawFlags ) - -#endif // DEPARTUREPAINTER_HEADER diff --git a/applet/departureprocessor.cpp b/applet/departureprocessor.cpp deleted file mode 100644 index 62c3c07..0000000 --- a/applet/departureprocessor.cpp +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright 2013 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "departureprocessor.h" - -// KDE includes -#include - -// Qt includes -#include // Member variable -#include - -const int DepartureProcessor::DEPARTURE_BATCH_SIZE = 10; -const int DepartureProcessor::JOURNEY_BATCH_SIZE = 10; - -DepartureProcessor::DepartureProcessor( QObject *parent ) - : QThread(parent), m_currentJob(NoJob), m_timeOffsetOfFirstDeparture(0), - m_isArrival(false), m_quit(false), m_abortCurrentJob(false), m_requeueCurrentJob(false), - m_mutex(new QMutex()) -{ - qRegisterMetaType< QList >( "QList" ); - qRegisterMetaType< QList >( "QList" ); -} - -DepartureProcessor::~DepartureProcessor() -{ - // Wake up, abort running jobs and quit - m_mutex->lock(); - m_quit = true; - m_abortCurrentJob = true; - m_cond.wakeOne(); - m_mutex->unlock(); - - // Wait for the abort to finish - wait(); - - // Cleanup - delete m_mutex; -} - -void DepartureProcessor::abortJobs( DepartureProcessor::JobTypes jobTypes ) -{ - QMutexLocker locker( m_mutex ); - if ( jobTypes.testFlag( m_currentJob ) ) { - m_abortCurrentJob = true; - } - - if ( jobTypes == AllJobs ) { - // Clear all jobs in the queue - m_jobQueue.clear(); - } else { - // Remove jobs of the given types - for ( int i = m_jobQueue.count() - 1; i >= 0; --i ) { - if ( jobTypes.testFlag( m_jobQueue[i]->type ) ) { - m_jobQueue.removeAt( i ); - } - } - } -} - -void DepartureProcessor::setFilters( const FilterSettingsList &filters ) -{ - QMutexLocker locker( m_mutex ); - m_filters = filters; - - if ( m_currentJob == ProcessDepartures && !m_jobQueue.isEmpty() ) { - m_requeueCurrentJob = true; - } -} - -void DepartureProcessor::setColorGroups( const ColorGroupSettingsList& colorGroups ) -{ - QMutexLocker locker( m_mutex ); - m_colorGroups = colorGroups; - - if ( m_currentJob == ProcessDepartures && !m_jobQueue.isEmpty() ) { - m_requeueCurrentJob = true; - } -} - -void DepartureProcessor::setFirstDepartureSettings( - FirstDepartureConfigMode firstDepartureConfigMode, const QTime& timeOfFirstDepartureCustom, - int timeOffsetOfFirstDeparture ) -{ - QMutexLocker locker( m_mutex ); - m_firstDepartureConfigMode = firstDepartureConfigMode; - m_timeOfFirstDepartureCustom = timeOfFirstDepartureCustom; - m_timeOffsetOfFirstDeparture = timeOffsetOfFirstDeparture; -} - -void DepartureProcessor::setDepartureArrivalListType( DepartureArrivalListType type ) -{ - QMutexLocker locker( m_mutex ); - m_isArrival = type == ArrivalList; -} - -void DepartureProcessor::setAlarms( const AlarmSettingsList &alarms ) -{ - QMutexLocker locker( m_mutex ); - m_alarms = alarms; - - if ( m_currentJob == ProcessDepartures && !m_jobQueue.isEmpty() ) { - m_requeueCurrentJob = true; - } -} - -bool DepartureProcessor::isTimeShown( const QDateTime& dateTime, - FirstDepartureConfigMode firstDepartureConfigMode, - const QTime &timeOfFirstDepartureCustom, - int timeOffsetOfFirstDeparture ) -{ - QDateTime firstDepartureTime = firstDepartureConfigMode == AtCustomTime - ? QDateTime( QDate::currentDate(), timeOfFirstDepartureCustom ) - : QDateTime::currentDateTime(); - int secsToDepartureTime = firstDepartureTime.secsTo( dateTime ); - if ( firstDepartureConfigMode == RelativeToCurrentTime ) { - secsToDepartureTime -= timeOffsetOfFirstDeparture * 60; - } - if ( -secsToDepartureTime / 3600 >= 23 ) { // for departures with guessed date - secsToDepartureTime += 24 * 3600; - } - return secsToDepartureTime > -60; -} - -void DepartureProcessor::startOrEnqueueJob( DepartureProcessor::JobInfo *job ) -{ - // private function, m_mutex is expected to be already locked - m_jobQueue.enqueue( job ); - - if ( !isRunning() ) { - start(); - } else { - m_cond.wakeOne(); - } -} - -void DepartureProcessor::processDepartures( const QString &sourceName, const QVariantHash& data ) -{ - QMutexLocker locker( m_mutex ); - DepartureJobInfo *job = new DepartureJobInfo(); - job->sourceName = sourceName; - job->data = data; - startOrEnqueueJob( job ); -} - -void DepartureProcessor::processJourneys( const QString& sourceName, const QVariantHash& data ) -{ - QMutexLocker locker( m_mutex ); - JourneyJobInfo *job = new JourneyJobInfo(); - job->sourceName = sourceName; - job->data = data; - startOrEnqueueJob( job ); -} - -void DepartureProcessor::filterDepartures( const QString &sourceName, - const QList< DepartureInfo > &departures, const QList< uint > &shownDepartures ) -{ - QMutexLocker locker( m_mutex ); - FilterJobInfo *job = new FilterJobInfo(); - job->sourceName = sourceName; - job->departures = departures; - job->shownDepartures = shownDepartures; // TODO - startOrEnqueueJob( job ); -} - -void DepartureProcessor::run() -{ - QMutexLocker locker( m_mutex ); - forever { - if ( m_quit ) { - break; - } - if ( m_jobQueue.isEmpty() ) { - // Wait for new jobs, the wait condition is met on exit or when a new job was queued - m_currentJob = NoJob; - m_cond.wait( m_mutex ); - if ( m_quit ) { - // Exit the thread - break; - } - Q_ASSERT( !m_jobQueue.isEmpty() ); - } - - // Get the next job - JobInfo *job = m_jobQueue.dequeue(); - m_currentJob = job->type; - - // Run the job with unlocked mutex - m_mutex->unlock(); - if ( job->type == ProcessDepartures ) { - doDepartureJob( static_cast( job ) ); - } else if ( job->type == FilterDepartures ) { - doFilterJob( static_cast( job ) ); - } else if ( job->type == ProcessJourneys ) { - doJourneyJob( static_cast( job ) ); - } - m_mutex->lock(); - - if ( !m_requeueCurrentJob ) { - delete job; - } else { - kDebug() << " .. requeue job .."; - } - m_abortCurrentJob = false; - m_requeueCurrentJob = false; - } - - qDeleteAll( m_jobQueue ); - kDebug() << "Thread terminated"; -} - -void DepartureProcessor::doDepartureJob( DepartureProcessor::DepartureJobInfo* departureJob ) -{ - const QString sourceName = departureJob->sourceName; - QVariantHash data = departureJob->data; - - m_mutex->lock(); - FilterSettingsList filters = m_filters; - ColorGroupSettingsList colorGroups = m_colorGroups; - AlarmSettingsList alarms = m_alarms; - - FirstDepartureConfigMode firstDepartureConfigMode = m_firstDepartureConfigMode; - const QTime timeOfFirstDepartureCustom = m_timeOfFirstDepartureCustom; - int timeOffsetOfFirstDeparture = m_timeOffsetOfFirstDeparture; - const DepartureInfo::DepartureFlags globalFlags = m_isArrival - ? DepartureInfo::IsArrival : DepartureInfo::NoDepartureFlags; - m_mutex->unlock(); - - emit beginDepartureProcessing( sourceName ); - - QList< DepartureInfo > departureInfos; - const QUrl url = data["requestUrl"].toUrl(); - const QDateTime updated = data["updated"].toDateTime(); - const QDateTime nextAutomaticUpdate = data["nextAutomaticUpdate"].toDateTime(); - const QDateTime minManualUpdateTime = data["minManualUpdateTime"].toDateTime(); - QVariantList departuresData = data.contains("departures") ? data["departures"].toList() - : data["arrivals"].toList(); - -// Q_ASSERT( departureJob->alreadyProcessed <= count ); - for ( int i = departureJob->alreadyProcessed; i < departuresData.count(); ++i ) { - QVariantHash departureData = departuresData[ i ].toHash(); - QList< QDateTime > routeTimes; - if ( departureData.contains("RouteTimes") ) { - QVariantList times = departureData[ "RouteTimes" ].toList(); - foreach( const QVariant &time, times ) { - routeTimes << time.toDateTime(); - } - } - - // Read departure flags - DepartureInfo::DepartureFlags flags = globalFlags; - const QString additionalDataState = departureData["additionalDataState"].toString(); - if ( additionalDataState == QLatin1String("included") ) { - // This departure includes additional timetable data, - // most other data is most probably unchanged - flags |= PublicTransport::DepartureInfo::IncludesAdditionalData; - } else if ( additionalDataState == QLatin1String("busy") ) { - // Additional data was requested for this departure, - // but the request did not finish yet - flags |= PublicTransport::DepartureInfo::WaitingForAdditionalData; - } - - DepartureInfo departureInfo( sourceName, i, flags, departureData["Operator"].toString(), - departureData["TransportLine"].toString(), - departureData["Target"].toString(), departureData["TargetShortened"].toString(), - departureData["DepartureDateTime"].toDateTime(), - static_cast( departureData["TypeOfVehicle"].toInt() ), - departureData["Nightline"].toBool(), departureData["Expressline"].toBool(), - departureData["Platform"].toString(), departureData["Delay"].toInt(), - departureData["DelayReason"].toString(), - departureData["JourneyNews"].toString(), - departureData["JourneyNewsUrl"].toString(), - departureData["RouteStops"].toStringList(), - departureData["RouteStopsShortened"].toStringList(), - routeTimes, departureData["RouteExactStops"].toInt(), - departureData["additionalDataError"].toString() ); - - // Update the list of alarms that match the current departure - departureInfo.matchedAlarms().clear(); - for ( int a = 0; a < alarms.count(); ++a ) { - AlarmSettings alarm = alarms.at( a ); - if ( alarm.enabled && alarm.filter.match( departureInfo ) ) { - departureInfo.matchedAlarms() << a; - } - } - - // Mark departures/arrivals as filtered out that are either filtered out - // or shouldn't be shown because of the first departure settings - if ( !isTimeShown(departureInfo.predictedDeparture(), firstDepartureConfigMode, - timeOfFirstDepartureCustom, timeOffsetOfFirstDeparture) - || filters.filterOut(departureInfo) - || colorGroups.filterOut(departureInfo) ) - { - departureInfo.setFlag( PublicTransport::DepartureInfo::IsFilteredOut ); - } - departureInfos << departureInfo; - - if ( departureInfos.count() == DEPARTURE_BATCH_SIZE ) { - emit departuresProcessed( sourceName, departureInfos, url, updated, - nextAutomaticUpdate, minManualUpdateTime, - departuresData.count() - i - 1 ); - departureInfos.clear(); - - QMutexLocker locker( m_mutex ); - if ( m_abortCurrentJob ) { - break; - } else if ( m_requeueCurrentJob ) { - departureJob->alreadyProcessed = i + 1; - m_jobQueue << departureJob; - break; - } - } - } // for ( int i = 0; i < count; ++i ) - - // Emit remaining departures - if ( !departureInfos.isEmpty() ) { - m_mutex->lock(); - const bool abortCurrentJob = m_abortCurrentJob; - m_mutex->unlock(); - - if ( !abortCurrentJob ) { - emit departuresProcessed( sourceName, departureInfos, url, updated, - nextAutomaticUpdate, minManualUpdateTime, 0 ); - } - } -} - -void DepartureProcessor::doJourneyJob( DepartureProcessor::JourneyJobInfo* journeyJob ) -{ - const QString sourceName = journeyJob->sourceName; - QVariantHash data = journeyJob->data; - - m_mutex->lock(); - AlarmSettingsList alarms = m_alarms; - m_mutex->unlock(); - - emit beginJourneyProcessing( sourceName ); - - QList< JourneyInfo > journeyInfos; - QUrl url = data["requestUrl"].toUrl(); - QDateTime updated = data["updated"].toDateTime(); - QVariantList journeysData = data["journeys"].toList(); - if ( journeyJob->alreadyProcessed > journeysData.count() ) { - kDebug() << "There was an error with the journey data source (journeyJob->alreadyProcessed > count)"; - } - for ( int i = journeyJob->alreadyProcessed; i < journeysData.count(); ++i ) { - QVariantHash journeyData = journeysData[i].toHash(); - QList routeTimesDeparture, routeTimesArrival; - if ( journeyData.contains( "RouteTimesDeparture" ) ) { - QVariantList times = journeyData[ "RouteTimesDeparture" ].toList(); - foreach( const QVariant &time, times ) { - routeTimesDeparture << time.toDateTime(); - } - } - if ( journeyData.contains( "RouteTimesArrival" ) ) { - QVariantList times = journeyData[ "RouteTimesArrival" ].toList(); - foreach( const QVariant &time, times ) { - routeTimesArrival << time.toDateTime(); - } - } - -// TODO Remove variables here - QStringList routeTransportLines, routePlatformsDeparture, routePlatformsArrival; - if ( journeyData.contains( "RouteTransportLines" ) ) { - routeTransportLines = journeyData[ "RouteTransportLines" ].toStringList(); - } - if ( journeyData.contains( "RoutePlatformsDeparture" ) ) { - routePlatformsDeparture = journeyData[ "RoutePlatformsDeparture" ].toStringList(); - } - if ( journeyData.contains( "RoutePlatformsArrival" ) ) { - routePlatformsArrival = journeyData[ "RoutePlatformsArrival" ].toStringList(); - } - - QList routeTimesDepartureDelay, routeTimesArrivalDelay; - if ( journeyData.contains( "RouteTimesDepartureDelay" ) ) { - QVariantList list = journeyData[ "RouteTimesDepartureDelay" ].toList(); - foreach( const QVariant &var, list ) { - routeTimesDepartureDelay << var.toInt(); - } - } - if ( journeyData.contains( "RouteTimesArrivalDelay" ) ) { - QVariantList list = journeyData[ "RouteTimesArrivalDelay" ].toList(); - foreach( const QVariant &var, list ) { - routeTimesArrivalDelay << var.toInt(); - } - } - - QList routeSubJourneys; - if ( journeyData.contains("RouteSubJourneys") ) { - QVariantList list = journeyData[ "RouteSubJourneys" ].toList(); - foreach ( const QVariant &item, list ) { - const QVariantMap map = item.toMap(); - - QStringList routeSubPlatformsDeparture, routeSubPlatformsArrival; - if ( map.contains( "RoutePlatformsDeparture" ) ) { - routeSubPlatformsDeparture = map[ "RoutePlatformsDeparture" ].toStringList(); - } - if ( map.contains( "RoutePlatformsArrival" ) ) { - routeSubPlatformsArrival = map[ "RoutePlatformsArrival" ].toStringList(); - } - QList routeSubTimesDepartureDelay, routeSubTimesArrivalDelay; - if ( map.contains( "RouteTimesDepartureDelay" ) ) { - QVariantList list = map[ "RouteTimesDepartureDelay" ].toList(); - foreach( const QVariant &var, list ) { - routeSubTimesDepartureDelay << var.toInt(); - } - } - if ( map.contains("RouteTimesArrivalDelay") ) { - QVariantList list = map[ "RouteTimesArrivalDelay" ].toList(); - foreach( const QVariant &var, list ) { - routeSubTimesArrivalDelay << var.toInt(); - } - } - QList routeSubTimesDeparture, routeSubTimesArrival; - if ( map.contains("RouteTimesDeparture") ) { - QVariantList times = map[ "RouteTimesDeparture" ].toList(); - foreach( const QVariant &time, times ) { - routeSubTimesDeparture << time.toDateTime(); - } - } - if ( map.contains("RouteTimesArrival") ) { - QVariantList times = map[ "RouteTimesArrival" ].toList(); - foreach( const QVariant &time, times ) { - routeSubTimesArrival << time.toDateTime(); - } - } - routeSubJourneys << RouteSubJourney( map["RouteStops"].toStringList(), - map["RouteStopsShortened"].toStringList(), map["RouteNews"].toStringList(), - map["RoutePlatformsDeparture"].toStringList(), - map["RoutePlatformsArrival"].toStringList(), - routeSubTimesDeparture, routeSubTimesArrival, - routeSubTimesDepartureDelay, routeSubTimesArrivalDelay ); - } - } - -// TODO - JourneyInfo journeyInfo( journeyData["Operator"].toString(), - journeyData["TypesOfVehicleInJourney"].toList(), - journeyData["DepartureDateTime"].toDateTime(), - journeyData["ArrivalDateTime"].toDateTime(), - journeyData["Pricing"].toString(), - journeyData["StartStopName"].toString(), - journeyData["TargetStopName"].toString(), - journeyData["Duration"].toInt(), - journeyData["Changes"].toInt(), - journeyData["JourneyNews"].toString(), - journeyData["JourneyNewsUrl"].toString(), - journeyData["RouteStops"].toStringList(), - journeyData["RouteStopsShortened"].toStringList(), - journeyData["RouteNews"].toStringList(), - routeTransportLines, routePlatformsDeparture, - routePlatformsArrival, - journeyData["RouteTypesOfVehicles"].toList(), - routeTimesDeparture, routeTimesArrival, - routeTimesDepartureDelay, routeTimesArrivalDelay, - routeSubJourneys ); - - journeyInfos << journeyInfo; - if ( journeyInfos.count() == JOURNEY_BATCH_SIZE ) { - emit journeysProcessed( sourceName, journeyInfos, url, updated ); - journeyInfos.clear(); - - QMutexLocker locker( m_mutex ); - if ( m_abortCurrentJob ) { - break; - } else if ( m_requeueCurrentJob ) { - journeyJob->alreadyProcessed = i + 1; - m_jobQueue << journeyJob; - break; - } - } - } - - // Emit remaining journeys - if ( !journeyInfos.isEmpty() ) { - m_mutex->lock(); - const bool abortCurrentJob = m_abortCurrentJob; - m_mutex->unlock(); - - if ( !abortCurrentJob ) { - emit journeysProcessed( sourceName, journeyInfos, url, updated ); - } - } -} - -void DepartureProcessor::doFilterJob( DepartureProcessor::FilterJobInfo* filterJob ) -{ - QList< DepartureInfo > departures = filterJob->departures; - QList< DepartureInfo > newlyFiltered, newlyNotFiltered; - - m_mutex->lock(); - FilterSettingsList filters = m_filters; - ColorGroupSettingsList colorGroups = m_colorGroups; - - FirstDepartureConfigMode firstDepartureConfigMode = m_firstDepartureConfigMode; - const QTime &timeOfFirstDepartureCustom = m_timeOfFirstDepartureCustom; - int timeOffsetOfFirstDeparture = m_timeOffsetOfFirstDeparture; - m_mutex->unlock(); - - emit beginFiltering( filterJob->sourceName ); - for ( int i = 0; i < departures.count(); ++i ) { - DepartureInfo &departureInfo = departures[ i ]; - const bool filterOut = filters.filterOut( departureInfo ) - || colorGroups.filterOut( departureInfo ); - - // Newly filtered departures are now filtered out and were shown. - // They may be newly filtered if they weren't filtered out, but - // they may haven't been shown nevertheless, because the maximum - // departure count was exceeded. - if ( filterOut && !departureInfo.isFilteredOut() && - filterJob->shownDepartures.contains(departureInfo.hash()) ) - { - newlyFiltered << departureInfo; - - // Newly not filtered departures are now not filtered out and - // weren't shown, ie. were filtered out. - // Newly not filtered departures also need to be shown with the current - // first departure settings - } else if ( !filterOut - && ( departureInfo.isFilteredOut() - || !filterJob->shownDepartures.contains(departureInfo.hash()) ) - && isTimeShown(departureInfo.predictedDeparture(), - firstDepartureConfigMode, timeOfFirstDepartureCustom, - timeOffsetOfFirstDeparture) ) - { - newlyNotFiltered << departureInfo; - } - departureInfo.setFlag( PublicTransport::DepartureInfo::IsFilteredOut, filterOut ); - } - - m_mutex->lock(); - const bool abortCurrentJob = m_abortCurrentJob; - m_mutex->unlock(); - - if ( !abortCurrentJob ) { - emit departuresFiltered( filterJob->sourceName, departures, newlyFiltered, newlyNotFiltered ); - } -} - -QDebug& operator <<( QDebug debug, DepartureProcessor::JobType jobType ) -{ - switch ( jobType ) { - case DepartureProcessor::ProcessDepartures: - return debug << "ProcessDepartures"; - case DepartureProcessor::ProcessJourneys: - return debug << "ProcessJourneys"; - case DepartureProcessor::FilterDepartures: - return debug << "FilterDepartures"; - - default: - return debug << "Job type unknown!" << static_cast< int >( jobType ); - } -} diff --git a/applet/departureprocessor.h b/applet/departureprocessor.h deleted file mode 100644 index d1946b8..0000000 --- a/applet/departureprocessor.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file - * @brief This file contains the QThread-derived class DepartureProcessor to process data from the public transport data engine. - * @author Friedrich Pülz */ - -#ifndef DEPARTUREPROCESSOR_HEADER -#define DEPARTUREPROCESSOR_HEADER - -// Own includes -#include "settings.h" - -// libpublictransporthelper includes -#include // Member variable -#include // Member variable - -// Qt includes -#include // Base class -#include // Member variable -#include // Member variable - -/** - * @brief Worker thread for PublicTransport - * - * A worker thread that puts data from the publicTransport data engine into DepartureInfo/ - * JourneyInfo instances. It also applies filters and checks if alarm filters match. - * Filters are given as FilterSettings by @ref setFilterSettings. They contain a list of filters - * which get OR combined. Each filter has a list of constraints which get AND combined. This could - * take some time with complex filter settings and a long list of departures/arrivals. That's - * actually the main reason to do this in a thread. - * To ensure that only departures get marked to be shown which departure time is greater than or - * equal to the first departure time, use @ref setFirstDepartureSettings. The thread uses a job - * queue, jobs can be cancelled by their type using @ref abortJobs. To add a new job to the queue - * use @ref processDepartures, @ref processJourneys or @ref filterDepartures. - * - * @ingroup models - **/ -class DepartureProcessor : public QThread { - Q_OBJECT - -public: - /** @brief Types of jobs. */ - enum JobType { - NoJob = 0x00, /**< No job. This is returned by @ref currentJob if the - * thread is idle. */ - ProcessDepartures = 0x01, /**< Processing departures, ie. putting data - * from the publicTransport data engine into DepartureInfo - * instances and apply filters/alarms. */ - FilterDepartures = 0x02, /**< Filtering departures @see filterSystem. */ - ProcessJourneys = 0x04, /**< Processing journeys, ie. putting data - * from the publicTransport data engine into JourneyInfo instances. */ - AllJobs = ProcessDepartures | FilterDepartures | ProcessJourneys - /**< All jobs, can be used with @ref abortJobs to abort all - * jobs at once. */ - }; - Q_DECLARE_FLAGS( JobTypes, JobType ) - - /** @brief Creates a new DepartureProcessor instance. */ - DepartureProcessor( QObject *parent = 0 ); - - /** @brief Destructor. */ - ~DepartureProcessor(); - - /** - * @brief The number of departures/arrivals in one batch, which gets send to the applet. - * - * If there are more items to be processed, they will be send in a later call to the applet. - **/ - static const int DEPARTURE_BATCH_SIZE; - - /** - * @brief The number of journeys in one batch, which gets send to the applet. - * - * If there are more items to be processed, they will be send in a later call to the applet. - **/ - static const int JOURNEY_BATCH_SIZE; - - /** - * @brief Set the filter settings to be used. - * @param filters A list of filter settings to be used. - **/ - void setFilters( const FilterSettingsList &filters ); - - /** - * @brief Set the color group settings to be used. - * @param colorGroup A list of color group settings to be used. - **/ - void setColorGroups( const ColorGroupSettingsList &colorGroups ); - - /** @brief Set the list of @p alarm to be used. */ - void setAlarms( const AlarmSettingsList &alarms ); - - /** - * @brief Set the first departure settings to be used. - * - * @param firstDepartureConfigMode The first departure time can be relative to the current - * time (@ref RelativeToCurrentTime) or a custom time (@ref AtCustomTime). - * @param timeOfFirstDepartureCustom A custom, fixed first departure time. - * Only used if @p firstDepartureConfigMode is set to @ref AtCustomTime. - * @param timeOffsetOfFirstDeparture The offset in minutes of the first result departure/arrival. - * - * @see FirstDepartureConfigMode - **/ - void setFirstDepartureSettings( FirstDepartureConfigMode firstDepartureConfigMode, - const QTime &timeOfFirstDepartureCustom, int timeOffsetOfFirstDeparture ); - - /** - * @brief Toggle between departures and arrivals. - **/ - void setDepartureArrivalListType( DepartureArrivalListType type ); - - /** - * @brief Enqueues a job of type @ref ProcessDepartures to the job queue. - * - * @param sourceName The data engine source name for the departure data. - * - * @param data The departure/arrival data from the publicTransport data engine to be processed, - * ie. put the data into DepartureInfo instances and apply filters/alarms. - **/ - void processDepartures( const QString &sourceName, const QVariantHash &data ); - - /** - * @brief Enqueues a job of type @ref FilterDepartures to the job queue. - * - * @param sourceName The data engine source name for the departure data. - * - * @param departures The list of departures that should be filtered. - * - * @param shownDepartures A list of hashes of all currently shown departures, as returned by - * @ref DepartureModel::itemHashes. Hashes can be retrieved using qHash or - * @ref DepartureInfo::hash. - **/ - void filterDepartures( const QString &sourceName, const QList< DepartureInfo > &departures, - const QList< uint > &shownDepartures = QList< uint >() ); - - /** - * @brief Enqueues a job of type @ref ProcessJourneys to the job queue. - * - * @param sourceName The data engine source name for the journey data. - * - * @param data The journey data from the publicTransport data engine to - * be processed, ie. put the data into JourneyInfo instances. - **/ - void processJourneys( const QString &sourceName, const QVariantHash &data ); - - /** - * @brief Aborts all jobs of the given @p jobTypes. - * - * @param jobTypes The types of jobs to abort, by default all jobs are aborted. - **/ - void abortJobs( DepartureProcessor::JobTypes jobTypes = AllJobs ); - - /** @returns the job that's currently being processed by this thread. */ - JobType currentJob() const { return m_currentJob; }; - - /** - * @brief Checks if a departure/arrival/journey should be shown with the given settings. - * - * Items won't be shown, if it's eg. before the configured time of the first departure/arrival. - * - * @param dateTime The date and time of the departure/arrival. - * - * @param firstDepartureConfigMode The settings mode for the first shown departure/arrival. - * - * @param timeOfFirstDepartureCustom The time set as the first departure/arrival time. - * Only used if @ref AtCustomTime is used in @p firstDepartureConfigMode. - * - * @param timeOffsetOfFirstDeparture The offset in minutes from now for the first - * departure/arrival. Only used if @ref RelativeToCurrentTime is used in - * @p firstDepartureConfigMode. - * - * @return True, if the departure/arrival/journey should be shown. False otherwise. - **/ - static bool isTimeShown( const QDateTime& dateTime, - FirstDepartureConfigMode firstDepartureConfigMode, - const QTime &timeOfFirstDepartureCustom, int timeOffsetOfFirstDeparture ); - -signals: - /** - * @brief A departure/arrival processing job now gets started. - * - * @param sourceName The data engine source name for the departure data. - **/ - void beginDepartureProcessing( const QString &sourceName ); - - /** - * @brief A departure/arrival processing job is finished (or a batch of departures/arrivals). - * - * @param sourceName The data engine source name for the departure data. - * @param departures A list of departures that were read. - * @param requestUrl The url that was used to download the departure data. - * @param lastUpdate The date and time of the last update of the data source. - * @param nextAutomaticUpdate The date and time of the next automatic update of the data source. - * @param minManualUpdateTime The minimal date and time of the next (manual) update of the - * data source. Earlier update requests will be rejected. - * @param departuresToGo The number of departures to still be processed. If this isn't 0 - * this signal gets emitted again after the next batch of departures has been processed. - **/ - void departuresProcessed( const QString &sourceName, const QList< DepartureInfo > &departures, - const QUrl &requestUrl, const QDateTime &lastUpdate, - const QDateTime &nextAutomaticUpdate, const QDateTime &minManualUpdateTime, - int departuresToGo = 0 ); - - /** - * @brief A journey processing job now gets started. - * - * @param sourceName The data engine source name for the journey data. - **/ - void beginJourneyProcessing( const QString &sourceName ); - - /** - * @brief A journey processing job is finished (or a batch of journeys). - * - * @param sourceName The data engine source name for the journey data. - * @param journeys A list of journeys that were read. - * @param requestUrl The url that was used to download the journey data. - * @param lastUpdate The date and time of the last update of the data. - **/ - void journeysProcessed( const QString &sourceName, const QList< JourneyInfo > &journeys, - const QUrl &requestUrl, const QDateTime &lastUpdate ); - - /** - * @brief A filter departures job now gets started. - * - * @param sourceName The data engine source name for the departure data. - **/ - void beginFiltering( const QString &sourceName ); - - /** - * @brief A filter departures job is finished. - * - * @param sourceName The data engine source name for the departure data. - * @param departures The list of departures that were filtered. Each - * departure now returns the correct value with isFilteredOut() according - * to the filter settings given to the worker thread. - * @param newlyFiltered A list of departures that should be made visible - * to match the current filter settings. - * @param newlyNotFiltered A list of departures that should be made - * invisible to match the current filter settings. - **/ - void departuresFiltered( const QString &sourceName, const QList< DepartureInfo > &departures, - const QList< DepartureInfo > &newlyFiltered, - const QList< DepartureInfo > &newlyNotFiltered ); - -protected: - virtual void run(); - -private: - struct JobInfo { - JobType type; - QString sourceName; - }; - struct DepartureJobInfo : public JobInfo { - DepartureJobInfo() { - type = ProcessDepartures; - alreadyProcessed = 0; - }; - - QVariantHash data; - int alreadyProcessed; // used for requeuing - }; - struct FilterJobInfo : public JobInfo { - FilterJobInfo() { type = FilterDepartures; }; - - QList< DepartureInfo > departures; - QList< uint > shownDepartures; - }; - struct JourneyJobInfo : public DepartureJobInfo { - JourneyJobInfo() { - type = ProcessJourneys; - alreadyProcessed = 0; - }; - }; - - void doDepartureJob( DepartureJobInfo *departureJob ); - void doJourneyJob( JourneyJobInfo *journeyJob ); - void doFilterJob( FilterJobInfo *filterJob ); - void startOrEnqueueJob( JobInfo *jobInfo ); - - QQueue< JobInfo* > m_jobQueue; - JobType m_currentJob; - - FilterSettingsList m_filters; - ColorGroupSettingsList m_colorGroups; - AlarmSettingsList m_alarms; - FirstDepartureConfigMode m_firstDepartureConfigMode; - QTime m_timeOfFirstDepartureCustom; - int m_timeOffsetOfFirstDeparture; - bool m_isArrival; - - bool m_quit, m_abortCurrentJob, m_requeueCurrentJob; - QMutex *const m_mutex; - QWaitCondition m_cond; -}; -Q_DECLARE_OPERATORS_FOR_FLAGS( DepartureProcessor::JobTypes ) - -QDebug &operator <<( QDebug debug, DepartureProcessor::JobType jobType ); - -#endif // Multiple inclusion guard diff --git a/applet/global.cpp b/applet/global.cpp deleted file mode 100644 index 8dee5cf..0000000 --- a/applet/global.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "global.h" - -// KDE includes -#include -#include -#include -#include - -// Qt includes -#include -#include - -// Plasma includes -#include -#include - -KIcon GlobalApplet::stopIcon( RouteStopFlags routeStopFlags ) -{ - if ( routeStopFlags.testFlag(RouteStopIsHighlighted) ) { - return KIcon("flag-blue"); - } else if ( routeStopFlags.testFlag(RouteStopIsHomeStop) ) { - return KIcon("go-home"); - } else if ( routeStopFlags.testFlag(RouteStopIsOrigin) ) { - return KIcon("flag-red"); - } else if ( routeStopFlags.testFlag(RouteStopIsTarget) ) { - return KIcon("flag-green"); - } else { - return KIcon("public-transport-stop"); - } -} - -KIcon GlobalApplet::makeOverlayIcon( const KIcon &icon, const KIcon &overlayIcon, - const QSize &overlaySize, int iconExtend ) -{ - QPixmap pixmap = icon.pixmap( iconExtend ), pixmapOverlay = overlayIcon.pixmap( overlaySize ); - QPainter p( &pixmap ); - p.drawPixmap( QPoint( iconExtend - overlaySize.width(), iconExtend - overlaySize.height() ), pixmapOverlay ); - p.end(); - KIcon resultIcon = KIcon(); - resultIcon.addPixmap( pixmap, QIcon::Normal ); - - KIconEffect iconEffect; - pixmap = iconEffect.apply( pixmap, KIconLoader::Small, KIconLoader::ActiveState ); - resultIcon.addPixmap( pixmap, QIcon::Selected ); - resultIcon.addPixmap( pixmap, QIcon::Active ); - - return resultIcon; -} - -KIcon GlobalApplet::makeOverlayIcon( const KIcon &icon, const QString &overlayIconName, - const QSize &overlaySize, int iconExtend ) -{ - return makeOverlayIcon( icon, KIcon( overlayIconName ), overlaySize, iconExtend ); -} - -KIcon GlobalApplet::makeOverlayIcon( const KIcon& icon, const QList &overlayIconsBottom, - const QSize& overlaySize, int iconExtend ) -{ - Q_ASSERT( !icon.isNull() ); - - QPixmap pixmap = icon.pixmap( iconExtend ); - if ( pixmap.isNull() ) { - kDebug() << "pixmap is Null"; - return icon; - } - - QPainter p( &pixmap ); - int x = 0, xStep = iconExtend / overlayIconsBottom.count(); - foreach( const KIcon &overlayIcon, overlayIconsBottom ) { - p.drawPixmap( QPoint( x, iconExtend - overlaySize.height() ), overlayIcon.pixmap( overlaySize ) ); - x += xStep; - } - p.end(); - KIcon resultIcon = KIcon(); - resultIcon.addPixmap( pixmap, QIcon::Normal ); - - KIconEffect iconEffect; - pixmap = iconEffect.apply( pixmap, KIconLoader::Small, KIconLoader::ActiveState ); - resultIcon.addPixmap( pixmap, QIcon::Selected ); - resultIcon.addPixmap( pixmap, QIcon::Active ); - - return resultIcon; -} - -void GlobalApplet::startFadeAnimation( QGraphicsWidget* w, qreal targetOpacity ) -{ - Plasma::Animation *anim = GlobalApplet::fadeAnimation( w, targetOpacity ); - if ( anim ) { - anim->start( QAbstractAnimation::DeleteWhenStopped ); - } -} - -Plasma::Animation* GlobalApplet::fadeAnimation( QGraphicsWidget* w, qreal targetOpacity ) -{ - if ( w->geometry().width() * w->geometry().height() > 250000 ) { - // Don't fade big widgets for performance reasons - w->setOpacity( targetOpacity ); - return 0; - } - - Plasma::Animation *anim = Plasma::Animator::create( Plasma::Animator::FadeAnimation ); - anim->setTargetWidget( w ); - anim->setProperty( "startOpacity", w->opacity() ); - anim->setProperty( "targetOpacity", targetOpacity ); - return anim; -} diff --git a/applet/global.h b/applet/global.h deleted file mode 100644 index 615dccc..0000000 --- a/applet/global.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef GLOBAL_HEADER -#define GLOBAL_HEADER - -/** @file - * @brief This file contains enumerations and the GlobalApplet class. - * @author Friedrich Pülz */ - -#include // Default argument -#include "kdeversion.h" - -template< class T > -class QList; -class KIcon; - -namespace Plasma { - class Animation; -} -class QGraphicsWidget; - -/** @brief Icons to be displayed by the Plasma::IconWidget in the applet's top left corner. */ -enum MainIconDisplay { - DepartureListErrorIcon, - ArrivalListErrorIcon = DepartureListErrorIcon, - - DepartureListOkIcon, - ArrivalListOkIcon = DepartureListOkIcon, - - AbortJourneySearchIcon, - GoBackIcon, - - JourneyListErrorIcon, - JourneyListOkIcon -}; - -/** - * @brief Types of departure / arrival lists. - * - * The values of the enumerators shouldn't be changed because they are saved to the config file. - **/ -enum DepartureArrivalListType { - _UseCurrentDepartureArrivalListType = 999, /**< Only for use as default parameter - * to use the settings from PublicTransportSettings */ - - DepartureList = 0, /**< A list of departures from the home stop */ - ArrivalList = 1 /**< A list of arrivals at the home stop */ -}; - -/** @brief Types of the title of the applet. */ -enum TitleType { - ShowDepartureArrivalListTitle = 0, /**< Shows an icon, the stop name and additional information */ - ShowSearchJourneyLineEdit = 1, /**< Shows a line edit for journey search requests */ - ShowSearchJourneyLineEditDisabled = 2, /**< Shows a disabled line edit for journey search requests */ - ShowJourneyListTitle = 3, /**< Shows an icon, a title and additional information */ - ShowIntermediateDepartureListTitle = 4 /**< Shows a back icon, the stop name and additional information */ -}; - -/** @brief A set of flags for route stops in the DepartureModel/JourneyModel. */ -enum RouteItemFlag { - RouteItemDefault = 0x0000, /**< Default route stop settings. */ - RouteItemHighlighted = 0x0001, /**< The stop item is currently highlighted. */ - RouteItemHomeStop = 0x0002 /**< The stop item is the currently selected home stop. */ -}; -Q_DECLARE_FLAGS( RouteItemFlags, RouteItemFlag ) -Q_DECLARE_OPERATORS_FOR_FLAGS( RouteItemFlags ) - -/** @brief A set of flags for stops in a route. */ -enum RouteStopFlag { - RouteStopDefault = 0x0000, /**< The route stop has no special settings. */ - RouteStopIsOrigin = 0x0001, /**< The route stop is the origin of the route. Can't be used - * together with RouteStopIsIntermediate or RouteStopIsTarget. */ - RouteStopIsTarget = 0x0002, /**< The route stop is the target of the route. Can't be used - * together with RouteStopIsIntermediate or RouteStopIsOrigin. */ - RouteStopIsIntermediate = 0x0004, /**< The route stop is an intermediate one (not the first - * and not the last). Can not be used together with - * RouteStopIsOrigin or RouteStopIsTarget. */ - RouteStopIsConnectingStop = 0x0008, /**< The route stop is a connecting stop (an intermediate - * stop with a change of the public transport vehicle). - * Can not be used together with RouteStopIsOrigin or - * RouteStopIsTarget. */ - RouteStopIsHomeStop = 0x0010, /**< The route stop is the currently selected home stop. */ - RouteStopIsHighlighted = 0x0020 /**< The route stop is the currently highlighted stop. */ -}; -Q_DECLARE_FLAGS( RouteStopFlags, RouteStopFlag ) -Q_DECLARE_OPERATORS_FOR_FLAGS( RouteStopFlags ) - -/** @brief Different states of alarm. */ -enum AlarmState { - NoAlarm = 0x0000, /**< No alarm is set. */ - - AlarmPending = 0x0001, /**< An alarm is set and pending. */ - AlarmFired = 0x0002, /**< An alarm has been fired. */ - - AlarmIsAutoGenerated = 0x0004, /**< There is an alarm setting with the - * same settings that are used to autogenerate alarms for departures - * using the context menu. Items with this alarm state can remove - * their alarm. */ - AlarmIsRecurring = 0x0008 /**< There is a recurring alarm that matches the departure. */ -}; -Q_DECLARE_FLAGS( AlarmStates, AlarmState ) -Q_DECLARE_OPERATORS_FOR_FLAGS( AlarmStates ) - -/** @class GlobalApplet - * @brief Contains global static methods. */ -class GlobalApplet { -public: - static Plasma::Animation *fadeAnimation( QGraphicsWidget *w, qreal targetOpacity ); - static void startFadeAnimation( QGraphicsWidget *w, qreal targetOpacity ); - - static KIcon stopIcon( RouteStopFlags routeStopFlags ); - - /** @brief Creates an icon that has another icon as overlay on the bottom right. */ - static KIcon makeOverlayIcon( const KIcon &icon, const KIcon &overlayIcon, - const QSize &overlaySize = QSize(10, 10), int iconExtend = 16 ); - - /** @brief Creates an icon that has another icon as overlay on the bottom right. */ - static KIcon makeOverlayIcon( const KIcon &icon, const QString &overlayIconName, - const QSize &overlaySize = QSize(10, 10), int iconExtend = 16 ); - - /** @brief Creates an icon that has other icons as overlay on the bottom. */ - static KIcon makeOverlayIcon( const KIcon &icon, const QList &overlayIconsBottom, - const QSize &overlaySize = QSize(10, 10), int iconExtend = 16 ); -}; - -#endif // Multiple inclusion guard diff --git a/applet/journeysearchitem.cpp b/applet/journeysearchitem.cpp deleted file mode 100644 index 3fb2f52..0000000 --- a/applet/journeysearchitem.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright (C) 2011 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -// Header include -#include "journeysearchitem.h" - -// Own includes -#include "journeysearchmodel.h" - -// Qt includes -#include - -JourneySearchItem::JourneySearchItem( const QString &journeySearch, const QString &name, - bool favorite ) - : m_journeySearch(journeySearch), m_name(name), m_favorite(favorite) -{ - -} - -JourneySearchItem::JourneySearchItem() : m_favorite(false) -{ - -} - -JourneySearchItem::JourneySearchItem( const JourneySearchItem &other ) - : m_journeySearch(other.m_journeySearch), m_name(other.m_name), m_favorite(other.m_favorite) -{ - -} - -QIcon JourneySearchItem::icon() const -{ - return JourneySearchModel::favoriteIcon( isFavorite() ); -} - -bool JourneySearchItem::operator==( const JourneySearchItem &other ) const -{ - return m_favorite == other.m_favorite && m_name == other.m_name && - m_journeySearch == other.m_journeySearch; -} diff --git a/applet/journeysearchitem.h b/applet/journeysearchitem.h deleted file mode 100644 index 3f6b90d..0000000 --- a/applet/journeysearchitem.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - Copyright (C) 2011 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -/** @file - * @brief This file contains a model for journey searches. - * @author Friedrich Pülz */ - -#ifndef JOURNEYSEARCHITEM_H -#define JOURNEYSEARCHITEM_H - -// Qt includes -#include // For Q_DECLARE_METATYPE - -class QIcon; - -/** - * @brief An item containing information about journey searches. - * - * Can be used independently from JourneySearchModel, which uses a derived class - * JourneySearchModelItem. - * - * JourneySearchItem's can be made favorite using setFavorite. Check if an item is a favorite - * journey search using isFavorite. - **/ -class JourneySearchItem { -public: - /** - * @brief Creates a new journey search item. - * - * @param journeySearch The journey search string to associate with this journey search item. - * @param name The name to be used as alias for @p journeySearch. - * @param favorite Whether or not @p journeySearch is a favorite journey search. - * Defaults to false. - **/ - explicit JourneySearchItem( const QString &journeySearch, - const QString &name = QString(), bool favorite = false ); - - /** - * @brief Creates an invalid journey search item. - * - * This constructor is needed for JourneySearchItem to work in QVariant's. - **/ - JourneySearchItem(); - - /** @brief Copy constructor. */ - JourneySearchItem( const JourneySearchItem &other ); - - /** @brief Destructor. */ - virtual ~JourneySearchItem() {}; - - /** @brief Gets the name to be used as alias for journeySearch() if not empty. */ - QString name() const { return m_name; }; - - /** @brief Gets the journey search string associated with this journey search item. */ - QString journeySearch() const { return m_journeySearch; }; - - /** @brief If name() is not empty it gets returned, otherwise journeySearch() gets returned. */ - QString nameOrJourneySearch() const { return m_name.isEmpty() ? m_journeySearch : m_name; }; - - /** @brief Gets the icon for this item. */ - QIcon icon() const; - - /** @brief Whether or not this journey search item is a favorite journey search. */ - bool isFavorite() const { return m_favorite; }; - - /** - * @brief Sets whether or not this journey search item is a favorite journey search. - * - * @param favorite True, if this journey search item is a favorite journey search. - * False, otherwise. - **/ - virtual void setFavorite( bool favorite ) { m_favorite = favorite; }; - - /** - * @brief Sets the journey search string associated with this journey search item. - * - * @param journeySearch The new journey search string. - **/ - virtual void setJourneySearch( const QString &journeySearch ) - { m_journeySearch = journeySearch; }; - - /** - * @brief Sets the name to be used as alias for journeySearch(). - * - * @param name The new name. - **/ - virtual void setName( const QString &name ) { m_name = name; }; - - /** @brief Compares this journey search item with @p other. */ - bool operator ==( const JourneySearchItem &other ) const; - -private: - QString m_journeySearch; - QString m_name; - bool m_favorite; -}; - -/** @brief Enable usage of JourneySearchItem's in QVariant's. */ -Q_DECLARE_METATYPE( JourneySearchItem ) - -/** @brief Enable usage of QList's of JourneySearchItem's in QVariant's. */ -Q_DECLARE_METATYPE( QList ) - -#endif // Multiple inclusion guard diff --git a/applet/journeysearchlineedit.cpp b/applet/journeysearchlineedit.cpp deleted file mode 100644 index d26be92..0000000 --- a/applet/journeysearchlineedit.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "journeysearchlineedit.h" -#include "journeysearchparser.h" - -#include -#include -#include -#include -#include -#include -#include - -JourneySearchHighlighter::JourneySearchHighlighter( QTextDocument* parent ) - : QSyntaxHighlighter( parent ) -{ - m_formatStopName.setFontWeight( QFont::Bold ); - m_formatStopName.setForeground( Qt::darkMagenta ); - m_formatKeyword.setFontWeight( QFont::Bold ); - m_formatKeyword.setForeground( Qt::darkRed ); - m_formatValue.setForeground( Qt::blue ); - m_formatError.setFontItalic( true ); - m_formatError.setForeground( Qt::red ); -} - -int JourneySearchHighlighter::highlightKeywords( const QString& text, - const QStringList& keywords, const QTextCharFormat& format, - int maxAllowedOccurances, int needsToStartAt ) -{ - QTextCharFormat curFormat = format, curKeywordFormat = m_formatKeyword; - QRegExp expression( QString( "\\b(%1)\\b" ).arg( keywords.join( "|" ) ), Qt::CaseInsensitive ); - int index = text.indexOf( expression ); - int count = 0; - while ( index >= 0 ) { - if (( needsToStartAt != -1 && index != needsToStartAt ) - || count == maxAllowedOccurances ) { - // The keyword doesn't start at needsToStartAt, if given - curFormat = m_formatError; - curKeywordFormat = m_formatError; - } - - if ( expression.captureCount() >= 3 ) { - // Matched a keyword with it's value - setFormat( expression.pos( 2 ), expression.cap( 2 ).length(), curKeywordFormat ); - setFormat( expression.pos( 3 ), expression.cap( 3 ).length(), curFormat ); - } else { - setFormat( index, expression.matchedLength(), curFormat ); - } - - // Get next keyword - index = text.indexOf( expression, index + expression.matchedLength() ); - ++count; - } - - return count; -} - -int JourneySearchHighlighter::highlightCombinations( const QString& text, - const QStringList& keywords, const QStringList& keywordValues, - const QTextCharFormat& format, int maxAllowedOccurances, int needsToStartAt ) -{ - int count = 0; - foreach( const QString &keyword, keywords ) { - foreach( const QString &value, keywordValues ) { - QString str = QString( "(%1) (%2)" ).arg( keyword ).arg( value ); - count += highlightKeywords( text, QStringList() << str, format, - maxAllowedOccurances, needsToStartAt ); - } - } - return count; -} - -void JourneySearchHighlighter::highlightBlock( const QString& text ) -{ - // Highlight keywords - highlightKeywords( text, QStringList() << JourneySearchParser::toKeywords() - << JourneySearchParser::fromKeywords(), m_formatKeyword, 1, 0 ); - highlightKeywords( text, QStringList() << JourneySearchParser::arrivalKeywords() - << JourneySearchParser::departureKeywords(), m_formatKeyword, 1 ); - highlightKeywords( text, JourneySearchParser::timeKeywordsTomorrow(), m_formatKeyword, 1 ); - - // Highlight date/time keys and values - // ("[time]" or "[date]" or "[time], [date]" or "[date], [time]") - int matched = highlightCombinations( text, JourneySearchParser::timeKeywordsAt(), - QStringList() << "\\d{2}:\\d{2}(, \\d{2}\\.\\d{2}\\.(\\d{2,4})?)?" - << "\\d{2}:\\d{2}(, \\d{2}-\\d{2}(-\\d{2,4})?)?" - << "\\d{2}:\\d{2}(, (\\d{2,4}-)?\\d{2}-\\d{2})?" - << "\\d{2}\\.\\d{2}\\.(\\d{2,4})?(, \\d{2}:\\d{2})?" - << "\\d{2}-\\d{2}(-\\d{2,4})?(, \\d{2}:\\d{2})?" - << "(\\d{2,4}-)?\\d{2}-\\d{2}(, \\d{2}:\\d{2})?", - m_formatValue, 1 ); - - // Highlight relative time keys and values - highlightCombinations( text, JourneySearchParser::timeKeywordsIn(), - QStringList() << JourneySearchParser::relativeTimeString( "\\d{1,}" ), - m_formatValue, matched == 0 ? 1 : 0 ); - - // Highlight stop name if it is inside double quotes - QRegExp expression = QRegExp( "\\s?\"[^\"]*\"\\s?" ); - int index = text.indexOf( expression ); - while ( index >= 0 ) { - int length = expression.matchedLength(); - setFormat( index, length, m_formatStopName ); - index = text.indexOf( expression, index + length ); - } -} - -JourneySearchLineEdit::JourneySearchLineEdit( QWidget* parent ) - : KLineEdit( parent ), m_doc(new QTextDocument(this)) -{ - init(); -} - -JourneySearchLineEdit::JourneySearchLineEdit( const QString& string, QWidget* parent ) - : KLineEdit( string, parent ), m_doc(new QTextDocument(this)) -{ - init(); -} - -void JourneySearchLineEdit::init() -{ - m_hScroll = m_cursor = 0; - - m_doc->setDocumentMargin( 0 ); - m_doc->setDefaultFont( font() ); - - // Set the QSyntaxHighlighter to be used - m_highlighter = new JourneySearchHighlighter( m_doc ); - m_highlighter->formatStopName().setForeground( - KColorScheme(QPalette::Active).foreground(KColorScheme::NeutralText) ); - m_highlighter->formatKeyword().setForeground( - KColorScheme(QPalette::Active).foreground(KColorScheme::PositiveText) ); - m_highlighter->formatValue().setForeground( - KColorScheme(QPalette::Active).foreground(KColorScheme::PositiveText) ); - m_highlighter->formatError().setForeground( - KColorScheme(QPalette::Active).foreground(KColorScheme::NegativeText) ); - - connect( this, SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString)) ); -} - -void JourneySearchLineEdit::slotTextChanged( const QString& ) -{ - m_doc->setHtml( text() ); - m_doc->documentLayout(); -} - -void JourneySearchLineEdit::mouseDoubleClickEvent( QMouseEvent* ev ) -{ - if ( ev->button() == Qt::LeftButton ) { - deselect(); - QRect cr = lineEditContents(); - m_cursor = m_doc->documentLayout()->hitTest( - ev->posF() - cr.topLeft() + QPoint( m_hScroll, 0 ), Qt::FuzzyHit ); - - QTextBlock block = m_doc->findBlockByNumber( 0 ); - if ( block.isValid() ) { - m_cursor = block.layout()->previousCursorPosition( m_cursor, QTextLayout::SkipWords ); - int end = block.layout()->nextCursorPosition( m_cursor, QTextLayout::SkipWords ); - - QString t = text(); - while ( end > m_cursor && t[end-1].isSpace() ) { - --end; - } - moveCursor( end, true ); - } - } else { - KLineEdit::mouseDoubleClickEvent( ev ); - } -} - -void JourneySearchLineEdit::mousePressEvent( QMouseEvent* ev ) -{ - if ( ev->button() == Qt::LeftButton ) { - // Send clicks on the clear button to KLineEdit - if ( isClearButtonShown() ) { - QSize sz = clearButtonUsedSize(); - QRect rect( width() - sz.width(), 0, sz.width(), sz.height() ); - if ( rect.contains( ev->pos() ) ) { - KLineEdit::mousePressEvent( ev ); - return; - } - } - - bool mark = ev->modifiers() & Qt::ShiftModifier; - QRect cr = lineEditContents(); - m_cursor = m_doc->documentLayout()->hitTest( - ev->posF() - cr.topLeft() + QPoint( m_hScroll, 0 ), Qt::FuzzyHit ); - moveCursor( m_cursor, mark ); - } else { - KLineEdit::mousePressEvent( ev ); - } -} - -void JourneySearchLineEdit::mouseMoveEvent( QMouseEvent* ev ) -{ - if ( ev->buttons().testFlag( Qt::LeftButton ) ) { - int cursor = 0; - QRect cr = lineEditContents(); - cursor = m_doc->documentLayout()->hitTest( - ev->posF() - cr.topLeft() + QPoint( m_hScroll, 0 ), Qt::FuzzyHit ); - - moveCursor( cursor, true ); - } else { - KLineEdit::mouseMoveEvent( ev ); - } -} - -void JourneySearchLineEdit::moveCursor( int pos, bool mark ) -{ - if ( mark ) { - setSelection( m_cursor, pos - m_cursor ); - } else { - setCursorPosition( pos ); - update(); - } -} - -void JourneySearchLineEdit::paintEvent( QPaintEvent* ) -{ - QPainter p( this ); - QRect cr = lineEditContents(); - - // Draw background panel - if ( hasFrame() ) { - QStyleOptionFrameV2 opt; - initStyleOption( &opt ); - style()->drawPrimitive( QStyle::PE_PanelLineEdit, &opt, &p, this ); - } - - // Draw text, cursor and selection - int cursorPos = cursorPosition(); - QTextBlock block = m_doc->findBlockByNumber( 0 ); - if ( block.isValid() ) { - int width = cr.width(); - if ( isClearButtonShown() ) { // Add space for the clear button - width -= clearButtonUsedSize().width(); - } - QTextLine line = block.layout()->lineForTextPosition( - block.layout()->isValidCursorPosition( cursorPos ) ? cursorPos : cursorPos - 1 ); - if ( line.isValid() ) { - // Horizontal scrolling - int cix = line.cursorToX( cursorPos ); - int minLB = qMax( 0, -fontMetrics().minLeftBearing() ); - int minRB = qMax( 0, -fontMetrics().minRightBearing() ); - int widthUsed = line.width() + 1 + minRB; - if (( minLB + widthUsed ) <= width ) { - switch ( alignment() ) { - case Qt::AlignRight: - m_hScroll = widthUsed - width; - break; - case Qt::AlignHCenter: - m_hScroll = ( widthUsed - width ) / 2; - break; - default: - m_hScroll = 0; - break; - } - m_hScroll -= minLB; - } else if ( cix - m_hScroll >= width ) { - m_hScroll = cix - width + 1; // Scroll to the right - } else if ( cix - m_hScroll < 0 ) { - m_hScroll = cix; // Scroll to the left - } else if ( widthUsed - m_hScroll < width ) { - // Scroll to the left, because there's space on the right - m_hScroll = widthUsed - width + 1; - } - } - QPoint topLeft = cr.topLeft() - QPoint( m_hScroll, 0 ); - - // Set formats for a selection - QVector formats; - if ( hasSelectedText() ) { - QTextLayout::FormatRange selection; - selection.format.setBackground( palette().highlight() ); - selection.format.setForeground( palette().highlightedText() ); - selection.start = selectionStart(); - selection.length = selectedText().length(); - formats << selection; - } - - // Draw text and cursor - block.layout()->drawCursor( &p, topLeft, cursorPosition() ); - - // The clipping of QTextLayout::draw doesn't work with no selection - p.setClipRect( cr ); - // Get text width - int textWidth = 0; - QTextBlock block = m_doc->findBlockByNumber( 0 ); - if ( block.isValid() ) { - textWidth = block.layout()->boundingRect().width(); - } - int availableWidth = cr.width() + m_hScroll; - if ( isClearButtonShown() ) { - availableWidth -= clearButtonUsedSize().width(); - } - if ( m_hScroll > 0 || textWidth > availableWidth ) { - int fadeArea = 20; - - QPixmap pix( cr.size() ); - pix.fill( Qt::transparent ); - QPainter pixPainter( &pix ); - - // Draw text - block.layout()->draw( &pixPainter, QPoint( -m_hScroll, 0 ), formats, - QRect( 0, 0, cr.width(), cr.height() ) ); - - // Draw fade out rects - pixPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn ); - if ( m_hScroll > 0 ) { - QLinearGradient alphaGradient( 0, 0, 1, 0 ); - alphaGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); - if ( isLeftToRight() ) { - alphaGradient.setColorAt( 0, Qt::transparent ); - alphaGradient.setColorAt( 1, Qt::black ); - } else { - alphaGradient.setColorAt( 0, Qt::black ); - alphaGradient.setColorAt( 1, Qt::transparent ); - } - - pixPainter.fillRect( QRect( 0, 0, fadeArea, cr.height() ), alphaGradient ); - } - - if ( textWidth > width + m_hScroll ) { - QLinearGradient alphaGradient( 0, 0, 1, 0 ); - alphaGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); - if ( isLeftToRight() ) { - alphaGradient.setColorAt( 0, Qt::black ); - alphaGradient.setColorAt( 1, Qt::transparent ); - } else { - alphaGradient.setColorAt( 0, Qt::transparent ); - alphaGradient.setColorAt( 1, Qt::black ); - } - - if ( isClearButtonShown() ) { - fadeArea += clearButtonUsedSize().width(); - } - pixPainter.fillRect( QRect( cr.width() - fadeArea, 0, fadeArea, cr.height() ), alphaGradient ); - } - - pixPainter.end(); - p.drawPixmap( cr, pix ); - } else { - block.layout()->draw( &p, topLeft, formats, cr ); - } - } -} - -QRect JourneySearchLineEdit::lineEditContents() const -{ - QStyleOptionFrameV2 opt; - initStyleOption( &opt ); - QRect cr = style()->subElementRect( QStyle::SE_LineEditContents, &opt, this ); - cr.setLeft( cr.left() + 2 ); - cr.setRight( cr.right() - 2 ); - cr.setTop(( height() - cr.height() ) / 2 + 1 ); - return cr; -} diff --git a/applet/journeysearchlineedit.h b/applet/journeysearchlineedit.h deleted file mode 100644 index 1ae5d59..0000000 --- a/applet/journeysearchlineedit.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef JOURNEYSEARCHLINEEDIT_HEADER -#define JOURNEYSEARCHLINEEDIT_HEADER - -// KDE includes -#include // Base class - -// Qt includes -#include // Base class - -class QTextDocument; - -/** @brief Highlights journey search keywords, values and the stop name if it is double quoted. */ -class JourneySearchHighlighter : public QSyntaxHighlighter { -public: - JourneySearchHighlighter( QTextDocument* parent ); - - /** @brief Format for the double quoted stop name (actually for every double quoted string). */ - QTextCharFormat &formatStopName() { return m_formatStopName; }; - - /** @brief Format for keywords. */ - QTextCharFormat &formatKeyword() { return m_formatKeyword; }; - - /** @brief Format for values of keywords. */ - QTextCharFormat &formatValue() { return m_formatValue; }; - - /** - * @brief Format for syntax errors, eg. a keyword which is already in the string but only - * allowed once. - * - * @note Not all syntax error are noticed currently. - **/ - QTextCharFormat &formatError() { return m_formatError; }; - -protected: - /** @return The number of matched keywords. */ - int highlightKeywords( const QString &text, const QStringList &keywords, - const QTextCharFormat &format, int maxAllowedOccurances = -1, int needsToStartAt = -1 ); - - /** @return The number of matched keyword value combinations. */ - int highlightCombinations( const QString &text, const QStringList &keywords, - const QStringList &keywordValues, const QTextCharFormat &format, - int maxAllowedOccurances = -1, int needsToStartAt = -1 ); - - virtual void highlightBlock( const QString& text ); - -private: - QTextCharFormat m_formatStopName, m_formatKeyword, m_formatValue, m_formatError; -}; - -/** - * @brief A KLineEdit with syntax highlighting. - * - * It uses @ref JourneySearchHighlighter but it could be replaced by any other QSyntaxHighlighter. - * Mouse events are reimplemented, to get correct positions in the highlighted - * QTextDocument. Some things are missing, like triple click or double click - * and mouse move to select more words (but one double click on a word works). */ -class JourneySearchLineEdit : public KLineEdit { - Q_OBJECT - -public: - JourneySearchLineEdit( QWidget* parent = 0 ); - explicit JourneySearchLineEdit( const QString &string, QWidget* parent = 0 ); - - ~JourneySearchLineEdit() { delete m_highlighter; }; - -protected slots: - /** @brief Sets the new text to the QTextDocument and highlights it. */ - void slotTextChanged( const QString &newText ); - -protected: - /** @brief Reimplemented to select the correct word in the QTextDocument. */ - virtual void mouseDoubleClickEvent( QMouseEvent *ev ); - - /** @brief Reimplemented to set the cursor to the correct position in the QTextDocument. */ - virtual void mousePressEvent( QMouseEvent *ev ); - - /** @brief Reimplemented to select the correct characters in the QTextDocument. */ - virtual void mouseMoveEvent( QMouseEvent *ev ); - - /** @brief Reimplemented to paint the highlighted QTextDocument. */ - virtual void paintEvent( QPaintEvent *ev ); - - /** @brief Does effectively almost the same as QLineEditPrivate::moveCursor(). */ - void moveCursor( int pos, bool mark ); - - /** @brief Gets the QRect in which the QTextDocument is drawn. */ - QRect lineEditContents() const; - -private: - void init(); - - int m_hScroll, m_cursor; // contains values that are normally stored in QLineEditPrivate - QTextDocument *const m_doc; // Used to draw the highlighted text - JourneySearchHighlighter *m_highlighter; // The used QSyntaxHighlighter -}; - -#endif // Multiple inclusion guard diff --git a/applet/journeysearchlistview.cpp b/applet/journeysearchlistview.cpp deleted file mode 100644 index 52601b1..0000000 --- a/applet/journeysearchlistview.cpp +++ /dev/null @@ -1,418 +0,0 @@ -/* - Copyright (C) 2011 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -// Header include -#include "journeysearchlistview.h" - -// Own includes -#include "journeysearchmodel.h" - -// KDE includes -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include - -JourneySearchListView::JourneySearchListView( QWidget *parent ) - : QListView(parent) -{ - // Create actions - m_addAction = new KAction( KIcon("list-add"), - i18nc("@action", "&Add Journey Search"), this ); - m_removeAction = new KAction( KIcon("list-remove"), - i18nc("@action", "&Remove"), this ); - m_editAction = new KAction( KIcon("document-edit"), - i18nc("@action", "&Edit"), this ); - m_toggleFavoriteAction = new KAction( this ); - - // Connect actions - connect( m_addAction, SIGNAL(triggered()), - this, SLOT(addJourneySearch()) ); - connect( m_removeAction, SIGNAL(triggered()), - this, SLOT(removeCurrentJourneySearch()) ); - connect( m_editAction, SIGNAL(triggered()), - this, SLOT(editJourneySearchAction()) ); - connect( m_toggleFavoriteAction, SIGNAL(triggered()), - this, SLOT(toggleFavorite()) ); - - // Add the actions to this views action list - addAction( m_addAction ); - addAction( m_removeAction ); - addAction( m_editAction ); - addAction( m_toggleFavoriteAction ); - - // Use JourneySearchDelegate for all items - setItemDelegate( new JourneySearchDelegate(this) ); -} - -void JourneySearchListView::contextMenuEvent( QContextMenuEvent *event ) -{ - if ( !qobject_cast(model()) ) { - kDebug() << "Needs a JourneySearchModel!"; - } - - // Get the model index to create a context menu for - QModelIndex index = indexAt( event->pos() ); - - // Disable remove journey search action, if nothing is selected - if ( m_removeAction ) { - m_removeAction->setEnabled( index.isValid() ); - } - if ( m_editAction ) { - m_editAction->setEnabled( index.isValid() ); - } - - // Update toggle favorite state action (add to/remove from favorites) - if ( m_toggleFavoriteAction ) { - bool isAddToFavoritesAction = false; - if ( index.isValid() ) { - m_toggleFavoriteAction->setEnabled( true ); - - const bool isFavorite = index.data( JourneySearchModel::FavoriteRole ).toBool(); - if ( isFavorite ) { - m_toggleFavoriteAction->setText( i18nc("@action", "Remove From Favorites") ); - m_toggleFavoriteAction->setIcon( - KIcon("favorites", 0, QStringList() << "list-remove") ); - } else { - isAddToFavoritesAction = true; - } - } else { - m_toggleFavoriteAction->setEnabled( false ); - - // Ensure action is initialized - isAddToFavoritesAction = true; - } - - if ( isAddToFavoritesAction ) { - m_toggleFavoriteAction->setText( i18nc("@action", "Add to Favorites") ); - m_toggleFavoriteAction->setIcon( KIcon("favorites", 0, QStringList() << "list-add") ); - } - } - - KMenu::exec( actions(), event->globalPos() ); -} - -void JourneySearchListView::addJourneySearch() -{ - // Add icon and set as favorite - JourneySearchModel *_model = qobject_cast( model() ); - Q_ASSERT_X( _model, "JourneySearchListView::addJourneySearch()", "Needs a JourneySearchModel!" ); - JourneySearchModelItem *item = _model->addJourneySearch( QString(), QString(), true ); - QModelIndex index = _model->indexFromItem( item ); - - // Start editing the new journey search - setCurrentIndex( index ); - edit( index ); -} - -void JourneySearchListView::removeCurrentJourneySearch() -{ - QModelIndex index = currentIndex(); - if ( !index.isValid() ) { - return; - } - - // Remove the journey search item at the current index - JourneySearchModel *_model = qobject_cast( model() ); - Q_ASSERT_X( _model, "JourneySearchListView::addJourneySearch()", "Needs a JourneySearchModel!" ); - _model->removeJourneySearch( index ); -} - -void JourneySearchListView::editJourneySearchAction() -{ - QModelIndex index = currentIndex(); - if ( !index.isValid() ) { - return; - } - - // Start the edit mode for the journey search item at the current index - edit( index ); -} - -void JourneySearchListView::toggleFavorite() -{ - QModelIndex index = currentIndex(); - if ( !index.isValid() ) { - return; - } - - // Toggle favorite state and resort the model - JourneySearchModel *_model = qobject_cast( model() ); - Q_ASSERT_X( _model, "JourneySearchListView::addJourneySearch()", "Needs a JourneySearchModel!" ); - JourneySearchModelItem *item = _model->item( index ); - item->setFavorite( !item->isFavorite() ); - _model->sort(); -} - -JourneySearchDelegate::JourneySearchDelegate( QObject *parent ) - : QStyledItemDelegate(parent) -{ -} - -QSize JourneySearchDelegate::sizeHint( const QStyleOptionViewItem &option, - const QModelIndex &index ) const -{ - const QString name = index.data(JourneySearchModel::NameRole).toString(); - const QString journeySearch = index.data(JourneySearchModel::JourneySearchRole).toString(); - - QStyleOptionViewItemV4 opt = option; - const int width = opt.decorationSize.width() + 8 + - qMax( option.fontMetrics.width(name), option.fontMetrics.width(journeySearch) ); - const int height = 2 * qMin( opt.decorationSize.height() + 4, option.fontMetrics.height() + 2 ); - return QSize( width, height ); -} - -void JourneySearchDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index ) const -{ - // Initialize style options - QStyleOptionViewItemV4 opt = option; - initStyleOption( &opt, index ); - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - opt.icon = QIcon(); - opt.showDecorationSelected = true; - - // Draw background only - opt.text.clear(); - style->drawControl( QStyle::CE_ItemViewItem, &opt, painter ); - - // Draw text items and icon only while not in edit mode - const bool isEditing = opt.state.testFlag( QStyle::State_Editing ); - if ( !isEditing ) { - // Save painter state - painter->save(); - - // Calculate rectangles for the text items - const QRect textRect = style->subElementRect( QStyle::SE_ItemViewItemText, &opt ); - const int vCenter = textRect.top() + textRect.height() / 2; - const QRect nameRect( textRect.left(), vCenter - opt.fontMetrics.height(), - textRect.width(), opt.fontMetrics.height() ); - const QRect journeySearchRect( textRect.left(), vCenter, - textRect.width(), opt.fontMetrics.height() ); - - // Get text/background colors - QColor textColor; - QColor backgroundColor; - QPalette::ColorGroup group = opt.state.testFlag(QStyle::State_Active) - ? QPalette::Active : QPalette::Inactive; - if ( opt.state.testFlag(QStyle::State_Selected) ) { - textColor = option.palette.color( group, QPalette::HighlightedText); - backgroundColor = option.palette.color( group, QPalette::Highlight ); - } else { - textColor = option.palette.color( group, QPalette::Text ); - backgroundColor = option.palette.color( group, QPalette::Background ); - } - - // Get strings for the text items and a lighter color for the journey search string. - // The journey search string color mixes the text color with the background color. - const QString name = index.data(JourneySearchModel::NameRole).toString(); - const QString journeySearch = index.data(JourneySearchModel::JourneySearchRole).toString(); - const QColor lightColor = KColorUtils::mix( textColor, backgroundColor, 0.4 ); - - // Draw the text items - if ( name.isEmpty() ) { - // No name specified for the journey search - painter->setPen( textColor ); - painter->drawText( nameRect, i18nc("@info/plain","(No name specified)") ); - } else { - // A name is specified, draw it in bold font - QFont boldFont = opt.font; - boldFont.setBold( true ); - painter->setFont( boldFont); - painter->setPen( textColor ); - painter->drawText( nameRect, name ); - - // Set default font again for the journey string - painter->setFont( opt.font ); - } - - // Draw journey search string in lighter color - painter->setPen( lightColor ); - painter->drawText( journeySearchRect, journeySearch ); - - // Draw icon - const bool isFavorite = index.data( JourneySearchModel::FavoriteRole ).toBool(); - const QIcon icon = index.data(Qt::DecorationRole).value(); - const QRect iconRect = style->subElementRect( QStyle::SE_ItemViewItemDecoration, &opt ); - style->drawItemPixmap( painter, iconRect, opt.decorationAlignment, - icon.pixmap(opt.decorationSize, isFavorite ? QIcon::Normal : QIcon::Disabled, - opt.state.testFlag(QStyle::State_MouseOver) ? QIcon::On : QIcon::Off) ); - - // Restore painter state - painter->restore(); - } -} - -QWidget* JourneySearchDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index ) const -{ - // Create container editor widget - QWidget *widget = new QWidget( parent ); - - // Get rectangles - QStyleOptionViewItemV4 opt = option; - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - initStyleOption( &opt, index ); - const QRect iconRect = style->subElementRect( QStyle::SE_ItemViewItemDecoration, &opt ); - const QRect textRect = style->subElementRect( QStyle::SE_ItemViewItemText, &opt ); - - // Create favorite toggle button - ToggleIconToolButton *button = new ToggleIconToolButton( widget ); - button->setIcon( index.data(Qt::DecorationRole).value() ); - button->setToolButtonStyle( Qt::ToolButtonIconOnly ); - button->setFixedSize( iconRect.size() ); - button->setAutoRaise( true ); - button->setCheckable( true ); - button->setToolTip( i18nc("@info:tooltip", "Toggle favorite state") ); - - // Create name edit widget - KLineEdit *lineEditName = new KLineEdit( widget ); - lineEditName->setText( index.data(JourneySearchModel::NameRole).toString() ); - lineEditName->setFrame( false ); - lineEditName->setClickMessage( - i18nc("@info/plain Click message for the widget editing the journey search name.", - "Name of the journey search") ); - lineEditName->setToolTip( - i18nc("@info:tooltip", "The name for the journey search string, eg. shown in menus.") ); - - // Create journey search edit widget - KLineEdit *lineEditJourneySearch = new KLineEdit( widget ); - lineEditJourneySearch->setText( index.data(JourneySearchModel::JourneySearchRole).toString() ); - lineEditJourneySearch->setFrame( false ); - lineEditJourneySearch->setClickMessage( - i18nc("@info/plain Click message for the widget editing the journey search string.", - "Journey search string") ); - lineEditJourneySearch->setToolTip( - i18nc("@info:tooltip", "This string gets used to request journeys.") ); - - // Create layouts for the three widgets - QVBoxLayout *vLayout = new QVBoxLayout(); - vLayout->setMargin( 0 ); - vLayout->setSpacing( 0 ); - vLayout->addWidget( lineEditName ); - vLayout->addWidget( lineEditJourneySearch ); - - QHBoxLayout *layout = new QHBoxLayout( widget ); - layout->setContentsMargins( iconRect.left(), 0, 0, 0 ); - layout->setSpacing( textRect.left() - iconRect.right() ); - layout->addWidget( button ); - layout->addLayout( vLayout ); - - // Initialize editor widget - setEditorData( widget, index ); - - // Enable focus for the editor widget, - // otherwise editing may be cancelled when clicking a subwidget - widget->setFocusPolicy( Qt::StrongFocus ); - - // Use the line edit as focus proxy for the container widget, - // ie. set focus to the line edit when the container widget gets focus, - // which is the editor widget - widget->setFocusProxy( lineEditName ); - - // Set the focus to the line edit and select all text in it - lineEditName->selectAll(); - lineEditName->setFocus(); - - return widget; -} - -void JourneySearchDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const -{ - if ( editor->layout()->count() < 2 ) { - return; // TODO - } - QToolButton *button = qobject_cast( editor->layout()->itemAt(0)->widget() ); - QLayout *vLayout = editor->layout()->itemAt(1)->layout(); - KLineEdit *lineEditName = qobject_cast( vLayout->itemAt(0)->widget() ); - KLineEdit *lineEditJourneySearch = qobject_cast( vLayout->itemAt(1)->widget() ); - if ( button && lineEditName && lineEditJourneySearch ) { - // Update widget states - const bool isFavorite = index.data( JourneySearchModel::FavoriteRole ).toBool(); - button->setChecked( isFavorite ); - lineEditName->setText( index.data(JourneySearchModel::NameRole).toString() ); - lineEditJourneySearch->setText( index.data(JourneySearchModel::JourneySearchRole).toString() ); - } else { - QStyledItemDelegate::setEditorData( editor, index ); - } -} - -void JourneySearchDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index ) const -{ - if ( editor->layout()->count() < 2 ) { - return; // TODO - } - QToolButton *button = qobject_cast( editor->layout()->itemAt(0)->widget() ); - QLayout *vLayout = editor->layout()->itemAt(1)->layout(); - KLineEdit *lineEditName = qobject_cast( vLayout->itemAt(0)->widget() ); - KLineEdit *lineEditJourneySearch = qobject_cast( vLayout->itemAt(1)->widget() ); - if ( button && lineEditName && lineEditJourneySearch ) { - if ( lineEditJourneySearch->text().isEmpty() ) { - // Remove items with empty journey search string - model->removeRow( index.row() ); - return; - } - - // Update both journey search string and favorite state at once in the model - QMap roles; - roles.insert( JourneySearchModel::NameRole, lineEditName->text() ); - roles.insert( JourneySearchModel::JourneySearchRole, lineEditJourneySearch->text() ); - roles.insert( JourneySearchModel::FavoriteRole, button->isChecked() ); - model->setItemData( index, roles ); - model->sort( 0 ); - } else { - QStyledItemDelegate::setModelData( editor, model, index ); - } -} - -void JourneySearchDelegate::updateEditorGeometry( QWidget *editor, - const QStyleOptionViewItem &option, const QModelIndex &index ) const -{ - Q_UNUSED( index ); - editor->setGeometry( option.rect ); -} - -void ToggleIconToolButton::paintEvent( QPaintEvent *event ) -{ - Q_UNUSED( event ); - QPainter painter( this ); - QPixmap _icon = JourneySearchModel::favoriteIconPixmap( isChecked() ); - if ( underMouse() ) { - // Draw a highlighted version of the icon if the button is hovered - QPixmap highlightIcon = KIconLoader::global()->iconEffect()->apply( - _icon, KIconEffect::ToGamma, 1.0, QColor(), false ); - painter.drawPixmap( contentsRect(), highlightIcon ); - } else { - // Draw default icon - painter.drawPixmap( contentsRect(), _icon ); - } -} diff --git a/applet/journeysearchlistview.h b/applet/journeysearchlistview.h deleted file mode 100644 index eba3549..0000000 --- a/applet/journeysearchlistview.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - Copyright (C) 2011 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -/** @file - * @brief This file contains a QListView for journey searches, using a custom delegate. - * @author Friedrich Pülz */ - -#ifndef JOURNEYSEARCHLISTVIEW_H -#define JOURNEYSEARCHLISTVIEW_H - -// Base class includes -#include // Base class -#include // Base class -#include // Base class - -class KAction; - -/** - * @brief A QListView for journey searches with a context menu. - * - * This view is intended to be used with JourneySearchModel. - * It offers a context menu with actions like adding/removing/editing journey searches and - * toggling their favorite states. To use the context menu the used model must be of type - * JourneySearchModel for simplicity. - * JourneySearchDelegate is set as the item delegate automatically. - * - * @see JourneySearchModel - **/ -class JourneySearchListView : public QListView { - Q_OBJECT - -public: - /** @brief Creates a new JourneySearchListView with the given @p parent. */ - explicit JourneySearchListView( QWidget *parent = 0 ); - -public slots: - /** @brief Adds a new empty journey search item. */ - void addJourneySearch(); - - /** @brief Removes the currently selected journey search item. */ - void removeCurrentJourneySearch(); - - /** @brief Starts the edit mode for the currently selected journey search item. */ - void editJourneySearchAction(); - - /** @brief Toggles the favorite state of the currently selected journey search item. */ - void toggleFavorite(); - -protected: - /** @brief Overridden to create a custom context menu */ - virtual void contextMenuEvent( QContextMenuEvent *event ); - -private: - KAction *m_addAction; - KAction *m_removeAction; - KAction *m_editAction; - KAction *m_toggleFavoriteAction; -}; - -/** - * @brief An item delegate for journey search items, eg. in a JourneySearchListView. - * - * This delegate implements editor functionality using a ToggleIconToolButton to configure the - * favorite state and KLineEdit's to configure the name and the journey search string. - **/ -class JourneySearchDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - /** @brief Creates a new JourneySearchDelegate with the given @p parent. */ - explicit JourneySearchDelegate( QObject *parent = 0 ); - -protected: - virtual QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const; - virtual void paint( QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index ) const; - virtual QWidget* createEditor( QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index ) const; - virtual void setEditorData( QWidget *editor, const QModelIndex &index ) const; - virtual void setModelData( QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index ) const; - virtual void updateEditorGeometry( QWidget *editor, const QStyleOptionViewItem &option, - const QModelIndex &index ) const; -}; - -/** - * @brief A QToolButton which displays journey search favorite/recent icons. - * - * This tool button uses JourneySearchModel::favoriteIconPixmap() with isChecked() as argument - * to get the icon to draw. It does not draw any frame. - **/ -class ToggleIconToolButton : public QToolButton { - Q_OBJECT - -public: - /** @brief Creates a new ToggleIconToolButton with the given @p parent. */ - explicit ToggleIconToolButton( QWidget *parent = 0 ) : QToolButton(parent) {}; - -protected: - /** @brief Overridden to draw the correct icon, depending on the checked state. */ - virtual void paintEvent( QPaintEvent *event ); -}; - -#endif // FAVORITEJOURNEYSEARCHWIDGET_H diff --git a/applet/journeysearchmodel.cpp b/applet/journeysearchmodel.cpp deleted file mode 100644 index 1035e4f..0000000 --- a/applet/journeysearchmodel.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/* - Copyright (C) 2011 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -// Header include -#include "journeysearchmodel.h" - -// KDE includes -#include -#include -#include - -// Qt includes -#include - -JourneySearchModelItem::JourneySearchModelItem( JourneySearchModel *model, const QString &journeySearch, - const QString &name, bool favorite ) - : JourneySearchItem(journeySearch, name, favorite), m_model(model) -{ - Q_ASSERT( model ); -} - -QModelIndex JourneySearchModelItem::index() const -{ - return m_model->indexFromItem( this ); -} - -void JourneySearchModelItem::dataChanged() const -{ - QModelIndex _index = index(); - m_model->emitDataChanged( _index, _index ); -} - -JourneySearchModel::JourneySearchModel( QObject *parent ) - : QAbstractListModel(parent) -{ -} - -QPixmap JourneySearchModel::favoriteIconPixmap( const QIcon &icon, bool favorite ) -{ - return icon.pixmap( KIconLoader::SizeSmall, favorite ? QIcon::Normal : QIcon::Disabled ); -} - -QPixmap JourneySearchModel::favoriteIconPixmap( bool favorite ) -{ - return favoriteIconPixmap( favoriteIcon(), favorite ); -} - -QIcon JourneySearchModel::favoriteIcon( bool favorite ) -{ - KIcon icon( "favorites" ); - if ( favorite ) { - return icon; - } - - QIcon nonFavoriteIcon; - nonFavoriteIcon.addPixmap( favoriteIconPixmap(icon, false) ); - return nonFavoriteIcon; -} - -void JourneySearchModel::emitDataChanged( const QModelIndex &topLeft, - const QModelIndex &bottomRight ) -{ - emit dataChanged( topLeft, bottomRight ); -} - -JourneySearchModelItem *JourneySearchModel::item( const QModelIndex &index ) -{ - return static_cast( index.internalPointer() ); -} - -JourneySearchModelItem *JourneySearchModel::addJourneySearch( const QString &journeySearch, - const QString &name, bool favorite ) -{ - JourneySearchModelItem *item = new JourneySearchModelItem( this, journeySearch, name, favorite ); - - beginInsertRows( QModelIndex(), 0, 0 ); - m_items.prepend( item ); - endInsertRows(); - - return item; -} - -bool JourneySearchModel::removeJourneySearch( const QModelIndex &index ) -{ - if ( !index.isValid() ) { - return false; - } - - beginRemoveRows( QModelIndex(), index.row(), index.row() ); - m_items.removeAt( index.row() ); - endRemoveRows(); - - return true; -} - -void JourneySearchModel::clear() -{ - beginRemoveRows( QModelIndex(), 0, m_items.count() ); - m_items.clear(); - endRemoveRows(); -} - -QModelIndex JourneySearchModel::index( int row, int column, const QModelIndex &parent ) const -{ - if ( parent.isValid() || !hasIndex(row, column, QModelIndex()) ) { - return QModelIndex(); - } else { - if ( row >= 0 && row < m_items.count() && column == 0 ) { - return createIndex( row, column, m_items[row] ); - } else { - return QModelIndex(); - } - } -} - -QModelIndex JourneySearchModel::indexFromJourneySearch( const QString &journeySearch ) -{ - for ( int row = 0; row < m_items.count(); ++row ) { - JourneySearchModelItem *item = m_items[row]; - if ( item->journeySearch() == journeySearch ) { - return createIndex( row, 0, item ); - } - } - - // JourneySearchModelItem for given journey search string not found - return QModelIndex(); -} - -bool JourneySearchModel::insertRows( int row, int count, const QModelIndex &parent ) -{ - if ( parent.isValid() ) { - // This model has no children.. and does not want any ;) - return false; - } - - Q_ASSERT( row >= 0 && row <= m_items.count() ); - beginInsertRows( parent, row, row + count ); - for ( int i = count; i > 0; --i ) { - m_items.insert( row, new JourneySearchModelItem(this, QString()) ); - } - endInsertRows(); - - return true; -} - -bool JourneySearchModel::removeRows( int row, int count, const QModelIndex &parent ) -{ - if ( parent.isValid() ) { - // This model has no children - return false; - } - - Q_ASSERT( row >= 0 && row <= m_items.count() ); - beginRemoveRows( parent, row, row + count ); - for ( int i = count; i > 0; --i ) { - m_items.removeAt( row ); - } - endRemoveRows(); - - return true; -} - -QVariant JourneySearchModel::data( const QModelIndex &index, int role ) const -{ - JourneySearchModelItem *item = static_cast( index.internalPointer() ); - if ( !item ) { - kDebug() << "No item found for index" << index; - return QVariant(); - } - - switch ( role ) { - case Qt::DisplayRole: - if ( item->name().isEmpty() ) { - return item->journeySearch(); - } else { - return QString("%1 %2").arg( item->name(), item->journeySearch() ); - } - case JourneySearchRole: - return item->journeySearch(); - case NameRole: - return item->name(); - case Qt::DecorationRole: - return item->icon(); - case FavoriteRole: - return item->isFavorite(); - } - - return QVariant(); -} - -bool JourneySearchModel::setDataWithoutNotify( JourneySearchModelItem *item, const QVariant &value, - int role ) -{ - switch ( role ) { - case JourneySearchRole: - item->setJourneySearch( value.toString() ); - break; - case NameRole: - item->setName( value.toString() ); - break; - case FavoriteRole: - item->setFavorite( value.toBool() ); - break; - default: - return false; - } - return true; -} - -bool JourneySearchModel::setData( const QModelIndex &index, const QVariant &value, int role ) -{ - if ( !index.isValid() ) { - return false; - } - - bool ret = setDataWithoutNotify( item(index), value, role ); - if ( ret ) { - emit dataChanged( index, index ); - } - return ret; -} - -bool JourneySearchModel::setItemData( const QModelIndex &index, const QMap< int, QVariant > &roles ) -{ - if ( !index.isValid() ) { - return false; - } - - bool ret = false; - for ( QMap::ConstIterator it = roles.constBegin(); - it != roles.constEnd(); ++it ) - { - if ( setDataWithoutNotify(item(index), it.value(), it.key()) ) { - ret = true; - } - } - if ( ret ) { - emit dataChanged( index, index ); - } - return ret; -} - -QList< JourneySearchItem > JourneySearchModel::journeySearchItems() -{ - QList< JourneySearchItem > items; - foreach ( const JourneySearchModelItem *modelItem, m_items ) { - items << *modelItem; - } - return items; -} - -// Used to sort journey search items in the model -class JourneySearchModelLessThan -{ -public: - inline bool operator()( const JourneySearchModelItem* l, const JourneySearchModelItem* r ) const { - if ( l->isFavorite() != r->isFavorite() ) { - // Sort favorites in front of non-favorites - return l->isFavorite() && !r->isFavorite(); - } else if ( l->name().isEmpty() != r->name().isEmpty() ) { - // Sort items with a name in front of items without a name - return !l->name().isEmpty() && r->name().isEmpty(); - } else if ( !l->name().isEmpty() ) { - // Favorite/name state is the same, names are available, sort by name alhabetical - return l->name().localeAwareCompare( r->name() ) < 0; - } else { - // Favorite/name state is the same, names are not available, - // sort by journey search string alhabetical - return l->journeySearch().localeAwareCompare( r->journeySearch() ) < 0; - } - }; -}; - -void JourneySearchModel::sort( int column, Qt::SortOrder order ) -{ - if ( column != 0 ) { - return; - } - - emit layoutAboutToBeChanged(); - if ( order == Qt::AscendingOrder ) { - qStableSort( m_items.begin(), m_items.end(), JourneySearchModelLessThan() ); - } else { - kDebug() << "Not implemented"; - } - emit layoutChanged(); -} diff --git a/applet/journeysearchmodel.h b/applet/journeysearchmodel.h deleted file mode 100644 index 225af23..0000000 --- a/applet/journeysearchmodel.h +++ /dev/null @@ -1,267 +0,0 @@ -/* - Copyright (C) 2011 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -/** @file - * @brief This file contains a model for journey searches. - * @author Friedrich Pülz */ - -#ifndef JOURNEYSEARCHMODEL_H -#define JOURNEYSEARCHMODEL_H - -// Own includes -#include "journeysearchitem.h" // Base class - -// Qt includes -#include // Base class - -class JourneySearchModel; -class QIcon; - -/** - * @brief An item containing information about journey searches, used by JourneySearchModel. - * - * This class gets used by JourneySearchModel, you can not create objects youself because there - * is no public constructor. Pointers to instances of this class can be retrieved from - * JourneySearchModel. - **/ -class JourneySearchModelItem : public JourneySearchItem { - friend class JourneySearchModel; - -public: - /** @brief Gets the model of this item. */ - JourneySearchModel *model() const { return m_model; }; - - /** @brief Gets the model index of this item. */ - QModelIndex index() const; - - /** @brief Overridden from JourneySearchItem, notifies the model about the change. */ - virtual inline void setFavorite( bool favorite ) { - JourneySearchItem::setFavorite( favorite ); - dataChanged(); - }; - - /** @brief Overridden from JourneySearchItem, notifies the model about the change. */ - virtual inline void setJourneySearch( const QString &journeySearch ) { - JourneySearchItem::setJourneySearch( journeySearch ); - dataChanged(); - }; - - /** @brief Overridden from JourneySearchItem, notifies the model about the change. */ - virtual inline void setName( const QString &name ) { - JourneySearchItem::setName( name ); - dataChanged(); - }; - -protected: - /** - * @brief Creates a new journey search item associated with @p model. - * - * @param model The model in which this journey search item is. - * @param journeySearch The journey search string to associate with this journey search item. - * @param name The name to be used as alias for @p journeySearch. - * @param favorite Whether or not @p journeySearch is a favorite journey search. - * Defaults to false. - **/ - JourneySearchModelItem( JourneySearchModel *model, const QString &journeySearch, - const QString &name = QString(), bool favorite = false ); - -private: - /** @brief Notifies the associated JourneySearchModel about a change in this item. */ - void dataChanged() const; - - JourneySearchModel *m_model; -}; - -/** - * @brief A model containing journey search string. - * - * Journey search strings are represented by JourneySearchModelItem's. - * The sort() function groups favorite and non-favorite journey search items. - **/ -class JourneySearchModel : public QAbstractListModel -{ - Q_OBJECT - friend class JourneySearchModelItem; - -public: - /** @brief Additional roles used by this model. */ - enum Roles { - JourneySearchRole = Qt::UserRole + 1, /**< Contains the journey search string. */ - FavoriteRole = Qt::UserRole + 2, /**< Contains a boolean, which is true if the associated - * journey search is a favorite. */ - NameRole = Qt::UserRole + 3 /**< Contains the name to be used as alias for the journey - * search string (JourneySearchRole). */ - }; - - /** - * @brief Creates a new journey search model. - * - * @param parent The parent of this model. Default is 0. - **/ - explicit JourneySearchModel( QObject *parent = 0 ); - - /** - * @brief Destructor. - **/ - virtual ~JourneySearchModel() { - qDeleteAll( m_items ); - }; - - /** - * @brief Gets the icon to be used for items of this model. - * - * @param favorite If this is true, the icon for favorite journey search items gets returned. - * Otherwise, the icon for non-favorite items gets returned. Default is true. - * @see favoriteIconPixmap - **/ - static QIcon favoriteIcon( bool favorite = true ); - - /** - * @brief Gets a pixmap of the icon to be used for items of this model. - * - * @param favorite If this is true, the icon for favorite journey search items gets returned. - * Otherwise, the icon for non-favorite items gets returned. Default is true. - * @see favoriteIcon - **/ - static QPixmap favoriteIconPixmap( bool favorite = true ); - - /** @brief Gets the item at @p index. */ - JourneySearchModelItem *item( const QModelIndex &index ); - - /** @brief Gets the item with the given @p journeySearch. */ - inline JourneySearchModelItem *item( const QString &journeySearch ) { - return item( indexFromJourneySearch(journeySearch) ); - }; - - /** - * @brief Add @p journeySearch to the model. - * - * @param journeySearch The new journey search string to add to the model. - * @param name The name to be used as alias for @p journeySearch. - * @param favorite Whether or not the new journey search item is a favorite. Default is false. - * @return JourneySearchItem* The newly added journey search item. - **/ - JourneySearchModelItem *addJourneySearch( const QString &journeySearch, - const QString &name = QString(), bool favorite = false ); - - /** - * @brief Add @p journeySearch to the model. - * - * This is an overloaded function provided for convenience. - * - * @param item An item with information about the journey search. - * @return JourneySearchModelItem* The newly added journey search item. - **/ - inline JourneySearchModelItem *addJourneySearch( const JourneySearchModelItem &item ) { - return addJourneySearch( item.journeySearch(), item.name(), item.isFavorite() ); - }; - - /** @brief Removes the journey search item at @p index and returns true on success. */ - bool removeJourneySearch( const QModelIndex &index ); - - /** - * @brief Removes @p journeySearch from the model and returns true on success. - * - * This is an overloaded function provided for convenience. - **/ - inline bool removeJourneySearch( const QString &journeySearch ) { - return removeJourneySearch( indexFromJourneySearch(journeySearch) ); - }; - - /** @brief Clears the model, ie. removes all journey search items. */ - void clear(); - - /** @brief Gets the QModelIndex of the item with the given @p journeySearch. */ - QModelIndex indexFromJourneySearch( const QString &journeySearch ); - - /** @brief Gets the QModelIndex of @p item. */ - inline QModelIndex indexFromItem( const JourneySearchModelItem *item ) { - return indexFromJourneySearch( item->journeySearch() ); - }; - - /** - * @brief Gets the data for the given @p index and @p role. - **/ - virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; - - /** - * @brief Gets the number of rows in this model. - * - * @param parent Isn't used, because this model has no subitems. - * If a valid parent index is given, 0 is returned. Defaults to QModelIndex(). - * @return The number of rows in this model. - **/ - virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const { - return parent.isValid() ? 0 : m_items.count(); - }; - - /** - * @brief Gets an index for the given @p row and @p column. @p parent isn't used. - **/ - virtual QModelIndex index( int row, int column, - const QModelIndex& parent = QModelIndex() ) const; - - /** @brief Overridden from base class. */ - virtual bool insertRows( int row, int count, const QModelIndex &parent = QModelIndex() ); - - /** @brief Overridden from base class. */ - virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); - - /** @brief Overridden from base class. */ - virtual bool setData( const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole ); - - /** @brief Overridden from base class. */ - virtual bool setItemData( const QModelIndex &index, const QMap< int, QVariant > &roles ); - - /** @brief Gets flags for the items of this model. */ - virtual Qt::ItemFlags flags( const QModelIndex &index ) const { - return !index.isValid() ? Qt::NoItemFlags - : Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; - }; - - /** - * @brief Sorts the items of this model. Overriden from base class. - * - * Use sort() instead to use the only supported parameters. - * - * @param column Only 0 is supported here. - * @param order Only Qt::AscendingOrder is supported here. - **/ - virtual void sort( int column, Qt::SortOrder order = Qt::AscendingOrder ); - - /** - * @overload JourneySearchModel::sort( int column, Qt::SortOrder order = Qt::AscendingOrder ) - **/ - inline void sort() { sort(0, Qt::AscendingOrder); }; - - - /** @brief Gets a list of JourneySearchItem's. */ - QList journeySearchItems(); - -protected: - void emitDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ); - virtual bool setDataWithoutNotify( JourneySearchModelItem *item, const QVariant &value, - int role = Qt::EditRole ); - static QPixmap favoriteIconPixmap( const QIcon &icon, bool favorite = true ); - -private: - QList m_items; -}; - -#endif // Multiple inclusion guard diff --git a/applet/journeysearchparser.cpp b/applet/journeysearchparser.cpp deleted file mode 100644 index 6616df9..0000000 --- a/applet/journeysearchparser.cpp +++ /dev/null @@ -1,615 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "journeysearchparser.h" - -#include -#include - -#include -#include -#include -#include -#include - -const QStringList JourneySearchParser::arrivalKeywords() -{ - return i18nc( "@info/plain A comma separated list of keywords for the journey search to " - "indicate that given times are meant as arrivals. The order is used for " - "autocompletion.\nNote: Keywords should be unique for each meaning.", - "arriving,arrive,arrival,arr" ).split( ',', QString::SkipEmptyParts ); -} - -const QStringList JourneySearchParser::departureKeywords() -{ - return i18nc( "@info/plain A comma separated list of keywords for the journey search to " - "indicate that given times are meant as departures (default). The order is used " - "for autocompletion.\nNote: Keywords should be unique for each meaning.", - "departing,depart,departure,dep" ).split( ',', QString::SkipEmptyParts ); -} - -const QStringList JourneySearchParser::fromKeywords() -{ - return i18nc( "@info/plain A comma separated list of keywords for the journey search, " - "indicating that a journey FROM the given stop should be searched. This keyword " - "needs to be placed at the beginning of the field.", "from" ) - .split( ',', QString::SkipEmptyParts ); -} - -const QStringList JourneySearchParser::toKeywords() -{ - return i18nc( "@info/plain A comma separated list of keywords for the journey search, " - "indicating that a journey TO the given stop should be searched. This keyword needs " - "to be placed at the beginning of the field.", "to" ) - .split( ',', QString::SkipEmptyParts ); -} - -const QStringList JourneySearchParser::timeKeywordsAt() -{ - return i18nc( "@info/plain A comma separated list of keywords for the journey search field, " - "indicating that a date/time string follows.\nNote: Keywords should be " - "unique for each meaning.", "at" ).split( ',', QString::SkipEmptyParts ); -} - -const QStringList JourneySearchParser::timeKeywordsIn() -{ - return i18nc( "@info/plain A comma separated list of keywords for the journey search field, " - "indicating that a relative time string follows.\nNote: Keywords should " - "be unique for each meaning.", "in" ).split( ',', QString::SkipEmptyParts ); -} - -const QStringList JourneySearchParser::timeKeywordsTomorrow() -{ - return i18nc( "@info/plain A comma separated list of keywords for the journey search field, as " - "replacement for tomorrows date.\nNote: Keywords should be unique for " - "each meaning.", "tomorrow" ).split( ',', QString::SkipEmptyParts ); -} - -const QString JourneySearchParser::relativeTimeString( const QVariant &value ) -{ - return i18nc( "@info/plain The automatically added relative time string, when the journey " - "search line ends with the keyword 'in'. This should be match by the " - "regular expression for a relative time, like '(in) 5 minutes'. That " - "regexp and the keyword ('in') are also localizable. Don't include " - "the 'in' here.", "%1 minutes", value.toString() ); -} - -const QString JourneySearchParser::relativeTimeStringPattern() -{ - return i18nc( "@info/plain This is a regular expression used to match a string after the " - "'in' keyword in the journey search line. The english version matches " - "'strings like '5 mins.', '1 minute', ... '\\d+' stands for at least " - "'one digit, '\\.' is just a point, a '?' after a character means " - "that it's optional (eg. the 's' in 'mins?' is optional to match " - "singular and plural forms). Normally you will only have to translate " - "'mins?' and 'minutes?'. The regexp must include one pair of matching " - "'parantheses, that match an int (the number of minutes from now). " - "Note: '(?:...)' are non-matching parantheses.", - "(\\d+)\\s+(?:mins?\\.?|minutes?)" ); -} - -void JourneySearchParser::doCorrections( KLineEdit *lineEdit, QString *searchLine, int cursorPos, - const QStringList &words, int removedWordsFromLeft ) -{ - int selStart = -1; - int selLength = 0; - - int pos = searchLine->lastIndexOf( ' ', cursorPos - 1 ); - int posEnd = searchLine->indexOf( ' ', cursorPos ); - if ( posEnd == -1 ) { - posEnd = searchLine->length(); - } - QString lastWordBeforeCursor; - if ( posEnd == cursorPos && pos != -1 && !( lastWordBeforeCursor = searchLine->mid( - pos, posEnd - pos ).trimmed() ).isEmpty() ) { - if ( timeKeywordsAt().contains( lastWordBeforeCursor, Qt::CaseInsensitive ) ) { - // Automatically add the current time after 'at' - QString formattedTime = KGlobal::locale()->formatTime( QTime::currentTime() ); - searchLine->insert( posEnd, ' ' + formattedTime ); - selStart = posEnd + 1; // +1 for the added space - selLength = formattedTime.length(); - } else if ( timeKeywordsIn().contains( lastWordBeforeCursor, Qt::CaseInsensitive ) ) { - // Automatically add '5 minutes' after 'in' - searchLine->insert( posEnd, ' ' + relativeTimeString(5) ); - selStart = posEnd + 1; // +1 for the added space - selLength = 1; // only select the number (5) - } else { - QStringList completionItems; - completionItems << timeKeywordsAt() << timeKeywordsIn() << timeKeywordsTomorrow() - << departureKeywords() << arrivalKeywords(); - - KCompletion *comp = lineEdit->completionObject( false ); - comp->setItems( completionItems ); - comp->setIgnoreCase( true ); - QString completion = comp->makeCompletion( lastWordBeforeCursor ); - setJourneySearchWordCompletion( lineEdit, completion ); - } - } - - // Select an appropriate substring after inserting something - if ( selStart != -1 ) { - QStringList removedWords = ( QStringList )words.mid( 0, removedWordsFromLeft ); - QString removedPart = removedWords.join( " " ).trimmed(); - QString correctedSearch; - if ( removedPart.isEmpty() ) { - correctedSearch = *searchLine; - } else { - correctedSearch = removedPart + ' ' + *searchLine; - selStart += removedPart.length() + 1; - } - lineEdit->setText( correctedSearch ); - lineEdit->setSelection( selStart, selLength ); - } -} - -bool JourneySearchParser::isInsideQuotedString( const QString& testString, int cursorPos ) -{ - int posQuotes1 = testString.indexOf( '\"' ); - int posQuotes2 = testString.indexOf( '\"', posQuotes1 + 1 ); - if ( posQuotes2 == -1 ) { - posQuotes2 = testString.length(); - } - return posQuotes1 == -1 ? false : cursorPos > posQuotes1 && cursorPos <= posQuotes2; -} - -bool JourneySearchParser::parseJourneySearch( KLineEdit* lineEdit, - const QString& search, QString* stop, QDateTime* departure, - bool* stopIsTarget, bool* timeIsDeparture, int* posStart, int* len, bool correctString ) -{ - kDebug() << search; - // Initialize output parameters - stop->clear(); - *departure = QDateTime(); - *stopIsTarget = true; - *timeIsDeparture = true; - if ( posStart ) { - *posStart = -1; - } - if ( len ) { - *len = 0; - } - - QString searchLine = search; - int removedWordsFromLeft = 0; - - // Remove double spaces without changing the cursor position or selection - int selStart = !lineEdit ? 0 : lineEdit->selectionStart(); - int selLength = !lineEdit ? 0 :lineEdit->selectedText().length(); - int cursorPos = !lineEdit ? 0 :lineEdit->cursorPosition(); - cursorPos -= searchLine.left( cursorPos ).count( " " ); - if ( selStart != -1 ) { - selStart -= searchLine.left( selStart ).count( " " ); - selLength -= searchLine.mid( selStart, selLength ).count( " " ); - } - - searchLine = searchLine.replace( " ", " " ); - if ( lineEdit ) { - lineEdit->setText( searchLine ); - lineEdit->setCursorPosition( cursorPos ); - if ( selStart != -1 ) { - lineEdit->setSelection( selStart, selLength ); - } - } - - // Get word list - QStringList words = searchLine.split( ' ', QString::SkipEmptyParts ); - if ( words.isEmpty() ) { - return false; - } - - // Combine words between double quotes to one word - // to allow stop names containing keywords. - JourneySearchParser::combineDoubleQuotedWords( &words ); - - // First search for keywords at the beginning of the string ('to' or 'from') - QString firstWord = words.first(); - if ( toKeywords().contains( firstWord, Qt::CaseInsensitive ) ) { - searchLine = searchLine.mid( firstWord.length() + 1 ); - cursorPos -= firstWord.length() + 1; - ++removedWordsFromLeft; - } else if ( fromKeywords().contains( firstWord, Qt::CaseInsensitive ) ) { - searchLine = searchLine.mid( firstWord.length() + 1 ); - *stopIsTarget = false; // the given stop is the origin - cursorPos -= firstWord.length() + 1; - ++removedWordsFromLeft; - } - if ( posStart ) { - QStringList removedWords = ( QStringList )words.mid( 0, removedWordsFromLeft ); - *posStart = removedWords.isEmpty() ? 0 : removedWords.join( " " ).length() + 1; - } - - // Do corrections - if ( correctString && !isInsideQuotedString(searchLine, cursorPos) && - lineEdit && lineEdit->completionMode() != KGlobalSettings::CompletionNone ) - { - doCorrections( lineEdit, &searchLine, cursorPos, words, removedWordsFromLeft ); - } - - // Now search for keywords inside the string from back - for ( int i = words.count() - 1; i >= removedWordsFromLeft; --i ) { - QString word = words[ i ]; - if ( timeKeywordsAt().contains( word, Qt::CaseInsensitive ) ) { - // An 'at' keyword was found at position i - QString sDeparture; - JourneySearchParser::splitWordList( words, i, stop, &sDeparture, removedWordsFromLeft ); - - // Search for keywords before 'at' - QDate date; - JourneySearchParser::searchForJourneySearchKeywords( *stop, - timeKeywordsTomorrow(), departureKeywords(), arrivalKeywords(), - &date, stop, timeIsDeparture, len ); - - // Parse date and/or time from the string after 'at' - JourneySearchParser::parseDateAndTime( sDeparture, departure, &date ); - return true; - } else if ( timeKeywordsIn().contains( word, Qt::CaseInsensitive ) ) { - // An 'in' keyword was found at position i - QString sDeparture; - JourneySearchParser::splitWordList( words, i, stop, &sDeparture, removedWordsFromLeft ); - - // Match the regexp and extract the relative datetime value - QRegExp rx( relativeTimeStringPattern(), Qt::CaseInsensitive ); - int pos = rx.indexIn( sDeparture ); - if ( pos != -1 ) { - int minutes = rx.cap( 1 ).toInt(); - - // Search for keywords before 'in' - QDate date = QDate::currentDate(); - JourneySearchParser::searchForJourneySearchKeywords( *stop, - timeKeywordsTomorrow(), departureKeywords(), arrivalKeywords(), - &date, stop, timeIsDeparture, len ); - *departure = QDateTime( date, QTime::currentTime().addSecs( minutes * 60 ) ); - return true; - } - } - } - - *stop = searchLine; - - // Search for keywords at the end of the string - QDate date = QDate::currentDate(); - JourneySearchParser::searchForJourneySearchKeywords( *stop, timeKeywordsTomorrow(), - departureKeywords(), arrivalKeywords(), - &date, stop, timeIsDeparture, len ); - *departure = QDateTime( date, QTime::currentTime() ); - return false; -} - -QHash< JourneySearchParser::Keyword, QVariant > JourneySearchParser::keywordValues( - const QString& searchLine ) -{ - QHash< Keyword, QVariant > ret; - - // Get word list - QStringList words = searchLine.split( ' ', QString::SkipEmptyParts ); - if ( words.isEmpty() ) { - return ret; - } - - // Combine words between double quotes to one word - // to allow stop names containing keywords. - JourneySearchParser::combineDoubleQuotedWords( &words ); - - // First search for keywords at the beginning of the string ('to' or 'from') - int removedWordsFromLeft = 0; - QString firstWord = words.first(); - if ( toKeywords().contains( firstWord, Qt::CaseInsensitive ) ) { - ret.insert( KeywordTo, true ); - ++removedWordsFromLeft; - } else if ( fromKeywords().contains( firstWord, Qt::CaseInsensitive ) ) { - ret.insert( KeywordFrom, true ); - ++removedWordsFromLeft; - } - - for ( int i = words.count() - 1; i >= removedWordsFromLeft; --i ) { - QString word = words[ i ]; - if ( timeKeywordsAt().contains( word, Qt::CaseInsensitive ) ) { - // An 'at' keyword was found at position i - QString sDeparture, stop; - QDate date; - JourneySearchParser::splitWordList( words, i, &stop, &sDeparture, removedWordsFromLeft ); - - // Parse date and/or time from the string after 'at' - QDateTime departure; - JourneySearchParser::parseDateAndTime( sDeparture, &departure, &date ); - ret.insert( KeywordTimeAt, departure ); - break; - } else if ( timeKeywordsIn().contains( word, Qt::CaseInsensitive ) ) { - // An 'in' keyword was found at position i - QString sDeparture, stop; - JourneySearchParser::splitWordList( words, i, &stop, &sDeparture, removedWordsFromLeft ); - - // Match the regexp and extract the relative datetime value - QRegExp rx( relativeTimeStringPattern(), Qt::CaseInsensitive ); - int pos = rx.indexIn( sDeparture ); - if ( pos != -1 ) { - int minutes = rx.cap( 1 ).toInt(); - ret.insert( KeywordTimeIn, minutes ); - break; - } - } - } - - return ret; -} - -void JourneySearchParser::stopNamePosition( KLineEdit *lineEdit, - int* posStart, int* len, QString* stop ) -{ - QString stopName; - bool stopIsTarget, timeIsDeparture; - QDateTime departure; - JourneySearchParser::parseJourneySearch( lineEdit, lineEdit->text(), - &stopName, &departure, &stopIsTarget, - &timeIsDeparture, posStart, len, false ); - if ( stop ) { - *stop = stopName; - } -} - -void JourneySearchParser::setJourneySearchStopNameCompletion( - KLineEdit *lineEdit, const QString &completion ) -{ - kDebug() << "MATCH" << completion; - if ( completion.isEmpty() ) { - return; - } - - int stopNamePosStart, stopNameLen; - JourneySearchParser::stopNamePosition( lineEdit, &stopNamePosStart, &stopNameLen ); - kDebug() << "STOPNAME =" << lineEdit->text().mid( stopNamePosStart, stopNameLen ); - - int selStart = lineEdit->selectionStart(); - if ( selStart == -1 ) { - selStart = lineEdit->cursorPosition(); - } - bool stopNameChanged = selStart > stopNamePosStart && selStart + lineEdit->selectedText().length() - <= stopNamePosStart + stopNameLen; - int posStart, len; - if ( stopNameChanged ) { - posStart = stopNamePosStart; - len = stopNameLen; - - lineEdit->setText( lineEdit->text().replace( posStart, len, completion ) ); - lineEdit->setSelection( posStart + len, completion.length() - len ); - } -} - -void JourneySearchParser::setJourneySearchWordCompletion( KLineEdit* lineEdit, - const QString& match ) -{ - kDebug() << "MATCH" << match; - if ( match.isEmpty() ) { - return; - } - - int posStart = lineEdit->text().lastIndexOf( ' ', lineEdit->cursorPosition() - 1 ) + 1; - if ( posStart == -1 ) { - posStart = 0; - } - - int posEnd = lineEdit->text().indexOf( ' ', lineEdit->cursorPosition() ); - if ( posEnd == -1 ) { - posEnd = lineEdit->text().length(); - } - - int len = posEnd - posStart; - if ( len == lineEdit->text().length() ) { - kDebug() << "I'm not going to replace the whole word."; - return; // Don't replace the whole word - } - kDebug() << "Current Word" << lineEdit->text().mid( posStart, len ) << posStart - << len << lineEdit->cursorPosition(); - - lineEdit->setText( lineEdit->text().replace( posStart, len, match ) ); - lineEdit->setSelection( posStart + len, match.length() - len ); -} - -bool JourneySearchParser::searchForJourneySearchKeywords( const QString& journeySearch, - const QStringList& timeKeywordsTomorrow, const QStringList& departureKeywords, - const QStringList& arrivalKeywords, QDate* date, QString* stop, - bool* timeIsDeparture, int* len ) -{ - if ( stop->startsWith( '\"' ) && stop->endsWith( '\"' ) ) { - if ( len ) { - *len = stop->length(); - } - *stop = stop->mid( 1, stop->length() - 2 ); - return false; - } else if ( stop->trimmed().isEmpty() ) { - if ( len ) { - *len = 0; - } - stop->clear(); - return false; - } - - bool found = false, continueSearch; - do { - continueSearch = false; - - // If the tomorrow keyword is found, set date to tomorrow - QStringList wordsStop = journeySearch.split( ' ', QString::SkipEmptyParts ); - if ( wordsStop.isEmpty() ) { - continue; - } - QString lastWordInStop = wordsStop.last(); - if ( !lastWordInStop.isEmpty() && timeKeywordsTomorrow.contains( - lastWordInStop, Qt::CaseInsensitive ) ) { - *stop = stop->left( stop->length() - lastWordInStop.length() ).trimmed(); - *date = QDate::currentDate().addDays( 1 ); - - found = continueSearch = true; - lastWordInStop = wordsStop.count() >= 2 ? wordsStop[ wordsStop.count() - 2 ] : ""; - } - - // Search for departure / arrival keywords - if ( !lastWordInStop.isEmpty() ) { - if ( departureKeywords.contains( lastWordInStop, Qt::CaseInsensitive ) ) { - // If a departure keyword is found, use given time as departure time - *stop = stop->left( stop->length() - lastWordInStop.length() ).trimmed(); - *timeIsDeparture = true; - found = continueSearch = true; - } else if ( arrivalKeywords.contains( lastWordInStop, Qt::CaseInsensitive ) ) { - // If an arrival keyword is found, use given time as arrival time - *stop = stop->left( stop->length() - lastWordInStop.length() ).trimmed(); - *timeIsDeparture = false; - found = continueSearch = true; - } - } - } while ( continueSearch ); - - if ( len ) { - *len = stop->length(); - } - if ( stop->startsWith('\"') && stop->endsWith('\"') ) { - *stop = stop->mid( 1, stop->length() - 2 ); - } - return found; -} - -QStringList JourneySearchParser::notDoubleQuotedWords( const QString& searchLine ) -{ - QStringList words = searchLine.split( ' ', QString::SkipEmptyParts ); - combineDoubleQuotedWords( &words, false ); - return words; -} - -void JourneySearchParser::combineDoubleQuotedWords( QStringList* words, bool reinsertQuotedWords ) -{ - int quotedStart = -1, quotedEnd = -1; - for ( int i = 0; i < words->count(); ++i ) { - if ( words->at( i ).startsWith( '\"' ) ) { - quotedStart = i; - } - if ( words->at( i ).endsWith( '\"' ) ) { - quotedEnd = i; - break; - } - } - if ( quotedStart != -1 ) { - if ( quotedEnd == -1 ) { - quotedEnd = words->count() - 1; - } - - // Combine words - QString combinedWord; - for ( int i = quotedEnd; i >= quotedStart; --i ) { - combinedWord = words->takeAt( i ) + ' ' + combinedWord; - } - - if ( reinsertQuotedWords ) { - words->insert( quotedStart, combinedWord.trimmed() ); - } - } -} - -void JourneySearchParser::splitWordList( const QStringList& wordList, int splitWordPos, - QString* leftOfSplitWord, QString* rightOfSplitWord, int excludeWordsFromleft ) -{ - *leftOfSplitWord = ((QStringList)wordList.mid(excludeWordsFromleft, - splitWordPos - excludeWordsFromleft)).join(" "); - *rightOfSplitWord = ((QStringList)wordList.mid(splitWordPos + 1, - wordList.count() - splitWordPos)).join(" "); -} - -void JourneySearchParser::parseDateAndTime( const QString& sDateTime, - QDateTime* dateTime, QDate* alreadyParsedDate ) -{ - QDate date; - QTime time; - bool callParseDate = alreadyParsedDate->isNull(); - - // Parse date and/or time from sDateTime - QStringList timeValues = sDateTime.split( QRegExp( "\\s|," ), QString::SkipEmptyParts ); - if ( timeValues.count() >= 2 ) { - if ( callParseDate && !parseDate(timeValues[0], &date) - && !parseDate(timeValues[1], &date) ) { - date = QDate::currentDate(); - } else { - date = *alreadyParsedDate; - } - - if ( !parseTime(timeValues[1], &time) && !parseTime(timeValues[0], &time) ) { - time = QTime::currentTime(); - } - } else { - if ( !parseTime(sDateTime, &time) ) { - time = QTime::currentTime(); - if ( callParseDate && !parseDate(sDateTime, &date) ) { - date = QDate::currentDate(); - } else { - date = *alreadyParsedDate; - } - } else if ( callParseDate ) { - date = QDate::currentDate(); - } else { - date = *alreadyParsedDate; - } - } - - *dateTime = QDateTime( date, time ); -} - -bool JourneySearchParser::parseDate( const QString& sDate, QDate* date ) -{ - if ( sDate == i18nc( "@info/plain Used as date keyword in the journey search string", "today" ) ) { - *date = QDate::currentDate(); - return true; - } else if ( sDate == i18nc( "@info/plain Used as date keyword in the journey search string", "tomorrow" ) ) { - *date = QDate::currentDate().addDays( 1 ); - return true; - } - - bool ok; - *date = KGlobal::locale()->readDate( sDate, &ok ); - if ( !ok ) { - // Allow date input without year - if ( sDate.count( '-' ) == 1 ) { // like 12-31 - *date = KGlobal::locale()->readDate( - QDate::currentDate().toString( "yy" ) + '-' + sDate, &ok ); - } else if ( sDate.count( '.' ) == 1 ) { // like 31.12 - *date = KGlobal::locale()->readDate( - sDate + '.' + QDate::currentDate().toString( "yy" ), &ok ); - } else if ( sDate.count( '.' ) == 2 && sDate.endsWith( '.' ) ) { // like 31.12. - *date = KGlobal::locale()->readDate( - sDate + QDate::currentDate().toString( "yy" ), &ok ); - } - - if ( !ok ) { - *date = QDate::currentDate(); - } - } - return ok; -} - -bool JourneySearchParser::parseTime( const QString& sTime, QTime* time ) -{ - if ( sTime == i18nc("@info/plain", "now") ) { - *time = QTime::currentTime(); - return true; - } - - bool ok; - *time = KGlobal::locale()->readTime( sTime, &ok ); - if ( !ok ) { - *time = QTime::currentTime(); - } - return ok; -} diff --git a/applet/journeysearchparser.h b/applet/journeysearchparser.h deleted file mode 100644 index bd9b160..0000000 --- a/applet/journeysearchparser.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef JOURNEYSEARCHPARSER_HEADER -#define JOURNEYSEARCHPARSER_HEADER - -template -class QHash; -class QVariant; -class QString; -class QStringList; -class QDate; -class QTime; -class QDateTime; -class KLineEdit; - -/** - * @brief Provides static methods to parse journey search strings. - * - * @todo Could need some improvement, eg. parseJourneySearch() with a QString, not KLineEdit, new - * methods for getting a list of keywords inside a given QString, removing/adding keywords. - **/ -class JourneySearchParser { -public: - /** - * @brief Keywords to be used in a journey search line. - * - * @todo Add all keywords. - **/ - enum Keyword { - KeywordTo, /**< The "to" keyword indicates that journey to the given - * stop should be searched. */ - KeywordFrom, /**< The "from" keyword indicates that journey from the - * given stop should be searched. */ - KeywordTimeAt, /**< The "at" keyword is followed by a time and or date - * string, indicating when the journeys should depart/arrive. */ - KeywordTimeIn /**< The "in" keyword is followed by a relative time - * string (ie. "in 5 minutes"), indicating when the journeys - * should depart/arrive relative to the current time. */ - }; - - static bool parseJourneySearch( KLineEdit *lineEdit, const QString &search, - QString *stop, QDateTime *departure, bool *stopIsTarget, - bool *timeIsDeparture, int *posStart = 0, int *len = 0, - bool correctString = true ); - static QHash keywordValues( const QString &searchLine ); - - /** - * @brief Searches for the stop name in the given @p lineEdit. - * - * Eg. a double quoted string or the words between a to/from keyword (or from the beginning) - * and the first other keyword (or the end of the text). The beginning is put into @p posStart, - * the length of the stop name is put into @p len and the found stop name is put into @p stop. - **/ - static void stopNamePosition( KLineEdit *lineEdit, - int *posStart, int *len, QString *stop = 0 ); - - static void setJourneySearchStopNameCompletion( KLineEdit *lineEdit, - const QString &completion ); - - static bool isInsideQuotedString( const QString &testString, int cursorPos ); - static void doCorrections( KLineEdit *lineEdit, QString *searchLine, - int cursorPos, const QStringList &words, int removedWordsFromLeft ); - - /** @brief Returns a list of words in @p searchLine that aren't double quoted, - * ie. may be keywords. */ - static QStringList notDoubleQuotedWords( const QString &searchLine ); - - /** @brief A list of keywords which may be at the beginning of the journey - * search string, indicating that a given stop name is the target stop. */ - static const QStringList toKeywords(); - - /** @brief A list of keywords which may be at the beginning of the journey - * search string, indicating that a given stop name is the origin stop. */ - static const QStringList fromKeywords(); - - /** @brief A list of keywords, indicating that a given time is the departure - * time of journeys to be found. */ - static const QStringList departureKeywords(); - - /** @brief A list of keywords, indicating that a given time is the arrival - * time of journeys to be found. */ - static const QStringList arrivalKeywords(); - - /** @brief A list of keywords which should be followed by a time and/or date - * string, eg. "at 12:00, 01.12.2010" (with "at" being the keyword). */ - static const QStringList timeKeywordsAt(); - - /** @brief A list of keywords which should be followed by a relative time string, - * eg. "in 5 minutes" (with "in" being the keyword). */ - static const QStringList timeKeywordsIn(); - - /** @brief A list of keywords to be used instead of writing the date string for tomorrow. */ - static const QStringList timeKeywordsTomorrow(); - - static const QString relativeTimeString( const QVariant &value ); - static const QString relativeTimeStringPattern(); - -private: - static void setJourneySearchWordCompletion( KLineEdit *lineEdit, - const QString &match ); - - static bool searchForJourneySearchKeywords( const QString &journeySearch, - const QStringList &timeKeywordsTomorrow, const QStringList &departureKeywords, - const QStringList &arrivalKeywords, QDate *date, QString *stop, - bool *timeIsDeparture, int *len ); - - static void combineDoubleQuotedWords( QStringList *words, bool reinsertQuotedWords = true ); - - /** - * @brief Get the strings on the left and right of the word at @p splitWordPos in @p wordList. - * - * The extracted strings are stored to @p leftOfSplitWord and @p rightOfSplitWord. - **/ - static void splitWordList( const QStringList &wordList, int splitWordPos, - QString *leftOfSplitWord, QString *rightOfSplitWord, - int excludeWordsFromleft = 0 ); - static void parseDateAndTime( const QString &sDateTime, QDateTime *dateTime, - QDate *alreadyParsedDate ); - static bool parseTime( const QString &sTime, QTime *time ); - static bool parseDate( const QString &sDate, QDate *date ); -}; - -#endif // Multiple inclusion guard diff --git a/applet/journeysearchsuggestionwidget.cpp b/applet/journeysearchsuggestionwidget.cpp deleted file mode 100644 index 589c5dd..0000000 --- a/applet/journeysearchsuggestionwidget.cpp +++ /dev/null @@ -1,928 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "journeysearchsuggestionwidget.h" - -// Own includes -#include "journeysearchparser.h" -#include "journeysearchmodel.h" -#include "settings.h" -#include "timetablewidget.h" - -// KDE+Plasma includes -#include -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include - -JourneySearchSuggestionItem::JourneySearchSuggestionItem( - JourneySearchSuggestionWidget *parent, const QModelIndex& modelIndex ) - : QGraphicsWidget(parent), m_textDocument(0), m_parent(parent) -{ - m_initializing = true; - Q_ASSERT_X( modelIndex.isValid(), "JourneySearchSuggestionItem", - "Invalid QModelIndex given in JourneySearchSuggestionItem constructor!" ); - - setFlags( ItemClipsToShape | ItemIsFocusable | ItemIsSelectable ); - updateData( modelIndex ); -} - -JourneySearchSuggestionItem::~JourneySearchSuggestionItem() -{ - delete m_textDocument; -} - -void JourneySearchSuggestionItem::updateTextLayout() -{ - if ( !m_initializing && (!m_textDocument || (m_textDocument->pageSize() != size())) ) { - updateData( index() ); - } -} - -void JourneySearchSuggestionItem::updateData(const QModelIndex& modelIndex) -{ - if ( modelIndex.isValid() ) { - setHtml( modelIndex.data().toString() ); - } else { - kDebug() << "Invalid index given!"; - } -} - -void JourneySearchSuggestionItem::setHtml(const QString& html) -{ - delete m_textDocument; - m_textDocument = TextDocumentHelper::createTextDocument( html, - QSizeF(qMax(qreal(20.0), qreal(parentWidget()->contentsRect().width())), 100.0), - QTextOption(), font() ); - updateGeometry(); -} - -QSizeF JourneySearchSuggestionItem::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const -{ - if ( m_textDocument && which == Qt::MinimumSize ) { - return QSizeF( qMax(qreal(30.0), TextDocumentHelper::textDocumentWidth(m_textDocument)), - qMax((qreal)QFontMetrics(font()).height() + 5.0, m_textDocument->size().height()) ); - } else if ( m_textDocument && which == Qt::MaximumSize ) { - return QSizeF( 999999.0, - qMax((qreal)QFontMetrics(font()).height() + 5.0, m_textDocument->size().height()) ); - } else { - return QGraphicsWidget::sizeHint( which, constraint ); - } -} - -void JourneySearchSuggestionItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget ) -{ - Q_UNUSED( widget ); - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - - if ( option->rect.isEmpty() ) { - kDebug() << "Empty rect given!"; - return; - } - - if ( !m_textDocument ) { - kDebug() << "No text document!"; - return; - } - - if ( option->state.testFlag(QStyle::State_HasFocus) || - option->state.testFlag(QStyle::State_Selected) ) - { - #if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - QColor focusColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::HighlightColor ); - #else - QColor focusColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::ViewFocusColor ); - #endif - if ( option->state.testFlag(QStyle::State_Selected) ) { - if ( option->state.testFlag(QStyle::State_MouseOver) ) { - focusColor.setAlpha( focusColor.alpha() * 0.65f ); - } else { - focusColor.setAlpha( focusColor.alpha() * 0.55f ); - } - } else if ( option->state.testFlag(QStyle::State_MouseOver) ) { - focusColor.setAlpha( focusColor.alpha() * 0.2f ); - } - - QLinearGradient bgGradient( 0, 0, 1, 0 ); - bgGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); - bgGradient.setColorAt( 0, Qt::transparent ); - bgGradient.setColorAt( 0.4, focusColor ); - bgGradient.setColorAt( 0.6, focusColor ); - bgGradient.setColorAt( 1, Qt::transparent ); - - painter->fillRect( option->rect, QBrush(bgGradient) ); - } - - QRectF iconRect( option->rect.left(), option->rect.top() + (option->rect.height() - 16) / 2.0, - 16, 16 ); - QRectF textRect( iconRect.right() + 5.0, option->rect.top(), - option->rect.width() - iconRect.width() - 5.0, option->rect.height() ); - - QModelIndex modelIndex = index(); - if ( modelIndex.isValid() ) { - QPixmap pixmap = modelIndex.data(Qt::DecorationRole).value().pixmap(16); - painter->drawPixmap( iconRect.toRect(), pixmap ); - } - - TextDocumentHelper::Option textOption; - if ( !m_parent->m_settings->drawShadows() ) { - textOption = TextDocumentHelper::DoNotDrawShadowOrHalos; - } else if ( qGray(painter->pen().color().rgb()) < 192 ) { - textOption = TextDocumentHelper::DrawHalos; - } else { - textOption = TextDocumentHelper::DrawShadows; - } - TextDocumentHelper::drawTextDocument( painter, option, m_textDocument, - textRect.toRect(), textOption ); -} - -QModelIndex JourneySearchSuggestionWidget::indexFromItem( JourneySearchSuggestionItem *item ) -{ - if ( !item ) { - kDebug() << "No item given!"; - return QModelIndex(); - } - - int row = m_items.indexOf( item ); - if ( row < 0 ) { - kDebug() << "delete later"; - deleteLater(); - return QModelIndex(); - } else { - return m_model->index( row, 0 ); - } -} - -QModelIndex JourneySearchSuggestionItem::index() -{ - return m_parent->indexFromItem( this ); -} - -void JourneySearchSuggestionItem::resizeEvent(QGraphicsSceneResizeEvent* event) -{ - QGraphicsWidget::resizeEvent(event); - updateTextLayout(); -} - -void JourneySearchSuggestionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) -{ - QGraphicsItem::mouseReleaseEvent(event); - - QModelIndex modelIndex = index(); - if ( modelIndex.isValid() && event->button() == Qt::LeftButton - && (event->lastPos() - event->pos()).manhattanLength() < 5 ) - { - emit suggestionClicked( modelIndex ); - } -} - -void JourneySearchSuggestionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) -{ - QGraphicsItem::mouseDoubleClickEvent(event); - - QModelIndex modelIndex = index(); - if ( modelIndex.isValid() && event->button() == Qt::LeftButton ) { - emit suggestionDoubleClicked( modelIndex ); - } -} - -JourneySearchSuggestionWidget::JourneySearchSuggestionWidget( QGraphicsItem *parent, - Settings *settings, const QPalette &palette ) - : Plasma::ScrollWidget(parent), m_settings(settings), m_lineEdit(0) -{ - QGraphicsWidget *container = new QGraphicsWidget( this ); - QGraphicsLinearLayout *l = new QGraphicsLinearLayout( Qt::Vertical, container ); - l->setSpacing( 1.0 ); - container->setLayout( l ); - setWidget( container ); - - m_journeySearchLastTextLength = 0; - m_enabledSuggestions = AllSuggestions; - - m_model = new QStandardItemModel( this ); - setModel( m_model ); - - setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - setFont( settings->sizedFont() ); - setPalette( palette ); - - // Add recent journey suggestions. - // Doesn't need attached line edit here, because it's normally empty at this time. - // If not, it gets updated once a line edit gets attached - addJourneySearchCompletions(); -} - -QModelIndex JourneySearchSuggestionWidget::currentIndex() const -{ - JourneySearchSuggestionItem *item = qgraphicsitem_cast( focusWidget() ); - return item ? item->index() : QModelIndex(); -} - -void JourneySearchSuggestionWidget::setCurrentIndex(const QModelIndex& currentIndex) -{ - foreach ( JourneySearchSuggestionItem *item, m_items ) { - if ( item->index() == currentIndex ) { - item->setFocus(); - return; - } - } - - kDebug() << "Didn't find an item for the given index" << currentIndex; -} - -void JourneySearchSuggestionWidget::setModel(QStandardItemModel* model) -{ - qDeleteAll( m_items ); - m_items.clear(); - - m_model = model; - - connect( m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(rowsInserted(QModelIndex,int,int)) ); - connect( m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), - this, SLOT(rowsRemoved(QModelIndex,int,int)) ); - connect( m_model, SIGNAL(modelReset()), this, SLOT(modelReset()) ); - connect( m_model, SIGNAL(layoutChanged()), this, SLOT(layoutChanged()) ); - connect( m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), - this, SLOT(dataChanged(QModelIndex,QModelIndex)) ); -} - -void JourneySearchSuggestionWidget::layoutChanged() -{ - kDebug() << "LAYOUT CHANGED"; -// TODO -} - -void JourneySearchSuggestionWidget::modelReset() -{ - qDeleteAll( m_items ); - m_items.clear(); -} - -void JourneySearchSuggestionWidget::rowsInserted(const QModelIndex& parent, int first, int last) -{ - if ( parent.isValid() ) { - kDebug() << "Item with parent" << parent << "Inserted" << first << last; - return; - } - - QGraphicsLinearLayout *l = static_cast( widget()->layout() ); - for ( int row = first; row <= last; ++row ) { - JourneySearchSuggestionItem *item = new JourneySearchSuggestionItem( this, - m_model->index(row, 0, parent) ); - m_items.insert( row, item ); - item->setInitialized(); - - connect( item, SIGNAL(suggestionClicked(QModelIndex)), - this, SLOT(suggestionClicked(QModelIndex)) ); - connect( item, SIGNAL(suggestionDoubleClicked(QModelIndex)), - this, SLOT(suggestionDoubleClicked(QModelIndex)) ); - - l->insertItem( row, item ); - } -} - -void JourneySearchSuggestionWidget::rowsRemoved(const QModelIndex& parent, int first, int last) -{ - if ( parent.isValid() ) { - kDebug() << "Item with parent" << parent << "Removed" << first << last; - return; - } - - if ( last >= m_items.count() ) { - kDebug() << "Cannot remove item, out of bounds:" << first << last; - last = m_items.count() - 1; - } - - for ( int row = last; row >= first; --row ) { - JourneySearchSuggestionItem *item = m_items.takeAt( row ); - delete item; - } -} - -void JourneySearchSuggestionWidget::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) -{ - for ( int row = topLeft.row(); row <= bottomRight.row(); ++row ) { - if ( row < m_model->rowCount() ) { - m_items[ row ]->updateData( m_model->index(row, 0) ); - } - } -} - -void JourneySearchSuggestionWidget::attachLineEdit(Plasma::LineEdit* lineEdit) -{ - m_lineEdit = lineEdit; - connect( lineEdit, SIGNAL(textEdited(QString)), this, SLOT(journeySearchLineEdited(QString)) ); - - if ( !lineEdit->text().isEmpty() ) { - // TODO - clear(); - addJourneySearchCompletions(); - } -} - -void JourneySearchSuggestionWidget::detachLineEdit() -{ - disconnect( m_lineEdit, SIGNAL(textEdited(QString)), this, SLOT(journeySearchLineEdited(QString)) ); - m_lineEdit = 0; -} - -void JourneySearchSuggestionWidget::clear() -{ - m_model->clear(); - - Q_ASSERT ( m_items.isEmpty() ); -} - -void JourneySearchSuggestionWidget::removeGeneralSuggestionItems() -{ - if ( (m_lineEdit && m_lineEdit->text().isEmpty()) || !m_model ) { - return; - } - - // Remove previously added suggestion items - QModelIndexList indices = m_model->match( m_model->index(0, 0), - Qt::UserRole + 5, true, -1, Qt::MatchExactly ); - for ( int i = indices.count() - 1; i >= 0; --i ) { - m_model->removeRow( indices.at(i).row() ); - } -} - -void JourneySearchSuggestionWidget::addJourneySearchCompletions() -{ - // Insert journey search completions to the top of the list - int row = 0; - - // Add recent journey searches - QList favoriteItems; - QList recentItems; - if ( m_enabledSuggestions.testFlag(RecentJourneySearchSuggestion) ) { - foreach( const JourneySearchItem &journeySearchItem, m_settings->currentJourneySearches() ) { - if ( m_lineEdit ) { - // Only add favorite/recent items which name or stop name is contained in the - // current journey search - int posStart, len; - QString stop; - JourneySearchParser::stopNamePosition( m_lineEdit->nativeWidget(), - &posStart, &len, &stop ); - if ( !journeySearchItem.journeySearch().contains(stop) && - !journeySearchItem.name().contains(m_lineEdit->text()) ) - { - continue; - } - } - - QStandardItem *item; - if ( journeySearchItem.isFavorite() ) { - item = new QStandardItem( KIcon("favorites"), - i18nc("@item:inlistbox/rich", "Favorite: %1", - journeySearchItem.nameOrJourneySearch()) ); - favoriteItems << item; - } else { - if ( recentItems.count() == 3 ) { - continue; // Only show the last three recent journey searches - } - item = new QStandardItem( KIcon("emblem-favorite"), - i18nc("@item:inlistbox/rich", "Recent: %1", - journeySearchItem.nameOrJourneySearch()) ); - recentItems << item; - } - item->setData( "recent", Qt::UserRole + 1 ); - item->setData( journeySearchItem.journeySearch(), Qt::UserRole + 2 ); - item->setData( true, Qt::UserRole + 5 ); // Mark as suggestion item to easily remove it again - } - - foreach ( QStandardItem *favoriteItem, favoriteItems ) { - m_model->insertRow( row, favoriteItem ); - ++row; - } - - foreach ( QStandardItem *recentItem, recentItems ) { - m_model->insertRow( row, recentItem ); - ++row; - } - } - - // Add other suggestions - if ( m_enabledSuggestions.testFlag(KeywordSuggestion) ) { - if ( m_lineEdit && !m_lineEdit->text().isEmpty() ) { - QStringList suggestions, suggestionValues; - // Check if there's already an "at" or "in" (time) keyword - QStringList words = JourneySearchParser::notDoubleQuotedWords( m_lineEdit->text() ); - QString timeKeywordIn, timeKeywordAt; - if ( !JourneySearchParser::timeKeywordsIn().isEmpty() ) { - timeKeywordIn = JourneySearchParser::timeKeywordsIn().first(); - } - if ( !JourneySearchParser::timeKeywordsAt().isEmpty() ) { - timeKeywordAt = JourneySearchParser::timeKeywordsAt().first(); - } - bool hasInKeyword = words.contains( timeKeywordIn ); - bool hasAtKeyword = words.contains( timeKeywordAt ); - bool hasTimeKeyword = hasInKeyword || hasAtKeyword; - - QString type; - QString extraRegExp; - if ( hasTimeKeyword ) { - type = "replaceTimeKeyword"; - - QHash keywordValues = - JourneySearchParser::keywordValues( m_lineEdit->text() ); - if ( keywordValues.contains( JourneySearchParser::KeywordTimeAt ) ) { - QDateTime dateTime = keywordValues[ JourneySearchParser::KeywordTimeAt ].toDateTime(); - extraRegExp = "(\\d{2}:\\d{2}|\\d{2}\\.\\d{2}(\\.\\d{2,4}))"; - - // Add "30 minutes later" - suggestions << i18nc( "@item:inlistbox/rich", "%1 minutes later", 30 ); - suggestionValues << QString( "%1 %2" ).arg( timeKeywordAt ) - .arg( KGlobal::locale()->formatTime( dateTime.addSecs(30 * 60).time() ) ); - - // Add "60 minutes later" - suggestions << i18nc( "@item:inlistbox/rich", "%1 minutes later", 60 ); - suggestionValues << QString( "%1 %2" ).arg( timeKeywordAt ) - .arg( KGlobal::locale()->formatTime( dateTime.addSecs(60 * 60).time() ) ); - - // Add "30 minutes earlier" - suggestions << i18nc( "@item:inlistbox/rich", "%1 minutes earlier", 30 ); - suggestionValues << QString( "%1 %2" ).arg( timeKeywordAt ) - .arg( KGlobal::locale()->formatTime( dateTime.addSecs(-30 * 60).time() ) ); - } else if ( keywordValues.contains( JourneySearchParser::KeywordTimeIn ) ) { - int minutes = keywordValues[ JourneySearchParser::KeywordTimeIn ].toInt(); - extraRegExp = JourneySearchParser::relativeTimeString( "\\d{1,}" ); - - // Add "30 minutes later" - suggestions << i18nc( "@item:inlistbox/rich", "%1 minutes later", 30 ); - suggestionValues << QString("%1 %2").arg( timeKeywordIn ) - .arg( JourneySearchParser::relativeTimeString(minutes + 30) ); - - // Add "60 minutes later" - suggestions << i18nc( "@item:inlistbox/rich", "%1 minutes later", 60 ); - suggestionValues << QString("%1 %2").arg( timeKeywordIn ) - .arg( JourneySearchParser::relativeTimeString(minutes + 60) ); - - // Add "30 minutes earlier" - suggestions << i18nc( "@item:inlistbox/rich", "%1 minutes earlier", 30 ); - suggestionValues << QString("%1 %2").arg( timeKeywordIn ) - .arg( JourneySearchParser::relativeTimeString(minutes - 30) ); - } - } else { - type = "additionalKeywordAtEnd"; - - // Use the first keyword of some types for suggestions - if ( !timeKeywordIn.isEmpty() ) { - // Add "in 5 minutes" - QString in5Minutes = QString("%1 %2").arg( timeKeywordIn ) - .arg( JourneySearchParser::relativeTimeString(5) ); - suggestions << in5Minutes; - suggestionValues << in5Minutes; - - // Add "in 15 minutes" - QString in15Minutes = QString("%1 %2").arg( timeKeywordIn ) - .arg( JourneySearchParser::relativeTimeString(15) ); - suggestions << in15Minutes; - suggestionValues << in15Minutes; - - // Add "in 30 minutes" - QString in30Minutes = QString("%1 %2").arg( timeKeywordIn ) - .arg( JourneySearchParser::relativeTimeString(30) ); - suggestions << in30Minutes; - suggestionValues << in30Minutes; - } - if ( !timeKeywordAt.isEmpty() ) { - QString timeKeywordTomorrow; - if ( !JourneySearchParser::timeKeywordsTomorrow().isEmpty() ) { - timeKeywordTomorrow = JourneySearchParser::timeKeywordsTomorrow().first(); - } - - if ( timeKeywordTomorrow.isNull() ) { - // Add "at 6:00" - QString at6 = QString( "%1 %2" ).arg( timeKeywordAt ) - .arg( QTime( 6, 0 ).toString( "hh:mm" ) ); - suggestions << at6; - suggestionValues << at6; - } else { - // Add "tomorrow at 6:00" - QString tomorrowAt6 = QString( "%1 %2 %3" ).arg( timeKeywordTomorrow ) - .arg( timeKeywordAt ).arg( QTime( 6, 0 ).toString( "hh:mm" ) ); - suggestions << tomorrowAt6; - suggestionValues << tomorrowAt6; - } - } - } - - // Add all suggestions - for ( int i = 0; i < suggestions.count(); ++i ) { - QStandardItem *item = new QStandardItem( KIcon("chronometer"), - i18nc("@item:inlistbox/rich", "Suggestion: %1", - suggestions.at(i)) ); - item->setData( type, Qt::UserRole + 1 ); - item->setData( suggestionValues.at(i), Qt::UserRole + 2 ); - if ( !extraRegExp.isNull() ) { - item->setData( extraRegExp, Qt::UserRole + 3 ); - } - item->setData( true, Qt::UserRole + 5 ); // Mark as suggestion item to easily remove it again - m_model->insertRow( row, item ); - ++row; - } - } - } -} - -void JourneySearchSuggestionWidget::addStopSuggestionItems(const QStringList& stopSuggestions) -{ - if ( !m_enabledSuggestions.testFlag(StopNameSuggestion) ) { - return; - } - - foreach( const QString &stop, stopSuggestions ) { - m_model->appendRow( new QStandardItem(KIcon("public-transport-stop"), stop) ); - } -} - -void JourneySearchSuggestionWidget::addAllKeywordAddRemoveItems() -{ - if ( !m_lineEdit ) { - kDebug() << "You need to attach a line edit before calling addAllKeywordAddRemoveItems"; - return; - } - if ( m_lineEdit->text().isEmpty() || !m_model ) { - return; - } - - QStringList words = JourneySearchParser::notDoubleQuotedWords( m_lineEdit->text() ); - QString timeKeywordIn, timeKeywordAt, arrivalKeyword, departureKeyword, - toKeyword, fromKeyword; - - // Use the first keyword of each type for keyword suggestions - if ( !JourneySearchParser::timeKeywordsIn().isEmpty() ) { - timeKeywordIn = JourneySearchParser::timeKeywordsIn().first(); - } - if ( !JourneySearchParser::timeKeywordsAt().isEmpty() ) { - timeKeywordAt = JourneySearchParser::timeKeywordsAt().first(); - } - if ( !JourneySearchParser::arrivalKeywords().isEmpty() ) { - arrivalKeyword = JourneySearchParser::arrivalKeywords().first(); - } - if ( !JourneySearchParser::departureKeywords().isEmpty() ) { - departureKeyword = JourneySearchParser::departureKeywords().first(); - } - if ( !JourneySearchParser::toKeywords().isEmpty() ) { - toKeyword = JourneySearchParser::toKeywords().first(); - } - if ( !JourneySearchParser::fromKeywords().isEmpty() ) { - fromKeyword = JourneySearchParser::fromKeywords().first(); - } - - // Add keyword suggestions, ie. keyword add/remove items - maybeAddKeywordAddRemoveItems( words, - QStringList() << toKeyword << fromKeyword, - "additionalKeywordAtBegin", - QStringList() << i18nc("@info Description for the 'to' keyword", - "Get journeys to the given stop") - << i18nc("@info Description for the 'from' keyword", - "Get journeys from the given stop") ); - maybeAddKeywordAddRemoveItems( words, - QStringList() << arrivalKeyword << departureKeyword, - "additionalKeywordAlmostAtEnd", - QStringList() << i18nc("@info Description for the 'departing' keyword", - "Get journeys departing at the given date/time") - << i18nc("@info Description for the 'arriving' keyword", - "Get journeys arriving at the given date/time") ); - maybeAddKeywordAddRemoveItems( words, - QStringList() << timeKeywordAt << timeKeywordIn, - "additionalKeywordAtEnd", - QStringList() << i18nc("@info Description for the 'at' keyword", - "Specify the departure/arrival time, eg. " - "\"%1 12:00, 20.04.2010\"", timeKeywordAt) - << i18nc("@info Description for the 'in' keyword", - "Specify the departure/arrival time, eg. \"%1 %2\"", - timeKeywordIn, JourneySearchParser::relativeTimeString(5)), - QStringList() << "(\\d{2}:\\d{2}|\\d{2}\\.\\d{2}(\\.\\d{2,4}))" - << JourneySearchParser::relativeTimeString("\\d{1,}") ); -} - -void JourneySearchSuggestionWidget::maybeAddKeywordAddRemoveItems(const QStringList& words, - const QStringList& keywords, const QString& type, const QStringList& descriptions, - const QStringList& extraRegExps) -{ - bool added = false; - QList addItems; - for ( int i = 0; i < keywords.count(); ++i ) { - const QString keyword = keywords.at( i ); - QString description = descriptions.at( i ); - QString extraRegExp; - if ( i < extraRegExps.count() ) { - extraRegExp = extraRegExps.at( i ); - } - - QStandardItem *item = 0; - QColor keywordColor = KColorScheme( QPalette::Active ) - .foreground( KColorScheme::PositiveText ).color(); - - if ( words.contains( keyword, Qt::CaseInsensitive ) ) { - // Keyword found, add a "remove keyword" item - item = new QStandardItem( KIcon("list-remove"), - i18nc("@item:inlistbox/rich", - "Remove Keyword: " - "%1%2", - keyword, description, keywordColor.name()) ); - item->setData( type + "Remove", Qt::UserRole + 1 ); - if ( !extraRegExp.isNull() ) { - item->setData( extraRegExp, Qt::UserRole + 3 ); - } - m_model->appendRow( item ); - added = true; - } else if ( !added ) { - // Keyword not found, add an "add keyword" item if no "remove keyword" - // items will get added - item = new QStandardItem( KIcon("list-add"), - i18nc("@item:inlistbox/rich", - "Add Keyword: " - "%1%2", - keyword, description, keywordColor.name()) ); - item->setData( type, Qt::UserRole + 1 ); - addItems << item; - } - - if ( item ) { - item->setData( keyword, Qt::UserRole + 2 ); // Store the keyword - item->setData( true, Qt::UserRole + 5 ); // Mark as suggestion item to easily remove it again - item->setData( 2, LinesPerRowRole ); // The description is displayed in the second row - } - } - - // Only add "add keyword" items if no "remove keyword" items have been added - // for this type, because only one keyword of each type is allowed - if ( !added ) { - foreach( QStandardItem *item, addItems ) { - m_model->appendRow( item ); - } - } else { - qDeleteAll( addItems ); - } -} - -void JourneySearchSuggestionWidget::journeySearchItemCompleted(const QString& newJourneySearch, - const QModelIndex& modelIndex, int newCursorPos) -{ - if ( !m_lineEdit ) { - kDebug() << "You need to attach a line edit first"; - return; - } - if ( modelIndex.isValid() ) { - m_model->removeRow( modelIndex.row() ); - } else { - kDebug() << "Index isn't valid, can't remove row from model" << newJourneySearch; - } - m_lineEdit->setText( newJourneySearch ); - - if ( newCursorPos != -1 ) { - m_lineEdit->nativeWidget()->setCursorPosition( newCursorPos ); - } -} - -void JourneySearchSuggestionWidget::useStopSuggestion(const QModelIndex& modelIndex) -{ - // Only start search if a stop suggestion or a recent item was activated - if ( !modelIndex.data(Qt::UserRole + 1).isValid() - || modelIndex.data(Qt::UserRole + 1).toString() == QLatin1String("recent") ) - { - suggestionClicked( modelIndex ); - } -} - -void JourneySearchSuggestionWidget::suggestionClicked(const QModelIndex& modelIndex) -{ - if ( !m_lineEdit ) { - kDebug() << "You need to attach a line edit first"; - return; - } - - if ( !modelIndex.isValid() ) { - kDebug() << "Index is invalid!"; - return; - } - - QString type = modelIndex.data( Qt::UserRole + 1 ).toString(); - if ( type == QLatin1String("recent") ) { - // Set recent journey search string - QString newText = modelIndex.data( Qt::UserRole + 2 ).toString(); - m_lineEdit->setText( newText ); - removeGeneralSuggestionItems(); - addJourneySearchCompletions(); - addAllKeywordAddRemoveItems(); - } else if ( type == QLatin1String("additionalKeywordAtEnd") ) { - // Add keyword at the endint newCursorPos - QString newText = m_lineEdit->text() + ' ' + - modelIndex.data( Qt::UserRole + 2 ).toString(); - journeySearchItemCompleted( newText, modelIndex ); - } else if ( type == QLatin1String("additionalKeywordAlmostAtEnd") ) { - // Add keyword after the stop name, if any - int posStart, len; - QString newText, keyword = modelIndex.data( Qt::UserRole + 2 ).toString(); - JourneySearchParser::stopNamePosition( m_lineEdit->nativeWidget(), &posStart, &len ); - if ( posStart != -1 ) { - newText = m_lineEdit->text().insert( posStart + len, ' ' + keyword ); - journeySearchItemCompleted( newText, modelIndex, posStart + len + keyword.length() + 1 ); - } else { - newText = m_lineEdit->text() + ' ' + keyword; - journeySearchItemCompleted( newText, modelIndex ); - } - } else if ( type == QLatin1String("additionalKeywordAtBegin") ) { - // Add keyword to the beginning - QString keyword = modelIndex.data( Qt::UserRole + 2 ).toString(); - QString newText = keyword + ' ' + m_lineEdit->text(); - journeySearchItemCompleted( newText, modelIndex, keyword.length() + 1 ); - } else if ( type == QLatin1String("additionalKeywordAtEndRemove") - || type == QLatin1String("additionalKeywordAlmostAtEndRemove") - || type == QLatin1String("replaceTimeKeyword") ) - { - // Remove first keyword appearance after the stop name, if any - QString keyword = modelIndex.data( Qt::UserRole + 2 ).toString(); - if ( type == QLatin1String("replaceTimeKeyword") ) - keyword = keyword.left( keyword.indexOf( ' ' ) ); - - QString pattern; - if ( modelIndex.data(Qt::UserRole + 3).isValid() ) { - // Use "extra reg exp" to also match values for keywords, eg. "[in] 5 minutes" - QString extraRegExp = modelIndex.data( Qt::UserRole + 3 ).toString(); - pattern = QString( "(?:\"[^\"]*\".*)?(\\s%1\\s%2)" ).arg( keyword ).arg( extraRegExp ); - } else { - pattern = QString( "(?:\"[^\"]*\".*)?(\\s%1)" ).arg( keyword ); - } - - QRegExp regExp( pattern, Qt::CaseInsensitive ); - regExp.setMinimal( true ); - QString newText = m_lineEdit->text().trimmed(); - int pos = regExp.lastIndexIn( newText ); - if ( pos != -1 ) { - // Keyword (and values) found, remove - newText = newText.remove( regExp.pos( 1 ), regExp.cap( 1 ).length() ); - if ( type == QLatin1String("replaceTimeKeyword") ) { // Add new time keyword - newText = newText + ' ' + modelIndex.data( Qt::UserRole + 2 ).toString(); - journeySearchItemCompleted( newText, modelIndex ); - } else { - journeySearchItemCompleted( newText, modelIndex, regExp.pos(1) ); - } - } - } else if ( type == QLatin1String("additionalKeywordAtBeginRemove") ) { - // Remove keyword from the beginning - QString keyword = modelIndex.data( Qt::UserRole + 2 ).toString(); - QString newText = m_lineEdit->text().trimmed().remove( - QRegExp( QString( "^%1\\s" ).arg( keyword ), Qt::CaseInsensitive ) ); - journeySearchItemCompleted( newText, modelIndex ); - } else { - // Insert the clicked stop into the journey search line, - // don't override keywords and other text - int posStart, len; - JourneySearchParser::stopNamePosition( m_lineEdit->nativeWidget(), &posStart, &len ); - - QString quotedStop = "\"" + modelIndex.data().toString() + "\""; - if ( posStart == -1 ) { - // No stop name found - m_lineEdit->setText( quotedStop ); - } else { - m_lineEdit->setText( m_lineEdit->text().replace( posStart, len, quotedStop ) ); - m_lineEdit->nativeWidget()->setCursorPosition( posStart + quotedStop.length() ); - } - - // Update suggestions - removeGeneralSuggestionItems(); - addJourneySearchCompletions(); - addAllKeywordAddRemoveItems(); - } - m_lineEdit->setFocus(); -} - -void JourneySearchSuggestionWidget::suggestionDoubleClicked(const QModelIndex& modelIndex) -{ - if ( !modelIndex.isValid() ) { - kDebug() << "Index is invalid!"; - return; - } - - // Only start search if a stop suggestion or a recent item was double clicked - if ( !modelIndex.data(Qt::UserRole + 1).isValid() - || modelIndex.data(Qt::UserRole + 1).toString() == QLatin1String("recent") ) - { - emit suggestionActivated(); - } -} - -void JourneySearchSuggestionWidget::journeySearchLineEdited(const QString& newText) -{ - QString stop; - QDateTime departure; - bool stopIsTarget, timeIsDeparture; - - removeGeneralSuggestionItems(); - addJourneySearchCompletions(); - addAllKeywordAddRemoveItems(); - - // Only correct the input string if letters were added (eg. not after pressing backspace). - m_lettersAddedToJourneySearchLine = newText.length() > m_journeySearchLastTextLength; - - JourneySearchParser::parseJourneySearch( m_lineEdit->nativeWidget(), - newText, &stop, &departure, &stopIsTarget, &timeIsDeparture, - 0, 0, m_lettersAddedToJourneySearchLine ); - m_journeySearchLastTextLength = m_lineEdit->text().length() - - m_lineEdit->nativeWidget()->selectedText().length(); - -// reconnectJourneySource( stop, departure, stopIsTarget, timeIsDeparture, true ); - emit journeySearchLineChanged( stop, departure, stopIsTarget, timeIsDeparture ); -} - -void JourneySearchSuggestionWidget::updateStopSuggestionItems(const QVariantHash& stopSuggestionData) -{ - // First read the data from stopSuggestionData - QVariantHash stopToStopID; - QHash< QString, int > stopToStopWeight; - QStringList stopSuggestions, weightedStops; - - // Get all stop names, IDs, weights - bool hasAtLeastOneWeight = false; - QVariantList stops = stopSuggestionData["stops"].toList(); - foreach ( const QVariant &stopData, stops ) { - QVariantHash stop = stopData.toHash(); - QString sStopName = stop["StopName"].toString(); - QString sStopID = stop["StopID"].toString(); - int stopWeight = stop["StopWeight"].toInt(); - stopSuggestions << sStopName; - if ( stopWeight <= 0 ) { - stopWeight = 0; - } else { - hasAtLeastOneWeight = true; - } - weightedStops << QString( "%1:%2" ).arg( sStopName ).arg( stopWeight ); - - stopToStopID.insert( sStopName, sStopID ); - stopToStopWeight.insert( sStopName, stopWeight ); - } - - // Set commpletion items - if ( m_lineEdit && m_lettersAddedToJourneySearchLine - && m_lineEdit->nativeWidget()->completionMode() != KGlobalSettings::CompletionNone ) - { - int posStart, len; - QString stopName; - KLineEdit *lineEdit = m_lineEdit->nativeWidget(); - JourneySearchParser::stopNamePosition( m_lineEdit->nativeWidget(), - &posStart, &len, &stopName ); - int selStart = lineEdit->selectionStart(); - if ( selStart == -1 ) { - selStart = lineEdit->cursorPosition(); - } - bool stopNameChanged = selStart > posStart && selStart + lineEdit->selectedText().length() - <= posStart + len; - if ( stopNameChanged ) { - KCompletion *comp = lineEdit->completionObject( false ); - comp->setIgnoreCase( true ); - if ( hasAtLeastOneWeight ) { - comp->setOrder( KCompletion::Weighted ); - comp->setItems( weightedStops ); - } else { - comp->setItems( stopSuggestions ); - } - QString completion = comp->makeCompletion( stopName ); - - if ( completion != stopName ) { - JourneySearchParser::setJourneySearchStopNameCompletion( lineEdit, completion ); - } - } - } - - // Update model - clear(); - removeGeneralSuggestionItems(); // TODO: Does this make sense after clear()?!? - addJourneySearchCompletions(); - addStopSuggestionItems( stopSuggestions ); - addAllKeywordAddRemoveItems(); -} diff --git a/applet/journeysearchsuggestionwidget.h b/applet/journeysearchsuggestionwidget.h deleted file mode 100644 index 8146629..0000000 --- a/applet/journeysearchsuggestionwidget.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file - * @brief This file contains the widget that is used to display journey search suggestions. - * @author Friedrich Pülz */ - -#ifndef JOURNEYSEARCHSUGGESTIONWIDGET_H -#define JOURNEYSEARCHSUGGESTIONWIDGET_H - -// Plasma includes -#include // Base class - -// Qt includes -#include // Base class -#include // Default argument - -class QStandardItemModel; -class QGraphicsSceneMouseEvent; -namespace Plasma { - class LineEdit; -} -class Settings; -class JourneySearchSuggestionItem; - -/** - * @brief Represents the widget used to display journey search suggestions. - * - * Derived from Plasma::ScrollWidget, shows suggestions. Suggestions are automatically added when - * the attached line edit widget is edited. To attach a line edit widget use @ref attachLineEdit. - * Completions are also automatically set on the attached line edit and it's text is updated when - * a suggestion is applied. - * - * By default all available suggestions are shown. To disable suggestions by type use - * @ref setEnabledSuggestions. - * - * @since 0.9.1 - **/ -class JourneySearchSuggestionWidget : public Plasma::ScrollWidget -{ - Q_OBJECT - friend class JourneySearchSuggestionItem; - -public: - /** - * @brief Types of suggestions displayed by this widget. - **/ - enum Suggestion { - NoSuggestions = 0x0000, /**< No suggestions. */ - - StopNameSuggestion = 0x0001, /**< A stop name suggestion. */ - RecentJourneySearchSuggestion = 0x0002, /**< A recent journey search suggestion. */ - KeywordSuggestion = 0x0004, /**< A keyword add/remove suggestion. */ - - AllSuggestions = StopNameSuggestion | RecentJourneySearchSuggestion | - KeywordSuggestion /**< All available suggestion types. */ - }; - Q_DECLARE_FLAGS(Suggestions, Suggestion) - - /** - * @brief Creates a new journey search suggestion widget. - * - * @param parent The parent item. - * - * @param settings A pointer to the settings object of the applet. - * - * @param palette The palette to use. Defaults to QPalette(). - **/ - explicit JourneySearchSuggestionWidget( QGraphicsItem *parent, Settings *settings, - const QPalette &palette = QPalette() ); - - QModelIndex currentIndex() const; - void setCurrentIndex( const QModelIndex ¤tIndex ); - - QStandardItemModel *model() const { return m_model; }; - void setModel( QStandardItemModel *model ); - - /** - * @brief Attaches the given @p lineEdit with this widget. All changes made to the text in - * @p lineEdit are handled to generate suggestions. - * - * @param lineEdit A pointer to the Plasma::LineEdit to attach with. - * - * @see detachLineEdit - **/ - void attachLineEdit( Plasma::LineEdit *lineEdit ); - - /** - * @brief Detaches a previously attached line edit widget. - * - * @see attachLineEdit - **/ - void detachLineEdit(); - - /** @brief Clears the suggestion model. */ - void clear(); - - /** @brief Sets the types of suggestions to show to @p suggestions. */ - void setEnabledSuggestions( Suggestions suggestions = AllSuggestions ) { - m_enabledSuggestions = suggestions; }; - - /** @brief Gets the types of suggestions to show. */ - Suggestions enabledSuggestions() const { return m_enabledSuggestions; }; - -signals: - /** @brief A suggestion has been activated, eg. by a double click. */ - void suggestionActivated(); - - /** - * @brief Emitted after the attached line edit has been edited and it's content has been parsed. - * - * @param stopName The parsed stop name. - * - * @param departure The parsed departure date and time. - * - * @param stopIsTarget Whether or not the parsed stop should be treated as target (true) - * or as origin stop (false). - * - * @param timeIsDeparture Whether or not the parsed time should be treated as departure (true) - * or as arrival time (false). - **/ - void journeySearchLineChanged( const QString &stopName, const QDateTime &departure, - bool stopIsTarget, bool timeIsDeparture ); - -public slots: - /** - * @brief Uses the stop suggestion at the given @p index. - * - * It is handled as if the stop suggestion was clicked. - * Only if the item at the given @p index is a stop suggestion or a recent journey search. - * - * @param index The QModelIndex of the stop suggestion to use. - **/ - void useStopSuggestion( const QModelIndex &index ); - - /** @brief Updates the stop suggestions with stop suggestions in @p stopSuggestionData. - * - * @param stopSuggestionData Data with stop suggestions from the PublicTransport data engine. - **/ - void updateStopSuggestionItems( const QVariantHash &stopSuggestionData ); - -protected slots: - virtual void rowsInserted( const QModelIndex &parent, int first, int last ); - virtual void rowsRemoved( const QModelIndex &parent, int first, int last ); - virtual void modelReset(); - virtual void layoutChanged(); - virtual void dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ); - - /** - * @brief A suggestion item was clicked. - * - * @param index The QModelIndex of the clicked item. - **/ - void suggestionClicked( const QModelIndex &index ); - - /** - * @brief A suggestion item was doubleclicked. - * - * @param index The QModelIndex of the clicked item. - **/ - void suggestionDoubleClicked( const QModelIndex &index ); - - /** - * @brief The journey search line edit has been edited. - * - * @param newText The new text. - **/ - void journeySearchLineEdited( const QString &newText ); - -protected: - QModelIndex indexFromItem( JourneySearchSuggestionItem *item ); - - /** @brief Removes all general suggestion items, ie. no stop suggestion items. */ - void removeGeneralSuggestionItems(); - - /** @brief Add general completions, eg. "in X minutes". */ - void addJourneySearchCompletions(); - - /** @brief Add stop suggestions given in @p stopSuggestions. */ - void addStopSuggestionItems( const QStringList &stopSuggestions ); - - void addAllKeywordAddRemoveItems(); - void maybeAddKeywordAddRemoveItems( const QStringList &words, const QStringList &keywords, - const QString &type, const QStringList &descriptions, - const QStringList &extraRegExps = QStringList() ); - - void journeySearchItemCompleted( const QString &newJourneySearch, - const QModelIndex &index = QModelIndex(), - int newCursorPos = -1 ); - -private: - QStandardItemModel *m_model; - QList m_items; - Settings *m_settings; - Plasma::LineEdit *m_lineEdit; - Suggestions m_enabledSuggestions; - - int m_journeySearchLastTextLength; /**< The last number of unselected characters in the - * journey search input field. */ - bool m_lettersAddedToJourneySearchLine; /**< Whether or not the last edit of the - * journey search line added letters o(r not. Used for auto completion. */ -}; -Q_DECLARE_OPERATORS_FOR_FLAGS(JourneySearchSuggestionWidget::Suggestions) - -/** - * @brief A QGraphicsWidget representing a single suggestion in a JourneySearchSuggestionWidget. - * - * It draws the icon stored in role Qt::DecorationRole and HTML code in Qt::DisplayRole gets - * drawn using a QTextDocument. - */ -class JourneySearchSuggestionItem : public QGraphicsWidget -{ - Q_OBJECT - friend class JourneySearchSuggestionWidget; - -public: - JourneySearchSuggestionItem( JourneySearchSuggestionWidget *parent, - const QModelIndex& index ); - ~JourneySearchSuggestionItem(); - - void updateTextLayout(); - void updateData( const QModelIndex& index ); - void setHtml( const QString &html ); - QModelIndex index(); - - void setInitialized() { m_initializing = false; }; - - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - -signals: - void suggestionClicked( const QModelIndex &index ); - void suggestionDoubleClicked( const QModelIndex &index ); - -protected: - virtual void resizeEvent( QGraphicsSceneResizeEvent* event ); - virtual QSizeF sizeHint( Qt::SizeHint which, const QSizeF& constraint = QSizeF() ) const; - virtual void mouseReleaseEvent( QGraphicsSceneMouseEvent* event ); - virtual void mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ); - -private: - QTextDocument *m_textDocument; - JourneySearchSuggestionWidget *m_parent; - bool m_initializing; -}; - -#endif // JOURNEYSEARCHSUGGESTIONWIDGET_H diff --git a/applet/overlaywidget.cpp b/applet/overlaywidget.cpp deleted file mode 100644 index 75b4a83..0000000 --- a/applet/overlaywidget.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "overlaywidget.h" - -#include "global.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -OverlayWidget::OverlayWidget( QGraphicsWidget* parent, QGraphicsWidget* under ) - : QGraphicsWidget( parent ), opacity( 0.4 ), m_under( 0 ), m_blur( 0 ) -{ - resize( parent->size() ); - setZValue( 10000 ); - m_under = under; - under->setEnabled( false ); - - if ( under && KGlobalSettings::graphicEffectsLevel() != KGlobalSettings::NoEffects ) { - m_blur = new QGraphicsBlurEffect( this ); - under->setGraphicsEffect( m_blur ); - if ( under->geometry().width() * under->geometry().height() <= 250000 ) { - m_blur->setBlurHints( QGraphicsBlurEffect::AnimationHint ); - QPropertyAnimation *blurAnim = new QPropertyAnimation( m_blur, "blurRadius" ); - blurAnim->setStartValue( 0 ); - blurAnim->setEndValue( 5 ); - blurAnim->setDuration( 1000 ); - blurAnim->start( QAbstractAnimation::DeleteWhenStopped ); - } - } else { // widget is too big - m_blur->setBlurHints( QGraphicsBlurEffect::PerformanceHint ); - } -} - -void OverlayWidget::destroy() -{ - if ( m_under->geometry().width() * m_under->geometry().height() <= 250000 ) { - Plasma::Animation *fadeAnim = GlobalApplet::fadeAnimation( this, 0 ); - - QParallelAnimationGroup *parGroup = new QParallelAnimationGroup; - connect( parGroup, SIGNAL(finished()), this, SLOT(overlayAnimationComplete()) ); - if ( fadeAnim ) { - parGroup->addAnimation( fadeAnim ); - } - if ( m_blur ) { - QPropertyAnimation *blurAnim = new QPropertyAnimation( m_blur, "blurRadius" ); - blurAnim->setStartValue( m_blur->blurRadius() ); - blurAnim->setEndValue( 0 ); - parGroup->addAnimation( blurAnim ); - } - parGroup->start( QAbstractAnimation::DeleteWhenStopped ); - - m_under->setEnabled( true ); - } else { // widget is too big - overlayAnimationComplete(); - } -} - -void OverlayWidget::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget ) -{ - Q_UNUSED( option ) - Q_UNUSED( widget ) - - if ( qFuzzyCompare(1, 1 + opacity) ) { - return; - } - - QColor wash = Plasma::Theme::defaultTheme()->color( Plasma::Theme::BackgroundColor ); - wash.setAlphaF( opacity ); - - Plasma::Applet *applet = qobject_cast( parentWidget() ); - QPainterPath backgroundShape; - if ( !applet || applet->backgroundHints() & Plasma::Applet::StandardBackground ) { - // FIXME: a resize here is nasty, but perhaps still better than an eventfilter just for that.. - if ( parentWidget()->contentsRect().size() != size() ) { - resize( parentWidget()->contentsRect().size() ); - } - - backgroundShape = Plasma::PaintUtils::roundedRectangle( contentsRect(), 5 ); - } else { - backgroundShape = shape(); - } - - painter->setRenderHints( QPainter::Antialiasing ); - painter->fillPath( backgroundShape, wash ); -} - -void OverlayWidget::overlayAnimationComplete() -{ - if ( scene() ) { - scene()->removeItem( this ); - } - deleteLater(); - - m_under->setEnabled( true ); - m_under->setGraphicsEffect( 0 ); -} diff --git a/applet/overlaywidget.h b/applet/overlaywidget.h deleted file mode 100644 index 92f2089..0000000 --- a/applet/overlaywidget.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef OVERLAYWIDGET_HEADER -#define OVERLAYWIDGET_HEADER - -/** @file - * @brief This file contains the OverlayWidget class. - * @author Friedrich Pülz */ - -#include // Base class - -class QGraphicsBlurEffect; - -/** - * @brief A widget which gets laid over the whole applet. - * - * Used for the action button view. - * Mostly copied from Plasma::Applet (the AppletOverlayWidget displayed when calling - * Plasma::Applet::setConfigurationRequired(true)). But with a blur effect ;) - **/ -class OverlayWidget : public QGraphicsWidget { - Q_OBJECT - -public: - explicit OverlayWidget( QGraphicsWidget* parent = 0, QGraphicsWidget* under = 0 ); - void destroy(); - -protected: - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - -public slots: - void overlayAnimationComplete(); - -private: - qreal opacity; - QGraphicsWidget* m_under; // TODO Better name... - QGraphicsBlurEffect *m_blur; -}; - -#endif // Multiple inclusion guard diff --git a/applet/plasma-applet-publictransport.desktop b/applet/plasma-applet-publictransport.desktop deleted file mode 100644 index e0b087c..0000000 --- a/applet/plasma-applet-publictransport.desktop +++ /dev/null @@ -1,60 +0,0 @@ -[Desktop Entry] -Name=Public Transport -Name[bs]=Javni Prijevoz -Name[ca]=Transport públic -Name[ca@valencia]=Transport públic -Name[cs]=Veřejná doprava -Name[de]=Öffentlicher Nahverkehr -Name[es]=Transporte público -Name[et]=Ühiskondlik transport -Name[fr]=Transports publics -Name[ga]=Iompar Poiblí -Name[gl]=Transporte público -Name[hu]=Tömegközlekedés -Name[km]=ការ​ដឹកជញ្ជូន​សាធារណៈ -Name[ko]=대중 교통 -Name[mr]=सार्वजनिक वाहतूक -Name[nds]=APNV -Name[nl]=Openbaar vervoer -Name[pl]=Komunikacja publiczna -Name[pt]=Transportes Públicos -Name[pt_BR]=Transportes Públicos -Name[ro]=Transport public -Name[sk]=Verejná doprava -Name[sv]=Kollektivtrafik -Name[ug]=ئاممىۋى قاتناش -Name[uk]=Громадський транспорт -Name[x-test]=xxPublic Transportxx -Comment=Plasma Public Transport shows a departure / arrival board for public transport. It can also show journeys. -Comment[bs]=Plazma Javni Prijevoz prikazuje tablu odlazaka / dolazaka za javni prijevoz. Također može prikazivati putovanja. -Comment[ca]=Transport públic de Plasma mostra un tauler d'arribades / sortides pel transport públic. També pot mostrar els viatges. -Comment[ca@valencia]=Transport públic de Plasma mostra un tauler d'arribades / eixides pel transport públic. També pot mostrar els viatges. -Comment[de]=Öffentlicher Nahverkehr für Plasma zeigt eine Ankunfts- / Abfahrtstafel für den öffentlichen Nahverkehr. Auch Fahrten können angezeigt werden. -Comment[es]=La miniaplicación de transporte público de Plasma muestra un panel de llegadas/salidas para el transporte público. Puede mostrar trayectos también. -Comment[et]=Plasma ühiskondliku transpordi vidin näitab liiklusvahendite saabumis- ja lahkumisaegu, samuti teekondi. -Comment[fr]=L'application « Transports publics » pour Plasma affiche un panneau pour les départs / les arrivées des transports publics. Elle peut également indiquer des trajets. -Comment[gl]=Transporte Público de Plasma mostra as saídas e chegadas dos transportes públicos. Mostra tamén as xornadas. -Comment[km]=ការ​ដឹកជញ្ជូន​សាធារណៈ​បង្ហាញ​ក្ដារ​ចេញដំណើរ/មកដល់​សម្រាប់​ការ​ដឹកជញ្ជូន​សាធារណៈ ។ វា​ក៏​អាច​បង្ហាញ​ការ​ធ្វើ​ដំណើរ​បាន​​ផងដែរ ។ -Comment[ko]=Plasma 대중 교통은 대중 교통의 출발과 도착을 표시합니다. 여행 경로를 표시할 수도 있습니다. -Comment[nl]=Plasma Openbaar vervoer toont een vertrek / aankomstbord voor openbaar vervoer. Het kan ook reizen tonen. -Comment[pl]=Komunikacja publiczna Plazmy pokazuje przyjazdy/odjazdy dla środków komunikacji publicznej. Może także pokazywać wędrówki. -Comment[pt]=Os Transportes Públicos do Plasma mostram uma tabela de partidas / chegadas para os transportes públicos. Também poderá mostrar as respectivas carreiras. -Comment[pt_BR]=Os Transportes Públicos do Plasma mostram os horários de partidas / chegadas dos transportes públicos. Ele também poderá mostrar os trajetos. -Comment[sk]=Plasma Public Transport zobrazuje odchody / príchody pre verejnú dopravu. Tiež môže zobraziť cesty. -Comment[sv]=Plasma kollektivtrafik visar en tavla med avgångar och ankomster för kollektivtrafik. Den kan också visa resor. -Comment[uk]=Плазмоїд громадського транспорту показує таблицю відправлення/прибуття для громадського транспорту. Він також може показувати маршрути. -Comment[x-test]=xxPlasma Public Transport shows a departure / arrival board for public transport. It can also show journeys.xx -Icon=public-transport-stop -Type=Service -X-KDE-ServiceTypes=Plasma/Applet -X-KDE-Library=plasma_applet_publictransport -X-KDE-PluginInfo-Author=Friedrich Pülz -X-KDE-PluginInfo-Email=fpuelz@gmx.de -X-KDE-PluginInfo-Name=publictransport -X-KDE-PluginInfo-Version=0.11 beta -X-KDE-PluginInfo-Website=http://userbase.kde.org/Plasma/Public_Transport -X-KDE-PluginInfo-Category=Online Services -X-KDE-PluginInfo-Depends= -X-KDE-PluginInfo-License=LGPL -X-KDE-PluginInfo-EnabledByDefault=true -X-Plasma-NotificationArea=true diff --git a/applet/popupicon.cpp b/applet/popupicon.cpp deleted file mode 100644 index ac151be..0000000 --- a/applet/popupicon.cpp +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "popupicon.h" - -#include "departuremodel.h" -#include "departurepainter.h" - -#include -#include -#include - -PopupIcon::PopupIcon( DeparturePainter *departurePainter, QObject* parent ) - : QObject(parent), m_model(0), m_departurePainter(departurePainter), - m_transitionAnimation(0), m_fadeAnimation(0), m_fadeBetweenDeparturesInGroupTimer(0) -{ - m_currentDepartureGroupIndexStep = 0.0; - m_currentDepartureIndexStep = 0.0; - m_startGroupIndex = 0; - m_endGroupIndex = 0; - - // Create and connect timer, that calls fadeToNextDepartureInGroup periodically. - // This timer gets started/stopped depending on wether the current group has more than one - // departure or not. - m_fadeBetweenDeparturesInGroupTimer = new QTimer( this ); - m_fadeBetweenDeparturesInGroupTimer->setInterval( - ANIMATION_DEPARTURE_TRANSITION_DURATION + ANIMATION_DEPARTURE_TRANSITION_PAUSE ); - connect( m_fadeBetweenDeparturesInGroupTimer, SIGNAL(timeout()), - this, SLOT(fadeToNextDepartureInGroup()) ); -} - -KIcon PopupIcon::createPopupIcon( const QSize &size ) -{ - KIcon icon; - QPixmap pixmap; - if ( m_model->isEmpty() || m_departureGroups.isEmpty() ) { - // No departures to show, draw the main icon - pixmap = m_departurePainter->createMainIconPixmap( size ); - } else { - // Draw current state of the popup icon (animations) - pixmap = m_departurePainter->createPopupIcon( this, m_model, size ); - } - icon.addPixmap( pixmap ); - return icon; -} - -bool PopupIcon::hasAlarms() const -{ - return m_model->hasAlarms(); -} - -void PopupIcon::createDepartureGroups() -{ - m_departureGroups.clear(); - - // Create departure groups (maximally PopupIconData::POPUP_ICON_DEPARTURE_GROUP_COUNT groups) - QDateTime lastTime; - for ( int row = 0; row < m_model->rowCount(); ++row ) { - DepartureItem *item = dynamic_cast( m_model->item(row) ); - const DepartureInfo *info = item->departureInfo(); - - QDateTime time = info->predictedDeparture(); - if ( m_departureGroups.count() == PopupIcon::POPUP_ICON_DEPARTURE_GROUP_COUNT - && time != lastTime ) - { - // Maximum group count reached and all groups filled - break; - } else if ( time == lastTime ) { - // Add item to the last group - m_departureGroups.last().append( item ); - } else { - // Create a new group - m_departureGroups << (DepartureGroup() << item); - lastTime = time; - } - } - - applyDepartureIndexLimit(); - startFadeTimerIfMultipleDepartures(); -} - -void PopupIcon::applyDepartureIndexLimit() -{ - qreal maxDepartureIndex = currentDepartureGroup().count(); - if ( m_currentDepartureIndexStep > maxDepartureIndex ) { - if ( m_fadeAnimation ) { - stopDepartureFadeAnimation(); - } - m_currentDepartureIndexStep = maxDepartureIndex; - } -} - -void PopupIcon::stopDepartureFadeAnimation() -{ - if ( m_fadeAnimation ) { - m_fadeAnimation->stop(); - fadeAnimationFinished(); - } - - startFadeTimerIfMultipleDepartures(); -} - -void PopupIcon::departuresAboutToBeRemoved( QList< ItemBase* > departures ) -{ - // Update departure groups - DepartureGroupList::iterator it = m_departureGroups.begin(); - int index = 0; - while ( it != m_departureGroups.end() ) { - // Remove all departures in the current group - // that are inside the given list of departures to be removed - DepartureGroup::iterator sub_it = it->begin(); - while ( sub_it != it->end() ) { - if ( departures.contains(*sub_it) ) { -// departureRemoved( departureIndex ); TODO - sub_it = it->erase( sub_it ); - } else { - ++sub_it; - } - } - - // Remove the group if all it's departures have been removed - if ( it->isEmpty() ) { - it = m_departureGroups.erase( it ); - departureGroupRemoved( index ); - } else { - ++it; - } - ++index; - } -} - -int PopupIcon::currentDepartureGroupIndex() const -{ - if ( m_transitionAnimation ) { - if ( qFloor(m_currentDepartureGroupIndexStep) == m_startGroupIndex ) { - // Animation has just started, use start group - return m_startGroupIndex; - } else { - // Animation is running, use end group - return m_endGroupIndex; - } - } else { - return qFloor( m_currentDepartureGroupIndexStep ); - } -} - -DepartureGroup PopupIcon::currentDepartureGroup() const -{ - if ( m_departureGroups.isEmpty() ) { - return DepartureGroup(); - } else { - const int groupIndex = currentDepartureGroupIndex(); - if ( groupIndex < 0 ) { - return m_model->hasAlarms() ? DepartureGroup() << m_model->nextAlarmDeparture() - : DepartureGroup(); - } else { - return m_departureGroups[ qMin(groupIndex, m_departureGroups.count() - 1) ]; - } - } -} - -bool PopupIcon::currentGroupIsAlarmGroup() const -{ - return currentDepartureGroupIndex() < 0; -} - -DepartureItem* PopupIcon::currentDeparture() const -{ - // Get the current departure of the current group - // or the target departure of a running fade animation (which always increasing the index) - return currentDepartureGroup()[ qCeil(m_currentDepartureIndexStep) ]; -} - -void PopupIcon::startFadeTimerIfMultipleDepartures() -{ - if ( currentDepartureGroup().count() > 1 ) { - if ( !m_fadeBetweenDeparturesInGroupTimer->isActive() ) { - // There are more than one departures in the current group - // and the fade animation timer is not running - m_fadeBetweenDeparturesInGroupTimer->start(); - kDebug() << "Start"; - } - } else if ( m_fadeBetweenDeparturesInGroupTimer->isActive() ) { - // There is only one departure in the current group - // and the fade animation timer is running - kDebug() << "Stop"; - m_fadeBetweenDeparturesInGroupTimer->stop(); - } -} - -void PopupIcon::fadeToNextDepartureInGroup() -{ - if ( currentDepartureGroup().count() <= 1 ) { - kDebug() << "Need at least two departures in the current group to fade between"; - stopDepartureFadeAnimation(); - startFadeTimerIfMultipleDepartures(); - return; - } - - // Create animation - if ( !m_fadeAnimation ) { - m_fadeAnimation = new QPropertyAnimation( this, "DepartureIndex", this ); - m_fadeAnimation->setEasingCurve( QEasingCurve(QEasingCurve::OutQuart) ); - m_fadeAnimation->setDuration( ANIMATION_DEPARTURE_TRANSITION_DURATION ); - connect( m_fadeAnimation, SIGNAL(finished()), this, SLOT(fadeAnimationFinished()) ); - } - - // Set start/end values to animate to the next departure. - // If the current departure is the last one of the current group, animate to the - // first departure again (modulo). - m_fadeAnimation->setStartValue( m_currentDepartureIndexStep ); - m_fadeAnimation->setEndValue( (qCeil(m_currentDepartureIndexStep) + 1) ); - m_fadeAnimation->start(); -} - -void PopupIcon::transitionAnimationFinished() -{ - delete m_transitionAnimation; - m_transitionAnimation = 0; - startFadeTimerIfMultipleDepartures(); -} - -void PopupIcon::fadeAnimationFinished() -{ - delete m_fadeAnimation; - m_fadeAnimation = 0; - const DepartureGroup group = currentDepartureGroup(); - if ( !group.isEmpty() ) { - m_currentDepartureIndexStep = qMax( minimalDepartureGroupIndex(), - qCeil(m_currentDepartureIndexStep) % group.count() ); - } -} - -void PopupIcon::animate( int delta ) -{ - Q_ASSERT( delta != 0 ); - const int oldGroupSpan = qAbs( m_endGroupIndex - m_startGroupIndex ); - const int oldStartGroupIndex = m_startGroupIndex; - const int oldEndGroupIndex = m_endGroupIndex; - if ( delta > 0 ) { - // Animate to next departure group - if ( m_endGroupIndex + 1 < m_departureGroups.count() ) { - if ( m_transitionAnimation ) { - // Already animating - if ( m_endGroupIndex < m_startGroupIndex ) { - // Direction was reversed - m_startGroupIndex = m_endGroupIndex; - } - - // Increase index of the departure group where the animation should end - ++m_endGroupIndex; - } else { - m_startGroupIndex = qFloor( m_currentDepartureGroupIndexStep ); - m_endGroupIndex = m_startGroupIndex + 1; - } - } else { - // Max departure group already reached - return; - } - } else { - // Animate to previous departure group - if ( m_endGroupIndex > minimalDepartureGroupIndex() ) { - if ( m_transitionAnimation ) { - // Already animating - if ( m_endGroupIndex > m_startGroupIndex ) { - // Direction was reversed - m_startGroupIndex = m_endGroupIndex; - } - - // Decrease index of the departure group where the animation should end - --m_endGroupIndex; - } else { - m_startGroupIndex = qFloor( m_currentDepartureGroupIndexStep ); - m_endGroupIndex = m_startGroupIndex - 1; - } - } else { - // Min departure group or alarm departure already reached - return; - } - } - - if ( m_transitionAnimation ) { - // Compute new starting index for the running animation - // animationPartDone is a value from 0 (not started) to 1 (old animation already finished) - const qreal animationPartDone = qAbs(m_currentDepartureGroupIndexStep - oldStartGroupIndex) - / oldGroupSpan; - if ( animationPartDone > 0.5 ) { - // Running animation visually almost finished (actually 50%, but the easing curve - // slows the animation down at the end) - // With this check, the possibility gets lowered, that the animation is spanned over - // more than one group - m_startGroupIndex = oldEndGroupIndex; - m_transitionAnimation->stop(); - m_transitionAnimation->setStartValue( m_startGroupIndex ); - } else { - const int newGroupSpan = /*qAbs(*/ m_endGroupIndex - m_startGroupIndex; - const qreal startDepartureIndex = m_startGroupIndex + animationPartDone * newGroupSpan; - m_transitionAnimation->stop(); - m_transitionAnimation->setStartValue( startDepartureIndex ); - } - } else { - // Create animation - m_transitionAnimation = new QPropertyAnimation( this, "DepartureGroupIndex", this ); - m_transitionAnimation->setEasingCurve( QEasingCurve(QEasingCurve::OutQuart) ); - m_transitionAnimation->setDuration( ANIMATION_DEPARTURE_GROUP_TRANSITION_DURATION ); - m_transitionAnimation->setStartValue( m_startGroupIndex ); - connect( m_transitionAnimation, SIGNAL(finished()), - this, SLOT(transitionAnimationFinished()) ); - } - - applyDepartureIndexLimit(); - - m_transitionAnimation->setEndValue( m_endGroupIndex ); - m_transitionAnimation->start(); -} - -void PopupIcon::departureGroupRemoved( int index ) -{ - if ( index <= m_currentDepartureGroupIndexStep ) { - // The currently shown departure group or a group before it has been removed, - // update group / departure index - int minimalGroupIndex = minimalDepartureGroupIndex(); - if ( m_currentDepartureGroupIndexStep > minimalGroupIndex ) { - // Decrement current departure group index if possible, to stay at the same group - if ( m_transitionAnimation ) { - if ( m_startGroupIndex > minimalGroupIndex && m_endGroupIndex > minimalGroupIndex ) { - // Update animation indices to point to the same groups as before the removal - --m_currentDepartureGroupIndexStep; - --m_startGroupIndex; - --m_endGroupIndex; - } else { - // Stop running group transition animation, - // the start or end group has been removed - m_transitionAnimation->stop(); - transitionAnimationFinished(); - } - } else { - setDepartureGroupIndex( m_currentDepartureGroupIndexStep - 1 ); - } - } - - if ( index == m_currentDepartureGroupIndexStep ) { - if ( m_fadeAnimation ) { - // Stop running fade animation between two departures in the removed group - stopDepartureFadeAnimation(); - } - m_currentDepartureIndexStep = 0.0; // New current departure is the first of the new group - } - } -} - -void PopupIcon::animateToAlarm() -{ - if ( !hasAlarms() ) { - return; // No pending alarms - } - - // Create and connect or stop and update animation - if ( m_transitionAnimation ) { - m_transitionAnimation->stop(); - m_transitionAnimation->setStartValue( m_currentDepartureGroupIndexStep ); - } else { - m_transitionAnimation = new QPropertyAnimation( this, "DepartureGroupIndex", this ); - m_transitionAnimation->setStartValue( m_startGroupIndex ); - connect( m_transitionAnimation, SIGNAL(finished()), - this, SLOT(transitionAnimationFinished()) ); - } - - // Set -1 as end value and start the animation. This index has a special meaning, - // it is showing the latest pending alarm. - m_transitionAnimation->setEndValue( -1 ); - m_transitionAnimation->start(); -} diff --git a/applet/popupicon.h b/applet/popupicon.h deleted file mode 100644 index 8924847..0000000 --- a/applet/popupicon.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef POPUPICON_HEADER -#define POPUPICON_HEADER - -/** @file - * @brief This file contains the PopupIcon class. - * @author Friedrich Pülz */ - -#include // Base class - -class DeparturePainter; -class DepartureModel; -class DepartureItem; -class ItemBase; -class KIcon; -class QSize; -class QTimer; -class QPropertyAnimation; - -typedef QList< DepartureItem* > DepartureGroup; -typedef QList< DepartureGroup > DepartureGroupList; - -/** - * @brief Holds data for the popup icon and handles animations. - * - * Use @ref createPopupIcon to create a KIcon with the current state of the popup icon, that can - * be used with eg. Plasma::Applet::setPopupIcon. This class uses the DeparturePainter given in - * the constructor to draw the popup icon. - * - * This class constructs (@ref createDepartureGroups) and stores a departure group list - * (@ref departureGroups), ie. a QList of QLists of DepartureItems. It can animate between these - * departure groups and the departures in the current group, using @ref animateToNextGroup, - * @ref animateToPreviousGroup, @ref animateToAlarm (handled as special group) and - * @ref fadeToNextDepartureInGroup. Animations between groups can go forward and backward, while - * animations between departures in the current group always go forward. - **/ -class PopupIcon : public QObject { - Q_OBJECT - - /** - * @brief The index of the current departure group. - * - * @note If a transition animation between departure groups is running, this property holds a - * non integer value. If you set it manually this may also be a non integer value. This - * shouldn't need to be set manually. It gets used for the transition animation between - * departure groups. - **/ - Q_PROPERTY( qreal DepartureGroupIndex READ departureGroupIndex WRITE setDepartureGroupIndex ) - - /** - * @brief The index of the current departure in the current group. - * - * @note If a fade animation between departures in the current group is running, this property - * holds a non integer value. If you set it manually this may also be a non integer value. - * This shouldn't need to be set manually. It gets used for the fade animation between - * departures in the current group. - **/ - Q_PROPERTY( qreal DepartureIndex READ departureIndex WRITE setDepartureIndex ) - -public: - /** - * @brief Creates a new PopupIcon object, using the given @p departurePainter. - **/ - explicit PopupIcon( DeparturePainter *departurePainter, QObject *parent = 0 ); - - /** @brief The maximum number of departure groups (at the same time) to cycle through in the - * popup icon. */ - static const int POPUP_ICON_DEPARTURE_GROUP_COUNT = 15; - - /** @brief Duration of the animation which does the transition between departure groups. */ - static const uint ANIMATION_DEPARTURE_GROUP_TRANSITION_DURATION = 500; - - /** @brief Duration of the animation which does the transition between departures in one group. */ - static const uint ANIMATION_DEPARTURE_TRANSITION_DURATION = 750; - - /** @brief Pause between animations which do the transition between departures in one group. */ - static const uint ANIMATION_DEPARTURE_TRANSITION_PAUSE = 1500; - - /** @brief Creates the popup icon with information about the next departure / alarm. */ - KIcon createPopupIcon( const QSize &size ); - - /** @brief Start animation to the next departure group, if any. */ - inline void animateToNextGroup() { return animate(1); }; - - /** - * @brief Start animation to the previous departure group, if any. - * - * If the current departure group is the first one and the model has a pending alarm, this - * animates to the alarm. - **/ - inline void animateToPreviousGroup() { return animate(-1); }; - - /** @brief Start animation to the next pending alarm, if any. */ - void animateToAlarm(); - - /** - * @brief The index of the current departure group. - * - * @note If a transition animation between departure groups is running, this returns a non - * integer value. If you call @ref setDepartureGroupIndex manually this may also be a non - * integer value. - **/ - qreal departureGroupIndex() const { return m_currentDepartureGroupIndexStep; }; - - /** - * @brief Sets the index of the current departure group. - * - * @param departureGroupIndex The index of the new departure group. - * - * @note This shouldn't need to be called manually. It gets used for the transition animation - * between departure groups. - **/ - void setDepartureGroupIndex( qreal departureGroupIndex ) { - const int groupIndexBefore = currentDepartureGroupIndex(); - m_currentDepartureGroupIndexStep = departureGroupIndex; - const int groupIndexAfter = currentDepartureGroupIndex(); - - emit currentDepartureGroupIndexChanged( departureGroupIndex ); - if ( groupIndexBefore != groupIndexAfter ) { - emit currentDepartureGroupChanged( groupIndexAfter ); - } - }; - - /** - * @brief The index of the current departure in the current group. - * - * @note If a fade animation between departures in the current group is running, this returns a - * non integer value. If you call @ref setDepartureIndex manually this may also be a non - * integer value. - **/ - qreal departureIndex() const { return m_currentDepartureIndexStep; }; - - /** - * @brief Sets the index of the current departure in the current group. - * - * @param departureIndex The index of the new departure. - * - * @note This shouldn't need to be called manually. It gets used for the fade animation - * between departures in the current group. - **/ - void setDepartureIndex( qreal departureIndex ) { - m_currentDepartureIndexStep = departureIndex; - emit currentDepartureIndexChanged( departureIndex ); - }; - - /** - * @brief Gets the index of the group, where a group transition animation started. - * - * @note If no transition animation between groups is running, the returned value is undefined. - **/ - int startDepartureGroupIndex() const { return m_startGroupIndex; }; - - /** - * @brief Gets the index of the group, where a group transition animation ends. - * - * @note If no transition animation between groups is running, the returned value is undefined. - **/ - int endDepartureGroupIndex() const { return m_endGroupIndex; }; - - /** @brief Gets the minimal departure group index. This can be -1, if there are pending alarms. */ - inline int minimalDepartureGroupIndex() const { return hasAlarms() ? -1 : 0; }; - - /** - * @brief Sets the used @ref DepartureModel to @p model. - * - * This gets used to check if there is a pending alarm. - **/ - void setModel( DepartureModel *model ) { m_model = model; }; - - /** @brief Checks if there is a pending alarm. */ - bool hasAlarms() const; - - bool currentGroupIsAlarmGroup() const; - - /** - * @brief Gets a pointer to the current list of departure groups. - * - * Departures get grouped by their departure time in @ref createDepartureGroups. - */ - const DepartureGroupList *departureGroups() const { return &m_departureGroups; }; - - /** @brief Gets the current departure group, ie. a QList of departures in the current group. */ - DepartureGroup currentDepartureGroup() const; - - /** @brief Gets the current departure in the current group. */ - DepartureItem *currentDeparture() const; - - /** - * @brief Creates a new list for the first departures that are shown in the popup icon. - * - * Each group can contain multiple departures if they depart at the same time. The number of - * departure groups that can be shown in the popup icon is limited to - * @ref POPUP_ICON_DEPARTURE_GROUP_COUNT. - **/ - void createDepartureGroups(); - -signals: - /** @brief Gets emitted if the current departure group has changed. */ - void currentDepartureGroupChanged( int departureGroupIndex ); - - /** @brief Gets emitted if the current departure group index has changed. */ - void currentDepartureGroupIndexChanged( qreal departureGroupIndex ); - - /** @brief Gets emitted if the current departure index in the current group has changed. */ - void currentDepartureIndexChanged( qreal departureIndex ); - -public slots: - /** @brief Starts the fade animation to the next departure in the current group. */ - void fadeToNextDepartureInGroup(); - - /** - * @brief Removes the given @p departures from the current groups. - * - * If a departure group is empty, after removing @p departures, the group gets also removed. - **/ - void departuresAboutToBeRemoved( QList< ItemBase* > departures ); - -protected slots: - /** @brief The transition animation between two departure groups in the has finished. */ - void transitionAnimationFinished(); - - /** @brief The fade animation between two departures in the current group in the has finished. */ - void fadeAnimationFinished(); - -private: - void animate( int delta ); // Animate between departure groups - void startFadeTimerIfMultipleDepartures(); // Starts/stops m_fadeBetweenDeparturesInGroupTimer - void applyDepartureIndexLimit(); // Limit departure index to correct values in the current group - void stopDepartureFadeAnimation(); // Stop running fade animation between two departures - void departureGroupRemoved( int index ); // The group with the given index has been removed - int currentDepartureGroupIndex() const; // The integer index of the current group (not qreal) - - DepartureModel *m_model; - DeparturePainter *m_departurePainter; - int m_startGroupIndex; // Index of the group where a running transition animation started - int m_endGroupIndex; // Index of the group where a running transition animation ends - qreal m_currentDepartureGroupIndexStep; // qreal for transition animations between groups - qreal m_currentDepartureIndexStep; // qreal for fade animations between departures - QPropertyAnimation *m_transitionAnimation; // Animates between departure groups - QPropertyAnimation *m_fadeAnimation; // Animates between departures in the current group - QTimer *m_fadeBetweenDeparturesInGroupTimer; // Periodically calls fadeToNextDepartureInGroup() - DepartureGroupList m_departureGroups; // Groups the first few departures by departure time. -}; - -#endif // Multiple inclusion guard diff --git a/applet/publicTransportAppearanceConfig.ui b/applet/publicTransportAppearanceConfig.ui deleted file mode 100644 index ce66265..0000000 --- a/applet/publicTransportAppearanceConfig.ui +++ /dev/null @@ -1,404 +0,0 @@ - - - publicTransportAppearanceConfig - - - - 0 - 0 - 434 - 328 - - - - - QFormLayout::ExpandingFieldsGrow - - - QFormLayout::DontWrapRows - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - 0 - 0 - - - - &Size: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - linesPerRow - - - - - - - - - Small - - - - - - - Sets the size of the applet contents. Make it big if you want to use the applet fullscreen. - - - 0 - - - 10 - - - 1 - - - 2 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 1 - - - - - - - Big - - - - - - - - - &Lines per Row: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - linesPerRow - - - - - - - - 0 - 0 - - - - The number of lines for each departure / arrival. - - - How many lines should be used for a single departure in the list. - - - 2 - - - 1 - - - 5 - - - lines - - - - - - false - - - - - - - &Font: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - radioUseDefaultFont - - - - - - - Use &default workspace theme font - - - true - - - - - - - Other: - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 16 - 20 - - - - - - - - false - - - - - - - - - S&hadow: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - shadow - - - - - - - - 0 - 0 - - - - Whether or not shadows should be drawn behind text. - - - Enabled - - - true - - - - - - - &Colorized Departure Groups: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - colorize - - - - - - - - 0 - 0 - - - - Whether or not departures should be grouped by direction. - - - If enabled, departures get grouped by direction automatically. A color gets assigned to each group and is used as background color for departures in the group.<nl/>Each group can be hidden easily using the filter menu. - - - Enabled - - - true - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 64 - 10 - - - - - - - - - 75 - true - - - - Departure / Arrival Column - - - - - - - &Information Shown: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - cmbDepartureColumnInfos - - - - - - - false - - - - Show time and remaining time - - - - - Show time only - - - - - Show remaining time only - - - - - - - - Display Time &Bold: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - displayTimeBold - - - - - - - - 0 - 0 - - - - Whether or not departure / arrival times should be bold. - - - Enabled - - - true - - - - - - - - KFontComboBox - KComboBox -
kfontcombobox.h
-
- - KComboBox - QComboBox -
kcombobox.h
-
- - KIntNumInput - QWidget -
knuminput.h
-
-
- - size - radioUseDefaultFont - radioUseOtherFont - font - cmbDepartureColumnInfos - displayTimeBold - - - - - radioUseOtherFont - toggled(bool) - font - setEnabled(bool) - - - 335 - 60 - - - 345 - 89 - - - - -
diff --git a/applet/publicTransportConfig.ui b/applet/publicTransportConfig.ui deleted file mode 100644 index 05494ac..0000000 --- a/applet/publicTransportConfig.ui +++ /dev/null @@ -1,84 +0,0 @@ - - - publicTransportConfig - - - - 0 - 0 - 366 - 275 - - - - - 0 - 0 - - - - Qt::LeftToRight - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 358 - 248 - - - - - QFormLayout::ExpandingFieldsGrow - - - 0 - - - - - - 0 - 0 - - - - - - - - - - - - Stops are shared between all public transport applets. - - - true - - - - - - - - diff --git a/applet/publicTransportConfigAdvanced.ui b/applet/publicTransportConfigAdvanced.ui deleted file mode 100644 index 0377e01..0000000 --- a/applet/publicTransportConfigAdvanced.ui +++ /dev/null @@ -1,184 +0,0 @@ - - - publicTransportConfigAdvanced - - - - 0 - 0 - 449 - 144 - - - - - QFormLayout::ExpandingFieldsGrow - - - - - - 0 - 0 - - - - Default View: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 0 - - - - - - 0 - 0 - - - - Show &departure list - - - true - - - - - - - Show &arrival list - - - - - - - - - - &Maximum Departures: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - maximalNumberOfDepartures - - - - - - - - 0 - 0 - - - - Here you can set how many departures should maximally be shown. It could be less, depending on the service provider. - - - - - - departures - - - - - - 1 - - - 20 - - - - - - - Additional Timetable Data: - - - additionalData - - - - - - - - - - <title>Additional Timetable Data</title> -<para> -Decide when additional timetable data should be requested. Additional timetable data can be eg. route data. - -<list> -<item><interface>Request all data directly</interface> should only be used when needed, because it causes more network and CPU usage. It should be used when filters are enabled that depend on the additional data (departures will not get filtered out until the needed additional data was received). For example the <interface>Via</interface> and <interface>Next Stop</interface> filters depend on route data, which gets provided as additional data by some providers.</item> - -<item><interface>Request when needed</interface> Additional timetable data gets requested when a departure gets unfolded for the first time to show available additional data. This is the default.</item> - -<item><interface>Never Request</interface> No additional data gets requested.</item> -</list> -</para> - - - 1 - - - - Request all data directly (more network usage) - - - - - Request when needed (recommended) - - - - - Never request - - - - - - - - - KComboBox - QComboBox -
kcombobox.h
-
-
- - showDepartures - showArrivals - maximalNumberOfDepartures - additionalData - - - -
diff --git a/applet/publicTransportFilterConfig.ui b/applet/publicTransportFilterConfig.ui deleted file mode 100644 index 9a13818..0000000 --- a/applet/publicTransportFilterConfig.ui +++ /dev/null @@ -1,229 +0,0 @@ - - - publicTransportFilterConfig - - - - 0 - 0 - 470 - 362 - - - - - - - &Filter Configuration: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - filterConfigurations - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - A list of available filter configurations - - - <para>A list of all available filter configurations. Filter configurations can be added/removed/renamed using the buttons on the right of this combobox. </para> -<para>Each filter configuration consists of a name, a list of stops using the filter configuration, a filter action and a list of filters. Each filter contains a list of constraints.</para> - - - - - - - Add a new filter configuration - - - ... - - - - - - - Delete the selected filter configuration - - - ... - - - - - - - Rename the selected filter configuration - - - ... - - - - - - - - - &Used With: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - affectedStops - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - The stops that use the selected filter configuration - - - - - - - Filter &Action: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - filterAction - - - - - - - The action to take on matching departures/arrivals - - - - Show Matching - - - - - Hide Matching - - - - - - - - - 0 - 222 - - - - Filter &Criteria - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 448 - 227 - - - - - QFormLayout::ExpandingFieldsGrow - - - 0 - - - - - - - - - - - - - - - - KComboBox - QComboBox -
kcombobox.h
-
- - CheckCombobox - QComboBox -
checkcombobox.h
-
- - PublicTransport::FilterListWidget - QWidget -
filterwidget.h
- 1 -
-
- - -
diff --git a/applet/publictransport.cpp b/applet/publictransport.cpp deleted file mode 100644 index d32e2f7..0000000 --- a/applet/publictransport.cpp +++ /dev/null @@ -1,2082 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Own includes -#include "publictransport.h" -#include "publictransport_p.h" -#include "overlaywidget.h" -#include "journeysearchparser.h" -#include "journeysearchsuggestionwidget.h" -#include "journeysearchmodel.h" -#include "journeysearchlistview.h" -#include "settingsui.h" -#include "marbleprocess.h" - -// KDE includes -#include -#include -#include -#include -#include -#include -#include // Currently only used to ask if marble should be installed (TODO should be done using Plasma::Applet::showMessage()) - -// Plasma includes -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include // DBus used for marble -#include -#include -#include -#include -#include -#include - -PublicTransportApplet::PublicTransportApplet( QObject *parent, const QVariantList &args ) - : Plasma::PopupApplet( parent, args ), d_ptr(new PublicTransportAppletPrivate(this)) -{ - setBackgroundHints( StandardBackground ); - setAspectRatioMode( Plasma::IgnoreAspectRatio ); - setHasConfigurationInterface( true ); - setPreferredSize( 400, 350 ); -} - -PublicTransportApplet::~PublicTransportApplet() -{ - if ( hasFailedToLaunch() ) { - // Do some cleanup here - } else { - } - - delete d_ptr; -} - -void PublicTransportApplet::init() -{ - Q_D( PublicTransportApplet ); - - // Create, initialize and connect objects - d->init(); - - // Set icon and text of the default "run associated application" action - if ( QAction *runAction = action("run associated application") ) { - runAction->setText( i18nc("@item:inmenu", "&Show in Web-Browser") ); - - KService::Ptr offer = KMimeTypeTrader::self()->preferredService( "text/html" ); - if ( !offer.isNull() ) { - runAction->setIcon( KIcon(offer->icon()) ); - } - } - - // Set popup icon - if ( isIconified() ) { - updatePopupIcon(); - } else { - // Set a popup icon, because otherwise the applet collapses to an icon - // when shown in a desktop - setPopupIcon( "public-transport-stop" ); - } - - connect( Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), - this, SLOT(themeChanged()) ); - emit settingsChanged(); - d->onServiceProviderSettingsChanged(); -} - -void PublicTransportApplet::themeChanged() -{ - Q_D( PublicTransportApplet ); - d->applyTheme(); -} - -void PublicTransportApplet::setSettings( const QString& serviceProviderID, const QString& stopName ) -{ - Q_D( const PublicTransportApplet ); - - // Set stop settings in a copy of the current settings. - // Then write the new settings. - Settings settings = d->settings; - StopSettings stop; - stop.set( ServiceProviderSetting, serviceProviderID ); - stop.setStop( stopName ); - StopSettingsList stopList; - stopList << stop; - settings.setStops( stopList ); - setSettings( settings ); -} - -void PublicTransportApplet::setSettings( const StopSettingsList& stops, - const FilterSettingsList& filters ) -{ - Q_D( const PublicTransportApplet ); - - // Set settings in a copy of the current settings. - // Then write the new settings. - Settings settings = d->settings; - settings.setStops( stops ); - settings.setFilters( filters ); - setSettings( settings ); -} - -void PublicTransportApplet::setupActions() -{ - Q_D( PublicTransportApplet ); - - KAction *actionUpdate = new KAction( KIcon("view-refresh"), - i18nc("@action:inmenu", "&Update Timetable"), this ); - connect( actionUpdate, SIGNAL(triggered()), this, SLOT(updateDataSource()) ); - addAction( "updateTimetable", actionUpdate ); - - KAction *showActionButtons = new KAction( /*KIcon("system-run"),*/ // TODO: better icon - i18nc("@action", "&Quick Actions"), this ); - addAction( "showActionButtons", showActionButtons ); - - KAction *actionCreateAlarmForDeparture = new KAction( - GlobalApplet::makeOverlayIcon( KIcon("task-reminder"), "list-add" ), - d->settings.departureArrivalListType() == DepartureList - ? i18nc("@action:inmenu", "Set &Alarm for This Departure") - : i18nc("@action:inmenu", "Set &Alarm for This Arrival"), this ); - connect( actionCreateAlarmForDeparture, SIGNAL(triggered()), - this, SLOT(createAlarmForDeparture()) ); - addAction( "createAlarmForDeparture", actionCreateAlarmForDeparture ); - - KAction *actionCreateAlarmForDepartureCurrentWeekDay = new KAction( - GlobalApplet::makeOverlayIcon( KIcon("task-reminder"), "list-add" ), - i18nc("@action:inmenu", "Set &Alarm for Current Weekday"), this ); - connect( actionCreateAlarmForDepartureCurrentWeekDay, SIGNAL(triggered()), - this, SLOT(createAlarmForDepartureCurrentWeekDay()) ); - addAction( "createAlarmForDepartureCurrentWeekDay", actionCreateAlarmForDepartureCurrentWeekDay ); - - KAction *actionRemoveAlarmForDeparture = new KAction( - GlobalApplet::makeOverlayIcon( KIcon("task-reminder"), "list-remove" ), - d->settings.departureArrivalListType() == DepartureList - ? i18nc("@action:inmenu", "Remove &Alarm for This Departure") - : i18nc("@action:inmenu", "Remove &Alarm for This Arrival"), this ); - connect( actionRemoveAlarmForDeparture, SIGNAL(triggered()), - this, SLOT(removeAlarmForDeparture()) ); - addAction( "removeAlarmForDeparture", actionRemoveAlarmForDeparture ); - - KAction *actionSearchJourneys = new KAction( KIcon("edit-find"), - i18nc("@action", "Search for &Journeys..."), this ); - addAction( "searchJourneys", actionSearchJourneys ); - - KAction *actionConfigureJourneys = new KAction( KIcon("configure"), - i18nc("@action", "&Configure Journey Searches"), this ); - connect( actionConfigureJourneys, SIGNAL(triggered()), this, SLOT(configureJourneySearches()) ); - addAction( "configureJourneys", actionConfigureJourneys ); - - KActionMenu *actionJourneys = new KActionMenu( KIcon("edit-find"), - i18nc("@action", "&Journeys"), this ); - connect( actionJourneys->menu(), SIGNAL(triggered(QAction*)), - this, SLOT(journeyActionTriggered(QAction*)) ); - addAction( "journeys", actionJourneys ); - - // Fill the journey menu with actions and pass the journey action to the title widget - d->updateJourneyMenu(); - d->titleWidget->setJourneysAction( actionJourneys ); - - int iconExtend = 32; - KAction *actionShowDepartures = new KAction( - GlobalApplet::makeOverlayIcon( KIcon("public-transport-stop"), - QList() << KIcon("go-home") << KIcon("go-next"), - QSize(iconExtend / 2, iconExtend / 2), iconExtend ), - i18nc("@action", "Show &Departures"), this ); - addAction( "showDepartures", actionShowDepartures ); - - KAction *actionShowArrivals = new KAction( - GlobalApplet::makeOverlayIcon( KIcon("public-transport-stop"), - QList() << KIcon("go-next") << KIcon("go-home"), - QSize(iconExtend / 2, iconExtend / 2), iconExtend ), - i18nc("@action", "Show &Arrivals"), this ); - addAction( "showArrivals", actionShowArrivals ); - - KAction *actionBackToDepartures = new KAction( KIcon("go-previous"), - i18nc("@action", "Back to &Departure List"), this ); - addAction( "backToDepartures", actionBackToDepartures ); - - d->filtersGroup = new QActionGroup( this ); - d->filtersGroup->setExclusive( false ); - connect( d->filtersGroup, SIGNAL(triggered(QAction*)), - this, SLOT(switchFilterConfiguration(QAction*)) ); - - d->colorFiltersGroup = new QActionGroup( this ); - d->colorFiltersGroup->setExclusive( false ); - connect( d->colorFiltersGroup, SIGNAL(triggered(QAction*)), - this, SLOT(switchFilterByGroupColor(QAction*)) ); - - KActionMenu *actionFilterConfiguration = new KActionMenu( KIcon("view-filter"), - i18nc("@action", "Filter"), this ); - addAction( "filterConfiguration", actionFilterConfiguration ); - d->titleWidget->setFiltersAction( actionFilterConfiguration ); - - KAction *actionToggleExpanded = new KAction( KIcon( "arrow-down" ), - i18nc( "@action:inmenu", "&Show Additional Information" ), this ); - connect( actionToggleExpanded, SIGNAL(triggered()), this, SLOT(toggleExpanded()) ); - addAction( "toggleExpanded", actionToggleExpanded ); - - KAction *actionUnhighlightStop = new KAction( KIcon("edit-select"), - i18nc("@action:inmenu", "&Unhighlight All Stops"), this ); - connect( actionUnhighlightStop, SIGNAL(triggered()), d->model, SLOT(setHighlightedStop()) ); - addAction( "unhighlightStop", actionUnhighlightStop ); - - // TODO: Combine actionHideColumnTarget and actionShowColumnTarget into one action? - KAction *actionHideColumnTarget = new KAction( KIcon("view-right-close"), - i18nc("@action:inmenu", "Hide &target column"), this ); - connect( actionHideColumnTarget, SIGNAL(triggered()), this, SLOT(hideColumnTarget()) ); - addAction( "hideColumnTarget", actionHideColumnTarget ); - - KAction *actionShowColumnTarget = new KAction( KIcon("view-right-new"), - i18nc("@action:inmenu", "Show &target column"), this ); - connect( actionShowColumnTarget, SIGNAL(triggered()), - this, SLOT(showColumnTarget()) ); - addAction( "showColumnTarget", actionShowColumnTarget ); -} - -QList< QAction* > PublicTransportApplet::contextualActions() -{ - Q_D( const PublicTransportApplet ); - - QAction *switchDepArr = d->settings.departureArrivalListType() == DepartureList - ? action( "showArrivals" ) : action( "showDepartures" ); - - // Add filter action if there is at least one filter or color group - KAction *actionFilter = 0; - if ( !d->settings.filters().isEmpty() || - !d->settings.colorGroups().isEmpty() ) - { - actionFilter = qobject_cast< KAction* >( action("filterConfiguration") ); - } - - // Add "Update Timetable" action - QList< QAction* > actions; - actions << action( "updateTimetable" ); - - // Add a separator - QAction *separator = new QAction( this ); - separator->setSeparator( true ); - actions.append( separator ); - - // Add actions: Switch Departures/Arrivals, Switch Current Stop, - if ( d->currentServiceProviderFeatures.contains("ProvidesArrivals") ) { - actions << switchDepArr; - } - - // When in intermediate departureview add an action to go back to the original stop - // Otherwise add an action to switch the current stop and a journey action if supported - if ( d->isStateActive("intermediateDepartureView") ) { - QAction *goBackAction = action("backToDepartures"); - goBackAction->setText( i18nc("@action:inmenu", "&Back To Original Stop") ); - actions << goBackAction; - } else { - if ( d->settings.stops().count() > 1 ) { - actions << d->createSwitchStopAction( this ); - } - if ( d->currentServiceProviderFeatures.contains("ProvidesJourneys") ) { - actions << action("journeys"); - } - } - - // Add an action to switch filters if filters are available - if ( actionFilter ) { - actions << actionFilter; - } - - separator = new QAction( this ); - separator->setSeparator( true ); - actions.append( separator ); - - return actions; -} - -void PublicTransportApplet::updateDataSource() -{ - Q_D( PublicTransportApplet ); - if ( d->isStateActive("journeyView") ) { - d->reconnectJourneySource(); - } else { - // Disable the update action until the update has finished - disableUpdateAction(); - - // Update all connected data sources - for( int n = 0; n < d->stopIndexToSourceName.count(); ++n ) { - const QString sourceName = d->stripDateAndTimeValues( d->stopIndexToSourceName[n] ); - Plasma::Service *service = dataEngine("publictransport")->serviceForSource( sourceName ); - if ( !service ) { - kWarning() << "No Timetable Service!"; - break; - } - - // Start the update job and increase number of running update requests - ++d->runningUpdateRequests; - KConfigGroup op = service->operationDescription("requestUpdate"); - Plasma::ServiceJob *updateJob = service->startOperationCall( op ); - connect( updateJob, SIGNAL(finished(KJob*)), service, SLOT(deleteLater()) ); - connect( updateJob, SIGNAL(finished(KJob*)), this, SLOT(updateRequestFinished(KJob*)) ); - } - - // Enable the update action again, if no update requests were started - if ( d->runningUpdateRequests == 0 ) { - QAction *updateAction = action("updateTimetable"); - updateAction->setEnabled( true ); - updateAction->setText( i18nc("@action:inmenu", "&Update Timetable") ); - } - } -} - -void PublicTransportApplet::updateRequestFinished( KJob *job ) -{ - Q_UNUSED( job ) - Q_D( PublicTransportApplet ); - - // Decrease number of running update requests, if no more updates are running, - // enable the update action again after one minute - --d->runningUpdateRequests; -} - -void PublicTransportApplet::enableUpdateAction() -{ - Q_D( PublicTransportApplet ); - // Enable the update action again and delete the timer - QAction *updateAction = action("updateTimetable"); - updateAction->setEnabled( true ); - updateAction->setText( i18nc("@action:inmenu", "&Update Timetable") ); - delete d->updateTimer; - d->updateTimer = 0; -} - -void PublicTransportApplet::disableUpdateAction() -{ - QAction *updateAction = action("updateTimetable"); - updateAction->setEnabled( false ); - updateAction->setText( i18nc("@action:inmenu", "&Update Timetable (please wait…)") ); -} - -void PublicTransportApplet::departuresFiltered( const QString& sourceName, - const QList< DepartureInfo > &departures, - const QList< DepartureInfo > &newlyFiltered, - const QList< DepartureInfo > &newlyNotFiltered ) -{ - Q_D( PublicTransportApplet ); - - if ( d->departureInfos.contains(sourceName) ) { - d->departureInfos[ sourceName ] = departures; - } else { - kDebug() << "Source name not found" << sourceName << "in" << d->departureInfos.keys(); - return; - } - - // Remove previously visible and now filtered out departures - if ( !newlyFiltered.isEmpty() ) { - kDebug() << "Remove" << newlyFiltered.count() << "previously unfiltered departures"; - } - foreach( const DepartureInfo &departureInfo, newlyFiltered ) { - int row = d->model->indexFromInfo( departureInfo ).row(); - if ( row == -1 ) { - kDebug() << "Didn't find departure" << departureInfo; - } else { - d->model->removeItem( d->model->itemFromInfo(departureInfo) ); - } - } - - // Append previously filtered out departures - if ( !newlyNotFiltered.isEmpty() ) { - kDebug() << "Add" << newlyNotFiltered.count() << "previously filtered departures"; - } - foreach( const DepartureInfo &departureInfo, newlyNotFiltered ) { - d->model->addItem( departureInfo ); - } - - // Limit item count to the maximal number of departure setting - int delta = d->model->rowCount() - d->settings.maximalNumberOfDepartures(); - if ( delta > 0 ) { - d->model->removeRows( d->settings.maximalNumberOfDepartures(), delta ); - } - - d->popupIcon->createDepartureGroups(); - updatePopupIcon(); - d->createTooltip(); -} - -void PublicTransportApplet::beginJourneyProcessing( const QString &/*sourceName*/ ) -{ - Q_D( PublicTransportApplet ); - - // Clear old journey list - d->journeyInfos.clear(); -} - -void PublicTransportApplet::journeysProcessed( const QString &/*sourceName*/, - const QList< JourneyInfo > &journeys, const QUrl &requestUrl, - const QDateTime &/*lastUpdate*/ ) -{ - Q_D( PublicTransportApplet ); - - // Set associated app url - d->urlJourneys = requestUrl; - setAssociatedApplicationUrlForJourneys(); - - // Append new journeys - kDebug() << journeys.count() << "journeys received from thread"; - d->journeyInfos << journeys; - - // Fill the model with the received journeys - d->fillModelJourney( journeys ); -} - -void PublicTransportApplet::beginDepartureProcessing( const QString& sourceName ) -{ - Q_D( PublicTransportApplet ); - - // Clear old departure / arrival list - QString strippedSourceName = d->stripDateAndTimeValues( sourceName ); - d->departureInfos[ strippedSourceName ].clear(); -} - -void PublicTransportApplet::departuresProcessed( const QString& sourceName, - const QList< DepartureInfo > &departures, const QUrl &requestUrl, - const QDateTime &lastUpdate, const QDateTime &nextAutomaticUpdate, - const QDateTime &minManualUpdateTime, int departuresToGo ) -{ - Q_D( PublicTransportApplet ); - - // Set associated app url - d->urlDeparturesArrivals = requestUrl; - if ( d->isStateActive("departureView") || d->isStateActive("journeySearch") || - d->isStateActive("journeysUnsupportedView") ) - { - setAssociatedApplicationUrlForDepartures(); - } - - // Put departures into the cache - const QString strippedSourceName = d->stripDateAndTimeValues( sourceName ); - d->departureInfos[ strippedSourceName ] << departures; - - // Remove config needed messages - setConfigurationRequired( false ); - - // Update "last update" time - if ( lastUpdate > d->lastSourceUpdate ) { - d->lastSourceUpdate = lastUpdate; - d->nextAutomaticSourceUpdate = nextAutomaticUpdate; - d->minManualSourceUpdateTime = minManualUpdateTime; - } - d->updateInfoText(); - - // Disable the update action until the minimal next (manual) update time - disableUpdateAction(); - if ( d->runningUpdateRequests == 0 && departuresToGo == 0 ) { - const int interval = QDateTime::currentDateTime().msecsTo( minManualUpdateTime ); - if ( interval > 0 ) { - if ( !d->updateTimer ) { - d->updateTimer = new QTimer( this ); - connect( d->updateTimer, SIGNAL(timeout()), this, SLOT(enableUpdateAction()) ); - } - d->updateTimer->start( interval + 5 ); - } else { - enableUpdateAction(); - } - } - - // Fill the model with the received departures - d->fillModel( departures ); - - // Update everything that might have changed when all departure data is there - if ( departuresToGo == 0 ) { - d->updateColorGroupSettings(); - d->popupIcon->createDepartureGroups(); - updatePopupIcon(); - d->createTooltip(); - } - - // Request additional data for new items - if ( d->settings.additionalDataRequestType() == Settings::RequestAdditionalDataDirectly ) { - int itemBegin = 999999999; - int itemEnd = 0; - foreach ( const DepartureInfo departure, departures ) { - if ( !departure.includesAdditionalData() && - !departure.isWaitingForAdditionalData() && - departure.additionalDataError().isEmpty() ) - { - const int index = departure.index(); - itemBegin = qMin( itemBegin, index ); - itemEnd = qMax( itemEnd, index ); - } - } - - if ( itemBegin < 999999999 ) { - Plasma::Service *service = dataEngine("publictransport")->serviceForSource( sourceName ); - if ( !service ) { - kWarning() << "No Timetable Service!"; - return; - } - - KConfigGroup op = service->operationDescription("requestAdditionalDataRange"); - op.writeEntry( "itemnumberbegin", itemBegin ); - op.writeEntry( "itemnumberend", itemEnd ); - Plasma::ServiceJob *additionDataJob = service->startOperationCall( op ); - connect( additionDataJob, SIGNAL(finished(KJob*)), service, SLOT(deleteLater()) ); - } - } -} - -void PublicTransportApplet::handleDataError( const QString &sourceName, - const Plasma::DataEngine::Data &data ) -{ - Q_D( PublicTransportApplet ); - - if ( data["parseMode"].toString() == QLatin1String("journeys") ) { - emit invalidJourneyDataReceived(); - - // Set associated application url - d->urlJourneys = data["requestUrl"].toUrl(); - kDebug() << "Erroneous journey url" << d->urlJourneys; - if ( d->isStateActive("journeyView") ) { - setAssociatedApplicationUrlForJourneys(); - } - } else if ( data["parseMode"].toString() == QLatin1String("departures") ) { - emit invalidDepartureDataReceived(); - - // Set associated application url - d->urlDeparturesArrivals = data["requestUrl"].toUrl(); - kDebug() << "Erroneous departure/arrival url" << d->urlDeparturesArrivals; - if ( d->isStateActive("departureView") || d->isStateActive("journeySearch") || - d->isStateActive("journeysUnsupportedView") ) - { - setAssociatedApplicationUrlForDepartures(); - } - - d->timetable->setNoItemsText( i18nc("@info/plain", - "There was an error:%1", data["errorMessage"].toString()) ); - } else { - kWarning() << "Error" << data["errorMessage"].toString() << "in source" << sourceName; - } -} - -void PublicTransportApplet::processStopSuggestions( const QString &sourceName, - const Plasma::DataEngine::Data &data ) -{ - Q_D( PublicTransportApplet ); - Q_UNUSED( sourceName ) - - // Test what data was requested - bool journeyData = data["parseMode"].toString() == QLatin1String("journeys"); - if ( journeyData || data["parseMode"].toString() == QLatin1String("stopSuggestions") ) { - if ( journeyData ) { - // Journey data was requested, but got stop suggestions, - // ie. invalid journey data because of ambiguous stop name(s) - emit invalidJourneyDataReceived(); - } - - // Update stop suggestions in the journey search widget, if still in journey search state - if ( d->listStopSuggestions ) { - d->listStopSuggestions->updateStopSuggestionItems( data ); - } - } else if ( data["parseMode"].toString() == QLatin1String("departures") ) { - // Departure/arrival data was requested, but got stop suggestions, - // ie. invalid departure data because of an ambiguous stop name - emit invalidDepartureDataReceived(); - d->clearDepartures(); - setConfigurationRequired( true, i18nc("@info", "The stop name is ambiguous.") ); - } -} - -void PublicTransportApplet::dataUpdated( const QString &sourceName, - const Plasma::DataEngine::Data &data ) -{ - Q_D( PublicTransportApplet ); - if ( sourceName.startsWith(QLatin1String("ServiceProvider ")) ) { - const QString providerId = d->settings.currentStop().get(ServiceProviderSetting); - if ( data["id"].toString() != providerId ) { - // Provider data for wrong provider received, maybe after changing the provider - kWarning() << "Data for wrong provider" << data["id"].toString() - << "instaed of" << providerId; - return; - } - - // The currently used provider has changed - d->providerDataUpdated( data ); - return; - } - - if ( data.isEmpty() || (!d->currentSources.contains(sourceName) - && sourceName != d->currentJourneySource) ) { - // Source isn't used anymore - kDebug() << "Data discarded" << sourceName; - return; - } - - if ( data["error"].toBool() ) { - // Error while parsing the data or no connection to server - handleDataError( sourceName, data ); - } else if ( data.contains("stops") ) { - // Stop suggestion list received - processStopSuggestions( sourceName, data ); - } else if ( data.contains("journeys") ) { - // List of journeys received - emit validJourneyDataReceived(); - if ( d->isStateActive("journeyView") ) { - d->departureProcessor->processJourneys( sourceName, data ); - } else { - kDebug() << "Received journey data, but journey list is hidden."; - } - } else if ( data.contains("departures") || data.contains("arrivals") ) { - // Disable the update action, it will get enabled when update requests will be accepted - // again by the engine (see departuresProcessed()) - disableUpdateAction(); - - // List of departures / arrivals received - emit validDepartureDataReceived(); - d->departureProcessor->processDepartures( sourceName, data ); - } -} - -void PublicTransportApplet::appletResized() -{ - Q_D( PublicTransportApplet ); - d->onResized(); -} - -void PublicTransportApplet::popupEvent( bool show ) -{ - action("backToDepartures")->trigger(); - Plasma::PopupApplet::popupEvent( show ); -} - -void PublicTransportApplet::wheelEvent( QGraphicsSceneWheelEvent* event ) -{ - Q_D( PublicTransportApplet ); - - QGraphicsItem::wheelEvent( event ); - - // Compute start and end indices of the departure groups to animate between - if ( event->delta() > 0 ) { - // Wheel rotated forward - d->popupIcon->animateToNextGroup(); - } else if ( event->delta() < 0 ) { - // Wheel rotated backward - d->popupIcon->animateToPreviousGroup(); - } else { - // Wheel not rotated? - return; - } -} - -void PublicTransportApplet::departuresAboutToBeRemoved( const QList& departures ) -{ - Q_D( PublicTransportApplet ); - - d->popupIcon->departuresAboutToBeRemoved( departures ); - updatePopupIcon(); - d->createTooltip(); -} - -void PublicTransportApplet::departuresLeft( const QList< DepartureInfo > &departures ) -{ - Q_UNUSED( departures ); -} - -void PublicTransportApplet::titleToggleAnimationFinished() -{ - Q_D( PublicTransportApplet ); - delete d->titleToggleAnimation; - d->titleToggleAnimation = 0; -} - -void PublicTransportApplet::updatePopupIcon() -{ - Q_D( PublicTransportApplet ); - - if ( isIconified() ) { - int iconSize = qMin( 128, int(size().width()) ); - setPopupIcon( d->popupIcon->createPopupIcon(QSize(iconSize, iconSize)) ); - } -} - -void PublicTransportApplet::updateTooltip() -{ - Q_D( PublicTransportApplet ); - d->createTooltip(); -} - -void PublicTransportApplet::resizeEvent( QGraphicsSceneResizeEvent *event ) -{ - Plasma::Applet::resizeEvent( event ); - - // Update popup icon to new size - updatePopupIcon(); -} - -void PublicTransportApplet::destroyOverlay() -{ - Q_D( PublicTransportApplet ); - - if ( d->overlay ) { - d->overlay->destroy(); - d->overlay = 0; - } -} - -void PublicTransportApplet::showActionButtons() -{ - Q_D( PublicTransportApplet ); - - // Create an overlay widget with gets placed over the applet - // and then filled with buttons - d->overlay = new OverlayWidget( d->graphicsWidget, d->mainGraphicsWidget ); - d->overlay->setGeometry( d->graphicsWidget->contentsRect() ); - d->overlay->setOpacity( 0 ); - - // Create a layout for the buttons and add a spacer at the top - QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical ); - layout->setContentsMargins( 15, 10, 15, 10 ); - QGraphicsWidget *spacer = new QGraphicsWidget( d->overlay ); - spacer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - layout->addItem( spacer ); - - // Add actions depending on active states / service provider features - QList actions; - if ( d->isStateActive("journeyView") ) { - actions << action("backToDepartures"); - } - if ( d->currentServiceProviderFeatures.contains("ProvidesArrivals") ) { - actions << (d->settings.departureArrivalListType() == DepartureList - ? action("showArrivals") : action("showDepartures")); - } - if ( d->currentServiceProviderFeatures.contains("ProvidesJourneys") ) { - actions << action("searchJourneys"); - } - - // Add stop selector if multiple stops are defined - if ( d->settings.stops().count() > 1 ) { - actions << d->createSwitchStopAction( 0, true ); // Parent gets set below - } - - // Create buttons for the actions and create a list of fade animations - foreach ( QAction *action, actions ) { - QGraphicsWidget *widget; - if ( action->menu() ) { - // Create tab bar and make it easy to find it in the overlay using QObject::findChild() - Plasma::TabBar *tabBar = new Plasma::TabBar( d->overlay ); - tabBar->setObjectName( "stopChooser" ); - tabBar->setParent( d->overlay ); - - // Add a tab for each configured stop - QFontMetrics fm( font() ); - foreach ( QAction *menuAction, action->menu()->actions() ) { - if ( !menuAction->isSeparator() && !menuAction->text().isEmpty() ) { - const QString text = fm.elidedText( menuAction->text(), Qt::ElideRight, - qMax(60, qCeil(d->graphicsWidget->size().width() / 3)) ); - tabBar->addTab( menuAction->icon(), text ); - } - } - - // Select the current stop - tabBar->setCurrentIndex( d->settings.currentStopIndex() ); - - widget = tabBar; - } else { - Plasma::PushButton *button = new Plasma::PushButton( d->overlay ); - button->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed ); - button->setAction( action ); - widget = button; - } - - layout->addItem( widget ); - layout->setAlignment( widget, Qt::AlignCenter ); - } - - // Add an OK button - Plasma::PushButton *btnOk = new Plasma::PushButton( d->overlay ); - btnOk->setText( i18nc("@action:button", "Ok") ); - btnOk->setIcon( KIcon("dialog-ok") ); - btnOk->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed ); - connect( btnOk, SIGNAL(clicked()), this, SLOT(acceptActionButtons()) ); - - // Add a cancel button - Plasma::PushButton *btnCancel = new Plasma::PushButton( d->overlay ); - btnCancel->setText( i18nc("@action:button", "Cancel") ); - btnCancel->setIcon( KIcon("dialog-cancel") ); - btnCancel->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed ); - connect( btnCancel, SIGNAL(clicked()), this, SIGNAL(hideActionButtons()) ); - - // Create a separate layout for the cancel button to have some more space - // between the cancel button and the others - QGraphicsLinearLayout *layoutButtons = new QGraphicsLinearLayout( Qt::Horizontal ); - layoutButtons->setContentsMargins( 0, 10, 0, 0 ); - layoutButtons->addItem( btnOk ); - layoutButtons->addItem( btnCancel ); - - // Finish layout with the cancel button and another spacer at the bottom - layout->addItem( layoutButtons ); - layout->setAlignment( layoutButtons, Qt::AlignCenter ); - QGraphicsWidget *spacer2 = new QGraphicsWidget( d->overlay ); - spacer2->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - layout->addItem( spacer2 ); - d->overlay->setLayout( layout ); - - // Create a fade in animation for the whole overlay - Plasma::Animation *animation = GlobalApplet::fadeAnimation( d->overlay, 1 ); - if ( animation ) { - animation->start( QAbstractAnimation::DeleteWhenStopped ); - } -} - -void PublicTransportApplet::acceptActionButtons() -{ - Q_D( PublicTransportApplet ); - Plasma::TabBar *tabBar = d->overlay->findChild< Plasma::TabBar* >( "stopChooser" ); - Q_ASSERT( tabBar ); - setCurrentStopIndex( tabBar->currentIndex() ); - emit hideActionButtons(); -} - -void PublicTransportApplet::setCurrentStopIndex( QAction* stopAction ) -{ - Q_D( const PublicTransportApplet ); - - bool ok; - const int stopIndex = stopAction->data().toInt( &ok ); - if ( !ok ) { - kDebug() << "Couldn't find stop index"; - return; - } - - setCurrentStopIndex( stopIndex ); -} - -void PublicTransportApplet::setCurrentStopIndex( int stopIndex ) -{ - Q_D( const PublicTransportApplet ); - - action("backToDepartures")->trigger(); - - Settings settings = d->settings; - settings.setCurrentStop( stopIndex ); - setSettings( settings ); -} - -void PublicTransportApplet::showDepartures() -{ - Q_D( const PublicTransportApplet ); - - // Change departure arrival list type in a copy of the settings. - // Then write the new settings. - Settings settings = d->settings; - settings.setDepartureArrivalListType( DepartureList ); - setSettings( settings ); -} - -void PublicTransportApplet::showArrivals() -{ - Q_D( const PublicTransportApplet ); - - // Change departure arrival list type in a copy of the settings. - // Then write the new settings. - Settings settings = d->settings; - settings.setDepartureArrivalListType( ArrivalList ); - setSettings( settings ); -} - -void PublicTransportApplet::switchFilterConfiguration( QAction* action ) -{ - Q_D( const PublicTransportApplet ); - - const QString filterConfig = KGlobal::locale()->removeAcceleratorMarker( action->text() ); - - // Change filter configuration of the current stop in a copy of the settings. - // Then write the new settings. - Settings settings = d->settings; - FilterSettingsList filters = settings.filters(); - for ( int i = 0; i < filters.count(); ++i ) { - const FilterSettings filter = filters[i]; - if ( filter.name == filterConfig ) { - // Switch filter configuration for current stop settings - if ( filter.affectedStops.contains(settings.currentStopIndex()) ) { - filters[i].affectedStops.remove( settings.currentStopIndex() ); - } else if ( !filter.affectedStops.contains(settings.currentStopIndex()) ) { - filters[i].affectedStops << settings.currentStopIndex(); - } - } - } - settings.setFilters( filters ); - setSettings( settings ); -} - -void PublicTransportApplet::switchFilterByGroupColor( QAction* action ) -{ - Q_D( const PublicTransportApplet ); - - const QColor color = action->data().value(); - const bool enable = action->isChecked(); - - // Change filter configuration of the current stop in a copy of the settings. - // Then write the new settings. - Settings settings = d->settings; - QList colorGroups = settings.colorGroups(); - colorGroups[ settings.currentStopIndex() ].enableColorGroup( color, enable ); - settings.setColorGroups( colorGroups ); - setSettings( settings ); -} - -void PublicTransportApplet::enableFilterConfiguration( const QString& filterConfiguration, bool enable ) -{ - Q_D( const PublicTransportApplet ); - - const QString filterConfig = filterConfiguration; - Q_ASSERT_X( d->settings.filters().hasName(filterConfig), - "PublicTransportApplet::switchFilterConfiguration", - QString("Filter '%1' not found!").arg(filterConfig).toLatin1().data() ); - - // Change filter configuration of the current stop in a copy of the settings. - // Then write the new settings. - Settings settings = d->settings; - FilterSettingsList filters = settings.filters(); - FilterSettings filter = filters.byName( filterConfig ); - if ( enable && !filter.affectedStops.contains(settings.currentStopIndex()) ) { - filter.affectedStops << settings.currentStopIndex(); - } else if ( !enable && filter.affectedStops.contains(settings.currentStopIndex()) ) { - filter.affectedStops.remove( settings.currentStopIndex() ); - } - filters.set( filter ); - settings.setFilters( filters ); - setSettings( settings ); -} - -void PublicTransportApplet::showDepartureList() -{ - Q_D( PublicTransportApplet ); - - d->fadeOutOldAppearance(); - d->titleWidget->setTitleType( ShowDepartureArrivalListTitle, - d->isStateActive("departureDataValid"), - d->isStateActive("journeyDataValid") ); - d->updateDepartureListIcon(); - d->updateInfoText(); - - d->timetable->update(); - geometryChanged(); - setBusy( d->isStateActive("departureDataWaiting") && d->model->isEmpty() ); - - showMainWidget( d->timetable ); - setAssociatedApplicationUrlForDepartures(); -} - -void PublicTransportApplet::showIntermediateDepartureList() -{ - Q_D( PublicTransportApplet ); - - d->fadeOutOldAppearance(); - d->titleWidget->setTitleType( ShowIntermediateDepartureListTitle, - d->isStateActive("departureDataValid"), - d->isStateActive("journeyDataValid") ); - d->updateDepartureListIcon(); - d->updateInfoText(); - - d->timetable->update(); - geometryChanged(); - setBusy( d->isStateActive("departureDataWaiting") && d->model->isEmpty() ); - - showMainWidget( d->timetable ); - setAssociatedApplicationUrlForDepartures(); -} - -void PublicTransportApplet::showJourneyList() -{ - Q_D( PublicTransportApplet ); - - d->fadeOutOldAppearance(); - d->titleWidget->setTitleType( ShowJourneyListTitle, - d->isStateActive("departureDataValid"), - d->isStateActive("journeyDataValid") ); - - // Create timetable widget for journeys - PublicTransportWidget::Options options = d->settings.drawShadows() - ? PublicTransportWidget::DrawShadowsOrHalos : PublicTransportWidget::NoOption; - JourneyTimetableWidget::Flags flags = - d->currentServiceProviderFeatures.contains("ProvidesMoreJourneys") - ? JourneyTimetableWidget::ShowEarlierAndLaterJourneysItems - : JourneyTimetableWidget::NoFlags; - d->journeyTimetable = new JourneyTimetableWidget( options, flags, - PublicTransportWidget::ExpandSingle, this ); - d->journeyTimetable->setModel( d->modelJourneys ); - d->journeyTimetable->setFont( d->settings.sizedFont() ); - d->journeyTimetable->setSvg( &d->vehiclesSvg ); - connect( d->journeyTimetable, SIGNAL(requestStopAction(StopAction::Type,QString,QString)), - this, SLOT(requestStopAction(StopAction::Type,QString,QString)) ); - connect( d->journeyTimetable, SIGNAL(requestAlarmCreation(QDateTime,QString,VehicleType,QString,QGraphicsWidget*)), - this, SLOT(processAlarmCreationRequest(QDateTime,QString,VehicleType,QString,QGraphicsWidget*)) ); - connect( d->journeyTimetable, SIGNAL(requestAlarmDeletion(QDateTime,QString,VehicleType,QString,QGraphicsWidget*)), - this, SLOT(processAlarmDeletionRequest(QDateTime,QString,VehicleType,QString,QGraphicsWidget*)) ); - connect( d->journeyTimetable, SIGNAL(requestEarlierItems()), this, SLOT(requestEarlierJourneys()) ); - connect( d->journeyTimetable, SIGNAL(requestLaterItems()), this, SLOT(requestLaterJourneys()) ); - d->journeyTimetable->setZoomFactor( d->settings.sizeFactor() ); - d->journeyTimetable->update(); - - d->titleWidget->setTitle( d->journeyTitleText.isEmpty() - ? i18nc("@info", "Journeys") - : d->journeyTitleText ); - geometryChanged(); - setBusy( d->isStateActive("journeyDataWaiting") && d->modelJourneys->isEmpty() ); - - showMainWidget( d->journeyTimetable ); - setAssociatedApplicationUrlForJourneys(); - - // Ensure the applet popup is shown - showPopup(); -} - -void PublicTransportApplet::showJourneySearch() -{ - Q_D( PublicTransportApplet ); - - d->fadeOutOldAppearance(); - d->titleWidget->setTitleType( ShowSearchJourneyLineEdit, - d->isStateActive("departureDataValid"), d->isStateActive("journeyDataValid") ); - - Plasma::LineEdit *journeySearch = - d->titleWidget->castedWidget( TitleWidget::WidgetJourneySearchLine ); - Q_ASSERT( journeySearch ); - - d->listStopSuggestions = new JourneySearchSuggestionWidget( - this, &d->settings, palette() ); - d->listStopSuggestions->attachLineEdit( journeySearch ); - connect( d->listStopSuggestions, SIGNAL(journeySearchLineChanged(QString,QDateTime,bool,bool)), - this, SLOT(journeySearchLineChanged(QString,QDateTime,bool,bool)) ); - - // Hide journey search action, because it switches to the currently active state - action("searchJourneys")->setVisible( false ); - - showMainWidget( d->listStopSuggestions ); - setBusy( false ); - - // Ensure the applet popup is shown - showPopup(); -} - -void PublicTransportApplet::exitJourneySearch() -{ - Q_D( PublicTransportApplet ); - if ( d->listStopSuggestions ) { - d->listStopSuggestions->deleteLater(); - d->listStopSuggestions = 0; - } - - // Show journey search action again - action("searchJourneys")->setVisible( true ); -} - -void PublicTransportApplet::showJourneysUnsupportedView() -{ - Q_D( PublicTransportApplet ); - - d->fadeOutOldAppearance(); - d->titleWidget->setTitleType( ShowSearchJourneyLineEditDisabled, - d->isStateActive("departureDataValid"), d->isStateActive("journeyDataValid") ); - - d->labelJourneysNotSupported = new Plasma::Label; - d->labelJourneysNotSupported->setAlignment( Qt::AlignCenter ); - d->labelJourneysNotSupported->setSizePolicy( QSizePolicy::Expanding, - QSizePolicy::Expanding, QSizePolicy::Label ); - d->labelJourneysNotSupported->setText( i18nc("@info/plain", - "Journey searches aren't supported by the currently used service provider.") ); - d->labelJourneysNotSupported->nativeWidget()->setWordWrap( true ); - connect( d->states["journeysUnsupportedView"], SIGNAL(exited()), - d->labelJourneysNotSupported, SLOT(deleteLater()) ); - - showMainWidget( d->labelJourneysNotSupported ); - setBusy( false ); - - // Ensure the applet popup is shown, - // but only for a few seconds as this just shows an error message - showPopup( 3000 ); -} - -void PublicTransportApplet::journeySearchInputFinished( const QString &text ) -{ - Q_D( PublicTransportApplet ); - - d->clearJourneys(); - - // Add journey search line to the list of recently used journey searches - // and cut recent journey searches if the limit is exceeded - Settings settings = d->settings; - settings.addRecentJourneySearch( text ); - setSettings( settings ); - - d->journeyTitleText.clear(); - QString stop; - QDateTime departure; - bool stopIsTarget, timeIsDeparture; - Plasma::LineEdit *journeySearch = - d->titleWidget->castedWidget( TitleWidget::WidgetJourneySearchLine ); -// Q_ASSERT( journeySearch ); // May be 0 here, TODO use new journey search parser - JourneySearchParser::parseJourneySearch( !journeySearch ? 0 : journeySearch->nativeWidget(), - text, &stop, &departure, &stopIsTarget, &timeIsDeparture ); - - d->reconnectJourneySource( stop, departure, stopIsTarget, timeIsDeparture ); - emit journeySearchFinished(); -} - -void PublicTransportApplet::journeySearchLineChanged( const QString& stopName, - const QDateTime& departure, bool stopIsTarget, bool timeIsDeparture ) -{ - Q_D( PublicTransportApplet ); - d->reconnectJourneySource( stopName, departure, stopIsTarget, timeIsDeparture, true ); -} - -QGraphicsWidget* PublicTransportApplet::graphicsWidget() -{ - Q_D( PublicTransportApplet ); - return d->graphicsWidget; -} - -bool PublicTransportApplet::sceneEventFilter( QGraphicsItem* watched, QEvent* event ) -{ - Q_D( const PublicTransportApplet ); - - if ( watched == d->labelInfo && event->type() == QEvent::GraphicsSceneMousePress ) { - return true; // To make links clickable, otherwise Plasma takes all clicks to move the applet - } - - return Plasma::Applet::sceneEventFilter( watched, event ); -} - -bool PublicTransportApplet::eventFilter( QObject *watched, QEvent *event ) -{ - Q_D( PublicTransportApplet ); - - Plasma::LineEdit *journeySearch = - d->titleWidget->castedWidget( TitleWidget::WidgetJourneySearchLine ); - if ( watched && watched == journeySearch && d->isStateActive("journeySearch") - && d->listStopSuggestions->model() - && d->listStopSuggestions->model()->rowCount() > 0 ) - { - QKeyEvent *keyEvent; - QModelIndex curIndex; - int row; - switch ( event->type() ) { - case QEvent::KeyPress: - keyEvent = dynamic_cast( event ); - curIndex = d->listStopSuggestions->currentIndex(); - - if ( keyEvent->key() == Qt::Key_Up ) { - if ( !curIndex.isValid() ) { - curIndex = d->listStopSuggestions->model()->index( 0, 0 ); - d->listStopSuggestions->setCurrentIndex( curIndex ); - d->listStopSuggestions->useStopSuggestion( curIndex ); - return true; - } else { - row = curIndex.row(); - if ( row >= 1 ) { - d->listStopSuggestions->setCurrentIndex( - d->listStopSuggestions->model()->index(row - 1, - curIndex.column(), curIndex.parent()) ); - d->listStopSuggestions->useStopSuggestion( - d->listStopSuggestions->currentIndex() ); - return true; - } else { - return false; - } - } - } else if ( keyEvent->key() == Qt::Key_Down ) { - if ( !curIndex.isValid() ) { - curIndex = d->listStopSuggestions->model()->index( 0, 0 ); - d->listStopSuggestions->setCurrentIndex( curIndex ); - d->listStopSuggestions->useStopSuggestion( curIndex ); - return true; - } else { - row = curIndex.row(); - if ( row < d->listStopSuggestions->model()->rowCount() - 1 ) { - d->listStopSuggestions->setCurrentIndex( - d->listStopSuggestions->model()->index(row + 1, - curIndex.column(), curIndex.parent()) ); - d->listStopSuggestions->useStopSuggestion( - d->listStopSuggestions->currentIndex() ); - return true; - } else { - return false; - } - } - } - break; - - default: - break; - } - } else if ( watched == d->labelInfo->nativeWidget() && event->type() == QEvent::ToolTip ) { - d->labelInfo->setToolTip( d->infoTooltip() ); - } - - return Plasma::PopupApplet::eventFilter( watched, event ); -} - -void PublicTransportApplet::requestEarlierJourneys() -{ - Q_D( PublicTransportApplet ); - Plasma::Service *service = - dataEngine("publictransport")->serviceForSource( d->currentJourneySource ); - if ( service ) { - KConfigGroup op = service->operationDescription("requestEarlierItems"); - Plasma::ServiceJob *ealierItemsJob = service->startOperationCall( op ); - connect( ealierItemsJob, SIGNAL(finished(KJob*)), service, SLOT(deleteLater()) ); - } -} - -void PublicTransportApplet::requestLaterJourneys() -{ - Q_D( PublicTransportApplet ); - Plasma::Service *service = - dataEngine("publictransport")->serviceForSource( d->currentJourneySource ); - if ( service ) { - KConfigGroup op = service->operationDescription("requestLaterItems"); - Plasma::ServiceJob *laterItemsJob = service->startOperationCall( op ); - connect( laterItemsJob, SIGNAL(finished(KJob*)), service, SLOT(deleteLater()) ); - } -} - -void PublicTransportApplet::createConfigurationInterface( KConfigDialog* parent ) -{ - Q_D( const PublicTransportApplet ); - - // Go back from intermediate departure list (which may be requested by a - // context menu action) before showing the configuration dialog, - // because stop settings may be changed and the intermediate stop - // shouldn't be shown in the configuration dialog. - if ( d->isStateActive("intermediateDepartureView") ) { - showDepartureList(); - } - - SettingsUiManager *settingsUiManager = new SettingsUiManager( d->settings, parent ); - connect( settingsUiManager, SIGNAL(settingsAccepted(Settings)), - this, SLOT(setSettings(Settings)) ); - connect( d->model, SIGNAL(updateAlarms(AlarmSettingsList,QList)), - settingsUiManager, SLOT(removeAlarms(AlarmSettingsList,QList)) ); -} - -void PublicTransportApplet::setSettings( const Settings& settings ) -{ - Q_D( PublicTransportApplet ); - SettingsIO::ChangedFlags changed = - SettingsIO::writeSettings( settings, d->settings, config(), globalConfig() ); - d->onSettingsChanged( settings, changed ); -} - -void PublicTransportApplet::showMainWidget( QGraphicsWidget* mainWidget ) -{ - Q_D( PublicTransportApplet ); - - // Setup new main layout - QGraphicsLinearLayout *layoutMainNew = new QGraphicsLinearLayout( - Qt::Vertical, d->mainGraphicsWidget ); - layoutMainNew->setContentsMargins( 0, 0, 0, 0 ); - layoutMainNew->setSpacing( 0 ); - - // Add widgets to new layout - layoutMainNew->addItem( d->titleWidget ); - layoutMainNew->addItem( mainWidget ); - layoutMainNew->addItem( d->labelInfo ); - layoutMainNew->setAlignment( d->labelInfo, Qt::AlignRight | Qt::AlignVCenter ); - - // Cleanup previously used main widget, but never delete the departure/arrival timetable - d->timetable->setVisible( d->isStateActive("departureView") || - d->isStateActive("intermediateDepartureView") ); - if ( !d->isStateActive("journeyView") && d->journeyTimetable ) { - d->journeyTimetable->deleteLater(); - d->journeyTimetable = 0; - } - if ( !d->isStateActive("journeySearch") && d->listStopSuggestions ) { - d->listStopSuggestions->deleteLater(); - d->listStopSuggestions = 0; - } -} - -void PublicTransportApplet::oldItemAnimationFinished() -{ - Q_D( PublicTransportApplet ); - d->onOldItemAnimationFinished(); -} - -void PublicTransportApplet::expandedStateChanged( PublicTransportGraphicsItem *item, bool expanded ) -{ - Q_D( PublicTransportApplet ); - // When an item gets expanded, try to load additional data using the timetable service - // of the PublicTransport engine, if not already included, requested or failed - DepartureGraphicsItem *departureItem = qobject_cast< DepartureGraphicsItem* >( item ); - if ( expanded && departureItem && departureItem->departureItem() && - !departureItem->departureItem()->includesAdditionalData() && - !departureItem->departureItem()->isWaitingForAdditionalData() && - (d->settings.additionalDataRequestType() == Settings::RequestAdditionalDataWhenNeeded || - d->settings.additionalDataRequestType() == Settings::RequestAdditionalDataDirectly) ) - { - const QString dataSource = departureItem->departureItem()->dataSource(); - Plasma::Service *service = dataEngine("publictransport")->serviceForSource( dataSource ); - if ( service ) { - KConfigGroup op = service->operationDescription("requestAdditionalData"); - op.writeEntry( "itemnumber", departureItem->departureItem()->dataSourceIndex() ); - Plasma::ServiceJob *additionDataJob = service->startOperationCall( op ); - connect( additionDataJob, SIGNAL(finished(KJob*)), service, SLOT(deleteLater()) ); - } - } -} - -void PublicTransportApplet::processJourneyRequest( const QString& stop, bool stopIsTarget ) -{ - Q_D( PublicTransportApplet ); - d->clearJourneys(); - d->reconnectJourneySource( stop, QDateTime(), stopIsTarget, true ); -} - -void PublicTransportApplet::journeySearchListUpdated( const QList &newJourneySearches ) -{ - Q_D( const PublicTransportApplet ); - Settings settings = d->settings; - settings.setCurrentJourneySearches( newJourneySearches ); - setSettings( settings ); -} - -void PublicTransportApplet::journeyActionTriggered( QAction *_action ) -{ - Q_D( PublicTransportApplet ); - - // The configure action has no data, only quick journey search items get the - // journey search string as data - if ( _action->data().isValid() ) { - // The given action is not the configure action - const QString journeySearch = KGlobal::locale()->removeAcceleratorMarker( - _action->data().toString() ); - - if ( d->isStateActive("journeySearch") ) { - // If in journey search view, put the selected journey search into the input line edit - d->titleWidget->setJourneySearch( journeySearch ); - } else { - // Go directly to the journey results view - journeySearchInputFinished( journeySearch ); - } - } -} - -void PublicTransportApplet::departureDataStateChanged() -{ - Q_D( PublicTransportApplet ); - d->onDepartureDataStateChanged(); -} - -void PublicTransportApplet::journeyDataStateChanged() -{ - Q_D( PublicTransportApplet ); - d->onJourneyDataStateChanged(); -} - -void PublicTransportApplet::disconnectJourneySource() -{ - Q_D( PublicTransportApplet ); - d->disconnectJourneySource(); -} - -void PublicTransportApplet::removeIntermediateStopSettings() -{ - Q_D( PublicTransportApplet ); - - // Remove intermediate stop settings - Settings settings = d->settings; - settings.removeIntermediateStops(); - - if ( d->originalStopIndex != -1 ) { - settings.setCurrentStop( qBound(0, d->originalStopIndex, settings.stops().count() - 1) ); - } - d->originalStopIndex = -1; - - setSettings( settings ); -} - -void PublicTransportApplet::hideColumnTarget() -{ - Q_D( const PublicTransportApplet ); - - // Change hide column target setting in a copy of the settings. - // Then write the new settings. - Settings settings = d->settings; - settings.setHideTargetColumn( true ); - setSettings( settings ); -} - -void PublicTransportApplet::showColumnTarget() -{ - Q_D( const PublicTransportApplet ); - - // Change hide column target setting in a copy of the settings. - // Then write the new settings. - Settings settings = d->settings; - settings.setHideTargetColumn( false ); - setSettings( settings ); -} - -void PublicTransportApplet::toggleExpanded() -{ - Q_D( PublicTransportApplet ); - if ( d->journeyTimetable && d->isStateActive("journeyView") ) { - d->journeyTimetable->toggleItemExpanded( d->clickedItemIndex.row() ); - } else { - d->timetable->toggleItemExpanded( d->clickedItemIndex.row() ); - } -} - -QAction* PublicTransportApplet::updatedAction( const QString& actionName ) -{ - Q_D( const PublicTransportApplet ); - - QAction *a = action( actionName ); - if ( !a ) { - kDebug() << "Action not found:" << actionName; - return 0; - } - - if ( actionName == QLatin1String("toggleExpanded") ) { - if ( (d->journeyTimetable && d->isStateActive("journeyView")) - ? d->journeyTimetable->item(d->clickedItemIndex.row())->isExpanded() - : d->timetable->item(d->clickedItemIndex.row())->isExpanded() ) - { - a->setText( i18nc("@action", "Hide Additional &Information") ); - a->setIcon( KIcon("arrow-up") ); - } else { - a->setText( i18nc("@action", "Show Additional &Information") ); - a->setIcon( KIcon("arrow-down") ); - } - } - - return a; -} - -void PublicTransportApplet::departureContextMenuRequested( PublicTransportGraphicsItem* item, const QPointF& pos ) -{ - Q_UNUSED( pos ); - Q_D( PublicTransportApplet ); - - // Save the index of the clicked item - d->clickedItemIndex = item->index(); - - // List actions for the context menu - QAction *infoAction = 0; - QObjectList objectsToBeDeleted; - QList actions; - actions.append( updatedAction("toggleExpanded") ); - if ( d->isStateActive("departureView") || d->isStateActive("intermediateDepartureView") ) { - DepartureItem *item = static_cast( d->model->item(d->clickedItemIndex.row()) ); - - // Add alarm actions (but not for departures in an intermediate departure list) - if ( d->isStateActive("departureView") ) { - if ( item->hasAlarm() ) { - if ( item->alarmStates().testFlag(AlarmIsAutoGenerated) ) { - actions.append( action("removeAlarmForDeparture") ); - } else if ( item->alarmStates().testFlag(AlarmIsRecurring) ) { - // The 'Remove this Alarm' menu entry can only be - // used with autogenerated alarms - if ( item->departureInfo()->matchedAlarms().count() == 1 ) { - infoAction = new QAction( KIcon("task-recurring"), - i18nc("@info/plain", "(has a recurring alarm)"), this ); - } else { - infoAction = new QAction( i18nc("@info/plain", "(has multiple alarms)"), this ); - } - } else { - // The 'Remove this Alarm' menu entry can only be - // used with autogenerated alarms - if ( item->departureInfo()->matchedAlarms().count() == 1 ) { - infoAction = new QAction( KIcon("task-recurring"), - i18nc("@info/plain", "(has a custom alarm)"), this ); - } else { - infoAction = new QAction( i18nc("@info/plain", "(has multiple alarms)"), this ); - } - } - if ( infoAction ) { - infoAction->setDisabled( true ); - actions.append( infoAction ); - objectsToBeDeleted << infoAction; - } - } else { - actions.append( action("createAlarmForDeparture") ); - actions.append( action("createAlarmForDepartureCurrentWeekDay") ); - } - } - - if ( !d->model->info().highlightedStop.isEmpty() ) { - actions.append( action("unhighlightStop") ); - } - - if ( !item->departureInfo()->routeStops().isEmpty() ) { - KMenu *menu = new KMenu(); - objectsToBeDeleted << menu; - QAction *routeStopsAction = new QAction( KIcon("public-transport-intermediate-stops"), - i18nc("@action:inmenu", "Route Stops"), menu ); - routeStopsAction->setMenu( menu ); - const DepartureInfo *info = item->departureInfo(); - int count = info->routeStops().count(); - for ( int index = 0; index < count; ++index ) { - const QString stopName = info->routeStops()[index]; - const QString stopNameShortened = info->routeStopsShortened()[index]; - - // Get time information and route flags - QDateTime time; - int minsFromFirstRouteStop = 0; - if ( index < info->routeTimes().count() && info->routeTimes()[index].isValid() ) { - time = info->routeTimes()[index]; - } - RouteStopFlags routeStopFlags = item->routeStopFlags( index, &minsFromFirstRouteStop ); - - KMenu *stopMenu = new KMenu( menu ); - QString stopText; - if ( minsFromFirstRouteStop == 0 || !time.isValid() ) { - stopText = stopNameShortened; - } else if ( minsFromFirstRouteStop < 0 ) { - stopText = QString("%1 (-%2)").arg(stopNameShortened) - .arg(KGlobal::locale()->prettyFormatDuration(-minsFromFirstRouteStop * 60000)); - } else { - stopText = QString("%1 (%2)").arg(stopNameShortened) - .arg(KGlobal::locale()->prettyFormatDuration(minsFromFirstRouteStop * 60000)); - } - QAction *routeStopsAction = new QAction( GlobalApplet::stopIcon(routeStopFlags), - stopText, stopMenu ); - - if ( !routeStopFlags.testFlag(RouteStopIsHomeStop) ) { - StopAction *showDeparturesAction = - new StopAction( StopAction::ShowDeparturesForStop, stopMenu ); - showDeparturesAction->setStopName( stopName, stopNameShortened ); - connect( showDeparturesAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SLOT(requestStopAction(StopAction::Type,QString,QString)) ); - stopMenu->addAction( showDeparturesAction ); - } - - StopAction *showInMapAction = - new StopAction( StopAction::ShowStopInMap, stopMenu ); - showInMapAction->setStopName( stopName, stopNameShortened ); - connect( showInMapAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SLOT(requestStopAction(StopAction::Type,QString,QString)) ); - stopMenu->addAction( showInMapAction ); - - if ( !routeStopFlags.testFlag(RouteStopIsHomeStop) ) { - StopAction *highlightAction = - new StopAction( StopAction::HighlightStop, stopMenu ); - highlightAction->setStopName( stopName, stopNameShortened ); - if ( d->model->info().highlightedStop == stopName ) { - highlightAction->setText( i18nc("@action:inmenu", "&Unhighlight This Stop") ); - } - connect( highlightAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SLOT(requestStopAction(StopAction::Type,QString,QString)) ); - stopMenu->addAction( highlightAction ); - - StopAction *createFilterAction = - new StopAction( StopAction::CreateFilterForStop, stopMenu ); - createFilterAction->setStopName( stopName, stopNameShortened ); - connect( createFilterAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SLOT(requestStopAction(StopAction::Type,QString,QString)) ); - stopMenu->addAction( createFilterAction ); - } - - StopAction *copyToClipboardAction = - new StopAction( StopAction::CopyStopNameToClipboard, stopMenu ); - copyToClipboardAction->setStopName( stopName, stopNameShortened ); - connect( copyToClipboardAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SLOT(requestStopAction(StopAction::Type,QString,QString)) ); - stopMenu->addAction( copyToClipboardAction ); - - menu->addAction( routeStopsAction ); - routeStopsAction->setMenu( stopMenu ); - } - actions.append( routeStopsAction ); - } - } - - // Show the menu if it's not empty - if ( actions.count() > 0 && view() ) { - QMenu::exec( actions, QCursor::pos() ); - } - - qDeleteAll( objectsToBeDeleted ); -} - -void PublicTransportApplet::requestStopAction( StopAction::Type stopAction, - const QString& stopName, const QString &stopNameShortened ) -{ - Q_D( PublicTransportApplet ); - - // Create and enable new filter - Settings settings = d->settings; - - switch ( stopAction ) { - case StopAction::RequestJourneysToStop: - // stopName is the target stop, the origin is the current home stop - processJourneyRequest( stopName, true ); - break; - case StopAction::RequestJourneysFromStop: - // stopName is the origin stop,, the target is the current home stop - processJourneyRequest( stopName, false ); - break; - case StopAction::CreateFilterForStop: { - QString filterName = i18nc("@info/plain Default name for a new filter via a given stop", - "Via %1", stopNameShortened); - Filter viaFilter; - viaFilter << Constraint( FilterByVia, FilterContains, stopName ); - FilterSettings filter; - filter.filters << viaFilter; - filter.name = filterName; - filter.affectedStops << settings.currentStopIndex(); - - settings.appendFilter( filter ); - setSettings( settings ); - break; - } case StopAction::ShowStopInMap: { - // Start marble and center the map on the current stop - const Stop stop = settings.currentStop().stop( 0 ); - if ( stop.hasValidCoordinates ) { - showStopInMarble( stop.name, true, stop.longitude, stop.latitude ); - } else { - const QString providerId = settings.currentStop().get( ServiceProviderSetting ); - StopDataConnection *connection = new StopDataConnection( - dataEngine("publictransport"), providerId, stopName, this ); - connect( connection, SIGNAL(stopDataReceived(QString,bool,qreal,qreal)), - this, SLOT(showStopInMarble(QString,bool,qreal,qreal)) ); - } - break; - } case StopAction::ShowDeparturesForStop: { - // Remove intermediate stop settings - settings.removeIntermediateStops(); - - if ( d->originalStopIndex != -1 ) { - kDebug() << "Set current stop index to" << d->originalStopIndex; - settings.setCurrentStop( qBound(0, d->originalStopIndex, - settings.stops().count() - 1) ); - } - - // Save original stop index from where sub requests were made - // (using the context menu). Only if the departure list wasn't requested - // already from a sub departure list. - d->originalStopIndex = settings.currentStopIndex(); - - // Search for a stop setting with the given stop name in it. - // Create an intermediate stop item if there is no such stop setting - // in the configuration (automatically deleted). - int stopIndex = settings.stops().findStopSettings( stopName ); - if ( stopIndex == -1 ) { - StopSettings stop( settings.currentStop() ); - stop.setStop( stopName ); - stop.set( UserSetting + 100, "-- Intermediate Stop --" ); - settings.appendStop( stop ); - stopIndex = settings.stops().count() - 1; - } - settings.setCurrentStop( stopIndex ); - setSettings( settings ); - - emit intermediateDepartureListRequested( stopName ); - break; - } case StopAction::HighlightStop: { - d->model->setHighlightedStop( - d->model->highlightedStop().compare(stopName, Qt::CaseInsensitive) == 0 - ? QString() : stopName ); - break; - } case StopAction::CopyStopNameToClipboard: { - QApplication::clipboard()->setText( stopNameShortened ); - break; - } - } -} - -void PublicTransportApplet::marbleFinished( int /*exitCode*/ ) -{ - Q_D( PublicTransportApplet ); - d->marble = 0; -} - -void PublicTransportApplet::showStopInMarble( const QString &stopName, bool coordinatesAreValid, - qreal longitude, qreal latitude ) -{ - Q_D( PublicTransportApplet ); - if ( !coordinatesAreValid ) { - kWarning() << "No valid coordinates available for stop" << stopName; - return; - } - - if ( d->marble ) { - // Marble is already running - d->marble->centerOnStop( stopName, longitude, latitude ); - } else { - d->marble = new MarbleProcess( stopName, longitude, latitude, this ); - connect( d->marble, SIGNAL(marbleError(QString)), this, SLOT(marbleError(QString)) ); - connect( d->marble, SIGNAL(finished(int)), this, SLOT(marbleFinished(int)) ); - d->marble->start(); - } -} - -void PublicTransportApplet::marbleError( const QString &errorMessage ) -{ - showMessage( KIcon("marble"), errorMessage, Plasma::ButtonOk ); -} - -void PublicTransportApplet::removeAlarmForDeparture( int row ) -{ - Q_D( const PublicTransportApplet ); - - DepartureItem *item = static_cast( d->model->item(row) ); - Q_ASSERT_X( item->alarmStates().testFlag(AlarmIsAutoGenerated), - "PublicTransportApplet::removeAlarmForDeparture", - "Only auto generated alarms can be removed automatically" ); - - // Find a matching autogenerated alarm - int matchingAlarmSettings = -1; - for ( int i = 0; i < d->settings.alarms().count(); ++i ) { - const AlarmSettings alarm = d->settings.alarm( i ); - if ( alarm.autoGenerated && alarm.enabled && - alarm.filter.match(*item->departureInfo()) ) - { - matchingAlarmSettings = i; - break; - } - } - if ( matchingAlarmSettings == -1 ) { - kDebug() << "Couldn't find a matching autogenerated alarm"; - return; - } - - // Remove the found alarm - item->removeAlarm(); - AlarmSettingsList newAlarmSettings = d->settings.alarms(); - newAlarmSettings.removeAt( matchingAlarmSettings ); - removeAlarms( newAlarmSettings, QList() << matchingAlarmSettings ); - - if ( d->clickedItemIndex.isValid() ) { - updatePopupIcon(); - } -} - -void PublicTransportApplet::removeAlarmForDeparture() -{ - Q_D( const PublicTransportApplet ); - removeAlarmForDeparture( d->clickedItemIndex.row() ); -} - -void PublicTransportApplet::processAlarmCreationRequest( const QDateTime& departure, - const QString& lineString, VehicleType vehicleType, const QString& target, - QGraphicsWidget *item ) -{ - Q_UNUSED( item ); - Q_D( const PublicTransportApplet ); - - // Autogenerate an alarm that only matches the given departure - AlarmSettings alarm; - alarm.autoGenerated = true; - alarm.affectedStops << d->settings.currentStopIndex(); - alarm.name = i18nc( "@info/plain Name for a new alarm, eg. requested using the context menu. " - "%1 is the departure time or the name of the used vehicle.", - "One-Time Alarm (%1)", departure.isValid() ? departure.toString() - : Global::vehicleTypeToString(vehicleType) ); - - // Add alarm filters - if ( !departure.isNull() ) { - alarm.filter << Constraint(FilterByDepartureTime, FilterEquals, departure.time()); - alarm.filter << Constraint(FilterByDepartureDate, FilterEquals, departure.date()); - } - if ( !lineString.isEmpty() ) { - alarm.filter << Constraint(FilterByTransportLine, FilterEquals, lineString); - } - alarm.filter << Constraint(FilterByVehicleType, FilterIsOneOf, QVariantList() << vehicleType); - if ( !target.isEmpty() ) { - alarm.filter << Constraint(FilterByTarget, FilterEquals, target); - } - - // Append new alarm in a copy of the settings. Then write the new settings. - Settings settings = d->settings; - settings.appendAlarm( alarm ); - setSettings( settings ); - - alarmCreated(); -} - -void PublicTransportApplet::processAlarmDeletionRequest( const QDateTime& departure, - const QString& lineString, VehicleType vehicleType, const QString& target, - QGraphicsWidget* item) -{ - Q_UNUSED( item ); - Q_D( const PublicTransportApplet ); - - // Autogenerate an alarm that only matches the given departure - AlarmSettings alarm; - alarm.autoGenerated = true; - alarm.affectedStops << d->settings.currentStopIndex(); - if ( !departure.isNull() ) { - alarm.filter << Constraint(FilterByDepartureTime, FilterEquals, departure.time()); - alarm.filter << Constraint(FilterByDepartureDate, FilterEquals, departure.date()); - } - if ( !lineString.isEmpty() ) { - alarm.filter << Constraint(FilterByTransportLine, FilterEquals, lineString); - } - alarm.filter << Constraint(FilterByVehicleType, FilterIsOneOf, QVariantList() << vehicleType); - if ( !target.isEmpty() ) { - alarm.filter << Constraint(FilterByTarget, FilterEquals, target); - } - - // Remove autogenerated alarms that equal [alarm] in a copy of the settings. Then write the new settings. - Settings settings = d->settings; - settings.removeAlarm( alarm ); - setSettings( settings ); - - updatePopupIcon(); -} - -void PublicTransportApplet::createAlarmSettingsForDeparture( const QPersistentModelIndex &modelIndex, - bool onlyForCurrentWeekday ) -{ - Q_D( const PublicTransportApplet ); - - if ( !modelIndex.isValid() ) { - kDebug() << "!modelIndex.isValid()"; - return; - } - - DepartureItem *item = static_cast( d->model->itemFromIndex(modelIndex) ); - DepartureInfo info = *item->departureInfo(); - QString departureTime = KGlobal::locale()->formatTime( info.departure().time() ); - - // Autogenerate an alarm that only matches the given departure - AlarmSettings alarm; - alarm.autoGenerated = true; - alarm.affectedStops << d->settings.currentStopIndex(); - alarm.filter.append( Constraint(FilterByDepartureTime, FilterEquals, info.departure().time()) ); - alarm.filter.append( Constraint(FilterByDepartureDate, FilterEquals, info.departure().date()) ); - alarm.filter.append( Constraint(FilterByTransportLine, FilterEquals, info.lineString()) ); - alarm.filter.append( Constraint(FilterByVehicleType, FilterIsOneOf, - QVariantList() << info.vehicleType()) ); - alarm.filter.append( Constraint(FilterByTarget, FilterEquals, info.target()) ); - if ( onlyForCurrentWeekday ) { - alarm.filter.append( Constraint(FilterByDayOfWeek, FilterIsOneOf, - QVariantList() << QDate::currentDate().dayOfWeek()) ); - alarm.name = i18nc( "@info/plain Name of new automatically generated alarm filters. " - "%1 is the departure time, %2 is a day of the week.", - "One-Time Alarm (%1, every %2)", departureTime, - QDate::longDayName(QDate::currentDate().dayOfWeek()) ); - } else { - alarm.name = i18nc( "@info/plain Name of new automatically generated alarm filters. " - "%1 is the departure time, %2 is the target.", - "One-Time Alarm (%1 to %2)", departureTime, info.target() ); - } - - // Append new alarm in a copy of the settings. Then write the new settings. - Settings settings = d->settings; - settings.appendAlarm( alarm ); - setSettings( settings ); - - // Add the new alarm to the list of alarms that match the given departure - const int index = settings.alarms().count() - 1; - info.matchedAlarms() << index; - item->setDepartureInfo( info ); -} - -void PublicTransportApplet::createAlarmForDeparture() -{ - Q_D( const PublicTransportApplet ); - createAlarmSettingsForDeparture( d->clickedItemIndex ); - alarmCreated(); -} - -void PublicTransportApplet::createAlarmForDepartureCurrentWeekDay() -{ - Q_D( const PublicTransportApplet ); - createAlarmSettingsForDeparture( d->clickedItemIndex, true ); - alarmCreated(); -} - -void PublicTransportApplet::alarmCreated() -{ - Q_D( PublicTransportApplet ); - - updatePopupIcon(); // TEST needed or already done in writeSettings? - - // Animate popup icon to show the alarm departure (at index -1) - d->popupIcon->animateToAlarm(); -} - -void PublicTransportApplet::alarmFired( DepartureItem* item, const AlarmSettings &alarm ) -{ - const DepartureInfo *departureInfo = item->departureInfo(); - QString sLine = departureInfo->lineString(); - QString sTarget = departureInfo->target(); - QDateTime predictedDeparture = departureInfo->predictedDeparture(); - int minsToDeparture = qCeil( QDateTime::currentDateTime().secsTo(predictedDeparture) / 60.0 ); - - QString message; - if ( minsToDeparture > 0 ) { - // Departure is in the future - if ( departureInfo->vehicleType() == UnknownVehicleType ) { - // Vehicle type is unknown - message = i18ncp( "@info/plain %5: Name of the Alarm", - "%5: Line %2 to '%3' departs in %1 minute at %4", - "%5: Line %2 to '%3' departs in %1 minutes at %4", - minsToDeparture, sLine, sTarget, - predictedDeparture.toString("hh:mm"), alarm.name ); - } else { - // Vehicle type is known - message = i18ncp( "@info/plain %2: Line string (e.g. 'U3'), %4: Vehicle type name " - "(e.g. tram, subway), %6: Name of the Alarm", - "%6: The %4 %2 to '%3' departs in %1 minute at %5", - "%6: The %4 %2 to '%3' departs in %1 minutes at %5", - minsToDeparture, sLine, sTarget, - Global::vehicleTypeToString(departureInfo->vehicleType()), - predictedDeparture.toString("hh:mm"), alarm.name ); - } - } else if ( minsToDeparture < 0 ) { - // Has already departed - if ( departureInfo->vehicleType() == UnknownVehicleType ) { - // Vehicle type is unknown - message = i18ncp( "@info/plain %5: Name of the Alarm", - "%5: Line %2 to '%3' has departed %1 minute ago at %4", - "%5: Line %2 to '%3' has departed %1 minutes ago at %4", - -minsToDeparture, sLine, sTarget, - predictedDeparture.toString("hh:mm"), alarm.name ); - } else { - // Vehicle type is known - message = i18ncp( "@info/plain %2: Line string (e.g. 'U3'), %4: Vehicle type name " - "(e.g. tram, subway), %6: Name of the Alarm", - "%6: The %4 %2 to '%3' has departed %1 minute ago at %5", - "%6: The %4 %2 to %3 has departed %1 minutes ago at %5", - -minsToDeparture, sLine, sTarget, - Global::vehicleTypeToString(departureInfo->vehicleType()), - predictedDeparture.toString("hh:mm"), alarm.name ); - } - } else { - // Departs now - if ( departureInfo->vehicleType() == UnknownVehicleType ) { - // Vehicle type is unknown - message = i18nc( "@info/plain %4: Name of the Alarm", - "%4: Line %1 to '%2' departs now at %3", - sLine, sTarget, predictedDeparture.toString("hh:mm"), - alarm.name ); - } else { - // Vehicle type is known - message = i18nc( "@info/plain %1: Line string (e.g. 'U3'), %3: Vehicle type name " - "(e.g. tram, subway), %5: Name of the Alarm", - "%5: The %3 %1 to '%2' departs now at %4", sLine, sTarget, - Global::vehicleTypeToString(departureInfo->vehicleType()), - predictedDeparture.toString("hh:mm"), alarm.name ); - } - } - - KNotification::event( KNotification::Warning, message, - KIcon("public-transport-stop").pixmap(16), 0L, - KNotification::Persistent ); -} - -void PublicTransportApplet::removeAlarms( const AlarmSettingsList &newAlarmSettings, - const QList &/*removedAlarms*/ ) -{ - Q_D( const PublicTransportApplet ); - - // Change alarm settings in a copy of the settings. Then write the new settings. - Settings settings = d->settings; - settings.setAlarms( newAlarmSettings ); - setSettings( settings ); -} - -void GraphicsPixmapWidget::paint( QPainter* painter, - const QStyleOptionGraphicsItem* option, QWidget* ) -{ - if ( !option->rect.isValid() ) { - return; - } - painter->drawPixmap( option->rect, m_pixmap ); -} - -QVariant PublicTransportApplet::supportedJourneySearchState() const -{ - Q_D( const PublicTransportApplet ); - - QObject *object = qobject_cast( - d->currentServiceProviderFeatures.contains("ProvidesJourneys") - ? d->states["journeySearch"] : d->states["journeysUnsupportedView"] ); - return qVariantFromValue( object ); -} - -void PublicTransportApplet::configureJourneySearches() -{ - Q_D( const PublicTransportApplet ); - - // First let the settings object be updated, then update the menu based on the new settings - QPointer dialog = new KDialog; - dialog->setWindowTitle( i18nc("@title:window", "Configure Journey Searches") ); - dialog->setWindowIcon( KIcon("configure") ); - QVBoxLayout *l = new QVBoxLayout( dialog->mainWidget() ); - l->setMargin( 0 ); - - QStyleOption option; - initStyleOption( &option ); - const KIcon icon("favorites"); - - // Add journey search list - JourneySearchListView *journeySearchList = new JourneySearchListView( dialog->mainWidget() ); - journeySearchList->setEditTriggers( QAbstractItemView::DoubleClicked | - QAbstractItemView::SelectedClicked | - QAbstractItemView::EditKeyPressed | - QAbstractItemView::AnyKeyPressed ); - - // Create model for journey searches - JourneySearchModel *model = new JourneySearchModel( dialog ); - QList< JourneySearchItem > journeySearches = d->settings.currentJourneySearches(); - for ( int i = 0; i < journeySearches.count(); ++i ) { - const JourneySearchItem item = journeySearches[i]; - model->addJourneySearch( item.journeySearch(), item.name(), item.isFavorite() ); - } - model->sort(); - journeySearchList->setModel( model ); - - QLabel *label = new QLabel( i18nc("@label:listbox", "Favorite and recent journey searches " - "for '%1':", d->currentProviderData["name"].toString()), - dialog->mainWidget() ); - label->setWordWrap( true ); - label->setBuddy( journeySearchList ); - - l->addWidget( label ); - l->addWidget( journeySearchList ); - if ( dialog->exec() == KDialog::Accepted ) { - journeySearchListUpdated( model->journeySearchItems() ); - } -} - -void PublicTransportApplet::setAssociatedApplicationUrlForJourneys() -{ - Q_D( const PublicTransportApplet ); - setAssociatedApplicationUrls (KUrl::List() << d->urlJourneys); -} - -void PublicTransportApplet::setAssociatedApplicationUrlForDepartures() -{ - Q_D( const PublicTransportApplet ); - setAssociatedApplicationUrls (KUrl::List() << d->urlDeparturesArrivals); -} - -int PublicTransportApplet::departureCount() const -{ - Q_D( const PublicTransportApplet ); - return d->departureInfos.count(); -} - -#include "publictransport.moc" diff --git a/applet/publictransport.h b/applet/publictransport.h deleted file mode 100644 index 441ee78..0000000 --- a/applet/publictransport.h +++ /dev/null @@ -1,638 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file - * @brief This file contains the public transport applet. - * @author Friedrich Pülz */ - -#ifndef PUBLICTRANSPORT_HEADER -#define PUBLICTRANSPORT_HEADER - -// KDE includes -#include // Base class -#include // For dataUpdated() slot, Plasma::DataEngine::Data -#include // For QProcess::ProcessError - -// Own includes -#include "stopaction.h" // For StopAction::Type -#include "settings.h" // Member variable - -namespace PublicTransport { - class JourneyInfo; -} - -class PublicTransportAppletPrivate; -class ItemBase; -class PublicTransportGraphicsItem; -class DepartureItem; - -class KJob; -class KSelectAction; -class QGraphicsSceneWheelEvent; -class QPersistentModelIndex; - -namespace Plasma { - class Label; -} - -/** @brief Simple pixmap graphics widgets (QGraphicsPixmapItem isn't a QGraphicsWidget). */ -class GraphicsPixmapWidget : public QGraphicsWidget { -public: - explicit GraphicsPixmapWidget( const QPixmap &pixmap, QGraphicsWidget *parent = 0 ) - : QGraphicsWidget( parent ), m_pixmap( pixmap ) { - setGeometry( QRectF(m_pixmap.rect()) ); - }; - - /** @returns the @ref QPixmap drawn by this graphics widget. */ - QPixmap pixmap() const { return m_pixmap; }; - virtual QRectF boundingRect() const { return geometry(); }; - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* /*widget*/ = 0 ); - -private: - QPixmap m_pixmap; -}; - -/** - * @brief Shows a departure/arrival board for public transport. - * - * It uses the "publictransport"-data engine and stores the data using @ref DepartureModel for - * departures/arrivals and @ref JourneyModel for journeys. The data from the data engine is read - * in a thread using @ref DepartureProcessor, which also applies filters and alarms. - * - * @ref TitleWidget is used as title of the applet. The departures/arrivals/journeys are shown - * in @ref TimetableWidget / @ref JourneyTimetableWidget. - **/ -class PublicTransportApplet : public Plasma::PopupApplet { - Q_OBJECT - - /** @brief The number of currently shown departures/arrivals. */ - Q_PROPERTY( int DepartureCount READ departureCount ) - - /** - * @brief The journey search state or the journey unsupported state. - * - * Depends on the features of the current service provider. - **/ - Q_PROPERTY( QVariant supportedJourneySearchState READ supportedJourneySearchState ) - -public: - /** @brief Basic create. */ - PublicTransportApplet( QObject *parent, const QVariantList &args ); - - /** @brief Destructor. */ - ~PublicTransportApplet(); - - /** - * @brief Maximum number of recent journey searches. - * - * When more journey searches get added, the oldest gets removed. - **/ - static const int MAX_RECENT_JOURNEY_SEARCHES = 10; - - /** @brief Return the widget with the contents of the applet. */ - virtual QGraphicsWidget* graphicsWidget(); - - /** @returns the number of currently shown departures/arrivals. */ - int departureCount() const; - - /** @brief Get a pointer to either the journey search state or the journeys unsupported state. */ - QVariant supportedJourneySearchState() const; - -signals: - /** @brief Emitted when the settings have changed. */ - void settingsChanged(); - - /** @brief Emitted when updated provider data has the state "ready". */ - void providerReady(); - - /** @brief Emitted when updated provider data does @em not have the state "ready". */ - void providerNotReady(); - - /** @brief Emitted when an intermediate departure list gets requested for @p stopName. */ - void intermediateDepartureListRequested( const QString &stopName ); - - /** - * @brief Emitted when the journey search is finished. - * - * This triggers a transition to the journey view state. - **/ - void journeySearchFinished(); - - /** @brief Emitted when the action buttons state was left. */ - void hideActionButtons(); - - /** @brief Emitted when the network connection is lost to go set the corresponding states. */ - void networkConnectionLost(); - - /** @brief Emitted when the network is being configured to go set the corresponding states. */ - void networkIsConfiguring(); - - /** @brief Emitted when the network connection is activated to go set the corresponding states. */ - void networkIsActivated(); - - /** @brief Emitted when new departure data gets requested to go set the corresponding states. */ - void requestedNewDepartureData(); - - /** @brief Emitted when valid departure data gets received to go set the corresponding states. */ - void validDepartureDataReceived(); - - /** @brief Emitted when invalid departure data gets received to go set the corresponding states. */ - void invalidDepartureDataReceived(); - - /** @brief Emitted when new journey data gets requested to go set the corresponding states. */ - void requestedNewJourneyData(); - - /** @brief Emitted when valid journey data gets received to go set the corresponding states. */ - void validJourneyDataReceived(); - - /** @brief Emitted when invalid journey data gets received to go set the corresponding states. */ - void invalidJourneyDataReceived(); - -public slots: - /** @brief Initializes the applet. */ - virtual void init(); - - /** - * @brief Clear the current list of stop settings and adds a new one. - * - * @param serviceProviderID The ID of the service provider to use for the new stop settings. - * @param stopName The stop name to use for the new stop settings. - **/ - void setSettings( const QString &serviceProviderID, const QString &stopName ); - - /** - * @brief Replace the current lists of stop and filter settings. - * - * @param stops The new list of stops. - * @param filters The new list of filters. - **/ - void setSettings( const StopSettingsList &stops, - const FilterSettingsList &filters ); - - /** - * @brief Replace the current settings and updates the applet accordingly. - * - * Settings are written using SettingsIO::writeSettings() which also compares them with the - * old settings and returns flags for changes (SettingsIO::ChangedFlags). Only parts of the - * applet that are affected by changed settings get updated. - * - * @param settings The new settings. - **/ - void setSettings( const Settings &settings ); - - void requestEarlierJourneys(); - void requestLaterJourneys(); - -protected slots: - /** @brief The geometry of the applet has changed. */ - void appletResized(); - - /** @brief Overriden to update the popup icon to new sizes using updatePopupIcon(). */ - virtual void resizeEvent( QGraphicsSceneResizeEvent* event ); - - /** - * @brief New @p data arrived from the data engine for the source with the name @p sourceName. - * - * @ingroup models - **/ - void dataUpdated( const QString &sourceName, const Plasma::DataEngine::Data &data ); - - void acceptActionButtons(); - - void setAssociatedApplicationUrlForDepartures(); - void setAssociatedApplicationUrlForJourneys(); - - /** @brief The state of the departure data from the data engine has changed (waiting, invalid/valid). */ - void departureDataStateChanged(); - - /** @brief The state of the journey data from the data engine has changed (waiting, invalid/valid). */ - void journeyDataStateChanged(); - - void disconnectJourneySource(); - - /** @brief The context menu for a departure @p item was requested. */ - void departureContextMenuRequested( PublicTransportGraphicsItem *item, const QPointF &pos ); - - /** - * @brief Get called by the context menu of route stops. - * - * Performs the given @p stopAction. - * - * @param stopAction The action that is requested to be performed. - * @param stopName The stop name to perform @p stopAction on. - * @param stopNameShortened The shortened stop name to show to the user. - **/ - void requestStopAction( StopAction::Type stopAction, - const QString &stopName, const QString &stopNameShortened ); - - /** - * @brief Show the departure list. - * - * Can be used eg. if the journey view is currently shown to go back - * to the departure view. - * - * This gets called when the departure view state is entered. - **/ - void showDepartureList(); - - /** - * @brief Show the journey list. - * - * This gets called when the journey view state is entered, eg. because - * a journey search was finished. - **/ - void showJourneyList(); - - /** - * @brief Show an intermediate departure list. - * - * Shows a departure list for another stop, with the option to go back to - * the original stop again. - * This uses a special StopSettings item in the list of stored stop - * settings, which will get deleted automatically when the intermediate - * departure list is closed again. - * - * This gets called when the intermediate departure view state is entered, - * eg. because it was requested by the context menu of a route stop item. - **/ - void showIntermediateDepartureList(); - - /** - * @brief Show departures in the departure list (not arrivals). - * - * Writes new settings with @ref Settings::departureArrivalListType set - * to @p DepartureList. This also updates the departure view on @ref configChanged. - * - * @note This doesn't switch to the departure list automatically. - * To do this also use @ref showDepartureList. - **/ - void showDepartures(); - - /** - * @brief Show arrivals in the departure list. - * - * Writes new settings with @ref Settings::departureArrivalListType set - * to @p ArrivalList. This also updates the departure view on @ref configChanged. - * - * @note This doesn't switch to the departure list automatically. - * To do this also use @ref showDepartureList. - **/ - void showArrivals(); - - /** - * @brief Show the journey search view. - * - * Switches to journey search mode. To go back to the departure list use - * @ref showDepartureList. If a journey search string is set the user can - * go to the journey mode from the search mode. - **/ - void showJourneySearch(); - - /** @brief Called when exiting the journey search state. */ - void exitJourneySearch(); - - /** - * @brief Show a message about unsupported journeys. - * - * This gets called if the journey search state should be entered, but - * journeys aren't supported by the current service provider. - **/ - void showJourneysUnsupportedView(); - - /** - * @brief Show the action button overlay. - * - * Blurs the applet and shows some buttons to perform actions like searching - * for a journey or toggling between departure and arrival view. - **/ - void showActionButtons(); - - /** - * @brief Show @p mainWidget in the applet. - * - * This gets used to switch eg. to the departure/arrival view (showDepartureList()) - * or to the journey view (showJourneyList()). - * Can also be used to show other widgets, eg. showing messages. - **/ - void showMainWidget( QGraphicsWidget *mainWidget ); - - /** @brief Remove stop settings, that were inserted for an intermediate - * departure list. */ - void removeIntermediateStopSettings(); - - /** - * @brief Finished editing the journey search line (return pressed, - * start search button clicked or stop suggestion double clicked). - * - * @param text The finished journey search text. - **/ - void journeySearchInputFinished( const QString &text ); - - /** - * @brief The journey search line has been changed and parsed. - * - * This slot gets called by JourneySearchSuggestionWidget. - * @param stopName The parsed stop name. - * @param departure The parsed departure date and time. - * @param stopIsTarget Whether or not the parsed stop should be treated as target (true) - * or as origin stop (false). - * @param timeIsDeparture Whether or not the parsed time should be treated as departure (true) - * or as arrival time (false). - **/ - void journeySearchLineChanged( const QString &stopName, const QDateTime &departure, - bool stopIsTarget, bool timeIsDeparture ); - - /** @brief The action to update the data source has been triggered. */ - void updateDataSource(); - - /** @brief The action to set an alarm for the selected departure/arrival has been triggered. */ - void createAlarmForDeparture(); - - /** @brief The action to set an alarm for the current weekday has been triggered. */ - void createAlarmForDepartureCurrentWeekDay(); - - /** @brief The action to remove an alarm from the selected departure/arrival has been triggered. */ - void removeAlarmForDeparture(); - - /** @brief The action to expand / collapse of the selected departure/arrival has been triggered. */ - void toggleExpanded(); - - /** @brief The action to hide the direction column of the tree view header has been triggered. */ - void hideColumnTarget(); - - /** @brief The action to show the direction column of the tree view header has been triggered. */ - void showColumnTarget(); - - /** @brief The plasma theme has been changed. */ - void themeChanged(); - - /** @brief Shows the stop at the given coordinates in a running Marble process. */ - void showStopInMarble( const QString &stopName = QString(), bool coordinatesAreValid = false, - qreal lon = 0.0, qreal lat = 0.0 ); - - /** @brief The 'marble' process has finished. */ - void marbleFinished( int exitCode ); - - /** @brief There was an error with Marble */ - void marbleError( const QString &errorMessage ); - - /** @brief The expanded state of @p item changed to @p expanded. */ - void expandedStateChanged( PublicTransportGraphicsItem *item, bool expanded ); - - /** @brief A recent journey @p action was triggered from the "quickJourneys" action. */ - void journeyActionTriggered( QAction *action ); - - /** - * @brief Updates the settings and change the current list of journey searches. - * Uses Settings::setCurrentJourneySearches(). - **/ - void journeySearchListUpdated( const QList &newJourneySearches ); - - /** - * @brief Processes a journey search request. - * - * @param stop The target/origin stop of the journey to search. - * @param stopIsTarget Whether @p stop is the target or the origin. The other stop is the - * current home stop. - **/ - void processJourneyRequest( const QString &stop, bool stopIsTarget ); - - /** - * @brief The worker thread starts processing departures/arrivals from the - * data engine. - * - * @param sourceName The data engine source name for the departure data. - * @see departuresProcessed - * @ingroup models - **/ - void beginDepartureProcessing( const QString &sourceName ); - - /** - * @brief The worker thread has finished processing departures/arrivals. - * - * @param sourceName The data engine source name for the departure data. - * @param departures A list of departures that were read by the worker thread. - * @param requestUrl The url that was used to download the departure data. - * @param lastUpdate The date and time of the last update of the data. - * @param nextAutomaticUpdate The date and time of the next automatic update of the data source. - * @param minManualUpdateTime The minimal date and time of the next (manual) update of the - * data source. Earlier update requests will be rejected. - * @param departuresToGo The number of departures to still be processed. If this isn't 0 - * this slot gets called again after the next batch of departures has been processed. - * - * @see DepartureInfo - * @see beginDepartureProcessing - * @ingroup models - **/ - void departuresProcessed( const QString &sourceName, - const QList< DepartureInfo > &departures, - const QUrl &requestUrl, const QDateTime &lastUpdate, - const QDateTime &nextAutomaticUpdate, - const QDateTime &minManualUpdateTime, int departuresToGo ); - - /** - * @brief The worker thread has finished filtering departures. - * - * @param sourceName The data engine source name for the departure data. - * @param departures The list of departures that were filtered. Each departure now returns - * the correct value with isFilteredOut() according to the filter settings given to the - * worker thread. - * @param newlyFiltered A list of departures that should be made visible to match the current - * filter settings. - * @param newlyNotFiltered A list of departures that should be made invisible to match the - * current filter settings. - * - * @ingroup models - **/ - void departuresFiltered( const QString &sourceName, - const QList< DepartureInfo > &departures, - const QList< DepartureInfo > &newlyFiltered, - const QList< DepartureInfo > &newlyNotFiltered ); - - /** - * @brief The worker thread starts processing journeys from the data engine. - * - * @see journeysProcessed - * @ingroup models - **/ - void beginJourneyProcessing( const QString &sourceName ); - - /** - * @brief The worker thread has finished processing journeys. - * - * @param sourceName The data engine source name for the journey data. - * @param journeys A list of journeys that were read by the worker thread. - * @param requestUrl The url that was used to download the journey data. - * @param lastUpdate The date and time of the last update of the data. - * - * @see JourneyInfo - * @see beginJourneyProcessing - * @ingroup models - **/ - void journeysProcessed( const QString &sourceName, - const QList< JourneyInfo > &journeys, - const QUrl &requestUrl, const QDateTime &lastUpdate ); - - /** @brief The animation fading out a pixmap of the old applet appearance has finished. */ - void oldItemAnimationFinished(); - - /** @brief The animation to toggle the display of the title has finished. */ - void titleToggleAnimationFinished(); - - /** @brief Delete the overlay item used when showing action buttons over the plasmoid. */ - void destroyOverlay(); - - /** @brief An action to change the currently shown stop has been triggered. */ - void setCurrentStopIndex( QAction *action ); - - void setCurrentStopIndex( int stopIndex ); - - /** @brief Enable @p filterConfiguration for the currently active stop settings. */ - void enableFilterConfiguration( const QString &filterConfiguration, bool enable = true ); - - /** - * @brief An action to toggle a filter configuration has been triggered. - * - * This uses the text of @p action to know which filter configuration to toggle. - **/ - void switchFilterConfiguration( QAction *action ); - - /** - * @brief An action to a filter by group color has been triggered. - * - * This uses the data in @p action (QAction::data()) to know which color group to toggle. - **/ - void switchFilterByGroupColor( QAction *action ); - - /** @brief An alarm has been fired for the given @p item. */ - void alarmFired( DepartureItem *item, const AlarmSettings &alarm ); - - void removeAlarms( const AlarmSettingsList &newAlarmSettings, - const QList &removedAlarms ); - - void processAlarmCreationRequest( const QDateTime &departure, const QString &lineString, - VehicleType vehicleType, const QString &target, - QGraphicsWidget *item ); - void processAlarmDeletionRequest( const QDateTime &departure, const QString &lineString, - VehicleType vehicleType, const QString &target, - QGraphicsWidget *item ); - - /** - * @brief The @p departures will get removed after this slot was called. - * - * @param items A list of DepartureItems, that will get removed - * after this slot was called. - * - * @note This slot is connected to the itemsAboutToBeRemoved signal - * of the departure/arrival model. - **/ - void departuresAboutToBeRemoved( const QList &departures ); - - /** - * @brief The @p departures have just left. - * - * The DepartureItems for the given @p departures were deleted before this - * slot gets called. - **/ - void departuresLeft( const QList &departures ); - - /** @brief Updates the popup icon with information about the next departure / alarm. */ - void updatePopupIcon(); - - /** @brief Updates the tooltip. */ - void updateTooltip(); - - /** @brief Opens a configuration dialog for recent/favorite journey searches. */ - void configureJourneySearches(); - - void updateRequestFinished( KJob *job ); - void enableUpdateAction(); - -protected: - /** - * @brief Create the configuration dialog contents. - * - * @param parent The config dialog in which the config interface should be created. - **/ - virtual void createConfigurationInterface( KConfigDialog *parent ); - - /** @brief Get a list of actions for the context menu. */ - virtual QList contextualActions(); - - /** @brief The popup gets shown or hidden. */ - virtual void popupEvent( bool show ); - - /** @brief Mouse wheel rotated on popup icon. */ - virtual void wheelEvent( QGraphicsSceneWheelEvent* event ); - - /** @brief Overridden to make links clickable in the bottom info label. */ - virtual bool sceneEventFilter( QGraphicsItem* watched, QEvent* event ); - - /** @brief Watch for up/down key presses in m_journeySearch to select stop suggestions. */ - virtual bool eventFilter( QObject* watched, QEvent* event ); - - /** @brief Create all used actions. */ - void setupActions(); - - void disableUpdateAction(); - - /** - * @brief Get an action with string and icon updated to the current settings. - * - * @param actionName The name of the action to return updated. - * @return The updated action. - **/ - QAction* updatedAction( const QString &actionName ); - - /** @brief Call after creating a new alarm, to update the UI accordingly. */ - void alarmCreated(); - - /** - * @brief Handle errors from the publictransport data engine for @p data from source @p sourceName. - * - * @ingroup models - **/ - void handleDataError( const QString &sourceName, const Plasma::DataEngine::Data& data ); - - /** - * @brief Read stop suggestions from the data engine. - * - * @ingroup models - **/ - void processStopSuggestions( const QString &sourceName, const Plasma::DataEngine::Data& data ); - - /** @brief Set an autogenerated alarm for the given departure/arrival. */ - void createAlarmSettingsForDeparture( const QPersistentModelIndex &modelIndex, - bool onlyForCurrentWeekday = false ); - - /** @brief Remove an autogenerated alarm from this departure/arrival if any. */ - void removeAlarmForDeparture( int row ); - -private: - PublicTransportAppletPrivate* const d_ptr; - Q_DECLARE_PRIVATE( PublicTransportApplet ) -}; - -#ifndef NO_EXPORT_PLASMA_APPLET // Needed for publictransport_p.h to include publictransport.h -// This is the command that links the applet to the .desktop file -K_EXPORT_PLASMA_APPLET( publictransport, PublicTransportApplet ) -#endif - -#endif // Multiple inclusion guard diff --git a/applet/publictransport_p.cpp b/applet/publictransport_p.cpp deleted file mode 100644 index 2f7e706..0000000 --- a/applet/publictransport_p.cpp +++ /dev/null @@ -1,1421 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Own includes -#include "publictransport_p.h" -#include "colorgroups.h" - -// Plasma includes -#include -#include -#include -#include -#include - -// KDE includes -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include -#include -#include - -ToPropertyTransition::ToPropertyTransition( QObject *sender, const char *signal, QState *source, - QObject *propertyObject, const char *targetStateProperty ) - : QSignalTransition( sender, signal, source ), - m_propertyObject( propertyObject ), - m_property( targetStateProperty ) -{ - qRegisterMetaType( "QState*" ); -} - -void ToPropertyTransition::setTargetStateProperty( const QObject *propertyObject, const char *property ) -{ - m_propertyObject = propertyObject; - m_property = property; -} - -PublicTransportAppletPrivate::PublicTransportAppletPrivate( PublicTransportApplet *q ) - : graphicsWidget(0), mainGraphicsWidget(0), - oldItem(0), titleWidget(0), labelInfo(0), timetable(0), journeyTimetable(0), - labelJourneysNotSupported(0), listStopSuggestions(0), overlay(0), model(0), - popupIcon(0), titleToggleAnimation(0), runningUpdateRequests(0), updateTimer(0), - modelJourneys(0), originalStopIndex(-1), filtersGroup(0), colorFiltersGroup(0), - departureProcessor(0), departurePainter(0), stateMachine(0), journeySearchTransition1(0), - journeySearchTransition2(0), journeySearchTransition3(0), marble(0), q_ptr( q ) -{ -} - -bool ToPropertyTransition::eventTest( QEvent* event ) -{ - if ( !QSignalTransition::eventTest(event) ) { - return false; - } - setTargetState( currentTargetState() ); - return true; -} - -void PublicTransportAppletPrivate::onSettingsChanged( const Settings &_settings, - SettingsIO::ChangedFlags changed ) -{ - Q_Q( PublicTransportApplet ); - - if ( !changed.testFlag(SettingsIO::IsChanged) ) { - kDebug() << "No changes made in the settings"; - return; - } - - // Copy new settings - settings = _settings; - - emit q->configNeedsSaving(); - emit q->settingsChanged(); - - // First update the departure processor - if ( changed.testFlag(SettingsIO::ChangedDepartureArrivalListType) ) { - departureProcessor->setDepartureArrivalListType( settings.departureArrivalListType() ); - } - if ( changed.testFlag(SettingsIO::ChangedFilterSettings) ) { - departureProcessor->setFilters( settings.currentFilters() ); - } - if ( changed.testFlag(SettingsIO::ChangedColorGroupSettings) ) { - departureProcessor->setColorGroups( settings.currentColorGroups() ); - } - if ( changed.testFlag(SettingsIO::ChangedLinesPerRow) ) { - timetable->setMaxLineCount( settings.linesPerRow() ); -// journeyTimetable->setMaxLineCount( settings.linesPerRow ); // TEST - model->setLinesPerRow( settings.linesPerRow() ); - } - // Apply show departures/arrivals setting - if ( changed.testFlag(SettingsIO::ChangedDepartureArrivalListType) ) { - model->setDepartureArrivalListType( settings.departureArrivalListType() ); - - // Update text in the departure/arrival view that gets shown when the model is empty - onDepartureDataStateChanged(); - } - - // If stop settings have changed the whole model gets cleared and refilled. - // Therefore the other change flags can be in 'else' parts - const bool reloadTimetableData = - changed.testFlag(SettingsIO::ChangedServiceProvider) || - changed.testFlag(SettingsIO::ChangedCurrentStopSettings) || - changed.testFlag(SettingsIO::ChangedCurrentStop); - if ( reloadTimetableData ) { - clearDepartures(); - - if ( changed.testFlag(SettingsIO::ChangedCurrentStopSettings) ) { - // Apply first departure settings to the worker thread - const StopSettings stop = settings.currentStop(); - departureProcessor->setFirstDepartureSettings( - static_cast(stop.get( - FirstDepartureConfigModeSetting)), - stop.get(TimeOfFirstDepartureSetting), - stop.get(TimeOffsetOfFirstDepartureSetting) ); - - int alarmMinsBeforeDeparture = stop.get( AlarmTimeSetting ); - model->setAlarmMinsBeforeDeparture( alarmMinsBeforeDeparture ); - modelJourneys->setAlarmMinsBeforeDeparture( alarmMinsBeforeDeparture ); - } - - updateInfoText(); - - settings.adjustColorGroupSettingsCount(); - onServiceProviderSettingsChanged(); - } else if ( changed.testFlag(SettingsIO::ChangedFilterSettings) || - changed.testFlag(SettingsIO::ChangedColorGroupSettings) ) - { - for( int n = 0; n < stopIndexToSourceName.count(); ++n ) { - QString sourceName = stripDateAndTimeValues( stopIndexToSourceName[n] ); - departureProcessor->filterDepartures( sourceName, - departureInfos[sourceName], model->itemHashes() ); - } - } else if ( changed.testFlag(SettingsIO::ChangedLinesPerRow) ) { - // Refill model to recompute item sizehints - model->clear(); - fillModel( mergedDepartureList() ); - } - - if ( !reloadTimetableData && - changed.testFlag(SettingsIO::ChangedAdditionalDataRequestSettings) ) - { - // Request additional data for all timetable items - if ( settings.additionalDataRequestType() == Settings::RequestAdditionalDataDirectly ) { - foreach ( const QString ¤tSource, currentSources ) { - Plasma::Service *service = - q->dataEngine("publictransport")->serviceForSource( currentSource ); - if ( !service ) { - kWarning() << "No Timetable Service!"; - return; - } - - int itemBegin = 999999999; - int itemEnd = 0; - foreach ( const DepartureInfo departure, model->departureInfos() ) { - if ( !departure.includesAdditionalData() && - !departure.isWaitingForAdditionalData() && - departure.additionalDataError().isEmpty() ) - { - const int index = departure.index(); - itemBegin = qMin( itemBegin, index ); - itemEnd = qMax( itemEnd, index ); - } - } - - if ( itemBegin < 999999999 ) { - KConfigGroup op = service->operationDescription("requestAdditionalDataRange"); - op.writeEntry( "itemnumberbegin", itemBegin ); - op.writeEntry( "itemnumberend", itemEnd ); - Plasma::ServiceJob *additionDataJob = service->startOperationCall( op ); - q->connect( additionDataJob, SIGNAL(finished(KJob*)), service, SLOT(deleteLater()) ); - } - } - } - } - - if ( changed.testFlag(SettingsIO::ChangedCurrentJourneySearchLists) || - changed.testFlag(SettingsIO::ChangedCurrentStop) ) - { - // Update the journeys menu - updateJourneyMenu(); - } - if ( changed.testFlag(SettingsIO::ChangedDepartureArrivalListType) ) { - onDepartureArrivalListTypeChanged(); - } - - // Update current stop settings / current home stop in the models - if ( changed.testFlag(SettingsIO::ChangedCurrentStop) || - changed.testFlag(SettingsIO::ChangedCurrentStopSettings) ) - { - onCurrentStopSettingsChanged(); - } - - // Update the filter widget - if ( changed.testFlag(SettingsIO::ChangedCurrentFilterSettings) || - changed.testFlag(SettingsIO::ChangedColorGroupSettings) ) - { - // Update the filter menu, if filter or color group settings have changed. - // If the current stop or it's settings have changed, the active filters - // and color groups may also have changed, requiring an update of the filter menu - updateFilterMenu(); - titleWidget->updateFilterWidget(); - } - - // Update alarm settings - if ( changed.testFlag(SettingsIO::ChangedAlarmSettings) ) { - model->setAlarmSettings( settings.alarms() ); - if ( modelJourneys ) { - modelJourneys->setAlarmSettings( settings.alarms() ); - } - departureProcessor->setAlarms( settings.alarms() ); - } - - // Apply font / size factor - if ( changed.testFlag(SettingsIO::ChangedFont) || - changed.testFlag(SettingsIO::ChangedSizeFactor) ) - { - // Get fonts - QFont font = settings.sizedFont(); - int smallPointSize = KGlobalSettings::smallestReadableFont().pointSize() * settings.sizeFactor(); - QFont smallFont = font; - smallFont.setPointSize( smallPointSize > 0 ? smallPointSize : 1 ); - - // Apply fonts - labelInfo->setFont( smallFont ); - timetable->setFont( font ); - if ( journeyTimetable && isStateActive("journeyView") ) { - journeyTimetable->setFont( font ); - } - } - - // Apply size factor settings - if ( changed.testFlag(SettingsIO::ChangedSizeFactor) ) { - model->setSizeFactor( settings.sizeFactor() ); // Used for sizes of icons returned by the model - timetable->setZoomFactor( settings.sizeFactor() ); - if ( journeyTimetable && isStateActive("journeyView") ) { - journeyTimetable->setZoomFactor( settings.sizeFactor() ); - } - } - - // Apply shadow settings - if ( changed.testFlag(SettingsIO::ChangedShadows) ) { - timetable->setOption( PublicTransportWidget::DrawShadowsOrHalos, settings.drawShadows() ); - if ( journeyTimetable && isStateActive("journeyView") ) { - journeyTimetable->setOption( PublicTransportWidget::DrawShadowsOrHalos, - settings.drawShadows() ); - } - } - - // Update title widget to settings - if ( changed.testFlag(SettingsIO::ChangedCurrentStopSettings) || - changed.testFlag(SettingsIO::ChangedFont) || - changed.testFlag(SettingsIO::ChangedSizeFactor) ) - { - titleWidget->settingsChanged(); - } - - // Apply target column settings - if ( changed.testFlag(SettingsIO::ChangedTargetColumn) && - stateMachine && isStateActive("departureView") ) - { - timetable->setTargetHidden( settings.hideTargetColumn() ); - timetable->updateItemLayouts(); - } - - // Limit model item count to the maximal number of departures setting - if ( model->rowCount() > settings.maximalNumberOfDepartures() ) { - model->removeRows( settings.maximalNumberOfDepartures(), - model->rowCount() - settings.maximalNumberOfDepartures() ); - } - - if ( changed.testFlag(SettingsIO::ChangedDepartureTimeSettings) ) { - model->setDepartureColumnSettings( settings.departureTimeFlags() ); - } -} - -void PublicTransportAppletPrivate::onDepartureArrivalListTypeChanged() -{ - Q_Q( PublicTransportApplet ); - - model->setDepartureArrivalListType( settings.departureArrivalListType() ); - timetable->updateItemLayouts(); - - // Adjust action texts to departure / arrival list - q->action( "removeAlarmForDeparture" )->setText( - settings.departureArrivalListType() == DepartureList - ? i18nc( "@action", "Remove &Alarm for This Departure" ) - : i18nc( "@action", "Remove &Alarm for This Arrival" ) ); - q->action( "createAlarmForDeparture" )->setText( - settings.departureArrivalListType() == DepartureList - ? i18nc( "@action", "Set &Alarm for This Departure" ) - : i18nc( "@action", "Set &Alarm for This Arrival" ) ); - q->action( "backToDepartures" )->setText( - settings.departureArrivalListType() == DepartureList - ? i18nc( "@action", "Back to &Departure List" ) - : i18nc( "@action", "Back to &Arrival List" ) ); -} - -void PublicTransportAppletPrivate::onCurrentStopSettingsChanged() -{ - model->setHomeStop( settings.currentStop().stop(0).name ); - model->setCurrentStopIndex( settings.currentStopIndex() ); - - if ( modelJourneys ) { - modelJourneys->setHomeStop( settings.currentStop().stop(0).name ); - modelJourneys->setCurrentStopIndex( settings.currentStopIndex() ); - } -} - -void PublicTransportAppletPrivate::onServiceProviderSettingsChanged() -{ - Q_Q( PublicTransportApplet ); - - if ( settings.checkConfig() ) { - // Configuration is valid - q->setConfigurationRequired( false ); - - // Connect to the "ServiceProvider [providerId]" source if not done already - // to get provider data and to get notified on changes - const QString previousProviderId = currentProviderData["id"].toString(); - const QString providerId = settings.currentStop().get< QString >( ServiceProviderSetting ); - if ( providerId != previousProviderId ) { - // First disable everything that depends on provider features, - // enable it when the provider data arrives and the required features are enabled - q->action( "journeys" )->setEnabled( false ); - titleWidget->setJourneysSupported( false ); - - // Clear old provider data - currentProviderData.clear(); - currentServiceProviderFeatures.clear(); - - // Disconnect previous provider data source if any and connect new one - Plasma::DataEngine *engine = q->dataEngine( "publictransport" ); - if ( !previousProviderId.isEmpty() ) { - engine->disconnectSource( "ServiceProvider " + previousProviderId, q ); - } - engine->connectSource( "ServiceProvider " + providerId, q ); - } else { - // Call providerDataUpdated() manually to enable provider feature dependend actions, - // the data source is already connected - providerDataUpdated( currentProviderData ); - } - - // Reconnect with new settings - reconnectSource(); - if ( !currentJourneySource.isEmpty() ) { - reconnectJourneySource(); - } - } else { - clearDepartures(); - - // Missing configuration, eg. no home stop - q->setConfigurationRequired( true, i18nc( "@info/plain", "Please check your configuration." ) ); - - q->action( "journeys" )->setEnabled( false ); - titleWidget->setJourneysSupported( false ); - } -} - -void PublicTransportAppletPrivate::providerDataUpdated( const QVariantHash &data ) -{ - Q_Q( PublicTransportApplet ); - currentProviderData = data; - currentServiceProviderFeatures = data["features"].toStringList(); - model->setProviderFeatures( currentServiceProviderFeatures ); - if ( modelJourneys ) { - modelJourneys->setProviderFeatures( currentServiceProviderFeatures ); - } - - // Only use the default target state (journey search) if journeys - // are supported by the used service provider. Otherwise go to the - // alternative target state (journeys not supported). - const bool journeysSupported = currentServiceProviderFeatures.contains( "ProvidesJourneys" ); - QAbstractState *target = journeysSupported - ? states["journeySearch"] : states["journeysUnsupportedView"]; - journeySearchTransition1->setTargetState( target ); - journeySearchTransition2->setTargetState( target ); - journeySearchTransition3->setTargetState( target ); - - q->action( "journeys" )->setEnabled( journeysSupported ); - titleWidget->setJourneysSupported( journeysSupported ); - - // Check provider state - const QString state = data[ "state" ].toString(); - if ( state == QLatin1String("ready") ) { - // Provider is ready to use - const bool hadProviderError = isStateActive("providerError"); - q->emit providerReady(); - - if ( hadProviderError ) { - reconnectSource(); - } - } else { - // provider is not ready, eg. needs to import a GTFS feed first - q->emit providerNotReady(); - clearDepartures(); - clearJourneys(); - } - - // Check if arrivals are currently shown but not supported by the new provider - if ( !currentServiceProviderFeatures.contains("ProvidesArrivals", Qt::CaseInsensitive) && - settings.departureArrivalListType() == ArrivalList ) - { - Settings newSettings = settings; - newSettings.setDepartureArrivalListType( DepartureList ); - q->setSettings( newSettings ); - } - - // Show error message in the departure/arrival view, if any - onDepartureDataStateChanged(); -} - -void PublicTransportAppletPrivate::onResized() -{ - Q_Q( PublicTransportApplet ); - - // Get the size of the applet/popup (not the size of the popup icon if iconified) - QSizeF size = graphicsWidget->size(); - - if ( titleWidget ) { - q->updatePopupIcon(); - - // Show/hide title widget - const qreal minHeightWithTitle = 200.0; - const qreal maxHeightWithoutTitle = 225.0; - if ( size.height() <= minHeightWithTitle // too small? - && ( ( !titleToggleAnimation // title not already hidden? - && titleWidget->maximumHeight() > 0.1 ) - || ( titleToggleAnimation // title not currently animated to be hidden? - && titleToggleAnimation->direction() != QAbstractAnimation::Forward ) ) ) { - // Hide title: The applets vertical size is too small to show it - // and the title is not already hidden or currently being faded out - if ( titleToggleAnimation ) { - delete titleToggleAnimation; - } - - // Create toggle animation with direction forward - // to indicate that the title gets hidden - titleToggleAnimation = new QParallelAnimationGroup( q ); - titleToggleAnimation->setDirection( QAbstractAnimation::Forward ); - - Plasma::Animation *fadeAnimation = Plasma::Animator::create( - Plasma::Animator::FadeAnimation, titleToggleAnimation ); - fadeAnimation->setTargetWidget( titleWidget ); - fadeAnimation->setProperty( "startOpacity", titleWidget->opacity() ); - fadeAnimation->setProperty( "targetOpacity", 0.0 ); - - QPropertyAnimation *shrinkAnimation = new QPropertyAnimation( - titleWidget, "maximumSize", titleToggleAnimation ); - shrinkAnimation->setStartValue( QSizeF( titleWidget->maximumWidth(), - titleWidget->layout()->preferredHeight() ) ); - shrinkAnimation->setEndValue( QSizeF( titleWidget->maximumWidth(), 0 ) ); - - q->connect( titleToggleAnimation, SIGNAL(finished()), - q, SLOT(titleToggleAnimationFinished()) ); - titleToggleAnimation->addAnimation( fadeAnimation ); - titleToggleAnimation->addAnimation( shrinkAnimation ); - titleToggleAnimation->start(); - } else if ( size.height() >= maxHeightWithoutTitle // big enough? - && ( ( !titleToggleAnimation // title not already shown? - && titleWidget->maximumHeight() < titleWidget->layout()->preferredHeight() ) - || ( titleToggleAnimation // title not currently animated to be shown? - && titleToggleAnimation->direction() != QAbstractAnimation::Backward ) ) ) { - // Show title: The applets vertical size is big enough to show it - // and the title is not already shown or currently beging faded in - if ( titleToggleAnimation ) { - delete titleToggleAnimation; - } - - // Create toggle animation with direction backward - // to indicate that the title gets shown again. - // The child animations use reversed start/end values. - titleToggleAnimation = new QParallelAnimationGroup( q ); - titleToggleAnimation->setDirection( QAbstractAnimation::Backward ); - - Plasma::Animation *fadeAnimation = Plasma::Animator::create( - Plasma::Animator::FadeAnimation, titleToggleAnimation ); - fadeAnimation->setTargetWidget( titleWidget ); - fadeAnimation->setProperty( "targetOpacity", titleWidget->opacity() ); - fadeAnimation->setProperty( "startOpacity", 1.0 ); - - QPropertyAnimation *growAnimation = new QPropertyAnimation( - titleWidget, "maximumSize", titleToggleAnimation ); - growAnimation->setEndValue( QSizeF( titleWidget->maximumWidth(), - titleWidget->maximumHeight() ) ); - growAnimation->setStartValue( QSizeF( titleWidget->maximumWidth(), - titleWidget->layout()->preferredHeight() ) ); - - q->connect( titleToggleAnimation, SIGNAL(finished()), - q, SLOT(titleToggleAnimationFinished()) ); - titleToggleAnimation->addAnimation( fadeAnimation ); - titleToggleAnimation->addAnimation( growAnimation ); - titleToggleAnimation->start(); - } - - // Show/hide vertical scrollbar - const qreal minWidthWithScrollBar = 250.0; - const qreal maxWidthWithoutScrollBar = 275.0; - if ( size.width() <= minWidthWithScrollBar ) { - timetable->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - } else if ( size.width() >= maxWidthWithoutScrollBar ) { - timetable->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); - } - - // Update quick journey search widget (show icon or icon with text) - Plasma::ToolButton *quickJourneySearchWidget = - titleWidget->castedWidget( TitleWidget::WidgetQuickJourneySearch ); - Plasma::ToolButton *filterWidget = - titleWidget->castedWidget( TitleWidget::WidgetFilter ); - if ( quickJourneySearchWidget ) { - if ( titleWidget->layout()->preferredWidth() > size.width() ) { - // Show only an icon on the quick journey search toolbutton, - // if there is not enough horizontal space - quickJourneySearchWidget->nativeWidget()->setToolButtonStyle( Qt::ToolButtonIconOnly ); - quickJourneySearchWidget->setMaximumWidth( quickJourneySearchWidget->size().height() ); - } else if ( quickJourneySearchWidget->nativeWidget()->toolButtonStyle() == Qt::ToolButtonIconOnly - && size.width() > titleWidget->layout()->minimumWidth() + - QFontMetrics( quickJourneySearchWidget->font() ).width( quickJourneySearchWidget->text() ) + - ( filterWidget->nativeWidget()->toolButtonStyle() == Qt::ToolButtonIconOnly - ? QFontMetrics( filterWidget->font() ).width( filterWidget->text() ) : 0 ) + 60 ) { - // Show the icon with text beside if there is enough horizontal space again - quickJourneySearchWidget->nativeWidget()->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); - quickJourneySearchWidget->setMaximumWidth( -1 ); - } - } - - // Update filter widget (show icon or icon with text) - if ( filterWidget ) { - if ( titleWidget->layout()->preferredWidth() > size.width() ) { - // Show only an icon on the filter toolbutton, - // if there is not enough horizontal space - filterWidget->nativeWidget()->setToolButtonStyle( Qt::ToolButtonIconOnly ); - filterWidget->setMaximumWidth( filterWidget->size().height() ); - } else if ( filterWidget->nativeWidget()->toolButtonStyle() == Qt::ToolButtonIconOnly - && size.width() > titleWidget->layout()->minimumWidth() + - QFontMetrics( filterWidget->font() ).width( filterWidget->text() ) + 60 ) { - // Show the icon with text beside if there is enough horizontal space again - filterWidget->nativeWidget()->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); - filterWidget->setMaximumWidth( -1 ); - } - } - } - - // Update line breaking of the courtesy label - updateInfoText(); -} - -void PublicTransportAppletPrivate::onOldItemAnimationFinished() -{ - if ( oldItem && oldItem->scene() ) { - oldItem->scene()->removeItem( oldItem ); - } - delete oldItem; - oldItem = 0; -} - -void PublicTransportAppletPrivate::updateInfoText() -{ - labelInfo->setText( infoText() ); - labelInfo->setToolTip( infoTooltip() ); -} - -void PublicTransportAppletPrivate::applyTheme() -{ - Q_Q( PublicTransportApplet ); - // Get theme colors - QColor textColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::TextColor ); - - // Create palette with the used theme colors - QPalette p = q->palette(); - p.setColor( QPalette::Background, Qt::transparent ); - p.setColor( QPalette::Base, Qt::transparent ); - p.setColor( QPalette::Button, Qt::transparent ); - p.setColor( QPalette::Foreground, textColor ); - p.setColor( QPalette::Text, textColor ); - p.setColor( QPalette::ButtonText, textColor ); - - QColor bgColor = KColorScheme( QPalette::Active ) - .background( KColorScheme::AlternateBackground ).color(); - bgColor.setAlpha( bgColor.alpha() / 3 ); - QLinearGradient bgGradient( 0, 0, 1, 0 ); - bgGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); - bgGradient.setColorAt( 0, Qt::transparent ); - bgGradient.setColorAt( 0.3, bgColor ); - bgGradient.setColorAt( 0.7, bgColor ); - bgGradient.setColorAt( 1, Qt::transparent ); - QBrush brush( bgGradient ); - p.setBrush( QPalette::AlternateBase, brush ); - - timetable->setPalette( p ); - - // To set new text color of the header items - model->setDepartureArrivalListType( settings.departureArrivalListType() ); - timetable->updateItemLayouts(); -} - -void PublicTransportAppletPrivate::createTooltip() -{ - Q_Q( PublicTransportApplet ); - - if ( q->formFactor() != Plasma::Horizontal && q->formFactor() != Plasma::Vertical ) { - // Create the tooltip only when in a panel - Plasma::ToolTipManager::self()->clearContent( q ); - return; - } - - Plasma::ToolTipContent data; - data.setMainText( i18nc( "@info", "Public Transport" ) ); - if ( popupIcon->departureGroups()->isEmpty() ) { - data.setSubText( i18nc( "@info", "View departure times for public transport" ) ); - } else if ( !popupIcon->departureGroups()->isEmpty() ) { - const DepartureGroup currentGroup = popupIcon->currentDepartureGroup(); - if ( currentGroup.isEmpty() ) { - kDebug() << "Empty group for popup icon!"; - return; - } - const bool isAlarmGroup = popupIcon->currentGroupIsAlarmGroup(); - const QString groupDurationString = currentGroup.first()->departureInfo()->durationString(); - QStringList infoStrings; - - if ( settings.departureArrivalListType() == DepartureList ) { - // Showing a departure list - foreach( const DepartureItem * item, currentGroup ) { - infoStrings << i18nc( "@info Text for one departure for the tooltip (%1: line string, " - "%2: target)", - "Line %1 " - "to %2", - item->departureInfo()->lineString(), - item->departureInfo()->target() ); - } - if ( isAlarmGroup ) { - data.setSubText( i18ncp( "@info %2 is the translated duration text (e.g. in 3 minutes), " - "%4 contains texts for a list of departures", - "Alarm (%2) for a departure from '%3':%4", - "%1 Alarms (%2) for departures from '%3':%4", - currentGroup.count(), - groupDurationString, settings.currentStop().stops().join( ", " ), - infoStrings.join( "," ) ) ); - } else { - data.setSubText( i18ncp( "@info %2 is the translated duration text (e.g. in 3 minutes), " - "%4 contains texts for a list of departures", - "Departure (%2) from '%3':%4", - "%1 Departures (%2) from '%3':%4", - currentGroup.count(), - groupDurationString, settings.currentStop().stops().join( ", " ), - infoStrings.join( "," ) ) ); - } - } else { - // Showing an arrival list - foreach( const DepartureItem * item, currentGroup ) { - infoStrings << i18nc( "@info Text for one arrival for the tooltip (%1: line string, " - "%2: origin)", - "Line %1 " - "from %2", - item->departureInfo()->lineString(), - item->departureInfo()->target() ); - } - if ( isAlarmGroup ) { - data.setSubText( i18ncp( "@info %2 is the translated duration text (e.g. in 3 minutes), " - "%4 contains texts for a list of arrivals", - "Alarm (%2) for an arrival at '%3':%4", - "%1 Alarms (%2) for arrivals at '%3':%4", - currentGroup.count(), - groupDurationString, settings.currentStop().stops().join( ", " ), - infoStrings.join( "," ) ) ); - } else { - data.setSubText( i18ncp( "@info %2 is the translated duration text (e.g. in 3 minutes), " - "%4 contains texts for a list of arrivals", - "Arrival (%2) at '%3':%4", - "%1 Arrivals (%2) at '%3':%4", - currentGroup.count(), - groupDurationString, settings.currentStop().stops().join( ", " ), - infoStrings.join( "," ) ) ); - } - } - } - - data.setImage( KIcon( "public-transport-stop" ).pixmap( IconSize( KIconLoader::Desktop ) ) ); - Plasma::ToolTipManager::self()->setContent( q, data ); -} - -QString PublicTransportAppletPrivate::stripDateAndTimeValues( const QString &sourceName ) const -{ - QString ret = sourceName; - QRegExp rx( "(time=[^\\|]*|datetime=[^\\|]*)", Qt::CaseInsensitive ); - rx.setMinimal( true ); - ret.replace( rx, QChar() ); - return ret; -} - -void PublicTransportAppletPrivate::fillModel( const QList &departures ) -{ - bool modelFilled = model->rowCount() >= settings.maximalNumberOfDepartures(); - foreach( const DepartureInfo &departureInfo, departures ) { - QModelIndex index = model->indexFromInfo( departureInfo ); - if ( !index.isValid() ) { - // Departure wasn't in the model - if ( !modelFilled && !departureInfo.isFilteredOut() ) { - // Departure doesn't get filtered out and the model isn't full => Add departure - model->addItem( departureInfo ); - modelFilled = model->rowCount() >= settings.maximalNumberOfDepartures(); - } - } else if ( departureInfo.isFilteredOut() ) { - // Departure has been marked as "filtered out" in the DepartureProcessor => Remove departure - model->removeItem( model->itemFromInfo( departureInfo ) ); - } else { - // Departure isn't filtered out => Update associated item in the model - DepartureItem *item = dynamic_cast< DepartureItem* >( model->itemFromIndex(index) ); - model->updateItem( item, departureInfo ); - } - } - - // Sort departures in the model. - // They are most probably already sorted, but sometimes they are not - model->sort( ColumnDeparture ); -} - -void PublicTransportAppletPrivate::fillModelJourney( const QList &journeys ) -{ - foreach( const JourneyInfo & journeyInfo, journeys ) { - int row = modelJourneys->indexFromInfo( journeyInfo ).row(); - if ( row == -1 ) { - // Journey wasn't in the model - modelJourneys->addItem( journeyInfo ); - } else { - // Update associated item in the model - JourneyItem *item = static_cast( modelJourneys->itemFromInfo( journeyInfo ) ); - modelJourneys->updateItem( item, journeyInfo ); - } - } - - // Sort departures in the model. - // They are most probably already sorted, but sometimes they are not - modelJourneys->sort( ColumnDeparture ); -} - -void PublicTransportAppletPrivate::updateFilterMenu() -{ - Q_Q( PublicTransportApplet ); - - KActionMenu *actionFilter = qobject_cast< KActionMenu * >( q->action( "filterConfiguration" ) ); - KMenu *menu = actionFilter->menu(); - menu->clear(); - - QList< QAction * > oldActions = filtersGroup->actions(); - foreach( QAction * oldAction, oldActions ) { - filtersGroup->removeAction( oldAction ); - delete oldAction; - } - - bool showColorGrous = settings.colorize() && !settings.colorGroups().isEmpty(); - if ( settings.filters().isEmpty() && !showColorGrous ) { - return; // Nothing to show in the filter menu - } - - if ( !settings.filters().isEmpty() ) { - menu->addTitle( KIcon( "view-filter" ), i18nc( "@title This is a menu title", - "Filters (reducing)" ) ); - foreach( const FilterSettings &filters, settings.filters() ) { - QAction *action = new QAction( filters.name, filtersGroup ); - action->setCheckable( true ); - if ( filters.affectedStops.contains( settings.currentStopIndex() ) ) { - action->setChecked( true ); - } - - menu->addAction( action ); - } - } - - if ( showColorGrous ) { - // Add checkbox entries to toggle color groups - if ( settings.departureArrivalListType() == ArrivalList ) { - menu->addTitle( KIcon( "object-group" ), i18nc( "@title This is a menu title", - "Arrival Groups (extending)" ) ); - } else { - menu->addTitle( KIcon( "object-group" ), i18nc( "@title This is a menu title", - "Departure Groups (extending)" ) ); - } - foreach( const ColorGroupSettings & colorGroup, - settings.currentColorGroups() ) { - // Create action for current color group - QAction *action = new QAction( colorGroup.displayText, colorFiltersGroup ); - action->setCheckable( true ); - if ( !colorGroup.filterOut ) { - action->setChecked( true ); - } - action->setData( QVariant::fromValue( colorGroup.color ) ); - - // Draw a color patch with the color of the color group - QPixmap pixmap( QSize( 16, 16 ) ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - p.setRenderHints( QPainter::Antialiasing ); - p.setBrush( colorGroup.color ); - QColor borderColor = KColorScheme( QPalette::Active ).foreground().color(); - borderColor.setAlphaF( 0.75 ); - p.setPen( borderColor ); - p.drawRoundedRect( QRect( QPoint( 1, 1 ), pixmap.size() - QSize( 2, 2 ) ), 4, 4 ); - p.end(); - - // Put the pixmap into a KIcon - KIcon colorIcon; - colorIcon.addPixmap( pixmap ); - action->setIcon( colorIcon ); - - menu->addAction( action ); - } - } -} - -void PublicTransportAppletPrivate::updateJourneyMenu() -{ - Q_Q( PublicTransportApplet ); - - KActionMenu *journeysAction = qobject_cast( q->action( "journeys" ) ); - KMenu *menu = journeysAction->menu(); - menu->clear(); - - // Add action to go to journey search view - // Do not add a separator after it, because a menu title item follows - menu->addAction( q->action( "searchJourneys" ) ); - - // Extract lists of journey search strings / names - QStringList favoriteJourneySearchNames; - QStringList favoriteJourneySearches; - QStringList recentJourneySearchNames; - QStringList recentJourneySearches; - foreach( const JourneySearchItem & item, settings.currentJourneySearches() ) { - if ( item.isFavorite() ) { - favoriteJourneySearches << item.journeySearch(); - favoriteJourneySearchNames << item.nameOrJourneySearch(); - } else { - recentJourneySearches << item.journeySearch(); - recentJourneySearchNames << item.nameOrJourneySearch(); - } - } - - // Add favorite journey searches - if ( !favoriteJourneySearches.isEmpty() ) { - menu->addTitle( KIcon( "favorites" ), - i18nc( "@title Title item in quick journey search menu", - "Favorite Journey Searches" ) ); - QList< QAction * > actions; - KIcon icon( "edit-find", 0, QStringList() << "favorites" ); - for( int i = 0; i < favoriteJourneySearches.count(); ++i ) { - KAction *action = new KAction( icon, favoriteJourneySearchNames[i], menu ); - action->setData( favoriteJourneySearches[i] ); - actions << action; - } - menu->addActions( actions ); - } - - // Add recent journey searches - if ( !recentJourneySearches.isEmpty() ) { - menu->addTitle( KIcon( "document-open-recent" ), - i18nc( "@title Title item in quick journey search menu", - "Recent Journey Searches" ) ); - QList< QAction * > actions; - KIcon icon( "edit-find" ); - for( int i = 0; i < recentJourneySearches.count(); ++i ) { - KAction *action = new KAction( icon, recentJourneySearchNames[i], menu ); - action->setData( recentJourneySearches[i] ); - actions << action; - } - menu->addActions( actions ); - } - - // Add a separator before the configure action - menu->addSeparator(); - - // Add the configure action, which is distinguishable from others by having no data - menu->addAction( q->action( "configureJourneys" ) ); -} - -QList PublicTransportAppletPrivate::mergedDepartureList( bool includeFiltered, - int max ) const -{ - QList< DepartureInfo > ret; - - for( int n = stopIndexToSourceName.count() - 1; n >= 0; --n ) { - QString sourceName = stripDateAndTimeValues( stopIndexToSourceName[n] ); - if ( departureInfos.contains( sourceName ) ) { - foreach( const DepartureInfo & departureInfo, departureInfos[sourceName] ) { - // Only add not filtered items - if ( !departureInfo.isFilteredOut() || includeFiltered ) { - ret << departureInfo; - } - } - } - } - - qSort( ret.begin(), ret.end() ); - return max == -1 ? ret.mid( 0, settings.maximalNumberOfDepartures() ) : ret.mid( 0, max ); -} - -void PublicTransportAppletPrivate::reconnectSource() -{ - Q_Q( PublicTransportApplet ); - - // Get current stop data - StopSettings stopSettings = settings.currentStop(); - if ( stopSettings.stopList().isEmpty() ) { - // Currently no stops configured - return; - } - - // Build source names for each (combined) stop for the publictransport data engine - const QString providerId = stopSettings.get< QString >( ServiceProviderSetting ); - const QString city = stopSettings.get< QString >( CitySetting ); - const FirstDepartureConfigMode firstDepartureMode = static_cast< FirstDepartureConfigMode >( - stopSettings.get(FirstDepartureConfigModeSetting) ); - QStringList sources; - stopIndexToSourceName.clear(); - const StopList stopList = stopSettings.stopList(); - for ( int i = 0; i < stopList.count(); ++i ) { - QString currentSource = QString( "%3 %1|count=%2" ) - .arg( providerId ).arg( settings.maximalNumberOfDepartures() ) - .arg( settings.departureArrivalListType() == ArrivalList - ? "Arrivals" : "Departures" ); - - const Stop &stop = stopList[i]; - if ( stop.id.isEmpty() ) { - currentSource += "|stop=" + stop.name; - } else { - currentSource += "|stopid=" + stop.id; - } - - switch ( firstDepartureMode ) { - case RelativeToCurrentTime: - currentSource += "|timeoffset=" + - stopSettings.get(TimeOffsetOfFirstDepartureSetting); - break; - case AtCustomTime: - currentSource += "|time=" + - stopSettings.get(TimeOfFirstDepartureSetting).toString("hh:mm"); - break; - default: - kWarning() << "Unknown FirstDepartureConfigMode" << firstDepartureMode; - break; - } - - if ( !city.isEmpty() ) { - currentSource += "|city=" + city; - } - - stopIndexToSourceName[ i ] = currentSource; - sources << currentSource; - } - - if ( sources == currentSources ) { - // Sources did not change - return; - } - - QStringList previousSources = currentSources; - currentSources.clear(); - - // Notify that new departure/arrival data gets requested now - emit q->requestedNewDepartureData(); - - // Connect all data sources, normally this is only one source for departures/arrivals - // from one stop, but departures/arrivals from multiple stops can be displayed combined - // in the applet, to do so for each stop one data source gets connected - foreach( const QString ¤tSource, sources ) { - currentSources << currentSource; - - // Do not connect with a polling interval, because this would cause updates to be received - // later, ie. when the interval has finished. Instead let the data engine push new data - // when available and let it decide itself when to update timetable data for connected - // sources. Manual updates are possible through the timetable service. - if ( !previousSources.removeOne(currentSource) ) { - // Source is not connected - kDebug() << "Connect data source" << currentSource; - q->dataEngine( "publictransport" )->connectSource( currentSource, q ); - } - } - - // Disconnect no longer used sources - foreach( const QString &previousSource, previousSources ) { - kDebug() << "Disconnect data source" << previousSource; - q->dataEngine( "publictransport" )->disconnectSource( previousSource, q ); - } -} - -void PublicTransportAppletPrivate::disconnectSources() -{ - Q_Q( PublicTransportApplet ); - if ( !currentSources.isEmpty() ) { - foreach( const QString ¤tSource, currentSources ) { - kDebug() << "Disconnect data source" << currentSource; - q->dataEngine( "publictransport" )->disconnectSource( currentSource, q ); - } - currentSources.clear(); - } -} - -void PublicTransportAppletPrivate::disconnectJourneySource() -{ - Q_Q( PublicTransportApplet ); - if ( !currentJourneySource.isEmpty() ) { - kDebug() << "Disconnect journey data source" << currentJourneySource; - q->dataEngine( "publictransport" )->disconnectSource( currentJourneySource, q ); - } -} - -bool PublicTransportAppletPrivate::isStateActive( const QString &stateName ) const -{ - return states.contains( stateName ) - && stateMachine->configuration().contains( states[stateName] ); -} - -void PublicTransportAppletPrivate::reconnectJourneySource( const QString &targetStopName, - const QDateTime &dateTime, bool stopIsTarget, bool timeIsDeparture, - bool requestStopSuggestions ) -{ - Q_Q( PublicTransportApplet ); - - disconnectJourneySource(); - - QString _targetStopName = targetStopName; - QDateTime _dateTime = dateTime; - if ( _targetStopName.isEmpty() ) { - if ( lastSecondStopName.isEmpty() ) { - return; - } - _targetStopName = lastSecondStopName; - } - if ( !_dateTime.isValid() ) { - _dateTime = lastJourneyDateTime; - } - - // Build a source name for the publictransport data engine - if ( requestStopSuggestions ) { - currentJourneySource = QString( "Stops %1|stop=%2" ) - .arg( settings.currentStop().get(ServiceProviderSetting) ) - .arg( _targetStopName ); - } else { - // Get current stop data - StopSettings stopSettings = settings.currentStop(); - if ( stopSettings.stopList().isEmpty() ) { - // Currently no stops configured - return; - } - - currentJourneySource = QString( stopIsTarget ? "%4 %1|targetstop=%2|datetime=%3" - : "%4 %1|originstop=%2|datetime=%3" ) - .arg( settings.currentStop().get(ServiceProviderSetting) ) - .arg( _targetStopName ) - .arg( _dateTime.toString(Qt::ISODate) ) - .arg( timeIsDeparture ? "Journeys" : "JourneysArr" ); - - const Stop stop = stopSettings.stop( 0 ); - if ( stop.id.isEmpty() ) { - currentJourneySource += (stopIsTarget ? "|originstop=" : "|targetstop=") + stop.name; - } else { - currentJourneySource += (stopIsTarget ? "|originstopid=" : "|targetstopid=") + stop.id; - } - - QString currentStop = settings.currentStop().stops().first(); - journeyTitleText = stopIsTarget - ? i18nc( "@info", "From %1to %2", - currentStop, _targetStopName ) - : i18nc( "@info", "From %1to %2", - _targetStopName, currentStop ); - if ( isStateActive( "journeyView" ) ) { - titleWidget->setTitle( journeyTitleText ); - } - } - - if ( !settings.currentStop().get(CitySetting).isEmpty() ) { - currentJourneySource += QString( "|city=%1" ).arg( - settings.currentStop().get(CitySetting) ); - } - - lastSecondStopName = _targetStopName; - emit q->requestedNewJourneyData(); - q->dataEngine( "publictransport" )->connectSource( currentJourneySource, q ); -} - -void PublicTransportAppletPrivate::updateColorGroupSettings() -{ - Q_Q( PublicTransportApplet ); - if ( settings.colorize() ) { - // Generate color groups from existing departure data - settings.adjustColorGroupSettingsCount(); - ColorGroupSettingsList colorGroups = settings.currentColorGroups(); - ColorGroupSettingsList newColorGroups = ColorGroups::generateColorGroupSettingsFrom( - mergedDepartureList(true, 40), settings.departureArrivalListType() ); - if ( colorGroups != newColorGroups ) { - // Copy filterOut values from old color group settings - for( int i = 0; i < newColorGroups.count(); ++i ) { - ColorGroupSettings &newColorGroup = newColorGroups[i]; - if ( colorGroups.hasColor(newColorGroup.color) ) { - ColorGroupSettings colorGroup = colorGroups.byColor( newColorGroup.color ); - newColorGroup.filterOut = colorGroup.filterOut; - } - } - model->setColorGroups( newColorGroups ); - departureProcessor->setColorGroups( newColorGroups ); - - // Change color group settings in a copy of the Settings object - // Then write the changed settings - Settings newSettings = settings; - QList< ColorGroupSettingsList > colorGroups = newSettings.colorGroups(); - colorGroups[ newSettings.currentStopIndex() ] = newColorGroups; - newSettings.setColorGroups( colorGroups ); - q->setSettings( newSettings ); - } - } else { - // Remove color groups if colorization was toggled off - // or if stop/filter settings were changed (update color groups after data arrived) - model->setColorGroups( ColorGroupSettingsList() ); - departureProcessor->setColorGroups( ColorGroupSettingsList() ); - } -} - -void PublicTransportAppletPrivate::updateDepartureListIcon() -{ - if ( isStateActive( "intermediateDepartureView" ) ) { - titleWidget->setIcon( GoBackIcon ); - } else { - titleWidget->setIcon( isStateActive( "departureDataValid" ) - ? DepartureListOkIcon : DepartureListErrorIcon ); - } -} - -QString PublicTransportAppletPrivate::courtesyToolTip() const -{ - // Get courtesy information for the current service provider from the data engine - QVariantHash data = currentProviderData; - QString credit; - if ( !data.isEmpty() ) { - credit = data["credit"].toString(); - } - - if ( credit.isEmpty() ) { - // No courtesy information given by the data engine - return QString(); - } else { - return credit; - } -} - -QString PublicTransportAppletPrivate::infoText() -{ - // Get information about the current service provider from the data engine - const QVariantHash data = currentProviderData; - const QString shortUrl = data.isEmpty() ? "-" : data["shortUrl"].toString(); - const QString url = data.isEmpty() ? "-" : data["url"].toString(); - QString credit = data["credit"].toString(); - const QSizeF size = graphicsWidget->size(); - const QFontMetrics fm( labelInfo->font() ); - - if ( !credit.isEmpty() ) { - // Credit string available, show it as link to the provider home page - return QString("%2") - .arg(url, fm.elidedText(credit, Qt::ElideMiddle, size.width() - 2)); - } else { - // No credit string available, show a link to the provider home page. - // If there is enough space, also show a label. - const QString labelText = i18nc( "@info/plain", "Data by" ); - const QString labelTextLong = i18nc( "@info/plain", "Timetable data by" ); - const QString linkHtml = QString( "%2" ).arg( url, shortUrl ); - const QString html = labelText + ": " + linkHtml; - const QString htmlLong = labelTextLong + ": " + linkHtml; - const int width = fm.width( labelText + ": " + shortUrl ); - const int widthLong = fm.width( labelTextLong + ": " + shortUrl ); - if ( size.width() >= widthLong ) { - // Enough horizontal space to show the longer label - return htmlLong; - } else if ( size.width() >= width ) { - // Enough horizontal space to show the shorter label - return html; - } else { - // Not enough horizontal space for the label, only show the link - return linkHtml; - } - } -} - -QString PublicTransportAppletPrivate::infoTooltip() -{ - QString tooltip = courtesyToolTip(); - if ( !nextAutomaticSourceUpdate.isValid() ) { - return tooltip; - } - - // Add information about the next automatic update time (with minute precision) - qint64 msecs = QDateTime::currentDateTime().msecsTo( nextAutomaticSourceUpdate ); - if ( msecs > 0 ) { - if ( !tooltip.isEmpty() ) { - tooltip += ", "; - } - tooltip += i18nc("@info:tooltip %1 is a duration string with minute precision, " - "as returned by KLocale::prettyFormatDuration()", - "next automatic update in %1", - KGlobal::locale()->prettyFormatDuration(qCeil(msecs / 60000.0) * 60000)); - } - - // Add information about the minimal next (malnual) update time (with second precision) - if ( minManualSourceUpdateTime.isValid() ) { - qint64 minMsecs = QDateTime::currentDateTime().msecsTo( minManualSourceUpdateTime ); - if ( minMsecs > 0 ) { - if ( !tooltip.isEmpty() ) { - tooltip += ", "; - } - tooltip += i18nc("@info:tooltip %1 is a duration string with second precision, " - "as returned by KLocale::prettyFormatDuration()", - "updates blocked for %1", - KGlobal::locale()->prettyFormatDuration(qCeil(minMsecs / 1000.0) * 1000)); - } - } - - QString sLastUpdate = lastSourceUpdate.toString( "hh:mm" ); - if ( sLastUpdate.isEmpty() ) { - sLastUpdate = i18nc( "@info/plain This is used as 'last data update' " - "text when there hasn't been any updates yet.", "none" ); - } - const QString dataByTextLocalized = i18nc( "@info/plain", "data by" ); - if ( !tooltip.isEmpty() ) { - tooltip += ", "; - } - tooltip += i18nc("@info/plain", "last update: %1", sLastUpdate ); - return tooltip; -} - -Plasma::Animation *PublicTransportAppletPrivate::fadeOutOldAppearance() -{ - Q_Q( PublicTransportApplet ); - - if ( q->isVisible() && stateMachine ) { - // Draw old appearance to pixmap - QPixmap pixmap( mainGraphicsWidget->size().toSize() ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - QRect sourceRect = mainGraphicsWidget->mapToScene( mainGraphicsWidget->boundingRect() ) - .boundingRect().toRect(); - QRectF rect( QPointF( 0, 0 ), mainGraphicsWidget->size() ); - titleWidget->scene()->render( &p, rect, sourceRect ); - - // Fade from old to new appearance - onOldItemAnimationFinished(); - oldItem = new GraphicsPixmapWidget( pixmap, graphicsWidget ); - oldItem->setPos( 0, 0 ); - oldItem->setZValue( 1000 ); - Plasma::Animation *animOut = Plasma::Animator::create( Plasma::Animator::FadeAnimation ); - animOut->setProperty( "startOpacity", 1 ); - animOut->setProperty( "targetOpacity", 0 ); - animOut->setTargetWidget( oldItem ); - q->connect( animOut, SIGNAL(finished()), q, SLOT(oldItemAnimationFinished()) ); - animOut->start( QAbstractAnimation::DeleteWhenStopped ); - return animOut; - } else { - return 0; - } -} - -KSelectAction *PublicTransportAppletPrivate::createSwitchStopAction( QObject *parent, bool destroyOverlayOnTrigger ) const -{ - Q_Q( const PublicTransportApplet ); - - KSelectAction *switchStopAction = new KSelectAction( - KIcon( "public-transport-stop" ), i18nc( "@action", "Switch Current Stop" ), parent ); - for( int i = 0; i < settings.stops().count(); ++i ) { - QString stopList = settings.stop( i ).stops().join( ",\n" ); - QString stopListShort = settings.stop( i ).stops().join( ", " ); - if ( stopListShort.length() > 30 ) { - stopListShort = stopListShort.left( 30 ).trimmed() + "..."; - } - - // Use a shortened stop name list as display text - // and the complete version as tooltip (if it is different) - QAction *stopAction = new QAction( stopListShort, parent ); - if ( stopList != stopListShort ) { - stopAction->setToolTip( stopList ); - } - stopAction->setData( i ); - if ( destroyOverlayOnTrigger ) { - q->connect( stopAction, SIGNAL(triggered()), q->action("backToDepartures"), - SLOT(trigger()) ); - } - - stopAction->setCheckable( true ); - stopAction->setChecked( i == settings.currentStopIndex() ); - switchStopAction->addAction( stopAction ); - } - - q->connect( switchStopAction, SIGNAL(triggered(QAction*)), - q, SLOT(setCurrentStopIndex(QAction*)) ); - return switchStopAction; -} - -void PublicTransportAppletPrivate::onDepartureDataStateChanged() -{ - Q_Q( PublicTransportApplet ); - QString noItemsText; - bool busy = false; - - if ( isStateActive("providerError") ) { - // The used provider has an error or is not ready - if ( currentProviderData["error"].toBool() ) { - noItemsText = currentProviderData["errorMessage"].toString(); - } else if ( currentProviderData["state"].toString() != QLatin1String("ready") ) { - noItemsText = currentProviderData["stateData"].toHash() - .value("statusMessage").toString(); - } else { - // Unknown error, use the same string like for the departureDataInvalid state - noItemsText = settings.departureArrivalListType() == ArrivalList - ? i18nc("@info/plain", "No arrivals due to an error.") - : i18nc("@info/plain", "No departures due to an error."); - } - } else if ( isStateActive("departureDataWaiting") ) { - if ( settings.departureArrivalListType() == ArrivalList ) { - noItemsText = i18nc("@info/plain", "Waiting for arrivals..."); - } else { - noItemsText = i18nc("@info/plain", "Waiting for departures..."); - } - busy = model->isEmpty(); - } else if ( isStateActive("departureDataInvalid") ) { - noItemsText = settings.departureArrivalListType() == ArrivalList - ? i18nc("@info/plain", "No arrivals due to an error.") - : i18nc("@info/plain", "No departures due to an error."); - } else if ( settings.departureArrivalListType() == ArrivalList ) { - // Valid arrivals - noItemsText = !settings.currentFilters().isEmpty() - ? i18nc("@info/plain", "No unfiltered arrivals." - "You can disable filters to see all arrivals.") - : i18nc("@info/plain", "No arrivals."); - } else { // Valid departures - noItemsText = !settings.currentFilters().isEmpty() - ? i18nc("@info/plain", "No unfiltered departures." - "You can disable filters to see all departures.") - : i18nc("@info/plain", "No departures."); - } - - updateDepartureListIcon(); - timetable->setNoItemsText( noItemsText ); - q->setBusy( busy ); -} - -void PublicTransportAppletPrivate::onJourneyDataStateChanged() -{ - Q_Q( PublicTransportApplet ); - if ( isStateActive("journeyView") ) { - MainIconDisplay icon; - QString noItemsText; - bool busy = false; - - if ( isStateActive("journeyDataWaiting") ) { - icon = JourneyListErrorIcon; - noItemsText = i18nc("@info/plain", "Waiting for journeys..."); - busy = modelJourneys->isEmpty(); - } else if ( isStateActive("journeyDataInvalid") ) { - icon = JourneyListErrorIcon; - noItemsText = i18nc("@info/plain", "No journeys due to an error."); - } else { - icon = JourneyListOkIcon; - noItemsText = i18nc("@info/plain", "No journeys."); - } - - titleWidget->setIcon( icon ); - journeyTimetable->setNoItemsText( noItemsText ); - q->setBusy( busy ); - } -} - -StopDataConnection::StopDataConnection( Plasma::DataEngine *engine, const QString &providerId, - const QString &stopName, QObject *parent ) - : QObject(parent) -{ - const QString sourceName = QString( "Stops %1|stop=%2" ).arg( providerId, stopName ); - engine->connectSource( sourceName, this ); -} - -void StopDataConnection::dataUpdated( const QString &sourceName, - const Plasma::DataEngine::Data &data ) -{ - Q_UNUSED( sourceName ); - if ( !data.contains("stops") ) { - kWarning() << "Stop coordinates not found"; - } else { - const QVariantHash stop = data[ "stops" ].toList().first().toHash(); // TODO Error handling - const QString stopName = stop[ "StopName" ].toString(); - if ( stop.contains("StopLongitude") && stop.contains("StopLatitude") ) { - const bool coordinatesAreValid = stop.contains("StopLongitude") && - stop.contains("StopLatitude"); - const qreal longitude = coordinatesAreValid ? stop["StopLongitude"].toReal() : 0.0; - const qreal latitude = coordinatesAreValid ? stop["StopLatitude"].toReal() : 0.0; - emit stopDataReceived( stopName, coordinatesAreValid, longitude, latitude ); - } - - emit stopDataReceived( stop ); - } - - // Automatically delete after data was received - deleteLater(); -} diff --git a/applet/publictransport_p.h b/applet/publictransport_p.h deleted file mode 100644 index 431f3dd..0000000 --- a/applet/publictransport_p.h +++ /dev/null @@ -1,677 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef PUBLICTRANSPORT_PRIVATE_HEADER -#define PUBLICTRANSPORT_PRIVATE_HEADER - -#define NO_EXPORT_PLASMA_APPLET - -// Own includes -#include "publictransport.h" -#include "departureprocessor.h" -#include "settingsio.h" -#include "departurepainter.h" -#include "departuremodel.h" -#include "titlewidget.h" -#include "timetablewidget.h" -#include "popupicon.h" - -// libpublictransporthelper includes -#include - -// Plasma includes -#include -#include - -// KDE includes -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include -#include -#include - -class MarbleProcess; -class OverlayWidget; -class JourneySearchSuggestionWidget; -class JourneyTimetableWidget; -class TimetableWidget; -class TitleWidget; -class GraphicsPixmapWidget; - -namespace Plasma -{ - class Animation; -} -class KSelectAction; - -class QActionGroup; -class QParallelAnimationGroup; -class QGraphicsWidget; -class QAbstractTransition; - -class ToPropertyTransition : public QSignalTransition -{ -public: - ToPropertyTransition( QObject *sender, const char *signal, QState *source, - QObject *propertyObject, const char *targetStateProperty ); - - const QObject *propertyObject() const { return m_propertyObject; }; - const char *targetStateProperty() const { return m_property; }; - QState *currentTargetState() const { - return qobject_cast( qvariant_cast(m_propertyObject->property(m_property)) ); - }; - void setTargetStateProperty( const QObject *propertyObject, const char *property ); - -protected: - virtual bool eventTest( QEvent *event ); - -private: - const QObject *m_propertyObject; - const char *m_property; -}; - -/** @brief Private class for the PublicTransportApplet class. */ -class PublicTransportAppletPrivate { -public: - PublicTransportAppletPrivate( PublicTransportApplet *q ); - -public: // Event handlers - /** @brief Update GUI and logic to new @p _settings, changes are indicated using @p changed. */ - void onSettingsChanged( const Settings& _settings, SettingsIO::ChangedFlags changed ); - - /** @brief Update GUI and action names to new departure arrival list type in current settings. */ - void onDepartureArrivalListTypeChanged(); - - /** @brief Tell the models about new home stop name / currently selected stop settings. */ - void onCurrentStopSettingsChanged(); - - /** @brief Settings that require a new data request have been changed. */ - void onServiceProviderSettingsChanged(); - - /** @brief The applets geometry has changed. */ - void onResized(); - - /** @brief The animation that fades out a pixmap widget with an old shapshot has finished. */ - void onOldItemAnimationFinished(); - - /** @brief The state of the departure data changed between valid/invalid/waiting. */ - void onDepartureDataStateChanged(); - - /** @brief The state of the journey data changed between valid/invalid/waiting. */ - void onJourneyDataStateChanged(); - -public: // Other functions - /** @brief Update the text and tooltip of the label shown in the applet. */ - void updateInfoText(); - - /** @brief Updates the KMenuAction used for the filters action. */ - void updateFilterMenu(); - - /** @brief Updates the KMenuAction used for the journeys menu. */ - void updateJourneyMenu(); - - /** @brief Update the color groups according to the currently shown departures. */ - void updateColorGroupSettings(); - - /** @brief Update the main icon widget shown in the applets title. */ - void updateDepartureListIcon(); - - /** @brief Applies theme properties (colors, fonts, etc.) after a change of the used theme. */ - void applyTheme(); - - /** @brief Generates tooltip data and registers this applet at plasma's TooltipManager. */ - void createTooltip(); - - /** @brief Updated provider @p data was received from the data engine. */ - void providerDataUpdated( const QVariantHash &data ); - - /** @brief Fills the departure data model with the given @p departures. - * - * This will stop if the maximum departure count is reached or if all @p departures - * have been added. - **/ - void fillModel( const QList &departures ); - - /** @brief Fills the journey data model with the given @p journeys. - * - * This will stop if all @p journeys have been added. - **/ - void fillModelJourney( const QList &journeys ); - - /** @brief Gets a list of current departures/arrivals for the selected stop(s). - * - * @param includeFiltered Whether or not to include filtered departures in the returned list. - * @param max -1 to use the maximum departure count from the current settings. Otherwise this - * gets used as the maximal number of departures for the returned list. - **/ - QList< DepartureInfo > mergedDepartureList( bool includeFiltered = false, int max = -1 ) const; - - /** @brief Checks if the state with the given @p stateName is currently active. */ - bool isStateActive( const QString &stateName ) const; - - /** @brief Disconnects a currently connected departure/arrival data source and - * connects a new source using the current configuration. - **/ - void reconnectSource(); - - /** @brief Disconnects a currently connected departure/arrival data source. */ - void disconnectSources(); - - /** @brief Disconnects a currently connected journey data source. */ - void disconnectJourneySource(); - - /** @brief Disconnects a currently connected journey data source and connects - * a new source using the current configuration. - **/ - void reconnectJourneySource( const QString &targetStopName = QString(), - const QDateTime &dateTime = QDateTime::currentDateTime(), - bool stopIsTarget = true, bool timeIsDeparture = true, - bool requestStopSuggestions = false ); - - /** @brief Gets the text to be displayed as tooltip for the info label. */ - QString courtesyToolTip() const; - - /** - * @brief Gets the text to be displayed on the bottom of the timetable. - * Contains courtesy information and is HTML formatted. - **/ - QString infoText(); - - /** @brief Gets the text to be used as tooltip for the bottom label. */ - QString infoTooltip(); - - /** @brief Create a new pixmap widget showing the old appearance of the applet and fades it out. */ - Plasma::Animation* fadeOutOldAppearance(); - - /** @brief Create a menu action, which lists all stops in the settings to switch between them. - * - * @param parent The parent of the menu action and it's sub-actions. - * @param destroyOverlayOnTrigger True, if the overlay widget should be - * destroyed when triggered. Defaults to false. - * - * @return KSelectAction* The created menu action. - **/ - KSelectAction *createSwitchStopAction( QObject *parent, - bool destroyOverlayOnTrigger = false ) const; - - /** @brief Remove any time and datetime parameters from @p sourceName and return the result. */ - QString stripDateAndTimeValues( const QString& sourceName ) const; - -public: // Inline functions, mostly used only once (therefore inline) or very short and used rarely - /** @brief Create, initialize and connect objects. */ - inline void init() { - Q_Q( PublicTransportApplet ); - - // Read settings - settings = SettingsIO::readSettings( q->config(), q->globalConfig() ); - - // Create and connect the worker thread - departureProcessor = new DepartureProcessor( q ); - q->connect( departureProcessor, SIGNAL(beginDepartureProcessing(QString)), - q, SLOT(beginDepartureProcessing(QString)) ); - q->connect( departureProcessor, SIGNAL(departuresProcessed(QString,QList,QUrl,QDateTime,QDateTime,QDateTime,int)), - q, SLOT(departuresProcessed(QString,QList,QUrl,QDateTime,QDateTime,QDateTime,int)) ); - q->connect( departureProcessor, SIGNAL(beginJourneyProcessing(QString)), - q, SLOT(beginJourneyProcessing(QString)) ); - q->connect( departureProcessor, SIGNAL(journeysProcessed(QString,QList,QUrl,QDateTime)), - q, SLOT(journeysProcessed(QString,QList,QUrl,QDateTime)) ); - q->connect( departureProcessor, SIGNAL(departuresFiltered(QString,QList,QList,QList)), - q, SLOT(departuresFiltered(QString,QList,QList,QList)) ); - departureProcessor->setAlarms( settings.alarms() ); - departureProcessor->setFilters( settings.currentFilters() ); - departureProcessor->setColorGroups( settings.currentColorGroups() ); - - // Create departure painter and load the vehicle type SVG - departurePainter = new DeparturePainter( q ); - vehiclesSvg.setImagePath( KGlobal::dirs()->findResource( "data", - "plasma_applet_publictransport/vehicles.svg" ) ); - vehiclesSvg.setContainsMultipleImages( true ); - departurePainter->setSvg( &vehiclesSvg ); - - // Create popup icon manager - popupIcon = new PopupIcon( departurePainter, q ); - q->connect( popupIcon, SIGNAL(currentDepartureGroupChanged(int)), - q, SLOT(updateTooltip()) ); - q->connect( popupIcon, SIGNAL(currentDepartureGroupIndexChanged(qreal)), - q, SLOT(updatePopupIcon()) ); - q->connect( popupIcon, SIGNAL(currentDepartureIndexChanged(qreal)), - q, SLOT(updatePopupIcon()) ); - - // Setup models and widgets - setupModels(); - setupWidgets(); - - // Setup actions and the state machine - q->setupActions(); - setupStateMachine(); - - // Create tooltip - createTooltip(); - }; - - /** @brief Create, initialize and connect the departure/journey models. */ - inline void setupModels() { - Q_Q( PublicTransportApplet ); - - StopSettings stop = settings.currentStop(); - model = new DepartureModel( q ); - model->setDepartureArrivalListType( settings.departureArrivalListType() ); - model->setHomeStop( stop.stopList().isEmpty() ? QString() : stop.stop(0).name ); - model->setCurrentStopIndex( settings.currentStopIndex() ); - model->setDepartureColumnSettings( settings.departureTimeFlags() ); - q->connect( model, SIGNAL(alarmFired(DepartureItem*,AlarmSettings)), - q, SLOT(alarmFired(DepartureItem*,AlarmSettings)) ); - q->connect( model, SIGNAL(updateAlarms(AlarmSettingsList,QList)), - q, SLOT(removeAlarms(AlarmSettingsList,QList)) ); - q->connect( model, SIGNAL(itemsAboutToBeRemoved(QList)), - q, SLOT(departuresAboutToBeRemoved(QList)) ); - q->connect( model, SIGNAL(departuresLeft(QList)), - q, SLOT(departuresLeft(QList)) ); - - modelJourneys = new JourneyModel( q ); - modelJourneys->setHomeStop( stop.stopList().isEmpty() - ? QString() : stop.stop(0).name ); - modelJourneys->setCurrentStopIndex( settings.currentStopIndex() ); - modelJourneys->setAlarmSettings( settings.alarms() ); - popupIcon->setModel( model ); - } - - /** @brief Create, initialize and connect the state machine and it's states. */ - inline void setupStateMachine() { - Q_Q( PublicTransportApplet ); - - // Create the state machine - stateMachine = new QStateMachine( q ); - - // Create parallel main state and sub group states - QState *mainStateGroup = new QState( QState::ParallelStates, stateMachine ); - QState *providerStateGroup = new QState( mainStateGroup ); - QState *viewStateGroup = new QState( mainStateGroup ); - QState *departureDataStateGroup = new QState( mainStateGroup ); - QState *journeyDataStateGroup = new QState( mainStateGroup ); - states.insert( "mainStateGroup", mainStateGroup ); - states.insert( "providerStateGroup", providerStateGroup ); - states.insert( "viewStateGroup", viewStateGroup ); - states.insert( "departureDataStateGroup", departureDataStateGroup ); - states.insert( "journeyDataStateGroup", journeyDataStateGroup ); - - // Create View states - QState *actionButtonsState = new QState( viewStateGroup ); - QState *departureViewState = new QState( viewStateGroup ); - QState *intermediateDepartureViewState = new QState( viewStateGroup ); - QState *journeyStateGroup = new QState( viewStateGroup ); - QState *journeyViewState = new QState( journeyStateGroup ); - QState *journeysUnsupportedViewState = new QState( journeyStateGroup ); - QState *journeySearchState = new QState( journeyStateGroup ); - states.insert( "actionButtons", actionButtonsState ); - states.insert( "departureView", departureViewState ); - states.insert( "intermediateDepartureView", intermediateDepartureViewState ); - states.insert( "journeyStateGroup", journeyStateGroup ); - states.insert( "journeyView", journeyViewState ); - states.insert( "journeysUnsupportedView", journeysUnsupportedViewState ); - states.insert( "journeySearch", journeySearchState ); - - viewStateGroup->setInitialState( departureViewState ); - QHistoryState *lastMainState = new QHistoryState( viewStateGroup ); - lastMainState->setDefaultState( departureViewState ); - - // Create sub states of the departure list state for arrivals/departures - QState *departureState = new QState( departureViewState ); - QState *arrivalState = new QState( departureViewState ); - departureViewState->setInitialState( settings.departureArrivalListType() == DepartureList - ? departureState : arrivalState ); - QHistoryState *lastDepartureListState = new QHistoryState( departureViewState ); - lastDepartureListState->setDefaultState( departureState ); - - // Create provider states - QState *providerReadyState = new QState( providerStateGroup ); - QState *providerErrorState = new QState( providerStateGroup ); - providerStateGroup->setInitialState( providerReadyState ); // TODO TEST - states.insert( "providerReady", providerReadyState ); - states.insert( "providerError", providerErrorState ); - - // Create departure data states - QState *departureDataWaitingState = new QState( departureDataStateGroup ); - QState *departureDataValidState = new QState( departureDataStateGroup ); - QState *departureDataInvalidState = new QState( departureDataStateGroup ); - departureDataStateGroup->setInitialState( departureDataValidState ); - states.insert( "departureDataWaiting", departureDataWaitingState ); - states.insert( "departureDataValid", departureDataValidState ); - states.insert( "departureDataInvalid", departureDataInvalidState ); - - // Create journey data states - QState *journeyDataWaitingState = new QState( journeyDataStateGroup ); - QState *journeyDataValidState = new QState( journeyDataStateGroup ); - QState *journeyDataInvalidState = new QState( journeyDataStateGroup ); - journeyDataStateGroup->setInitialState( journeyDataWaitingState ); - states.insert( "journeyDataWaiting", journeyDataWaitingState ); - states.insert( "journeyDataValid", journeyDataValidState ); - states.insert( "journeyDataInvalid", journeyDataInvalidState ); - - // "Search Journeys..." action transitions to the journey search view (state "journeySearch"). - // If journeys aren't supported by the current service provider, a message gets displayed - // and the target state is "journeysUnsupportedView". The target states of these transitions - // get dynamically adjusted when the service provider settings change - journeySearchTransition1 = - new ToPropertyTransition( q->action("searchJourneys"), SIGNAL(triggered()), - actionButtonsState, q, "supportedJourneySearchState" ); - journeySearchTransition2 = - new ToPropertyTransition( q->action("searchJourneys"), SIGNAL(triggered()), - departureViewState, q, "supportedJourneySearchState" ); - journeySearchTransition3 = - new ToPropertyTransition( q->action("searchJourneys"), SIGNAL(triggered()), - journeyViewState, q, "supportedJourneySearchState" ); - - actionButtonsState->addTransition( - q->action( "showDepartures" ), SIGNAL(triggered()), departureState ); - actionButtonsState->addTransition( - q->action( "showArrivals" ), SIGNAL(triggered()), arrivalState ); - actionButtonsState->addTransition( - q, SIGNAL(hideActionButtons()), lastMainState ); - actionButtonsState->addTransition( - q->action( "backToDepartures" ), SIGNAL(triggered()), lastDepartureListState ); - actionButtonsState->addTransition( - q, SIGNAL(journeySearchFinished()), journeyViewState ); - departureViewState->addTransition( - titleWidget, SIGNAL(iconClicked()), actionButtonsState ); - departureViewState->addTransition( - q->action( "showActionButtons" ), SIGNAL(triggered()), actionButtonsState ); - journeyViewState->addTransition( - q->action( "showActionButtons" ), SIGNAL(triggered()), actionButtonsState ); - journeySearchState->addTransition( - q, SIGNAL(journeySearchFinished()), journeyViewState ); - - // Direct transition from departure view to journey view using a favorite/recent journey action - departureViewState->addTransition( - q, SIGNAL(journeySearchFinished()), journeyViewState ); - - // Add a transition to the intermediate departure list state. - // Gets triggered by eg. the context menus of route stop items. - departureViewState->addTransition( - q, SIGNAL(intermediateDepartureListRequested(QString)), - intermediateDepartureViewState ); - - // Add transitions to departure list and to arrival list - departureViewState->addTransition( - q->action("showDepartures"), SIGNAL(triggered()), departureState ); - departureViewState->addTransition( - q->action("showArrivals"), SIGNAL(triggered()), arrivalState ); - - intermediateDepartureViewState->addTransition( - titleWidget, SIGNAL(iconClicked()), lastMainState ); - intermediateDepartureViewState->addTransition( - q->action("backToDepartures"), SIGNAL(triggered()), lastMainState ); - - journeySearchState->addTransition( - titleWidget, SIGNAL(iconClicked()), lastMainState ); - journeySearchState->addTransition( - titleWidget, SIGNAL(closeIconClicked()), lastMainState ); - - journeyViewState->addTransition( - titleWidget, SIGNAL(iconClicked()), actionButtonsState ); - journeyViewState->addTransition( - titleWidget, SIGNAL(closeIconClicked()), lastDepartureListState ); - journeyViewState->addTransition( - q->action("backToDepartures"), SIGNAL(triggered()), lastDepartureListState ); - - providerReadyState->addTransition( q, SIGNAL(providerNotReady()), providerErrorState ); - providerErrorState->addTransition( q, SIGNAL(providerReady()), providerReadyState ); - - departureDataWaitingState->addTransition( - q, SIGNAL(validDepartureDataReceived()), departureDataValidState ); - departureDataWaitingState->addTransition( - q, SIGNAL(invalidDepartureDataReceived()), departureDataInvalidState ); - departureDataValidState->addTransition( - q, SIGNAL(requestedNewDepartureData()), departureDataWaitingState ); - departureDataInvalidState->addTransition( - q, SIGNAL(requestedNewDepartureData()), departureDataWaitingState ); - - journeyDataWaitingState->addTransition( - q, SIGNAL(validJourneyDataReceived()), journeyDataValidState ); - journeyDataWaitingState->addTransition( - q, SIGNAL(invalidJourneyDataReceived()), journeyDataInvalidState ); - journeyDataValidState->addTransition( - q, SIGNAL(requestedNewJourneyData()), journeyDataWaitingState ); - journeyDataInvalidState->addTransition( - q, SIGNAL(requestedNewJourneyData()), journeyDataWaitingState ); - - q->connect( actionButtonsState, SIGNAL(entered()), - q, SLOT(showActionButtons()) ); - q->connect( actionButtonsState, SIGNAL(exited()), - q, SLOT(destroyOverlay()) ); - - q->connect( departureViewState, SIGNAL(entered()), - q, SLOT(showDepartureList()) ); - - q->connect( arrivalState, SIGNAL(entered()), q, SLOT(showArrivals()) ); - q->connect( departureState, SIGNAL(entered()), q, SLOT(showDepartures()) ); - - q->connect( journeySearchState, SIGNAL(entered() ), - q, SLOT(showJourneySearch()) ); - q->connect( journeySearchState, SIGNAL(exited()), - q, SLOT(exitJourneySearch()) ); - q->connect( journeysUnsupportedViewState, SIGNAL(entered()), - q, SLOT(showJourneysUnsupportedView()) ); - q->connect( journeyViewState, SIGNAL(entered()), - q, SLOT(showJourneyList()) ); - q->connect( journeyViewState, SIGNAL(exited()), - q, SLOT(disconnectJourneySource()) ); - - q->connect( intermediateDepartureViewState, SIGNAL(entered()), - q, SLOT(showIntermediateDepartureList()) ); - q->connect( intermediateDepartureViewState, SIGNAL(exited()), - q, SLOT(removeIntermediateStopSettings()) ); - - q->connect( departureViewState, SIGNAL(entered() ), - q, SLOT(setAssociatedApplicationUrlForDepartures()) ); - q->connect( journeyViewState, SIGNAL(entered() ), - q, SLOT(setAssociatedApplicationUrlForJourneys()) ); - - q->connect( departureDataWaitingState, SIGNAL(entered()), - q, SLOT(departureDataStateChanged()) ); - q->connect( departureDataInvalidState, SIGNAL(entered()), - q, SLOT(departureDataStateChanged()) ); - q->connect( departureDataValidState, SIGNAL(entered()), - q, SLOT(departureDataStateChanged()) ); - - q->connect( journeyDataWaitingState, SIGNAL(entered()), - q, SLOT(journeyDataStateChanged()) ); - q->connect( journeyDataInvalidState, SIGNAL(entered()), - q, SLOT(journeyDataStateChanged()) ); - q->connect( journeyDataValidState, SIGNAL(entered()), - q, SLOT(journeyDataStateChanged()) ); - - stateMachine->setInitialState( mainStateGroup ); - stateMachine->start(); - }; - - /** @brief Create, initialize and connect widgets used for the applet. */ - inline void setupWidgets() { - Q_Q( PublicTransportApplet ); - - Q_ASSERT_X( !graphicsWidget, "PublicTransportPrivate::setupWidgets", - "This function should only be called once and the main graphics widget " - "should not be created somewhere else." ); - graphicsWidget = new QGraphicsWidget( q ); - graphicsWidget->setMinimumSize( 150, 150 ); // TODO allow smaller sizes, if zoom factor is small - graphicsWidget->setPreferredSize( q->preferredSize() ); - q->connect( graphicsWidget, SIGNAL(geometryChanged()), q, SLOT(appletResized()) ); - - // Create a child graphics widget, eg. to apply a blur effect to it - // but not to an overlay widget (which then gets a child of graphicsWidget). - mainGraphicsWidget = new QGraphicsWidget( graphicsWidget ); - mainGraphicsWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - QGraphicsLinearLayout *mainLayout = new QGraphicsLinearLayout( Qt::Vertical ); - mainLayout->setContentsMargins( 0, 0, 0, 0 ); - mainLayout->addItem( mainGraphicsWidget ); - graphicsWidget->setLayout( mainLayout ); - - // Create the title widget and connect slots - titleWidget = new TitleWidget( ShowDepartureArrivalListTitle, - &settings, currentServiceProviderFeatures.contains("ProvidesJourneys"), - mainGraphicsWidget ); - q->connect( titleWidget, SIGNAL(journeySearchInputFinished(QString)), - q, SLOT(journeySearchInputFinished(QString)) ); - q->connect( titleWidget, SIGNAL(journeySearchListUpdated(QList)), - q, SLOT(journeySearchListUpdated(QList)) ); // TODO Unused, remove? - - labelInfo = new Plasma::Label( mainGraphicsWidget ); - labelInfo->setAlignment( Qt::AlignVCenter | Qt::AlignRight ); - labelInfo->setMaximumHeight( labelInfo->geometry().height() ); - q->connect( labelInfo, SIGNAL(linkActivated(QString)), - KToolInvocation::self(), SLOT(invokeBrowser(QString)) ); - QLabel *_labelInfo = labelInfo->nativeWidget(); - _labelInfo->setOpenExternalLinks( true ); - _labelInfo->setText( infoText() ); - - // Watch for tooltip events and update the tooltip before showing it - _labelInfo->installEventFilter( q ); - - // Create timetable item for departures/arrivals - timetable = new TimetableWidget( settings.drawShadows() - ? PublicTransportWidget::DrawShadowsOrHalos : PublicTransportWidget::NoOption, - PublicTransportWidget::ExpandSingle, mainGraphicsWidget ); - timetable->setModel( model ); - timetable->setSvg( &vehiclesSvg ); - q->connect( timetable, SIGNAL(expandedStateChanged(PublicTransportGraphicsItem*,bool)), - q, SLOT(expandedStateChanged(PublicTransportGraphicsItem*,bool)) ); - q->connect( timetable, SIGNAL(contextMenuRequested(PublicTransportGraphicsItem*,QPointF)), - q, SLOT(departureContextMenuRequested(PublicTransportGraphicsItem*,QPointF)) ); - q->connect( timetable, SIGNAL(requestStopAction(StopAction::Type,QString,QString)), - q, SLOT(requestStopAction(StopAction::Type,QString,QString)) ); - - QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical ); - layout->setContentsMargins( 0, 0, 0, 0 ); - layout->setSpacing( 0 ); - layout->addItem( titleWidget ); - layout->addItem( timetable ); - layout->addItem( labelInfo ); - layout->setAlignment( labelInfo, Qt::AlignRight | Qt::AlignVCenter ); - mainGraphicsWidget->setLayout( layout ); - - q->registerAsDragHandle( mainGraphicsWidget ); - q->registerAsDragHandle( titleWidget->titleWidget() ); - - // To make the link clickable (don't let plasma eat click events for dragging) - labelInfo->installSceneEventFilter( q ); - - // Apply properties of the plasma theme - applyTheme(); - }; - - /** @brief Clears the departure list received from the data engine and displayed by the applet. */ - inline void clearDepartures() { - departureInfos.clear(); // Clear data from data engine - model->clear(); // Clear data to be displayed - }; - - /** @brief Clears the journey list received from the data engine and displayed by the applet. */ - inline void clearJourneys() { - journeyInfos.clear(); // Clear data from data engine - modelJourneys->clear(); // Clear data to be displayed - }; - -protected: - QGraphicsWidget *graphicsWidget, *mainGraphicsWidget; - GraphicsPixmapWidget *oldItem; - TitleWidget *titleWidget; // A widget used as the title of the applet. - Plasma::Label *labelInfo; // A label used to display additional information. - - TimetableWidget *timetable; // The graphics widget showing the departure/arrival board. - JourneyTimetableWidget *journeyTimetable; // The graphics widget showing journeys. - - Plasma::Label *labelJourneysNotSupported; // A label used to display an info about unsupported journey search. - JourneySearchSuggestionWidget *listStopSuggestions; // A list of stop suggestions for the current input. - OverlayWidget *overlay; - Plasma::Svg vehiclesSvg; // An SVG containing SVG elements for vehicle types. - - DepartureModel *model; // The model containing the departures/arrivals. - QHash< QString, QList > departureInfos; // List of current departures/arrivals for each stop. - PopupIcon *popupIcon; - QParallelAnimationGroup *titleToggleAnimation; // Hiding/Showing the title on resizing - QHash< int, QString > stopIndexToSourceName; // A hash from the stop index to the source name. - QStringList currentSources; // Current source names at the publictransport data engine. - int runningUpdateRequests; // The number of currently running update requests. - QTimer *updateTimer; // A timer used to enable the update action again - - JourneyModel *modelJourneys; // The model for journeys from or to the "home stop". - QList journeyInfos; // List of current journeys. - QString currentJourneySource; // Current source name for journeys at the publictransport data engine. - QString journeyTitleText; - - QString lastSecondStopName; // The last used second stop name for journey search. - QDateTime lastJourneyDateTime; // The last used date and time for journey search. - - QDateTime lastSourceUpdate; // The last update of the data source inside the data engine. - QDateTime nextAutomaticSourceUpdate; // The next automatic update of the data source. - QDateTime minManualSourceUpdateTime; // The minimal next (manual) update time. - QUrl urlDeparturesArrivals, urlJourneys; // Urls to set as associated application urls, - // when switching from/to journey mode. - - Settings settings; // Current applet settings. - int originalStopIndex; // Index of the stop before showing an intermediate list via context menu. - QVariantHash currentProviderData; // The last received provider data, including it's current state - QStringList currentServiceProviderFeatures; - - QPersistentModelIndex clickedItemIndex; // Index of the clicked item in departure view - // for the context menu actions. - - QActionGroup *filtersGroup; // An action group to toggle filter configurations. - QActionGroup *colorFiltersGroup; // An action group to toggle color groups. - - DepartureProcessor *departureProcessor; // Applies filters, alarms and creates departure data structures in threads - DeparturePainter *departurePainter; // Paints departures - - // State machine, states and dynamic transitions - QStateMachine *stateMachine; - QHash< QString, QState* > states; // Stores states by name - QAbstractTransition *journeySearchTransition1; - QAbstractTransition *journeySearchTransition2; - QAbstractTransition *journeySearchTransition3; - - MarbleProcess *marble; - -private: - PublicTransportApplet *q_ptr; - Q_DECLARE_PUBLIC( PublicTransportApplet ) -}; - -class StopDataConnection : public QObject { - Q_OBJECT -public: - StopDataConnection( Plasma::DataEngine *engine, const QString &providerId, - const QString &stopName, QObject *parent = 0 ); - -signals: - void stopDataReceived( const QVariantHash &stopData ); - void stopDataReceived( const QString &stopName, bool coordinatesAreValid, qreal lon, qreal lat ); - -public slots: - /** @brief New @p data arrived from the data engine for source @p sourceName. */ - void dataUpdated( const QString &sourceName, const Plasma::DataEngine::Data &data ); -}; - -#endif // Multiple inclusion guard diff --git a/applet/routegraphicsitem.cpp b/applet/routegraphicsitem.cpp deleted file mode 100644 index e85d7c2..0000000 --- a/applet/routegraphicsitem.cpp +++ /dev/null @@ -1,1372 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Own includes -#include "routegraphicsitem.h" -#include "departuremodel.h" -#include "timetablewidget.h" - -// libpublictransport helper includes -#include - -// Plasma/KDE includes -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include -#include -#include - -RouteGraphicsItem::RouteGraphicsItem( QGraphicsItem* parent, DepartureItem *item, - StopAction *copyStopToClipboardAction, StopAction *showInMapAction, - StopAction *showDeparturesAction, StopAction *highlightStopAction, - StopAction *newFilterViaStopAction ) - : QGraphicsWidget(parent), m_item(item), - m_copyStopToClipboardAction(copyStopToClipboardAction), m_showInMapAction(showInMapAction), - m_showDeparturesAction(showDeparturesAction), m_highlightStopAction(highlightStopAction), - m_newFilterViaStopAction(newFilterViaStopAction) -{ - setFlag( ItemClipsToShape ); - m_zoomFactor = 1.0; - m_textAngle = 30.0; - m_maxTextWidth = 100.0; - updateData( item ); -} - -void RouteGraphicsItem::resizeEvent( QGraphicsSceneResizeEvent *event ) -{ - QGraphicsWidget::resizeEvent(event); - if ( !isVisible() ) { - // Don't rearrange if the route item isn't visible (TODO rearrange if shown?) - return; - } - arrangeStopItems(); -} - -void RouteGraphicsItem::showEvent( QShowEvent *event ) -{ - arrangeStopItems(); - QGraphicsWidget::showEvent(event); -} - -void RouteGraphicsItem::setZoomFactor( qreal zoomFactor ) -{ - m_zoomFactor = zoomFactor; - arrangeStopItems(); - updateGeometry(); - update(); -} - -void JourneyRouteStopGraphicsItem::setZoomFactor( qreal zoomFactor ) -{ - m_zoomFactor = zoomFactor; - update(); -} - -int RouteGraphicsItem::minStopDistance( const QFontMetrics &fontMetrics ) -{ - return 2 * fontMetrics.height(); -} - -qreal RouteGraphicsItem::textAngle( const QFontMetrics &fontMetrics, qreal zoomFactor ) -{ - // Padding is the distance in pixels of two text base lines at the nearest point - const int padding = 4 * zoomFactor; - - // Compute text angle between 30 and 70 degrees. Use the same angle for all route items to - // prevent visual clutter when multiple route items get shown with different text angles. - // Use a text angle that does not produce overlapping text for the case that the stop - // distance is minimal (many route stops). This angle will also not produce overlapping text - // for higher stop distances. - const qreal angle = qAsin(qreal(fontMetrics.lineSpacing() + padding) / - minStopDistance(fontMetrics)) * 180.0 / 3.14159; - return qBound( 30.0, angle, 70.0 ); -} - -qreal RouteGraphicsItem::maxTextWidth( qreal height, int fontHeight ) const -{ - // Compute maximal text width for the computed angle, - // so that the stop name won't go outside of the route item - const qreal angle = m_textAngle * 3.14159 / 180.0; - return height / qSin(angle) - fontHeight / qTan(angle); -} - -QPointF RouteGraphicsItem::stopTextPosition( const QFontMetrics &fontMetrics, - const QPointF &stopMarkerPosition ) const -{ - // Position the stop text under the stop marker center - return QPointF( stopMarkerPosition.x() - fontMetrics.height() / 3.0, - stopMarkerPosition.y() + (6 + padding()) * m_zoomFactor ); -} - -void RouteGraphicsItem::arrangeStopItems() -{ - if ( !m_item ) { - return; - } - - const DepartureInfo *info = m_item->departureInfo(); - if ( info->routeStops().count() != m_textItems.count() ) { - updateData( m_item ); - } else if ( !info->routeStops().isEmpty() ) { - int count = info->routeStops().count(); - QFont routeFont = KGlobalSettings::smallestReadableFont(); - const qreal smallestReadableFontSize = routeFont.pointSizeF(); - const qreal targetFontSize = smallestReadableFontSize * m_zoomFactor; - if ( targetFontSize >= smallestReadableFontSize ) { - routeFont = parentWidget()->font(); - } - routeFont.setPointSizeF( routeFont.pointSizeF() * m_zoomFactor ); - - QFont boldRouteFont = routeFont; - boldRouteFont.setBold( true ); - QFontMetrics fm( routeFont ); - QFontMetrics fmBold( boldRouteFont ); - const QRectF routeRect = rect(); - const qreal routeStopAreaWidth = routeRect.width() - 45 * m_zoomFactor; - - // Width of the route line (on which the stop items are displayed) - const qreal routeLineWidth = 4.0 * m_zoomFactor; - - // The position of the first stop item - const QPointF startStopPos( padding() * m_zoomFactor, - padding() * m_zoomFactor + routeLineWidth / 2.0 ); - - // Distance between two stop items - const int minStep = minStopDistance( fm ); - if ( (routeRect.width() - 4 * padding() * m_zoomFactor) / count < minStep ) { - count = qFloor( routeRect.width() / minStep ); - } - const qreal step = routeStopAreaWidth / count; - QPointF startStopTextPos = stopTextPosition( fm, startStopPos ); - const qreal height = routeRect.height() - startStopTextPos.y() + 6 * m_zoomFactor; - - // Compute text angle so that the stop names don't overlap - m_textAngle = textAngle( fm, m_zoomFactor ); - - // Compute maximal text width for the computed angle, - // so that the stop name won't go outside of routeRect - m_maxTextWidth = maxTextWidth( height, fm.height() ); - - for ( int i = 0; i < count; ++i ) { - QPointF stopMarkerPos( startStopPos.x() + i * step, startStopPos.y() ); - const QPointF stopTextPos = stopTextPosition( fm, stopMarkerPos ); - const QString stopName = info->routeStops()[i]; - const QString stopNameShortened = info->routeStopsShortened()[i]; - QFontMetrics *fontMetrics; - QFont *font; - if ( i == 0 || i == count - 1 ) { - font = &boldRouteFont; - fontMetrics = &fmBold; - } else { - font = &routeFont; - fontMetrics = &fm; - } - - // Move the last stop marker a bit further to the right (more space for the arrow) - // But do not move the stop text - if ( i == count - 1 ) { - stopMarkerPos.setX( stopMarkerPos.x() + 4 * m_zoomFactor ); - } - - RouteStopMarkerGraphicsItem *markerItem = m_markerItems[ i ]; - markerItem->setPos( stopMarkerPos ); - - // Get time information - QDateTime time; - int minsFromFirstRouteStop = -1; - if ( i < info->routeTimes().count() && info->routeTimes()[i].isValid() ) { - time = info->routeTimes()[i]; - minsFromFirstRouteStop = qCeil( info->departure().secsTo(time) / 60.0 ); - } - - qreal baseSize; - if ( i >= count - 2 ) { - // The last stop names may not fit horizontally (correct the last two here) - baseSize = qMin( m_maxTextWidth, - (routeRect.width() - stopTextPos.x() - padding()) - / qCos(m_textAngle * 3.14159 / 180.0) ); - } else { - baseSize = m_maxTextWidth; - } - - // Create sub item, that displays a single stop name - // and automatically elides it and stretches it on hover to show hidden text - RouteStopTextGraphicsItem *textItem = m_textItems[ i ]; - textItem->resetTransform(); - textItem->setStop( time, stopName, stopNameShortened, minsFromFirstRouteStop ); - textItem->setFont( *font ); - textItem->setPos( stopTextPos ); - textItem->setBaseSize( baseSize ); - textItem->resize( baseSize, fontMetrics->height() ); - const qreal half = textItem->geometry().height() / 2; - textItem->setTransformOriginPoint( half, half ); - textItem->setRotation( m_textAngle ); - } - } -} - -void RouteGraphicsItem::updateData( DepartureItem *item ) -{ - if ( rect().isEmpty() ) { -// kDebug() << "Empty rect for the RouteGraphicsItem"; - return; - } - m_item = item; - const DepartureInfo *info = m_item->departureInfo(); - - // First remove all old RouteStopGraphicsItems - qDeleteAll( m_textItems ); - qDeleteAll( m_markerItems ); - m_textItems.clear(); - m_markerItems.clear(); - - if ( info->routeStops().count() >= 2 ) { - // Add route stops if there are at least two stops given from the data engine - QFont routeFont = KGlobalSettings::smallestReadableFont(); - const qreal smallestReadableFontSize = routeFont.pointSizeF(); - const qreal targetFontSize = smallestReadableFontSize * m_zoomFactor; - if ( targetFontSize >= smallestReadableFontSize ) { - routeFont = parentWidget()->font(); - } - routeFont.setPointSizeF( routeFont.pointSizeF() * m_zoomFactor ); - QFont boldRouteFont = routeFont; - boldRouteFont.setBold( true ); - QFontMetrics fm( routeFont ); - QFontMetrics fmBold( boldRouteFont ); - const QRectF routeRect = rect(); - const qreal routeLineWidth = 4.0 * m_zoomFactor; - const QPointF startStopPos( (10 /*- 4*/) * m_zoomFactor, padding() + routeLineWidth / 2.0 ); - const qreal routeStopAreaWidth = routeRect.width() - 45 * m_zoomFactor; - const qreal minStep = fm.height() * 1.5; // This controls how close route stops get shown - - // Compute number of route stop items - // without using more space than routeStopAreaWidth - int count = info->routeStops().count(); - if ( minStep * count > routeStopAreaWidth ) { - count = qFloor( routeStopAreaWidth / minStep ); - } - - // Compute distance between two route stop items - const qreal step = routeStopAreaWidth / count; - QPointF startStopTextPos = stopTextPosition( fm, startStopPos ); - const qreal height = routeRect.height() - startStopTextPos.y() + 6 * m_zoomFactor; - - // Compute text angle so that the stop names don't overlap - m_textAngle = textAngle( fm, m_zoomFactor ); - - // Compute maximal text width for the computed angle, - // so that the stop name won't go outside of routeRect - m_maxTextWidth = maxTextWidth( height, fm.height() ); - - // TODO: Ensure the highlighted stop name gets shown (not omitted) - int omitCount = info->routeStops().count() - count; - int omitIndex = omitCount == 0 ? -1 : qFloor(count / 2.0); - - DepartureModel *model = qobject_cast( m_item->model() ); - QPalette highlightedPalette = palette(); - QPalette defaultPalette = palette(); - if ( !model->highlightedStop().isEmpty() ) { - QColor highlightColor = KColorUtils::mix( - Plasma::Theme::defaultTheme()->color(Plasma::Theme::HighlightColor), - palette().color(QPalette::Active, QPalette::Text), 0.3 ); - highlightedPalette.setColor( QPalette::Active, QPalette::Text, - highlightColor ); - highlightedPalette.setColor( QPalette::Active, QPalette::ButtonText, - highlightColor ); - } else { - #if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - defaultPalette.setColor( QPalette::Active, QPalette::ButtonText, - Plasma::Theme::TextColor ); - #else - defaultPalette.setColor( QPalette::Active, QPalette::ButtonText, - Plasma::Theme::ViewTextColor ); - #endif - } - - for ( int positionIndex = 0; positionIndex < count; ++positionIndex ) { - QPointF stopMarkerPos( startStopPos.x() + positionIndex * step, startStopPos.y() ); - int index = -1; - if ( positionIndex == omitIndex ) { // Currently at first omitted stop - // Create intermediate marker item - RouteStopMarkerGraphicsItem *markerItem = new RouteStopMarkerGraphicsItem( - this, 0, RouteStopMarkerGraphicsItem::IntermediateStopMarker ); - markerItem->setPos( stopMarkerPos ); - m_markerItems << markerItem; - - // Create a list with all omitted stops (and times if available) - // to be used for the tooltip of the intermediate marker item - QStringList omittedStopList; - for ( int omittedIndex = omitIndex; - omittedIndex <= omitIndex + omitCount; ++omittedIndex ) - { - QString stopText = info->routeStopsShortened()[omittedIndex]; - - // Prepend departure time at the current stop, if a time is given - const QDateTime time = omittedIndex < info->routeTimes().count() - ? info->routeTimes()[omittedIndex] : QDateTime(); - if ( time.isValid() ) { - stopText.prepend( KGlobal::locale()->formatTime(time.time()) + ": " ); - } else { - kDebug() << "Invalid QTime in RouteTimes at index" << index; - } - - bool manuallyHighlighted = - model->routeItemFlags(stopText).testFlag(RouteItemHighlighted); - omittedStopList << (manuallyHighlighted - ? QString("%1").arg(stopText) - : stopText); - } - markerItem->setToolTip( i18nc("@info:tooltip This is the title for " - "tooltips of stop marker items for omitted route stops. " - "The names (and times if available) of the omitted stops " - "get placed at '%1'.", - "Intermediate stops:%1", - omittedStopList.join(",")) ); - continue; - } else if ( positionIndex > omitIndex ) { - // Currently after the omitted stops, compute index in stop list - // by adding omitted count to positional index - index = positionIndex + omitCount; - } else { - // Currently before the omitted stops, index in stop list equals - // positional index - index = positionIndex; - } - - const QPointF stopTextPos = stopTextPosition( fm, stopMarkerPos ); - QString stopName = info->routeStops()[index]; - QString stopNameShortened = info->routeStopsShortened()[index]; - QString stopText = stopNameShortened; - QFontMetrics *fontMetrics; - QFont *font; - - // Move the last stop marker a bit further to the right (more space for the arrow) - // But do not move the stop text - if ( positionIndex == count - 1 ) { - stopMarkerPos.setX( stopMarkerPos.x() + 4 * m_zoomFactor ); - } - - const bool manuallyHighlighted = - model->routeItemFlags(stopName).testFlag(RouteItemHighlighted); - if ( index == 0 || index == info->routeStops().count() - 1 // first and last item - || manuallyHighlighted ) - { - font = &boldRouteFont; - fontMetrics = &fmBold; - } else { - font = &routeFont; - fontMetrics = &fm; - } - - // Prepend departure time at the current stop, if a time is given - const QDateTime time = index < info->routeTimes().count() - ? info->routeTimes()[index] : QDateTime(); - if ( time.isValid() ) { - stopText.prepend( KGlobal::locale()->formatTime(time.time()) + ": " ); - } else { - kDebug() << "Invalid QTime in RouteTimes at index" << index; - } - - // Get max text width - qreal baseSize; - if ( index >= info->routeStops().count() - 2 ) { - // The last stop names may not fit horizontally (correct the last two here) - baseSize = qMin( m_maxTextWidth, - (routeRect.width() - stopTextPos.x() - padding()) - / qCos(m_textAngle * 3.14159 / 180.0) ); - } else { - baseSize = m_maxTextWidth; - } - - // Get route flags - int minsFromFirstRouteStop = -1; - RouteStopFlags routeStopFlags = item->routeStopFlags( index, &minsFromFirstRouteStop ); - - // Create text item, that displays a single stop name - // and automatically elides and stretches it on hover to show hidden text - RouteStopTextGraphicsItem *textItem = new RouteStopTextGraphicsItem( - this, model, *font, baseSize, time, stopName, stopNameShortened, - minsFromFirstRouteStop, routeStopFlags ); - textItem->setPos( stopTextPos ); - textItem->resize( baseSize, fontMetrics->height() ); - const qreal half = textItem->geometry().height() / 2; - textItem->setTransformOriginPoint( half, half ); - textItem->setRotation( m_textAngle ); - QList actions; - if ( routeStopFlags.testFlag(RouteStopIsHomeStop) ) { - if ( m_showInMapAction ) { - actions << m_showInMapAction; - } - actions << m_copyStopToClipboardAction; - } else { - actions << m_showDeparturesAction; - if ( m_showInMapAction ) { - actions << m_showInMapAction; - } - actions << m_highlightStopAction << m_newFilterViaStopAction - << m_copyStopToClipboardAction; - } - textItem->addActions( actions ); - if ( manuallyHighlighted ) { - textItem->setPalette( manuallyHighlighted - ? highlightedPalette : defaultPalette ); - } - m_textItems << textItem; - - // Create marker item - RouteStopMarkerGraphicsItem *markerItem = new RouteStopMarkerGraphicsItem( this, - textItem, RouteStopMarkerGraphicsItem::DefaultStopMarker, routeStopFlags ); - markerItem->setPos( stopMarkerPos ); - m_markerItems << markerItem; - - // Connect (un)hovered signals and (un)hover slots of text and marker items - connect( markerItem, SIGNAL(hovered(RouteStopMarkerGraphicsItem*)), - textItem, SLOT(hover()) ); - connect( markerItem, SIGNAL(unhovered(RouteStopMarkerGraphicsItem*)), - textItem, SLOT(unhover()) ); - connect( textItem, SIGNAL(hovered(RouteStopTextGraphicsItem*)), - markerItem, SLOT(hover()) ); - connect( textItem, SIGNAL(unhovered(RouteStopTextGraphicsItem*)), - markerItem, SLOT(unhover()) ); - } - } -} - -void RouteGraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget ) -{ - Q_UNUSED( option ); - Q_UNUSED( widget ); - - if ( !m_item || m_markerItems.isEmpty() ) { - // Item was already deleted or no route data available - return; - } - - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - - const QRectF routeRect = rect(); - const qreal routeLineWidth = 4.0 * m_zoomFactor; - - // Draw horizontal timeline -#if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - QColor backgroundColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor); - painter->setPen( Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor) ); -#else - QColor backgroundColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::ViewBackgroundColor); - painter->setPen( Plasma::Theme::defaultTheme()->color(Plasma::Theme::ViewTextColor) ); -#endif - QColor backgroundFadeColor = backgroundColor; - backgroundFadeColor.setAlphaF( 0.5 ); - QLinearGradient backgroundGradient( 0, 0, 1, 0 ); - backgroundGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); - backgroundGradient.setColorAt( 0, backgroundColor ); - backgroundGradient.setColorAt( 1, backgroundFadeColor ); - painter->setBrush( backgroundGradient ); - const qreal arrowWidth = routeLineWidth * 2.5; - const qreal arrowHeight = routeLineWidth * 1.0; - const qreal timelineTop = routeRect.top() + padding(); - const qreal timelineBottom = timelineTop + routeLineWidth; - const qreal timelineLeft = routeRect.left() + routeLineWidth * 3.0; - const qreal timelineRight = (m_markerItems.isEmpty() ? routeRect.right() - : m_markerItems.last()->pos().x() - m_markerItems.last()->size().width() / 2.0) - + 1 * m_zoomFactor - arrowWidth; - const QPointF points[7] = { - QPointF(timelineLeft, timelineBottom), - QPointF(timelineLeft, timelineTop), - QPointF(timelineRight, timelineTop), - QPointF(timelineRight, timelineTop - arrowHeight), - QPointF(timelineRight + arrowWidth, timelineTop + routeLineWidth / 2.0), - QPointF(timelineRight, timelineBottom + arrowHeight), - QPointF(timelineRight, timelineBottom) }; - painter->drawConvexPolygon( points, 7 ); -} - -RouteStopMarkerGraphicsItem::RouteStopMarkerGraphicsItem( QGraphicsItem* parent, - RouteStopTextGraphicsItem *textItem, MarkerType markerType, RouteStopFlags stopFlags ) - : QGraphicsWidget(parent), m_textItem(textItem) -{ - setFlag( ItemClipsToShape ); - m_hoverStep = 0.0; - m_markerType = markerType; - m_stopFlags = stopFlags; - setAcceptHoverEvents( true ); - - QPalette p = palette(); -#if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - p.setColor( QPalette::Active, QPalette::Background, - Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor) ); -#else - p.setColor( QPalette::Active, QPalette::Background, - Plasma::Theme::defaultTheme()->color(Plasma::Theme::ViewBackgroundColor) ); -#endif - setPalette( p ); - - QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect; - shadowEffect->setBlurRadius( 8.0 ); - shadowEffect->setOffset( 1.0 ); - setGraphicsEffect( shadowEffect ); -} - -void RouteStopMarkerGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event) -{ - QGraphicsItem::hoverEnterEvent(event); - hover(); - emit hovered( this ); -} - -void RouteStopMarkerGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) -{ - QGraphicsWidget::hoverLeaveEvent(event); - unhover(); - emit unhovered( this ); -} - -void RouteStopMarkerGraphicsItem::hover() -{ - setZValue( 1.0 ); - QPropertyAnimation *hoverAnimation = new QPropertyAnimation( this, "hoverStep" ); - hoverAnimation->setEasingCurve( QEasingCurve(QEasingCurve::OutCubic) ); - hoverAnimation->setStartValue( hoverStep() ); - hoverAnimation->setEndValue( 1.0 ); - hoverAnimation->start( QAbstractAnimation::DeleteWhenStopped ); -} - -void RouteStopMarkerGraphicsItem::unhover() -{ - setZValue( 0.0 ); - QPropertyAnimation *hoverAnimation = new QPropertyAnimation( this, "hoverStep" ); - hoverAnimation->setEasingCurve( QEasingCurve(QEasingCurve::InOutCubic) ); - hoverAnimation->setStartValue( hoverStep() ); - hoverAnimation->setEndValue( 0.0 ); - hoverAnimation->start( QAbstractAnimation::DeleteWhenStopped ); -} - -void RouteStopMarkerGraphicsItem::setHoverStep( qreal hoverStep ) -{ -#if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - QColor normalColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::BackgroundColor ); - QColor hoverColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::HighlightColor ); -#else - QColor normalColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::ViewBackgroundColor ); - QColor hoverColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::ViewHoverColor ); -#endif - QColor currentColor = KColorUtils::mix( normalColor, hoverColor, hoverStep ); - QPalette p = palette(); - p.setColor( QPalette::Active, QPalette::Background, currentColor ); - setPalette( p ); - - m_hoverStep = hoverStep; - updateGeometry(); -} - -qreal RouteStopMarkerGraphicsItem::radius() const -{ - RouteGraphicsItem *routeItem = qgraphicsitem_cast( parentItem() ); - qreal zoomFactor = routeItem->zoomFactor(); - if ( m_markerType == IntermediateStopMarker ) { - return (12.0 + 2.0 * m_hoverStep) * zoomFactor; - } else { - RouteStopFlags stopFlags = routeStopFlags(); - if ( stopFlags.testFlag(RouteStopIsHighlighted) ) { - return (7.5 + 2.0 * m_hoverStep) * zoomFactor; - } else if ( stopFlags.testFlag(RouteStopIsHomeStop) ) { - return (7.5 + 2.0 * m_hoverStep) * zoomFactor; - } else if ( stopFlags.testFlag(RouteStopIsOrigin) ) { - return (7.5 + 2.0 * m_hoverStep) * zoomFactor; - } else if ( stopFlags.testFlag(RouteStopIsTarget) ) { - return (7.5 + 2.0 * m_hoverStep) * zoomFactor; - } else { - return (6.0 + 2.0 * m_hoverStep) * zoomFactor; - } - } -} - -void RouteStopMarkerGraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget ) -{ - Q_UNUSED( option ); - Q_UNUSED( widget ); - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - - KIcon stopIcon; - if ( m_markerType == IntermediateStopMarker ) { - stopIcon = KIcon( "public-transport-intermediate-stops" ); - } else { - stopIcon = GlobalApplet::stopIcon( routeStopFlags() ); - } - stopIcon.paint( painter, QRect(-radius(), -radius(), 2 * radius(), 2 * radius()) ); -} - -RouteStopFlags RouteStopMarkerGraphicsItem::routeStopFlags() const -{ - return m_textItem->routeStopFlags(); -} - -RouteStopFlags RouteStopTextGraphicsItem::routeStopFlags() const -{ - RouteStopFlags stopFlags = m_stopFlags; - if ( !m_model ) { - return stopFlags; - } - - const RouteItemFlags itemFlags = m_model->routeItemFlags( m_stopName ); - if ( itemFlags.testFlag(RouteItemHighlighted) ) { - stopFlags |= RouteStopIsHighlighted; - } - if ( itemFlags.testFlag(RouteItemHomeStop) ) { - stopFlags |= RouteStopIsHomeStop; - } - return stopFlags; -} - -RouteStopFlags JourneyRouteStopGraphicsItem::routeStopFlags() const -{ - RouteStopFlags stopFlags = m_stopFlags; - RouteGraphicsItem *routeItem = qgraphicsitem_cast( parentItem() ); - JourneyModel *model = !routeItem || !routeItem->item() ? 0 : - qobject_cast( routeItem->item()->model() ); - RouteItemFlags itemFlags = model ? model->routeItemFlags(m_stopName) : RouteItemDefault; - if ( itemFlags.testFlag(RouteItemHighlighted) ) { - stopFlags |= RouteStopIsHighlighted; - } - if ( itemFlags.testFlag(RouteItemHomeStop) ) { - stopFlags |= RouteStopIsHomeStop; - } - return stopFlags; -} - -RouteStopTextGraphicsItem::RouteStopTextGraphicsItem( QGraphicsItem* parent, DepartureModel *model, - const QFont &font, qreal baseSize, const QDateTime &time, - const QString &stopName, const QString &stopNameShortened, - int minsFromFirstRouteStop, RouteStopFlags routeStopFlags ) - : QGraphicsWidget(parent), m_model(model) -{ - m_expandStep = 0.0; - m_baseSize = baseSize; - m_stopFlags = routeStopFlags; - setFont( font ); - setStop( time, stopName, stopNameShortened, minsFromFirstRouteStop ); - setAcceptHoverEvents( true ); -} - -void RouteStopTextGraphicsItem::setStop( const QDateTime &time, const QString &stopName, - const QString &stopNameShortened, - int minsFromFirstRouteStop ) -{ - m_stopName = stopName; - m_stopNameShortened = stopNameShortened; - m_stopText = minsFromFirstRouteStop == 999999 || !time.isValid() - ? stopName : QString("%1: %2").arg(minsFromFirstRouteStop).arg(stopNameShortened); - - qreal maxSize = QFontMetrics( font() ).width( m_stopText ) + 5; - if ( maxSize > m_baseSize ) { - if ( time.isValid() ) { - setToolTip( QString("%1: %2").arg(KGlobal::locale()->formatTime(time.time())) - .arg(stopNameShortened) ); - } else { - setToolTip( stopNameShortened ); - } - } else { - setToolTip( QString() ); - } -} - -void RouteStopTextGraphicsItem::hover() -{ - setZValue( 1.0 ); - QPropertyAnimation *expandAnimation = new QPropertyAnimation( this, "expandStep" ); - expandAnimation->setEasingCurve( QEasingCurve(QEasingCurve::OutCubic) ); - expandAnimation->setStartValue( expandStep() ); - expandAnimation->setEndValue( 1.0 ); - expandAnimation->start( QAbstractAnimation::DeleteWhenStopped ); -} - -void RouteStopTextGraphicsItem::unhover() -{ - setZValue( 0.0 ); - QPropertyAnimation *expandAnimation = new QPropertyAnimation( this, "expandStep" ); - expandAnimation->setEasingCurve( QEasingCurve(QEasingCurve::InOutCubic) ); - expandAnimation->setStartValue( expandStep() ); - expandAnimation->setEndValue( 0.0 ); - expandAnimation->start( QAbstractAnimation::DeleteWhenStopped ); -} - -void RouteStopTextGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event) -{ - QGraphicsItem::hoverEnterEvent(event); - hover(); - emit hovered( this ); -} - -void RouteStopTextGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) -{ - QGraphicsWidget::hoverLeaveEvent(event); - unhover(); - emit unhovered( this ); -} - -void RouteStopTextGraphicsItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) -{ - QList actionList = actions(); - if ( actionList.isEmpty() ) { - return; // Don't show an empty menu if there are no actions to show - } - - for ( int i = 0; i < actionList.count(); ++i ) { - StopAction *action = qobject_cast( actionList[i] ); - action->setStopName( m_stopName, m_stopNameShortened ); - - if ( action->type() == StopAction::HighlightStop ) { - // Update text of the highlight stop action - RouteGraphicsItem *routeItem = qgraphicsitem_cast( parentItem() ); - DepartureModel *model = !routeItem || !routeItem->item() ? 0 : - qobject_cast( routeItem->item()->model() ); - QString highlightStopActionText = - (!model || !model->routeItemFlags(m_stopName).testFlag(RouteItemHighlighted)) - ? i18nc("@action:inmenu", "&Highlight This Stop") - : i18nc("@action:inmenu", "&Unhighlight This Stop"); - action->setText( highlightStopActionText ); - } - } - - KMenu contextMenu; - contextMenu.addTitle( GlobalApplet::stopIcon(routeStopFlags()), m_stopNameShortened ); - contextMenu.addActions( actionList ); - contextMenu.exec( event->screenPos() ); -} - -void RouteStopTextGraphicsItem::setExpandStep( qreal expandStep ) -{ - qreal maxSize = QFontMetrics( font() ).width( m_stopText ) + 5; - if ( m_baseSize < maxSize ) { - resize( m_baseSize + (maxSize - m_baseSize) * expandStep, size().height() ); - } - - QColor normalColor = palette().color( QPalette::Active, QPalette::ButtonText ); -#if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - QColor hoverColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::HighlightColor ); -#else - QColor hoverColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::ViewHoverColor ); -#endif - QColor currentColor = KColorUtils::mix( normalColor, hoverColor, expandStep / 2.0 ); - QPalette p = palette(); - p.setColor( QPalette::Active, QPalette::Text, currentColor ); - setPalette( p ); - - m_expandStep = expandStep; -} - -void RouteStopTextGraphicsItem::setBaseSize( qreal baseSize ) -{ - m_baseSize = baseSize + 10; -} - -void RouteStopTextGraphicsItem::paint( QPainter* painter, - const QStyleOptionGraphicsItem* option, QWidget* widget ) -{ - Q_UNUSED( widget ); - - // Get the departure graphics item (parent of this is RouteGraphicsItem, - // parent of RouteGraphicsItem is DepartureGraphicsItem) - // to get access to the textColor() function - DepartureGraphicsItem *departureItem = - qgraphicsitem_cast( parentWidget()->parentWidget() ); - QColor textColor = departureItem->textColor(); - const bool drawShadowsOrHalos = departureItem->publicTransportWidget()->isOptionEnabled( - PublicTransportWidget::DrawShadowsOrHalos ); - const bool drawHalos = drawShadowsOrHalos && qGray(textColor.rgb()) < 192; - - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - painter->setFont( font() ); - - const QFontMetrics fm( font() ); - QRect rect = option->rect; - rect.setTop( 0 ); - QString stopText = fm.elidedText( m_stopText, Qt::ElideRight, rect.width() ); - - // Create an a bit bigger rectangle to not clip the halo/shadow - const int hHaloPad = 10; - const int vHaloPad = 5; - QRect paddedRect = rect.adjusted( -hHaloPad, -vHaloPad, hHaloPad, vHaloPad ); - - // Prepare a pixmap and a painter drawing to that pixmap - QPixmap pixmap( paddedRect.size() ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - p.setRenderHints( QPainter::Antialiasing ); - p.setBrush( textColor ); // Set text color as brush, because it's filled using a QPainterPath - p.setPen( Qt::NoPen ); // No text outline - - if ( drawHalos ) { - Plasma::PaintUtils::drawHalo( &p, QRectF(hHaloPad, vHaloPad, - fm.width(stopText), fm.height()) ); - } - - // Use a QPainterPath to draw the text, because it's better antialiased then - QPainterPath path; - path.addText( hHaloPad, vHaloPad + fm.ascent(), font(), stopText ); - p.drawPath( path ); - p.end(); - - if ( !drawHalos && drawShadowsOrHalos ) { - // Create and draw a shadow - QImage shadow = pixmap.toImage(); - Plasma::PaintUtils::shadowBlur( shadow, 3, Qt::black ); - painter->drawImage( paddedRect.topLeft() + QPoint(1, 2), shadow ); - } - - // Draw the route pixmap - painter->drawPixmap( paddedRect.topLeft(), pixmap ); -} - -JourneyRouteStopGraphicsItem::JourneyRouteStopGraphicsItem( JourneyRouteGraphicsItem* parent, - const QPixmap &vehiclePixmap, const QString &text, RouteStopFlags routeStopFlags, - const QString &stopName, const QString &stopNameShortened ) - : QGraphicsWidget(parent), m_parent(parent), m_infoTextDocument(0) -{ - m_zoomFactor = 1.0; - m_vehiclePixmap = vehiclePixmap; - m_stopFlags = routeStopFlags; - m_stopName = stopName; - m_stopNameShortened = stopNameShortened; - setText( text ); - setAcceptHoverEvents( true ); -} - -JourneyRouteStopGraphicsItem::~JourneyRouteStopGraphicsItem() -{ - delete m_infoTextDocument; -} - -void JourneyRouteStopGraphicsItem::setText( const QString& text ) -{ - delete m_infoTextDocument; - m_infoTextDocument = 0; - - QTextOption textOption( Qt::AlignVCenter | Qt::AlignLeft ); - m_infoTextDocument = TextDocumentHelper::createTextDocument( text, infoTextRect().size(), - textOption, font() ); - - updateGeometry(); - update(); -} - -void JourneyRouteStopGraphicsItem::contextMenuEvent( QGraphicsSceneContextMenuEvent *event ) -{ - QList actionList = actions(); - if ( actionList.isEmpty() ) { - return; // Don't show an empty menu if there are no actions to show - } - for ( int i = 0; i < actionList.count(); ++i ) { - StopAction *action = qobject_cast( actionList[i] ); - action->setStopName( m_stopName, m_stopNameShortened ); - } - - KAction *toggleIntermediateStopsAction = new KAction( m_parent->showIntermediateStops() - ? i18nc("@info/plain", "&Hide intermediate stops") - : i18nc("@info/plain", "&Show intermediate stops"), - this ); - actionList << toggleIntermediateStopsAction; - - KMenu contextMenu; - contextMenu.addTitle( GlobalApplet::stopIcon(routeStopFlags()), m_stopNameShortened ); - contextMenu.addActions( actionList ); - QAction *executeAction = contextMenu.exec( event->screenPos() ); - if ( executeAction == toggleIntermediateStopsAction ) { - m_parent->setShowIntermediateStops( !m_parent->showIntermediateStops() ); - } - delete toggleIntermediateStopsAction; -} - -QSizeF JourneyRouteStopGraphicsItem::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const -{ - if ( which == Qt::MinimumSize || which == Qt::MaximumSize ) { - const qreal marginLeft = 32.0 * m_zoomFactor; - return QSizeF( marginLeft + TextDocumentHelper::textDocumentWidth(m_infoTextDocument), - m_infoTextDocument->size().height() + 5 * m_zoomFactor ); - } else { - return QGraphicsWidget::sizeHint(which, constraint); - } -} - -QRectF JourneyRouteStopGraphicsItem::infoTextRect() const -{ - const qreal marginLeft = 32.0 * m_zoomFactor; - return contentsRect().adjusted( marginLeft, 0.0, 0.0, 0.0 ); -} - -void JourneyRouteStopGraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget ) -{ - Q_UNUSED( widget ); - - if ( option->state.testFlag(QStyle::State_MouseOver) ) { - #if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - QColor hoverColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::HighlightColor ); - #else - QColor hoverColor = Plasma::Theme::defaultTheme()->color( Plasma::Theme::ViewHoverColor ); - #endif - - // Draw hover background - QLinearGradient bgGradient( 0, 0, 1, 0 ); - bgGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); - bgGradient.setColorAt( 0, Qt::transparent ); - bgGradient.setColorAt( 0.4, hoverColor ); - bgGradient.setColorAt( 0.6, hoverColor ); - bgGradient.setColorAt( 1, Qt::transparent ); - - painter->fillRect( option->rect, QBrush(bgGradient) ); - } - - // Draw text - const JourneyGraphicsItem *journeyItem = qgraphicsitem_cast( - qgraphicsitem_cast(parentWidget())->parentWidget() ); - -#if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - QColor textColor = journeyItem ? journeyItem->textColor - : Plasma::Theme::defaultTheme()->color( Plasma::Theme::TextColor ); -#else - QColor textColor = journeyItem ? journeyItem->textColor() - : Plasma::Theme::defaultTheme()->color( Plasma::Theme::ViewTextColor ); -#endif - TextDocumentHelper::Option textOption; - if ( !journeyItem->publicTransportWidget()->isOptionEnabled( - PublicTransportWidget::DrawShadowsOrHalos) ) - { - textOption = TextDocumentHelper::DoNotDrawShadowOrHalos; - } else if ( qGray(textColor.rgb()) < 192 ) { - textOption = TextDocumentHelper::DrawHalos; - } else { - textOption = TextDocumentHelper::DrawShadows; - } - QRectF textRect = infoTextRect(); - painter->setPen( textColor ); - TextDocumentHelper::drawTextDocument( painter, option, m_infoTextDocument, - textRect.toRect(), textOption ); -} - -JourneyRouteGraphicsItem::JourneyRouteGraphicsItem( QGraphicsItem* parent, JourneyItem* item, - Plasma::Svg* svg, StopAction *copyStopToClipboardAction, StopAction *showInMapAction, - StopAction *requestJourneyToStopAction, StopAction *requestJourneyFromStopAction ) - : QGraphicsWidget(parent), m_item(item), m_svg(svg), - m_copyStopToClipboardAction(copyStopToClipboardAction), - m_showInMapAction(showInMapAction), - m_requestJourneyToStopAction(requestJourneyToStopAction), - m_requestJourneyFromStopAction(requestJourneyFromStopAction), - m_showIntermediateStops(false) -{ - setFlag( ItemClipsChildrenToShape ); - m_zoomFactor = 1.0; - new QGraphicsLinearLayout( Qt::Vertical, this ); - updateData( item ); -} - -void JourneyRouteGraphicsItem::setZoomFactor( qreal zoomFactor ) -{ - m_zoomFactor = zoomFactor; - updateData( m_item ); - updateGeometry(); -} - -void JourneyRouteGraphicsItem::updateData( JourneyItem* item ) -{ - if ( rect().isEmpty() ) { -// kDebug() << "Empty rect for the JourneyRouteGraphicsItem"; - return; - } - m_item = item; - const JourneyInfo *info = m_item->journeyInfo(); - - // First remove all old RouteStopGraphicsItems - foreach ( JourneyRouteStopGraphicsItem *item, m_routeItems ) { - item->deleteLater(); - } - m_routeItems.clear(); - - // Add route stops if there are at least two stops given from the data engine - QGraphicsLinearLayout *l = static_cast( layout() ); - if ( info->routeStops().count() >= 2 ) { - QFont routeFont = font(); - routeFont.setPointSizeF( routeFont.pointSizeF() * m_zoomFactor ); - QFont boldRouteFont = routeFont; - boldRouteFont.setBold( true ); - QFontMetrics fm( routeFont ); - QFontMetrics fmBold( boldRouteFont ); - - // Add the route stop items (JourneyRouteStopGraphicsItem) - for ( int i = 0; i < info->routeStops().count(); ++i ) { - QFont *font = i == 0 || i == info->routeStops().count() - 1 - ? &boldRouteFont : &routeFont; - - const RouteSubJourney subJourney = - i < info->routeSubJourneys().count() && m_showIntermediateStops - ? info->routeSubJourneys()[i] : RouteSubJourney(); - - QStringList routePartStops = QStringList() << info->routeStops()[i]; - QStringList routePartStopsShortened = QStringList() << info->routeStopsShortened()[i]; - if ( i != info->routeStops().count() - 1 ) { - routePartStops << subJourney.routeStops; - routePartStopsShortened << - (subJourney.routeStopsShortened.count() != subJourney.routeStops.count() - ? subJourney.routeStops : subJourney.routeStopsShortened); - } - - QStringList routePlatformsArrival, routePlatformsDeparture, routeNews; - QList routeTimesArrival, routeTimesDeparture; - QList routeTimesArrivalDelay, routeTimesDepartureDelay; - - if ( i < info->routeTimesDeparture().count() && - info->routeTimesDeparture()[i].isValid() ) - { - // First add the subjourney departure time, then the intermediate departure times - routeTimesDeparture << info->routeTimesDeparture()[i] - << subJourney.routeTimesDeparture; - } - - if ( i < info->routeTimesDepartureDelay().count() ) { - routeTimesDepartureDelay << info->routeTimesDepartureDelay()[i] - << subJourney.routeTimesDepartureDelay; - } - - if ( i < info->routePlatformsDeparture().count() ) { - routePlatformsDeparture << info->routePlatformsDeparture()[i] - << subJourney.routePlatformsDeparture; - } - - if ( i < info->routeNews().count() ) { - routeNews << info->routeNews()[i] << subJourney.routeNews; - } - - if ( i == 0 ) { - routeTimesArrival << QDateTime(); - routeTimesArrivalDelay << -1; - routePlatformsArrival << QString(); - } else { - if( i - 1 < info->routeTimesArrival().count() && - info->routeTimesArrival()[i - 1].isValid() ) - { - // First add the intermediate arrival times, then the subjourney arrival time - routeTimesArrival << info->routeTimesArrival()[i - 1]; - } - - if ( i - 1 < info->routeTimesArrivalDelay().count() ) { - routeTimesArrivalDelay << info->routeTimesArrivalDelay()[i - 1]; - } - - if ( i - 1 < info->routePlatformsArrival().count() ) { - routePlatformsArrival << info->routePlatformsArrival()[i - 1]; - } - } - routeTimesArrival << subJourney.routeTimesArrival; - routeTimesArrivalDelay << subJourney.routeTimesArrivalDelay; - routePlatformsArrival << subJourney.routePlatformsArrival; - - for ( int n = 0; n < routePartStops.count(); ++n ) { - const QString stopName = routePartStops[n]; - const QString stopNameShortened = routePartStopsShortened[n]; - QString text = QString( "%1" ).arg( stopNameShortened ); - RouteStopFlags routeStopFlags = item->departureRouteStopFlags( i, n ); - - // Prepend departure time at the current stop, if a time is given - QDateTime departureTime = (n < routeTimesDeparture.count() && routeTimesDeparture[n].isValid()) - ? routeTimesDeparture[n] : QDateTime(); - QDateTime arrivalTime = (n < routeTimesArrival.count() && routeTimesArrival[n].isValid()) - ? routeTimesArrival[n] : QDateTime(); - if ( arrivalTime.isValid() && arrivalTime != departureTime ) { - QString timeString = KGlobal::locale()->formatTime(arrivalTime.time()); - QString timeText( "" + timeString + "" ); - if ( n < routeTimesArrivalDelay.count() ) { - const int delay = routeTimesArrivalDelay[n]; - if ( delay == 0 ) { - timeText.prepend( QString("") - .arg(Global::textColorOnSchedule().name()) ) - .append( QLatin1String("") ); - } else if ( delay > 0 ) { - timeText += ' ' + i18ncp( "@info/plain", "+%1 minute", "+%1 minutes", delay ); - timeText.replace( QRegExp( "(+?\\s*\\d+)" ), - QString( "%1 + \\1" ) - .arg(timeString).arg(Global::textColorDelayed().name()) ); - } - } - text.append( "
" + i18nc("@info", "Arrival:") + ' ' + timeText ); - - if ( n < routePlatformsArrival.count() && - !routePlatformsArrival[n].isEmpty() ) - { - text = text.append( i18nc("@info Info string for a stop in a journey shown in " - "the route item after the arrival time", - " at platform %1", - routePlatformsArrival[n]) ); - } - } - if ( departureTime.isValid() ) { - QString timeString = KGlobal::locale()->formatTime( departureTime.time() ); - QString timeText( "" + timeString + "" ); - if ( n < routeTimesDepartureDelay.count() ) { - const int delay = routeTimesDepartureDelay[n]; - if ( delay == 0 ) { - timeText.prepend( QString("") - .arg(Global::textColorOnSchedule().name()) ) - .append( QLatin1String("") ); - } else if ( delay > 0 ) { - timeText += ' ' + i18ncp( "@info/plain", "+%1 minute", "+%1 minutes", delay ); - timeText.replace( QRegExp( "(+?\\s*\\d+)" ), - QString( " + \\1" ) - .arg(Global::textColorDelayed().name()) ); - } - } - text.append( "
" + i18nc("@info", "Departure:") + ' ' + timeText ); - - if ( n < routePlatformsDeparture.count() && - !routePlatformsDeparture[n].isEmpty() ) - { - text = text.append( i18nc("@info Info string for a stop in a journey shown in " - "the route item after the departure time", - " from platform %1", - routePlatformsDeparture[n]) ); - } - - if ( routeStopFlags.testFlag(RouteStopIsConnectingStop) || - !routeStopFlags.testFlag(RouteStopIsIntermediate) ) - { - if ( i < info->routeTransportLines().count() && - !info->routeTransportLines()[i].isEmpty() ) - { - // TODO Show vehicle type and transport line only at the first stop of the journey part - text = text.append( "
" + i18nc("@info Info string for a stop in a journey shown in " - "the route item after the departure time. %1 is one of the transport " - "lines used in the journey, %2 is the name of the used vehicle if " - "available.", " using %1%2", - info->routeTransportLines()[i], i < info->routeVehicleTypes().count() - ? QString(" (%1)").arg(Global::vehicleTypeToString(info->routeVehicleTypes()[i])) - : QString()) ); - } - } - } - if ( n < routeNews.count() && !routeNews[n].isEmpty() ) { - text.append( "
" + i18nc("@info", "News:") + ' ' + routeNews[n] ); - } - - JourneyRouteStopGraphicsItem *routeItem = new JourneyRouteStopGraphicsItem( - this, QPixmap(32, 32), text, routeStopFlags, stopName, stopNameShortened ); - routeItem->setZoomFactor( m_zoomFactor ); - routeItem->setFont( *font ); - if ( n < routeNews.count() && !routeNews[n].isEmpty() ) { - routeItem->setToolTip( routeNews[n] ); - } - - QList actionList; - if ( !routeStopFlags.testFlag(RouteStopIsHomeStop) ) { - if ( !routeStopFlags.testFlag(RouteStopIsTarget) ) { - routeItem->addAction( m_requestJourneyToStopAction ); - } - if ( !routeStopFlags.testFlag(RouteStopIsOrigin) ) { - routeItem->addAction( m_requestJourneyFromStopAction ); - } - } - - if ( m_showInMapAction ) { - actionList << m_showInMapAction; - } - actionList << m_copyStopToClipboardAction;; - routeItem->addActions( actionList ); - - m_routeItems << routeItem; - l->addItem( routeItem ); - } // for n, routePartStops - } // for i, routeStops - } // routeStops.count() >= 2 -} - -QString JourneyRouteGraphicsItem::svgVehicleKey( VehicleType vehicleType ) const -{ - switch ( vehicleType ) { - case Tram: - return "tram"; - case Bus: - return "bus"; - case TrolleyBus: - return "trolleybus"; - case Subway: - return "subway"; - case Metro: - return "metro"; - case InterurbanTrain: - return "interurbantrain"; - case RegionalTrain: - return "regionaltrain"; - case RegionalExpressTrain: - return "regionalexpresstrain"; - case InterregionalTrain: - return "interregionaltrain"; - case IntercityTrain: - return "intercitytrain"; - case HighSpeedTrain: - return "highspeedtrain"; - case Feet: - return "feet"; - case Ship: - return "ship"; - case Plane: - return "plane"; - default: - return QString(); - } -} - -void JourneyRouteGraphicsItem::setShowIntermediateStops( bool showIntermediateStops ) -{ - m_showIntermediateStops = showIntermediateStops; - updateData( m_item ); -} - -void JourneyRouteGraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget ) -{ - Q_UNUSED( option ); - Q_UNUSED( widget ); - - if ( m_item.isNull() ) { - // Item already deleted - return; - } - - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - - const qreal marginLeft = 32.0 * m_zoomFactor; // TODO - const qreal iconSizeConnecting = 32.0 * m_zoomFactor; - const qreal iconSizeIntermediate = 32.0 * m_zoomFactor/* * 0.7*/; - QRectF timelineRect = contentsRect(); - timelineRect.setWidth( marginLeft - 6 * m_zoomFactor ); - timelineRect.setLeft( iconSizeConnecting / 2.0 ); - const qreal routeLineWidth = 4.0 * m_zoomFactor; - - // Draw vertical timeline -#if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - painter->setPen( Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor) ); - painter->setBrush( Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor) ); -#else - painter->setPen( Plasma::Theme::defaultTheme()->color(Plasma::Theme::ViewTextColor) ); - painter->setBrush( Plasma::Theme::defaultTheme()->color(Plasma::Theme::ViewBackgroundColor) ); -#endif - painter->drawRoundedRect( QRectF(timelineRect.left(), timelineRect.top() + padding(), - routeLineWidth, timelineRect.height() - 2 * padding()), - routeLineWidth / 2.0, routeLineWidth / 2.0 ); - - if ( m_routeItems.isEmpty() ) { - return; - } - - qreal stopRadius = 8.0 * m_zoomFactor; - qreal lastY = -stopRadius; - bool hasSubJourneys = false; - for ( int i = 0, index = 0; i < m_routeItems.count() - 1; ++i ) { - JourneyRouteStopGraphicsItem *routeItem = m_routeItems[i]; - JourneyRouteStopGraphicsItem *nextRouteItem = m_routeItems[i + 1]; - const RouteStopFlags flags = routeItem->routeStopFlags(); - const RouteStopFlags nextFlags = nextRouteItem->routeStopFlags(); - const bool isConnectingStop = flags.testFlag( RouteStopIsConnectingStop ); - const bool isOriginStop = i == 0 && flags.testFlag(RouteStopIsOrigin); - if ( m_showIntermediateStops && (isConnectingStop || isOriginStop) ) { - hasSubJourneys = !nextFlags.testFlag(RouteStopIsConnectingStop); - } - if ( (!hasSubJourneys && i > 0) || isConnectingStop ) { - ++index; - } - const qreal iconSize = isConnectingStop ? iconSizeConnecting : iconSizeIntermediate; - qreal y = routeItem->pos().y() + routeItem->size().height(); - QPointF stopPos( timelineRect.left() + routeLineWidth / 2.0, - lastY + (y - lastY) / 2.0 + 1.0 ); - lastY = y; - - // Draw lines to connect to the stop text - qreal lineWidth = iconSize / 2.0; - qreal lineHeight = routeItem->size().height() / 3.0; - painter->drawLine( stopPos.x(), stopPos.y(), stopPos.x() + lineWidth, stopPos.y() ); - painter->drawLine( stopPos.x() + lineWidth, stopPos.y() - lineHeight, - stopPos.x() + lineWidth, stopPos.y() + lineHeight ); - - const QRect stopRect( stopPos.x() - stopRadius, stopPos.y() - stopRadius, - 2.0 * stopRadius, 2.0 * stopRadius ); - const QRectF iconRect = hasSubJourneys ? stopRect.adjusted(-5, -5, 5, 5) - : QRectF( timelineRect.left() + (routeLineWidth - iconSize) / 2.0, - y - iconSize / 2.0, iconSize, iconSize ); - const int shadowWidth = 4; - if ( !hasSubJourneys || (!isConnectingStop && !isOriginStop) ) { - if ( index < m_item->journeyInfo()->routeVehicleTypes().count() ) { - const VehicleType vehicleType = m_item->journeyInfo()->routeVehicleTypes()[index]; - if ( vehicleType == UnknownVehicleType ) { - painter->drawEllipse( iconRect.adjusted(5, 5, -5, -5) ); - painter->drawText( iconRect, "?", QTextOption(Qt::AlignCenter) ); - continue; - } - - const QString vehicleKey = svgVehicleKey( vehicleType ); - if ( !m_svg->hasElement(vehicleKey) ) { - kDebug() << "SVG element" << vehicleKey << "not found"; - } else { - // Draw SVG vehicle element into pixmap - QPixmap vehicleTypePixmap( (int)iconRect.width(), (int)iconRect.height() ); - vehicleTypePixmap.fill( Qt::transparent ); - QPainter p( &vehicleTypePixmap ); - m_svg->resize( iconRect.width() - 2 * shadowWidth, iconRect.height() - 2 * shadowWidth ); - m_svg->paint( &p, shadowWidth, shadowWidth, vehicleKey ); - - // Draw the vehicle type with a shadow TODO: Use shadow setting - QImage shadow = vehicleTypePixmap.toImage(); - Plasma::PaintUtils::shadowBlur( shadow, shadowWidth - 1, Qt::black ); - const QPoint pos = iconRect.topLeft().toPoint(); - painter->drawImage( pos + QPoint(1, 2), shadow ); - painter->drawPixmap( pos, vehicleTypePixmap ); - } - } - } - - if ( !hasSubJourneys || isConnectingStop || isOriginStop ) { - // Draw the stop - KIcon stopIcon = GlobalApplet::stopIcon( routeItem->routeStopFlags() ); - stopIcon.paint( painter, stopRect ); - } - } - - // Draw last stop marker - JourneyRouteStopGraphicsItem *routeItem = m_routeItems.last(); - QPointF stopPos( timelineRect.left() + routeLineWidth / 2.0, - lastY + (timelineRect.bottom() - lastY) / 2.0 + 1.0 ); - qreal lineWidth = iconSizeConnecting / 2.0; - qreal lineHeight = routeItem->size().height() / 3.0; - painter->drawLine( stopPos.x(), stopPos.y(), stopPos.x() + lineWidth, stopPos.y() ); - painter->drawLine( stopPos.x() + lineWidth, stopPos.y() - lineHeight, - stopPos.x() + lineWidth, stopPos.y() + lineHeight ); - KIcon stopIcon = GlobalApplet::stopIcon( routeItem->routeStopFlags() ); - stopIcon.paint( painter, stopPos.x() - stopRadius, stopPos.y() - stopRadius, - 2.0 * stopRadius, 2.0 * stopRadius ); -} diff --git a/applet/routegraphicsitem.h b/applet/routegraphicsitem.h deleted file mode 100644 index 03f92ae..0000000 --- a/applet/routegraphicsitem.h +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef ROUTEGRAPHICSITEM_HEADER -#define ROUTEGRAPHICSITEM_HEADER - -// Own includes -#include "global.h" - -// libpublictransporthelper includes -#include - -// Qt includes -#include // Base class -#include // Member variable - -namespace Plasma { - class Svg; -} - -class DepartureModel; -class JourneyItem; -class DepartureItem; -class RouteStopTextGraphicsItem; -class StopAction; - -/** - * @brief A QGraphicsWidget showing an icon for a single route stop of a public transport vehicle. - **/ -class RouteStopMarkerGraphicsItem : public QGraphicsWidget { - Q_OBJECT - Q_PROPERTY( qreal hoverStep READ hoverStep WRITE setHoverStep ) - -public: - /** - * @brief The type of a RouteStopMarkerGraphicsItem. - **/ - enum MarkerType { - DefaultStopMarker = 0, /**< A default route stop marker. */ - IntermediateStopMarker = 1 /**< A route stop marker for intermediate - * stops that are omitted / not displayed. */ - }; - - enum { Type = UserType + 11 }; - virtual int type() const { return Type; }; - - explicit RouteStopMarkerGraphicsItem( QGraphicsItem* parent = 0, - RouteStopTextGraphicsItem *textItem = 0, - MarkerType markerType = DefaultStopMarker, - RouteStopFlags stopFlags = RouteStopIsIntermediate ); - - /** @brief Gets the radius of the marker circle. */ - qreal radius() const; - - /** @brief Gets the marker type of this item. */ - MarkerType markerType() const { return m_markerType; }; - - /** - * @brief Gets flags for the associated stop. - * - * This method also checks the model for the currently highlighted stop / home stop and sets - * RouteStopIsHighlighted / RouteStopIsHomeStop in the return value if necessary. - **/ - RouteStopFlags routeStopFlags() const; - - RouteStopTextGraphicsItem *textItem() const { return m_textItem; }; - - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - - qreal hoverStep() const { return m_hoverStep; }; - void setHoverStep( qreal expandStep ); - - virtual QRectF boundingRect() const { - return QRectF( -radius() * 1.5, -radius() * 1.5, 2 * radius() * 1.5, 2 * radius() * 1.5 ); - }; - virtual QPainterPath shape() const { QPainterPath p; p.addEllipse(boundingRect()); return p; }; - - virtual QSizeF sizeHint( Qt::SizeHint which, const QSizeF& constraint = QSizeF() ) const { - if ( which == Qt::MinimumSize || which == Qt::MaximumSize ) { - return QSizeF( 2 * radius(), 2 * radius() ); - } else { - return QGraphicsWidget::sizeHint( which, constraint ); - } - }; - -signals: - void hovered( RouteStopMarkerGraphicsItem *item ); - void unhovered( RouteStopMarkerGraphicsItem *item ); - -public slots: - void hover(); - void unhover(); - -protected: - virtual void hoverEnterEvent( QGraphicsSceneHoverEvent* event ); - virtual void hoverLeaveEvent( QGraphicsSceneHoverEvent* event ); - -private: - qreal m_hoverStep; - MarkerType m_markerType; - RouteStopFlags m_stopFlags; - RouteStopTextGraphicsItem *m_textItem; -}; - -/** - * @brief A QGraphicsWidget showing the stop name a single route stop of a public transport vehicle. - * - * On hover it expands too show all of the given stop text (if it is too long). - **/ -class RouteStopTextGraphicsItem : public QGraphicsWidget { - Q_OBJECT - Q_PROPERTY( qreal expandStep READ expandStep WRITE setExpandStep ) - -public: - enum { Type = UserType + 12 }; - virtual int type() const { return Type; }; - - /** - * @brief Create a new route stop text item. - * - * This class visualizes the text shown for a stop in a route. - * RouteStopMarkerGraphicsItem is responsible for showing a stop icon on a "route line". - * - * RouteGraphicsItem normally is the parent of both classes (this and - * RouteStopMarkerGraphicsItem). RouteGraphicsItem creates these items for each route stop - * to be shown. - * - * @param parent The parent item. - * @param model The model which includes the route stop item this RouteStopTextGraphicsItem - * visualizes. - * @param font The font to use. - * @param baseSize A zooming factor. - * @param time The time at which the vehicle is at this route stop. - * @param stopName The name of the stop this RouteStopTextGraphicsItem visualizes. - * @param minsFromFirstRouteStop The time in minutes the vehicle needs from the first stop of - * the route until this route stop. - * @param stopFlags Flags of this route stop, eg. whether or not this is the current home stop. - **/ - RouteStopTextGraphicsItem( QGraphicsItem* parent, DepartureModel *model, const QFont &font, - qreal baseSize, const QDateTime &time, - const QString &stopName, const QString &stopNameShortened, - int minsFromFirstRouteStop, - RouteStopFlags stopFlags = RouteStopIsIntermediate ); - - /** @brief Gets the stop text (stop name including time information). */ - QString stopText() const { return m_stopText; }; - - /** @brief Gets the name of the associated stop. */ - QString stopName() const { return m_stopName; }; - - /** @brief Gets the shortened name of the associated stop. */ - QString stopNameShortened() const { return m_stopNameShortened; }; - - /** - * @brief Gets flags for the associated stop. - * - * This method also checks the model for the currently highlighted stop / home stop and sets - * RouteStopIsHighlighted / RouteStopIsHomeStop in the return value if necessary. - **/ - RouteStopFlags routeStopFlags() const; - - /** - * @brief Sets information about the new associated stop. - * - * @param time The time when the vehicle is at the associated stop. - * @param stopName The name of the associated stop. - * @param stopNameShortened The shortened name of the associated stop. - * @param minsFromFirstRouteStop The time when the vehicle is at the associated stop, - * relative to the first stop. At the first stop this is 0. Use the default value - * (999999) if the times isn't known. - **/ - void setStop( const QDateTime &time, const QString &stopName, const QString &stopNameShortened, - int minsFromFirstRouteStop = 999999 ); - - qreal expandStep() const { return m_expandStep; }; - void setExpandStep( qreal expandStep ); - qreal baseSize() const { return m_baseSize; }; - void setBaseSize( qreal baseSize ); - - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - - DepartureModel *model() const; - -signals: - void hovered( RouteStopTextGraphicsItem *item ); - void unhovered( RouteStopTextGraphicsItem *item ); - -public slots: - void hover(); - void unhover(); - -protected: - virtual void hoverEnterEvent( QGraphicsSceneHoverEvent* event ); - virtual void hoverLeaveEvent( QGraphicsSceneHoverEvent* event ); - virtual void contextMenuEvent( QGraphicsSceneContextMenuEvent* event ); - -private: - QString m_stopText; - QString m_stopName; - QString m_stopNameShortened; - qreal m_expandStep; - qreal m_baseSize; - RouteStopFlags m_stopFlags; - DepartureModel *m_model; -}; - -/** - * @brief A QGraphicsWidget showing the route of a public transport vehicle. - * - * The route is shown as a thick white line with stop markers on it. For each - * stop marker the associated stop name is shown with it's departure time. - * This widget automatically shows/hides stops on size changes. - **/ -class RouteGraphicsItem : public QGraphicsWidget { - Q_OBJECT - -public: - enum { Type = UserType + 10 }; - virtual int type() const { return Type; }; - - RouteGraphicsItem( QGraphicsItem* parent, DepartureItem *item, - StopAction *copyStopToClipboardAction = 0, StopAction *showInMapAction = 0, - StopAction *showDeparturesAction = 0, StopAction *highlightStopAction = 0, - StopAction *newFilterViaStopAction = 0 ); - - void updateData( DepartureItem *item ); - - /** - * @brief The minimal distance between two stop items. - * If not all route stops fit into the route item, some stops in the middle are left out. - **/ - static int minStopDistance( const QFontMetrics &fontMetrics ); - - /** - * @brief Compute text angle for route stop names. - * The text angle gets chosen so that the stop names do not overlap when using a font with - * the given @p fontMetrics. - **/ - static qreal textAngle( const QFontMetrics &fontMetrics, qreal zoomFactor = 1.0 ); - - /** - * @brief Compute maximal text width for the computed angle. - * so that the stop name won't go outside of routeRect. - **/ - qreal maxTextWidth( qreal height, int fontHeight ) const; - - void setZoomFactor( qreal zoomFactor = 1.0 ); - qreal zoomFactor() const { return m_zoomFactor; }; - inline qreal padding() const { return 5.0; }; - - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - - const DepartureItem *item() const { return m_item.data(); }; - -protected: - virtual void resizeEvent( QGraphicsSceneResizeEvent *event ); - virtual void showEvent( QShowEvent *event ); - void arrangeStopItems(); - - /** @brief Get the position for stop text (before rotation) from the stop marker position. */ - QPointF stopTextPosition( const QFontMetrics &fontMetrics, - const QPointF &stopMarkerPosition ) const; - -private: - QPointer m_item; - QList m_markerItems; - QList m_textItems; - qreal m_zoomFactor; - qreal m_textAngle; - qreal m_maxTextWidth; - StopAction *m_copyStopToClipboardAction; - StopAction *m_showInMapAction; - StopAction *m_showDeparturesAction; - StopAction *m_highlightStopAction; - StopAction *m_newFilterViaStopAction; -}; - -class JourneyRouteGraphicsItem; -/** - * @brief A QGraphicsWidget showing a single route stop of a journey. - **/ -class JourneyRouteStopGraphicsItem : public QGraphicsWidget { - Q_OBJECT - -public: - enum { Type = UserType + 13 }; - virtual int type() const { return Type; }; - - JourneyRouteStopGraphicsItem( JourneyRouteGraphicsItem* parent, const QPixmap &vehiclePixmap, - const QString &text, RouteStopFlags routeStopFlags, - const QString &stopName, const QString &stopNameShortened ); - virtual ~JourneyRouteStopGraphicsItem(); - - void setText( const QString &text ); - - QRectF infoTextRect() const; - - /** - * @brief Gets flags for the associated stop. - * - * This method also checks the model for the currently highlighted stop / home stop and sets - * RouteStopIsHighlighted / RouteStopIsHomeStop in the return value if necessary. - **/ - RouteStopFlags routeStopFlags() const; - - QString stopName() const { return m_stopName; }; - QString stopNameShortened() const { return m_stopNameShortened; }; - - void setZoomFactor( qreal zoomFactor = 1.0 ); - qreal zoomFactor() const { return m_zoomFactor; }; - - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - -protected: - virtual QSizeF sizeHint( Qt::SizeHint which, const QSizeF& constraint = QSizeF() ) const; - virtual void contextMenuEvent( QGraphicsSceneContextMenuEvent* event ); - virtual void hoverLeaveEvent( QGraphicsSceneHoverEvent* /*event*/ ) { update(); }; - -private: - JourneyRouteGraphicsItem *m_parent; - QPixmap m_vehiclePixmap; - QTextDocument *m_infoTextDocument; - - RouteStopFlags m_stopFlags; - QString m_stopName; - QString m_stopNameShortened; - qreal m_zoomFactor; -}; - -/** - * @brief A QGraphicsWidget showing the route of a journey. - **/ -class JourneyRouteGraphicsItem : public QGraphicsWidget { - Q_OBJECT - -public: - JourneyRouteGraphicsItem( QGraphicsItem *parent, JourneyItem *item, Plasma::Svg *svg, - StopAction *copyStopToClipboardAction = 0, - StopAction *showInMapAction = 0, - StopAction *requestJourneyToStopAction = 0, - StopAction *requestJourneyFromStopAction = 0 ); - - void updateData( JourneyItem *item ); - - bool showIntermediateStops() const { return m_showIntermediateStops; }; - void setShowIntermediateStops( bool showIntermediateStops ); - - void setZoomFactor( qreal zoomFactor = 1.0 ); - qreal zoomFactor() const { return m_zoomFactor; }; - inline qreal padding() const { return 5.0; }; - Plasma::Svg *svg() const { return m_svg; }; - JourneyItem *journeyItem() const { return m_item; }; - - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - - QString svgVehicleKey( PublicTransport::VehicleType vehicleType ) const; - -private: - QPointer m_item; - Plasma::Svg *m_svg; - qreal m_zoomFactor; - QList m_routeItems; - StopAction *m_copyStopToClipboardAction; - StopAction *m_showInMapAction; - StopAction *m_requestJourneyToStopAction; - StopAction *m_requestJourneyFromStopAction; - bool m_showIntermediateStops; -}; - -#endif // Multiple inclusion guard diff --git a/applet/settings.cpp b/applet/settings.cpp deleted file mode 100644 index 9d749d1..0000000 --- a/applet/settings.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Own includes -#include "settings.h" - -Settings::Settings() : m_settingsFlags(DefaultSettingsFlags), - m_departureTimeFlags(DefaultDepartureTimeFlags), - m_additionalDataRequestType(DefaultAdditionalDataRequestType), m_currentStopIndex(0) -{ -} - -Settings::Settings( const Settings& other ) -{ - *this = other; -} - -Settings::~Settings() -{ -} - -void Settings::favorJourneySearch( const QString &journeySearch ) -{ - QList< JourneySearchItem > journeySearches = currentJourneySearches(); - for ( int i = 0; i < journeySearches.count(); ++i ) { - if ( journeySearches[i].journeySearch() == journeySearch ) { - journeySearches[i].setFavorite( true ); - setCurrentJourneySearches( journeySearches ); - break; - } - } -} - -void Settings::removeJourneySearch( const QString &journeySearch ) -{ - QList< JourneySearchItem > journeySearches = currentJourneySearches(); - for ( int i = 0; i < journeySearches.count(); ++i ) { - if ( journeySearches[i].journeySearch() == journeySearch ) { - journeySearches.removeAt( i ); - setCurrentJourneySearches( journeySearches ); - break; - } - } -} - -void Settings::addRecentJourneySearch( const QString &journeySearch ) -{ - QList< JourneySearchItem > journeySearches = currentJourneySearches(); - for ( int i = 0; i < journeySearches.count(); ++i ) { - if ( journeySearches[i].journeySearch() == journeySearch ) { - // Do not add already existing journey search strings - return; - } - } - - // The given journeySearch string is not already in journeySearches, add it - journeySearches << JourneySearchItem( journeySearch ); - setCurrentJourneySearches( journeySearches ); -} - -bool AlarmSettings::isOneTimeAlarm() const -{ - return type == AlarmRemoveAfterFirstMatch || filter.isOneTimeFilter(); -} - -bool AlarmSettings::isExpired() const -{ - if ( !isOneTimeAlarm() ) { - return false; - } - - return filter.isExpired(); -} - -QStringList AlarmSettingsList::names() const { - QStringList ret; - foreach ( const AlarmSettings &alarm, *this ) { - ret << alarm.name; - } - return ret; -} - -bool AlarmSettingsList::hasName( const QString& name ) const { - foreach ( const AlarmSettings &alarm, *this ) { - if ( alarm.name == name ) { - return true; - } - } - return false; // No alarm with the given name found -} - -AlarmSettings AlarmSettingsList::byName( const QString& name ) const { - foreach ( const AlarmSettings &alarm, *this ) { - if ( alarm.name == name ) { - return alarm; - } - } - return AlarmSettings(); // No alarm with the given name found -} - -void AlarmSettingsList::removeByName( const QString& name ) { - for ( int i = 0; i < count(); ++i ) { - if ( operator[](i).name == name ) { - removeAt( i ); - return; - } - } - - kDebug() << "No alarm with the given name found:" << name; - kDebug() << "Available names are:" << names(); -} - -void AlarmSettingsList::set( const AlarmSettings& newAlarmSettings ) { - for ( int i = 0; i < count(); ++i ) { - if ( operator[](i).name == newAlarmSettings.name ) { - operator[]( i ) = newAlarmSettings; - return; - } - } - - // No alarm with the given name found, add newAlarmSettings to this list - *this << newAlarmSettings; -} - -bool AlarmSettingsList::removeAlarm( const AlarmSettings &alarm ) -{ - for ( int i = 0; i < count(); ++i ) { - const AlarmSettings currentAlarm = operator[]( i ); - if ( currentAlarm.equals(alarm) ) { - removeAt( i ); - return true; - } - } - return false; -} - -bool operator ==( const AlarmSettings &l, const AlarmSettings &r ) -{ - return l.name == r.name && l.enabled == r.enabled && l.type == r.type - && l.affectedStops == r.affectedStops && l.filter == r.filter - && l.lastFired == r.lastFired; -} - -bool operator==( const ColorGroupSettings &l, const ColorGroupSettings &r ) -{ - return l.color == r.color && l.filters == r.filters && l.filterOut == r.filterOut - && l.target == r.target; -} - -ColorGroupSettings ColorGroupSettingsList::byColor( const QColor &color ) { - foreach ( const ColorGroupSettings &colorSettings, *this ) { - if ( colorSettings.color == color ) { - return colorSettings; - } - } - - // No color group with the given color found, return an "empty" object - return ColorGroupSettings(); -} - -void ColorGroupSettingsList::set( const ColorGroupSettings &newColorGroupSettings ) { - for ( int i = 0; i < count(); ++i ) { - if ( operator[](i).color == newColorGroupSettings.color ) { - operator[]( i ) = newColorGroupSettings; - return; - } - } - - // No color group with the given color found, add newColorGroupSettings to this list - *this << newColorGroupSettings; -} - -bool ColorGroupSettingsList::hasColor( const QColor &color ) const { - for ( int i = 0; i < count(); ++i ) { - if ( operator[](i).color == color ) { - return true; - } - } - - return false; -} - -bool ColorGroupSettingsList::removeColor( const QColor &color ) { - for ( int i = 0; i < count(); ++i ) { - if ( operator[](i).color == color ) { - removeAt( i ); - return true; - } - } - - return false; -} - -void ColorGroupSettingsList::enableColorGroup( const QColor &color, bool enable ) { - for ( int i = 0; i < count(); ++i ) { - if ( operator[](i).color == color ) { - operator[](i).filterOut = !enable; - return; - } - } -} - -bool ColorGroupSettingsList::filterOut( const PublicTransport::DepartureInfo &departureInfo ) const { - foreach ( const ColorGroupSettings &colorSettings, *this ) { - if ( colorSettings.filterOut && colorSettings.matches(departureInfo) ) { - return true; - } - } - - return false; -} - -void Settings::adjustColorGroupSettingsCount() -{ - while ( m_colorGroups.count() < m_stops.count() ) { - m_colorGroups << ColorGroupSettingsList(); - } - while ( m_colorGroups.count() > m_stops.count() ) { - m_colorGroups.removeLast(); - } -} - -FilterSettingsList Settings::currentFilters() const -{ - // Construct filter settings per stop (constructed of multiple filter configs) - FilterSettingsList activeFilterSettings; - foreach( const FilterSettings &filters, m_filters ) { - if ( filters.affectedStops.contains(m_currentStopIndex) ) { - activeFilterSettings << filters; - } - } - return activeFilterSettings; -} - -ColorGroupSettingsList Settings::currentColorGroups() const -{ - if ( m_currentStopIndex >= 0 && - m_currentStopIndex < m_colorGroups.count() ) - { - return m_colorGroups[ m_currentStopIndex ]; - } else { - return ColorGroupSettingsList(); - } -} - -QFont Settings::sizedFont() const -{ - QFont f = m_font; - if ( f.pointSize() == -1 ) { - int pixelSize = f.pixelSize() * m_sizeFactor; - f.setPixelSize( pixelSize > 0 ? pixelSize : 1 ); - } else { - int pointSize = f.pointSize() * m_sizeFactor; - f.setPointSize( pointSize > 0 ? pointSize : 1 ); - } - return f; -} - -bool Settings::checkConfig() -{ - // TODO: Check when adding stops in StopSettingsDialog - if ( m_stops.isEmpty() ) { - return false; - } else { - foreach ( const StopSettings &stop, m_stops ) { - if ( stop.stops().isEmpty() ) { - return false; - } else { - foreach ( const QString &stop, stop.stops() ) { - if ( stop.isEmpty() ) { - return false; - } - } - } - } - } - return true; - - // if ( m_useSeparateCityValue && (m_city.isEmpty() - // || m_stops.isEmpty() || m_stops.first().isEmpty()) ) - // emit configurationRequired(true, i18n("Please set a city and a stop.")); - // else if ( m_stops.isEmpty() || m_stops.first().isEmpty() ) - // emit configurationRequired(true, i18n("Please set a stop.")); - // else if ( m_serviceProvider == "" ) - // emit configurationRequired(true, i18n("Please select a service provider.")); - // else { - // emit configurationRequired(false); - // return true; - // } - // - // return false; -} diff --git a/applet/settings.h b/applet/settings.h deleted file mode 100644 index c6d67ca..0000000 --- a/applet/settings.h +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file - * @brief This file contains classes for managing settings of the PublicTransport applet. - * @author Friedrich Pülz */ - -#ifndef SETTINGS_HEADER -#define SETTINGS_HEADER - -// Own includes -#include "global.h" -#include "journeysearchitem.h" - -// libpublictransporthelper includes -#include -#include - -// Qt includes -#include - -using namespace PublicTransport; - -/** @brief Different types of alarms. */ -enum AlarmType { - AlarmRemoveAfterFirstMatch = 0, /**< The alarm will get removed once fired. */ - AlarmApplyToNewDepartures /**< The alarm won't be removed automatically, - * ie. it's a recurring alarm. */ -}; - -/** @brief Contains information about an alarm. */ -struct AlarmSettings { - AlarmSettings( const QString &name = "", bool autoGenerated = false ) { - this->name = name; - this->autoGenerated = autoGenerated; - this->enabled = true; - this->type = AlarmRemoveAfterFirstMatch; - }; - - QString name; /**< The name of this alarm. */ - bool enabled; /**< Whether or not this alarm is enabled. */ - bool autoGenerated; /**< Whether or not this alarm is auto generated. - * Alarms that get changed are no longer treated as auto generated. */ - Filter filter; /**< The filter to match alarm departures/arrivals. */ - AlarmType type; /**< The type of this alarm. */ - QList< int > affectedStops; /**< A list of stop settings indices for which - * this alarm should be applied. */ - QDateTime lastFired; /**< The time when this alarm was last fired. */ - - /** - * @brief Compares this alarm with @p other without checking the name or target constraints. - * - * Can be used to find an autogenerated alarm in an AlarmSettingsList. - * @param other The other alarm to compare with. - * @return bool True, if the alarms are "equal", ie. both autogenerated and equal except - * for the name/affectedStops/lastFired settings and without target constraints in the - * filter of this alarm. - **/ - bool equals( const AlarmSettings &other ) const { - return autoGenerated == other.autoGenerated && type == other.type && - enabled == other.enabled && filter == other.filter; - }; - - bool isOneTimeAlarm() const; - bool isExpired() const; // only if one time alarm -}; -bool operator ==( const AlarmSettings &l, const AlarmSettings &r ); - -/** @brief A QList of AlarmSettings with some convenience methods. */ -class AlarmSettingsList : public QList< AlarmSettings > { -public: - /** @brief Get a list of the names of all alarm settings in this list. */ - QStringList names() const; - - /** @brief Checks if there is an alarm settings object with the given @p name in this list. */ - bool hasName( const QString &name ) const; - - /** - * @brief Gets the alarm settings object with the given @p name from this list. - * - * If there is no such alarm settings object, a default constructed AlarmSettings object - * gets returned. - **/ - AlarmSettings byName( const QString &name ) const; - - /** @brief Removes the alarm settings object with the given @p name from this list. */ - void removeByName( const QString &name ); - - /** @brief Adds the given @p newAlarmSettings to this list or changes an existing one with - * the same name, if there is already one in this list. */ - void set( const AlarmSettings &newAlarmSettings ); - - /** @brief Remove one alarm which is equal to @p alarm according to AlarmSettings::equals(). */ - bool removeAlarm( const AlarmSettings &alarm ); -}; - -/** @brief Contains information about a color group configuration, ie. it's color. */ -struct ColorGroupSettings { - /** - * @brief A list of filters for this color group configuration. - * - * Filters are OR combined while constraints are AND combined. - * Departures/arrivals that match these filters are colored using the - * coor of this ColorGroupSettings. - **/ - FilterList filters; - - /**< @brief The color of this color group. */ - QColor color; - - /**< @brief Whether or not departures in this color group should be filtered out. */ - bool filterOut; - - QString target; - QString displayText; - - ColorGroupSettings( const QColor &color = Qt::transparent ) { - this->color = color; - this->filterOut = false; - }; - - /** @brief Applies the filters of this color group configuration - * on the given @p departureInfo. */ - bool matches( const DepartureInfo& departureInfo ) const { - return filters.match( departureInfo ); - }; -}; -bool operator ==( const ColorGroupSettings &l, const ColorGroupSettings &r ); - -/** @brief A list of ColorGroupSettings. */ -class ColorGroupSettingsList : public QList< ColorGroupSettings > { -public: - /** @brief Find a ColorGroupSettings object with the given @p color in the list. */ - ColorGroupSettings byColor( const QColor &color ); - - void set( const ColorGroupSettings& newColorGroupSettings ); - - /** @brief Checks if there is a color group settings object with the given @p color in this list. */ - bool hasColor( const QColor &color ) const; - - /** @brief Removes the color group settings object with the given @p color from this list. */ - bool removeColor( const QColor &color ); - - /** @brief Enables/disables the ColorGroupSettings object with the given @p color in the list. */ - void enableColorGroup( const QColor &color, bool enable = true ); - - /** - * @brief Applies the filters of the color group configurations in the list to @p departureInfo. - * - * @param departureInfo The departure to test. - * @returns True, if the given @p departureInfo matches a filtered out color group in the list. - * False, otherwise. - **/ - bool filterOut( const DepartureInfo& departureInfo ) const; -}; - -inline uint qHash( const QStringList &key ) -{ - uint result = 1; - foreach ( const QString &k, key ) { - result += qHash( k ); - } - return result; -} - -/** - * @brief Contains all settings of the PublicTransport applet. - * - * Use SettingsIO to read/write settings from/to disk, use SettingsUiManager to synchronize and - * connect the settings in this class with the widget states in the configuration dialog. - **/ -class Settings { -public: - /** @brief Named user settings for use in PublicTransport::StopSettings. */ - enum ExtendedStopSetting { - JourneySearchSetting = UserSetting /**< A list of journey searches, - * QList\ */ - }; - - /** @brief Global boolean settings. */ - enum SettingsFlag { - NoSettingsFlags = 0x0000, - - ColorizeDepartureGroups = 0x0001, - DrawShadows = 0x0004, - HideTargetColumn = 0x0008, - UseThemeFont = 0x0010, - - DefaultSettingsFlags = ColorizeDepartureGroups | DrawShadows | UseThemeFont - }; - Q_DECLARE_FLAGS( SettingsFlags, SettingsFlag ) - - /** @brief Boolean flags controlling the departure time display. */ - enum DepartureTimeFlag { - DoNotShowDepartureTime = 0x00, - - ShowDepartureTime = 0x01, - ShowRemainingTime = 0x02, - DisplayDepartureTimeBold = 0x04, - - ShowTimeAndRemainingTime = ShowDepartureTime | ShowRemainingTime, - DefaultDepartureTimeFlags = ShowTimeAndRemainingTime | DisplayDepartureTimeBold - }; - Q_DECLARE_FLAGS( DepartureTimeFlags, DepartureTimeFlag ) - - /** @brief Different ways to request additional timetable data. */ - enum AdditionalDataRequestType { - NeverRequestAdditionalData, - - RequestAdditionalDataWhenNeeded, - RequestAdditionalDataDirectly, - - DefaultAdditionalDataRequestType = RequestAdditionalDataWhenNeeded - }; - - /** - * @brief Gets the size factor to be used for the given @p size value. - * - * @param size An integer value, ie. configurable using a slider widget. Smallest value is null. - * @return The size factor associated with the given @p size value. - **/ - static inline qreal sizeFactorFromSize( int size ) { return (size + 3) * 0.2; }; - - /** - * @brief Gets the integer size value to be used for the given @p sizeFactor. - * - * @param sizeFactor The zoom factor, to get the integer size value for. - * @return The size associated with the given @p sizeFactor. - **/ - static inline int sizeFromSizeFactor( qreal sizeFactor ) { return qRound(sizeFactor / 0.2) - 3; }; - - /** @brief Creates a new Settings object. */ - Settings(); - - /** @brief Copy constructor. */ - Settings( const Settings &other ); - - /** @brief Destructor. */ - virtual ~Settings(); - - /** - * @brief Get the stop settings with the given @p stopIndex. - * @see currentStop() - **/ - inline const StopSettings stop( int stopIndex ) const { - return m_stops[stopIndex]; }; - - /** - * @brief Get the filter settings with the given @p filterIndex. - * @see currentFilters() - **/ - inline const FilterSettings filter( int filterIndex ) const { - return m_filters[filterIndex]; }; - - /** @brief Get the alarm settings with the given @p alarmIndex. */ - inline const AlarmSettings alarm( int alarmIndex ) const { - return m_alarms[alarmIndex]; }; - - /** - * @brief Get color group settings with the given @p stopIndex. - * @note If colorize() returns false these color groups won't be used. - * @see currentColorGroups() - * @see colorGroups() - **/ - inline const ColorGroupSettingsList colorGroup( int stopIndex ) const { - return m_colorGroups[stopIndex]; }; - - /** - * @brief A list of all stop settings. - * @see stop() - * @see currentStop() - **/ - inline const StopSettingsList stops() const { return m_stops; }; - - /** - * @brief A list of all filter settings. - * @note FilterSettings objects returned in the list may not apply to the current stop. - * To get a list of filter settings that apply to the current stop use currentFilters(). - * @see filters() - * @see currentFilters() - **/ - inline const FilterSettingsList filters() const { return m_filters; }; - - /** - * @brief A list of all alarm settings. - * @note AlarmSettings objects returned in the list may not apply to the current stop. - * To get a list of alarm settings that apply to the current stop use currentAlarms(). - * @see alarm() - **/ - inline const AlarmSettingsList alarms() const { return m_alarms; }; - - /** - * @brief A list of all color group settings lists (one list for each stop). - * @note If colorize() returns false these color groups won't be used. - * @note Color group settings returned in the list may not apply to the current stop. - * To get a list of color groups that apply to the current stop use currentColorGroups(). - * @see colorGroup() - **/ - inline const QList colorGroups() const { - return m_colorGroups; }; - - /** - * @brief Gets the currently used stop settings. - * - * If the current stop settings index is invalid an empty StopSettings object gets returned. - * @see isCurrentStopIndexValid - **/ - const StopSettings currentStop() const { - if ( !isCurrentStopIndexValid() ) { - kDebug() << "Current stop index invalid" << m_currentStopIndex - << "Stop settings count:" << m_stops.count(); - return StopSettings(); - } - return m_stops[ m_currentStopIndex ]; - }; - - /** - * @brief Gets a modifyable reference to the currently used stop settings. - * @warning This crashes with invalid stop settings index. - * @see isCurrentStopIndexValid - **/ - StopSettings ¤tStop() { - Q_ASSERT_X( isCurrentStopIndexValid(), "StopSettings::currentStop", - QString("There's no stop settings with index %1 to get a " - "reference to").arg(m_currentStopIndex).toLatin1() ); - return m_stops[ m_currentStopIndex ]; - }; - - /** @brief Get a list of all filters that apply to the current stop. */ - FilterSettingsList currentFilters() const; - - /** @brief Get a list of all alarms that apply to the current stop. */ - AlarmSettingsList currentAlarms() const; - - /** @brief Get a list of all color groups that apply to the current stop. */ - ColorGroupSettingsList currentColorGroups() const; - - /** @brief Whether or not the index of the currently used stop is valid. */ - bool isCurrentStopIndexValid() const { - return m_currentStopIndex >= 0 && - m_currentStopIndex < m_stops.count(); - }; - - /** @brief Remove all intermediate stops. */ - void removeIntermediateStops() { m_stops.removeIntermediateStops(); }; - - /** - * @brief Append @p stop to the list of stops. - * @see stops() - **/ - void appendStop( const StopSettings &stop ) { m_stops << stop; }; - - /** - * @brief Append @p filter to the list of filters. - * @see filters() - **/ - void appendFilter( const FilterSettings &filter ) { m_filters << filter; }; - - /** - * @brief Append @p alarm to the list of alarms. - * @see alarms() - **/ - void appendAlarm( const AlarmSettings &alarm ) { m_alarms << alarm; }; - - /** - * @brief Remove @p alarm from the list of alarms. - * @see alarms() - **/ - void removeAlarm( const AlarmSettings &alarm ) { m_alarms.removeAlarm(alarm); }; - - /** - * @brief The index of the currently used stop settings. - * - * Use currentStop() to get the StopSettings object, this index is pointing at. If - * isCurrentStopIndexValid() returns false, this index is not in the range of - * available stop settings. - * - * @see currentStop - * @see isCurrentStopIndexValid - **/ - inline int currentStopIndex() const { return m_currentStopIndex; }; - - /** @brief How many lines each row in the departure/arrival view should have. */ - inline int linesPerRow() const { return m_linesPerRow; }; - - /** @brief The maximal number of displayed departures. */ - inline int maximalNumberOfDepartures() const { return m_maximalNumberOfDepartures; }; - - /** - * @brief A zoom factor to use for item/font sizes. - * - * This value gets stored and configured in the dialog as integer. This integer gets converted - * to the size factor using sizeFactorFromSize. To convert back sizeFromSizeFactor is used. - **/ - inline float sizeFactor() const { return m_sizeFactor; }; - - /** - * @brief The type of data to be shown in the default timetable view. - * - * The default timetable view can show either departures or arrivals. - **/ - inline DepartureArrivalListType departureArrivalListType() const { - return m_departureArrivalListType; }; - - /** @brief Whether or not shadows should be drawn in the applet. */ - inline bool drawShadows() const { return m_settingsFlags.testFlag(DrawShadows); }; - - /** @brief Whether or not the target/origin column should be shown in the departure view. */ - inline bool hideTargetColumn() const { return m_settingsFlags.testFlag(HideTargetColumn); }; - - /** @brief Whether or not the default plasma theme's font is used. */ - inline bool useThemeFont() const { return m_settingsFlags.testFlag(UseThemeFont); }; - - /** @brief Whether or not departures should be colorized by groups. */ - inline bool colorize() const { return m_settingsFlags.testFlag(ColorizeDepartureGroups); }; - - /** @brief Whether or not the departure time should be shown in the timetable. */ - inline bool showDepartureTime() const { - return m_departureTimeFlags.testFlag(ShowDepartureTime); }; - - /** @brief Whether or not the remaining time should be shown in the timetable. */ - inline bool showRemainingTime() const { - return m_departureTimeFlags.testFlag(ShowRemainingTime); }; - - /** @brief Whether or not the departure time should be displayed bold in the timetable. */ - inline bool displayDepartureTimeBold() const { - return m_departureTimeFlags.testFlag(DisplayDepartureTimeBold); }; - - /** - * @brief Contains some global boolean settings. - * @since 0.11 - **/ - inline SettingsFlags settingsFlags() const { return m_settingsFlags; }; - - /** - * @brief Flags for the display of the departure/arrival time. - * - * Before version 0.11, there were three boolean settings "showRemainingMinutes", - * "showDepartureTime" and "displayTimeBold", now replaced with the DepartureTimeFlags - * enumeration. - * @since 0.11 - **/ - inline DepartureTimeFlags departureTimeFlags() const { return m_departureTimeFlags; }; - - inline AdditionalDataRequestType additionalDataRequestType() const { - return m_additionalDataRequestType; }; - - /** - * @brief The font to be used in the applet. - * - * This font gets only used, if useThemeFont() returns false. - * @note If the font size is smaller than the font size of KGlobalSettings::smallestReadableFont - * that smallest readable font gets used instead to ensure best possible readability. - * @see sizedFont() - **/ - QFont font() const { return m_font; }; - - /** @brief Gets font with the size zoomed by sizeFactor. */ - QFont sizedFont() const; - - /** @brief Get a list of JourneySearchItem's for the current stop settings. */ - QList currentJourneySearches() const { - return currentStop().get< QList >( JourneySearchSetting ); - }; - - /** @brief Whether or not shadows should be drawn in the applet. */ - void setDrawShadows( bool drawShadows ) { - if ( m_settingsFlags.testFlag(DrawShadows) != drawShadows ) - m_settingsFlags ^= DrawShadows; - }; - - /** @brief Whether or not the target/origin column should be shown in the departure view. */ - void setHideTargetColumn( bool hideTargetColumn ) { - if ( m_settingsFlags.testFlag(HideTargetColumn) != hideTargetColumn ) - m_settingsFlags ^= HideTargetColumn; - }; - - /** @brief Whether or not the default plasma theme's font is used. */ - void setUseThemeFont( bool useThemeFont ) { - if ( m_settingsFlags.testFlag(UseThemeFont) != useThemeFont ) - m_settingsFlags ^= UseThemeFont; - }; - - /** @brief Whether or not departures should be colorized by groups. */ - void setColorize( bool colorize ) { - if ( m_settingsFlags.testFlag(ColorizeDepartureGroups) != colorize ) - m_settingsFlags ^= ColorizeDepartureGroups; - }; - - void setShowDepartureTime( bool showDepartureTime ) { - if ( m_departureTimeFlags.testFlag(ShowDepartureTime) != showDepartureTime ) - m_departureTimeFlags ^= ShowDepartureTime; - }; - - void setShowRemainingTime( bool showRemainingTime ) { - if ( m_departureTimeFlags.testFlag(ShowRemainingTime) != showRemainingTime ) - m_departureTimeFlags ^= ShowRemainingTime; - }; - - void setDisplayDepartureTimeBold( bool displayDepartureTimeBold ) { - if ( m_departureTimeFlags.testFlag(DisplayDepartureTimeBold) != displayDepartureTimeBold ) - m_departureTimeFlags ^= DisplayDepartureTimeBold; - }; - - void setFont( const QFont &font ) { m_font = font; }; - - void setSettingsFlags( SettingsFlags settingsFlags ) { m_settingsFlags = settingsFlags; }; - - void setDepartureTimeFlags( DepartureTimeFlags departureTimeFlags ) { - m_departureTimeFlags = departureTimeFlags; }; - - void setAdditionalDataRequestType( AdditionalDataRequestType additionalDataRequestType ) { - m_additionalDataRequestType = additionalDataRequestType; }; - - /** @brief Sets a list of JourneySearchItem's for the current stop settings. */ - void setCurrentJourneySearches( const QList &journeySearches ) { - StopSettings &stop = currentStop(); - stop.set( JourneySearchSetting, QVariant::fromValue(journeySearches) ); - }; - - void setMaximalNumberOfDepartures( int maximalNumberOfDepartures ) { - m_maximalNumberOfDepartures = maximalNumberOfDepartures; }; - - void setLinesPerRow( int linesPerRow ) { m_linesPerRow = linesPerRow; }; - - void setSizeFactor( float sizeFactor ) { m_sizeFactor = sizeFactor; }; - - void setDepartureArrivalListType( DepartureArrivalListType type ) { - m_departureArrivalListType = type; }; - - void setCurrentStop( int stopIndex ) { - m_currentStopIndex = qBound(0, stopIndex, m_stops.count()); }; - - /** @brief A list of all stop settings. */ - void setStops( const StopSettingsList & stopList ) { m_stops = stopList; }; - - /** @brief A list of all filter settings. */ - void setFilters( const FilterSettingsList &filters ) { m_filters = filters; }; - - /** @brief A list of all alarm settings. */ - void setAlarms( const AlarmSettingsList &alarms ) { m_alarms = alarms; }; - - void setColorGroups( const QList &colorGroups ) { - m_colorGroups = colorGroups; }; - - /** @brief Ensures that there is one color group settings list for each stop setting. */ - void adjustColorGroupSettingsCount(); - - /** @brief Favorize the given @p journeySearch.*/ - void favorJourneySearch( const QString &journeySearch ); - - /** @brief Removes the given @p journeySearch from the list of favored/recent journey searches. */ - void removeJourneySearch( const QString &journeySearch ); - - /** - * @brief Add the given @p journeySearch to the list of recent journey searches. - * - * If @p journeySearch is a favored journey search, this function does nothing. - **/ - void addRecentJourneySearch( const QString &journeySearch ); - - bool checkConfig(); - -private: - SettingsFlags m_settingsFlags; - DepartureTimeFlags m_departureTimeFlags; - AdditionalDataRequestType m_additionalDataRequestType; - - StopSettingsList m_stops; - FilterSettingsList m_filters; - AlarmSettingsList m_alarms; - QList m_colorGroups; - - DepartureArrivalListType m_departureArrivalListType; - QFont m_font; - int m_currentStopIndex; - int m_linesPerRow; - int m_maximalNumberOfDepartures; - float m_sizeFactor; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS( Settings::DepartureTimeFlags ) -Q_DECLARE_OPERATORS_FOR_FLAGS( Settings::SettingsFlags ) - -#endif // SETTINGS_HEADER diff --git a/applet/settingsio.cpp b/applet/settingsio.cpp deleted file mode 100644 index ed87b73..0000000 --- a/applet/settingsio.cpp +++ /dev/null @@ -1,579 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "settingsio.h" - -// KDE+Plasma includes -#include -#include -#include - -Settings SettingsIO::readSettings( KConfigGroup cg, KConfigGroup cgGlobal, - Plasma::DataEngine *publictransportEngine ) -{ - Settings settings; - if ( !cg.hasKey("departureTimeFlags") && (cg.hasKey("showRemainingMinutes") || - cg.hasKey("showDepartureTime") || cg.hasKey("displayTimeBold")) ) - { - // DEPRECATED This reads settings stored in old format (version < 0.11) - kDebug() << "Reading settings in old format, will be converted to new format"; - settings.setShowRemainingTime( cg.readEntry("showRemainingMinutes", true) ); - cg.deleteEntry( "showRemainingMinutes" ); - - - settings.setDisplayDepartureTimeBold( cg.readEntry("displayTimeBold", true) ); - cg.deleteEntry( "displayTimeBold" ); - - cg.writeEntry( "departureTimeFlags", static_cast(settings.departureTimeFlags()) ); - cg.sync(); - } else { - settings.setDepartureTimeFlags( static_cast< Settings::DepartureTimeFlags >( - cg.readEntry("departureTimeFlags", static_cast(Settings::DefaultDepartureTimeFlags))) ); - } - - const Settings::AdditionalDataRequestType requestType = static_cast< Settings::AdditionalDataRequestType >( - cg.readEntry("additionalDataRequestType", static_cast(Settings::DefaultAdditionalDataRequestType))); - settings.setAdditionalDataRequestType( requestType ); - - // Read stop settings TODO: Store in config groups like filters - StopSettingsList stopSettingsList; - int stopSettingCount = cgGlobal.readEntry( "stopSettings", 1 ); - QString test = "location"; - int i = 1; - while ( cgGlobal.hasKey(test) ) { - StopSettings stopSettings; - QString suffix = i == 1 ? QString() : '_' + QString::number( i ); - stopSettings.set( LocationSetting, cgGlobal.readEntry("location" + suffix, "showAll") ); - stopSettings.set( ServiceProviderSetting, - cgGlobal.readEntry("serviceProvider" + suffix, "de_db") ); - stopSettings.set( CitySetting, cgGlobal.readEntry("city" + suffix, QString()) ); - stopSettings.setStops( cgGlobal.readEntry("stop" + suffix, QStringList()), - cgGlobal.readEntry("stopID" + suffix, QStringList()) ); - stopSettings.set( TimeOffsetOfFirstDepartureSetting, - cgGlobal.readEntry("timeOffsetOfFirstDeparture" + suffix, 0) ); - stopSettings.set( TimeOfFirstDepartureSetting, - QTime::fromString(cgGlobal.readEntry("timeOfFirstDepartureCustom" + suffix, - "12:00"), "hh:mm") ); - stopSettings.set( FirstDepartureConfigModeSetting, - cgGlobal.readEntry("firstDepartureConfigMode" + suffix, - static_cast(RelativeToCurrentTime)) ); - stopSettings.set( AlarmTimeSetting, cgGlobal.readEntry("alarmTime" + suffix, 5) ); - - // Read favorite/recent journey search items for the current stop settings - QByteArray journeySearchesData = - cgGlobal.readEntry( "journeySearches" + suffix, QByteArray() ); - stopSettings.set( Settings::JourneySearchSetting, QVariant::fromValue( - SettingsIO::decodeJourneySearchItems(&journeySearchesData)) ); - stopSettingsList << stopSettings; - - ++i; - test = "location_" + QString::number( i ); - if ( i > stopSettingCount ) { - break; - } - } - - settings.setCurrentStop( cg.readEntry("currentStopIndex", 0) ); - - // Add initial stop settings when no settings are available - if ( stopSettingsList.isEmpty() ) { - kDebug() << "Stop settings list in settings is empty"; - if ( publictransportEngine ) { - QString countryCode = KGlobal::locale()->country(); - Plasma::DataEngine::Data locationData = publictransportEngine->query( "Locations" ); - QString defaultServiceProviderId = - locationData[countryCode].toHash()["defaultProvider"].toString(); - - StopSettings stopSettings; - if ( defaultServiceProviderId.isEmpty() ) { - stopSettings.set( LocationSetting, "showAll" ); - } else { - stopSettings.set( LocationSetting, countryCode ); - stopSettings.set( ServiceProviderSetting, defaultServiceProviderId ); - } - stopSettings.setStop( QString() ); - // TODO: Get initial stop names using StopFinder - - stopSettingsList << stopSettings; - } else { - stopSettingsList << StopSettings(); - } - - } - settings.setStops( stopSettingsList ); - - if ( settings.currentStopIndex() < 0 ) { - settings.setCurrentStop( 0 ); // For compatibility with versions < 0.7 - } else if ( settings.currentStopIndex() >= settings.stops().count() ) { - kDebug() << "Current stop index in settings invalid"; - settings.setCurrentStop( settings.stops().count() - 1 ); - } - - settings.setMaximalNumberOfDepartures( cg.readEntry("maximalNumberOfDepartures", 50) ); - settings.setLinesPerRow( cg.readEntry("linesPerRow", 2) ); - settings.setSizeFactor( Settings::sizeFactorFromSize(cg.readEntry("size", 2)) ); - settings.setDepartureArrivalListType( static_cast( - cg.readEntry("departureArrivalListType", static_cast(DepartureList))) ); - - settings.setDrawShadows( cg.readEntry("drawShadows", true) ); - settings.setHideTargetColumn( cg.readEntry("hideColumnTarget", false) ); - settings.setColorize( cg.readEntry("colorize", true) ); - - QString fontFamily = cg.readEntry( "fontFamily", QString() ); - settings.setUseThemeFont( fontFamily.isEmpty() ); - if ( !settings.useThemeFont() ) { - settings.setFont( QFont(fontFamily) ); - } else { - settings.setFont( Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont) ); - } - - // ***************** DEPRECATED BEGIN ***************************************************** - // *** Used for migration of filter settings from versions prior to version 0.10 RC1 ****** - FilterSettingsList filtersList; - if ( cgGlobal.hasKey("filterConfigurationList") ) { - kDebug() << "DEPRECATED Filter settings will be restructured for new version"; - QStringList filterConfigurationList = - cgGlobal.readEntry( "filterConfigurationList", QStringList() ); - for ( int i = filterConfigurationList.count() - 1; i >= 0; --i ) { - const QString &filterConfiguration = filterConfigurationList[ i ]; - if ( filterConfiguration.isEmpty() ) { - filterConfigurationList.removeAt( i ); - } - } - - kDebug() << "Config group list" << cgGlobal.groupList(); - kDebug() << "Filter config list:" << filterConfigurationList; - - // Read old filter settings - foreach( const QString &filterConfiguration, filterConfigurationList ) { - FilterSettings filters = - readFilterConfig( cgGlobal.group("filterConfig_" + filterConfiguration) ); - filters.name = filterConfiguration; - filtersList << filters; - } - // ***************** DEPRECATED END ******************************************************* - } else { - // New filter storage structure - int filterCount = cgGlobal.readEntry( "filterCount", 0 ); - test = "filterConfig_1"; - i = 1; - while ( i <= filterCount && cgGlobal.hasGroup(test) ) { - FilterSettings filters = readFilterConfig( cgGlobal.group(test) ); - if ( filters.name.isEmpty() ) { - kDebug() << "Filter settings without a name found!"; - filters.name = "Unnamed" + QString::number( i ); - } - filtersList << filters; - - ++i; - test = "filterConfig_" + QString::number( i ); - } - } - settings.setFilters( filtersList ); - - // Read alarm settings - // TODO Store in config groups like filters - AlarmSettingsList alarms; - int alarmCount = cgGlobal.readEntry( "alarmCount", 0 ); - test = "alarmType"; - i = 1; - while ( i <= alarmCount && cgGlobal.hasKey(test) ) { - AlarmSettings alarm; - QString suffix = i == 1 ? QString() : '_' + QString::number( i ); - alarm.type = static_cast( - cgGlobal.readEntry("alarmType" + suffix, static_cast(AlarmRemoveAfterFirstMatch)) ); - alarm.affectedStops = cgGlobal.readEntry( "alarmStops" + suffix, QList() ); - alarm.enabled = cgGlobal.readEntry( "alarmEnabled" + suffix, true ); - alarm.name = cgGlobal.readEntry( "alarmName" + suffix, "Unnamed" ); - alarm.lastFired = cgGlobal.readEntry( "alarmLastFired" + suffix, QDateTime() ); - alarm.autoGenerated = cgGlobal.readEntry( "alarmAutogenerated" + suffix, false ); - QByteArray baAlarmFilter = cgGlobal.readEntry( "alarmFilter" + suffix, QByteArray() ); - alarm.filter.fromData( baAlarmFilter ); - - // Use date and time of one-time alarms to check if they are expired - if ( alarm.isOneTimeAlarm() && alarm.isExpired() ) { - kDebug() << "Removing one expired one-time alarm"; - } else { - alarms << alarm; - } - - ++i; - test = "alarmType_" + QString::number( i ); - } - settings.setAlarms( alarms ); - - return settings; -} - -SettingsIO::ChangedFlags SettingsIO::writeSettings( const Settings &settings, - const Settings &oldSettings, KConfigGroup cg, KConfigGroup cgGlobal ) -{ - ChangedFlags changed = NothingChanged; - - if ( settings.currentStopIndex() != oldSettings.currentStopIndex() ) { - cg.writeEntry( "currentStopIndex", settings.currentStopIndex() ); - changed |= IsChanged | ChangedCurrentStop | ChangedCurrentStopSettings; - } - - // Write stop settings - if ( settings.stops() != oldSettings.stops() ) { - changed |= IsChanged | ChangedStopSettings; - - // Get current stop settings and compare journey search lists - const StopSettings stopSettings = settings.currentStop(); - const StopSettings oldStopSettings = oldSettings.currentStop(); - if ( stopSettings.get< QList >(Settings::JourneySearchSetting) != - oldStopSettings.get< QList >(Settings::JourneySearchSetting) ) - { - kDebug() << "Changed journey search list"; - changed |= ChangedCurrentJourneySearchLists; - } - - // Get QHash with the current stop settings, remove values that do not require - // timetable data to be requested again and compare the remaining settings - QHash stopSettingsRequireRequest = stopSettings.settings(); - QHash oldStopSettingsRequireRequest = oldStopSettings.settings(); - stopSettingsRequireRequest.remove( AlarmTimeSetting ); - oldStopSettingsRequireRequest.remove( AlarmTimeSetting ); - stopSettingsRequireRequest.remove( Settings::JourneySearchSetting ); - oldStopSettingsRequireRequest.remove( Settings::JourneySearchSetting ); - if ( stopSettingsRequireRequest != oldStopSettingsRequireRequest ) { - changed |= ChangedCurrentStopSettings; - } - - int i = 1; - cgGlobal.writeEntry( "stopSettings", settings.stops().count() ); // Not needed if deleteEntry/Group works, don't know what's wrong (sync() and Plasma::Applet::configNeedsSaving() doesn't help) - - foreach( const StopSettings &stopSettings, settings.stops() ) { - QString suffix = i == 1 ? QString() : '_' + QString::number( i ); - cgGlobal.writeEntry( "location" + suffix, - stopSettings.get(LocationSetting) ); - cgGlobal.writeEntry( "serviceProvider" + suffix, - stopSettings.get(ServiceProviderSetting) ); - cgGlobal.writeEntry( "city" + suffix, stopSettings.get(CitySetting) ); - cgGlobal.writeEntry( "stop" + suffix, stopSettings.stops() ); - cgGlobal.writeEntry( "stopID" + suffix, stopSettings.stopIDs() ); - cgGlobal.writeEntry( "timeOffsetOfFirstDeparture" + suffix, - stopSettings.get(TimeOffsetOfFirstDepartureSetting) ); - cgGlobal.writeEntry( "timeOfFirstDepartureCustom" + suffix, - stopSettings.get(TimeOfFirstDepartureSetting).toString("hh:mm") ); - cgGlobal.writeEntry( "firstDepartureConfigMode" + suffix, - stopSettings.get(FirstDepartureConfigModeSetting) ); - cgGlobal.writeEntry( "alarmTime" + suffix, stopSettings.get(AlarmTimeSetting) ); - - // Write journey search items in encoded form - const QByteArray journeySearchesData = SettingsIO::encodeJourneySearchItems( - stopSettings.get< QList >(Settings::JourneySearchSetting) ); - cgGlobal.writeEntry( "journeySearches" + suffix, journeySearchesData ); - ++i; - } - - // Delete old stop settings entries - QString test = "location_" + QString::number( i ); - while ( cgGlobal.hasKey( test ) ) { - QString suffix = '_' + QString::number( i ); - cgGlobal.deleteEntry( "location" + suffix ); - cgGlobal.deleteEntry( "serviceProvider" + suffix ); - cgGlobal.deleteEntry( "city" + suffix ); - cgGlobal.deleteEntry( "stop" + suffix ); - cgGlobal.deleteEntry( "stopID" + suffix ); - cgGlobal.deleteEntry( "timeOffsetOfFirstDeparture" + suffix ); - cgGlobal.deleteEntry( "timeOfFirstDepartureCustom" + suffix ); - cgGlobal.deleteEntry( "firstDepartureConfigMode" + suffix ); - cgGlobal.deleteEntry( "alarmTime" + suffix ); - cgGlobal.deleteEntry( "journeySearches" + suffix ); - ++i; - test = "location_" + QString::number( i ); - } - } - - if ( settings.settingsFlags() != oldSettings.settingsFlags() ) { - if ( settings.drawShadows() != oldSettings.drawShadows() ) { - cg.writeEntry( "drawShadows", settings.drawShadows() ); - changed |= IsChanged | ChangedShadows; - } - if ( settings.hideTargetColumn() != oldSettings.hideTargetColumn() ) { - cg.writeEntry( "hideColumnTarget", settings.hideTargetColumn() ); - changed |= IsChanged | ChangedTargetColumn; - } - if ( settings.useThemeFont() != oldSettings.useThemeFont() || - (!settings.useThemeFont() && settings.font() != oldSettings.font()) ) - { - cg.writeEntry( "fontFamily", settings.useThemeFont() - ? QString() : settings.font().family() ); - changed |= IsChanged | ChangedFont; - } - if ( settings.colorize() != oldSettings.colorize() ) { - cg.writeEntry( "colorize", settings.colorize() ); - changed |= IsChanged | ChangedColorization; - } - } - - if ( settings.departureArrivalListType() != oldSettings.departureArrivalListType() ) { - cg.writeEntry( "departureArrivalListType", - static_cast(settings.departureArrivalListType()) ); - changed |= IsChanged | ChangedServiceProvider | ChangedDepartureArrivalListType; - } - - if ( settings.departureTimeFlags() != oldSettings.departureTimeFlags() ) { - cg.writeEntry( "departureTimeFlags", static_cast(settings.departureTimeFlags()) ); - changed |= IsChanged | ChangedDepartureTimeSettings; - } - - if ( settings.additionalDataRequestType() != oldSettings.additionalDataRequestType() ) { - cg.writeEntry( "additionalDataRequestType", static_cast(settings.additionalDataRequestType()) ); - changed |= IsChanged | ChangedAdditionalDataRequestSettings; - } - - if ( settings.maximalNumberOfDepartures() != oldSettings.maximalNumberOfDepartures() ) { - cg.writeEntry( "maximalNumberOfDepartures", settings.maximalNumberOfDepartures() ); - changed |= IsChanged | ChangedServiceProvider; - } - - if ( settings.linesPerRow() != oldSettings.linesPerRow() ) { - cg.writeEntry( "linesPerRow", settings.linesPerRow() ); - changed |= IsChanged | ChangedLinesPerRow; - } - - if ( settings.sizeFactor() != oldSettings.sizeFactor() ) { - cg.writeEntry( "size", Settings::sizeFromSizeFactor(settings.sizeFactor()) ); - changed |= IsChanged | ChangedSizeFactor; - } - - // ***************** DEPRECATED BEGIN ***************************************************** - // *** Used for migration of filter settings from versions prior to version 0.10 Beta 9 *** - if ( cgGlobal.hasKey("filterConfigurationList") ) { - // Read deprecated filter configuration names - QStringList filterConfigurationList = - cgGlobal.readEntry( "filterConfigurationList", QStringList() ); - for ( int i = filterConfigurationList.count() - 1; i >= 0; --i ) { - const QString &filterConfiguration = filterConfigurationList[ i ]; - if ( filterConfiguration.isEmpty() ) { - filterConfigurationList.removeAt( i ); - } - } - - kDebug() << "Delete deprecated entry \"filterConfigurationList\""; - cgGlobal.deleteEntry( "filterConfigurationList" ); - - // Delete deprecated filter settings - foreach( const QString &group, cgGlobal.groupList() ) { - if ( !filterConfigurationList.contains( - group.mid(QString("filterConfig_").length())) ) - { - kDebug() << "Delete deprecated group" << group; - cgGlobal.deleteGroup( group ); - } - } - - // Delete filter configuration names in stop settings - const QString filterConfigurationKey = "filterConfiguration"; - QString currentFilterConfigurationKey = filterConfigurationKey; - int i = 2; - while ( cgGlobal.hasKey(currentFilterConfigurationKey) ) { - kDebug() << "Delete deprecated filter using entry" << currentFilterConfigurationKey; - cgGlobal.deleteEntry( currentFilterConfigurationKey ); - - currentFilterConfigurationKey = filterConfigurationKey + '_' + QString::number(i); - ++i; - } - - cgGlobal.sync(); - } - - // ***************** DEPRECATED END ******************************************************* - - // Write filter settings - if ( settings.filters() != oldSettings.filters() ) { - cgGlobal.writeEntry( "filterCount", settings.filters().count() ); - int i = 1; - foreach ( const FilterSettings &filters, settings.filters() ) { - if ( filters.name.isEmpty() ) { - kDebug() << "Empty filter config name, can't write settings"; - continue; - } - - if ( oldSettings.filters().hasName(filters.name) ) { - SettingsIO::writeFilterConfig( filters, - oldSettings.filters().byName(filters.name), - cgGlobal.group("filterConfig_" + QString::number(i)) ); - } else { - SettingsIO::writeFilterConfig( filters, - cgGlobal.group("filterConfig_" + QString::number(i)) ); - } - ++i; - } - - // Delete old filter settings groups - while ( i <= oldSettings.filters().count() ) { - cgGlobal.deleteGroup( "filterConfig_" + QString::number(i) ); - cgGlobal.sync(); - ++i; - } - - changed |= IsChanged | ChangedFilterSettings; - } - - // Compare (don't write) color group settings - if ( settings.colorGroups() != oldSettings.colorGroups() ) { - changed |= IsChanged | ChangedColorGroupSettings; - } - - // Write alarm settings - if ( settings.alarms() != oldSettings.alarms() ) { - changed |= IsChanged | ChangedAlarmSettings; - int i = 1; - cgGlobal.writeEntry( "alarmCount", settings.alarms().count() ); - foreach( const AlarmSettings &alarm, settings.alarms() ) { - QString suffix = i == 1 ? QString() : '_' + QString::number( i ); - cgGlobal.writeEntry( "alarmType" + suffix, static_cast( alarm.type ) ); - cgGlobal.writeEntry( "alarmStops" + suffix, alarm.affectedStops ); - cgGlobal.writeEntry( "alarmFilter" + suffix, alarm.filter.toData() ); - cgGlobal.writeEntry( "alarmEnabled" + suffix, alarm.enabled ); - cgGlobal.writeEntry( "alarmName" + suffix, alarm.name ); - cgGlobal.writeEntry( "alarmLastFired" + suffix, alarm.lastFired ); - cgGlobal.writeEntry( "alarmAutogenerated" + suffix, alarm.autoGenerated ); - ++i; - } - - // Delete old stop settings entries - QString test = "alarmType" + QString::number( i ); - while ( cgGlobal.hasKey( test ) ) { - QString suffix = i == 1 ? QString() : '_' + QString::number( i ); - cgGlobal.deleteEntry( "alarmType" + suffix ); - cgGlobal.deleteEntry( "alarmStops" + suffix ); - cgGlobal.deleteEntry( "alarmFilter" + suffix ); - cgGlobal.deleteEntry( "alarmEnabled" + suffix ); - cgGlobal.deleteEntry( "alarmName" + suffix ); - cgGlobal.deleteEntry( "alarmLastFired" + suffix ); - cgGlobal.deleteEntry( "alarmAutogenerated" + suffix ); - ++i; - test = "alarmType_" + QString::number( i ); - } - } - - return changed; -} - -QList< JourneySearchItem > SettingsIO::decodeJourneySearchItems( QByteArray *data ) -{ - QDataStream stream( data, QIODevice::ReadOnly ); - if ( stream.atEnd() ) { - return QList< JourneySearchItem >(); - } - - // Test for correct data structure by the stored version - quint8 version; - stream >> version; - if ( version != 1 ) { - kDebug() << "Wrong setting version" << version; - return QList< JourneySearchItem >(); - } - - // Read number of items - quint8 count; - stream >> count; - - // Read count items - QList< JourneySearchItem > journeySearches; - for ( int i = 0; i < count; ++i ) { - QString name; - QString journeySearch; - bool favorite; - stream >> journeySearch; - stream >> name; - stream >> favorite; - - journeySearches << JourneySearchItem( journeySearch, name, favorite ); - } - - return journeySearches; -} - -QByteArray SettingsIO::encodeJourneySearchItems( const QList< JourneySearchItem > &journeySearches ) -{ - QByteArray data; - QDataStream stream( &data, QIODevice::WriteOnly ); - - // Store version of the data structure, needs to be incremented whenever it changes, - // max version is 256 - stream << quint8(1); - - // Store number of items - stream << quint8(journeySearches.count()); // Max 256 items - - // Store items - foreach ( const JourneySearchItem &item, journeySearches ) { - stream << item.journeySearch(); - stream << item.name(); - stream << item.isFavorite(); - } - return data; -} - -FilterSettings SettingsIO::readFilterConfig( const KConfigGroup &cgGlobal ) -{ - FilterSettings filters; - filters.name = cgGlobal.readEntry( "Name", QString() ); - filters.filterAction = static_cast< FilterAction >( - cgGlobal.readEntry("FilterAction", static_cast(ShowMatching)) ); - filters.affectedStops = cgGlobal.readEntry( "AffectedStops", QList() ).toSet(); - - QByteArray baFilters = cgGlobal.readEntry( "Filters", QByteArray() ); - filters.filters.fromData( baFilters ); - return filters; -} - -bool SettingsIO::writeFilterConfig( const FilterSettings &filters, - const FilterSettings &oldFilterSettings, KConfigGroup cgGlobal ) -{ - bool changed = false; - - if ( filters.name != oldFilterSettings.name ) { - cgGlobal.writeEntry( "Name", filters.name ); - changed = true; - } - - if ( filters.filters != oldFilterSettings.filters ) { - cgGlobal.writeEntry( "Filters", filters.filters.toData() ); - changed = true; - } - - if ( filters.filterAction != oldFilterSettings.filterAction ) { - cgGlobal.writeEntry( "FilterAction", static_cast(filters.filterAction) ); - changed = true; - } - - if ( filters.affectedStops != oldFilterSettings.affectedStops ) { - cgGlobal.writeEntry( "AffectedStops", filters.affectedStops.toList() ); - changed = true; - } - - return changed; -} - -void SettingsIO::writeFilterConfig( const FilterSettings& filters, - KConfigGroup cgGlobal ) -{ - cgGlobal.writeEntry( "Name", filters.name ); - cgGlobal.writeEntry( "Filters", filters.filters.toData() ); - cgGlobal.writeEntry( "FilterAction", static_cast( filters.filterAction ) ); - cgGlobal.writeEntry( "AffectedStops", filters.affectedStops.toList() ); -} diff --git a/applet/settingsio.h b/applet/settingsio.h deleted file mode 100644 index c9ba94f..0000000 --- a/applet/settingsio.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file - * @brief This file contains classes for synchronizing settings with widgets in the UI. - * @author Friedrich Pülz */ - -#ifndef SETTINGSIO_HEADER -#define SETTINGSIO_HEADER - -// Own includes -#include "settings.h" - -// KDE includes -#include - -namespace Plasma { - class DataEngine; -} - -/** - * @brief Contains static methods to read/write settings. - * - * Stop and filter settings are stored globally for all PublicTransport applets. - **/ -class SettingsIO { -public: - /** @brief These flags describe what settings have changed. */ - enum ChangedFlag { - NothingChanged = 0x000000, /**< Nothing has changed. */ - - IsChanged = 0x000001, /**< This flag is set if something has changed. - * If another change flag is set (except for NothingChanged), this flag is also - * set. This flag also gets set for changes not covered by the other change flags. */ - ChangedServiceProvider = 0x000002, /**< Service provider settings have been changed - * (stop name, service provider, ...). */ // TODO - ChangedDepartureArrivalListType - = 0x000004, /**< Changed from showing departures to arrivals - * or vice versa. */ - ChangedStopSettings = 0x000008, /**< Stop settings have been changed. This flag also gets - * set if only eg. the favorite/recent journey searches have been changed. - * Use ChangedCurrentStopSettings to check if timetable data needs to be requested - * from the data engine again with the changed settings. */ - ChangedCurrentStopSettings - = 0x000010, /**< Stop settings of the current stop - * have been changed, that require timetable data to be requested from the data - * engine again. If this flag is set, the current timetable data may not represent - * correct results any longer for the changed stop settings. - * Stop settings that do not require a new timetable data request are unaffected. - * This flag is always set if ChangedCurrentStop is set. */ - ChangedCurrentJourneySearchLists - = 0x000020, /**< The list of favorite and/or recent journey - * searches has been changed for the current stop. - * This does not cover changes in the current journey - * list caused by changing the current stop settings. */ - ChangedCurrentStop = 0x000040, /**< The current stop has been changed. */ - ChangedFilterSettings = 0x000080, /**< Filter settings have been changed. */ - ChangedLinesPerRow = 0x000100, /**< The lines per row setting has been changed. */ - ChangedAlarmSettings = 0x000200, /**< Alarm settings have been changed. This does not - * include AlarmTimeSetting in stop settings. */ - ChangedColorization = 0x000400, /**< Colorization of departures has been toggled. */ - ChangedColorGroupSettings - = 0x000800, /**< Color group settings have been changed. */ - - ChangedFont = 0x001000, /**< The font was changed. */ - ChangedSizeFactor = 0x002000, /**< The size factor was changed. - * This also affects the font size. */ - ChangedShadows = 0x004000, /**< Shadow visibility has been toggled. */ - ChangedTargetColumn = 0x008000, /**< Target column visibility has been toggled. */ - ChangedDepartureTimeSettings - = 0x010000, /**< Settings for how to display the departure time - * have been changed, eg. whether or not to show the departure time, whether or not - * to display it bold or whether or not remaining minutes should be displayed. */ - ChangedAdditionalDataRequestSettings - = 0x02000, /**< Changed when additional timetable data should - * be requested. */ - - ChangedCurrentFilterSettings = ChangedCurrentStop || ChangedCurrentStopSettings || - ChangedFilterSettings - /**< The currently active filter settings may have changed. If ChangedFilterSettings - * is set this flag is always also set, meaning that the filter settings for the - * current stop have been changed. The currently active filter settings may also - * change when the current stop settings change. */ - }; - Q_DECLARE_FLAGS( ChangedFlags, ChangedFlag ) - - /** @brief Read settings from @p cg and @p cgGlobal. */ - static Settings readSettings( KConfigGroup cg, KConfigGroup cgGlobal, - Plasma::DataEngine *publicTransportEngine = 0 ); - - /** - * @brief Write changed @p settings to @p cg and @p cgGlobal. - * - * @p oldSettings is used to see which settings have been changed. - * - * @returns What settings have been changed. - * @see ChangedFlags */ - static ChangedFlags writeSettings( const Settings &settings, const Settings &oldSettings, - KConfigGroup cg, KConfigGroup cgGlobal ); - - /** - * @brief Decodes journey search items from @p data. - * - * @param data Journey search items encoded using encodeJourneySearchItems. - * @return The list of journey search items decoded from @p data. - * @see encodeJourneySearchItems - **/ - static QList decodeJourneySearchItems( QByteArray *data ); - - /** - * @brief Encodes @p journeySearches into a QByteArray. - * - * @param journeySearches Journey search items to encode. - * @return @p journeySearches encoded in a QByteArray. - * @see decodeJourneySearchItems - **/ - static QByteArray encodeJourneySearchItems( const QList &journeySearches ); - - /** @brief Read filter configuration from @p cgGlobal. */ - static FilterSettings readFilterConfig( const KConfigGroup &cgGlobal ); - - /** - * @brief Write filter configuration @p filters to @p cgGlobal. - * - * This function only writes settings that have changed compared to @p oldFilterSettings. - **/ - static bool writeFilterConfig( const FilterSettings &filters, - const FilterSettings &oldFilterSettings, KConfigGroup cgGlobal ); - - /** @brief Write filter configuration @p filters to @p cgGlobal. */ - static void writeFilterConfig( const FilterSettings &filters, KConfigGroup cgGlobal ); -}; -Q_DECLARE_OPERATORS_FOR_FLAGS( SettingsIO::ChangedFlags ) - -#endif // Multiple inclusion guard diff --git a/applet/settingsui.cpp b/applet/settingsui.cpp deleted file mode 100644 index 22d25c4..0000000 --- a/applet/settingsui.cpp +++ /dev/null @@ -1,1171 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "settingsui.h" - -// Own includes -#include "settingsio.h" // For export/import of filter settings - -// libpublictransporthelper includes -#include -#include -#include -#include - -// Qt includes -#include - -// KDE+Plasma includes -#include -#include -#include -#include -#include -#include -#include - -SettingsUiManager::SettingsUiManager( const Settings &settings, - KConfigDialog *parentDialog, DeletionPolicy deletionPolicy ) - : QObject( parentDialog ), m_deletionPolicy( deletionPolicy ), - m_configDialog( parentDialog ), m_modelServiceProvider( 0 ), - m_modelLocations( 0 ), m_stopListWidget( 0 ) -{ - // Store settings that have no associated widgets - m_currentStopSettingsIndex = settings.currentStopIndex(); - m_hideTargetColumn = settings.hideTargetColumn(); - - m_filters = settings.filters(); - m_filterConfigChanged = false; - m_colorGroup = settings.colorGroups(); - - m_alarm = settings.alarms(); - m_lastAlarm = -1; - m_alarmsChanged = false; - - // Setup tab page widgets - QWidget *widgetStop = new QWidget; - QWidget *widgetAdvanced = new QWidget; - QWidget *widgetAppearance = new QWidget; - QWidget *widgetFilter = new QWidget; - QWidget *widgetAlarms = new QWidget; - m_ui.setupUi( widgetStop ); - m_uiAdvanced.setupUi( widgetAdvanced ); - m_uiAppearance.setupUi( widgetAppearance ); - m_uiFilter.setupUi( widgetFilter ); - m_uiAlarms.setupUi( widgetAlarms ); - - // Setup tab widget of the stop settings page - KTabWidget *tabMain = new KTabWidget; - tabMain->setObjectName( "generalTabWidget" ); - tabMain->addTab( widgetStop, i18nc( "@title:tab", "&Stop selection" ) ); - tabMain->addTab( widgetAdvanced, i18nc( "@title:tab Advanced settings tab label", "&Advanced" ) ); - - // Add settings pages - m_configDialog->addPage( tabMain, i18nc( "@title:group General settings page name", "General" ), - "public-transport-stop" ); - m_configDialog->addPage( widgetAppearance, i18nc( "@title:group", "Appearance" ), - "video-display" ); - m_configDialog->addPage( widgetFilter, i18nc( "@title:group", "Filter" ), "view-filter" ); - m_configDialog->addPage( widgetAlarms, i18nc( "@title:group", "Alarms" ), "task-reminder" ); - - // Setup model for the service provider combobox - m_modelServiceProvider = new ServiceProviderModel( this ); - - // Setup model for the location combobox - m_modelLocations = new LocationModel( this ); - - // Setup stop widgets - m_stopListWidget = new StopListWidget( m_ui.stopList, m_modelServiceProvider, settings.stops(), - StopSettingsDialog::ExtendedStopSelection, - ServiceProviderDataDialog::DefaultOptions, - &m_filters ); - m_stopListWidget->setWhatsThis( i18nc( "@info:whatsthis", - "This shows the stop settings you have set." - "The applet shows results for one of them at a time. To switch the " - "currently used stop setting use the context menu of the applet." - "For each stop setting another set of filter configurations can be used. " - "To edit filter configurations use the Filter " - "section in the settings dialog. You can define a list of stops for " - "each stop setting that are then displayed combined (eg. stops near " - "to each other)." ) ); - m_stopListWidget->setCurrentStopSettingIndex( m_currentStopSettingsIndex ); - connect( m_stopListWidget, SIGNAL(changed(int,StopSettings)), this, SLOT(stopSettingsChanged()) ); - connect( m_stopListWidget, SIGNAL(added(QWidget*)), this, SLOT(stopSettingsAdded()) ); - connect( m_stopListWidget, SIGNAL(removed(QWidget*,int)), - this, SLOT(stopSettingsRemoved(QWidget*,int)) ); - stopSettingsChanged(); - - // Add stop list widget - QVBoxLayout *lStop = new QVBoxLayout( m_ui.stopList ); - lStop->setContentsMargins( 0, 0, 0, 0 ); - lStop->addWidget( m_stopListWidget ); - - // Setup filter widgets - m_uiFilter.filters->setWhatsThis( i18nc( "@info:whatsthis", - "This shows the filters of the selected filter configuration." - "Each filter configuration consists of a name, a list of stops using the filter " - "configuration, a filter action and a list of filters. Each filter contains a list of " - "constraints." - "A filter matches, if all it's constraints match (logical AND) while a filter " - "configuration matches, if one of it's filters match (logical OR)." - "For each filter configuration a list of stops can be set, that use that filter. " - "Check each stop you want to use the selected filter configuration in the " - "Used With combobox. You can also select the filters to be used " - "by a specific stop in the stop settings or in the applet itself." - "Filter Types" - "" - "Vehicle: Filters by vehicle types." - "Line String: Filters by transport line strings." - "Line number: Filters by transport line numbers." - "Target: Filters by target/origin." - "Delay: Filters by delay." - "" - "Filters that need route data" - "Warning: The following filters need route data, which may " - "need to be requested explicitly as additional timetable data for your provider. See " - "the Advanced tab in the general settings to adjust, when " - "additional timetable data should be requested." - "" - "Via: Filters by intermediate stops." - "Next Stop: Filters by the next intermediate stop." - "" ) ); - m_uiFilter.affectedStops->setMultipleSelectionOptions( CheckCombobox::ShowStringList ); - connect( m_uiFilter.filters, SIGNAL(changed()), this, SLOT(filtersChanged()) ); - connect( m_uiFilter.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsFilterChanged()) ); - - // Setup alarm widgets - m_uiAlarms.alarmFilter->setWidgetCountRange(); - m_uiAlarms.alarmFilter->removeAllWidgets(); - m_uiAlarms.alarmFilter->setAllowedFilterTypes( QList() - << FilterByDepartureTime << FilterByDepartureDate << FilterByDayOfWeek << FilterByVehicleType - << FilterByTarget << FilterByVia << FilterByNextStop << FilterByTransportLine - << FilterByTransportLineNumber << FilterByDelay ); - m_uiAlarms.alarmFilter->setWidgetCountRange( 1 ); - m_uiAlarms.affectedStops->setMultipleSelectionOptions( CheckCombobox::ShowStringList ); - m_uiAlarms.addAlarm->setIcon( KIcon( "list-add" ) ); - m_uiAlarms.removeAlarm->setIcon( KIcon( "list-remove" ) ); - connect( m_uiAlarms.alarms, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmChanged(int)) ); - connect( m_uiAlarms.addAlarm, SIGNAL(clicked()), this, SLOT(addAlarmClicked()) ); - connect( m_uiAlarms.removeAlarm, SIGNAL(clicked()), this, SLOT(removeAlarmClicked()) ); - connect( m_uiAlarms.renameAlarm, SIGNAL(clicked()), this, SLOT(renameAlarmClicked()) ); - connect( m_uiAlarms.alarmFilter, SIGNAL(changed()), this, SLOT(alarmChanged()) ); - connect( m_uiAlarms.alarmType, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmTypeChanged(int)) ); - connect( m_uiAlarms.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsAlarmChanged()) ); - - // Set values of the given settings for each page - setValuesOfAdvancedConfig( settings ); - setValuesOfAppearanceConfig( settings ); - setValuesOfAlarmConfig(); - setValuesOfFilterConfig(); - currentAlarmChanged( m_uiAlarms.alarms->currentIndex() ); - - // Connect all widgets with the changed() slot, that enables the apply button - connect( m_stopListWidget, SIGNAL(changed(int,StopSettings)), this, SLOT(changed()) ); - connect( m_stopListWidget, SIGNAL(added(QWidget*)), this, SLOT(changed()) ); - connect( m_stopListWidget, SIGNAL(removed(QWidget*,int)), this, SLOT(changed()) ); - connect( m_uiAdvanced.maximalNumberOfDepartures, SIGNAL(valueChanged(int)), this, SLOT(changed()) ); - connect( m_uiAdvanced.showArrivals, SIGNAL(toggled(bool)), this, SLOT(changed()) ); - connect( m_uiAdvanced.showDepartures, SIGNAL(toggled(bool)), this, SLOT(changed()) ); - connect( m_uiAdvanced.additionalData, SIGNAL(currentIndexChanged(int)), this, SLOT(changed()) ); - connect( m_uiAlarms.affectedStops, SIGNAL(checkedItemsChanged()), this, SLOT(changed()) ); - connect( m_uiAlarms.alarmFilter, SIGNAL(changed()), this, SLOT(changed()) ); - connect( m_uiAlarms.alarmType, SIGNAL(currentIndexChanged(int)), this, SLOT(changed()) ); - connect( m_uiFilter.affectedStops, SIGNAL(checkedItemsChanged()), this, SLOT(changed()) ); - connect( m_uiFilter.filterAction, SIGNAL(currentIndexChanged(int)), this, SLOT(changed()) ); - connect( m_uiFilter.filters, SIGNAL(changed()), this, SLOT(changed()) ); - connect( m_uiAppearance.shadow, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); - connect( m_uiAppearance.cmbDepartureColumnInfos, SIGNAL(currentIndexChanged(int)), this, SLOT(changed()) ); - connect( m_uiAppearance.colorize, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); - connect( m_uiAppearance.displayTimeBold, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); - connect( m_uiAppearance.font, SIGNAL(currentFontChanged(QFont)), this, SLOT(changed()) ); - connect( m_uiAppearance.linesPerRow, SIGNAL(valueChanged(int)), this, SLOT(changed()) ); - connect( m_uiAppearance.size, SIGNAL(valueChanged(int)), this, SLOT(changed()) ); - connect( m_uiAppearance.radioUseDefaultFont, SIGNAL(toggled(bool)), this, SLOT(changed()) ); - connect( m_uiAppearance.radioUseOtherFont, SIGNAL(toggled(bool)), this, SLOT(changed()) ); - - m_uiAlarms.addAlarm->setIcon( KIcon("list-add") ); - m_uiAlarms.removeAlarm->setIcon( KIcon("list-remove") ); - m_uiAlarms.renameAlarm->setIcon( KIcon("edit-rename") ); - - m_uiFilter.addFilterConfiguration->setIcon( KIcon("list-add") ); - m_uiFilter.removeFilterConfiguration->setIcon( KIcon("list-remove") ); - m_uiFilter.renameFilterConfiguration->setIcon( KIcon("edit-rename") ); - - connect( m_configDialog, SIGNAL(finished()), this, SLOT(configFinished()) ); - connect( m_configDialog, SIGNAL(okClicked()), this, SLOT(configAccepted()) ); - connect( m_configDialog, SIGNAL(applyClicked()), this, SLOT(configAccepted()) ); - - connect( m_uiFilter.filterAction, SIGNAL(currentIndexChanged(int)), - this, SLOT(filterActionChanged(int)) ); - - connect( m_uiFilter.filterConfigurations, SIGNAL(currentIndexChanged(QString)), - this, SLOT(loadFilterConfiguration(QString)) ); - connect( m_uiFilter.addFilterConfiguration, SIGNAL(clicked()), - this, SLOT(addFilterConfiguration()) ); - connect( m_uiFilter.removeFilterConfiguration, SIGNAL(clicked()), - this, SLOT(removeFilterConfiguration()) ); - connect( m_uiFilter.renameFilterConfiguration, SIGNAL(clicked()), - this, SLOT(renameFilterConfiguration()) ); -} - -void SettingsUiManager::changed() -{ - m_configDialog->enableButtonApply( true ); -} - -void SettingsUiManager::configFinished() -{ - emit settingsFinished(); - if( m_deletionPolicy == DeleteWhenFinished ) - deleteLater(); -} - -void SettingsUiManager::configAccepted() -{ - emit settingsAccepted( settings() ); -} - -void SettingsUiManager::removeAlarms( const AlarmSettingsList & /*newAlarmSettings*/, - const QList &/*removedAlarms*/ ) -{ -// TODO -// foreach ( int alarmIndex, removedAlarms ) { -// m_alarm.removeAt( alarmIndex ); -// disconnect( m_uiAlarms.alarms, SIGNAL(currentRowChanged(int)), -// this, SLOT(currentAlarmChanged(int)) ); -// delete m_uiAlarms.alarms->takeItem( m_uiAlarms.alarms->currentRow() ); -// connect( m_uiAlarms.alarms, SIGNAL(currentRowChanged(int)), -// this, SLOT(currentAlarmChanged(int)) ); -// m_lastAlarm = m_uiAlarms.alarms->currentRow(); -// setValuesOfAlarmConfig(); -// } - -// alarmChanged(); -} - -void SettingsUiManager::alarmChanged( int index ) -{ - Q_UNUSED( index ); - m_alarmsChanged = true; // Leave values of autoGenerated and lastFired -} - -void SettingsUiManager::currentAlarmChanged( int row ) -{ - if( row != -1 ) { - if( m_alarmsChanged && m_lastAlarm != -1 ) { - // Store to last edited alarm settings - if( m_lastAlarm < m_alarm.count() ) { - m_alarm[ m_lastAlarm ] = currentAlarmSettings( - m_uiAlarms.alarms->model()->data( - m_uiAlarms.alarms->model()->index( m_lastAlarm, 0 ) ).toString() ); - } else { - kDebug() << "m_lastAlarm is bad" << m_lastAlarm; - } - } - - disconnect( m_uiAlarms.alarmType, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmTypeChanged(int)) ); - disconnect( m_uiAlarms.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsAlarmChanged()) ); - setValuesOfAlarmConfig(); - connect( m_uiAlarms.alarmType, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmTypeChanged(int)) ); - connect( m_uiAlarms.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsAlarmChanged()) ); - - setAlarmTextColor( m_uiAlarms.alarms->currentIndex(), - m_uiAlarms.affectedStops->hasCheckedItems() ); - m_alarmsChanged = false; - } else { - setValuesOfAlarmConfig(); - } - - m_lastAlarm = row; -} - -void SettingsUiManager::addAlarmClicked() -{ - // Get an unused name for the new alarm - QString name = i18nc( "@info/plain Default name of a new alarm", "New Alarm" ); - int i = 2; - while( m_alarm.hasName( name ) ) { - name = i18nc( "@info/plain Default name of a new alarm, if other default names are " - "already used", "New Alarm %1", i ); - ++i; - } - - bool ok; - do { - name = KInputDialog::getText( i18nc( "@title:window", "Choose a Name" ), - i18nc( "@label:textbox", "Name of the new Alarm:" ), name, - &ok, m_configDialog, new QRegExpValidator( QRegExp( "[^\\*&]*" ), this ) ); - if( !ok || name.isNull() ) { - return; // Canceled - } - if( m_alarm.hasName( name ) ) { - KMessageBox::information( m_configDialog, i18nc( "@info/plain", - "There is already an alarm with the name %1. " - "Please choose another one.", name ) ); - } else { - // Got a valid name, done with asking for a name - break; - } - } while( true ); - - // Append new alarm settings - AlarmSettings alarm = AlarmSettings( name ); - m_alarm << alarm; - - disconnect( m_uiAlarms.alarms, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmChanged(int)) ); - QAbstractItemModel *model = m_uiAlarms.alarms->model(); - int row = model->rowCount(); - model->insertRow( row ); - QModelIndex index = model->index( row, 0 ); - model->setData( index, name, Qt::DisplayRole ); - setAlarmTextColor( row, !alarm.affectedStops.isEmpty() ); - connect( m_uiAlarms.alarms, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmChanged(int)) ); - - m_uiAlarms.alarms->setCurrentIndex( row ); - - setValuesOfAlarmConfig(); - changed(); -} - -void SettingsUiManager::removeAlarmClicked() -{ - if( m_uiAlarms.alarms->currentIndex() == -1 ) { - return; - } - - m_alarm.removeAt( m_uiAlarms.alarms->currentIndex() ); - disconnect( m_uiAlarms.alarms, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmChanged(int)) ); - m_uiAlarms.alarms->removeItem( m_uiAlarms.alarms->currentIndex() ); - connect( m_uiAlarms.alarms, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmChanged(int)) ); - m_lastAlarm = m_uiAlarms.alarms->currentIndex(); - currentAlarmChanged( m_lastAlarm ); - - alarmChanged(); - changed(); -} - -void SettingsUiManager::renameAlarmClicked() -{ - if( m_uiAlarms.alarms->currentIndex() == -1 ) { - return; - } - - int currentIndex = m_uiAlarms.alarms->currentIndex(); - AlarmSettings currentAlarm = m_alarm[ currentIndex ]; - bool ok; - QString newAlarmName = KInputDialog::getText( i18nc( "@title:window", "Choose a Name" ), - i18nc( "@label:textbox", "New Name of the Alarm:" ), currentAlarm.name, - &ok, m_configDialog, new QRegExpValidator( QRegExp( "[^\\*&]*" ), this ) ); - if( !ok || newAlarmName.isNull() ) { - return; // Canceled - } - - // Get key name of the current filter configuration - if( newAlarmName == currentAlarm.name ) { - return; // Not changed, but the old name was accepted - } - - // Check if the new name is valid. - // '*' or '&' is also not allowed in the name but that's already validated by a QRegExpValidator. - if( newAlarmName.isEmpty() ) { - KMessageBox::information( m_configDialog, i18nc( "@info", "Empty names are not allowed." ) ); - return; - } - - // Check if the new name is already used and ask if it should be overwritten - if( m_alarm.hasName( newAlarmName ) - && KMessageBox::warningYesNo( m_configDialog, - i18nc( "@info", "There is already an alarm configuration with the name " - "%1.Do you want to overwrite it?", - newAlarmName ) ) - != KMessageBox::Yes ) { - return; // No (don't overwrite) pressed - } - - // Remove alarm settings with old name - m_alarm.removeByName( currentAlarm.name ); - - // Change the name to the new one and reinsert - currentAlarm.name = newAlarmName; - m_alarm.insert( currentIndex, currentAlarm ); - - // Update name in the combobox - m_uiAlarms.alarms->model()->setData( m_uiAlarms.alarms->model()->index( currentIndex, 0 ), - newAlarmName, Qt::DisplayRole ); - changed(); -} - -void SettingsUiManager::alarmChanged() -{ - int row = m_uiAlarms.alarms->currentIndex(); - if( row != -1 ) { - // Reenable this alarm for all departures if changed - m_alarm[ row ].lastFired = QDateTime(); - - // Changed alarms are no longer consired auto generated. - // Only auto generated alarms can be removed using the applet's context menu - m_alarm[ row ].autoGenerated = false; - } - m_alarmsChanged = true; - - m_uiAlarms.removeAlarm->setDisabled( m_alarm.isEmpty() ); - m_uiAlarms.renameAlarm->setDisabled( m_alarm.isEmpty() ); -} - -void SettingsUiManager::currentAlarmTypeChanged( int index ) -{ - Q_UNUSED( index ); - // Make font bold if a recurring alarm is selected TODO -// QListWidgetItem *item = m_uiAlarms.alarms->currentItem(); -// QFont font = item->font(); -// font.setBold( static_cast( index ) != AlarmRemoveAfterFirstMatch ); -// item->setFont( font ); - - alarmChanged(); -} - -void SettingsUiManager::affectedStopsFilterChanged() -{ - kDebug() << "Affected stops changed!"; - setFilterConfigurationChanged(); - m_filters.set( currentFilterSettings() ); - setFilterConfigurationChanged( false ); -} - -void SettingsUiManager::affectedStopsAlarmChanged() -{ - setAlarmTextColor( m_uiAlarms.alarms->currentIndex(), - m_uiAlarms.affectedStops->hasCheckedItems() ); - - alarmChanged(); -} - -void SettingsUiManager::setAlarmTextColor( int index, bool hasAffectedStops ) const -{ - // Use negative text color if no affected stop is selected - QColor color = !hasAffectedStops - ? KColorScheme( QPalette::Active ).foreground( KColorScheme::NegativeText ).color() - : KColorScheme( QPalette::Active ).foreground( KColorScheme::NormalText ).color(); -// TODO TEST - m_uiAlarms.alarms->model()->setData( m_uiAlarms.alarms->model()->index( index, 0 ), - QVariant::fromValue( color ), Qt::TextColorRole ); - QPalette p = m_uiAlarms.affectedStops->palette(); - KColorScheme::adjustForeground( p, - hasAffectedStops ? KColorScheme::NormalText : KColorScheme::NegativeText, - QPalette::ButtonText, KColorScheme::Button ); - m_uiAlarms.affectedStops->setPalette( p ); -} - -void SettingsUiManager::stopSettingsAdded() -{ - StopSettings stopSettings = m_stopListWidget->stopSettingsList().last(); - QString text = stopSettings.stops().join( ", " ); - - // Add " in CITY" if a city value is given - if( !stopSettings.get( CitySetting ).isEmpty() ) { - text += " in " + stopSettings.get( CitySetting ); - } - m_uiFilter.affectedStops->addItem( text ); - m_uiAlarms.affectedStops->addItem( text ); - - // Adjust color group settings list - m_colorGroup << ColorGroupSettingsList(); - - updateStopNamesInWidgets(); -} - -void SettingsUiManager::stopSettingsRemoved( QWidget *, int widgetIndex ) -{ - // Store current alarm settings if they are changed - if( m_alarmsChanged && m_uiAlarms.alarms->currentIndex() != -1 ) { - m_alarm[m_uiAlarms.alarms->currentIndex()] = currentAlarmSettings(); - } - - // Adjust stop indices in alarm settings - for( int i = m_alarm.count() - 1; i >= 0; --i ) { - AlarmSettings &alarm = m_alarm[ i ]; - for( int n = alarm.affectedStops.count() - 1; n >= 0; --n ) { - if( alarm.affectedStops[n] == widgetIndex ) { - alarm.affectedStops.removeAt( n ); - } else if( alarm.affectedStops[n] > widgetIndex ) { - --alarm.affectedStops[n]; - } - } - } - - // Adjust stop indices in filter settings - for( int i = m_filters.count() - 1; i >= 0; --i ) { - FilterSettings &filters = m_filters[ i ]; - QSet changedIndices; - for( QSet::iterator it = filters.affectedStops.begin(); - it != filters.affectedStops.end(); ++it ) { - int stopIndex = *it; - if( stopIndex == widgetIndex ) { - it = filters.affectedStops.erase( it ); - } else if( stopIndex > widgetIndex ) { - changedIndices << --stopIndex; - it = filters.affectedStops.erase( it ); - } - - if( it == filters.affectedStops.end() ) { - break; // TODO Make a do or while loop here? - } - } - filters.affectedStops.unite( changedIndices ); - } - - // Adjust color group settings list - m_colorGroup.removeAt( widgetIndex ); - - updateStopNamesInWidgets(); -} - -void SettingsUiManager::stopSettingsChanged() -{ - updateStopNamesInWidgets(); -} - -void SettingsUiManager::updateStopNamesInWidgets() -{ - StopSettingsList stopSettingsList = m_stopListWidget->stopSettingsList(); - - // Get a string for each stop setting - QStringList stopLabels; - foreach( const StopSettings &stopSettings, stopSettingsList ) { - QString text = stopSettings.stops().join( ", " ); - if( !stopSettings.get( CitySetting ).isEmpty() ) { - text += " in " + stopSettings.get( CitySetting ); - } - stopLabels << text; - } - - // Update stop list in the filter settings page - disconnect( m_uiFilter.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsFilterChanged()) ); - m_uiFilter.affectedStops->clear(); - m_uiFilter.affectedStops->addItems( stopLabels ); - - // Get index of filter settings - int index = -1; - QString filterConfiguration = m_uiFilter.filterConfigurations->currentText(); - for( int i = 0; i < m_filters.count(); ++i ) { - if( m_filters[i].name == filterConfiguration ) { - index = i; - kDebug() << "Filter configuration found at" << index << filterConfiguration; - break; - } - } - if( index != -1 ) { - kDebug() << "Update affected stops in GUI of" << index << m_filters[index].name - << m_filters[index].affectedStops; - kDebug() << "From (old GUI settings)" << m_uiFilter.affectedStops->checkedRows(); - - m_uiFilter.affectedStops->setCheckedRows( m_filters[index].affectedStops.toList() ); - } - connect( m_uiFilter.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsFilterChanged()) ); - - // Update stop list in the alarm settings page - disconnect( m_uiAlarms.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsAlarmChanged()) ); - m_uiAlarms.affectedStops->clear(); - m_uiAlarms.affectedStops->addItems( stopLabels ); - if( m_uiAlarms.alarms->currentIndex() != -1 ) { - m_uiAlarms.affectedStops->setCheckedRows( - m_alarm[m_uiAlarms.alarms->currentIndex()].affectedStops ); - } - connect( m_uiAlarms.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsAlarmChanged()) ); -} - -void SettingsUiManager::usedFilterConfigChanged( QWidget *widget ) -{ - disconnect( m_stopListWidget, SIGNAL(changed(int,StopSettings)), - this, SLOT(stopSettingsChanged()) ); - disconnect( m_stopListWidget, SIGNAL(added(QWidget*)), this, SLOT(stopSettingsAdded()) ); - disconnect( m_stopListWidget, SIGNAL(removed(QWidget*,int)), - this, SLOT(stopSettingsRemoved(QWidget*,int )) ); - - int index = widget->objectName().mid( 14 ).toInt(); - StopSettingsList stopSettingsList = m_stopListWidget->stopSettingsList(); - if( stopSettingsList.count() > index ) { - stopSettingsList[ index ].set( FilterConfigurationSetting, - qobject_cast( widget )->currentText() ); - m_stopListWidget->setStopSettingsList( stopSettingsList ); - } - - connect( m_stopListWidget, SIGNAL(changed(int,StopSettings)), - this, SLOT(stopSettingsChanged()) ); - connect( m_stopListWidget, SIGNAL(added(QWidget*)), this, SLOT(stopSettingsAdded()) ); - connect( m_stopListWidget, SIGNAL(removed(QWidget*,int)), - this, SLOT(stopSettingsRemoved(QWidget*,int)) ); -} - -void SettingsUiManager::setValuesOfAdvancedConfig( const Settings &settings ) -{ - m_uiAdvanced.showDepartures->setChecked( settings.departureArrivalListType() == DepartureList ); - m_uiAdvanced.showArrivals->setChecked( settings.departureArrivalListType() == ArrivalList ); - m_uiAdvanced.maximalNumberOfDepartures->setValue( settings.maximalNumberOfDepartures() ); - switch ( settings.additionalDataRequestType() ) { - case Settings::NeverRequestAdditionalData: - m_uiAppearance.cmbDepartureColumnInfos->setCurrentIndex( 2 ); - case Settings::RequestAdditionalDataDirectly: - m_uiAppearance.cmbDepartureColumnInfos->setCurrentIndex( 0 ); - case Settings::RequestAdditionalDataWhenNeeded: - default: - m_uiAppearance.cmbDepartureColumnInfos->setCurrentIndex( 1 ); - } -} - -void SettingsUiManager::setValuesOfAppearanceConfig( const Settings &settings ) -{ - m_uiAppearance.linesPerRow->setValue( settings.linesPerRow() ); - m_uiAppearance.size->setValue( Settings::sizeFromSizeFactor( settings.sizeFactor() ) ); - if ( settings.departureTimeFlags().testFlag(Settings::ShowTimeAndRemainingTime) ) { - m_uiAppearance.cmbDepartureColumnInfos->setCurrentIndex( 0 ); - } else if ( settings.departureTimeFlags().testFlag(Settings::ShowRemainingTime) ) { - m_uiAppearance.cmbDepartureColumnInfos->setCurrentIndex( 2 ); - } else { - m_uiAppearance.cmbDepartureColumnInfos->setCurrentIndex( 1 ); - } - m_uiAppearance.displayTimeBold->setChecked( settings.displayDepartureTimeBold() ); - - m_uiAppearance.shadow->setChecked( settings.drawShadows() ); - m_uiAppearance.radioUseDefaultFont->setChecked( settings.useThemeFont() ); - m_uiAppearance.radioUseOtherFont->setChecked( !settings.useThemeFont() ); - m_uiAppearance.font->setCurrentFont( settings.font() ); - m_uiAppearance.colorize->setChecked( settings.colorize() ); -} - -void SettingsUiManager::setValuesOfAlarmConfig() -{ - kDebug() << "Set Alarm Values, in list:" << m_uiAlarms.alarms->count() - << "in variable:" << m_alarm.count(); - - disconnect( m_uiAlarms.alarms, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmChanged(int)) ); - int row = m_uiAlarms.alarms->currentIndex(); - m_uiAlarms.alarms->clear(); - - QAbstractItemModel *model = m_uiAlarms.alarms->model(); - for( int i = 0; i < m_alarm.count(); ++i ) { - const AlarmSettings &alarm = m_alarm[i]; - - model->insertRow( i ); - QModelIndex index = model->index( i, 0 ); - model->setData( index, alarm.name, Qt::DisplayRole ); - setAlarmTextColor( i, !alarm.affectedStops.isEmpty() ); - - QFont font = m_uiAlarms.alarms->font(); - // TODO: Update this on alarm type change - font.setBold( alarm.type != AlarmRemoveAfterFirstMatch ); - model->setData( index, font, Qt::FontRole ); - } - if( row < m_alarm.count() && row != -1 ) { - m_uiAlarms.alarms->setCurrentIndex( row ); - } else if( !m_alarm.isEmpty() ) { - m_uiAlarms.alarms->setCurrentIndex( row = 0 ); - } - - // Load currently selected alarm, if any - if( row < m_alarm.count() && row != -1 ) { - const AlarmSettings alarm = m_alarm.at( row ); - disconnect( m_uiAlarms.alarmType, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmTypeChanged(int)) ); - m_uiAlarms.alarmType->setCurrentIndex( static_cast( alarm.type ) ); - connect( m_uiAlarms.alarmType, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmTypeChanged(int)) ); - - disconnect( m_uiAlarms.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsAlarmChanged()) ); - m_uiAlarms.affectedStops->setCheckedRows( alarm.affectedStops ); - connect( m_uiAlarms.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsAlarmChanged()) ); - - disconnect( m_uiAlarms.alarmFilter, SIGNAL(changed()), this, SLOT(alarmChanged()) ); - m_uiAlarms.alarmFilter->setFilter( alarm.filter ); - connect( m_uiAlarms.alarmFilter, SIGNAL(changed()), this, SLOT(alarmChanged()) ); - } - - bool enableWidgets = !m_alarm.isEmpty(); - m_uiAlarms.removeAlarm->setEnabled( enableWidgets ); - m_uiAlarms.renameAlarm->setEnabled( enableWidgets ); - m_uiAlarms.lblAlarms->setEnabled( enableWidgets ); - m_uiAlarms.alarms->setEnabled( enableWidgets ); - m_uiAlarms.lblAffectedStops->setEnabled( enableWidgets ); - m_uiAlarms.affectedStops->setEnabled( enableWidgets ); - m_uiAlarms.lblAlarmType->setEnabled( enableWidgets ); - m_uiAlarms.alarmType->setEnabled( enableWidgets ); - m_uiAlarms.grpAlarmFilters->setEnabled( enableWidgets ); - - connect( m_uiAlarms.alarms, SIGNAL(currentIndexChanged(int)), - this, SLOT(currentAlarmChanged(int)) ); -} - -void SettingsUiManager::setValuesOfFilterConfig() -{ - kDebug() << "Set GUI Values"; - if( m_uiFilter.filterConfigurations->currentIndex() == -1 ) { - kDebug() << "No filter configuration selected, select first one now"; - m_uiFilter.filterConfigurations->setCurrentIndex( 0 ); - } - - // Build list of filter configuration names - QStringList filterConfigs = m_filters.names(); - - // Store selected filter configuration - QString currentFilterConfiguration = m_uiFilter.filterConfigurations->currentText(); - - // Clear the list of filter configurations and add the new ones. - // The currentIndexChanged signal is disconnected meanwhile, - // because the filter configuration doesn't need to be reloaded. - disconnect( m_uiFilter.filterConfigurations, SIGNAL(currentIndexChanged(QString)), - this, SLOT(loadFilterConfiguration(QString)) ); - m_uiFilter.filterConfigurations->clear(); - m_uiFilter.filterConfigurations->addItems( filterConfigs ); - if( currentFilterConfiguration.isEmpty() ) { - m_uiFilter.filterConfigurations->setCurrentIndex( 0 ); - } else { - m_uiFilter.filterConfigurations->setCurrentItem( currentFilterConfiguration ); - } - connect( m_uiFilter.filterConfigurations, SIGNAL(currentIndexChanged(QString)), - this, SLOT(loadFilterConfiguration(QString)) ); - - if( currentFilterConfiguration.isEmpty() ) { - currentFilterConfiguration = m_uiFilter.filterConfigurations->currentText(); - kDebug() << "No Item Selected" << currentFilterConfiguration; - } - kDebug() << "Filter configuration selected" << currentFilterConfiguration; - - bool enableWidgets = m_uiFilter.filterConfigurations->count() != 0; - m_uiFilter.lblAffectedStops->setEnabled( enableWidgets ); - m_uiFilter.affectedStops->setEnabled( enableWidgets ); - m_uiFilter.lblFilterAction->setEnabled( enableWidgets ); - m_uiFilter.filterAction->setEnabled( enableWidgets ); - m_uiFilter.grpFilterCriteria->setEnabled( enableWidgets ); - m_uiFilter.filterConfigurations->setEnabled( enableWidgets ); - m_uiFilter.removeFilterConfiguration->setEnabled( enableWidgets ); - m_uiFilter.renameFilterConfiguration->setEnabled( enableWidgets ); - if( enableWidgets ) { - QString filterConfiguration = currentFilterConfiguration; - FilterSettings filters = m_filters.byName( filterConfiguration ); - m_uiFilter.filterAction->setCurrentIndex( static_cast(filters.filterAction) ); - - disconnect( m_uiFilter.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsFilterChanged()) ); - m_uiFilter.affectedStops->setCheckedRows( filters.affectedStops.toList() ); - connect( m_uiFilter.affectedStops, SIGNAL(checkedItemsChanged()), - this, SLOT(affectedStopsFilterChanged()) ); - - // Clear old filter widgets - int minWidgetCount = m_uiFilter.filters->minimumWidgetCount(); - int maxWidgetCount = m_uiFilter.filters->maximumWidgetCount(); - m_uiFilter.filters->setWidgetCountRange(); - m_uiFilter.filters->removeAllWidgets(); - - // Setup FilterWidgets from m_filters - foreach( const Filter & filter, filters.filters ) { - m_uiFilter.filters->addFilter( filter ); - } - - m_uiFilter.filters->setWidgetCountRange( minWidgetCount, maxWidgetCount ); - } -} - -Settings SettingsUiManager::settings() -{ - Settings ret; - - // Set stop settings list (general settings page) - ret.setStops( m_stopListWidget->stopSettingsList() ); - - // Set stored "no-Gui" settings (without widgets in the configuration dialog) - ret.setColorGroups( m_colorGroup ); - ret.setCurrentStop( m_currentStopSettingsIndex ); - if( ret.currentStopIndex() >= ret.stops().count() ) { - ret.setCurrentStop( ret.stops().count() - 1 ); - } - - // Set filter settings list and update stored settings if there are changes in the GUI widgets - if( m_filterConfigChanged ) { - m_filters.set( currentFilterSettings() ); - } - ret.setFilters( m_filters ); - - // Set alarm settings list and update stored settings if there are changes in the GUI widgets - if( m_alarmsChanged && m_uiAlarms.alarms->currentIndex() != -1 ) { - m_alarm[ m_uiAlarms.alarms->currentIndex() ] = currentAlarmSettings(); - } - ret.setAlarms( m_alarm ); - - // Set advanced settings - if( m_uiAdvanced.showArrivals->isChecked() ) { - ret.setDepartureArrivalListType( ArrivalList ); - } else { - ret.setDepartureArrivalListType( DepartureList ); - } - - Settings::SettingsFlags flags = Settings::NoSettingsFlags; - if ( m_hideTargetColumn ) { - flags |= Settings::HideTargetColumn; - } - if ( m_uiAppearance.shadow->isChecked() ) { - flags |= Settings::DrawShadows; - } - if ( m_uiAppearance.radioUseDefaultFont->isChecked() ) { - flags |= Settings::UseThemeFont; - } - if ( m_uiAppearance.colorize->isChecked() ) { - flags |= Settings::ColorizeDepartureGroups; - } - ret.setSettingsFlags( flags ); - - switch ( m_uiAdvanced.additionalData->currentIndex() ) { - case 2: - ret.setAdditionalDataRequestType( Settings::NeverRequestAdditionalData ); - break; - case 0: - ret.setAdditionalDataRequestType( Settings::RequestAdditionalDataDirectly ); - break; - case 1: - ret.setAdditionalDataRequestType( Settings::RequestAdditionalDataWhenNeeded ); - break; - default: - kDebug() << "Unknown additional data request type index:" << m_uiAdvanced.additionalData->currentIndex() - << "Using default type:" << Settings::DefaultAdditionalDataRequestType; - ret.setAdditionalDataRequestType( Settings::DefaultAdditionalDataRequestType ); - break; - } - - // Set appearance settings - Settings::DepartureTimeFlags timeFlags = Settings::DoNotShowDepartureTime; - if ( m_uiAppearance.cmbDepartureColumnInfos->currentIndex() != 1 ) { - timeFlags |= Settings::ShowRemainingTime; - } - if ( m_uiAppearance.cmbDepartureColumnInfos->currentIndex() <= 1 ) { - timeFlags |= Settings::ShowDepartureTime; - } - if ( m_uiAppearance.displayTimeBold->checkState() == Qt::Checked ) { - timeFlags |= Settings::DisplayDepartureTimeBold; - } - ret.setDepartureTimeFlags( timeFlags ); - - ret.setMaximalNumberOfDepartures( m_uiAdvanced.maximalNumberOfDepartures->value() ); - ret.setLinesPerRow( m_uiAppearance.linesPerRow->value() ); - ret.setSizeFactor( Settings::sizeFactorFromSize(m_uiAppearance.size->value()) ); - if( flags.testFlag(Settings::UseThemeFont) ) { - ret.setFont( Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont) ); - } else { - QFont font = ret.font(); - font.setFamily( m_uiAppearance.font->currentFont().family() ); - ret.setFont( font ); - } - - return ret; -} - -FilterSettings SettingsUiManager::currentFilterSettings() const -{ - FilterSettings filters; - filters.filterAction = static_cast< FilterAction >( m_uiFilter.filterAction->currentIndex() ); - filters.affectedStops = m_uiFilter.affectedStops->checkedRows().toSet(); - filters.filters = m_uiFilter.filters->filters(); - filters.name = m_uiFilter.filterConfigurations->currentText(); - return filters; -} - -AlarmSettings SettingsUiManager::currentAlarmSettings( const QString &name ) const -{ - Q_ASSERT( m_uiAlarms.alarms->currentIndex() != -1 ); - - AlarmSettings alarm; - int row = m_uiAlarms.alarms->findText( name ); - if( row >= 0 && row < m_alarm.count() ) { - alarm = m_alarm[ row ]; - } else { - kDebug() << "No existing alarm settings found for the current alarm" << name; - } -// alarm.enabled = m_uiAlarms.alarms->currentItem()->checkState() == Qt::Checked; - alarm.name = name.isNull() ? m_uiAlarms.alarms->currentText() : name; - alarm.affectedStops = m_uiAlarms.affectedStops->checkedRows(); - alarm.type = static_cast( m_uiAlarms.alarmType->currentIndex() ); - alarm.filter = m_uiAlarms.alarmFilter->filter(); - return alarm; -} - -void SettingsUiManager::loadFilterConfiguration( const QString &filterConfig ) -{ - if( filterConfig.isEmpty() ) { - return; - } - - if( filterConfig == m_lastFilterConfiguration ) { - return; // Selected the same filter configuration again - } - - if( m_filterConfigChanged && !m_lastFilterConfiguration.isEmpty() ) { - // Store to previously selected filter configuration - FilterSettings filters = currentFilterSettings(); - filters.name = m_lastFilterConfiguration; - m_filters.set( filters ); - } - - m_lastFilterConfiguration = filterConfig; - setValuesOfFilterConfig(); - setFilterConfigurationChanged( false ); -} - -void SettingsUiManager::addFilterConfiguration() -{ - // Get an unused filter configuration name - QString newFilterConfig = i18nc( "@info/plain Default name of a new filter configuration", - "New Configuration" ); - int i = 2; - while( m_filters.hasName( newFilterConfig ) ) { - newFilterConfig = i18nc( "@info/plain Default name of a new filter configuration " - "if the other default names are already used", - "New Configuration %1", i ); - ++i; - } - - bool ok; - do { - newFilterConfig = KInputDialog::getText( i18nc( "@title:window", "Choose a Name" ), - i18nc( "@label:textbox", "Name of the new Filter Configuration:" ), newFilterConfig, - &ok, m_configDialog, new QRegExpValidator( QRegExp( "[^\\*&]*" ), this ) ); - if( !ok || newFilterConfig.isNull() ) { - return; // Canceled - } - if( m_filters.hasName( newFilterConfig ) ) { - KMessageBox::information( m_configDialog, i18nc( "@info/plain", - "There is already a filter configuration with the name %1. " - "Please choose another one.", newFilterConfig ) ); - } else { - // Got a valid name, done with asking for a name - break; - } - } while( true ); - - // Append new filter settings - FilterSettings filters; - filters.name = newFilterConfig; - m_filters << filters; - - m_uiFilter.filterConfigurations->setCurrentItem( newFilterConfig, true ); - setFilterConfigurationChanged(); - changed(); -} - -void SettingsUiManager::removeFilterConfiguration() -{ - int index = m_uiFilter.filterConfigurations->currentIndex(); - if( index == -1 ) { - kDebug() << "No selection, nothing to delete"; - return; - } - - // Show a warning - QString currentFilterConfiguration = m_uiFilter.filterConfigurations->currentText(); - if( KMessageBox::warningContinueCancel( m_configDialog, - i18nc( "@info", "This will permanently delete the selected filter " - "configuration %1.", currentFilterConfiguration ), - QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), - "deleteFilterSettings" ) - != KMessageBox::Continue ) { - return; // Cancel clicked - } - - // Remove filter configuration from the filter settings list - m_filters.removeByName( currentFilterConfiguration ); - - // Remove filter configuration from the UI filter list - // but without calling loadFilterConfiguration here, therefore the disconnect - disconnect( m_uiFilter.filterConfigurations, SIGNAL(currentIndexChanged(QString)), - this, SLOT(loadFilterConfiguration(QString)) ); - m_uiFilter.filterConfigurations->removeItem( index ); - connect( m_uiFilter.filterConfigurations, SIGNAL(currentIndexChanged(QString)), - this, SLOT(loadFilterConfiguration(QString)) ); - - // Select default filter configuration - if( index >= m_uiFilter.filterConfigurations->count() ) { - index = m_uiFilter.filterConfigurations->count() - 1; - } - if( index != -1 ) { - m_uiFilter.filterConfigurations->setCurrentIndex( index ); - } else { - setValuesOfFilterConfig(); - } - - changed(); -} - -void SettingsUiManager::renameFilterConfiguration() -{ - QString currentFilterConfiguration = m_uiFilter.filterConfigurations->currentText(); - bool ok; - QString newFilterConfig = KInputDialog::getText( i18nc( "@title:window", "Choose a Name" ), - i18nc( "@label:textbox", "New Name of the Filter Configuration:" ), currentFilterConfiguration, - &ok, m_configDialog, new QRegExpValidator( QRegExp( "[^\\*&]*" ), this ) ); - if( !ok || newFilterConfig.isNull() ) { - return; // Canceled - } - - // Get key name of the current filter configuration - if( newFilterConfig == currentFilterConfiguration ) { - return; // Not changed, but the old name was accepted - } - - // Check if the new name is valid. - // '*' or '&' is also not allowed in the name but that's already validated by a QRegExpValidator. - if( newFilterConfig.isEmpty() ) { - KMessageBox::information( m_configDialog, i18nc( "@info", "Empty names are not allowed." ) ); - return; - } - - // Check if the new name is already used and ask if it should be overwritten - if( m_filters.hasName( newFilterConfig ) - && KMessageBox::warningYesNo( m_configDialog, - i18nc( "@info", "There is already a filter configuration with the name " - "%1.Do you want to overwrite it?", - newFilterConfig ) ) - != KMessageBox::Yes ) { - return; // No (don't overwrite) pressed - } - - // Remove the filter configuration from the old key name - // and add it with the new key name - FilterSettings filters = m_filters.byName( currentFilterConfiguration ); - m_filters.removeByName( currentFilterConfiguration ); - filters.name = newFilterConfig; - m_filters.set( filters ); - - // Remove old name from the list of filter configurations and add the new one. - // The currentIndexChanged signal is disconnected while changing the name, - // because the filter configuration doesn't need to be reloaded. - disconnect( m_uiFilter.filterConfigurations, SIGNAL(currentIndexChanged(QString)), - this, SLOT(loadFilterConfiguration(QString)) ); - int index = m_uiFilter.filterConfigurations->currentIndex(); - if( index == -1 ) { - kDebug() << "Removed filter config not found in list" << currentFilterConfiguration; - } else { - m_uiFilter.filterConfigurations->removeItem( index ); - } - m_uiFilter.filterConfigurations->setCurrentItem( newFilterConfig, true ); - m_lastFilterConfiguration = newFilterConfig; - connect( m_uiFilter.filterConfigurations, SIGNAL(currentIndexChanged(QString)), - this, SLOT(loadFilterConfiguration(QString)) ); - - // Update filter configuration name in stop settings - StopSettingsList stopSettingsList = m_stopListWidget->stopSettingsList(); - for( int i = 0; i < stopSettingsList.count(); ++i ) { - if( stopSettingsList[i].get( FilterConfigurationSetting ) == currentFilterConfiguration ) { - stopSettingsList[i].set( FilterConfigurationSetting, newFilterConfig ); - } - } - m_stopListWidget->setStopSettingsList( stopSettingsList ); - changed(); - - // Update widgets containing a list of filter configuration names - // NOTE not needed any longer (because of pointers to m_filters -// updateFilterConfigurationLists(); -} - -void SettingsUiManager::filterActionChanged( int index ) -{ - FilterAction filterAction = static_cast< FilterAction >( index ); - - // Store to last edited filter settings - QString currentFilterConfiguration = m_uiFilter.filterConfigurations->currentText(); - FilterSettings filters = m_filters.byName( currentFilterConfiguration ); - filters.filterAction = filterAction; - m_filters.set( filters ); - - kDebug() << "Filter configuration changed to" << filterAction; -// setFilterConfigurationChanged(); -} - -void SettingsUiManager::filtersChanged() -{ - kDebug() << "Filters changed, directly write them to m_filters"; - m_filters.set( currentFilterSettings() ); -} - -void SettingsUiManager::setFilterConfigurationChanged( bool changed ) -{ - if( m_filterConfigChanged == changed ) { - return; - } - - bool noFilter = m_filters.isEmpty(); - m_uiFilter.filterConfigurations->setDisabled( noFilter ); - m_uiFilter.removeFilterConfiguration->setDisabled( noFilter ); - m_uiFilter.renameFilterConfiguration->setDisabled( noFilter ); - - m_filterConfigChanged = changed; -} - -int SettingsUiManager::filterConfigurationIndex( const QString &filterConfig ) -{ - int index = m_uiFilter.filterConfigurations->findText( filterConfig ); - if( index == -1 ) { - kWarning() << "Item" << filterConfig << "not found!"; - } - return index; -} - -void SettingsUiManager::exportFilterSettings() -{ - QString fileName = KFileDialog::getSaveFileName( - KUrl( "kfiledialog:///filters" ), QString(), m_configDialog, - i18nc( "@title:window", "Export Filter Settings" ) ); - if( fileName.isEmpty() ) { - return; - } - - KConfig config( fileName, KConfig::SimpleConfig ); - SettingsIO::writeFilterConfig( currentFilterSettings(), config.group( QString() ) ); -} - -void SettingsUiManager::importFilterSettings() -{ - QString fileName = KFileDialog::getOpenFileName( - KUrl( "kfiledialog:///filters" ), QString(), m_configDialog, - i18nc( "@title:window", "Import Filter Settings" ) ); - if( fileName.isEmpty() ) { - return; - } - - KConfig config( fileName, KConfig::SimpleConfig ); - FilterSettings filters = SettingsIO::readFilterConfig( config.group( QString() ) ); -// TODO: Set filters in GUI -} -// kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/applet/settingsui.h b/applet/settingsui.h deleted file mode 100644 index 5810c64..0000000 --- a/applet/settingsui.h +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file - * @brief This file contains classes for synchronizing settings with widgets in the UI. - * @author Friedrich Pülz */ - -#ifndef SETTINGSUI_HEADER -#define SETTINGSUI_HEADER - -// UI includes -#include "ui_publicTransportConfig.h" -#include "ui_publicTransportConfigAdvanced.h" -#include "ui_publicTransportFilterConfig.h" -#include "ui_publicTransportAppearanceConfig.h" -#include "ui_alarmConfig.h" - -// Own includes -#include "settings.h" - -class KConfigDialog; -namespace PublicTransport { - class LocationModel; - class ServiceProviderModel; - class StopListWidget; -} - -/** - * @brief Manages the configuration dialog and synchronizes with Settings. - * - * Get the current settings in the dialog using settings(), changing the settings programatically - * is only done class intern. - **/ -class SettingsUiManager : public QObject { - Q_OBJECT - -public: - enum DeletionPolicy { - DeleteWhenFinished, - KeepWhenFinished - }; - - SettingsUiManager( const Settings &settings, KConfigDialog *parentDialog, - DeletionPolicy deletionPolicy = DeleteWhenFinished ); - - /** @brief Gets a Settings object with the current settings in the dialog. */ - Settings settings(); - -signals: - void settingsAccepted( const Settings &settings ); - void settingsFinished(); - -public slots: - void removeAlarms( const AlarmSettingsList &newAlarmSettings, - const QList &removedAlarms ); - -protected slots: - void changed(); - - /** @brief The config dialog has been closed. */ - void configFinished(); - - /** @brief Ok pressed in the config dialog. */ - void configAccepted(); - - /** @brief Loads the given @p filterConfig into the dialog. */ - void loadFilterConfiguration( const QString &filterConfig ); - - /** @brief Adds a new filter configuration. */ - void addFilterConfiguration(); - - /** @brief Removes the current filter configuration. */ - void removeFilterConfiguration(); - - /** @brief Renames the current filter configuration. */ - void renameFilterConfiguration(); - - /** @brief The action of the current filter has been changed. */ - void filterActionChanged( int index ); - - /** @brief The list of affected stops of a filter configuration has been changed. */ - void affectedStopsFilterChanged(); - - void filtersChanged(); - void setFilterConfigurationChanged( bool changed = true ); - - void exportFilterSettings(); - void importFilterSettings(); - - /** @brief Another alarm has been selected. */ - void currentAlarmChanged( int row ); - - /** @brief The add alarm button has been clicked. */ - void addAlarmClicked(); - - /** @brief The remove alarm button has been clicked. */ - void removeAlarmClicked(); - - /** @brief The rename alarm button has been clicked. */ - void renameAlarmClicked(); - - /** @brief The current alarm has been changed. */ - void alarmChanged(); - - /** @brief The type of the current alarm has been changed. */ - void currentAlarmTypeChanged( int index ); - - /** @brief The list of affected stops of this alarm has been changed. */ - void affectedStopsAlarmChanged(); - - /** @brief An alarm item has been changed, eg. it's text or checked state. */ - void alarmChanged( int index ); - - /** @brief The list of stop settings has been changed. */ - void stopSettingsChanged(); - - /** @brief A new stop has been added. */ - void stopSettingsAdded(); - - /** @brief A stop has been removed. */ - void stopSettingsRemoved( QWidget *widget, int widgetIndex ); - - /** @brief The used filter configuration has been changed in the "filter uses" - * tab of the filters page. */ - void usedFilterConfigChanged( QWidget *widget ); - -protected: - void setValuesOfAdvancedConfig( const Settings &settings ); - void setValuesOfFilterConfig(); - void setValuesOfAlarmConfig(); - void setValuesOfAppearanceConfig( const Settings &settings ); - -private: - FilterSettings currentFilterSettings() const; - AlarmSettings currentAlarmSettings( const QString &name = QString() ) const; - int filterConfigurationIndex( const QString &filterConfig ); - void setAlarmTextColor( int index, bool hasAffectedStops = true ) const; - void updateStopNamesInWidgets(); - - DeletionPolicy m_deletionPolicy; -// DataSourceTester *m_dataSourceTester; // Tests data sources - KConfigDialog *m_configDialog; // Stored for the service provider info dialog as parent - - Ui::publicTransportConfig m_ui; - Ui::publicTransportConfigAdvanced m_uiAdvanced; - Ui::publicTransportAppearanceConfig m_uiAppearance; - Ui::publicTransportFilterConfig m_uiFilter; - Ui::alarmConfig m_uiAlarms; - - ServiceProviderModel *m_modelServiceProvider; // The model for the service provider combobox in the config dialog - LocationModel *m_modelLocations; // The model for the location combobox in the config dialog - QVariantHash m_serviceProviderData; // Service provider information from the data engine - QVariantHash m_locationData; // Location information from the data engine. - - StopListWidget *m_stopListWidget; - - int m_currentStopSettingsIndex; - bool m_showHeader; - bool m_hideTargetColumn; - - FilterSettingsList m_filters; - QString m_lastFilterConfiguration; // The last set filter configuration - bool m_filterConfigChanged; // Whether or not the filter configuration has changed from that defined in the filter configuration with the name [m_lastFilterConfiguration] - - QList m_colorGroup; - - AlarmSettingsList m_alarm; - int m_lastAlarm; - bool m_alarmsChanged; -}; - -#endif // SETTINGSUI_HEADER diff --git a/applet/stopaction.cpp b/applet/stopaction.cpp deleted file mode 100644 index 0cae515..0000000 --- a/applet/stopaction.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "stopaction.h" - -#include -#include - -StopAction::StopAction( StopAction::Type type, QObject* parent, StopAction::TitleType titleType, - const QString &stopName, const QString &stopNameShortened ) - : QAction(parent), m_type(type), m_titleType(titleType), - m_stopName(stopName), m_stopNameShortened(stopNameShortened) -{ - switch ( type ) { - case ShowDeparturesForStop: - setIcon( KIcon("public-transport-stop") ); - switch ( titleType ) { - case ShowActionNameOnly: - setText( i18nc("@action:inmenu", "Show &Departures From This Stop") ); - break; - case ShowStopNameOnly: - setText( i18nc("@action:inmenu", "Show &Departures From '%1'", m_stopNameShortened) ); - break; - case ShowActionNameAndStopName: - setText( m_stopNameShortened ); - break; - } - break; - case ShowStopInMap: - setIcon( KIcon("marble") ); - switch ( titleType ) { - case ShowActionNameOnly: - setText( i18nc("@action:inmenu", "Show This Stop in a Map") ); - break; - case ShowStopNameOnly: - setText( i18nc("@action:inmenu", "Show '%1' in a Map", m_stopNameShortened) ); - break; - case ShowActionNameAndStopName: - setText( m_stopNameShortened ); - break; - } - break; - case HighlightStop: - setIcon( KIcon("edit-select") ); - switch ( titleType ) { - case ShowActionNameOnly: // TODO "Unhighlight" if the stop is already highlighted - setText( i18nc("@action:inmenu", "&Highlight This Stop") ); - break; - case ShowStopNameOnly: - setText( i18nc("@action:inmenu", "&Highlight '%1'", m_stopNameShortened) ); - break; - case ShowActionNameAndStopName: - setText( m_stopNameShortened ); - break; - } - break; - case CreateFilterForStop: - setIcon( KIcon("view-filter") ); - switch ( titleType ) { - case ShowActionNameOnly: - setText( i18nc("@action:inmenu", "&Create Filter 'Via This Stop'") ); - break; - case ShowStopNameOnly: - setText( i18nc("@action:inmenu", "&Create Filter 'Via %1'", m_stopNameShortened) ); - break; - case ShowActionNameAndStopName: - setText( m_stopNameShortened ); - break; - } - break; - case CopyStopNameToClipboard: - setIcon( KIcon("edit-copy") ); - switch ( titleType ) { - case ShowActionNameOnly: - setText( i18nc("@action:inmenu", "&Copy Stop Name") ); - break; - case ShowStopNameOnly: - setText( i18nc("@action:inmenu", "&Copy '%1'", m_stopNameShortened) ); - break; - case ShowActionNameAndStopName: - setText( m_stopNameShortened ); - break; - } - break; - case RequestJourneysFromStop: - setIcon( KIcon("edit-find") ); - switch ( titleType ) { - case ShowActionNameOnly: - setText( i18nc("@action:inmenu", "&Search Journeys From This Stop") ); - break; - case ShowStopNameOnly: - setText( i18nc("@action:inmenu", "&Search Journeys From '%1'", m_stopNameShortened) ); - break; - case ShowActionNameAndStopName: - setText( m_stopNameShortened ); - break; - } - break; - case RequestJourneysToStop: - setIcon( KIcon("edit-find") ); - switch ( titleType ) { - case ShowActionNameOnly: - setText( i18nc("@action:inmenu", "&Search Journeys to This Stop") ); - break; - case ShowStopNameOnly: - setText( i18nc("@action:inmenu", "&Search Journeys to '%1'", m_stopNameShortened) ); - break; - case ShowActionNameAndStopName: - setText( m_stopNameShortened ); - break; - } - break; - } - - connect( this, SIGNAL(triggered()), this, SLOT(slotTriggered()) ); -} - -void StopAction::slotTriggered() -{ - emit stopActionTriggered( m_type, m_stopName, m_stopNameShortened ); -} diff --git a/applet/stopaction.h b/applet/stopaction.h deleted file mode 100644 index f3771c8..0000000 --- a/applet/stopaction.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef STOPACTION_HEADER -#define STOPACTION_HEADER - -/** @file - * Contains the StopAction class for actions with associated route stops. - * @author Friedrich Pülz */ - -#include // Base class - -class StopAction : public QAction -{ - Q_OBJECT -public: - /** @brief Actions for intermediate stops, shown in RouteGraphicsItems. */ - enum Type { - ShowDeparturesForStop, /**< Show a departure list for the associated stop. */ - CreateFilterForStop, /**< Create a filter via the associated stop. */ - CopyStopNameToClipboard, /**< Copy the name of the associated stop to the clipboard. */ - HighlightStop, /**< Highlight the associated stop in all route items. - * If the stop was already highlighted, it should - * be unhighlighted. */ - RequestJourneysToStop, /**< Request journeys to the associated stop. The origin stop - * can be given as QVariant data argument to stop action requests. */ - RequestJourneysFromStop, /**< Request journeys from the associated stop. The target stop - * can be given as QVariant data argument to stop action requests. */ - ShowStopInMap /**< Show a map with the stop, eg. in a web browser. */ - }; - - enum TitleType { - ShowActionNameOnly, - ShowStopNameOnly, - ShowActionNameAndStopName - }; - - StopAction( Type type, QObject* parent, TitleType titleType = ShowActionNameOnly, - const QString &stopName = QString(), const QString &stopNameShortened = QString() ); - - Type type() const { return m_type; }; - TitleType titleType() const { return m_titleType; }; - - QString stopName() const { return m_stopName; }; - QString stopNameShortened() const { return m_stopNameShortened; }; - void setStopName( const QString &stopName, const QString &stopNameShortened = QString() ) { - m_stopName = stopName; - m_stopNameShortened = stopNameShortened.isEmpty() ? stopName : stopNameShortened; - }; - -signals: - /** - * @brief This signal gets fired when this QAction signals triggered(), but with more arguments. - * - * @param type The type of the triggered stop action. - * @param stopName The name of the stop associated with the action. - * @param stopNameShortened The shortened name of the stop associated with the action. - **/ - void stopActionTriggered( StopAction::Type type, - const QString &stopName, const QString &stopNameShortened ); - -protected slots: - void slotTriggered(); - -private: - Q_DISABLE_COPY(StopAction) - - const Type m_type; - const TitleType m_titleType; - QString m_stopName; - QString m_stopNameShortened; -}; - -#endif // STOPACTION_HEADER diff --git a/applet/tests/CMakeLists.txt b/applet/tests/CMakeLists.txt deleted file mode 100644 index d5be998..0000000 --- a/applet/tests/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ - -find_package( KDE4 REQUIRED ) -include( KDE4Defaults ) - -include_directories( ${QT_INCLUDES} ${KDE4_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR} ) -set( CMAKE_EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) - -set( PublicTransportAppletTest_SRCS PublicTransportAppletTest.cpp ) -qt4_automoc( ${PublicTransportAppletTest_SRCS} ) -add_executable( PublicTransportAppletTest ${PublicTransportAppletTest_SRCS} ) -add_test( PublicTransportAppletTest PublicTransportAppletTest ) -target_link_libraries( PublicTransportAppletTest - ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDE4_PLASMA_LIBS} publictransporthelper -) diff --git a/applet/tests/PublicTransportAppletTest.cpp b/applet/tests/PublicTransportAppletTest.cpp deleted file mode 100644 index a61add7..0000000 --- a/applet/tests/PublicTransportAppletTest.cpp +++ /dev/null @@ -1,380 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#include "PublicTransportAppletTest.h" - -// This can be uncommented, to wait for updates to be drawn, animations to finish (manual check) -// #define WaitForGuiUpdates - -#ifdef WaitForGuiUpdates - #define UpdateGUI() for ( int i = 0; i < 50; ++i ) { QCoreApplication::processEvents(); QTest::qSleep(10); } -#else - #define UpdateGUI() -#endif - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -void PublicTransportAppletTest::initTestCase() -{ - m_dialog = NULL; - m_pageGeneral = NULL; - m_pageFilter = NULL; - m_pageAlarms = NULL; - m_pageGeneralWidget = NULL; - m_pageFilterWidget = NULL; - m_pageAlarmsWidget = NULL; - m_pageWidget = NULL; - m_pageModel = NULL; - m_stopsWidget = NULL; - m_filterConfigurationsWidget = NULL; - m_filterAction = NULL; - m_affectedStops = NULL; - m_filtersWidget = NULL; - m_addFilterConfiguration = NULL; - m_removeFilterConfiguration = NULL; - - // Init settings - m_stopSettings.setStop( Stop(QLatin1String("Custom Stop"), QLatin1String("123456")) ); - QCOMPARE( m_stopSettings.stops().count(), 1 ); - QCOMPARE( m_stopSettings.stopList().count(), 1 ); - QCOMPARE( m_stopSettings.stop(0).name, QLatin1String("Custom Stop") ); - QCOMPARE( m_stopSettings.stop(0).id, QLatin1String("123456") ); - QCOMPARE( m_stopSettings.stop(0).nameOrId(), QLatin1String("123456") ); - - m_stopSettings.set( ServiceProviderSetting, QLatin1String("de_db") ); - QCOMPARE( m_stopSettings[ServiceProviderSetting].toString(), QLatin1String("de_db") ); - - m_stopSettings.set( LocationSetting, QLatin1String("de") ); - QCOMPARE( m_stopSettings[LocationSetting].toString(), QLatin1String("de") ); - - FilterSettings filters1, filters2; - filters1.name = "Filter configuration 1"; - Filter filter1 = Filter() << Constraint( FilterByTarget, FilterContains, "TestTarget" ); - filters1.filters << filter1; - filters1.affectedStops << 0; - - filters2.name = "Filter configuration 2"; - Filter filter2 = Filter() << Constraint( FilterByTarget, FilterContains, "TestTarget2" ); - filters2.filters << filter2; - - m_filterConfigurations << filters1 << filters2; - - // Add the desktop containment - m_corona = new Plasma::Corona( this ); - m_containment = m_corona->addContainment( "desktop" ); - QVERIFY2( m_containment, "The plasma desktop containment could not be added. Ensure that you " - "have plasma installed." ); - - // Add the PublicTransport applet - m_applet = m_containment->addApplet( "publictransport" ); - QVERIFY2( m_applet, "The plasma desktop containment could not be added. Ensure that you have " - "plasma installed." ); -} - -void PublicTransportAppletTest::init() -{ - // Set settings of the PublicTransport applet using a specific slot - int indexSetSettings = m_applet->metaObject()->indexOfSlot( - /*QMetaType::*/ "setSettings(StopSettingsList,FilterSettingsList)" ); - QVERIFY2( indexSetSettings != -1, "Couldn't find slot with signarture " - "setSettings(StopSettingsList,FilterSettingsList) in the publicTransport m_applet." ); - - bool success = m_applet->metaObject()->method( indexSetSettings ).invoke( m_applet, - Q_ARG(QList, StopSettingsList() << m_stopSettings), - Q_ARG(FilterSettingsList, m_filterConfigurations) ); - QVERIFY2( success, "A call to setSettings in the publicTransport m_applet wasn't successful." ); - - // Create and show the configuration dialog - KConfigSkeleton config; - m_dialog = new KConfigDialog( NULL, "Applet Settings", &config ); - m_applet->createConfigurationInterface( m_dialog ); - m_dialog->show(); - - // Find main page widget and get it's model - m_pageWidget = m_dialog->findChild(); - QVERIFY2( m_pageWidget, "No KPageWidget found in the dialog." ); - m_pageModel = static_cast(m_pageWidget->model()); - QVERIFY2( m_pageModel, "No KPageWidgetModel found for the KPageWidget." ); - - // Find sub widgets (inside the pages) - m_pageGeneralWidget = m_dialog->findChild("generalTabWidget"); - QVERIFY2( m_pageGeneralWidget, "The widget for the general page wasn't found (widget with name 'generalTabWidget')." ); - m_pageFilterWidget = m_dialog->findChild("publicTransportFilterConfig"); - QVERIFY2( m_pageFilterWidget, "The widget for the filter page wasn't found (widget with name 'publicTransportFilterConfig')." ); - m_pageAlarmsWidget = m_dialog->findChild("alarmConfig"); - QVERIFY2( m_pageAlarmsWidget, "The widget for the alarms page wasn't found (widget with name 'alarmConfig')." ); - - // Find page items - m_pageGeneral = NULL; - m_pageFilter = NULL; - m_pageAlarms = NULL; - for ( int row = 0; row < m_pageModel->rowCount(); ++row ) { - KPageWidgetItem *page = m_pageModel->item( m_pageModel->index(row, 0) ); - if ( page->widget() == m_pageGeneralWidget->parentWidget() ) { - m_pageGeneral = page; - } else if ( page->widget() == m_pageFilterWidget->parentWidget() ) { - m_pageFilter = page; - } else if ( page->widget() == m_pageAlarmsWidget->parentWidget() ) { - m_pageAlarms = page; - } - } - QVERIFY2( m_pageGeneral, "General page wasn't found in the configuration dialog of the applet." ); - QVERIFY2( m_pageFilter, "Filter page wasn't found in the configuration dialog of the applet." ); - QVERIFY2( m_pageAlarms, "Alarms page wasn't found in the configuration dialog of the applet." ); - - // Find stop list widgets - m_stopsWidget = m_pageGeneralWidget->findChild(); - QVERIFY2( m_stopsWidget, "The StopListWidget showing the list of stops wasn't found." ); - - // Find filter widgets - m_filterConfigurationsWidget = m_pageFilterWidget->findChild( "filterConfigurations" ); - QVERIFY2( m_filterConfigurationsWidget, "The KCombobox showing the filter configurations wasn't found (widget with name 'filterConfigurations')." ); - m_filtersWidget = m_pageFilterWidget->findChild("filters"); - QVERIFY2( m_filtersWidget, "The widget showing the current filter constraints wasn't found (widget with name 'filters')." ); - m_affectedStops = m_pageFilterWidget->findChild("affectedStops"); - QVERIFY2( m_affectedStops, "The widget showing the affected stops of the current filter wasn't found (widget with name 'affectedStops')." ); - m_filterAction = m_pageFilterWidget->findChild("filterAction"); - QVERIFY2( m_filterAction, "The widget showing the action of the current filter wasn't found (widget with name 'filterAction')." ); - m_addFilterConfiguration = m_pageFilterWidget->findChild("addFilterConfiguration"); - QVERIFY2( m_addFilterConfiguration, "The widget to add a new filter configuration wasn't found (widget with name 'addFilterConfiguration')." ); - m_removeFilterConfiguration = m_pageFilterWidget->findChild("removeFilterConfiguration"); - QVERIFY2( m_removeFilterConfiguration, "The widget to remove a filter configuration wasn't found (widget with name 'removeFilterConfiguration')." ); -} - -void PublicTransportAppletTest::cleanup() -{ - delete m_dialog; - m_dialog = NULL; - m_pageGeneral = NULL; - m_pageFilter = NULL; - m_pageAlarms = NULL; - m_pageGeneralWidget = NULL; - m_pageFilterWidget = NULL; - m_pageAlarmsWidget = NULL; - m_pageWidget = NULL; - m_pageModel = NULL; - m_stopsWidget = NULL; - m_filterConfigurationsWidget = NULL; - m_filterAction = NULL; - m_affectedStops = NULL; - m_filtersWidget = NULL; - m_addFilterConfiguration = NULL; - m_removeFilterConfiguration = NULL; -} - -void PublicTransportAppletTest::cleanupTestCase() -{ - m_containment->clearApplets(); - delete m_applet; - delete m_containment; -} - -void PublicTransportAppletTest::simulateAddFilterConfiguration() { - // Click the add filter configuration button, - // call dialogDone later to automatically accept the dialog without blocking here - QTimer::singleShot( 50, this, SLOT(acceptSubDialog()) ); - QTest::mouseClick( m_addFilterConfiguration, Qt::LeftButton ); // Blocks here until the dialogDone() timer signals - UpdateGUI(); -} - -void PublicTransportAppletTest::simulateRemoveFilterConfiguration() { - // Click the add filter configuration button, - // call dialogDone later to automatically accept the dialog without blocking here - QTimer::singleShot( 50, this, SLOT(acceptSubDialog()) ); - QTest::mouseClick( m_removeFilterConfiguration, Qt::LeftButton ); // Blocks here until the dialogDone() timer signals - UpdateGUI(); -} - -QList vehicleTypesToRows( const QVariantList &vehicleTypes, - QAbstractItemModel *vehicleConstraintListModel ) -{ - QList rows; - // "Translate" checkedVehicles (stored in Qt::UserRole) to checkedVehicleRows - for ( int row = 0; row < vehicleConstraintListModel->rowCount(); ++row ) { - QModelIndex index = vehicleConstraintListModel->index(row, 0); - VehicleType currentVehicleType = static_cast( - vehicleConstraintListModel->data(index, Qt::UserRole).toInt() ); - if ( vehicleTypes.contains(currentVehicleType) ) { - rows << row; - } - } - return rows; -} - -void PublicTransportAppletTest::appletTest() -{ - m_dialog->setCurrentPage( m_pageFilter ); - - // Check if all filter configuration names are listed in the combobox - QCOMPARE( m_filterConfigurationsWidget->count(), m_filterConfigurations.count() ); - for ( int i = 0; i < m_filterConfigurationsWidget->count(); ++i ) { - QCOMPARE( m_filterConfigurationsWidget->itemText(i), m_filterConfigurations[i].name ); - QCOMPARE( (*m_stopsWidget->filterConfigurations())[i].name, m_filterConfigurations[i].name ); - } - - // Compare values in the filter widgets with the values in the configuration object - FilterSettings currentFilterSettings = m_filterConfigurations[ - m_filterConfigurationsWidget->currentIndex() ]; - QCOMPARE( m_filtersWidget->filters().count(), - qMax(m_filtersWidget->minimumWidgetCount(), currentFilterSettings.filters.count()) ); - QCOMPARE( m_filtersWidget->filters(), currentFilterSettings.filters ); - QCOMPARE( m_affectedStops->checkedRows().toSet(), currentFilterSettings.affectedStops ); - QCOMPARE( static_cast(m_filterAction->currentIndex()), - currentFilterSettings.filterAction ); - - // Add a filter configuration by clicking the add button - simulateAddFilterConfiguration(); - - // Check if all filter configuration names are still listed in the combobox - QCOMPARE( m_filterConfigurationsWidget->count(), m_filterConfigurations.count() + 1 ); // There should be one more filter configuration now - QCOMPARE( m_filtersWidget->filterWidgets().count(), 1 ); - QCOMPARE( m_filtersWidget->filterWidgets().first()->constraintWidgets().count(), 1 ); // New filter configuration should have one default constraint - for ( int i = 0; i < m_filterConfigurations.count(); ++i ) { - // The first values shouldn't be changed when adding another filter configuration - QCOMPARE( m_filterConfigurationsWidget->itemText(i), m_filterConfigurations[i].name ); - QCOMPARE( (*m_stopsWidget->filterConfigurations())[i].name, m_filterConfigurations[i].name ); - } - - // Change filter of the newly added filter configuration - ConstraintListWidget *vehicleConstraint = qobject_cast( - m_filtersWidget->filterWidgets().first()->constraintWidgets().first() ); - CheckCombobox *vehicleConstraintList = vehicleConstraint->list(); - QAbstractItemModel *vehicleConstraintListModel = vehicleConstraintList->model(); - QVariantList checkedVehicles = QVariantList() << static_cast(Tram) << static_cast(Bus); - QList checkedVehicleRows = vehicleTypesToRows( checkedVehicles, vehicleConstraintListModel ); - vehicleConstraintList->setCheckedRows( checkedVehicleRows ); - QCOMPARE( vehicleConstraint->value().toList(), checkedVehicles ); - UpdateGUI(); - - // Store current filter settings from the widgets (currently at the new filter configuration) - Filter newFilter; - newFilter << Constraint( FilterByVehicleType, FilterIsOneOf, checkedVehicles ); - FilterSettings newFilterSettings( m_filterConfigurationsWidget->currentText() ); - newFilterSettings.affectedStops = m_affectedStops->checkedRows().toSet(); - newFilterSettings.filterAction = static_cast(m_filterAction->currentIndex()); - newFilterSettings.filters << newFilter; - QCOMPARE( m_filtersWidget->filters(), newFilterSettings.filters ); - - // Select all except the newly added filter configuration and test values of widgets - for ( int i = 0; i < m_filterConfigurations.count(); ++i ) { - m_filterConfigurationsWidget->setCurrentIndex( i ); - QCoreApplication::processEvents(); // Wait for filter widgets to get updated - FilterSettings currentFilterSettings = m_filterConfigurations[ i ]; - - QCOMPARE( m_filterConfigurationsWidget->currentText(), currentFilterSettings.name ); - QCOMPARE( m_filtersWidget->filters().count(), - qMax(m_filtersWidget->minimumWidgetCount(), currentFilterSettings.filters.count()) ); - QCOMPARE( m_filtersWidget->filters(), currentFilterSettings.filters ); - QCOMPARE( m_affectedStops->checkedRows().toSet(), currentFilterSettings.affectedStops ); - QCOMPARE( static_cast(m_filterAction->currentIndex()), - currentFilterSettings.filterAction ); - UpdateGUI(); - } - - // Select newly added (and changed) filter configuration - m_filterConfigurationsWidget->setCurrentIndex( 2 ); - QCoreApplication::processEvents(); // Wait for filter widgets to get updated - UpdateGUI(); - - // Check filter widget values - QCOMPARE( m_filterConfigurationsWidget->currentText(), newFilterSettings.name ); - QCOMPARE( m_filtersWidget->filters().count(), - qMax(m_filtersWidget->minimumWidgetCount(), newFilterSettings.filters.count()) ); - QCOMPARE( m_filtersWidget->filters(), newFilterSettings.filters ); - QCOMPARE( m_affectedStops->checkedRows().toSet(), newFilterSettings.affectedStops ); - QCOMPARE( static_cast(m_filterAction->currentIndex()), - newFilterSettings.filterAction ); - vehicleConstraint = qobject_cast( - m_filtersWidget->filterWidgets().first()->constraintWidgets().first() ); - vehicleConstraintList = vehicleConstraint->list(); - QCOMPARE( vehicleConstraint->value().toList(), checkedVehicles ); - - // Check values in a StopSettingsDialog, which gets opened from StopListWidget - m_dialog->setCurrentPage( m_pageGeneral ); - - QTimer::singleShot( 50, this, SLOT(checkAndCloseStopSettingsDialog()) ); - m_stopsWidget->stopWidget(0)->editSettings(); - -// TODO add more stop settings in init() -// QTimer::singleShot( 50, this, SLOT(checkAndCloseStopSettingsDialog()) ); -// m_stopsWidget->stopWidget(1)->editSettings(); -} - -void PublicTransportAppletTest::checkAndCloseStopSettingsDialog() -{ - QVERIFY2( m_dialog, "No config dialog created?" ); - - QPointer stopSettingsDialog = NULL; - stopSettingsDialog = m_dialog->findChild(); - QVERIFY2( stopSettingsDialog, "No stop settings dialog found" ); - - CheckCombobox *filterConfigurationsOfStopWidget = qobject_cast( - stopSettingsDialog->settingWidget(FilterConfigurationSetting) ); - QVERIFY2( filterConfigurationsOfStopWidget, "No filter configuration setting found in the StopSettingsDialog" ); - - QSet filterConfigurationsOfStop = filterConfigurationsOfStopWidget->checkedRows().toSet(); - int stopIndex = stopSettingsDialog->stopIndex(); - for ( int index = 0; index < m_filterConfigurations.count(); ++index ) { - const FilterSettings &filterConfiguration = m_filterConfigurations[index]; - if ( filterConfiguration.affectedStops.contains(stopIndex) ) { - QVERIFY2( filterConfigurationsOfStop.contains(index), "If a filter configuration is " - "checked for the stop in the StopSettingsDialog, the filter configuration " - "should have that stop in it's list of affected stops" ); - } - } - - kDebug() << "Close opened sub dialog" << stopSettingsDialog->windowTitle() << stopSettingsDialog; - QTest::mouseClick( stopSettingsDialog->button(KDialog::Cancel), Qt::LeftButton ); -} - -void PublicTransportAppletTest::acceptSubDialog() -{ - QVERIFY2( m_dialog, "No config dialog created?" ); - - QPointer subDialog = NULL; - subDialog = m_dialog->findChild(); // KInputDialogHelper - QVERIFY2( subDialog, "No sub dialog found" ); - - kDebug() << "Close opened sub dialog" << subDialog->windowTitle() << subDialog; - QTest::mouseClick( subDialog->button(KDialog::Ok), Qt::LeftButton ); -} - -QTEST_MAIN(PublicTransportAppletTest) -#include "PublicTransportAppletTest.moc" diff --git a/applet/tests/PublicTransportAppletTest.h b/applet/tests/PublicTransportAppletTest.h deleted file mode 100644 index 218ac5d..0000000 --- a/applet/tests/PublicTransportAppletTest.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef PublicTransportAppletTest_H -#define PublicTransportAppletTest_H - -#define QT_GUI_LIB - -#include -#include -#include - -class CheckCombobox; -class QToolButton; -class KComboBox; -class KPageWidgetItem; -class KPageWidgetModel; -class KPageWidget; -class KConfigDialog; -namespace Plasma { - class Applet; - class Containment; - class Corona; -} -namespace PublicTransport { - class StopListWidget; - class FilterListWidget; -} -using namespace PublicTransport; - -class PublicTransportAppletTest : public QObject -{ - Q_OBJECT - -public slots: - void acceptSubDialog(); - void checkAndCloseStopSettingsDialog(); - void simulateAddFilterConfiguration(); - void simulateRemoveFilterConfiguration(); - -private slots: - // Initialize containment and applet, set settings of the applet - void initTestCase(); - - // Create the applet's configuration dialog and find widgets (store in member variables) - // The dialog gets created for each test (because init is called before each test) - void init(); - - // Close and delete the configuration dialog - void cleanup(); - - void cleanupTestCase(); - - void appletTest(); - -private: - StopSettings m_stopSettings; - FilterSettingsList m_filterConfigurations; - - Plasma::Applet *m_applet; - Plasma::Containment *m_containment; - Plasma::Corona *m_corona; - - KConfigDialog *m_dialog; - KPageWidget *m_pageWidget; - KPageWidgetModel *m_pageModel; - - KPageWidgetItem *m_pageGeneral; - KPageWidgetItem *m_pageFilter; - KPageWidgetItem *m_pageAlarms; - - QWidget *m_pageGeneralWidget; - QWidget *m_pageFilterWidget; - QWidget *m_pageAlarmsWidget; - - StopListWidget *m_stopsWidget; - - KComboBox *m_filterConfigurationsWidget; - FilterListWidget *m_filtersWidget; - CheckCombobox *m_affectedStops; - KComboBox *m_filterAction; - QToolButton *m_addFilterConfiguration; - QToolButton *m_removeFilterConfiguration; -}; - -Q_DECLARE_METATYPE( QList ) - -#endif // PublicTransportAppletTest_H diff --git a/applet/timetablewidget.cpp b/applet/timetablewidget.cpp deleted file mode 100644 index 8e315b4..0000000 --- a/applet/timetablewidget.cpp +++ /dev/null @@ -1,2337 +0,0 @@ -/* -* Copyright 2013 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -// Own includes -#include "timetablewidget.h" -#include "routegraphicsitem.h" -#include "departuremodel.h" - -// Plasma includes -#include -#include -#include -#include -#include -#include -#include - -// KDE includes -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -PublicTransportGraphicsItem::PublicTransportGraphicsItem( - PublicTransportWidget *publicTransportWidget, QGraphicsItem *parent, - StopAction *copyStopToClipboardAction, StopAction *showInMapAction/*, QAction *toggleAlarmAction*/ ) - : QGraphicsWidget(parent), m_item(0), m_parent(publicTransportWidget), - m_resizeAnimation(0), m_pixmap(0), m_copyStopToClipboardAction(copyStopToClipboardAction), - m_showInMapAction(showInMapAction), m_ensureVisibleTimer(0)/*, - m_toggleAlarmAction(toggleAlarmAction)*/ -{ - setFlag( ItemClipsToShape ); - setFlag( ItemClipsChildrenToShape ); - m_expanded = false; - m_expandStep = 0.0; - m_fadeOut = 1.0; -} - -PublicTransportGraphicsItem::~PublicTransportGraphicsItem() -{ - delete m_pixmap; -} - -QList< QAction* > JourneyTimetableWidget::contextMenuActions() -{ - QList< QAction* > actions; - if ( m_earlierAction ) { - actions << m_earlierAction; - } - if ( m_laterAction ) { - actions << m_laterAction; - } - return actions; -} - -void DepartureGraphicsItem::updateSettings() -{ - if ( m_routeItem ) { - m_routeItem->setZoomFactor( m_parent->zoomFactor() ); - updateGeometry(); - update(); - } -} - -void JourneyGraphicsItem::updateSettings() -{ - if ( m_routeItem ) { - m_routeItem->setZoomFactor( m_parent->zoomFactor() ); - updateGeometry(); - update(); - } -} - -void PublicTransportGraphicsItem::setExpandedNotAffectingOtherItems( bool expand ) -{ - if ( m_expanded == expand ) { - return; // Unchanged - } - - m_expanded = expand; - if ( expand ) { - QGraphicsWidget *route = routeItem(); - if ( route ) { - route->setVisible( true ); - } - } - - if ( m_resizeAnimation ) { - m_resizeAnimation->stop(); - } else { - m_resizeAnimation = new QPropertyAnimation( this, "expandStep" ); - connect( m_resizeAnimation, SIGNAL(finished()), this, SLOT(resizeAnimationFinished()) ); - - // The easing curve should be chosen so that another running resize animation - // to the other direction will (almost) not move the top edge of following items - m_resizeAnimation->setEasingCurve( QEasingCurve(QEasingCurve::InOutBack) ); - - // Make the duration longer, because many visible items may get moved when resizing an item - m_resizeAnimation->setDuration( 550 ); - - if ( expand ) { - // Call ensureVisible() multiple times while expanding - if ( !m_ensureVisibleTimer ) { - m_ensureVisibleTimer = new QTimer( this ); - connect( m_ensureVisibleTimer, SIGNAL(timeout()), this, SLOT(ensureVisibleSnapped()) ); - } - m_ensureVisibleTimer->start( 120 ); - } - } - - m_resizeAnimation->setStartValue( m_expandStep ); - m_resizeAnimation->setEndValue( expand ? 1.0 : 0.0 ); - m_resizeAnimation->start( QAbstractAnimation::KeepWhenStopped ); - updateGeometry(); - - emit expandedStateChanged( this, expand ); -} - -void PublicTransportGraphicsItem::setExpanded( bool expand ) -{ - publicTransportWidget()->setItemExpanded( this, expand ); -} - -void PublicTransportGraphicsItem::ensureVisibleSnapped() -{ - // Ensure that the item is visible, - // if it does not fit ensure that the top area of the item is visible - const qreal height = m_parent->size().height(); - QRectF rect = geometry(); - if ( rect.height() > height ) { - rect.setHeight( height ); - } - - // Align the rect vertically to scroll snap points, - // otherwise the item might get moved back afterwards by the snap scroll feature - const qreal heightUnit = m_parent->snapSize().height(); - rect.moveTop( qCeil(rect.top() / heightUnit) * heightUnit - 0.5 ); - m_parent->ensureRectVisible( rect ); -} - -void PublicTransportGraphicsItem::resizeAnimationFinished() -{ - QGraphicsWidget *route = routeItem(); - if ( route ) { - route->setVisible( m_expanded ); - } - - delete m_ensureVisibleTimer; - m_ensureVisibleTimer = 0; - - delete m_resizeAnimation; - m_resizeAnimation = 0; -} - -void PublicTransportGraphicsItem::updateGeometry() -{ - QGraphicsWidget::updateGeometry(); -} - -void TimetableListItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, - QWidget *widget ) -{ - Q_UNUSED( widget ); - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - - // Paint background on whole item - const QFontMetrics fontMetrics( font() ); - const QString text = KGlobal::locale()->removeAcceleratorMarker( m_action->text() ); - const int iconSize = 16; - const int padding = 4; - const int textWidth = fontMetrics.width( text ); - const int usedWidth = iconSize + padding + textWidth; - const QRect targetRect( (option->rect.width() - usedWidth) / 2, - (option->rect.height() - iconSize) / 2, - usedWidth, iconSize ); - const QRect iconRect( 0, 0, iconSize, iconSize ); - const QRect textRect( iconRect.right() + padding, (iconSize - fontMetrics.height()) / 2, - textWidth, fontMetrics.height() ); - QPixmap iconPixmap = m_action->icon().pixmap( iconSize ); - - // Draw halo/shadow - // TODO Requires KDE > 4.6, remove other 4.6 #ifdefs - const QColor textColor = Plasma::Theme::defaultTheme()->color( - option->state.testFlag(QStyle::State_HasFocus) ? Plasma::Theme::ViewFocusColor : - (option->state.testFlag(QStyle::State_MouseOver) - ? Plasma::Theme::ViewHoverColor : Plasma::Theme::ViewTextColor) ); - const bool drawShadowsOrHalos = - m_parent->isOptionEnabled( PublicTransportWidget::DrawShadowsOrHalos ); - const bool drawHalos = drawShadowsOrHalos && qGray(textColor.rgb()) < 192; - - // Draw everything to a pixmap - QPixmap pixmap( usedWidth, iconSize ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - p.setPen( textColor ); - p.drawPixmap( iconRect, iconPixmap ); - p.drawText( textRect, text ); - p.end(); - - // Draw halos/shadows under the pixmap - if ( drawHalos ) { - Plasma::PaintUtils::drawHalo( painter, - textRect.adjusted(targetRect.left(), targetRect.top(), - targetRect.left(), targetRect.top()) ); - } else if ( drawShadowsOrHalos ) { - QImage shadow = pixmap.toImage(); - Plasma::PaintUtils::shadowBlur( shadow, 3, Qt::black ); - painter->drawImage( targetRect.topLeft() + QPoint(1, 2), shadow ); - } - - // Draw the pixmap above halos/shadows - painter->drawPixmap( targetRect.topLeft(), pixmap ); -} - -void PublicTransportGraphicsItem::paint( QPainter* painter, - const QStyleOptionGraphicsItem* option, QWidget* widget ) -{ - Q_UNUSED( widget ); - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - if ( !m_item || !isValid() ) { - if ( m_pixmap ) { - // Draw captured pixmap, the item in the model is already deleted - // but still needs to be drawn here while animating - QRectF sourceRect = boundingRect(); - sourceRect.moveTopLeft( QPointF(0, 0) ); - painter->drawPixmap( boundingRect(), *m_pixmap, sourceRect ); - } - - return; - } - - // Paint background on whole item (including expand area) - const QRectF _rect = rect(); - paintBackground( painter, option, _rect ); - - // Paint item (excluding expand area) - paintItem( painter, option, _rect ); - - // Draw expand area if this item isn't currently completely unexpanded - if ( m_expanded || !qFuzzyIsNull(m_expandStep) ) { - QRectF rectItem = _rect; - rectItem.setHeight( unexpandedHeight() ); - qreal pad = padding(); - qreal indentation = expandAreaIndentation(); - QRectF rectExpanded( rectItem.left() + indentation, rectItem.bottom() + 2 * pad, - rectItem.width() - indentation - pad, expandAreaHeight() - 2 * pad ); - paintExpanded( painter, option, rectExpanded ); - } -} - -void PublicTransportGraphicsItem::capturePixmap() -{ - // Delete previous pixmap if any - delete m_pixmap; - m_pixmap = 0; - - // Create new pixmap - m_pixmap = new QPixmap( size().toSize() ); - m_pixmap->fill( Qt::transparent ); - - // Draw this item into the new pixmap - QPainter p( m_pixmap ); - QStyleOptionGraphicsItem option; - option.rect = rect().toRect(); - paint( &p, &option ); -} - -qreal PublicTransportGraphicsItem::padding() const -{ - return padding( m_parent->zoomFactor() ); -} - -qreal PublicTransportGraphicsItem::padding( qreal zoomFactor ) -{ - return 4.0 * zoomFactor; -} - -qreal PublicTransportGraphicsItem::unexpandedHeight() const -{ - return unexpandedHeight( m_parent->iconSize(), padding(), - QFontMetrics(font()).lineSpacing(), m_parent->maxLineCount() ); -} - -qreal PublicTransportGraphicsItem::unexpandedHeight( qreal iconSize, qreal padding, - int lineSpacing, int maxLineCount ) -{ - return qMax( iconSize * 1.1, lineSpacing * maxLineCount + padding ); -} - -qreal PublicTransportGraphicsItem::routeItemHeight() const -{ - return 3 * unexpandedHeight(); -} - -bool PublicTransportGraphicsItem::hasExtraIcon( Columns column ) const -{ - if ( !m_item ) { - // Item was already deleted - return false; - } - - QModelIndex modelIndex = index().model()->index( index().row(), column ); - return modelIndex.data( Qt::DecorationRole ).isValid() - && !modelIndex.data( Qt::DecorationRole ).value().isNull(); -} - -int PublicTransportGraphicsItem::extraIconSize() const -{ - return m_parent->iconSize() / 2; -} - -QTextDocument* TextDocumentHelper::createTextDocument( const QString& html, const QSizeF& size, - const QTextOption &textOption, const QFont &font ) -{ - QTextDocument *textDocument = new QTextDocument; - textDocument->setDefaultFont( font ); - textDocument->setDocumentMargin( 0 ); - textDocument->setDefaultTextOption( textOption ); - textDocument->setPageSize( size ); - textDocument->setHtml( html ); - textDocument->documentLayout(); - return textDocument; -} - -void TextDocumentHelper::drawTextDocument( QPainter *painter, - const QStyleOptionGraphicsItem* option, QTextDocument *document, - const QRectF &textRect, Option options ) -{ - if ( textRect.size().toSize().isEmpty() ) { - kDebug() << "No text visible, do not draw anything!" << textRect; - return; - } - - QList< QRectF > haloRects, fadeRects; - const qreal fadeWidth = 30; - - QPixmap pixmap( textRect.size().toSize() ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - p.setPen( painter->pen() ); - p.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - - const QPointF position( 0, (textRect.height() - document->size().height()) / 2.0f ); - for ( int b = 0; b < document->blockCount(); ++b ) { - QTextLayout *textLayout = document->findBlockByNumber( b ).layout(); - const int lines = textLayout->lineCount(); - for ( int l = 0; l < lines; ++l ) { - // Draw a text line - QTextLine textLine = textLayout->lineAt( l ); - textLine.draw( &p, position ); - - if ( options == DrawHalos ) { - // Calculate halo rect - QSizeF textSize = textLine.naturalTextRect().size(); - if ( textSize.width() > textRect.width() ) { - textSize.setWidth( textRect.width() ); - } - QRectF haloRect = textLine.naturalTextRect(); - haloRect.moveTopLeft( haloRect.topLeft() + position + textRect.topLeft() ); - if ( haloRect.top() <= textRect.bottom() ) { - if ( haloRect.width() > textRect.width() ) { - haloRect.setWidth( textRect.width() ); - } - // Add a halo rect for each drawn text line - haloRects << haloRect; - } - } - - // Add a fade out rect to the list if the line is too long - if ( textLine.naturalTextWidth() > textRect.width() - textLine.x() ) { - const qreal x = qMin(textLine.naturalTextWidth(), textRect.width() - textLine.x()) - - fadeWidth + textLine.x() + position.x(); - const qreal y = textLine.position().y() + position.y(); - fadeRects << QRectF( x, y, fadeWidth, textLine.height() ); - } - } - } - - // Reduce the alpha in each fade out rect using the alpha gradient - if ( !fadeRects.isEmpty() ) { - // (From the tasks plasmoid) Create the alpha gradient for the fade out effect - QLinearGradient alphaGradient( 0, 0, 1, 0 ); - alphaGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); - if ( option->direction == Qt::LeftToRight ) { - alphaGradient.setColorAt( 0, Qt::black ); - alphaGradient.setColorAt( 1, Qt::transparent ); - } else { - alphaGradient.setColorAt( 0, Qt::transparent ); - alphaGradient.setColorAt( 1, Qt::black ); - } - - p.setCompositionMode( QPainter::CompositionMode_DestinationIn ); - foreach( const QRectF &rect, fadeRects ) { - p.fillRect( rect, alphaGradient ); - } - } - p.end(); - - // Draw halo/shadow - if ( options == DrawHalos ) { - foreach( const QRectF &haloRect, haloRects ) { - Plasma::PaintUtils::drawHalo( painter, haloRect ); - } - } else if ( options == DrawShadows ) { - QImage shadow = pixmap.toImage(); - Plasma::PaintUtils::shadowBlur( shadow, 3, Qt::black ); - painter->drawImage( textRect.topLeft() + QPointF(1, 2), shadow ); - } - - painter->drawPixmap( textRect.topLeft(), pixmap ); -} - -qreal TextDocumentHelper::textDocumentWidth( QTextDocument* document ) -{ - if ( !document ) { - return 0.0; - } - - qreal maxWidth = 0.0; - int blockCount = document->blockCount(); - for ( int b = 0; b < blockCount; ++b ) { - QTextLayout *textLayout = document->findBlockByNumber( b ).layout(); - int lines = textLayout->lineCount(); - for ( int l = 0; l < lines; ++l ) { - QTextLine textLine = textLayout->lineAt( l ); - if ( textLine.naturalTextWidth() > maxWidth ) { - maxWidth = textLine.naturalTextWidth(); - } - } - } - return maxWidth; -} - -void PublicTransportGraphicsItem::resizeEvent( QGraphicsSceneResizeEvent* event ) -{ - QGraphicsWidget::resizeEvent( event ); - updateTextLayouts(); -} - -void DepartureGraphicsItem::resizeEvent( QGraphicsSceneResizeEvent* event ) -{ - PublicTransportGraphicsItem::resizeEvent(event); - - if ( m_routeItem || m_routeInfoWidget ) { - const qreal indentation = expandAreaIndentation(); - const QRectF _rect = rect(); - const QRectF routeRect( _rect.left() + indentation, - _rect.top() + unexpandedHeight() + padding(), - _rect.width() - padding() - indentation, - routeItemHeight() ); - if ( m_routeItem ) { - m_routeItem->setGeometry( routeRect ); - } else if ( m_routeInfoWidget ) { - m_routeInfoWidget->setGeometry( routeRect ); - } - } -} - -void JourneyGraphicsItem::resizeEvent( QGraphicsSceneResizeEvent* event ) -{ - PublicTransportGraphicsItem::resizeEvent(event); - - if ( m_routeItem ) { - QRectF _infoRect = infoRect( rect() ); - m_routeItem->setGeometry( _infoRect.left(), rect().top() + unexpandedHeight() + padding(), - rect().width() - padding() - _infoRect.left(), - m_routeItem->size().height() ); - } -} - -void JourneyGraphicsItem::contextMenuEvent( QGraphicsSceneContextMenuEvent* event ) -{ - JourneyItem *item = qobject_cast( m_item ); - KMenu contextMenu; - - QList< QAction* > actionList; - QAction *infoAction = 0; - QAction *addAlarmAction = 0; - QAction *removeAlarmAction = 0; - if ( item->hasAlarm() ) { - if ( item->alarmStates().testFlag(AlarmIsAutoGenerated) ) { - removeAlarmAction = new QAction( KIcon("task-reminder"), - i18nc("@action:inmenu", "Remove &Alarm For This Journey"), &contextMenu ); - actionList << removeAlarmAction; - } else if ( item->alarmStates().testFlag(AlarmIsRecurring) ) { - // The 'Remove this Alarm' menu entry can only be - // used with autogenerated alarms - if ( item->journeyInfo()->matchedAlarms().count() == 1 ) { - infoAction = new QAction( KIcon("task-recurring"), - i18nc("@info/plain", "(has a recurring alarm)"), this ); - } else { - infoAction = new QAction( - i18nc("@action:inmenu", "(has recurring/multiple alarms)"), this ); - } - } else { - // The 'Remove this Alarm' menu entry can only be - // used with autogenerated alarms - if ( item->journeyInfo()->matchedAlarms().count() == 1 ) { - infoAction = new QAction( KIcon("task-recurring"), - i18nc("@info/plain", "(has a custom alarm)"), this ); - } else { - infoAction = new QAction( - i18nc("@action:inmenu", "(has custom/multiple alarms)"), this ); - } - } - if ( infoAction ) { - infoAction->setDisabled( true ); - actionList << infoAction; - } - } else { - addAlarmAction = new QAction( KIcon("task-reminder"), - i18nc("@action:inmenu", "Add &Alarm For This Journey"), &contextMenu ); - actionList << addAlarmAction; - } - - // Add context menu actions of the parent PublicTransportWidget - QList< QAction* > parentActions = m_parent->contextMenuActions(); - if ( !parentActions.isEmpty() ) { - QAction *separator = new QAction( &contextMenu ); - separator->setSeparator( true ); - actionList << separator; - actionList << parentActions; - } - - contextMenu.addActions( actionList ); - - QAction *executedAction = contextMenu.exec( event->screenPos() ); - - if ( executedAction ) { - const JourneyInfo *info = journeyItem()->journeyInfo(); - QString lineString = info->routeTransportLines().isEmpty() - ? QString() : info->routeTransportLines().first(); - VehicleType vehicleType = info->routeVehicleTypes().isEmpty() - ? UnknownVehicleType : info->routeVehicleTypes().first(); - if ( executedAction == addAlarmAction ) { - emit requestAlarmCreation( info->departure(), lineString, vehicleType, QString(), this ); - } else if ( executedAction == removeAlarmAction ) { - emit requestAlarmDeletion( info->departure(), lineString, vehicleType, QString(), this ); - } - } -} - -void PublicTransportGraphicsItem::mousePressEvent( QGraphicsSceneMouseEvent* event ) -{ - QGraphicsItem::mousePressEvent( event ); - if ( event->button() == Qt::LeftButton ) { - event->accept(); - } -} - -void PublicTransportGraphicsItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) -{ - if ( event->button() == Qt::LeftButton && - (event->lastPos() - event->pos()).manhattanLength() < 5 ) - { - toggleExpanded(); - event->accept(); - } else { - QGraphicsItem::mouseReleaseEvent( event ); - } -} - -QPointF DepartureGraphicsItem::othersTextPos() const -{ - const qreal indentation = expandAreaIndentation(); - qreal y = unexpandedHeight() + 2 * padding(); - qreal height = expandAreaHeight() - 2 * padding(); - if ( m_routeItem ) { - y += m_routeItem->size().height() + padding(); - height -= m_routeItem->size().height() + padding(); - } else if ( m_routeInfoWidget ) { - y += m_routeInfoWidget->size().height() + padding(); - height -= m_routeInfoWidget->size().height() + padding(); - } - if ( m_othersTextDocument && m_othersTextDocument->blockCount() > 0 ) { - // Shift y because of vertical alignment - y += (height - m_othersTextDocument->size().height()) / 2.0f; - } - return QPointF( indentation, y ); -} - -void DepartureGraphicsItem::hoverMoveEvent( QGraphicsSceneHoverEvent *event ) -{ - QGraphicsWidget::hoverMoveEvent(event); - if ( !m_othersTextDocument ) { - return; - } - const QString anchor = m_othersTextDocument->documentLayout()->anchorAt( - event->pos() - othersTextPos() ); - if ( !anchor.isEmpty() ) { - setCursor( QCursor(Qt::PointingHandCursor) ); - event->accept(); - } else { - unsetCursor(); - } -} - -void DepartureGraphicsItem::mouseReleaseEvent( QGraphicsSceneMouseEvent *event ) -{ - if ( !m_othersTextDocument || event->button() != Qt::LeftButton ) { - PublicTransportGraphicsItem::mouseReleaseEvent(event); - return; - } - const QString anchor = m_othersTextDocument->documentLayout()->anchorAt( - event->pos() - othersTextPos() ); - if ( !anchor.isEmpty() ) { - KToolInvocation::self()->invokeBrowser( anchor ); - event->accept(); - } else { - PublicTransportGraphicsItem::mouseReleaseEvent(event); - } -} - -DepartureGraphicsItem::DepartureGraphicsItem( PublicTransportWidget* publicTransportWidget, - QGraphicsItem* parent, - StopAction *copyStopToClipboardAction, StopAction *showInMapAction, - StopAction *showDeparturesAction, StopAction *highlightStopAction, - StopAction *newFilterViaStopAction, KPixmapCache *pixmapCache ) - : PublicTransportGraphicsItem( publicTransportWidget, parent, copyStopToClipboardAction, - showInMapAction ), - m_infoTextDocument(0), m_timeTextDocument(0), m_othersTextDocument(0), m_routeItem(0), - m_routeInfoWidget(0), m_highlighted(false), m_leavingAnimation(0), - m_showDeparturesAction(showDeparturesAction), m_highlightStopAction(highlightStopAction), - m_newFilterViaStopAction(newFilterViaStopAction), m_updateAdditionalDataAction(0), - m_pixmapCache(pixmapCache) -{ - m_leavingStep = 0.0; - m_updateAdditionalDataAction = new KAction( KIcon("view-refresh"), - i18nc("@info", "Refresh"), this ); - connect( m_updateAdditionalDataAction, SIGNAL(triggered(bool)), - this, SIGNAL(updateAdditionalDataRequest()) ); - setAcceptHoverEvents( true ); // Hover anchors in the document -} - -DepartureGraphicsItem::~DepartureGraphicsItem() -{ - if ( m_leavingAnimation ) { - m_leavingAnimation->stop(); - } - - delete m_infoTextDocument; - delete m_timeTextDocument; - delete m_othersTextDocument; -} - -JourneyGraphicsItem::JourneyGraphicsItem( PublicTransportWidget* publicTransportWidget, - QGraphicsItem* parent, - StopAction *copyStopToClipboardAction, StopAction *showInMapAction, - StopAction *requestJourneyToStopAction, StopAction *requestJourneyFromStopAction ) - : PublicTransportGraphicsItem( publicTransportWidget, parent, copyStopToClipboardAction, - showInMapAction - /*, toggleAlarmAction*/ ), - m_infoTextDocument(0), m_routeItem(0), - m_requestJourneyToStopAction(requestJourneyToStopAction), - m_requestJourneyFromStopAction(requestJourneyFromStopAction) -{ -} - -JourneyGraphicsItem::~JourneyGraphicsItem() -{ - delete m_infoTextDocument; -} - -void DepartureGraphicsItem::setLeavingStep( qreal leavingStep ) -{ - m_leavingStep = leavingStep; - setOpacity( 1.0 - leavingStep ); - update(); -} - -void DepartureGraphicsItem::updateTextLayouts() -{ - if ( !m_item ) { - // Item already destroyed - return; - } - - QRectF rect = contentsRect(); - const QRectF _timeRect = timeRect( rect ); - QTextOption textOption( Qt::AlignVCenter | Qt::AlignLeft ); - textOption.setWrapMode( m_parent->maxLineCount() == 1 - ? QTextOption::NoWrap : QTextOption::WordWrap ); - - // Update text layouts - if ( !m_timeTextDocument || (m_timeTextDocument->pageSize() != _timeRect.size()) ) { - delete m_timeTextDocument; - textOption.setAlignment( Qt::AlignVCenter | Qt::AlignRight ); - m_timeTextDocument = TextDocumentHelper::createTextDocument( - index().model()->index(index().row(), 2).data(FormattedTextRole).toString(), - _timeRect.size(), textOption, font() ); - } - - const qreal timeWidth = timeColumnWidth(); - const QRectF _infoRect = infoRect( rect, timeWidth ); - - // Create layout for the main column showing information about the departure - if ( !m_infoTextDocument || (m_infoTextDocument->pageSize() != _infoRect.size()) ) { - delete m_infoTextDocument; - textOption.setAlignment( Qt::AlignVCenter | Qt::AlignLeft ); - QString html; - const DepartureInfo *info = departureItem()->departureInfo(); - TimetableWidget *timetableWidget = qobject_cast( m_parent ); - if ( timetableWidget->isTargetHidden() ) { - html = i18nc("@info", "%1", info->lineString()); - } else if ( departureItem()->model()->info().departureArrivalListType == ArrivalList ) { - html = i18nc("@info", "%1 from %2", - info->lineString(), info->targetShortened()); - } else { // if ( departureItem()->model()->info().departureArrivalListType == DepartureList ) { - html = i18nc("@info", "%1 to %2", - info->lineString(), info->targetShortened()); - } - m_infoTextDocument = TextDocumentHelper::createTextDocument( html, _infoRect.size(), - textOption, font() ); - } - - QSizeF othersSize( rect.width() - expandAreaIndentation() - padding(), 999 ); - const QString html = othersText(); - if ( !m_othersTextDocument || m_othersTextDocument->textWidth() != othersSize.width() || - html != m_othersTextDocument->toHtml() ) - { - delete m_othersTextDocument; - textOption.setAlignment( Qt::AlignBottom | Qt::AlignLeft ); - - m_othersTextDocument = TextDocumentHelper::createTextDocument( html, othersSize, - textOption, font() ); - } -} - -QString DepartureGraphicsItem::othersText() const -{ - DepartureItem *item = qobject_cast< DepartureItem* >( m_item.data() ); - ChildItem *delayItem = m_item->childByType( DelayItem ); - ChildItem *platformItem = m_item->childByType( PlatformItem ); - ChildItem *operatorItem = m_item->childByType( OperatorItem ); - ChildItem *journeyNewsItem = m_item->childByType( JourneyNewsItem ); - - QString html; - if ( delayItem ) { - html.append( delayItem->formattedText() ); - } - if ( platformItem ) { - if ( !html.isEmpty() ) { - html.append("
"); - } - html.append( platformItem->formattedText() ); - } - if ( operatorItem ) { - if ( !html.isEmpty() ) { - html.append("
"); - } - html.append( operatorItem->formattedText() ); - } - if ( journeyNewsItem ) { - if ( !html.isEmpty() ) { - html.append("
"); - } - if ( journeyNewsItem ) { - html.append( journeyNewsItem->formattedText() ); - } - } - return html; -} - -void JourneyGraphicsItem::updateTextLayouts() -{ - if ( !m_item ) { - return; - } - - QRectF rect = contentsRect(); - QTextOption textOption( Qt::AlignVCenter | Qt::AlignLeft ); - textOption.setWrapMode( m_parent->maxLineCount() == 1 - ? QTextOption::NoWrap : QTextOption::ManualWrap ); - - // Create layout for the main column showing information about the departure - const QRectF _infoRect = infoRect( rect/*, departureTimeWidth, arrivalTimeWidth*/ ); - if ( !m_infoTextDocument || (m_infoTextDocument->pageSize() != _infoRect.size()) ) { - delete m_infoTextDocument; - textOption.setAlignment( Qt::AlignVCenter | Qt::AlignLeft ); - QString html; - const JourneyInfo *info = journeyItem()->journeyInfo(); - if ( m_parent->maxLineCount() == 1 ) { - // Single line string - html = i18nc("@info", "Duration: %1, " - "Changes: %2", - KGlobal::locale()->formatDuration(info->duration()*60*1000), - ((info->changes() == 0 - ? i18nc("@info No vehicle changes in a journey", "none") - : QString::number(info->changes())))); - } else { - // Two (or more) line string - html = i18nc("@info", "Duration: %1, " - "Changes: %2" - "Departing: %3, " - "Arriving: %4", - KGlobal::locale()->formatDuration(info->duration()*60*1000), - (info->changes() == 0 - ? i18nc("@info No vehicle changes in a journey", "none") - : QString::number(info->changes())), - KGlobal::locale()->formatDateTime(info->departure(), KLocale::FancyShortDate), - KGlobal::locale()->formatDateTime(info->arrival(), KLocale::FancyShortDate)); - } - m_infoTextDocument = TextDocumentHelper::createTextDocument( html, _infoRect.size(), - textOption, font() ); - } -} - -void JourneyGraphicsItem::updateData( JourneyItem* item, bool updateLayouts ) -{ - m_item = item; - setAcceptHoverEvents( true ); - updateGeometry(); - - if ( updateLayouts ) { - delete m_infoTextDocument; - m_infoTextDocument = 0; - } - updateTextLayouts(); - - if ( !item->journeyInfo()->routeStopsShortened().isEmpty() ) { - if ( m_routeItem ) { - m_routeItem->updateData( item ); - } else { - m_routeItem = new JourneyRouteGraphicsItem( this, item, m_parent->svg(), - m_copyStopToClipboardAction, m_showInMapAction, m_requestJourneyToStopAction, - m_requestJourneyFromStopAction ); - QRectF _infoRect = infoRect( rect() ); - m_routeItem->setZoomFactor( m_parent->zoomFactor() ); - m_routeItem->setPos( _infoRect.left(), rect().top() + unexpandedHeight() + padding() ); - m_routeItem->resize( rect().width() - padding() - _infoRect.left(), - m_routeItem->size().height() ); - m_routeItem->updateData( item ); - } - } else if ( m_routeItem ) { - delete m_routeItem; - m_routeItem = 0; - } - - update(); -} - -void DepartureGraphicsItem::hideRouteInfoWidget() -{ - if ( m_routeInfoWidget ) { - // Fade out the route busy widget, delete the route busy widget and the animation when done - Plasma::Animation *animation = - Plasma::Animator::create( Plasma::Animator::FadeAnimation, m_routeInfoWidget ); - animation->setTargetWidget( m_routeInfoWidget ); - animation->setProperty( "startOpacity", m_routeInfoWidget->opacity() ); - animation->setProperty( "targetOpacity", 0.0 ); - connect( animation, SIGNAL(finished()), m_routeInfoWidget, SLOT(deleteLater()) ); - animation->start( QAbstractAnimation::DeleteWhenStopped ); - m_routeInfoWidget = 0; - } -} - -void DepartureGraphicsItem::showRouteInfoWidget( QGraphicsWidget *routeInfoWidget ) -{ - // Show the route info widget (busy widget or an error label) over the route item for - // the fade out animation of the route info widget, while the route item gets faded in - routeInfoWidget->setZValue( 100 ); - - // Position the route info widget TODO Do the SAME for the route item - const qreal indentation = expandAreaIndentation(); - const QRectF _rect = rect(); - routeInfoWidget->setPos( _rect.left() + indentation, - _rect.top() + unexpandedHeight() + padding() ); - routeInfoWidget->resize( _rect.width() - padding() - indentation, routeItemHeight() ); - m_routeInfoWidget = routeInfoWidget; -} - -bool DepartureGraphicsItem::isRouteDataAvailable() const -{ - DepartureItem *item = qobject_cast( m_item ); - if ( !item ) { - return false; - } - return !item->departureInfo()->routeStopsShortened().isEmpty(); -} - -bool DepartureGraphicsItem::isRouteDataRequestable() const -{ - DepartureItem *item = qobject_cast( m_item ); - if ( !item ) { - return false; - } - return !item->departureInfo()->includesAdditionalData() && - publicTransportWidget()->model()->info().providerFeatures.contains( - QLatin1String("ProvidesAdditionalData")) && - publicTransportWidget()->model()->info().providerFeatures.contains( - QLatin1String("ProvidesRouteInformation")); -} - -void DepartureGraphicsItem::updateData( DepartureItem* item, bool updateLayouts ) -{ - m_item = item; - updateGeometry(); - - if ( updateLayouts ) { - delete m_infoTextDocument; - delete m_timeTextDocument; - m_infoTextDocument = 0; - m_timeTextDocument = 0; - } - updateTextLayouts(); - - // Test if route data is already available or if it should be available as additional data - if ( isRouteDataAvailable() ) { - hideRouteInfoWidget(); - if ( m_routeItem ) { - m_routeItem->updateData( item ); - } else { - m_routeItem = new RouteGraphicsItem( this, item, m_copyStopToClipboardAction, - m_showInMapAction, m_showDeparturesAction, m_highlightStopAction, - m_newFilterViaStopAction ); - QRectF _infoRect = infoRect( rect(), 0 ); - m_routeItem->setZoomFactor( m_parent->zoomFactor() ); - m_routeItem->setPos( _infoRect.left(), rect().top() + unexpandedHeight() + padding() ); - m_routeItem->resize( rect().width() - padding() - _infoRect.left(), - routeItemHeight() ); - if ( isVisible() ) { - // Fade-in animation for the route item - m_routeItem->setOpacity( 0.0 ); - Plasma::Animation *animation = - Plasma::Animator::create( Plasma::Animator::FadeAnimation, m_routeItem ); - animation->setTargetWidget( m_routeItem ); - animation->setProperty( "startOpacity", 0.0 ); - animation->setProperty( "targetOpacity", 1.0 ); - animation->start( QAbstractAnimation::DeleteWhenStopped ); - } else { - m_routeItem->hide(); - } - } - } else if ( isRouteDataRequestable() ) { - if ( item->departureInfo()->isWaitingForAdditionalData() ) { - if ( !qgraphicsitem_cast(m_routeInfoWidget) ) { - // No busy widget shown - if ( m_routeInfoWidget ) { - // Hide other info widget - hideRouteInfoWidget(); - } - showRouteInfoWidget( new Plasma::BusyWidget(this) ); - } - } else { - Plasma::Label *label = qgraphicsitem_cast( m_routeInfoWidget ); - if ( !label ) { - // No label shown, create a new one - if ( m_routeInfoWidget ) { - // Hide other info widget - hideRouteInfoWidget(); - } - - if ( !item->departureInfo()->additionalDataError().isEmpty() ) { - label = new Plasma::Label( this ); - label->addAction( m_updateAdditionalDataAction ); - label->nativeWidget()->setContextMenuPolicy( Qt::ActionsContextMenu ); - showRouteInfoWidget( label ); - } - } - - // Update error text shown in the label - if ( item->departureInfo()->additionalDataError().isEmpty() ) { - // Hide error text if it changed to an empty string - hideRouteInfoWidget(); - } else { - label->setText( item->departureInfo()->additionalDataError() ); - } - } - } else if ( m_routeItem ) { - delete m_routeItem; - m_routeItem = 0; - } else if ( m_routeInfoWidget ) { - hideRouteInfoWidget(); - } - - if ( item->isLeavingSoon() && !m_leavingAnimation ) { - m_leavingAnimation = new QPropertyAnimation( this, "leavingStep", this ); - m_leavingAnimation->setStartValue( 0.0 ); - m_leavingAnimation->setKeyValueAt( 0.5, 0.5 ); - m_leavingAnimation->setEndValue( 0.0 ); - m_leavingAnimation->setDuration( 1000 ); - m_leavingAnimation->setEasingCurve( QEasingCurve(QEasingCurve::InOutCubic) ); - m_leavingAnimation->setLoopCount( -1 ); - m_leavingAnimation->start( QAbstractAnimation::DeleteWhenStopped ); - } - - update(); -} - -qreal DepartureGraphicsItem::timeColumnWidth() const -{ - qreal width = TextDocumentHelper::textDocumentWidth( m_timeTextDocument ); - - QRectF rect = contentsRect(); - if ( qobject_cast(m_parent)->isTargetHidden() ) { - if ( width > rect.width() * 3 / 4 - padding() ) { - width = rect.width() * 3 / 4 - padding(); - } - } else if ( width > rect.width() / 2 - padding() ) { - width = rect.width() / 2 - padding(); - } - - return width; -} - -void JourneyGraphicsItem::paintBackground( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF& rect ) -{ - Q_UNUSED( option ); - QColor borderColor = textColor(); - borderColor.setAlphaF( 0.5 ); - - QRectF pixmapRect( 0, 0, rect.width(), rect.height() ); - QPixmap pixmap( pixmapRect.size().toSize() ); - QColor backgroundColor = Qt::transparent; - pixmap.fill( backgroundColor ); - - // Use journey rating background: - // green for relatively short duration, less changes; - // red for relatively long duration, more changes (controlled by the model). - const QColor neutralBackground = KColorScheme( QPalette::Active ) - .background( KColorScheme::NeutralBackground ).color(); - QVariant vr = index().data( JourneyRatingRole ); - if ( vr.isValid() ) { - qreal rating = vr.toReal(); - const QColor positiveBackground = KColorScheme( QPalette::Active ) - .background( KColorScheme::PositiveBackground ).color(); - const QColor negativeBackground = KColorScheme( QPalette::Active ) - .background( KColorScheme::NegativeBackground ).color(); - backgroundColor = rating > 0.5 - ? KColorUtils::mix( neutralBackground, negativeBackground, qMin(1.0, (rating - 0.5) * 2.5) ) - : KColorUtils::mix( positiveBackground, neutralBackground, rating * 2.0 ); - } else if ( index().row() % 2 == 1 ) { - // Use alternate background (if journey ratings aren't available) - backgroundColor = KColorScheme( QPalette::Active, KColorScheme::View ) - .background( KColorScheme::AlternateBackground ).color(); - backgroundColor.setAlphaF( 0.3 ); - } else { - backgroundColor = neutralBackground; - } - - const int textGray = qGray( borderColor.rgb() ); - int backgroundGray = qGray( backgroundColor.rgb() ); - const int minDifference = 128; - int difference = textGray - backgroundGray; - if ( textGray >= 128 && difference < minDifference ) { - if ( difference <= 15 ) { - difference = 15; // Prevent division by zero - } - // Text is light, but background not dark enough - const float darkenFactor = qBound( 100, - 18 * qAbs(minDifference / difference) * backgroundGray / 164, 300 ) / 100.0f; - int h, s, v; - backgroundColor.getHsv( &h, &s, &v ); - backgroundColor.setHsv( h, s * darkenFactor, v / darkenFactor ); - } else if ( textGray < 128 && difference > -minDifference ) { - if ( difference >= -15 ) { - difference = -15; // Prevent division by zero - } - if ( backgroundGray == 0 ) { - backgroundGray = 1; - } - // Text is dark, but background not light enough - const float lightenFactor = qMax( 100, - 18 * qAbs(minDifference / -difference) * 164 / backgroundGray ) / 100.0f; - int h, s, v; - backgroundColor.getHsv( &h, &s, &v ); - backgroundColor.setHsv( h, s, v * lightenFactor ); - } - - // Fill the pixmap with the mixed background color - pixmap.fill( backgroundColor ); - - // Draw special background for departures with an alarm - QPainter p( &pixmap ); - if ( index().data(DrawAlarmBackgroundRole).toBool() ) { - drawAlarmBackground( &p, pixmapRect ); - } - - // Draw a line at the bottom of this TimetableItem - p.setPen( borderColor ); - p.drawLine( 0, pixmap.size().height() - 0.5, - pixmap.size().width(), pixmap.size().height() - 0.5 ); - - // Fade out to the left and right - drawFadeOutLeftAndRight( &p, pixmapRect ); - p.end(); - - // Draw pixmap - painter->drawPixmap( rect.topLeft(), pixmap ); -} - -void JourneyGraphicsItem::paintItem( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ) -{ - QFontMetrics fm( font() ); - const QRectF _vehicleRect = vehicleRect( rect ); - const QRectF _infoRect = infoRect( rect ); - - const int shadowWidth = 4; - QPixmap pixmap( (int)_vehicleRect.width(), (int)_vehicleRect.height() ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - - // Get a list of vehicles used in the journey, but without unknown vehicle type. - QSet vehicleTypeSet = journeyItem()->journeyInfo()->vehicleTypes(); - vehicleTypeSet.remove( UnknownVehicleType ); - QList vehicleTypes = vehicleTypeSet.toList(); - - // Calculate values for arranging vehicle type icons - const int vehiclesPerRow = qCeil( qSqrt(vehicleTypes.count()) ); - const int rows = qCeil( (qreal)vehicleTypes.count() / (qreal)vehiclesPerRow ); - const qreal vehicleSize = vehiclesPerRow == 1 ? _vehicleRect.width() - : _vehicleRect.width() / ( 0.7 * vehiclesPerRow ); - const qreal vehicleOffsetX = vehiclesPerRow == 1 ? 0.0 - : (_vehicleRect.width() - vehicleSize) / (vehiclesPerRow - 1); - const qreal vehicleOffsetY = rows == 1 ? 0.0 - : (_vehicleRect.height() - vehicleSize) / (rows - 1); - int vehiclesInCurrentRow = 0; - qreal x = shadowWidth; - qreal y = shadowWidth; - QSizeF iconSize( vehicleSize - 2 * shadowWidth, vehicleSize - 2 * shadowWidth ); - m_parent->svg()->resize( iconSize ); - - // Draw the vehicle type icons - for ( int i = 0; i < vehicleTypes.count(); ++i ) { - VehicleType vehicleType = vehicleTypes[i]; - if ( vehiclesInCurrentRow == vehiclesPerRow ) { - vehiclesInCurrentRow = 0; - if ( vehicleTypes.count() - i < vehiclesPerRow ) { - x = shadowWidth + vehicleOffsetX / 2.0; - } else { - x = shadowWidth; - } - y += vehicleOffsetY; - } - - QString vehicleKey; - switch ( vehicleType ) { - case Tram: vehicleKey = "tram"; break; - case Bus: vehicleKey = "bus"; break; - case TrolleyBus: vehicleKey = "trolleybus"; break; - case Subway: vehicleKey = "subway"; break; - case Metro: vehicleKey = "metro"; break; - case InterurbanTrain: vehicleKey = "interurbantrain"; break; - case RegionalTrain: vehicleKey = "regionaltrain"; break; - case RegionalExpressTrain: vehicleKey = "regionalexpresstrain"; break; - case InterregionalTrain: vehicleKey = "interregionaltrain"; break; - case IntercityTrain: vehicleKey = "intercitytrain"; break; - case HighSpeedTrain: vehicleKey = "highspeedtrain"; break; - case Feet: vehicleKey = "feet"; break; - case Ship: vehicleKey = "ship"; break; - case Plane: vehicleKey = "plane"; break; - default: - kDebug() << "Unknown vehicle type" << vehicleType; - painter->drawEllipse( QRectF(x, y, iconSize.width(), iconSize.height()) - .adjusted(5, 5, -5, -5) ); - painter->drawText( QRectF(x, y, iconSize.width(), iconSize.height()), - "?", QTextOption(Qt::AlignCenter) ); - } - if ( !m_parent->svg()->hasElement(vehicleKey) ) { - kDebug() << "SVG element" << vehicleKey << "not found"; - } else { - // Draw SVG vehicle element into pixmap - m_parent->svg()->paint( &p, x, y, vehicleKey ); - } - - ++vehiclesInCurrentRow; - x += vehicleOffsetX; - } - p.end(); - - QPixmap fadePixmap( pixmap.size() ); - fadePixmap.fill( Qt::transparent ); - QPainter p2( &fadePixmap ); - - // Create shadow for the SVG element and draw the SVG and it's shadow. - QImage shadow = pixmap.toImage(); - Plasma::PaintUtils::shadowBlur( shadow, shadowWidth - 1, Qt::black ); - p2.drawImage( QPoint(1, 2), shadow ); - p2.drawPixmap( QPoint(0, 0), pixmap ); - - // Make startTransitionPixmap more transparent (for fading) - p2.setCompositionMode( QPainter::CompositionMode_DestinationIn ); - QLinearGradient gradient( pixmap.width() / 4, 0, pixmap.width(), 0 ); - gradient.setColorAt( 0.0, Qt::black ); - gradient.setColorAt( 1.0, Qt::transparent ); - p2.fillRect( fadePixmap.rect(), QBrush(gradient) ); - p2.end(); - - painter->drawPixmap( _vehicleRect.topLeft(), fadePixmap ); - - // Draw text - QColor _textColor = textColor(); - const bool drawShadowsOrHalos = m_parent->isOptionEnabled( PublicTransportWidget::DrawShadowsOrHalos ); - const bool drawHalos = drawShadowsOrHalos && qGray(_textColor.rgb()) < 192; - painter->setPen( _textColor ); - TextDocumentHelper::drawTextDocument( painter, option, m_infoTextDocument, - _infoRect, drawShadowsOrHalos - ? (drawHalos ? TextDocumentHelper::DrawHalos : TextDocumentHelper::DrawShadows) - : TextDocumentHelper::DoNotDrawShadowOrHalos ); - - // Draw extra icon(s) - QRectF _extraIconRect; - if ( hasExtraIcon() ) { - QModelIndex modelIndex = index().model()->index( index().row(), ColumnTarget ); - QIcon icon = modelIndex.data( Qt::DecorationRole ).value(); - _extraIconRect = extraIconRect(rect/*, departureTimeWidth, arrivalTimeWidth*/); - painter->drawPixmap( _extraIconRect.toRect(), icon.pixmap(extraIconSize()) ); - } - if ( hasExtraIcon(ColumnDeparture) ) { - if ( _extraIconRect.isValid() ) { - // Move icon rect to the left - _extraIconRect.moveRight( _extraIconRect.left() - 4 ); - } else { - _extraIconRect = extraIconRect(rect/*, departureTimeWidth, arrivalTimeWidth*/); - } - - QModelIndex modelIndex = index().model()->index( index().row(), ColumnDeparture ); - QIcon icon = modelIndex.data( Qt::DecorationRole ).value(); - painter->drawPixmap( _extraIconRect.toRect(), icon.pixmap(extraIconSize()) ); - } -} - -QColor PublicTransportGraphicsItem::textColor() const -{ - bool manuallyHighlighted = false; - DepartureModel *model = !m_item ? 0 : qobject_cast( m_item->model() ); - if ( model ) { - // Only proceed with highlighted stops, if the model is a DepartureModel (not a JourneyModel) - DepartureItem *item = qobject_cast( m_item.data() ); - manuallyHighlighted = item->departureInfo()->routeStops() - .contains( model->highlightedStop(), Qt::CaseInsensitive ); - } - - if ( manuallyHighlighted ) { - QColor highlightColor = KColorUtils::mix( - Plasma::Theme::defaultTheme()->color(Plasma::Theme::HighlightColor), - palette().color(QPalette::Active, QPalette::Text), 0.5 ); - return highlightColor; - } else { - #if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - QColor color = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor); - #else - QColor color = Plasma::Theme::defaultTheme()->color(Plasma::Theme::ViewTextColor); - #endif - - // Make text color a little transparent, if not highlighted - color.setAlpha( 230 ); - - return color; - } -} - -QColor PublicTransportGraphicsItem::backgroundColor() const -{ - #if KDE_VERSION < KDE_MAKE_VERSION(4,6,0) - return Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor); - #else - return Plasma::Theme::defaultTheme()->color(Plasma::Theme::ViewBackgroundColor); - #endif -} - -void DepartureGraphicsItem::paintBackground( QPainter* painter, - const QStyleOptionGraphicsItem* option, const QRectF& rect ) -{ - Q_UNUSED( option ); - QColor borderColor = textColor(); - borderColor.setAlphaF( 0.5 ); - - QRectF pixmapRect( 0, 0, rect.width(), rect.height() ); - QPixmap pixmap( pixmapRect.size().toSize() ); - - // Get the background color for departures in color groups / alternative background colors - QColor backgroundColor = index().data( Qt::BackgroundColorRole ).value(); - if ( backgroundColor == Qt::transparent && index().row() % 2 == 1 ) { - QColor alternateBackgroundColor = KColorScheme( QPalette::Active, KColorScheme::View ) - .background( KColorScheme::AlternateBackground ).color(); - backgroundColor = KColorUtils::mix( backgroundColor, alternateBackgroundColor, 0.4 ); - } - - // Fill the pixmap with the mixed background color - pixmap.fill( backgroundColor ); - - // Draw special background for departures with an alarm - QPainter p( &pixmap ); - if ( index().data(DrawAlarmBackgroundRole).toBool() ) { -// qreal bias = index().data( AlarmColorIntensityRole ).toReal(); - drawAlarmBackground( &p, pixmapRect ); - } - - // Draw a line at the bottom of this TimetableItem, - // -0.5 to draw the line completely inside the pixmap - p.setPen( borderColor ); - p.drawLine( 0, pixmap.size().height() - 0.5, - pixmap.size().width(), pixmap.size().height() - 0.5 ); - - // Fade out to the left and right - drawFadeOutLeftAndRight( &p, pixmapRect ); - p.end(); - - // Draw pixmap - painter->drawPixmap( rect.topLeft(), pixmap ); -} - -void PublicTransportGraphicsItem::drawAlarmBackground( QPainter *painter, const QRectF &rect ) -{ - // alarmColor is oxygen color "brick red5", with an alpha value added - const QColor alarmColor( 191, 3, 3, 180 ); - // Draw the alarm gradients over the first and last third vertically - const int alarmHeight = unexpandedHeight() / 3; - - // Draw the gradient at the top - QLinearGradient alarmGradientTop( 0, 0, 0, alarmHeight ); - alarmGradientTop.setColorAt( 0, alarmColor ); - alarmGradientTop.setColorAt( 1, Qt::transparent ); - painter->fillRect( QRectF(0, 0, rect.width(), alarmHeight), alarmGradientTop ); - - // Draw the gradient at the bottom - QLinearGradient alarmGradientBottom( 0, rect.height() - alarmHeight, 0, rect.height() ); - alarmGradientBottom.setColorAt( 0, Qt::transparent ); - alarmGradientBottom.setColorAt( 1, alarmColor ); - painter->fillRect( QRectF(0, rect.height() - alarmHeight, rect.width(), alarmHeight), - alarmGradientBottom ); -} - -void PublicTransportGraphicsItem::drawFadeOutLeftAndRight( QPainter* painter, const QRectF& rect, - int fadeWidth ) -{ - painter->setCompositionMode( QPainter::CompositionMode_DestinationIn ); - QLinearGradient alphaGradient( 0, 0, 1, 0 ); - alphaGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); - alphaGradient.setColorAt( 0, Qt::transparent ); - alphaGradient.setColorAt( 1, Qt::black ); - // Fade out on the left - painter->fillRect( QRectF(rect.left(), rect.top(), fadeWidth, rect.height()), alphaGradient ); - - alphaGradient.setColorAt( 0, Qt::black ); - alphaGradient.setColorAt( 1, Qt::transparent ); - // Fade out on the right (the +1 is to be sure, to not have a 1 pixel line on the right, which - // isn't made transparent at all) - painter->fillRect( QRectF(rect.right() - fadeWidth, rect.top(), fadeWidth + 1, rect.height()), - alphaGradient ); -} - -void DepartureGraphicsItem::paintItem( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ) -{ - QColor _backgroundColor = backgroundColor(); // TODO: store these colors in option->palette? - QColor _textColor = textColor(); - QFontMetrics fm( font() ); - const QRectF _vehicleRect = vehicleRect( rect ); - const QRectF _timeRect = timeRect( rect ); - const qreal timeWidth = timeColumnWidth(); - const QRectF _infoRect = infoRect( rect, timeWidth ); - - int shadowWidth = 4; - QSizeF iconSize( _vehicleRect.width() - 2 * shadowWidth, _vehicleRect.height() - 2 * shadowWidth ); - QString vehicleKey; - switch ( departureItem()->departureInfo()->vehicleType() ) { - case Tram: vehicleKey = "tram"; break; - case Bus: vehicleKey = "bus"; break; - case TrolleyBus: vehicleKey = "trolleybus"; break; - case Subway: vehicleKey = "subway"; break; - case Metro: vehicleKey = "metro"; break; - case InterurbanTrain: vehicleKey = "interurbantrain"; break; - case RegionalTrain: vehicleKey = "regionaltrain"; break; - case RegionalExpressTrain: vehicleKey = "regionalexpresstrain"; break; - case InterregionalTrain: vehicleKey = "interregionaltrain"; break; - case IntercityTrain: vehicleKey = "intercitytrain"; break; - case HighSpeedTrain: vehicleKey = "highspeedtrain"; break; - case Feet: vehicleKey = "feet"; break; - case Ship: vehicleKey = "ship"; break; - case Plane: vehicleKey = "plane"; break; - default: - kDebug() << "Unknown vehicle type" << departureItem()->departureInfo()->vehicleType(); - painter->setPen( _textColor ); - painter->setBrush( _backgroundColor ); - painter->drawEllipse( QRectF(shadowWidth, shadowWidth, iconSize.width(), iconSize.height()) - .adjusted(2, 2, -2, -2) ); - painter->drawText( QRectF(shadowWidth, shadowWidth, iconSize.width(), iconSize.height()), - "?", QTextOption(Qt::AlignCenter) ); - } - - const QString vehicleCacheKey - = vehicleKey + QString("%1%2").arg( iconSize.width() ).arg( iconSize.height() ); - QPixmap vehiclePixmap; - if ( !m_pixmapCache || !m_pixmapCache->find(vehicleCacheKey, vehiclePixmap) ) { - if ( !m_parent->svg()->hasElement(vehicleKey) ) { - if ( !vehicleKey.isEmpty() ) { - kDebug() << "SVG element" << vehicleKey << "not found"; - } - } else { - // Draw SVG vehicle element into pixmap - QPixmap pixmap( (int)_vehicleRect.width(), (int)_vehicleRect.height() ); - pixmap.fill( Qt::transparent ); - QPainter p( &pixmap ); - m_parent->svg()->resize( iconSize ); - m_parent->svg()->paint( &p, shadowWidth, shadowWidth, vehicleKey ); - - vehiclePixmap = QPixmap( pixmap.size() ); - vehiclePixmap.fill( Qt::transparent ); - QPainter p2( &vehiclePixmap ); - - // Create shadow for the SVG element and draw the SVG and it's shadow. - QImage shadow = pixmap.toImage(); - Plasma::PaintUtils::shadowBlur( shadow, shadowWidth - 1, Qt::black ); - p2.drawImage( QPoint(1, 2), shadow ); - p2.drawPixmap( QPoint(0, 0), pixmap ); - - // Make startTransitionPixmap more transparent (for fading) - p2.setCompositionMode( QPainter::CompositionMode_DestinationIn ); - QLinearGradient gradient( pixmap.width() / 4, 0, pixmap.width(), 0 ); - gradient.setColorAt( 0.0, Qt::black ); - gradient.setColorAt( 1.0, Qt::transparent ); - p2.fillRect( vehiclePixmap.rect(), QBrush(gradient) ); - p2.end(); - - if ( m_pixmapCache ) { - m_pixmapCache->insert( vehicleCacheKey, vehiclePixmap ); - } - } - } - if ( !vehicleKey.isEmpty() ) { - painter->drawPixmap( _vehicleRect.topLeft(), vehiclePixmap ); - } - - // Draw text - const bool drawShadowsOrHalos = m_parent->isOptionEnabled( PublicTransportWidget::DrawShadowsOrHalos ); - const bool drawHalos = drawShadowsOrHalos && qGray(_textColor.rgb()) < 192; - painter->setPen( _textColor ); - - DepartureModel *model = qobject_cast( m_item->model() ); - bool manuallyHighlighted = false; - if ( model ) { - // Only proceed with highlighted stops, if the model is a DepartureModel (not a JourneyModel) - DepartureItem *item = qobject_cast( m_item.data() ); - manuallyHighlighted = item->departureInfo()->routeStops() - .contains( model->highlightedStop(), Qt::CaseInsensitive ); - } - - if ( m_highlighted != manuallyHighlighted ) { - QFont _font = font(); - _font.setItalic( manuallyHighlighted ); - m_infoTextDocument->setDefaultFont( _font ); - m_timeTextDocument->setDefaultFont( _font ); - m_othersTextDocument->setDefaultFont( _font ); - setFont( _font ); - m_highlighted = manuallyHighlighted; - } - - TextDocumentHelper::Option options = drawShadowsOrHalos - ? (drawHalos ? TextDocumentHelper::DrawHalos : TextDocumentHelper::DrawShadows) - : TextDocumentHelper::DoNotDrawShadowOrHalos; - TextDocumentHelper::drawTextDocument( painter, option, m_infoTextDocument, - _infoRect, options ); - TextDocumentHelper::drawTextDocument( painter, option, m_timeTextDocument, - _timeRect, options ); - - // Draw extra icon(s), eg. an alarm icon or an indicator for additional news for a journey - QRectF _extraIconRect; - if ( hasExtraIcon() ) { - QModelIndex modelIndex = index().model()->index( index().row(), ColumnTarget ); - QIcon icon = modelIndex.data( Qt::DecorationRole ).value(); - _extraIconRect = extraIconRect( rect, timeWidth ); - painter->drawPixmap( _extraIconRect.toRect(), icon.pixmap(extraIconSize()) ); - } - if ( hasExtraIcon(ColumnDeparture) ) { - if ( _extraIconRect.isValid() ) { - // Move icon rect to the left - _extraIconRect.moveRight( _extraIconRect.left() - 4 ); - } else { - _extraIconRect = extraIconRect(rect, timeWidth); - } - - QModelIndex modelIndex = index().model()->index( index().row(), ColumnDeparture ); - QIcon icon = modelIndex.data( Qt::DecorationRole ).value(); - painter->drawPixmap( _extraIconRect.toRect(), icon.pixmap(extraIconSize()) ); - } -} - -void JourneyGraphicsItem::paintExpanded( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF& rect ) -{ - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - QColor _textColor = textColor(); - const bool drawShadowsOrHalos = m_parent->isOptionEnabled( PublicTransportWidget::DrawShadowsOrHalos ); - const bool drawHalos = drawShadowsOrHalos && qGray(_textColor.rgb()) < 192; - - qreal y = rect.top() - padding(); - if ( m_routeItem ) { - y += m_routeItem->size().height() + padding(); - } - - if ( y > rect.bottom() ) { - return; // Currently not expanded enough to show more information (is animating) - } - - QString html; - ChildItem *operatorItem = m_item->childByType( OperatorItem ); - ChildItem *journeyNewsItem = m_item->childByType( JourneyNewsItem ); - ChildItem *pricingItem = m_item->childByType( PricingItem ); - if ( operatorItem ) { - if ( !html.isEmpty() ) { - html.append("
"); - } - html.append( operatorItem->formattedText() ); - } - if ( journeyNewsItem ) { - if ( !html.isEmpty() ) { - html.append("
"); - } - html.append( journeyNewsItem->formattedText() ); - } - if ( pricingItem ) { - if ( !html.isEmpty() ) { - html.append("
"); - } - html.append( pricingItem->formattedText() ); - } - - if ( !html.isEmpty() ) { - QFontMetrics fm( font() ); - QRectF htmlRect( rect.left(), y, rect.width(), rect.bottom() - y ); - - // Create layout for the departure time column - QTextDocument additionalInformationTextDocument; - additionalInformationTextDocument.setDefaultFont( font() ); - QTextOption textOption( Qt::AlignVCenter | Qt::AlignLeft ); - additionalInformationTextDocument.setDefaultTextOption( textOption ); - additionalInformationTextDocument.setDocumentMargin( 0 ); - additionalInformationTextDocument.setPageSize( htmlRect.size() ); - additionalInformationTextDocument.setHtml( html ); - additionalInformationTextDocument.documentLayout(); - - painter->setPen( _textColor ); - TextDocumentHelper::drawTextDocument( painter, option, &additionalInformationTextDocument, - htmlRect, drawShadowsOrHalos - ? (drawHalos ? TextDocumentHelper::DrawHalos : TextDocumentHelper::DrawShadows) - : TextDocumentHelper::DoNotDrawShadowOrHalos ); - } -} - -void DepartureGraphicsItem::paintExpanded( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF& rect ) -{ - painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform ); - QColor _textColor = textColor(); - const bool drawShadowsOrHalos = m_parent->isOptionEnabled( PublicTransportWidget::DrawShadowsOrHalos ); - const bool drawHalos = drawShadowsOrHalos && qGray(_textColor.rgb()) < 192; - - qreal y = rect.top(); - if ( m_routeItem ) { - y += m_routeItem->size().height() + padding(); - } else if ( m_routeInfoWidget ) { - y += m_routeInfoWidget->size().height() + padding(); - } - - if ( y >= size().height() ) { - return; // Currently not expanded enough to show more information (is animating) - } - - if ( m_othersTextDocument ) { - QFontMetrics fm( font() ); - QRectF htmlRect( rect.left(), y, rect.width(), rect.bottom() - y ); - painter->setPen( _textColor ); - TextDocumentHelper::drawTextDocument( painter, option, m_othersTextDocument, - htmlRect, drawShadowsOrHalos - ? (drawHalos ? TextDocumentHelper::DrawHalos : TextDocumentHelper::DrawShadows) - : TextDocumentHelper::DoNotDrawShadowOrHalos); - } -} - -qreal PublicTransportGraphicsItem::expandAreaHeightTarget() const -{ - // Round expand area height to multiples of unexpandedHeight(), - // because this is used for the snap size of the ScrollWidget to stop scrolling - // at the top of an item (cannot set individual scroll stop positions) - const qreal height = expandAreaHeightMinimum(); - const qreal heightUnit = unexpandedHeight(); - return qCeil(height / heightUnit) * heightUnit; -} - -qreal PublicTransportGraphicsItem::expandAreaHeight() const -{ - return expandAreaHeightTarget() * m_expandStep; -} - -qreal JourneyGraphicsItem::expandAreaHeightMinimum() const -{ - if ( !m_item || qFuzzyIsNull(m_expandStep) ) { - return 0.0; - } - - qreal height = padding(); - if ( m_routeItem ) { - height += m_routeItem->size().height() + padding(); - } - - qreal extraInformationHeight = 0.0; - QFontMetrics fm( font() ); - ChildItem *delayItem = m_item->childByType( DelayItem ); - if ( delayItem ) { - extraInformationHeight += 2 * fm.height(); - } - - ChildItem *operatorItem = m_item->childByType( OperatorItem ); - if ( operatorItem ) { - extraInformationHeight += fm.height(); - } - - ChildItem *journeyNewsItem = m_item->childByType( JourneyNewsItem ); - if ( journeyNewsItem ) { - extraInformationHeight += 3 * fm.height(); - } - - ChildItem *pricingItem = m_item->childByType( PricingItem ); - if ( pricingItem ) { - extraInformationHeight += fm.height(); - } - - if ( extraInformationHeight != 0.0 ) { - height += extraInformationHeight + padding(); - } - - return height; -} - -qreal DepartureGraphicsItem::expandAreaHeightMinimum() const -{ - if ( !m_item || qFuzzyIsNull(m_expandStep) ) { - return 0.0; - } - - qreal height = padding(); - if ( isRouteDataAvailable() || isRouteDataRequestable() ) { - height += routeItemHeight() + padding(); - } - if ( m_othersTextDocument ) { - height += m_othersTextDocument->size().height() + padding(); - } - return height; -} - -qreal DepartureGraphicsItem::expandAreaHeightMaximum() const -{ - if ( !m_item ) { - return 0.0; - } - - qreal height = padding(); - height += routeItemHeight() + padding(); - if ( m_othersTextDocument ) { - height += m_othersTextDocument->documentLayout()->documentSize().height() + padding(); - } - const qreal heightUnit = unexpandedHeight(); - return qCeil(height / heightUnit) * heightUnit; -} - -qreal DepartureGraphicsItem::expandAreaIndentation() const -{ - return m_parent->iconSize() * 0.65 + padding(); -} - -qreal JourneyGraphicsItem::expandAreaIndentation() const -{ - return m_parent->iconSize() * 0.65 + padding(); -} - -QSizeF TimetableListItem::sizeHint( Qt::SizeHint which, const QSizeF &constraint ) const -{ - if ( which == Qt::MinimumSize || which == Qt::MaximumSize ) { - return QSizeF( which == Qt::MinimumSize ? 100 : 100000, - qMax(m_parent->iconSize() * 1.1, - QFontMetrics(font()).lineSpacing() + 4.0 * m_parent->zoomFactor()) ); - } else { - return QGraphicsWidget::sizeHint( which, constraint ); - } -} - -QSizeF PublicTransportGraphicsItem::sizeHint(Qt::SizeHint which, const QSizeF& constraint) const -{ - if ( which == Qt::MinimumSize ) { - return QSizeF( 100, m_fadeOut * - (m_expanded || !qFuzzyIsNull(m_expandStep) - ? qFloor(unexpandedHeight() + expandAreaHeight()) : qFloor(unexpandedHeight())) ); - } else if ( which == Qt::MaximumSize ) { - return QSizeF( 100000, m_fadeOut * - (m_expanded || !qFuzzyIsNull(m_expandStep) - ? qFloor(unexpandedHeight() + expandAreaHeight()) : qFloor(unexpandedHeight())) ); - } else { - return QGraphicsWidget::sizeHint( which, constraint ); - } -} - -QSizeF PublicTransportWidget::sizeHint(Qt::SizeHint which, const QSizeF& constraint) const -{ - if ( which == Qt::MinimumSize ) { - return QSizeF( 100, 50 ); - } else { - return Plasma::ScrollWidget::sizeHint(which, constraint); - } -} - -PublicTransportWidget::PublicTransportWidget( Options options, ExpandingOption expandingOption, - QGraphicsItem* parent ) - : Plasma::ScrollWidget( parent ), m_options(options), m_expandingOption(expandingOption), - m_model(0), m_prefixItem(0), m_postfixItem(0), m_svg(0), - m_copyStopToClipboardAction(0), m_showInMapAction(0) -{ - setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - setupActions(); - - QGraphicsWidget *container = new QGraphicsWidget( this ); - QGraphicsLinearLayout *l = new QGraphicsLinearLayout( Qt::Vertical, container ); - l->setSpacing( 0.0 ); - l->setContentsMargins( 0, 0, 0, 0 ); - container->setLayout( l ); - setWidget( container ); - - m_maxLineCount = 2; - m_iconSize = 32; - m_zoomFactor = 1.0; - updateSnapSize(); -} - -QPainterPath PublicTransportGraphicsItem::shape() const -{ - // Get the viewport geometry of the ScrollWidget in local coordinates - // and adjusted to clip some pixels of children at the top/bottom - // for the ScrollWidget overflow border - QPainterPath parentPath; - QRectF viewport = publicTransportWidget()->viewportGeometry(); - if ( publicTransportWidget()->overflowBordersVisible() ) { - viewport.adjust( 0, 2, 0, -1 ); - } - parentPath.addRect( viewport ); - parentPath = mapFromItem( publicTransportWidget(), parentPath ); - - // Intersect the parent bounding rectangle with the bounding rectangle of this item - // and return it as visible shape - QPainterPath path; - QRectF rect = parentPath.boundingRect().intersected( boundingRect() ); - path.addRect( rect ); - return path; -} - -QPainterPath TimetableListItem::shape() const -{ - // Get the viewport geometry of the ScrollWidget in local coordinates - // and adjusted to clip some pixels of children at the top/bottom - // for the ScrollWidget overflow border - QPainterPath parentPath; - QRectF viewport = m_parent->viewportGeometry(); - if ( m_parent->overflowBordersVisible() ) { - viewport.adjust( 0, 2, 0, -1 ); - } - parentPath.addRect( viewport ); - parentPath = mapFromItem( m_parent, parentPath ); - - // Intersect the parent bounding rectangle with the bounding rectangle of this item - // and return it as visible shape - QPainterPath path; - QRectF rect = parentPath.boundingRect().intersected( boundingRect() ); - path.addRect( rect ); - return path; -} - -void PublicTransportWidget::setupActions() -{ - m_copyStopToClipboardAction = new StopAction( StopAction::CopyStopNameToClipboard, this ); - connect( m_copyStopToClipboardAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SIGNAL(requestStopAction(StopAction::Type,QString,QString)) ); - - m_showInMapAction = new StopAction( StopAction::ShowStopInMap, this ); - connect( m_showInMapAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SIGNAL(requestStopAction(StopAction::Type,QString,QString)) ); -} - -void PublicTransportWidget::setOption( PublicTransportWidget::Option option, bool enable ) -{ - m_options = enable ? m_options | option : m_options & ~option; - update(); -} - -void PublicTransportWidget::setOptions( PublicTransportWidget::Options options ) -{ - m_options = options; - update(); -} - -void PublicTransportWidget::updateSnapSize() -{ - setSnapSize( QSizeF(0, PublicTransportGraphicsItem::unexpandedHeight(m_iconSize, - PublicTransportGraphicsItem::padding(m_zoomFactor), QFontMetrics(font()).lineSpacing(), - m_maxLineCount)) ); -} - -void PublicTransportWidget::setPrefixItem( TimetableListItem *prefixItem ) -{ - QGraphicsLinearLayout *l = static_cast( widget()->layout() ); - if ( m_prefixItem == prefixItem ) { - if ( l->count() == 0 || l->itemAt(0) != m_prefixItem ) { - l->insertItem( 0, prefixItem ); - } - return; - } - - if ( m_prefixItem ) { - l->removeItem( m_prefixItem ); - delete m_prefixItem; - m_prefixItem = 0; - } - if ( prefixItem ) { - kDebug() << prefixItem; - l->insertItem( 0, prefixItem ); - m_prefixItem = prefixItem; - } -} - -void PublicTransportWidget::setPostfixItem( TimetableListItem *postfixItem ) -{ - QGraphicsLinearLayout *l = static_cast( widget()->layout() ); - if ( m_postfixItem == postfixItem ) { - if ( l->count() == 0 || l->itemAt(l->count() - 1) != m_postfixItem ) { - l->addItem( postfixItem ); - } - return; - } - - if ( m_postfixItem ) { - l->removeItem( m_postfixItem ); - delete m_postfixItem; - m_postfixItem = 0; - } - if ( postfixItem ) { - l->addItem( postfixItem ); - m_postfixItem = postfixItem; - } -} - -JourneyTimetableWidget::JourneyTimetableWidget( Options options, Flags flags, - ExpandingOption expandingOption, - QGraphicsItem* parent ) - : PublicTransportWidget(options, expandingOption, parent), m_flags(flags), - m_requestJourneyToStopAction(0), m_requestJourneyFromStopAction(0), m_earlierAction(0), - m_laterAction(0) -{ - setupActions(); - - // Add items to request earlier/later journeys - if ( m_flags.testFlag(ShowEarlierJourneysItem) && m_earlierAction ) { - setPrefixItem( new TimetableListItem(m_earlierAction, this, this) ); - } - if ( m_flags.testFlag(ShowLaterJourneysItem) && m_laterAction ) { - setPostfixItem( new TimetableListItem(m_laterAction, this, this) ); - } -} - -TimetableWidget::TimetableWidget( Options options, ExpandingOption expandingOption, - QGraphicsItem* parent ) - : PublicTransportWidget(options, expandingOption, parent), m_showDeparturesAction(0), - m_highlightStopAction(0), m_newFilterViaStopAction(0), - m_pixmapCache(new KPixmapCache("PublicTransportVehicleIcons")) -{ - m_targetHidden = false; - setupActions(); -} - -TimetableWidget::~TimetableWidget() -{ - delete m_pixmapCache; -} - -void TimetableWidget::setupActions() -{ - PublicTransportWidget::setupActions(); - - m_showDeparturesAction = new StopAction( StopAction::ShowDeparturesForStop, this ); - m_highlightStopAction= new StopAction( StopAction::HighlightStop, this ); - m_newFilterViaStopAction = new StopAction( StopAction::CreateFilterForStop, this ); - connect( m_showDeparturesAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SIGNAL(requestStopAction(StopAction::Type,QString,QString)) ); - connect( m_highlightStopAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SIGNAL(requestStopAction(StopAction::Type,QString,QString)) ); - connect( m_newFilterViaStopAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SIGNAL(requestStopAction(StopAction::Type,QString,QString)) ); -} - -void JourneyTimetableWidget::setupActions() -{ - PublicTransportWidget::setupActions(); - - m_requestJourneyToStopAction = new StopAction( StopAction::RequestJourneysToStop, this ); - m_requestJourneyFromStopAction = new StopAction( StopAction::RequestJourneysFromStop, this ); - connect( m_requestJourneyToStopAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SIGNAL(requestStopAction(StopAction::Type,QString,QString)) ); - connect( m_requestJourneyFromStopAction, SIGNAL(stopActionTriggered(StopAction::Type,QString,QString)), - this, SIGNAL(requestStopAction(StopAction::Type,QString,QString)) ); - - if ( m_flags.testFlag(ShowEarlierJourneysItem) ) { - m_earlierAction = new QAction( KIcon("arrow-up-double"), - i18nc("@action:inmenu", "Get &Earlier Journeys"), this ); - connect( m_earlierAction, SIGNAL(triggered(bool)), this, SIGNAL(requestEarlierItems()) ); - } - if ( m_flags.testFlag(ShowLaterJourneysItem) ) { - m_laterAction = new QAction( KIcon("arrow-down-double"), - i18nc("@action:inmenu", "Get &Later Journeys"), this ); - connect( m_laterAction, SIGNAL(triggered(bool)), this, SIGNAL(requestLaterItems()) ); - } -} - -void PublicTransportWidget::setModel( PublicTransportModel* model ) -{ - if ( m_model ) { - kWarning() << "Model already set"; - return; - } - m_model = model; - - if ( m_model ) { - connect( m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(rowsInserted(QModelIndex,int,int)) ); - connect( m_model, SIGNAL(itemsAboutToBeRemoved(QList)), - this, SLOT(itemsAboutToBeRemoved(QList)) ); - connect( m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), - this, SLOT(rowsRemoved(QModelIndex,int,int)) ); - connect( m_model, SIGNAL(modelReset()), this, SLOT(modelReset()) ); - connect( m_model, SIGNAL(layoutChanged()), this, SLOT(layoutChanged()) ); - connect( m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), - this, SLOT(dataChanged(QModelIndex,QModelIndex)) ); - } -} - -PublicTransportGraphicsItem* PublicTransportWidget::item( const QModelIndex& index ) -{ - foreach ( PublicTransportGraphicsItem *item, m_items ) { - if ( item->index() == index ) { - return item; - } - } - - return 0; -} - -int PublicTransportWidget::rowFromItem( PublicTransportGraphicsItem *item ) const -{ - for ( int row = 0; row < m_items.count(); ++row ) { - if ( m_items[row] == item ) { - // Found the item - return row; - } - } - - // No such item found - return -1; -} - -void PublicTransportWidget::setExpandOption( ExpandingOption expandingOption ) -{ - if ( expandingOption == NoExpanding ) { - foreach ( PublicTransportGraphicsItem *item, m_items ) { - item->setExpanded( false ); - } - } else if ( expandingOption == ExpandSingle ) { - PublicTransportGraphicsItem *visibleExpandedItem = 0; - QList< PublicTransportGraphicsItem* > expandedItems; - foreach ( PublicTransportGraphicsItem *item, m_items ) { - if ( item->isExpanded() ) { - if ( !visibleExpandedItem && item->geometry().intersects(boundingRect()) ) { - kDebug() << item->geometry(); - visibleExpandedItem = item; - } else { - expandedItems << item; - } - } - } - - if ( !expandedItems.isEmpty() || visibleExpandedItem ) { - if ( !visibleExpandedItem ) { - visibleExpandedItem = expandedItems.takeFirst(); - } - foreach ( PublicTransportGraphicsItem *item, expandedItems ) { - item->setExpandedNotAffectingOtherItems( false ); - } - } - } - - m_expandingOption = expandingOption; -} - -bool PublicTransportWidget::isItemExpanded( int row ) const -{ - return m_items[ row ]->isExpanded(); -} - -void PublicTransportWidget::setItemExpanded( int row, bool expanded ) -{ - if ( m_expandingOption == NoExpanding ) { - return; - } - - if ( m_expandingOption == ExpandSingle && expanded ) { - // Toggle expanded items TODO fix docu... - foreach ( PublicTransportGraphicsItem *item, m_items ) { - item->setExpandedNotAffectingOtherItems( false ); - } - } - PublicTransportGraphicsItem *expandItem = m_items[ row ]; - expandItem->setExpandedNotAffectingOtherItems( expanded ); -} - -void PublicTransportWidget::setZoomFactor( qreal zoomFactor ) -{ - m_zoomFactor = zoomFactor; - - for ( int i = 0; i < m_items.count(); ++i ) { - // Notify children about changed settings - m_items[i]->updateSettings(); - } - updateGeometry(); - updateItemGeometries(); - updateSnapSize(); - update(); -} - -void PublicTransportWidget::contextMenuEvent( QGraphicsSceneContextMenuEvent* event ) -{ - KMenu contextMenu; - QList< QAction* > actions = contextMenuActions(); - if ( actions.isEmpty() ) { - QGraphicsItem::contextMenuEvent( event ); - return; - } - event->accept(); - - contextMenu.addActions( actions ); - contextMenu.exec( event->screenPos() ); -} - -void TimetableWidget::contextMenuEvent( QGraphicsSceneContextMenuEvent *event ) -{ - PublicTransportGraphicsItem *item = dynamic_cast( - scene()->itemAt(event->scenePos()) ); - if ( item ) { - event->accept(); - emit contextMenuRequested( item, event->pos() ); - } else { - PublicTransportWidget::contextMenuEvent( event ); - } -} - -void PublicTransportWidget::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget ) -{ - QGraphicsWidget::paint(painter, option, widget); - - if ( m_items.isEmpty() && !m_noItemsText.isEmpty() ) { - painter->drawText( boundingRect(), m_noItemsText, QTextOption(Qt::AlignCenter) ); - } -} - -void PublicTransportWidget::updateItemLayouts() -{ - foreach ( PublicTransportGraphicsItem *item, m_items ) { - item->updateTextLayouts(); - } -} - -void PublicTransportWidget::updateItemGeometries() -{ - foreach ( PublicTransportGraphicsItem *item, m_items ) { - item->updateGeometry(); - } -} - -void PublicTransportWidget::modelReset() -{ - qDeleteAll( m_items ); - m_items.clear(); -} - -void TimetableWidget::dataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight ) -{ - if ( !topLeft.isValid() || !bottomRight.isValid() ) { - return; - } - for ( int row = topLeft.row(); row <= bottomRight.row() && row < m_model->rowCount(); ++row ) { - departureItem( row )->updateData( static_cast(m_model->item(row)), true ); - } -} - -void JourneyTimetableWidget::dataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight ) -{ - if ( !topLeft.isValid() || !bottomRight.isValid() ) { - return; - } - for ( int row = topLeft.row(); row <= bottomRight.row() && row < m_model->rowCount(); ++row ) { - journeyItem( row )->updateData( static_cast(m_model->item(row)), true ); - } -} - -void PublicTransportWidget::layoutChanged() -{ - -} - -void JourneyTimetableWidget::rowsInserted( const QModelIndex& parent, int first, int last ) -{ - if ( parent.isValid() ) { - kDebug() << "Item with parent" << parent << "Inserted" << first << last; - return; - } - - QGraphicsLinearLayout *l = static_cast( widget()->layout() ); - const int prefixOffset = m_prefixItem ? 1 : 0; - - if ( m_items.isEmpty() ) { - setPrefixItem( m_prefixItem ); - setPostfixItem( m_postfixItem ); - } - - for ( int row = first; row <= last; ++row ) { - JourneyGraphicsItem *item = new JourneyGraphicsItem( this, widget(), - m_copyStopToClipboardAction, m_showInMapAction, - m_requestJourneyToStopAction, m_requestJourneyFromStopAction ); - item->updateData( static_cast(m_model->item(row)) ); - connect( item, SIGNAL(requestAlarmCreation(QDateTime,QString,VehicleType,QString,QGraphicsWidget*)), - this, SIGNAL(requestAlarmCreation(QDateTime,QString,VehicleType,QString,QGraphicsWidget*)) ); - connect( item, SIGNAL(requestAlarmDeletion(QDateTime,QString,VehicleType,QString,QGraphicsWidget*)), - this, SIGNAL(requestAlarmDeletion(QDateTime,QString,VehicleType,QString,QGraphicsWidget*)) ); - connect( item, SIGNAL(expandedStateChanged(PublicTransportGraphicsItem*,bool)), - this, SIGNAL(expandedStateChanged(PublicTransportGraphicsItem*,bool)) ); - m_items.insert( row, item ); - - // Fade new items in - Plasma::Animation *fadeAnimation = Plasma::Animator::create( - Plasma::Animator::FadeAnimation, item ); - fadeAnimation->setTargetWidget( item ); - fadeAnimation->setProperty( "startOpacity", 0.0 ); - fadeAnimation->setProperty( "targetOpacity", 1.0 ); - fadeAnimation->start( QAbstractAnimation::DeleteWhenStopped ); - - l->insertItem( row + prefixOffset, item ); - } -} - -void TimetableWidget::rowsInserted( const QModelIndex& parent, int first, int last ) -{ - if ( parent.isValid() ) { - QModelIndex topLevelParent = parent; - while ( parent.parent().isValid() ) { - topLevelParent = topLevelParent.parent(); - } - DepartureGraphicsItem *departure = departureItem( topLevelParent ); - if ( departure ) { - departure->updateData( departure->departureItem(), true ); - } - return; - } - - QGraphicsLinearLayout *l = static_cast( widget()->layout() ); - const int prefixOffset = m_prefixItem ? 1 : 0; - for ( int row = first; row <= last; ++row ) { - DepartureGraphicsItem *item = new DepartureGraphicsItem( this, widget(), - m_copyStopToClipboardAction, m_showInMapAction, m_showDeparturesAction, - m_highlightStopAction, m_newFilterViaStopAction, m_pixmapCache ); - item->updateData( static_cast(m_model->item(row)) ); - connect( item, SIGNAL(expandedStateChanged(PublicTransportGraphicsItem*,bool)), - this, SIGNAL(expandedStateChanged(PublicTransportGraphicsItem*,bool)) ); - m_items.insert( row, item ); - - // Fade new items in - Plasma::Animation *fadeAnimation = Plasma::Animator::create( - Plasma::Animator::FadeAnimation, item ); - fadeAnimation->setTargetWidget( item ); - fadeAnimation->setProperty( "startOpacity", 0.0 ); - fadeAnimation->setProperty( "targetOpacity", 1.0 ); - fadeAnimation->start( QAbstractAnimation::DeleteWhenStopped ); - - l->insertItem( row + prefixOffset, item ); - } -} - -void PublicTransportWidget::itemsAboutToBeRemoved( const QList< ItemBase* >& items ) -{ - // Capture pixmaps for departures that will get removed - // to be able to animate it's disappearance - foreach ( const ItemBase *item, items ) { - if ( item->row() >= m_items.count() ) { - kDebug() << "Index out of bounds!"; - continue; - } - - PublicTransportGraphicsItem *timetableItem = m_items[ item->row() ]; - timetableItem->capturePixmap(); - } -} - -void PublicTransportWidget::rowsRemoved( const QModelIndex& parent, int first, int last ) -{ - if ( parent.isValid() ) { - kDebug() << "Item with parent" << parent << "Removed" << first << last; - return; - } - - if ( last >= m_items.count() ) { - kDebug() << "Cannot remove item, out of bounds:" << first << last; - last = m_items.count() - 1; - } - - if ( first == 0 && last == m_items.count() - 1 ) { - // All items get removed, the shrink animations wouldn't be smooth - for ( int row = last; row >= first; --row ) { - PublicTransportGraphicsItem *item = m_items.takeAt( row ); - - // Fade old items out - Plasma::Animation *fadeAnimation = Plasma::Animator::create( - Plasma::Animator::FadeAnimation, item ); - fadeAnimation->setTargetWidget( item ); - fadeAnimation->setProperty( "startOpacity", 1.0 ); - fadeAnimation->setProperty( "targetOpacity", 0.0 ); - connect( fadeAnimation, SIGNAL(finished()), item, SLOT(deleteLater()) ); - fadeAnimation->start( QAbstractAnimation::DeleteWhenStopped ); - } - } else { - // Only some items get removed, most probably they're currently departing - for ( int row = last; row >= first; --row ) { - PublicTransportGraphicsItem *item = m_items.takeAt( row ); - - // Shrink departing items - QPropertyAnimation *shrinkAnimation = new QPropertyAnimation( item, "fadeOut" ); - shrinkAnimation->setEasingCurve( QEasingCurve(QEasingCurve::InOutQuart) ); - shrinkAnimation->setStartValue( item->fadeOut() ); - shrinkAnimation->setEndValue( 0.0 ); - shrinkAnimation->setDuration( 1000 ); - connect( shrinkAnimation, SIGNAL(finished()), item, SLOT(deleteLater()) ); - shrinkAnimation->start( QAbstractAnimation::DeleteWhenStopped ); - } - } -} - -QRectF JourneyGraphicsItem::vehicleRect(const QRectF& rect) const -{ - return QRectF( rect.left(), rect.top() + (unexpandedHeight() - m_parent->iconSize()) / 2, - m_parent->iconSize(), m_parent->iconSize() ); -} - -QRectF JourneyGraphicsItem::infoRect( const QRectF& rect ) const -{ - qreal indentation = expandAreaIndentation(); - return QRectF( rect.left() + indentation, rect.top(), - rect.width() - indentation - padding() - - (hasExtraIcon() ? m_parent->iconSize() + padding() : 0), - unexpandedHeight() ); -} - -QRectF JourneyGraphicsItem::extraIconRect( const QRectF& rect ) const -{ - return QRectF( rect.right() - extraIconSize() - 2 * padding(), - rect.top() + (unexpandedHeight() - extraIconSize()) / 2, - extraIconSize(), extraIconSize() ); -} - -QRectF DepartureGraphicsItem::vehicleRect(const QRectF& rect) const { - return QRectF( rect.left(), rect.top() + (unexpandedHeight() - m_parent->iconSize()) / 2, - m_parent->iconSize(), m_parent->iconSize() ); -} - -QRectF DepartureGraphicsItem::infoRect(const QRectF& rect, qreal timeColumnWidth) const { - qreal indentation = expandAreaIndentation(); - return QRectF( rect.left() + indentation, rect.top(), - rect.width() - indentation - padding() - timeColumnWidth - - (hasExtraIcon() ? m_parent->iconSize() + padding() : 0), - unexpandedHeight() ); -} - -QRectF DepartureGraphicsItem::extraIconRect(const QRectF& rect, qreal timeColumnWidth) const { - return QRectF( rect.right() - timeColumnWidth - extraIconSize() - 2 * padding(), - rect.top() + (unexpandedHeight() - extraIconSize()) / 2, - extraIconSize(), extraIconSize() ); -} - -QRectF DepartureGraphicsItem::timeRect(const QRectF& rect) const { - if ( qobject_cast(m_parent)->isTargetHidden() ) { - return QRectF( rect.width() / 4, rect.top(), - rect.width() * 3 / 4 - padding(), unexpandedHeight() ); - } else { - return QRectF( rect.width() / 2, rect.top(), - rect.width() / 2 - padding(), unexpandedHeight() ); - } -} - -QGraphicsWidget* DepartureGraphicsItem::routeItem() const -{ - return m_routeItem; -} - -QGraphicsWidget* JourneyGraphicsItem::routeItem() const -{ - return m_routeItem; -} diff --git a/applet/timetablewidget.h b/applet/timetablewidget.h deleted file mode 100644 index 6d6aabe..0000000 --- a/applet/timetablewidget.h +++ /dev/null @@ -1,944 +0,0 @@ -/* - * Copyright 2013 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef TIMETABLEWIDGET_H -#define TIMETABLEWIDGET_H - -// Own includes -#include "stopaction.h" // for StopAction::Type -#include "departuremodel.h" - -// Plasma includes -#include // Base class - -// Qt includes -#include // Base class -#include // Member variable - -/** @file - * @brief This file contains the TimetableWidget / JourneyTimetableWidget and it's item classes. - * @author Friedrich Pülz */ - -class QTimer; -class KPixmapCache; -namespace Plasma -{ - class Svg; - class BusyWidget; -} - -class QPropertyAnimation; -class QWidget; -class QStyleOptionGraphicsItem; -class QPainter; -class QTextOption; - -class RouteGraphicsItem; -class JourneyRouteGraphicsItem; -class DepartureModel; -class DepartureItem; -class TimetableWidget; -class PublicTransportWidget; - -class TimetableListItem : public QGraphicsWidget { - Q_OBJECT - -public: - TimetableListItem( QAction *action, PublicTransportWidget *timetableWidget, - QGraphicsItem *parent = 0 ) - : QGraphicsWidget(parent), m_action(action), m_parent(timetableWidget) - { - setFlag( ItemIsFocusable ); - setFlag( ItemClipsToShape ); - setFlag( ItemClipsChildrenToShape ); - setAcceptHoverEvents( true ); - }; - - virtual QSizeF sizeHint( Qt::SizeHint which, const QSizeF& constraint = QSizeF() ) const; - -protected: - virtual void hoverLeaveEvent( QGraphicsSceneHoverEvent* ) { update(); }; - virtual void mousePressEvent( QGraphicsSceneMouseEvent* ) { m_action->trigger(); }; - virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *option, - QWidget *widget = 0 ); - virtual QPainterPath shape() const; - -private: - QAction *m_action; - PublicTransportWidget *m_parent; -}; - -/** - * @brief Abstract base class for DepartureGraphicsItem and JourneyGraphicsItem. - * - * For this QGraphicsWidget an expand area can be shown/hidden by clicking it. - * - * The item itself (without the expand area) gets drawn in @ref paintItem, while the background - * gets drawn in @ref paintBackground (stretched over the expand area). The area only visible when - * expanded gets drawn in @ref paintExpanded. - * - * The height of the item without the expand area can be given by overwriting @ref unexpandedHeight, - * overwrite @ref expandAreaHeight to give the height of the expand area. The expand area can be - * indented by overwriting @ref expandAreaIndentation, by default no indentation is used. - * - * To programatically change the expanded state use @ref toggleExpanded or @ref setExpanded, get - * the state using @ref isExpanded. - **/ -class PublicTransportGraphicsItem : public QGraphicsWidget { - Q_OBJECT - Q_PROPERTY( qreal expandStep READ expandStep WRITE setExpandStep ) - Q_PROPERTY( qreal fadeOut READ fadeOut WRITE setFadeOut ) - friend class PublicTransportWidget; - -public: - explicit PublicTransportGraphicsItem( PublicTransportWidget *publicTransportWidget, - QGraphicsItem *parent = 0, - StopAction *copyStopToClipboardAction = 0, - StopAction *showInMapAction = 0 ); - virtual ~PublicTransportGraphicsItem(); - - enum { Type = UserType + 4 }; - virtual int type() const { return Type; }; - - /** - * @brief Get the visible part of the shape of this item. - * - * PublicTransportGraphicsItem's by default clip to their shape, ie. to the painter path - * returned by this function. It intersects the items shape with the currently visible area - * of the PublicTransportWidget that contains this item. - **/ - virtual QPainterPath shape() const; - - /** @brief A pointer to the containing PublicTransportWidget. */ - PublicTransportWidget *publicTransportWidget() const { return m_parent; }; - - /** @brief The QModelIndex to the data for this item. */ - QModelIndex index() const { return m_item ? m_item->index() : QModelIndex(); }; - - /** - * @brief Notifies this item about changed settings in the parent PublicTransportWidget. - * - * The default implementation does nothing. - **/ - virtual void updateSettings() {}; - - virtual QSizeF sizeHint( Qt::SizeHint which, const QSizeF& constraint = QSizeF() ) const; - - /** - * @brief Gets the size of the always shown part (without the expand area). - * - * The default implementation uses the PublicTransportWidget given in the constructor to - * calculate a size, using @ref PublicTransportWidget::iconSize and - * @ref PublicTransportWidget::maxLineCount. - * - * If you overwrite this method you should remember to add @ref padding. - **/ - qreal unexpandedHeight() const; - - static qreal unexpandedHeight( qreal iconSize, qreal padding, - int lineSpacing, int maxLineCount ); - - /** - * @brief The height of route items. - * @return A multiple of unexpandedHeight(). - **/ - qreal routeItemHeight() const; - - /** - * @brief Get the height of the expand area. - * - * Uses the function expandAreaHeightMinimum() which gets overwritten by deriving classes - * and rounds it to multiples of unexpandedHeight(). It also applies the current expanding - * animation step, ie. 0 gets returned at the start of the animation and the full expand - * area height at the end. - **/ - qreal expandAreaHeight() const; - - /** - * @brief Overwrite to calculate the indentation of the expand area. - * - * The default implementation returns 0.0, ie. no indentation. - **/ - virtual qreal expandAreaIndentation() const { return 0.0; }; - - /** - * @brief The extend of additional icons, eg. an alarm icons or journey news hint icons. - * - * The default implementation uses half of @ref PublicTransportWidget::iconSize, making - * additional icons half as big as the vehicle type icons. - **/ - int extraIconSize() const; - - /** - * @brief The padding to the borders of this widget - * - * Scales with @ref PublicTransportWidget::zoomFactor. - **/ - inline qreal padding() const; - - static qreal padding( qreal zoomFactor ); - - /** - * @brief Checks whether or not there is a extra icon in the model in the given @p column. - * - * @param column The column to check for an icon. Default is ColumnTarget. - * - * @return True, if there is an icon in the given @p column, ie. there is a QIcon stored in - * the model for the @ref Qt::DecorationRole. - **/ - bool hasExtraIcon( Columns column = ColumnTarget ) const; - - /** @brief Whether or not this item is valid, ie. it has data for painting. */ - virtual bool isValid() const { return true; }; - - /** @brief Whether or not the expand area is currently shown or not. */ - bool isExpanded() const { return m_expanded; }; - - /** @brief Toggles the expanded state, ie. showing/hiding the expand area. */ - inline void toggleExpanded() { setExpanded(!m_expanded); }; - - /** - * @brief Sets the expanded state to @p expand. - * - * @param expand Whether or not the expand area should be shown. Default is true. - **/ - void setExpanded( bool expand = true ); - - /** - * @brief The amount between 0 and 1 the item is currently expanded. - * - * 0 means not expanded, 1 means fully expanded, everything in between means partly expanded. - **/ - qreal expandStep() const { return m_expandStep; }; - - /** - * @brief Sets the amount between 0 and 1 the item should be expanded to @p expandStep. - * - * This gets used to animate showing/hiding of the expand area. - * - * @param expandStep The new expand amount between 0 and 1. 0 means not expanded, 1 means - * fully expanded, everything in between means partly expanded. - **/ - void setExpandStep( qreal expandStep ) { m_expandStep = expandStep; updateGeometry(); }; - - /** - * @brief The amount between 0 and 1 the item is currently faded out. - * - * 0 means fully shown, 1 means fully faded out, everything in between means partly faded out. - **/ - qreal fadeOut() const { return m_fadeOut; }; - - /** - * @brief Sets the amount between 0 and 1 the item should be faded out to @p fadeOut. - * - * This gets used to animate hiding/deletion of PublicTransportGraphicsItem's. - * - * @param fadeOut The new fade out amount between 0 and 1. 0 means not faded out, 1 means - * fully faded out, everything in between means partly faded out. - **/ - void setFadeOut( qreal fadeOut ) { m_fadeOut = fadeOut; updateGeometry(); }; - - /** - * @brief Paints this item to an internal cache. - * - * The cached pixmap can later be used for drawing, if the model data is already deleted. - * So it's a good idea to call this method directly before the model data gets deleted, eg. - * by connecting to @ref PublicTransportModel::itemsAboutToBeRemoved. - **/ - void capturePixmap(); - - /** - * @brief Calls @ref paintItem and @ref paintExpanded, if expanded. - * - * To draw the item (without the expand area) @ref paintItem gets called always from here. - * If this item is currently expanded (ie. @ref isExpanded returns true) or partly expanded - * (@ref expandStep > 0) @ref paintExpanded gets called to draw to the expand area. - * - * @note You should not need to overwrite this method, but more likely @ref paintItem - * and @ref paintExpanded. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param widget - **/ - virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, - QWidget* widget = 0 ); - - /** - * @brief Called to paint the background of this item, including the expand area (if expanded). - * - * This gets always called on @ref paint. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. It's height changes when the expand area - * gets toggled. - **/ - virtual void paintBackground( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ) = 0; - - /** - * @brief Called to paint this item, without the expand area. - * - * This gets always called on @ref paint. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. - **/ - virtual void paintItem( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ) = 0; - - /** - * @brief Called to paint the expand area. - * - * This gets only called if @ref isExpanded returns true or if @ref expandStep is bigger - * than zero. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. - **/ - virtual void paintExpanded( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ) = 0; - - /** @brief The color to be used for text. */ - virtual QColor textColor() const; - - /** @brief The color to be used as background. */ - virtual QColor backgroundColor() const; - -signals: - void expandedStateChanged( PublicTransportGraphicsItem *item, bool expanded ); - - void requestAlarmCreation( const QDateTime &departure, const QString &lineString, - VehicleType vehicleType, const QString &target, - QGraphicsWidget *item ); - void requestAlarmDeletion( const QDateTime &departure, const QString &lineString, - VehicleType vehicleType, const QString &target, - QGraphicsWidget *item ); - -protected slots: - void resizeAnimationFinished(); - void ensureVisibleSnapped(); - -protected: - virtual void resizeEvent( QGraphicsSceneResizeEvent* event ); - virtual void mousePressEvent( QGraphicsSceneMouseEvent* event ); - virtual void mouseReleaseEvent( QGraphicsSceneMouseEvent* event ); - virtual void updateGeometry(); - virtual void updateTextLayouts() = 0; - virtual QGraphicsWidget *routeItem() const = 0; - virtual void drawFadeOutLeftAndRight( QPainter *painter, const QRectF &rect, int fadeWidth = 40 ); - virtual void drawAlarmBackground( QPainter *painter, const QRectF &rect ); - - /** @brief The minimum size of the expand area. */ - virtual qreal expandAreaHeightMinimum() const = 0; - - /** - * @brief The maximum size of the expand area. - * Used when an item gets expanded to ensure the expanded area is visible, including data that - * may get added later, eg. route data. - **/ - virtual qreal expandAreaHeightMaximum() const { return expandAreaHeightMinimum(); }; - - /** @brief Used by expandAreaHeight(), here without animation step applied. */ - qreal expandAreaHeightTarget() const; - - void setExpandedNotAffectingOtherItems( bool expand = true ); - - QPointer m_item; - PublicTransportWidget *m_parent; - bool m_expanded; - qreal m_expandStep; - qreal m_fadeOut; - QPropertyAnimation *m_resizeAnimation; - QPixmap *m_pixmap; - StopAction *m_copyStopToClipboardAction; - StopAction *m_showInMapAction; - QTimer *m_ensureVisibleTimer; -}; - -class TextDocumentHelper { -public: - enum Option { - DoNotDrawShadowOrHalos, DrawShadows, DrawHalos, DefaultOption = DrawShadows - }; - - static QTextDocument *createTextDocument( const QString &html, const QSizeF &size, - const QTextOption &textOption, const QFont &font ); - static void drawTextDocument( QPainter *painter, const QStyleOptionGraphicsItem* option, - QTextDocument *document, const QRectF &textRect, Option options = DefaultOption ); - static qreal textDocumentWidth( QTextDocument *document ); -}; - -/** - * @brief A QGraphicsWidget representing a departure/arrival with public transport. - * - * It gets normally added into @ref TimetableWidget and can include other items like - * @ref RouteGraphicsItem. - **/ -class DepartureGraphicsItem : public PublicTransportGraphicsItem { - Q_OBJECT - Q_PROPERTY( qreal leavingStep READ leavingStep WRITE setLeavingStep ) - friend class TimetableWidget; - -public: - explicit DepartureGraphicsItem( PublicTransportWidget* publicTransportWidget, - QGraphicsItem* parent = 0, - StopAction *copyStopToClipboardAction = 0, - StopAction *showInMapAction = 0, - StopAction *showDeparturesAction = 0, - StopAction *highlightStopAction = 0, - StopAction *newFilterViaStopAction = 0, - KPixmapCache *pixmapCache = 0 ); - virtual ~DepartureGraphicsItem(); - - enum { Type = UserType + 5 }; - virtual int type() const { return Type; }; - - /** - * @brief Updates this graphics item to visualize the given @p item. - * - * @param item The item with the new data. - * @param update Whether or not to update the layouts of the QTextDocuments. Default is false. - **/ - void updateData( DepartureItem* item, bool update = false ); - - bool isRouteDataAvailable() const; - bool isRouteDataRequestable() const; - - /** @brief Notifies this item about changed settings in the parent PublicTransportWidget. */ - virtual void updateSettings(); - - /** @brief The DepartureItem representing this item in the model. */ - inline DepartureItem *departureItem() const { return qobject_cast(m_item); }; - - /** @brief Whether or not this item is valid, ie. it has data for painting. */ - virtual bool isValid() const { return m_infoTextDocument && m_timeTextDocument; }; - - /** @brief The indentation of the expand area. */ - virtual qreal expandAreaIndentation() const; - - /** - * @brief Paint the background of this item, including the expand area (if expanded). - * - * This gets always called on @ref paint. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. It's height changes when the expand area - * gets toggled. - **/ - virtual void paintBackground( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ); - - /** - * @brief Paint this item, without the expand area. - * - * This gets always called on @ref paint. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. - **/ - virtual void paintItem( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ); - - /** - * @brief Paint the expand area. - * - * This gets only called if @ref isExpanded returns true or if @ref expandStep is bigger - * than zero. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. - **/ - virtual void paintExpanded( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ); - - /** - * @brief Calculates the bounding rect for the vehicle type icon. - * - * @param rect The rect of the item (without the expand area). - **/ - QRectF vehicleRect( const QRectF &rect ) const; - - /** - * @brief Calculates the bounding rect for the info text (line string and target). - * - * @param rect The rect of the item (without the expand area). - * @param timeColumnWidth The width of the time column. - **/ - QRectF infoRect( const QRectF &rect, qreal timeColumnWidth ) const; - - /** - * @brief Calculates the bounding rect for the departure/arrival time text. - * - * @param rect The rect of the item (without the expand area). - **/ - QRectF timeRect( const QRectF &rect ) const; - - /** - * @brief Calculates the bounding rect for an additional icon. - * - * @param rect The rect of the item (without the expand area). - * @param timeColumnWidth The width of the time column. - **/ - QRectF extraIconRect( const QRectF &rect, qreal timeColumnWidth ) const; - - /** - * @brief A value between 0 and 1 to control the leaving animation. - * - * The leaving animation (currently) makes the item fade in and out to show that the associated - * departure is leaving soon. - **/ - qreal leavingStep() const { return m_leavingStep; }; - - /** @brief Set a value between 0 and 1 to control the leaving animation. */ - void setLeavingStep( qreal leavingStep ); - -signals: - /** - * @brief Emitted to request an update of the additional data. - * Can be used eg. when a previous request failed. - */ - void updateAdditionalDataRequest(); - -protected: - virtual void updateTextLayouts(); - QString othersText() const; - qreal timeColumnWidth() const; - virtual void resizeEvent( QGraphicsSceneResizeEvent* event ); - virtual QGraphicsWidget *routeItem() const; - void hideRouteInfoWidget(); - void showRouteInfoWidget( QGraphicsWidget *routeInfoWidget ); - QPointF othersTextPos() const; - virtual void mouseReleaseEvent( QGraphicsSceneMouseEvent *event ); - virtual void hoverMoveEvent( QGraphicsSceneHoverEvent *event ); - - /** @brief The minimum size of the expand area. */ - virtual qreal expandAreaHeightMinimum() const; - - /** @brief The maximum size of the expand area. */ - virtual qreal expandAreaHeightMaximum() const; - -private: - QTextDocument *m_infoTextDocument; - QTextDocument *m_timeTextDocument; - QTextDocument *m_othersTextDocument; - RouteGraphicsItem *m_routeItem; // Pointer to the route item or 0 if no route data is available - QGraphicsWidget *m_routeInfoWidget; - bool m_highlighted; - - QPropertyAnimation *m_leavingAnimation; - qreal m_leavingStep; - - StopAction *m_showDeparturesAction; - StopAction *m_highlightStopAction; - StopAction *m_newFilterViaStopAction; - QAction *m_updateAdditionalDataAction; - - KPixmapCache *m_pixmapCache; -}; - -/** - * @brief A QGraphicsWidget representing a journey with public transport from A to B. - * - * It gets normally added into @ref JourneyTimetableWidget and can include other items like - * @ref JourneyRouteGraphicsItem. - **/ -class JourneyGraphicsItem : public PublicTransportGraphicsItem { - Q_OBJECT - friend class TimetableWidget; - -public: - explicit JourneyGraphicsItem( PublicTransportWidget* publicTransportWidget, - QGraphicsItem* parent = 0, - StopAction *copyStopToClipboardAction = 0, - StopAction *showInMapAction = 0, - StopAction *requestJourneyToStopAction = 0, - StopAction *requestJourneyFromStopAction = 0 ); - virtual ~JourneyGraphicsItem(); - - enum { Type = UserType + 6 }; - virtual int type() const { return Type; }; - - /** - * @brief Updates this graphics item to visualize the given @p item. - * - * @param item The item with the new data. - * @param update Whether or not to update the layout of the QTextDocument. Default is false. - **/ - void updateData( JourneyItem* item, bool update = false ); - - /** @brief Notifies this item about changed settings in the parent PublicTransportWidget. */ - virtual void updateSettings(); - - /** @brief The JourneyItem representing this item in the model. */ - inline JourneyItem *journeyItem() const { return qobject_cast(m_item); }; - - /** @brief Whether or not this item is valid, ie. it has data for painting. */ - virtual bool isValid() const { return m_infoTextDocument; }; - - /** @brief The indentation of the expand area. */ - virtual qreal expandAreaIndentation() const; - - /** - * @brief Paint the background of this item, including the expand area (if expanded). - * - * This gets always called on @ref paint. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. It's heihgt changes when the expand area - * gets toggled. - **/ - virtual void paintBackground( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ); - - /** - * @brief Paint this item, without the expand area. - * - * This gets always called on @ref paint. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. - **/ - virtual void paintItem( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ); - - /** - * @brief Paint the expand area. - * - * This gets only called if @ref isExpanded returns true or if @ref expandStep is bigger - * than zero. - * - * @param painter The painter to use for painting. - * @param option Style options. - * @param rect The rectangle in which to paint. - **/ - virtual void paintExpanded( QPainter* painter, const QStyleOptionGraphicsItem* option, - const QRectF &rect ); - - /** - * @brief Calculates the bounding rect for the vehicle type icon. - * - * @param rect The rect of the item (without the expand area). - **/ - QRectF vehicleRect( const QRectF &rect ) const; - - /** - * @brief Calculates the bounding rect for the info text (departure, arrival, changes, duration). - * - * @param rect The rect of the item (without the expand area). - * @param timeColumnWidth The width of the time column. - **/ - QRectF infoRect( const QRectF &rect ) const; - - /** - * @brief Calculates the bounding rect for an additional icon. - * - * @param rect The rect of the item (without the expand area). - * @param timeColumnWidth The width of the time column. - **/ - QRectF extraIconRect( const QRectF &rect ) const; - -protected: - virtual void resizeEvent( QGraphicsSceneResizeEvent *event ); - virtual void contextMenuEvent( QGraphicsSceneContextMenuEvent *event ); - virtual void updateTextLayouts(); - virtual QGraphicsWidget *routeItem() const; - - /** @brief The minimum size of the expand area. */ - virtual qreal expandAreaHeightMinimum() const; - -private: - QTextDocument *m_infoTextDocument; - JourneyRouteGraphicsItem *m_routeItem; // Pointer to the route item or 0 if no route data is available - StopAction *m_requestJourneyToStopAction; - StopAction *m_requestJourneyFromStopAction; -}; - -/** - * @brief Base class for TimetableWidget and JourneyTimetableWidget. - **/ -class PublicTransportWidget : public Plasma::ScrollWidget -{ - Q_OBJECT - Q_PROPERTY( QString noItemsText READ noItemsText WRITE setNoItemsText ) - friend class PublicTransportGraphicsItem; - friend class DepartureItem; - -public: - enum Option { - NoOption = 0x0000, /**< No special option. */ - DrawShadowsOrHalos = 0x0001, /**< Draw shadows/halos behind text, dependend on color. */ - - DefaultOptions = DrawShadowsOrHalos /**< Options used by default */ - }; - Q_DECLARE_FLAGS( Options, Option ) - - /** @brief Options describing how to expand items. */ - enum ExpandingOption { - NoExpanding = 0, /**< Never expand items. */ - ExpandSingle, /**< Ensure, that only one item is expanded at a time. */ - ExpandMultiple /**< Allow to expand multiple items at the same time. */ - }; - - PublicTransportWidget( Options options = DefaultOptions, - ExpandingOption expandingOption = ExpandSingle, - QGraphicsItem* parent = 0 ); - - /** @brief Gets the model containing the data for this widget. */ - PublicTransportModel *model() const { return m_model; }; - - enum { Type = UserType + 1 }; - virtual int type() const { return Type; }; - - /** @brief Sets the model containing the data for this widget to @p model. */ - void setModel( PublicTransportModel *model ); - - /** @brief Gets the item at the given @p row. */ - PublicTransportGraphicsItem *item( int row ) { return m_items[row]; }; - - /** @brief Gets the item with the given @p index. */ - PublicTransportGraphicsItem *item( const QModelIndex &index ); - - void setExpandOption( ExpandingOption expandingOption ); - ExpandingOption expandingOption() const { return m_expandingOption; }; - - int rowFromItem( PublicTransportGraphicsItem *item ) const; - - bool isItemExpanded( int row ) const; - void setItemExpanded( int row, bool expanded = true ); - inline void toggleItemExpanded( int row ) { setItemExpanded(row, !isItemExpanded(row)); }; - - inline bool isItemExpanded( PublicTransportGraphicsItem *item ) const { - return isItemExpanded(rowFromItem(item)); }; - inline void setItemExpanded( PublicTransportGraphicsItem *item, bool expanded = true ) { - setItemExpanded(rowFromItem(item), expanded); }; - inline void toggleItemExpanded( PublicTransportGraphicsItem *item ) { - setItemExpanded(rowFromItem(item), !item->isExpanded()); }; - - void setSvg( Plasma::Svg *svg ) { m_svg = svg; }; - Plasma::Svg *svg() const { return m_svg; }; - - void setIconSize( qreal iconSize ) { m_iconSize = iconSize; updateItemLayouts(); }; - qreal iconSize() const { return m_maxLineCount == 1 ? m_iconSize * m_zoomFactor * 0.75 - : m_iconSize * m_zoomFactor; }; - - void setZoomFactor( qreal zoomFactor = 1.0 ); - qreal zoomFactor() const { return m_zoomFactor; }; - - Options options() const { return m_options; }; - inline bool isOptionEnabled( Option option ) const { return m_options.testFlag(option); }; - void setOptions( Options options ); - void setOption( Option option, bool enable = true ); - - /** @brief Sets the text shown when this item contains no item (ie. is empty) to @p noItemsText. */ - void setNoItemsText( const QString &noItemsText ) { - m_noItemsText = noItemsText; - update(); - }; - - /** @brief Gets the text shown when this item contains no item, ie. is empty. */ - QString noItemsText() const { return m_noItemsText; }; - - void setMaxLineCount( int maxLineCount ) { m_maxLineCount = maxLineCount; updateItemGeometries(); }; - int maxLineCount() const { return m_maxLineCount; }; - - /** @brief Call this eg. when the DepartureArrivalListType changes in the model - * (only header data gets changed...). */ - void updateItemLayouts(); - - virtual QList< QAction* > contextMenuActions() { return QList(); }; - -signals: - void contextMenuRequested( PublicTransportGraphicsItem *item, const QPointF &pos ); - void expandedStateChanged( PublicTransportGraphicsItem *item, bool expanded ); - - /** - * @brief Emitted, if a stop action was triggered from a route stop's context menu. - * - * @param stopAction The action to execute. - * @param stopName The stop name for which the action should be executed. - **/ - void requestStopAction( StopAction::Type stopAction, const QString &stopName, - const QString &stopNameShortened ); - - void requestAlarmCreation( const QDateTime &departure, const QString &lineString, - VehicleType vehicleType, const QString &target, - QGraphicsWidget *item ); - void requestAlarmDeletion( const QDateTime &departure, const QString &lineString, - VehicleType vehicleType, const QString &target, - QGraphicsWidget *item ); - - void requestEarlierItems(); - void requestLaterItems(); - -protected slots: - void itemsAboutToBeRemoved( const QList &journeys ); - virtual void rowsRemoved( const QModelIndex &parent, int first, int last ); - virtual void modelReset(); - virtual void layoutChanged(); - virtual void dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ) = 0; - -protected: - virtual QSizeF sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const; - virtual void contextMenuEvent( QGraphicsSceneContextMenuEvent *event ); - virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *option, - QWidget *widget = 0 ); - void updateItemGeometries(); - virtual void setupActions(); - void setPrefixItem( TimetableListItem *prefixItem ); - void setPostfixItem( TimetableListItem *postfixItem ); - void updateSnapSize(); - - Options m_options; - ExpandingOption m_expandingOption; - PublicTransportModel *m_model; - TimetableListItem *m_prefixItem; - TimetableListItem *m_postfixItem; - QList m_items; - Plasma::Svg *m_svg; - qreal m_iconSize; - qreal m_zoomFactor; - int m_maxLineCount; - QString m_noItemsText; - bool m_enableOpenStreetMap; // Enable actions using the openstreetmap data engine - StopAction *m_copyStopToClipboardAction; - StopAction *m_showInMapAction; -}; - -/** - * @brief A Plasma::ScrollWidget containing DepartureGraphicsItems. - **/ -class TimetableWidget : public PublicTransportWidget -{ - Q_OBJECT - friend class PublicTransportGraphicsItem; - friend class DepartureGraphicsItem; - -public: - TimetableWidget( Options options = DefaultOptions, - ExpandingOption expandingOption = ExpandSingle, QGraphicsItem* parent = 0 ); - virtual ~TimetableWidget(); - - enum { Type = UserType + 2 }; - virtual int type() const { return Type; }; - - void setTargetHidden( bool targetHidden ) { m_targetHidden = targetHidden; updateItemLayouts(); }; - bool isTargetHidden() const { return m_targetHidden; }; - - /** @brief Gets the departure item at the given @p row. */ - inline DepartureGraphicsItem *departureItem( int row ) { - return qobject_cast(item(row)); - }; - - /** @brief Gets the departure item with the given @p index. */ - inline DepartureGraphicsItem *departureItem( const QModelIndex &index ) { - return qobject_cast(item(index)); - }; - - /** @brief Gets the departure model containing the data for this widget. */ - inline DepartureModel *departureModel() const { - return qobject_cast(m_model); - }; - -protected slots: - virtual void rowsInserted( const QModelIndex &parent, int first, int last ); - virtual void dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ); - -protected: - virtual void contextMenuEvent( QGraphicsSceneContextMenuEvent *event ); - virtual void setupActions(); - -private: - bool m_targetHidden; - StopAction *m_showDeparturesAction; - StopAction *m_highlightStopAction; - StopAction *m_newFilterViaStopAction; - KPixmapCache *m_pixmapCache; -}; - -/** - * @brief A Plasma::ScrollWidget containing JourneyGraphicsItems. - **/ -class JourneyTimetableWidget : public PublicTransportWidget -{ - Q_OBJECT - friend class PublicTransportGraphicsItem; - -public: - enum Flag { - NoFlags = 0x00, - - ShowEarlierJourneysItem = 0x01, - ShowLaterJourneysItem = 0x02, - - ShowEarlierAndLaterJourneysItems = ShowEarlierJourneysItem | ShowLaterJourneysItem - }; - Q_DECLARE_FLAGS( Flags, Flag ) - - JourneyTimetableWidget( Options options = DefaultOptions, - Flags flags = ShowEarlierAndLaterJourneysItems, - ExpandingOption expandingOption = ExpandSingle, - QGraphicsItem* parent = 0 ); - - enum { Type = UserType + 3 }; - virtual int type() const { return Type; }; - - /** @brief Gets the journey item at the given @p row. */ - inline JourneyGraphicsItem *journeyItem( int row ) { - return qobject_cast(item(row)); - }; - - /** @brief Gets the journey item with the given @p index. */ - inline JourneyGraphicsItem *journeyItem( const QModelIndex &index ) { - return qobject_cast(item(index)); - }; - - /** @brief Gets the journey model containing the data for this widget. */ - inline JourneyModel *journeyModel() const { - return qobject_cast(m_model); - }; - - virtual QList< QAction* > contextMenuActions(); - -protected slots: - virtual void rowsInserted( const QModelIndex &parent, int first, int last ); - virtual void dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ); - -protected: - virtual void setupActions(); - -private: - Flags m_flags; - StopAction *m_requestJourneyToStopAction; - StopAction *m_requestJourneyFromStopAction; - QAction *m_earlierAction; - QAction *m_laterAction; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS( JourneyTimetableWidget::Flags ) - -#endif // TIMETABLEWIDGET_H diff --git a/applet/titlewidget.cpp b/applet/titlewidget.cpp deleted file mode 100644 index f7759b9..0000000 --- a/applet/titlewidget.cpp +++ /dev/null @@ -1,656 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "titlewidget.h" - -// Own includes -#include "settings.h" -#include "journeysearchlineedit.h" -#include "journeysearchmodel.h" -#include "journeysearchlistview.h" - -// KDE+Plasma includes -#include -#include -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include - -TitleWidget::TitleWidget( TitleType titleType, Settings *settings, bool journeysSupported, - QGraphicsItem *parent ) - : QGraphicsWidget(parent), m_icon(0), m_filterWidget(0), m_journeysWidget(0), - m_layout(new QGraphicsLinearLayout(Qt::Horizontal, this)), m_settings(settings), - m_journeysSupported(journeysSupported), m_journeysAction(0), m_filtersAction(0) -{ - m_type = titleType; - m_layout->setContentsMargins( 1, 1, 0, 0 ); - m_layout->setSpacing( 1 ); - m_layout->setItemSpacing( 0, 4 ); - - // Initialize icon widget - int iconExtend = 26 * settings->sizeFactor(); - Plasma::IconWidget *icon = new Plasma::IconWidget; - icon->setIcon( "public-transport-stop" ); - icon->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); - icon->setMinimumSize( iconExtend, iconExtend ); - icon->setMaximumSize( iconExtend, iconExtend ); - setIcon( icon ); - - // Add a title label - Plasma::Label *title = new Plasma::Label( this ); - title->setAlignment( Qt::AlignVCenter | Qt::AlignLeft ); - title->setWordWrap( false ); - title->setMinimumWidth( 75 ); - QLabel *_title = title->nativeWidget(); - _title->setTextInteractionFlags( Qt::LinksAccessibleByMouse ); - addWidget( title, WidgetTitle ); - - if ( m_journeysSupported ) { - // Add a quick journey search widget - createAndAddWidget( WidgetQuickJourneySearch ); - } - - // Add a filter widget - createAndAddWidget( WidgetFilter ); - - // Adjust to settings - settingsChanged(); -} - -void TitleWidget::setJourneysSupported( bool supported ) -{ - if ( m_journeysSupported == supported ) { - return; - } - - m_journeysSupported = supported; - if ( supported ) { - createAndAddWidget( WidgetQuickJourneySearch ); - } else { - removeWidget( WidgetQuickJourneySearch ); - delete m_journeysWidget; - m_journeysWidget = 0; - } -} - -QString TitleWidget::title() const -{ - return m_title->text(); -} - -Plasma::Label* TitleWidget::titleWidget() const -{ - return m_title; -} - -void TitleWidget::setTitleType( TitleType titleType, - bool validDepartureData, bool validJourneyData ) -{ - // Remove old additional widgets - clearWidgets(); - - // New type - m_type = titleType; - switch ( titleType ) { - case ShowDepartureArrivalListTitle: - // Default state, a departure/arrival board is shown - setIcon( validDepartureData ? DepartureListOkIcon : DepartureListErrorIcon ); - m_icon->setToolTip( i18nc("@info:tooltip", "Show available actions in the applet") ); - setTitle( titleText() ); - - // Show a title (with the stop name) and the filter and quick journey search widgets - addWidget( m_title, WidgetTitle ); - if ( m_journeysSupported ) { - addWidget( m_journeysWidget, WidgetQuickJourneySearch ); - } - addWidget( m_filterWidget, WidgetFilter ); - break; - - case ShowIntermediateDepartureListTitle: - // An intermediate deparure list is shown - setIcon( GoBackIcon ); - m_icon->setToolTip( i18nc("@info:tooltip", "Go back to original stop") ); - setTitle( titleText() ); - - // Same as for normal departure/arrival boards - addWidget( m_title, WidgetTitle ); - if ( m_journeysSupported ) { - addWidget( m_journeysWidget, WidgetQuickJourneySearch ); - } - addWidget( m_filterWidget, WidgetFilter ); - break; - - case ShowSearchJourneyLineEdit: { - // The journey search UI is shown - setIcon( AbortJourneySearchIcon ); - m_icon->setToolTip( i18nc("@info:tooltip", "Abort search for journeys " - "to or from the home stop" ) ); - - // Add widgets - addJourneySearchWidgets(); - Plasma::LineEdit *journeySearchLine = - castedWidget(WidgetJourneySearchLine); - journeySearchLine->setEnabled( true ); - journeySearchLine->setFocus(); - journeySearchLine->nativeWidget()->selectAll(); - break; - } - case ShowSearchJourneyLineEditDisabled: - // The journey search UI is shown, - // but the currently used service provider does not support journeys - setIcon( AbortJourneySearchIcon ); - m_icon->setToolTip( i18nc("@info:tooltip", "Abort search for journeys " - "to or from the home stop") ); - - // Add widgets - addJourneySearchWidgets(); - - // Disable all widgets, because journeys are not supported by the currently used - // service provider - castedWidget(WidgetJourneySearchLine)->setEnabled( false ); - castedWidget(WidgetFillJourneySearchLineButton)->setEnabled( false ); - castedWidget(WidgetStartJourneySearchButton)->setEnabled( false ); - break; - - case ShowJourneyListTitle: { - // A list of journeys is shown - setIcon( validJourneyData ? JourneyListOkIcon : JourneyListErrorIcon ); - m_icon->setToolTip( i18nc("@info:tooltip", "Show available actions in the applet") ); - - // Add a close icon to close the journey view - int iconExtend = 26 * m_settings->sizeFactor(); - Plasma::IconWidget *closeIcon = new Plasma::IconWidget; - closeIcon->setIcon( "window-close" ); - closeIcon->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); - closeIcon->setMinimumSize( iconExtend, iconExtend ); - closeIcon->setMaximumSize( iconExtend, iconExtend ); - closeIcon->setToolTip( i18nc("@info:tooltip", "Show departures / arrivals") ); - connect( closeIcon, SIGNAL(clicked()), this, SIGNAL(closeIconClicked()) ); - addWidget( closeIcon, WidgetCloseIcon ); - - // Add a title label - addWidget( m_title, WidgetTitle ); - break; - } - } -} - -QString TitleWidget::titleText() const -{ - QString sStops = m_settings->currentStop().stops().join( ", " ); - if ( !m_settings->currentStop().get(CitySetting).isEmpty() ) { - return QString( "%1, %2" ).arg( sStops ) - .arg( m_settings->currentStop().get(CitySetting) ); - } else { - return QString( "%1" ).arg( sStops ); - } -} - -void TitleWidget::slotJourneySearchInputFinished() -{ - Plasma::LineEdit *journeySearch = - castedWidget( TitleWidget::WidgetJourneySearchLine ); - Q_ASSERT( journeySearch ); - - emit journeySearchInputFinished( journeySearch->text() ); -} - -void TitleWidget::addJourneySearchWidgets() -{ - // Add recent journeys button - Plasma::ToolButton *recentJourneysButton = new Plasma::ToolButton; - recentJourneysButton->setIcon( KIcon("document-open-recent") ); - recentJourneysButton->setToolTip( i18nc("@info:tooltip", "Use a favorite/recent journey search") ); - recentJourneysButton->nativeWidget()->setPopupMode( QToolButton::InstantPopup ); - // This is needed, to have the popup menu drawn above other widgets - recentJourneysButton->setZValue( 9999 ); - connect( recentJourneysButton, SIGNAL(clicked()), this, SLOT(slotJourneysIconClicked()) ); - - // Add button to start the journey search - Plasma::ToolButton *journeySearchButton = new Plasma::ToolButton; - journeySearchButton->setIcon( KIcon("edit-find") ); - journeySearchButton->setToolTip( i18nc("@info:tooltip", "Find journeys") ); - journeySearchButton->setEnabled( false ); - connect( journeySearchButton, SIGNAL(clicked()), this, SLOT(slotJourneySearchInputFinished()) ); - - // Add journey search query input field - Plasma::LineEdit *journeySearchLineEdit = new Plasma::LineEdit; - journeySearchLineEdit->setNativeWidget( new JourneySearchLineEdit ); - journeySearchLineEdit->setToolTip( - i18nc("@info:tooltip This should match the localized keywords.", - "Type a target stop or " - "journey request." - "Samples:" - "To target in 15 mins" - "From origin arriving tomorrow at 18:00" - "Target at 6:00 2010-03-07" - "") ); - journeySearchLineEdit->installEventFilter( this ); // Handle up/down keys (selecting stop suggestions) - journeySearchLineEdit->setClearButtonShown( true ); - journeySearchLineEdit->nativeWidget()->setCompletionMode( KGlobalSettings::CompletionAuto ); - journeySearchLineEdit->nativeWidget()->setCompletionModeDisabled( - KGlobalSettings::CompletionMan ); - journeySearchLineEdit->nativeWidget()->setCompletionModeDisabled( - KGlobalSettings::CompletionPopup ); - journeySearchLineEdit->nativeWidget()->setCompletionModeDisabled( - KGlobalSettings::CompletionPopupAuto ); - journeySearchLineEdit->nativeWidget()->setCompletionModeDisabled( - KGlobalSettings::CompletionShell ); - journeySearchLineEdit->setEnabled( true ); - - KLineEdit *journeySearch = journeySearchLineEdit->nativeWidget(); - journeySearch->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); - journeySearch->setClickMessage( i18nc("@info/plain", "Target stop name or journey request") ); - KCompletion *completion = journeySearch->completionObject( false ); - completion->setIgnoreCase( true ); - journeySearchLineEdit->setFont( m_settings->sizedFont() ); - connect( journeySearchLineEdit, SIGNAL(returnPressed()), - this, SLOT(slotJourneySearchInputFinished()) ); - connect( journeySearchLineEdit, SIGNAL(textEdited(QString)), - this, SIGNAL(journeySearchInputEdited(QString)) ); - connect( journeySearchLineEdit, SIGNAL(textChanged(QString)), - this, SLOT(slotJourneySearchInputChanged(QString)) ); - - // Add widgets - addWidget( journeySearchLineEdit, WidgetJourneySearchLine ); - addWidget( recentJourneysButton, WidgetFillJourneySearchLineButton ); - addWidget( journeySearchButton, WidgetStartJourneySearchButton ); -} - -void TitleWidget::slotFilterIconClicked() -{ - KActionMenu *filtersAction = qobject_cast( m_filtersAction ); - Q_ASSERT( filtersAction ); - - // Show the journeys menu under the journey search icon - filtersAction->menu()->exec( QCursor::pos() ); -} - -void TitleWidget::slotJourneysIconClicked() -{ - KActionMenu *journeysAction = qobject_cast( m_journeysAction ); - Q_ASSERT( journeysAction ); - - // Show the journeys menu under the journey search icon - journeysAction->menu()->exec( QCursor::pos() ); -} - -void TitleWidget::setJourneySearch( const QString &journeySearch ) -{ - Plasma::LineEdit* journeySearchLine = castedWidget( WidgetJourneySearchLine ); - if ( journeySearchLine ) { - journeySearchLine->setText( journeySearch ); - journeySearchLine->setFocus(); - } -} - -void TitleWidget::removeJourneySearchWidgets() -{ - removeWidget( WidgetStartJourneySearchButton ); - removeWidget( WidgetJourneySearchLine ); - removeWidget( WidgetFillJourneySearchLineButton ); -} - -void TitleWidget::setTitle( const QString &title ) -{ - m_titleText = title; - updateTitle(); -} - -void TitleWidget::setIcon( Plasma::IconWidget *icon ) -{ - if ( m_icon ) { - m_layout->removeItem(m_icon); - delete m_icon; - } - - m_icon = icon; - connect( icon, SIGNAL(clicked()), this, SIGNAL(iconClicked()) ); - m_layout->insertItem( 0, m_icon ); -} - -void TitleWidget::setIcon( MainIconDisplay iconType ) -{ - Q_ASSERT( m_icon ); - - KIcon icon; - KIconEffect iconEffect; - QPixmap pixmap; - int iconExtend = m_icon->size().width(); - - // Create an icon of the given type. - switch ( iconType ) { - case DepartureListErrorIcon: { - // Create an icon to be shown on errors with a departure/arrival board - QList overlays; - if ( m_settings->departureArrivalListType() == DepartureList ) { - // Use a public transport stop icon with a house and an arrow away from it - // to indicate that a departure list is shown - overlays << KIcon("go-home") << KIcon("go-next"); - } else { - // Use a public transport stop icon with a house and an arrow towards it - // to indicate that an arrival list is shown - overlays << KIcon("go-next") << KIcon("go-home"); - } - icon = GlobalApplet::makeOverlayIcon( KIcon("public-transport-stop"), - overlays, QSize(iconExtend / 2, iconExtend / 2), iconExtend ); - pixmap = icon.pixmap( iconExtend ); - pixmap = iconEffect.apply( pixmap, KIconLoader::Small, KIconLoader::DisabledState ); - icon = KIcon(); - icon.addPixmap( pixmap, QIcon::Normal ); - break; - } - case DepartureListOkIcon: { - // Create an icon to be shown for departure/arrival boards without errors. - // This icon is the same as the departure error icon, but without the icon effect - QList overlays; - if ( m_settings->departureArrivalListType() == DepartureList ) { - overlays << KIcon("go-home") << KIcon("go-next"); - } else { - overlays << KIcon("go-next") << KIcon("go-home"); - } - icon = GlobalApplet::makeOverlayIcon( KIcon("public-transport-stop"), overlays, - QSize( iconExtend / 2, iconExtend / 2 ), iconExtend ); - break; - } - case JourneyListOkIcon: - // Create an icon to be shown for journey lists without errors. - // This icon is the same as the journey error icon, but without the icon effect - icon = GlobalApplet::makeOverlayIcon( KIcon("public-transport-stop"), - QList() << KIcon("go-home") - << KIcon("go-next-view") << KIcon("public-transport-stop"), - QSize(iconExtend / 3, iconExtend / 3), iconExtend ); - break; - - case JourneyListErrorIcon: - // Create an icon to be shown on errors with a journey list - icon = GlobalApplet::makeOverlayIcon( KIcon("public-transport-stop"), - QList() << KIcon("go-home") - << KIcon("go-next-view") << KIcon("public-transport-stop"), - QSize(iconExtend / 3, iconExtend / 3), iconExtend ); - pixmap = icon.pixmap( iconExtend ); - pixmap = iconEffect.apply( pixmap, KIconLoader::Small, KIconLoader::DisabledState ); - icon = KIcon(); - icon.addPixmap( pixmap, QIcon::Normal ); - break; - - case AbortJourneySearchIcon: - // Create an icon to be shown for the journey search - icon = KIcon("edit-delete"); - break; - - case GoBackIcon: - // Create an icon to be used for "go back" functionality - icon = KIcon("arrow-left"); - break; - } - - m_icon->setIcon( icon ); -} - -QGraphicsWidget* TitleWidget::createAndAddWidget( TitleWidget::WidgetType widgetType ) -{ - switch ( widgetType ) { - case WidgetFilter: - if ( !m_filterWidget ) { - // Create the filter widget showing the currently active filters - m_filterWidget = new Plasma::ToolButton( this ); - m_filterWidget->setIcon( KIcon("view-filter") ); - m_filterWidget->setToolTip( i18nc("@info:tooltip", - "Shows a menu that allows to toggle filters / color groups") ); - m_filterWidget->nativeWidget()->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); - connect( m_filterWidget, SIGNAL(clicked()), this, SLOT(slotFilterIconClicked()) ); - addWidget( m_filterWidget, WidgetFilter ); - } - - updateFilterWidget(); - return m_filterWidget; - - case WidgetQuickJourneySearch: - if ( !m_journeysWidget ) { - // Create the filter widget showing the currently active filters - m_journeysWidget = new Plasma::ToolButton( this ); - m_journeysWidget->setIcon( KIcon("edit-find") ); - m_journeysWidget->setText( i18nc("@action:button", "Quick Journey Search") ); - m_journeysWidget->setToolTip( i18nc("@info:tooltip", - "Shows a menu with favorite/recent journey search items") ); - m_journeysWidget->nativeWidget()->setToolButtonStyle( Qt::ToolButtonIconOnly ); - m_journeysWidget->setMaximumWidth( m_journeysWidget->size().height() ); - connect( m_journeysWidget, SIGNAL(clicked()), - this, SLOT(slotJourneysIconClicked()) ); - addWidget( m_journeysWidget, WidgetQuickJourneySearch ); - } - - return m_journeysWidget; - - default: - Q_ASSERT( false ); - return 0; - } -} - -void TitleWidget::addWidget( QGraphicsWidget *widget, WidgetType widgetType ) -{ - if ( m_widgets.contains(widgetType) ) { - // Widget is already created and in the widget list - widget->show(); - return; - } - - if ( widgetType == WidgetTitle ) { - m_title = qgraphicsitem_cast( widget ); - m_layout->insertItem( 1, widget ); - } else if ( widgetType == WidgetQuickJourneySearch && m_filterWidget ) { - m_layout->insertItem( 2, widget ); - m_layout->setAlignment( widget, Qt::AlignVCenter | Qt::AlignLeft ); - } else { - m_layout->addItem( widget ); - m_layout->setAlignment( widget, Qt::AlignVCenter | Qt::AlignLeft ); - } - m_widgets.insert( widgetType, widget ); - widget->show(); -} - -bool TitleWidget::removeWidget( TitleWidget::WidgetType widgetType, RemoveWidgetOptions options ) -{ - if ( !m_widgets.contains(widgetType) ) { - return false; - } - - if ( widgetType == WidgetFilter || widgetType == WidgetQuickJourneySearch || - widgetType == WidgetTitle ) - { - // Don't delete widgets that are also stored in a member variable - // (m_filterWidget, m_journeySearchWidget, m_title) - options ^= DeleteWidget; - options |= HideAndRemoveWidget; - } - - QGraphicsWidget *widget; - if ( options.testFlag(RemoveWidget) || options.testFlag(DeleteWidget) ) { - widget = m_widgets.take(widgetType); - m_layout->removeItem(widget); - } else { - widget = m_widgets[widgetType]; - } - if ( !widget ) { - // A null value is stored in m_widgets, can happen if a default value gets constructed - kDebug() << "Null value stored for widget type" << widgetType << "This can happen if a " - "default value gets constructed for that widget type, ie. when a widget of that " - "type gets requested from TitleWidget::m_widgets without checking if it is contained."; - m_widgets.remove( widgetType ); - return false; - } - - if ( options == DeleteWidget ) { - widget->deleteLater(); - } else if ( options.testFlag(HideWidget) ) { - widget->hide(); - } - return true; -} - -void TitleWidget::clearWidgets() -{ - while ( !m_widgets.isEmpty() ) { - removeWidget( m_widgets.keys().first() ); - } -} - -void TitleWidget::updateFilterWidget() -{ - FilterSettingsList filters = m_settings->currentFilters(); - ColorGroupSettingsList colorGroups = m_settings->currentColorGroups(); - ColorGroupSettingsList disabledColorGroups; - foreach ( const ColorGroupSettings &colorGroup, colorGroups ) { - if ( colorGroup.filterOut ) { - disabledColorGroups << colorGroup; - } - } - if ( filters.isEmpty() && disabledColorGroups.isEmpty() ) { - m_filterWidget->setOpacity( 0.6 ); - m_filterWidget->setText( i18nc("@info/plain Shown in the applet to indicate that no " - "filters are currently active", "(No active filter)") ); - // Do not show any text, if no filter is active - m_filterWidget->nativeWidget()->setToolButtonStyle( Qt::ToolButtonIconOnly ); - m_filterWidget->setIcon( KIcon("view-filter") ); - } else { - QFontMetrics fm( m_filterWidget->font() ); - QString text; - m_filterWidget->nativeWidget()->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); - if ( filters.count() == 1 && disabledColorGroups.isEmpty() ) { - text = fm.elidedText( filters.first().name, - Qt::ElideRight, boundingRect().width() * 0.45 ); - m_filterWidget->setIcon( KIcon("view-filter") ); - } else if ( filters.count() > 1 && disabledColorGroups.isEmpty() ) { - text = fm.elidedText( i18ncp("@info/plain", "%1 active filter", "%1 active filters", - filters.count()), - Qt::ElideRight, boundingRect().width() * 0.45 ); - m_filterWidget->setIcon( KIcon("object-group") ); - } else if ( filters.isEmpty() && disabledColorGroups.count() >= 1 ) { - text = fm.elidedText( i18ncp("@info/plain", "%1 disabled color group", - "%1 disabled color groups", disabledColorGroups.count()), - Qt::ElideRight, boundingRect().width() * 0.45 ); - m_filterWidget->setIcon( KIcon("object-group") ); - } else { - text = fm.elidedText( i18ncp("@info/plain", "%1 active (color) filter", - "%1 active (color) filters", - filters.count() + disabledColorGroups.count()), - Qt::ElideRight, boundingRect().width() * 0.45 ); - m_filterWidget->setIcon( KIcon("view-filter") ); - } - - m_filterWidget->setOpacity( 1 ); - m_filterWidget->setText( text ); - } -} - -void TitleWidget::slotJourneySearchInputChanged( const QString &text ) -{ - // Disable start search button if the journey search line is empty - Plasma::ToolButton *startJourneySearchButton = - castedWidget( WidgetStartJourneySearchButton ); - if ( startJourneySearchButton ) { - startJourneySearchButton->setEnabled( !text.isEmpty() ); - } -} - -void TitleWidget::settingsChanged() -{ - int mainIconExtend = qCeil(26 * m_settings->sizeFactor()); - m_icon->setMinimumSize( mainIconExtend, mainIconExtend ); - m_icon->setMaximumSize( mainIconExtend, mainIconExtend ); - - QFont font = m_settings->sizedFont(); - QFont boldFont = font; - boldFont.setBold( true ); - m_title->setFont( boldFont ); - - if ( m_filterWidget ) { - m_filterWidget->setFont( font ); - } - if ( m_journeysWidget ) { - m_journeysWidget->setFont( font ); - } - - if ( m_type == ShowDepartureArrivalListTitle - || m_type == ShowIntermediateDepartureListTitle ) - { - setTitle( titleText() ); - } -} - -void TitleWidget::resizeEvent( QGraphicsSceneResizeEvent *event ) -{ - QGraphicsWidget::resizeEvent( event ); - - m_title->setMinimumWidth( qMin(100, int(event->newSize().width() / 3)) ); - updateTitle(); -} - -void TitleWidget::updateTitle() -{ - qreal maxStopNameWidth = contentsRect().width() - m_icon->boundingRect().right() - 10; - if ( m_filterWidget ) { - maxStopNameWidth -= m_filterWidget->boundingRect().width(); - } - if ( m_journeysWidget ) { - maxStopNameWidth -= m_journeysWidget->boundingRect().width(); - } - - if ( !m_titleText.contains(QRegExp("<\\/?[^>]+>")) ) { - const QFontMetrics fm( m_title->font() ); - QString _title = m_titleText; - const int width = fm.width( _title ); - if ( width > maxStopNameWidth ) { - // Try to break into two lines - const int wrapPos = _title.lastIndexOf( ' ', _title.length() / 2 ); - QStringList lines; - if ( wrapPos == -1 ) { - lines << _title; - } else { - lines << _title.left( wrapPos ) << _title.mid( wrapPos + 1); - } - - // Elide each text line and construct new title - _title.clear(); - foreach ( const QString &line, lines ) { - if ( !_title.isEmpty() ) { - _title.append( "
" ); - } - _title.append( fm.elidedText(line, Qt::ElideRight, maxStopNameWidth) ); - } - } - m_title->setText( _title ); - } else { - m_title->setText( m_titleText ); - } -} diff --git a/applet/titlewidget.h b/applet/titlewidget.h deleted file mode 100644 index be9ba2e..0000000 --- a/applet/titlewidget.h +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file - * @brief This file contains the TitleWidget class that is used for the title of the public transport applet. - * @author Friedrich Pülz */ - -#ifndef TITLEWIDGET_H -#define TITLEWIDGET_H - -// Own includes -#include "global.h" -#include "journeysearchitem.h" - -// Qt includes -#include - -class JourneySearchModel; -class KMenu; -class Settings; -class QGraphicsLinearLayout; -class QMenu; -namespace Plasma { - class IconWidget; - class Label; - class ToolButton; -} - -/** - * @brief Represents the widget used as title for the publictransport applet. - * - * It uses a linear horizontal layout. The first item is always a Plasma::IconWidget. After that - * a title widget is shown (Plasma::Label). The title can easily be changed using @ref setTitle. - * Additional widgets are added using @ref addWidget and removed with @ref removeWidget. - * You can also clear all additional widgets using @ref clearWidgets. - * The widget can switch between different layouts (different sets of widgets shown) using - * @ref setTitleType. - * - * @since 0.9.1 - **/ -class TitleWidget : public QGraphicsWidget -{ - Q_OBJECT - -public: - /** - * @brief The types of additional widgets. - * @note The main icon widget isn't contained in this list and handled separately, because - * it's always visible. - **/ - enum WidgetType { - WidgetTitle = 0x0001, /**< The title widget, a Plasma::Label. */ - WidgetFilter = 0x0002, /**< The filter widget, a Plasma::ToolButton. */ - WidgetQuickJourneySearch = 0x0004, /**< The quick journey search button, - * a Plasma::ToolButton. */ - WidgetJourneySearchLine = 0x0010, /**< The journey search edit box, a Plasma::LineEdit. */ - WidgetFillJourneySearchLineButton = 0x0020, /**< The button to fill the journey search - * line with a favorite/recent journey search, a Plasma::ToolButton. */ - WidgetStartJourneySearchButton = 0x0040, /**< The button to start a journey search, - * a Plasma::ToolButton. */ - WidgetCloseIcon = 0x0080, /**< The icon to close the current view (eg. the journey search), - * a Plasma::IconWidget. */ - WidgetUser = 0x1000 /**< The first unused number in the Widget enum. Can be used for - * custom types (should be of type QGraphicsWidget). */ - }; - Q_DECLARE_FLAGS(WidgetTypes, WidgetType) - - /** - * @brief Options for removing widgets. - * @see removeWidget - **/ - enum RemoveWidgetOption { - DeleteWidget = 0x0000, /**< Delete and remove the widget. */ - HideWidget = 0x0001, /**< Hide the widget. */ - RemoveWidget = 0x0002, /**< Only remove the widget without deleting or hiding it. */ - HideAndRemoveWidget = HideWidget | RemoveWidget /**< Combination of HideWidget and RemoveWidget. */ - }; - Q_DECLARE_FLAGS(RemoveWidgetOptions, RemoveWidgetOption) - - /** - * @brief Creates a new title widget. - * - * @param titleType The initial type of this title widget. - * @param settings A pointer to the settings object of the applet. - * @param journeysSupported Whether or not journeys are supported by the current - * service provider. - * @param parent The parent QGraphicsItem. Defaults to 0. - **/ - TitleWidget( TitleType titleType, Settings *settings, bool journeysSupported = false, - QGraphicsItem* parent = 0 ); - - /** @brief Gets the current type of this title widget. */ - TitleType titleType() const { return m_type; }; - - /** @brief Gets the main icon widget. */ - Plasma::IconWidget* icon() const { return m_icon; }; - - /** @brief Gets the title string. */ - QString title() const; - - /** @brief Gets the title widget. */ - Plasma::Label *titleWidget() const; - - /** @brief Gets additional widgets used for the current @ref titleType. */ - QList widgets() const { return m_widgets.values(); }; - - /** - * @brief Returns the additional widget at @p index, which is of type T*. - * - * @param widgetType The type of the additional widget to get. - * @return The additional widget of type @p widgetType, casted to type T*. It uses - * qgraphicsitem_cast, so it returns 0, if the widget isn't of the given type. - **/ - template - T* castedWidget( WidgetType widgetType ) const { - return !m_widgets.contains(widgetType) ? 0 : qgraphicsitem_cast( m_widgets[widgetType] ); - } - - /** - * @brief Sets the type of this title widget to @p titleType. - * - * @param titleType The new title type. - * @param validDepartureData Whether or not valid departure data has been received. - * @param validJourneyData Whether or not valid journey data has been received. - **/ - void setTitleType( TitleType titleType, bool validDepartureData = false, - bool validJourneyData = false ); - - /** - * @brief Sets the title. - * - * @param title The new title string. - **/ - void setTitle( const QString &title ); - - /** - * @brief Changes the icon of the icon widget to the given @p iconType. - * - * @param iconType The new icon type for the icon widget. - **/ - void setIcon( MainIconDisplay iconType ); - - /** @brief Adds a new @p widget of the given @p widgetType to this title item. */ - void addWidget( QGraphicsWidget *widget, WidgetType widgetType ); - - /** @brief Removes the widget of the given @p widgetType. - * - * @param widgetType The type of the widget to be removed. - * @param options Options for removing the widget, ie. whether or not to delete the widget - * after removing it from the layout. Widget of type @ref WidgetFilter and @ref WidgetTitle - * will never be deleted. - * @returns True on success. Otherwise, false (eg. if there is no widget of the - * given @p widgetType). */ - bool removeWidget( WidgetType widgetType, RemoveWidgetOptions options = DeleteWidget ); - - /** @brief Removes and deletes all additional widgets, ie. not the icon and title widgets. */ - void clearWidgets(); - - QGraphicsWidget *createAndAddWidget( WidgetType widgetType ); - - /** - * @brief Sets the filter action to use for the filter widget. - * - * It is expected that @p filterAction is of type KActionMenu, it's menu gets used. - **/ - void setFiltersAction( QAction *filtersAction ) { - m_filtersAction = filtersAction; - }; - - /** - * @brief Sets the journey action to use for the journey widget. - * - * It is expected that @p journeysAction is of type KActionMenu, it's menu gets used. - **/ - void setJourneysAction( QAction *journeysAction ) { - m_journeysAction = journeysAction; - }; - - /** - * @brief Creates/deletes widgets for journeys. - * - * If @p supported is false, all widgets associated with journey functionality will not be used. - * @param supported Whether or not journeys are supported. Default is true. - **/ - void setJourneysSupported( bool supported = true ); - - /** - * @brief Sets the current journey search input to @p journeySearch. - * - * This only works when in journey search mode. Otherwise this function does nothing. - * @param journeySearch The new journey search string. - **/ - void setJourneySearch( const QString &journeySearch ); - -signals: - /** @brief The icon widget was clicked. */ - void iconClicked(); - - /** @brief The widget in the additional widget list with type @ref WidgetCloseIcon was clicked. */ - void closeIconClicked(); - - /** @brief The widget in the additional widget list with type @ref WidgetFilter was clicked. */ - void filterIconClicked(); - - /** @brief The widget in the additional widget list with type @ref WidgetJourneySearch was clicked. */ - void journeySearchIconClicked(); - - /** @brief The recent journeys button in journey search view was clicked. */ - void recentJourneysIconClicked(); - - /** - * @brief The widget of type WidgetJourneySearchButton was clicked or enter was pressed - * in widget of type @ref WidgetJourneySearchLine. - * - * @param text The finished journey search text. - **/ - void journeySearchInputFinished( const QString &text ); - - /** - * @brief The text of the widget of type @ref WidgetJourneySearchLine has changed. - * - * @param text The new text. - **/ - void journeySearchInputEdited( const QString &text ); - - // TODO Unused, remove? - void journeySearchListUpdated( const QList &newJourneySearches ); - -public slots: - /** @brief Updates the filter widget based on the current applet settings. */ - void updateFilterWidget(); - - /** @brief Call this when the applet settings have changed. */ - void settingsChanged(); - -protected slots: - void slotJourneySearchInputChanged( const QString &text ); - void slotJourneySearchInputFinished(); - void slotFilterIconClicked(); - void slotJourneysIconClicked(); - -protected: - /** - * @brief Sets the icon widget and deletes the old one. - * - * @param icon The new icon widget. - **/ - void setIcon( Plasma::IconWidget *icon ); - - /** @brief Adds widgets used for the journey search title type. */ - void addJourneySearchWidgets(); - - /** @brief Remove widgets used for the journey search title type. */ - void removeJourneySearchWidgets(); - - /** @brief Gets a new title string based on the current applet settings. */ - QString titleText() const; - - virtual void resizeEvent( QGraphicsSceneResizeEvent* event ); - void updateTitle(); - - TitleType m_type; - Plasma::IconWidget *m_icon; - Plasma::Label *m_title; - Plasma::ToolButton *m_filterWidget; - Plasma::ToolButton *m_journeysWidget; - QHash< WidgetType, QGraphicsWidget* > m_widgets; - QGraphicsLinearLayout *m_layout; - Settings *m_settings; - QString m_titleText; - bool m_journeysSupported; - QAction *m_journeysAction; - QAction *m_filtersAction; -}; -Q_DECLARE_OPERATORS_FOR_FLAGS(TitleWidget::WidgetTypes) -Q_DECLARE_OPERATORS_FOR_FLAGS(TitleWidget::RemoveWidgetOptions) - -#endif // TITLEWIDGET_H diff --git a/applet/vehicles.svg b/applet/vehicles.svg deleted file mode 100644 index b12a007..0000000 --- a/applet/vehicles.svg +++ /dev/null @@ -1,3338 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - Bus - - - - - - - S - - - - - - - Tram - - - - - - - U - - - - - - - Tro - - - - - - - M - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - H - - - - - - - - - - - - - - - - - Tro - - - - - - Bus - - - - - - - - S - - - - - - - - - U - - - - - - - - M - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/engine-openstreetmap/AUTHORS b/engine-openstreetmap/AUTHORS deleted file mode 100644 index db15527..0000000 --- a/engine-openstreetmap/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Friedrich Pülz, fpuelz@gmx.de \ No newline at end of file diff --git a/engine-openstreetmap/CHANGELOG b/engine-openstreetmap/CHANGELOG deleted file mode 100644 index a27575e..0000000 --- a/engine-openstreetmap/CHANGELOG +++ /dev/null @@ -1,15 +0,0 @@ - -Changelog of plasma-engine-openstreetmap - -0.1.2 -- Fix openstreepmap source URL, now use jxapi, the xapi.openstreetmap.org-server is overloaded/dead? -- New data source: "getCoords [element] [name]" searches for osm nodes with the given name, element can be eg. "publictransportstops". - -0.1.1 -- Only return data with a "name" tag for "publictransportstops" data. -- BugFix: ""-tags where read until "" instead of "". - -0.1 -- Fixed a crash when the XML reader is finished, but more data arrives. -- Less memory consumption in the XML reader (don't store all data, only the data for the current chunk). -- Added a key to the data ("finished") which is true, once all data has been read. diff --git a/engine-openstreetmap/CMakeLists.txt b/engine-openstreetmap/CMakeLists.txt deleted file mode 100644 index c650c3a..0000000 --- a/engine-openstreetmap/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -project( plasma-engine-openstreetmap ) - -# Find the required Libaries -find_package( KDE4 REQUIRED ) -include( KDE4Defaults ) - -add_definitions( ${QT_DEFINITIONS} ${KDE4_DEFINITIONS} ) -include_directories( - ${CMAKE_SOURCE_DIR} - ${CMAKE_BINARY_DIR} - ${KDE4_INCLUDES} ) - -# We add our source code here -set( openstreetmapdataengine_SRCS openstreetmapdataengine.cpp - osmreader.cpp ) - -# Now make sure all files get to the right place -kde4_add_plugin( plasma_engine_openstreetmap ${openstreetmapdataengine_SRCS} ) -target_link_libraries( plasma_engine_openstreetmap - ${KDE4_PLASMA_LIBS} ${KDE4_KDEUI_LIBS} kio ) - -install( TARGETS plasma_engine_openstreetmap - DESTINATION ${PLUGIN_INSTALL_DIR} ) - -install( FILES plasma-engine-openstreetmap.desktop - DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/engine-openstreetmap/COPYING b/engine-openstreetmap/COPYING deleted file mode 100644 index 54c5c69..0000000 --- a/engine-openstreetmap/COPYING +++ /dev/null @@ -1,341 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - PublicTransport - This is a plasma applet that displays a departure/arrival board - for your stop. Plasma is part of KDE. - Copyright (C) 2010 Friedrich Pülz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/engine-openstreetmap/openstreetmapdataengine.cpp b/engine-openstreetmap/openstreetmapdataengine.cpp deleted file mode 100644 index 109ae31..0000000 --- a/engine-openstreetmap/openstreetmapdataengine.cpp +++ /dev/null @@ -1,312 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#include "openstreetmapdataengine.h" - -#include -#include -#include -#include - -const double OpenStreetMapEngine::maxAreaSize = 0.5; -const double OpenStreetMapEngine::defautAreaSize = 0.02; - -OpenStreetMapEngine::OpenStreetMapEngine( QObject *parent, const QVariantList &args ) - : Plasma::DataEngine(parent, args) { - // Update maximally every 5 mins, openstreetmap data doesn't change too much - setMinimumPollingInterval( 300000 ); - - // Fill list of 'short filters'. - // TODO: Maybe plurals are better here? - m_shortFilter.insert( "bank", Filter(Node, "amenity=bank") ); - m_shortFilter.insert( "cafe", Filter(Node, "amenity=cafe") ); - m_shortFilter.insert( "cinema", Filter(Node, "amenity=cinema") ); - m_shortFilter.insert( "college", Filter(Node, "amenity=college") ); - m_shortFilter.insert( "fastfood", Filter(Node, "amenity=fast_food") ); - m_shortFilter.insert( "hospital", Filter(Node, "amenity=hospital") ); - m_shortFilter.insert( "library", Filter(Node, "amenity=library") ); - m_shortFilter.insert( "nightclub", Filter(Node, "amenity=nightclub") ); - m_shortFilter.insert( "parking", Filter(Node, "amenity=parking") ); - m_shortFilter.insert( "pharmacy", Filter(Node, "amenity=pharmacy") ); - m_shortFilter.insert( "placeofworship", Filter(Node, "amenity=place_of_worship") ); - m_shortFilter.insert( "police", Filter(Node, "amenity=police") ); - m_shortFilter.insert( "postbox", Filter(Node, "amenity=post_box") ); - m_shortFilter.insert( "postoffice", Filter(Node, "amenity=post_office") ); - m_shortFilter.insert( "pub", Filter(Node, "amenity=pub") ); - m_shortFilter.insert( "publicbuilding", Filter(Node, "amenity=public_building") ); - m_shortFilter.insert( "restaurant", Filter(Node, "amenity=restaurant") ); - m_shortFilter.insert( "school", Filter(Node, "amenity=school") ); - m_shortFilter.insert( "telephone", Filter(Node, "amenity=telephone") ); - m_shortFilter.insert( "theatre", Filter(Node, "amenity=theatre") ); - m_shortFilter.insert( "toilets", Filter(Node, "amenity=toilets") ); - m_shortFilter.insert( "townhall", Filter(Node, "amenity=townhall") ); - m_shortFilter.insert( "university", Filter(Node, "amenity=university") ); - - m_shortFilter.insert( "water", Filter(Node, "natural=water") ); - m_shortFilter.insert( "forest", Filter(Node, "natural=forest") ); - m_shortFilter.insert( "park", Filter(Node, "natural=park") ); - -// m_shortFilter.insert( "publictransportstops", Filter(Relation, "public_transport=stop_area") ); - m_shortFilter.insert( "publictransportstops", Filter(Node, "public_transport=*") ); //stop_position|platform -} - -bool OpenStreetMapEngine::sourceRequestEvent( const QString& source ) { -// kDebug() << "Request" << source; - setData( source, DataEngine::Data() ); // Create source, TODO: check if [source] is valid? - return updateSourceEvent( source ); -} - -bool OpenStreetMapEngine::updateSourceEvent( const QString& source ) { - foreach ( JobInfo jobInfo, m_jobInfos ) { - if ( jobInfo.sourceName == source ) { - kDebug() << "Source gets already updated" << source; - return true; - } - } - kDebug() << "Update" << source; - - // Parse the source name - // "[longitude],[latitude] ([mapArea]) ([element] [filter]|[short-filter])" - // mapArea should be < 0.5, otherwise parsing could take some time. - int pos = source.indexOf( " " ); - int pos2 = source.indexOf( " ", pos + 1 ); - if ( pos == -1 ) { - return false; - } - - QString osmUrl; - OsmReader::ResultFlags resultFlags = OsmReader::AllResults; - - // Special source to get the coordinates of something - // "getCoords publictransportstops Pappelstraße" - if ( source.startsWith(QLatin1String("getCoords "), Qt::CaseInsensitive) ) { - QString maybeElement = source.mid( pos + 1, pos2 - pos - 1 ).toLower(); - QString element, search, sFilter; - int pos3 = source.indexOf( " ", pos2 + 1 ); - if ( m_shortFilter.contains(maybeElement) ) { - // Replace 'short filters', like "hospital" -> "amenity=hospital" (with element="node") - Filter filter = m_shortFilter[ maybeElement ]; - element = elementToString( filter.element ); - sFilter = filter.filter; - - search = source.mid( pos2 + 1 ).trimmed(); - } else { - // A custom filter - element = maybeElement; - if ( pos3 == -1 ) { - kDebug() << "No search string given"; - return false; - } else { - sFilter = source.mid( pos2 + 1, pos3 - pos2 ).trimmed(); - search = source.mid( pos3 + 1 ).trimmed(); - } - } - - if ( search.isEmpty() ) { - kDebug() << "No search string given"; - return false; - } else if ( search.contains(QLatin1String("Hauptbahnhof"), Qt::CaseInsensitive) ) { - QString alternativeSearch = search; - alternativeSearch.replace( QLatin1String("Hauptbahnhof"), QLatin1String("Hbf"), - Qt::CaseInsensitive ); - search += '|' + alternativeSearch; - } - - // Build url - osmUrl = QString( "%1%2[%3][name=%4]" ) - .arg( baseUrl(), element, sFilter, search ); - kDebug() << "URL:" << osmUrl; -// "http://jxapi.openstreetmap.org/xapi/api/0.6/node[public_transport=stop_position][name=Huckelriede]" - } else { - // First comes latitude,longitute - QStringList values = source.left( pos ).split( ',' ); - if ( values.count() != 2 ) { - return false; - } - double latitude = values[0].toDouble(); // Latitude first - double longitude = values[1].toDouble(); - double mapBoxSize; // Size of the area, in which to search - - // Then the size of the area to search in - // or the elements to filter, which can be "node", "way" or "relation". - // Can also be a 'short filter', see below (search area and element omitted) - QString maybeElement = source.mid( pos + 1, pos2 - pos - 1 ).toLower(); - bool ok; - mapBoxSize = maybeElement.toDouble( &ok ); - - QString element; - if ( ok ) { - // Area given, use next word as element or 'short filter' - int pos3 = source.indexOf( " ", pos2 + 1 ); - maybeElement = pos3 == -1 ? source.mid( pos2 + 1 ).toLower() - : source.mid( pos2 + 1, pos3 - pos2 - 1 ).toLower(); - pos2 = pos3; - - // Prevent too big areas - if ( mapBoxSize > maxAreaSize ) { - mapBoxSize = maxAreaSize; - } - } else { - // Use default area size - mapBoxSize = defautAreaSize; - } - - QString sFilter; - if ( m_shortFilter.contains(maybeElement) ) { - // Replace 'short filters', like "hospital" -> "amenity=hospital" (with element="node") - Filter filter = m_shortFilter[ maybeElement ]; - element = elementToString( filter.element ); - sFilter = filter.filter; - - if ( maybeElement == QLatin1String("publictransportstops") ) { - resultFlags |= OsmReader::OnlyResultsWithNameAttribute; - } - } else { - // A custom filter - element = maybeElement; - sFilter = source.mid( pos2 + 1 ).trimmed(); - } - - // Build url - osmUrl = QString( "%1%2[%3][bbox=%4,%5,%6,%7]" ) - .arg( baseUrl(), element, sFilter ) - .arg( longitude - mapBoxSize/2 ).arg( latitude - mapBoxSize/2 ) - .arg( longitude + mapBoxSize/2 ).arg( latitude + mapBoxSize/2 ); - kDebug() << "URL:" << osmUrl; - } - - // Tell visualizations that not all data has been read - setData( source, "finished", false ); - - // Start download - KIO::TransferJob *job = KIO::get( osmUrl, KIO::NoReload, KIO::HideProgressInfo ); - connect( job, SIGNAL(data(KIO::Job*,QByteArray)), - this, SLOT(data(KIO::Job*,QByteArray)) ); - connect( job, SIGNAL(result(KJob*)), this, SLOT(finished(KJob*)) ); - - // Store source name and reader associated with the job - QPointer osmReader = new OsmReader( source, osmUrl, resultFlags ); - connect( osmReader, SIGNAL(chunkRead(QPointer,Plasma::DataEngine::Data)), - this, SLOT(osmChunkRead(QPointer,Plasma::DataEngine::Data)) ); - connect( osmReader, SIGNAL(finishedReading(QPointer,Plasma::DataEngine::Data)), - this, SLOT(osmFinishedReading(QPointer,Plasma::DataEngine::Data)) ); - m_jobInfos.insert( job, JobInfo(source, osmReader) ); - return true; -} - -void OpenStreetMapEngine::data( KIO::Job* job, const QByteArray& ba ) { - JobInfo &jobInfo = m_jobInfos[ job ]; // Get associated information - - kDebug() << "Got some data" << ba; - jobInfo.osmReader->addData( ba ); - if ( jobInfo.readStarted ) { - // Continue reading - jobInfo.osmReader->resumeReading(); - } else { - // Start reading if not already started - jobInfo.readStarted = true; - jobInfo.osmReader->read(); - } -} - -void OpenStreetMapEngine::finished( KJob* job ) { - // Remove the finished job from the job info hash - m_jobInfos.remove( job ); -} - -void OpenStreetMapEngine::osmChunkRead( QPointer osmReader, - const Plasma::DataEngine::Data &data ) { - // Update data - if ( !data.isEmpty() ) { - setData( osmReader->associatedSourceName(), data ); - } -} - -void OpenStreetMapEngine::osmFinishedReading( QPointer osmReader, - const Plasma::DataEngine::Data& data ) { - // Update data - bool finished = true; - if ( !data.isEmpty() ) { - setData( osmReader->associatedSourceName(), data ); - } else if ( osmReader->sourceUrl().contains(QLatin1String("public_transport=*")) - || osmReader->sourceUrl().contains(QLatin1String("railway=tram_stop")) ) - { - QString newUrl = osmReader->sourceUrl().replace( - QLatin1String("railway=tram_stop"), - QLatin1String("highway=bus_stop") ) - .replace( - QLatin1String("public_transport=*"), - QLatin1String("railway=tram_stop") ); - kDebug() << "NEW URL:" << newUrl; - - // Start download - KIO::TransferJob *job = KIO::get( newUrl, KIO::NoReload, KIO::HideProgressInfo ); - connect( job, SIGNAL(data(KIO::Job*,QByteArray)), - this, SLOT(data(KIO::Job*,QByteArray)) ); - connect( job, SIGNAL(result(KJob*)), this, SLOT(finished(KJob*)) ); - - // Store source name and reader associated with the job - QPointer newOsmReader = new OsmReader( osmReader->associatedSourceName(), newUrl ); - connect( newOsmReader, SIGNAL(chunkRead(QPointer,Plasma::DataEngine::Data)), - this, SLOT(osmChunkRead(QPointer,Plasma::DataEngine::Data)) ); - connect( newOsmReader, SIGNAL(finishedReading(QPointer,Plasma::DataEngine::Data)), - this, SLOT(osmFinishedReading(QPointer,Plasma::DataEngine::Data)) ); - m_jobInfos.insert( job, JobInfo(osmReader->associatedSourceName(), newOsmReader) ); - - finished = false; - } - - // Tell visualizations that all data has been read - if ( finished ) { - setData( osmReader->associatedSourceName(), "finished", true ); - } - - // Kill still running jobs (probably receiving a NULL string) - // AND If no results have been found in the first OSM-URL try another one - for( QHash< KJob*, JobInfo >::const_iterator it = m_jobInfos.constBegin(); - it != m_jobInfos.constEnd(); ++it ) - { - if ( it.value().osmReader == osmReader ) { - // Don't get more data, when the XML reader has completed reading - // otherwise the data engine crashes (because osmReader gets deleted here) - it.key()->kill( KJob::EmitResult ); - break; - } - } - - // Delete the finished reader - osmReader->deleteLater(); -} - -QString OpenStreetMapEngine::elementToString( OpenStreetMapEngine::Element element ) const { - switch ( element ) { - case Node: return "node"; - case Relation: return "relation"; - case Way: return "way"; - - default: - kDebug() << "Element unknown"; - return ""; - } -} - - -K_EXPORT_PLASMA_DATAENGINE(openstreetmap, OpenStreetMapEngine) - -#include "build/openstreetmapdataengine.moc" diff --git a/engine-openstreetmap/openstreetmapdataengine.h b/engine-openstreetmap/openstreetmapdataengine.h deleted file mode 100644 index 6ab458f..0000000 --- a/engine-openstreetmap/openstreetmapdataengine.h +++ /dev/null @@ -1,116 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#ifndef OPENSTREETMAPDATAENGINE_HEADER -#define OPENSTREETMAPDATAENGINE_HEADER - -#include - -#include "osmreader.h" - -class KJob; -namespace KIO { - class Job; -} -class QSizeF; -class QBuffer; - -/** - * @brief This engine searches for information from OpenStreetMap. - * - * The source name is: - * "[longitude],[latitude] ([mapArea]) ([element] [filter]|[short-filter])". - * For example: "53.069,8.8 theatre" - * "53.069,8.8 0.1 theatre" (search in a bigger area) - * "53.069,8.8 publictransportstops" - * "53.069,8.8 node amenity=theatre" (custom search) - * - * TODO: Could use libweb from Project Silk, the base class for REST apis? - **/ -class OpenStreetMapEngine : public Plasma::DataEngine { - Q_OBJECT -public: - // Basic Create/Destroy - OpenStreetMapEngine( QObject *parent, const QVariantList &args ); - - static const int maxResults = 50; - static const double maxAreaSize; - static const double defautAreaSize; - -protected: - virtual bool sourceRequestEvent( const QString& source ); - virtual bool updateSourceEvent( const QString& source ); - - const QString baseUrl() const { - return "http://jxapi.openstreetmap.org/xapi/api/0.6/"; }; -// return "http://www.informationfreeway.org/api/0.6/"; }; - -public slots: - /** @brief Download has finished. */ - void finished( KJob *job ); - - /** @brief More downloaded data is available. */ - void data( KIO::Job *job, const QByteArray &ba ); - - /** - * @brief Reading an XML document has finished (reached the end of the document). - * - * @Note @p data only contains the last chunk of data. - **/ - void osmFinishedReading( QPointer osmReader, const Plasma::DataEngine::Data &data ); - - /** @brief A new chunk of the XML document has been read. */ - void osmChunkRead( QPointer osmReader, const Plasma::DataEngine::Data &data ); - -private: - enum Element { - Node, Relation, Way - }; - - struct Filter { - Element element; - QString filter; - - Filter() {}; - Filter( Element element, const QString &filter ) { - this->element = element; - this->filter = filter; }; - }; - - /** @brief Data stored for each download job. */ - struct JobInfo { - QString sourceName; - QPointer osmReader; - bool readStarted; - - JobInfo() {}; - JobInfo( const QString &sourceName, QPointer osmReader ) { - this->sourceName = sourceName; - this->osmReader = osmReader; - this->readStarted = false; - }; - }; - - QString elementToString( Element element ) const; - - QHash< KJob*, JobInfo > m_jobInfos; - QHash< QString, Filter > m_shortFilter; -}; - -#endif // Multiple include guard diff --git a/engine-openstreetmap/osmreader.cpp b/engine-openstreetmap/osmreader.cpp deleted file mode 100644 index 21becad..0000000 --- a/engine-openstreetmap/osmreader.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#include "osmreader.h" - - -void OsmReader::read() { - m_data.clear(); - - while ( !atEnd() || waitOnRecoverableError() ) { - readNext(); - - if ( isStartElement() ) { - if ( name().compare(QLatin1String("osm"), Qt::CaseInsensitive) == 0 ) { - readOsm(); - break; - } - } - } - - kDebug() << "Read complete:" << (hasError() ? "No error." : errorString() ); - emit finishedReading( this, m_data ); -} - -bool OsmReader::waitOnRecoverableError() { - if ( error() == PrematureEndOfDocumentError ) { - if ( !m_data.isEmpty() ) { - emit chunkRead( this, m_data ); - } - m_data.clear(); // Clear old data - m_loop.exec(); // Wait for more data - return true; - } else { - return false; - } -} - -void OsmReader::readUnknownElement() { - Q_ASSERT( isStartElement() ); - - while ( !atEnd() || waitOnRecoverableError() ) { - readNext(); - - if ( isEndElement() ) { - break; - } - - if ( isStartElement() ) { - readUnknownElement(); - } - } -} - -void OsmReader::readOsm() { - while ( !atEnd() || waitOnRecoverableError() ) { - readNext(); - - if ( isEndElement() && name().compare(QLatin1String("osm"), Qt::CaseInsensitive) == 0 ) { - kDebug() << "Closing tag read"; - break; - } - - if ( isStartElement() ) { - if ( name().compare(QLatin1String("node"), Qt::CaseInsensitive) == 0 ) { - readNode(); - } else if ( name().compare(QLatin1String("way"), Qt::CaseInsensitive) == 0 ) { - readWay(); - } else if ( name().compare(QLatin1String("relation"), Qt::CaseInsensitive) == 0 ) { - readRelation(); - } else { - readUnknownElement(); - } - } - } - - kDebug() << "Finished reading the tag"; -} - -bool OsmReader::isResultValid( const QVariantHash& data ) const { - return !m_resultFlags.testFlag(OnlyResultsWithNameAttribute) - || data.contains("name"); -} - -void OsmReader::readNode() { - QString id = attributes().value( QLatin1String("id") ).toString(); - double longitude = attributes().value( QLatin1String("lon") ).toString().toDouble(); - double latitude = attributes().value( QLatin1String("lat") ).toString().toDouble(); - // Could read more information from attributes (user, uid, timestamp, version, changeset) - - QVariantHash nodeData; - nodeData.insert( "longitude", longitude ); - nodeData.insert( "latitude", latitude ); - nodeData.insert( "type", "node" ); - - while ( !atEnd() || waitOnRecoverableError() ) { - readNext(); - - if ( isEndElement() && name().compare(QLatin1String("node"), Qt::CaseInsensitive) == 0 ) { - break; - } - - if ( isStartElement() ) { - if ( name().compare(QLatin1String("tag"), Qt::CaseInsensitive) == 0 ) { - readTag( &nodeData ); - } else { - readUnknownElement(); - } - } - } - - if ( isResultValid(nodeData) ) { - m_data.insert( id, nodeData ); - } -} - -void OsmReader::readWay() { - QString id = attributes().value( QLatin1String("id") ).toString(); - // Could read more information from attributes (user, uid, timestamp, version, changeset) - QVariantHash nodeData; - QStringList nodes; - nodeData.insert( "type", "way" ); - - while ( !atEnd() || waitOnRecoverableError() ) { - readNext(); - - if ( isEndElement() && name().compare(QLatin1String("way"), Qt::CaseInsensitive) == 0 ) - break; - - if ( isStartElement() ) { - if ( name().compare(QLatin1String("tag"), Qt::CaseInsensitive) == 0 ) { - readTag( &nodeData ); - } else if ( name().compare(QLatin1String("nd"), Qt::CaseInsensitive) == 0 ) { - QString node = attributes().value( QLatin1String("ref") ).toString(); - if ( !node.isEmpty() ) { - nodes << node; - } - } else { - readUnknownElement(); - } - } - } - - if ( isResultValid(nodeData) ) { - if ( !nodes.isEmpty() ) { - nodeData.insert( "nodes", nodes ); // IDs of associated nodes - } - m_data.insert( id, nodeData ); - } -} - -void OsmReader::readRelation() { - QString id = attributes().value( QLatin1String("id") ).toString(); - // Could read more information from attributes (user, uid, timestamp, version, changeset) - QVariantHash nodeData; - QStringList nodes, ways; - nodeData.insert( "type", "relation" ); - - while ( !atEnd() || waitOnRecoverableError() ) { - readNext(); - - if ( isEndElement() && name().compare(QLatin1String("relation"), Qt::CaseInsensitive) == 0 ) { - break; - } - - if ( isStartElement() ) { - if ( name().compare(QLatin1String("tag"), Qt::CaseInsensitive) == 0 ) { - readTag( &nodeData ); - } else if ( name().compare(QLatin1String("member"), Qt::CaseInsensitive) == 0 ) { - QString nodeOrWay = attributes().value( QLatin1String("ref") ).toString(); - if ( !nodeOrWay.isEmpty() ) { - QString type = attributes().value( QLatin1String("type") ).toString(); - if ( type == QLatin1String("node") ) { - nodes << nodeOrWay; - } else if ( type == QLatin1String("way") ) { - ways << nodeOrWay; - } else { - kDebug() << "Unknown member type" << type - << "of relation" << id; - } - } - } else { - readUnknownElement(); - } - } - } - - if ( isResultValid(nodeData) ) { - if ( !nodes.isEmpty() ) { - nodeData.insert( "nodes", nodes ); // IDs of associated nodes - } - if ( !ways.isEmpty() ) { - nodeData.insert( "ways", ways ); // IDs of associated ways - } - m_data.insert( id, nodeData ); - } -} - -void OsmReader::readTag( QVariantHash *nodeData ) { - if ( !attributes().hasAttribute("k") || !attributes().hasAttribute("v") ) { - kDebug() << "Key or value attribute not found for "; - } - - // Simply use the keys from OpenStreetMap. Maybe it's better to translate - // them ("addr:street" => "street", then maybe combined with "addr:housenumber"). - nodeData->insert( attributes().value("k").toString(), attributes().value("v").toString() ); -} diff --git a/engine-openstreetmap/osmreader.h b/engine-openstreetmap/osmreader.h deleted file mode 100644 index 881d182..0000000 --- a/engine-openstreetmap/osmreader.h +++ /dev/null @@ -1,87 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#ifndef OSMREADER_HEADER -#define OSMREADER_HEADER - -#include - -#include -#include - -class ServiceProvider; - -class OsmReader : public QObject, public QXmlStreamReader { - Q_OBJECT - -public: - enum ResultFlag { - AllResults = 0x0000, - OnlyResultsWithNameAttribute = 0x0001 - }; - Q_DECLARE_FLAGS( ResultFlags, ResultFlag ) - - OsmReader( const QString &associatedSourceName, const QString &sourceUrl, - ResultFlags resultFlags = ResultFlags(AllResults) ) - : QXmlStreamReader() - { - m_associatedSourceName = associatedSourceName; - m_sourceUrl = sourceUrl; - m_resultFlags = resultFlags; - }; - - void read(); - Plasma::DataEngine::Data data() const { return m_data; }; - - void resumeReading() { m_loop.quit(); }; - QString associatedSourceName() const { return m_associatedSourceName; }; - QString sourceUrl() const { return m_sourceUrl; }; - -signals: - /** - * @brief Reading an XML document has finished (reached the end of the document). - * - * @Note @p lastDataChunk only contains the last chunk of data. - **/ - void finishedReading( QPointer reader, const Plasma::DataEngine::Data &lastDataChunk ); - - /** @brief A new chunk of the XML document has been read. */ - void chunkRead( QPointer reader, const Plasma::DataEngine::Data &dataChunk ); - -private: - bool isResultValid( const QVariantHash &data ) const; - - void readUnknownElement(); - void readOsm(); - void readNode(); - void readWay(); - void readRelation(); - void readTag( QHash< QString, QVariant > *nodeData ); - - bool waitOnRecoverableError(); - - Plasma::DataEngine::Data m_data; - QEventLoop m_loop; - QString m_associatedSourceName; - ResultFlags m_resultFlags; - QString m_sourceUrl; -}; -Q_DECLARE_OPERATORS_FOR_FLAGS( OsmReader::ResultFlags ) - -#endif // OSMREADER_HEADER diff --git a/engine-openstreetmap/plasma-engine-openstreetmap.desktop b/engine-openstreetmap/plasma-engine-openstreetmap.desktop deleted file mode 100644 index f9765e5..0000000 --- a/engine-openstreetmap/plasma-engine-openstreetmap.desktop +++ /dev/null @@ -1,57 +0,0 @@ -[Desktop Entry] -Name=OpenStreetMap data engine -Name[bs]=OtvorenaMapaUlice podaci motora -Name[ca]=Motor de dades OpenStreetMap -Name[ca@valencia]=Motor de dades OpenStreetMap -Name[de]=OpenStreetMap-Datenmodul -Name[es]=Motor de datos de OpenStreetMap -Name[et]=OpenStreetMapi andmete mootor -Name[fr]=Moteur de données « OpenStreetMap » -Name[gl]=Motor de datos OpenStreetMap -Name[hu]=OpenStreetMap adatmotor -Name[km]=ម៉ាស៊ីន​ទិន្នន័យ OpenStreetMap -Name[ko]=OpenStreetMap 데이터 엔진 -Name[mr]=ओपनस्ट्रीटमेप डेटाइंजिन -Name[nl]=Gegevensengine voor OpenStreetMap -Name[pl]=Silnik danych OpenStreetMap -Name[pt]=Motor de dados do OpenStreetMap -Name[pt_BR]=Mecanismo de dados do OpenStreetMap -Name[ro]=Motor de date OpenStreetMap -Name[sk]=Dátový nástroj OpenStreetMap -Name[sv]=Datagränssnitt för Open Street Map -Name[uk]=Рушій даних OpenStreetMap -Name[x-test]=xxOpenStreetMap data enginexx -Comment=The OpenStreetMap data engine can be used to query things near the user. -Comment[bs]=OpenStreetMap pogon podataka se može koristiti za upit stvari pored korisnika. -Comment[ca]=El motor de dades OpenStreetMap es pot utilitzar per consultar coses a prop de l'usuari. -Comment[ca@valencia]=El motor de dades OpenStreetMap es pot utilitzar per consultar coses a prop de l'usuari. -Comment[de]=Mit dem OpenStreetMap-Datenmodul können Informationen nahe dem Standort des Benutzers abgefragt werden. -Comment[es]=El motor de datos de OpenStreetMap puede ser usado para consultar eventos cercanos al usuario. -Comment[et]=OpenStreetMapi andmete mootorit saab kasutada teabe hankimiseks kasutaja lähedal asuvate objektide kohta. -Comment[fr]=Le moteur de données « OpenStreetMap » peut servir à faire une recherche dans le voisinage de l'utilisateur. -Comment[gl]=O motor de datos OpenStreetMap pódese usar para consultar os servizos de preto do usuario. -Comment[km]=ម៉ាស៊ីន​ទិន្នន័យ OpenStreetMap អាច​ត្រូវ​បាន​ប្រើ​ដើម្បី​សួររក​វត្ថុ​នៅ​ជិត​អ្នក​ប្រើ ។ -Comment[ko]=OpenStreetMap 데이터 엔진을 사용하여 사용자 근처에 있는 것을 알아볼 수 있습니다. -Comment[nl]=De gegevensengine OpenStreetMap kan worden gebruikt om zaken in de buurt van de gebruiker op te vragen. -Comment[pl]=Silnik danych OpenStreetMap może być użyty do odpytania miejsc znajdujących się w pobliżu użytkownika. -Comment[pt]=O motor de dados do OpenStreetMap pode ser usado para pesquisar itens perto do utilizador. -Comment[pt_BR]=O mecanismo de dados OpenStreetMap pode ser usado para pesquisar itens próximos do usuário. -Comment[sk]=Dátový engine OpenStreetMap sa dá použiť na dotazovanie vecí pri používateľovi. -Comment[sv]=Datagränssnittet för Open Street Map kan användas för att fråga om saker nära användaren. -Comment[uk]=За допомогою рушія даних OpenStreetMap можна отримувати дані щодо об’єктів, розташованих поруч з користувачем. -Comment[x-test]=xxThe OpenStreetMap data engine can be used to query things near the user.xx -Type=Service - -X-KDE-ServiceTypes=Plasma/DataEngine -X-Plasma-EngineName=openstreetmap -X-KDE-Library=plasma_engine_openstreetmap -X-KDE-PluginInfo-Author=Friedrich Pülz -X-KDE-PluginInfo-Email=fpuelz@gmx.de -X-KDE-PluginInfo-Name=openstreetmap -X-KDE-PluginInfo-Version=0.1.2 -X-KDE-PluginInfo-Website=http://plasma.kde.org/ -X-KDE-PluginInfo-Category=Online Services -X-KDE-PluginInfo-Depends= -X-KDE-PluginInfo-License=GPL -X-KDE-PluginInfo-EnabledByDefault=true - diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index c4f783b..f423e66 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -1,154 +1,142 @@ project( plasma-engine-publictransport ) # Find the required Libaries add_definitions( ${QT_DEFINITIONS} ) include_directories( ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} # For "config.h" ) # Include cmake_dependent_option command include( CMakeDependentOption ) # Add custom CMake modules set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ) # Find mime type relevant stuff set( SHARED_MIME_INFO_MINIMUM_VERSION "0.30" ) find_package( SharedMimeInfo ) # Create some variables to control the build, # each provider type can be disabled, ie. not build, but at least one needs to be enabled -option( BUILD_PROVIDER_TYPE_SCRIPT "Build with support for scripted providers" ON ) option( BUILD_PROVIDER_TYPE_GTFS "Build with support for GTFS providers" ON ) option( ENABLE_DEBUG_ENGINE_JOBS "Enable debug output for started/finished jobs in the engine" OFF ) option( ENABLE_DEBUG_NETWORK "Enable debug output for network events in scripts" OFF ) option( ENABLE_DEBUG_SCRIPT_HELPER "Enable debug output for warnings/errors in the script 'helper' object, eg. when debugging scripts that use the 'helper' object" OFF ) option( ENABLE_DEBUG_SCRIPT_ERROR "Enable debug output for error messages from scripts" OFF ) # Fatal error if no provider types are enabled -if ( NOT BUILD_PROVIDER_TYPE_SCRIPT AND NOT BUILD_PROVIDER_TYPE_GTFS ) +if ( NOT BUILD_PROVIDER_TYPE_GTFS ) message( FATAL_ERROR " - No provider types enabled, this would make the engine useless!\n" - "Solution: Use at least one of the available provider types\n" - " BUILD_PROVIDER_TYPE_SCRIPT or BUILD_PROVIDER_TYPE_GTFS." ) -endif ( NOT BUILD_PROVIDER_TYPE_SCRIPT AND NOT BUILD_PROVIDER_TYPE_GTFS ) + "Solution: Use the available provider type : BUILD_PROVIDER_TYPE_GTFS.") +endif ( NOT BUILD_PROVIDER_TYPE_GTFS ) # Source files qt5_wrap_cpp( publictransport_engine_MOC_SRCS enums.h ) set( publictransport_engine_SRCS publictransportdataengine.cpp datasource.cpp timetableservice.cpp global.cpp departureinfo.cpp request.cpp serviceprovider.cpp serviceproviderdata.cpp serviceproviderdatareader.cpp serviceproviderglobal.cpp serviceprovidertestdata.cpp ${publictransport_engine_MOC_SRCS} ) if ( BUILD_PROVIDER_TYPE_GTFS ) # Find ProtocolBuffers, needed for GTFS-realtime find_package( ProtocolBuffers QUIET CONFIG ) set_package_properties( ProtocolBuffers PROPERTIES DESCRIPTION "GTFS-realtime support" URL "http://developers.google.com/protocol-buffers" TYPE OPTIONAL PURPOSE "Protocol buffers are used for GTFS-realtime support." ) if ( PROTOBUF_FOUND ) # Generate gtfs-realtime protocol buffer sources when found WRAP_PROTO( PROTO_SRC gtfs-realtime.proto ) list ( APPEND publictransport_engine_SRCS ${PROTO_SRC} ) endif ( PROTOBUF_FOUND ) # Enable/disable GTFS-realtime support with ProtocolBuffers set( BUILD_GTFS_REALTIME ${PROTOBUF_FOUND} ) else ( BUILD_PROVIDER_TYPE_GTFS ) set( BUILD_GTFS_REALTIME FALSE ) endif ( BUILD_PROVIDER_TYPE_GTFS ) # Create config.h from config.h.in, ie. create #define's for each enabled variable from above configure_file( config.h.in config.h ) # Add option to build TimetableMate or not option( INSTALL_TIMETABLEMATE "A tool/little IDE to add support for new service providers. Offers code completion for scripts and helps when checking the correct behaviour of the script" OFF ) # Add a variable for the service provider plugin installation directory set( SERVICE_PROVIDER_DIR ${DATA_INSTALL_DIR}/plasma_engine_publictransport/serviceProviders ) # Add TimetableMate if ( INSTALL_TIMETABLEMATE ) message( " - TimetableMate" ) add_subdirectory( timetablemate ) endif ( INSTALL_TIMETABLEMATE ) # Add provider types (adding their sources to publictransport_engine_SRCS) -if ( BUILD_PROVIDER_TYPE_SCRIPT ) - message( " - Build script provider type" ) - add_subdirectory( script ) -endif ( BUILD_PROVIDER_TYPE_SCRIPT ) - if ( BUILD_PROVIDER_TYPE_GTFS ) message( " - Build GTFS provider type" ) add_subdirectory( gtfs ) endif ( BUILD_PROVIDER_TYPE_GTFS ) add_subdirectory(timetable) # Message, when debug output is enabled for specific events if ( ENABLE_DEBUG_NETWORK ) message( " - Enable debug output for script network events" ) endif ( ENABLE_DEBUG_NETWORK ) if ( ENABLE_DEBUG_SCRIPT_HELPER ) message( " - Enable debug output for warnings/errors in the script 'helper' object" ) endif ( ENABLE_DEBUG_SCRIPT_HELPER ) # Add unit tests if ( BUILD_TESTS ) add_subdirectory( tests ) endif ( BUILD_TESTS ) # Now make sure all files get to the right place add_library(plasma_engine_publictransport MODULE ${publictransport_engine_SRCS} ) # Collect all needed libraries in LIBS set( LIBS KF5::Archive KF5::CoreAddons KF5::Plasma KF5::KIOCore KF5::KDELibs4Support KF5::ThreadWeaver z ) -# Add libraries needed for the script provider type -if ( BUILD_PROVIDER_TYPE_SCRIPT ) - set( LIBS ${LIBS} Qt5::Core Qt5::Script ) -endif ( BUILD_PROVIDER_TYPE_SCRIPT ) - # Add libraries needed for the GTFS provider type if ( BUILD_PROVIDER_TYPE_GTFS ) set( LIBS ${LIBS} Qt5::Core Qt5::Xml Qt5::Sql ) if ( BUILD_GTFS_REALTIME ) set( LIBS ${LIBS} ${PROTOBUF_LIBRARY} pthread ) # pthread is needed for protobuf endif ( BUILD_GTFS_REALTIME ) endif ( BUILD_PROVIDER_TYPE_GTFS ) # Link all collected libraries target_link_libraries( plasma_engine_publictransport ${LIBS} ) kcoreaddons_desktop_to_json(plasma_engine_publictransport plasma-engine-publictransport.desktop) # Install engine and it's .desktop-file install( TARGETS plasma_engine_publictransport DESTINATION ${PLUGIN_INSTALL_DIR}/plasma/dataengine ) install( FILES plasma-engine-publictransport.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) # Install mimetype "application/x-publictransport-engine", # files with this mimetype are the XML-files used for service provider plugins for the # PublicTransport data engine. # In TimetableMate they are also used as project files, without additional data install( FILES serviceproviderplugin.xml DESTINATION ${XDG_MIME_INSTALL_DIR} ) update_xdg_mimetypes( ${XDG_MIME_INSTALL_DIR} ) # Install .operations-file describing the "Timetable" service # for timetable data sources install( FILES timetable.operations DESTINATION ${DATA_INSTALL_DIR}/plasma/services ) diff --git a/engine/script/CMakeLists.txt b/engine/script/CMakeLists.txt deleted file mode 100644 index 3200d46..0000000 --- a/engine/script/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ - -# Sources for the script provider type -set ( script_SRCS - script/serviceproviderscript.cpp - script/script_thread.cpp - script/scriptapi.cpp - script/scriptobjects.cpp -) - -# Add sources of this directory to the sources list of the parent directories CMakeLists.txt -set ( publictransport_engine_SRCS ${publictransport_engine_SRCS} ${script_SRCS} PARENT_SCOPE ) - -# Add base script provider plugins -add_subdirectory( serviceProviders ) diff --git a/engine/script/script-doc.h b/engine/script/script-doc.h deleted file mode 100644 index 86df298..0000000 --- a/engine/script/script-doc.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** - * @defgroup scriptProviders Script Provider Plugins - * - * Script provider plugins implement execution of timetable data requests in a script. The script - * file to use gets referenced in the .pts file in the \ tag, see @ref provider_plugin_pts. - * Script extensions that are needed inside the script (eg. @em qt.xml) can be specified in an - * @em extensions attribute for that tag. The script can use the @ref scriptApi, which provides - * ways to start network requests synchronously and asynchronously, helper functions to parse - * HTML, a Storage to cache/save information etc. - * - * Currently all included script providers need a network connection to work and directly get the - * state @em ready if there are no errors in the script. If a provider instead requires to download - * all timetable data at once and could then execute requests offline, this provider plugin type - * is not the right one. If you want to add support for a GTFS provider, use @ref gtfsProviders. - * Otherwise a new plugin type could be created with different request strategies, state handling, - * error checking and maybe an own data engine service. - * - * @see scriptApi, @ref provider_plugin_types - **/ diff --git a/engine/script/script_thread.cpp b/engine/script/script_thread.cpp deleted file mode 100644 index c15fcce..0000000 --- a/engine/script/script_thread.cpp +++ /dev/null @@ -1,907 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -// Header -#include "script_thread.h" - -// Own includes -#include "config.h" -#include "scriptapi.h" -#include "script/serviceproviderscript.h" -#include "serviceproviderdata.h" -#include "request.h" -#include "serviceproviderglobal.h" - -// KDE includes -#include -#include -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -ScriptJob::ScriptJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - QObject* parent ) - : ThreadWeaver::Job(), m_engine(0), m_mutex(new QMutex(QMutex::Recursive)), - m_data(data), m_eventLoop(0), m_published(0), m_quit(false), m_success(true) -{ - Q_ASSERT_X( data.isValid(), "ScriptJob constructor", "Needs valid script data" ); - - // Use global storage, may contain non-persistent data from earlier requests. - // Does not need to live in this thread, it does not create any new QObjects. - QMutexLocker locker( m_mutex ); - m_objects.storage = scriptStorage; - - qRegisterMetaType( "DepartureRequest" ); - qRegisterMetaType( "ArrivalRequest" ); - qRegisterMetaType( "JourneyRequest" ); - qRegisterMetaType( "StopSuggestionRequest" ); - qRegisterMetaType( "StopsByGeoPositionRequest" ); - qRegisterMetaType( "AdditionalDataRequest" ); -} - -ScriptJob::~ScriptJob() -{ - // Abort, if still running - requestAbort(); - - // Do some cleanup if not done already - cleanup(); - - delete m_mutex; -} - -void ScriptJob::requestAbort() -{ - m_mutex->lock(); - if ( !m_engine ) { - // Is already finished - m_mutex->unlock(); - return; - } else if ( m_quit ) { - // Is already aborting, wait for the script engine to get destroyed - QEventLoop loop; - connect( m_engine, SIGNAL(destroyed(QObject*)), &loop, SLOT(quit()) ); - m_mutex->unlock(); - - // Run the event loop, waiting for the engine to get destroyed or a 1 second timeout - QTimer::singleShot( 1000, &loop, SLOT(quit()) ); - loop.exec(); - return; - } - - // Is still running, remember to abort - m_quit = true; - - // Abort running network requests, if any - if ( m_objects.network->hasRunningRequests() ) { - m_objects.network->abortAllRequests(); - } - - // Abort script evaluation - if ( m_engine ) { - m_engine->abortEvaluation(); - } - - // Wake waiting event loops - if ( m_eventLoop ) { - QEventLoop *loop = m_eventLoop; - m_eventLoop = 0; - loop->quit(); - } - m_mutex->unlock(); - - // Wait until signals are processed - qApp->processEvents(); -} - -ScriptAgent::ScriptAgent( QScriptEngine* engine, QObject *parent ) - : QObject(parent), QScriptEngineAgent(engine) -{ -} - -void ScriptAgent::functionExit( qint64 scriptId, const QScriptValue& returnValue ) -{ - Q_UNUSED( scriptId ); - Q_UNUSED( returnValue ); - QTimer::singleShot( 250, this, SLOT(checkExecution()) ); -} - -void ScriptAgent::checkExecution() -{ - if ( !engine()->isEvaluating() ) { - emit scriptFinished(); - } -} - -void ScriptJob::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) -{ - m_mutex->lock(); - if ( !loadScript(m_data.program) ) { - kDebug() << "Script could not be loaded correctly"; - m_mutex->unlock(); - return; - } - - QScriptEngine *engine = m_engine; - ScriptObjects objects = m_objects; - const QString scriptFileName = m_data.provider.scriptFileName(); - const QScriptValueList arguments = QScriptValueList() << request()->toScriptValue( engine ); - const ParseDocumentMode parseMode = request()->parseMode(); - m_mutex->unlock(); - - // Add call to the appropriate function - QString functionName; - switch ( parseMode ) { - case ParseForDepartures: - case ParseForArrivals: - functionName = ServiceProviderScript::SCRIPT_FUNCTION_GETTIMETABLE; - break; - case ParseForJourneysByDepartureTime: - case ParseForJourneysByArrivalTime: - functionName = ServiceProviderScript::SCRIPT_FUNCTION_GETJOURNEYS; - break; - case ParseForStopSuggestions: - functionName = ServiceProviderScript::SCRIPT_FUNCTION_GETSTOPSUGGESTIONS; - break; - case ParseForAdditionalData: - functionName = ServiceProviderScript::SCRIPT_FUNCTION_GETADDITIONALDATA; - break; - default: - kDebug() << "Parse mode unsupported:" << parseMode; - break; - } - - if ( functionName.isEmpty() ) { - // This should never happen, therefore no i18n - handleError( "Unknown parse mode" ); - return; - } - - // Check if the script function is implemented - QScriptValue function = engine->globalObject().property( functionName ); - if ( !function.isFunction() ) { - handleError( i18nc("@info/plain", "Function %1 not implemented by " - "the script %1", functionName, scriptFileName) ); - return; - } - - // Store start time of the script - QElapsedTimer timer; - timer.start(); - - // Call script function - QScriptValue result = function.call( QScriptValue(), arguments ); - if ( engine->hasUncaughtException() ) { - // TODO Get filename where the exception occured, maybe use ScriptAgent for that - handleError( i18nc("@info/plain", "Error in script function %1, " - "line %2: %3.", - functionName, engine->uncaughtExceptionLineNumber(), - engine->uncaughtException().toString()) ); - return; - } - - // Inform about script run time - DEBUG_ENGINE_JOBS( "Script finished in" << (timer.elapsed() / 1000.0) - << "seconds: " << scriptFileName << parseMode ); - - GlobalTimetableInfo globalInfo; - globalInfo.requestDate = QDate::currentDate(); - globalInfo.delayInfoAvailable = !objects.result->isHintGiven( ResultObject::NoDelaysForStop ); - - // The called function returned, but asynchronous network requests may have been started. - // Slots in the script may be connected to those requests and start script execution again. - // In the execution new network requests may get started and so on. - // - // Wait until all network requests are finished and the script is not evaluating at the same - // time. Define a global timeout, each waitFor() call subtracts the elapsed time. - // - // NOTE Network requests by default use a 30 seconds timeout, the global timeout should - // be bigger. Synchronous requests that were started by signals of running asynchronous - // requests are accounted as script execution time here. Synchronous requests that were - // started before are already done. - const int startTimeout = 60000; - int timeout = startTimeout; - while ( objects.network->hasRunningRequests() || engine->isEvaluating() ) { - // Wait until all asynchronous network requests are finished - if ( !waitFor(objects.network.data(), SIGNAL(allRequestsFinished()), - WaitForNetwork, &timeout) ) - { - if ( timeout <= 0 ) { - // Timeout expired while waiting for network requests to finish, - // abort all requests that are still running - objects.network->abortAllRequests(); - - QMutexLocker locker( m_mutex ); - m_success = false; - m_errorString = i18nc("@info", "Timeout expired after %1 while waiting for " - "network requests to finish", - KGlobal::locale()->prettyFormatDuration(startTimeout)); - } - - cleanup(); - return; - } - - // Wait for script execution to finish, ie. slots connected to the last finished request(s) - ScriptAgent agent( engine ); - if ( !waitFor(&agent, SIGNAL(scriptFinished()), WaitForScriptFinish, &timeout) ) { - if ( timeout <= 0 ) { - // Timeout expired while waiting for script execution to finish, abort evaluation - engine->abortEvaluation(); - - QMutexLocker locker( m_mutex ); - m_success = false; - m_errorString = i18nc("@info", "Timeout expired after %1 while waiting for script " - "execution to finish", - KGlobal::locale()->prettyFormatDuration(startTimeout)); - } - - cleanup(); - return; - } - - // Check for new exceptions after waiting for script execution to finish (again) - if ( engine->hasUncaughtException() ) { - // TODO Get filename where the exception occured, maybe use ScriptAgent for that - handleError( i18nc("@info/plain", "Error in script function %1, " - "line %2: %3.", - functionName, engine->uncaughtExceptionLineNumber(), - engine->uncaughtException().toString()) ); - return; - } - } - - // Publish remaining items, if any - publish(); - - // Cleanup - cleanup(); -} - -void ScriptJob::cleanup() -{ - QMutexLocker locker( m_mutex ); - if ( !m_objects.storage.isNull() ) { - m_objects.storage->checkLifetime(); - } - if ( !m_objects.result.isNull() ) { - disconnect( m_objects.result.data(), 0, this, 0 ); - } - if ( m_engine ) { - m_engine->deleteLater(); - m_engine = 0; - } -} - -void ScriptJob::handleError( const QString &errorMessage ) -{ - QMutexLocker locker( m_mutex ); - kDebug() << "Error:" << errorMessage; - kDebug() << "Backtrace:" << m_engine->uncaughtExceptionBacktrace().join("\n"); - m_errorString = errorMessage; - m_success = false; - cleanup(); -} - -QScriptValue networkRequestToScript( QScriptEngine *engine, const NetworkRequestPtr &request ) -{ - return engine->newQObject( const_cast(request), QScriptEngine::QtOwnership, - QScriptEngine::PreferExistingWrapperObject ); -} - -void networkRequestFromScript( const QScriptValue &object, NetworkRequestPtr &request ) -{ - request = qobject_cast( object.toQObject() ); -} - -bool importExtension( QScriptEngine *engine, const QString &extension ) -{ - if ( !ServiceProviderScript::allowedExtensions().contains(extension, Qt::CaseInsensitive) ) { - if ( engine->availableExtensions().contains(extension, Qt::CaseInsensitive) ) { - kDebug() << "Extension" << extension << "is not allowed currently"; - } else { - kDebug() << "Extension" << extension << "could not be found"; - kDebug() << "Available extensions:" << engine->availableExtensions(); - } - kDebug() << "Allowed extensions:" << ServiceProviderScript::allowedExtensions(); - return false; - } else { - if ( engine->importExtension(extension).isUndefined() ) { - return true; - } else { - if ( engine->hasUncaughtException() ) { - kDebug() << "Could not import extension" << extension - << engine->uncaughtExceptionLineNumber() - << engine->uncaughtException().toString(); - kDebug() << "Backtrace:" << engine->uncaughtExceptionBacktrace().join("\n"); - } - return false; - } - } -} - -bool ScriptJob::waitFor( QObject *sender, const char *signal, WaitForType type, int *timeout ) -{ - if ( *timeout <= 0 ) { - return false; - } - - // Do not wait if the job was aborted or failed - QMutexLocker locker( m_mutex ); - if ( !m_success || m_quit ) { - return false; - } - - QScriptEngine *engine = m_engine; - ScriptObjects objects = m_objects; - - // Test if the target condition is already met - if ( (type == WaitForNetwork && objects.network->hasRunningRequests()) || - (type == WaitForScriptFinish && engine->isEvaluating()) ) - { - // Not finished yet, wait for the given signal, - // that should get emitted when the target condition is met - QEventLoop loop; - connect( sender, signal, &loop, SLOT(quit()) ); - - // Add a timeout to not wait forever (eg. because of an infinite loop in the script). - // Use a QTimer variable to be able to check if the timeout caused the event loop to quit - // rather than the given signal - QTimer timer; - timer.setSingleShot( true ); - connect( &timer, SIGNAL(timeout()), &loop, SLOT(quit()) ); - - // Store a pointer to the event loop, to be able to quit it on abort. - // Then start the timeout. - m_eventLoop = &loop; - QElapsedTimer elapsedTimer; - elapsedTimer.start(); - timer.start( *timeout ); - m_mutex->unlock(); - - // Start the event loop waiting for the given signal / timeout. - // QScriptEngine continues execution here or network requests continue to get handled and - // may call script slots on finish. - loop.exec(); - - // Test if the timeout has expired, ie. if the timer is no longer running (single shot) - m_mutex->lock(); - const bool timeoutExpired = !timer.isActive(); - - // Update remaining time of the timeout - if ( timeoutExpired ) { - *timeout = 0; - } else { - *timeout -= elapsedTimer.elapsed(); - } - - // Test if the job was aborted while waiting - if ( !m_eventLoop || m_quit ) { - // Job was aborted - m_eventLoop = 0; - return false; - } - m_eventLoop = 0; - - // Generate errors if the timeout has expired and abort network requests / script execution - if ( timeoutExpired ) { - return false; - } - } - - // The target condition was met in time or was already met - return true; -} - -QScriptValue include( QScriptContext *context, QScriptEngine *engine ) -{ - // Check argument count, include() call in global context? - const QScriptContextInfo contextInfo( context->parentContext() ); - if ( context->argumentCount() < 1 ) { - context->throwError( i18nc("@info/plain", "One argument expected for include()") ); - return engine->undefinedValue(); - } else if ( context->parentContext() && context->parentContext()->parentContext() ) { - QScriptContext *parentContext = context->parentContext()->parentContext(); - bool error = false; - while ( parentContext ) { - const QScriptContextInfo parentContextInfo( parentContext ); - if ( !parentContextInfo.fileName().isEmpty() && - parentContextInfo.fileName() == contextInfo.fileName() ) - { - // Parent context is in the same file, error - error = true; - break; - } - parentContext = parentContext->parentContext(); - } - - if ( error ) { - context->throwError( i18nc("@info/plain", "include() calls must be in global context") ); - return engine->undefinedValue(); - } - } - - // Check if this include() call is before all other statements - QVariantHash includeData = context->callee().data().toVariant().toHash(); - if ( includeData.contains(contextInfo.fileName()) ) { - const quint16 maxIncludeLine = includeData[ contextInfo.fileName() ].toInt(); - if ( contextInfo.lineNumber() > maxIncludeLine ) { - context->throwError( i18nc("@info/plain", "include() calls must be the first statements") ); - return engine->undefinedValue(); - } - } - - // Get argument and check that it's not pointing to another directory - const QString fileName = context->argument(0).toString(); - if ( fileName.contains('/') ) { - context->throwError( i18nc("@info/plain", "Cannot include files from other directories") ); - return engine->undefinedValue(); - } - - // Find the script to be included - const QString subDirectory = ServiceProviderGlobal::installationSubDirectory(); - const QString filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, subDirectory + fileName ); - - // Check if the script was already included - QStringList includedFiles = - engine->globalObject().property( "includedFiles" ).toVariant().toStringList(); - if ( includedFiles.contains(filePath) ) { - qWarning() << "File already included" << filePath; - return engine->undefinedValue(); - } - - // Try to open the file to be included - QFile scriptFile( filePath ); - if ( !scriptFile.open(QIODevice::ReadOnly) ) { - context->throwError( i18nc("@info/plain", "Cannot find file to be included: " - "%1", fileName) ); - return engine->undefinedValue(); - } - - // Read the file - QTextStream stream( &scriptFile ); - const QString program = stream.readAll(); - scriptFile.close(); - - if ( !includeData.contains(scriptFile.fileName()) ) { - includeData[ scriptFile.fileName() ] = maxIncludeLine( program ); - - QScriptValue includeFunction = engine->globalObject().property("include"); - Q_ASSERT( includeFunction.isValid() ); - includeFunction.setData( qScriptValueFromValue(engine, includeData) ); - engine->globalObject().setProperty( "include", includeFunction, - QScriptValue::KeepExistingFlags ); - } - - // Set script context - QScriptContext *parent = context->parentContext(); - if ( parent ) { - context->setActivationObject( parent->activationObject() ); - context->setThisObject( parent->thisObject() ); - } - - // Store included files in global property "includedFiles" - includedFiles << filePath; - includedFiles.removeDuplicates(); - QScriptValue::PropertyFlags flags = QScriptValue::ReadOnly | QScriptValue::Undeletable; - engine->globalObject().setProperty( "includedFiles", engine->newVariant(includedFiles), flags ); - - // Evaluate script - return engine->evaluate( program, filePath ); -} - -quint16 maxIncludeLine( const QString &program ) -{ - // Get first lines of script code until the first statement which is not an include() call - // The regular expression matches in blocks: Multiline comments (needs non-greedy regexp), - // one line comments, whitespaces & newlines, include("...") calls and semi-colons (needed, - // because the regexp is non-greedy) - QRegExp programRegExp( "\\s*(?:\\s*//[^\\n]*\\n|/\\*.*\\*/|[\\s\\n]+|include\\s*\\(\\s*\"[^\"]*\"\\s*\\)|;)+" ); - programRegExp.setMinimal( true ); - int pos = 0, nextPos = 0; - QString programBegin; - while ( (pos = programRegExp.indexIn(program, pos)) == nextPos ) { - const QString capture = programRegExp.cap(); - programBegin.append( capture ); - pos += programRegExp.matchedLength(); - nextPos = pos; - } - return programBegin.count( '\n' ); -} - -bool ScriptJob::loadScript( const QSharedPointer< QScriptProgram > &script ) -{ - if ( !script ) { - kDebug() << "Invalid script data"; - return false; - } - - // Create script engine - QMutexLocker locker( m_mutex ); - m_engine = new QScriptEngine(); - foreach ( const QString &extension, m_data.provider.scriptExtensions() ) { - if ( !importExtension(m_engine, extension) ) { - m_errorString = i18nc("@info/plain", "Could not load script extension " - "%1.", extension); - m_success = false; - cleanup(); - return false; - } - } - - // Create and attach script objects. - // The Storage object is already created and will not be replaced by a new instance. - // It lives in the GUI thread and gets used in all thread jobs to not erase non-persistently - // stored data after each request. - m_objects.createObjects( m_data ); - m_objects.attachToEngine( m_engine, m_data ); - - // Connect the publish() signal directly (the result object lives in the thread that gets - // run by this job, this job itself lives in the GUI thread). Connect directly to ensure - // the script objects and the request are still valid (prevent crashes). - connect( m_objects.result.data(), SIGNAL(publish()), this, SLOT(publish()), - Qt::DirectConnection ); - - // Load the script program - m_mutex->unlock(); - m_engine->evaluate( *script ); - m_mutex->lock(); - - if ( m_engine->hasUncaughtException() ) { - kDebug() << "Error in the script" << m_engine->uncaughtExceptionLineNumber() - << m_engine->uncaughtException().toString(); - kDebug() << "Backtrace:" << m_engine->uncaughtExceptionBacktrace().join("\n"); - m_errorString = i18nc("@info/plain", "Error in script, line %1: %2.", - m_engine->uncaughtExceptionLineNumber(), - m_engine->uncaughtException().toString()); - m_success = false; - cleanup(); - return false; - } else { - return true; - } -} - -bool ScriptJob::hasDataToBePublished() const -{ - QMutexLocker locker( m_mutex ); - return !m_objects.isValid() ? false : m_objects.result->count() > m_published; -} - -void ScriptJob::publish() -{ - // This slot gets run in the thread of this job - // Only publish, if there is data which is not already published - if ( hasDataToBePublished() ) { - m_mutex->lock(); - GlobalTimetableInfo globalInfo; - const QList< TimetableData > data = m_objects.result->data().mid( m_published ); - const ResultObject::Features features = m_objects.result->features(); - const ResultObject::Hints hints = m_objects.result->hints(); - const QString lastUserUrl = m_objects.network->lastUserUrl(); - const bool couldNeedForcedUpdate = m_published > 0; - const MoreItemsRequest *moreItemsRequest = dynamic_cast(request()); - const AbstractRequest *_request = - moreItemsRequest ? moreItemsRequest->request().data() : request(); - const ParseDocumentMode parseMode = request()->parseMode(); - - m_lastUrl = m_objects.network->lastUrl(); // TODO Store all URLs - m_lastUserUrl = m_objects.network->lastUserUrl(); - m_published += data.count(); - - // Unlock mutex after copying the request object, then emit it with an xxxReady() signal - switch ( parseMode ) { - case ParseForDepartures: { - Q_ASSERT(dynamic_cast(_request)); - const DepartureRequest departureRequest = - *dynamic_cast(_request); - m_mutex->unlock(); - emit departuresReady( data, features, hints, lastUserUrl, globalInfo, - departureRequest, couldNeedForcedUpdate ); - break; - } - case ParseForArrivals: { - Q_ASSERT(dynamic_cast(_request)); - const ArrivalRequest arrivalRequest = *dynamic_cast(_request); - m_mutex->unlock(); - emit arrivalsReady( data, features, hints, lastUserUrl, globalInfo, - arrivalRequest, couldNeedForcedUpdate ); - break; - } - case ParseForJourneysByDepartureTime: - case ParseForJourneysByArrivalTime: { - Q_ASSERT(dynamic_cast(_request)); - const JourneyRequest journeyRequest = *dynamic_cast(_request); - m_mutex->unlock(); - emit journeysReady( data, features, hints, lastUserUrl, globalInfo, - journeyRequest, couldNeedForcedUpdate ); - break; - } - case ParseForStopSuggestions: { - Q_ASSERT(dynamic_cast(_request)); - const StopSuggestionRequest stopSuggestionRequest = - *dynamic_cast< const StopSuggestionRequest* >( _request ); - m_mutex->unlock(); - emit stopSuggestionsReady( data, features, hints, lastUserUrl, globalInfo, - stopSuggestionRequest, couldNeedForcedUpdate ); - break; - } - case ParseForAdditionalData: { - if ( data.count() > 1 ) { - qWarning() << "The script added more than one result in an additional data request"; - kDebug() << "All received additional data for item" - << dynamic_cast(_request)->itemNumber() - << ':' << data; - m_errorString = i18nc("@info/plain", "The script added more than one result in " - "an additional data request."); - m_success = false; - cleanup(); - m_mutex->unlock(); - return; - } - - // Additional data gets requested per timetable item, only one result expected - const TimetableData additionalData = data.first(); - if ( additionalData.isEmpty() ) { - qWarning() << "Did not find any additional data."; - m_errorString = i18nc("@info/plain", "TODO."); // TODO - m_success = false; - cleanup(); - m_mutex->unlock(); - return; - } - - Q_ASSERT(dynamic_cast(_request)); - const AdditionalDataRequest additionalDataRequest = - *dynamic_cast< const AdditionalDataRequest* >( _request ); - m_mutex->unlock(); - - emit additionalDataReady( additionalData, features, hints, lastUserUrl, globalInfo, - additionalDataRequest, couldNeedForcedUpdate ); - break; - } - - default: - m_mutex->unlock(); - kDebug() << "Parse mode unsupported:" << parseMode; - break; - } - } -} - -class DepartureJobPrivate { -public: - DepartureJobPrivate( const DepartureRequest &request ) : request(request) {}; - DepartureRequest request; -}; - -DepartureJob::DepartureJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - const DepartureRequest& request, QObject* parent ) - : ScriptJob(data, scriptStorage, parent), d(new DepartureJobPrivate(request)) -{ -} - -DepartureJob::~DepartureJob() -{ - delete d; -} - - -const AbstractRequest *DepartureJob::request() const -{ - QMutexLocker locker( m_mutex ); - return &d->request; -} - -QString ScriptJob::sourceName() const -{ - QMutexLocker locker( m_mutex ); - return request()->sourceName(); -} - -const AbstractRequest *ScriptJob::cloneRequest() const -{ - QMutexLocker locker( m_mutex ); - return request()->clone(); -} - -class ArrivalJobPrivate { -public: - ArrivalJobPrivate( const ArrivalRequest &request ) : request(request) {}; - ArrivalRequest request; -}; - -ArrivalJob::ArrivalJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - const ArrivalRequest& request, QObject* parent ) - : ScriptJob(data, scriptStorage, parent), d(new ArrivalJobPrivate(request)) -{ -} - -ArrivalJob::~ArrivalJob() -{ - delete d; -} - - -const AbstractRequest *ArrivalJob::request() const -{ - QMutexLocker locker( m_mutex ); - return &d->request; -} - -class JourneyJobPrivate { -public: - JourneyJobPrivate( const JourneyRequest &request ) : request(request) {}; - JourneyRequest request; -}; - -JourneyJob::JourneyJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - const JourneyRequest& request, QObject* parent ) - : ScriptJob(data, scriptStorage, parent), d(new JourneyJobPrivate(request)) -{ -} - -JourneyJob::~JourneyJob() -{ - delete d; -} - -const AbstractRequest *JourneyJob::request() const -{ - QMutexLocker locker( m_mutex ); - return &d->request; -} - -class StopSuggestionsJobPrivate { -public: - StopSuggestionsJobPrivate( const StopSuggestionRequest &request ) : request(request) {}; - StopSuggestionRequest request; -}; - -StopSuggestionsJob::StopSuggestionsJob( const ScriptData &data, - const QSharedPointer< Storage > &scriptStorage, - const StopSuggestionRequest& request, QObject* parent ) - : ScriptJob(data, scriptStorage, parent), d(new StopSuggestionsJobPrivate(request)) -{ -} - -StopSuggestionsJob::~StopSuggestionsJob() -{ - delete d; -} - -const AbstractRequest *StopSuggestionsJob::request() const -{ - QMutexLocker locker( m_mutex ); - return &d->request; -} - -class StopsByGeoPositionJobPrivate { -public: - StopsByGeoPositionJobPrivate( const StopsByGeoPositionRequest &request ) - : request(request) {}; - StopsByGeoPositionRequest request; -}; - -StopsByGeoPositionJob::StopsByGeoPositionJob( - const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - const StopsByGeoPositionRequest& request, QObject* parent ) - : ScriptJob(data, scriptStorage, parent), d(new StopsByGeoPositionJobPrivate(request)) -{ -} - -StopsByGeoPositionJob::~StopsByGeoPositionJob() -{ - delete d; -} - -const AbstractRequest *StopsByGeoPositionJob::request() const -{ - QMutexLocker locker( m_mutex ); - return &d->request; -} - -class AdditionalDataJobPrivate { -public: - AdditionalDataJobPrivate( const AdditionalDataRequest &request ) : request(request) {}; - AdditionalDataRequest request; -}; - -AdditionalDataJob::AdditionalDataJob( const ScriptData &data, - const QSharedPointer< Storage > &scriptStorage, - const AdditionalDataRequest &request, QObject *parent ) - : ScriptJob(data, scriptStorage, parent), d(new AdditionalDataJobPrivate(request)) -{ -} - -AdditionalDataJob::~AdditionalDataJob() -{ - delete d; -} - -const AbstractRequest *AdditionalDataJob::request() const -{ - QMutexLocker locker( m_mutex ); - return &d->request; -} - -class MoreItemsJobPrivate { -public: - MoreItemsJobPrivate( const MoreItemsRequest &request ) : request(request) {}; - MoreItemsRequest request; -}; - -MoreItemsJob::MoreItemsJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - const MoreItemsRequest &request, QObject *parent ) - : ScriptJob(data, scriptStorage, parent), d(new MoreItemsJobPrivate(request)) -{ -} - -MoreItemsJob::~MoreItemsJob() -{ - delete d; -} - -const AbstractRequest *MoreItemsJob::request() const -{ - QMutexLocker locker( m_mutex ); - return &d->request; -} - -bool ScriptJob::success() const -{ - QMutexLocker locker( m_mutex ); - return m_success; -} - -int ScriptJob::publishedItems() const -{ - QMutexLocker locker( m_mutex ); - return m_published; -} -QString ScriptJob::errorString() const -{ - QMutexLocker locker( m_mutex ); - return m_errorString; -} - -QString ScriptJob::lastDownloadUrl() const -{ - QMutexLocker locker( m_mutex ); - return m_lastUrl; -} - -QString ScriptJob::lastUserUrl() const -{ - QMutexLocker locker( m_mutex ); - return m_lastUserUrl; -} diff --git a/engine/script/script_thread.h b/engine/script/script_thread.h deleted file mode 100644 index 5987813..0000000 --- a/engine/script/script_thread.h +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** @file -* @brief This file contains a thread which executes service provider plugin scripts. -* -* @author Friedrich Pülz */ - -#ifndef SCRIPTTHREAD_HEADER -#define SCRIPTTHREAD_HEADER - -// Own includes -#include "enums.h" -#include "scriptapi.h" -#include "scriptobjects.h" -#include "serviceproviderdata.h" - -// KDE includes -#include - -// Qt includes -#include // Base class -#include - -class QMutex; -class AbstractRequest; -class DepartureRequest; -class ArrivalRequest; -class JourneyRequest; -class StopSuggestionRequest; -class StopsByGeoPositionRequest; -class AdditionalDataRequest; -class MoreItemsRequest; - -class ServiceProviderData; -namespace ScriptApi { - class Storage; - class Network; - class Helper; - class ResultObject; -} - -class QScriptProgram; -class QScriptEngine; -class QEventLoop; - -using namespace ScriptApi; - -/** @brief Stores information about a departure/arrival/journey/stop suggestion. */ -typedef QHash TimetableData; - -typedef NetworkRequest* NetworkRequestPtr; -QScriptValue networkRequestToScript( QScriptEngine *engine, const NetworkRequestPtr &request ); -void networkRequestFromScript( const QScriptValue &object, NetworkRequestPtr &request ); -bool importExtension( QScriptEngine *engine, const QString &extension ); - -/** - * @brief Script function to include external script files. - * - * Calls to this function need to be the first statements in the global context of the script file, - * otherwise an exception gets thrown. It expects one argument, the name of the file to be included, - * without it's path. The file needs to be in the same directory as the main script. - * If the file is already included this function does nothing. A list of included files gets stored - * in the engine's global object, in the "includedFiles" property, as QStringList. - * @see maxIncludeLine() - **/ -QScriptValue include( QScriptContext *context, QScriptEngine *engine ); - -/** @brief Get the maximum line number for valid include() calls in @p program. */ -quint16 maxIncludeLine( const QString &program ); - -/** - * @brief A QScriptEngineAgent that signals when a script finishes. - * - * After a function exit the agent waits a little bit and checks if the script is still executing - * using QScriptEngineAgent::isEvaluating(). - **/ -class ScriptAgent : public QObject, public QScriptEngineAgent { - Q_OBJECT - -public: - /** @brief Creates a new ScriptAgent instance. */ - ScriptAgent( QScriptEngine* engine = 0, QObject *parent = 0 ); - - /** Overwritten to get noticed when a script might have finished. */ - virtual void functionExit( qint64 scriptId, const QScriptValue& returnValue ); - -signals: - /** @brief Emitted, when the script is no longer running */ - void scriptFinished(); - -protected slots: - void checkExecution(); -}; - -/** @brief Implements the script function 'importExtension()'. */ -bool importExtension( QScriptEngine *engine, const QString &extension ); - -/** - * @brief Executes a script. - **/ -class ScriptJob : public QObject, public ThreadWeaver::Job { - Q_OBJECT - -public: - /** - * @brief Creates a new ScriptJob. - * - * @param script The script to executes. - * @param data Information about the service provider. - * @param scriptStorage The shared Storage object. - **/ - explicit ScriptJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - QObject* parent = 0 ); - - /** @brief Destructor. */ - virtual ~ScriptJob(); - - /** @brief Abort the job. */ - virtual void requestAbort(); - - /** @brief Overwritten from ThreadWeaver::Job to return whether or not the job was successful. */ - virtual bool success() const; - - /** @brief Return the number of items which are already published. */ - int publishedItems() const; - - /** @brief Return a string describing the error, if success() returns false. */ - QString errorString() const; - - /** @brief Return the URL of the last finished request. */ - QString lastDownloadUrl() const; - - /** @brief Return an URL for the last finished request that should be shown to users. */ - QString lastUserUrl() const; - - /** @brief Get the data source name associated with this job. */ - QString sourceName() const; - - /** @brief Return a copy of the object containing inforamtion about the request of this job. */ - const AbstractRequest *cloneRequest() const; - -signals: - /** @brief Signals ready TimetableData items. */ - void departuresReady( const QList &departures, - ResultObject::Features features, ResultObject::Hints hints, - const QString &url, const GlobalTimetableInfo &globalInfo, - const DepartureRequest &request, bool couldNeedForcedUpdate = false ); - - /** @brief Signals ready TimetableData items. */ - void arrivalsReady( const QList &arrivals, - ResultObject::Features features, ResultObject::Hints hints, - const QString &url, const GlobalTimetableInfo &globalInfo, - const ArrivalRequest &request, bool couldNeedForcedUpdate = false ); - - /** @brief Signals ready TimetableData items. */ - void journeysReady( const QList &journeys, - ResultObject::Features features, ResultObject::Hints hints, - const QString &url, const GlobalTimetableInfo &globalInfo, - const JourneyRequest &request, bool couldNeedForcedUpdate = false ); - - /** @brief Signals ready TimetableData items. */ - void stopSuggestionsReady( const QList &stops, - ResultObject::Features features, ResultObject::Hints hints, - const QString &url, const GlobalTimetableInfo &globalInfo, - const StopSuggestionRequest &request, - bool couldNeedForcedUpdate = false ); - - /** @brief Signals ready additional data for a TimetableData item. */ - void additionalDataReady( const TimetableData &data, - ResultObject::Features features, ResultObject::Hints hints, - const QString &url, const GlobalTimetableInfo &globalInfo, - const AdditionalDataRequest &request, - bool couldNeedForcedUpdate = false ); - -protected slots: - /** @brief Handle the ResultObject::publish() signal by emitting dataReady(). */ - void publish(); - -protected: - /** @brief Perform the job. */ - virtual void run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread); - - /** @brief Return a pointer to the object containing information about the request of this job. */ - virtual const AbstractRequest* request() const = 0; - - /** @brief Load @p script into the engine and insert some objects/functions. */ - bool loadScript( const QSharedPointer< QScriptProgram > &script ); - - bool waitFor( QObject *sender, const char *signal, WaitForType type, int *timeout ); - - bool hasDataToBePublished() const; - - void handleError( const QString &errorMessage ); - - void cleanup(); - - QScriptEngine *m_engine; - QMutex *m_mutex; - ScriptData m_data; - ScriptObjects m_objects; - QEventLoop *m_eventLoop; - int m_published; - bool m_quit; - bool m_success; - QString m_errorString; - QString m_lastUrl; - QString m_lastUserUrl; -}; - -class DepartureJobPrivate; -class DepartureJob : public ScriptJob { - Q_OBJECT - -public: - explicit DepartureJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - const DepartureRequest& request, QObject* parent = 0); - - virtual ~DepartureJob(); - -protected: - virtual const AbstractRequest *request() const; - -private: - const DepartureJobPrivate *d; -}; - -class ArrivalJobPrivate; -class ArrivalJob : public ScriptJob { - Q_OBJECT - -public: - explicit ArrivalJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - const ArrivalRequest& request, QObject* parent = 0); - - virtual ~ArrivalJob(); - -protected: - virtual const AbstractRequest* request() const; - -private: - const ArrivalJobPrivate *d; -}; - -class JourneyJobPrivate; -class JourneyJob : public ScriptJob { - Q_OBJECT - -public: - explicit JourneyJob( const ScriptData &data, const QSharedPointer< Storage > &scriptStorage, - const JourneyRequest& request, QObject* parent = 0); - - virtual ~JourneyJob(); - -protected: - virtual const AbstractRequest* request() const; - -private: - const JourneyJobPrivate *d; -}; - -class StopSuggestionsJobPrivate; -class StopSuggestionsJob : public ScriptJob { - Q_OBJECT - -public: - explicit StopSuggestionsJob( const ScriptData &data, - const QSharedPointer< Storage > &scriptStorage, - const StopSuggestionRequest& request, - QObject* parent = 0); - - virtual ~StopSuggestionsJob(); - -protected: - virtual const AbstractRequest* request() const; - -private: - const StopSuggestionsJobPrivate *d; -}; - -class StopsByGeoPositionJobPrivate; -class StopsByGeoPositionJob : public ScriptJob { - Q_OBJECT - -public: - explicit StopsByGeoPositionJob( const ScriptData &data, - const QSharedPointer< Storage > &scriptStorage, - const StopsByGeoPositionRequest& request, QObject* parent = 0); - - virtual ~StopsByGeoPositionJob(); - -protected: - virtual const AbstractRequest* request() const; - -private: - const StopsByGeoPositionJobPrivate *d; -}; - -class AdditionalDataJobPrivate; -class AdditionalDataJob : public ScriptJob { - Q_OBJECT - -public: - explicit AdditionalDataJob( const ScriptData &data, - const QSharedPointer< Storage > &scriptStorage, - const AdditionalDataRequest& request, - QObject* parent = 0); - virtual ~AdditionalDataJob(); - -protected: - virtual const AbstractRequest* request() const; - -private: - const AdditionalDataJobPrivate *d; -}; - -class MoreItemsJobPrivate; -class MoreItemsJob : public ScriptJob { - Q_OBJECT - -public: - explicit MoreItemsJob( const ScriptData &data, - const QSharedPointer< Storage > &scriptStorage, - const MoreItemsRequest& request, - QObject* parent = 0); - virtual ~MoreItemsJob(); - -protected: - virtual const AbstractRequest* request() const; - -private: - const MoreItemsJobPrivate *d; -}; - -#endif // Multiple inclusion guard diff --git a/engine/script/scriptapi-doc.h b/engine/script/scriptapi-doc.h deleted file mode 100644 index 3bce81c..0000000 --- a/engine/script/scriptapi-doc.h +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright 2012 Friedrich Pülz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** - * @defgroup scriptApi Provider Plugin Script API - * - * These classes get exposed to scripts or are used by scripted service provider plugins. - * Each call to a script from the data engine creates a new thread using ThreadWeaver. Each thread - * uses it's own QScriptEngine instance to execute the script. - * - * Scripts are written in ECMAScript, but they can access Kross to support other languages, ie. - * Python or Ruby. Kross needs to be imported explicitly. That can be done by adding an - * @em "extensions" attribute to the @em \ tag in the XML file, like this: - * @verbatim@endverbatim. Other extensions can also be - * imported, eg. "qt.core" to use classes provided by the "qt.core" bindings. - * - * To use eg. Python code in the script, the following code can then be used in a script: - * @code - * // Create Kross action - * var action = Kross.action( "MyPythonScript" ); - * - * // Propagate action to the Python script - * action.addQObject( action, "MyAction" ); - * - * // Set the interpreter to use, eg. "python", "ruby" - * action.setInterpreter( "python" ); - * - * // Set the code to execute and trigger execution - * action.setCode("import MyAction ; print 'This is Python. name=>',MyAction.interpreter()"); - * action.trigger(); - * @endcode - * @todo If needed a later version might make this simpler by examining the file type of the script - * file and automatically insert the contents of the script file into the setCode() function - * like above. - * - * @n - * - * @section script_exposed Classes Exposed to Scripts - * - * Scripts can access some objects that represent classes mentioned here. Only one instance of - * these classes is available in a script. - * @li @em ResultObject Stores results, ie. parsed data for departures, arrivals, journeys or - * stop suggestions. Available for scripts under the name @b result. - * @li @em Network Provides network access to scripts and is available for scripts under - * the name @b network. This class can create objects of type NetworkRequest using - * Network::createRequest() for asynchronous requests. NetworkRequest objects have signals - * that scripts can connect to, ie. NetworkRequest::finished() to indicate a finished request. - * @li @em Storage Stores data between script calls. Can store data in memory or persistently, - * ie. on disk. An object of this class is available for scripts under the name @b storage. - * The storage object gets shared between calls to the same script (for the same service - * provider) and can be use by multiple script instances at the same time. - * @li @em Helper Provides some helper functions to scripts, available under the name @b helper. - * - * @n - * - * @section script_functions Script Functions to be Implemented - * - * There should be specially named functions in the script, that get called by the data engine. - * Only the @em getTimetable function @em must be implemented. - * - * There is a flexible base script available for providers using the HAFAS API. - * See @ref examples_script. - * - * @subsection script_functions_gettimetable getTimetable( values ) - * This function @em must be implemented (but that may change). - * @note This function gets called to get departures @em or arrivals, depending on the - * @em dataType property of the parameter. If arrivals are supported add the - * Enums::ProvidesArrivals feature to the return value of the features() script - * function. - * - * The only argument contains information about the request with these properties: - * @li @em stop: The name/ID of the stop to get departures/arrivals for. - * @li @em stopIsId: @c True, if @em stop contains an ID, @c false otherwise. - * @li @em dateTime: A Date object with the date and time of the earliest departure/arrival to get. - * @li @em dataType: This can be "arrivals" or "departures". - * @li @em city: If used, this contains the city name to get departures/arrivals for. Only some - * service providers need a separate city value, most are happy with a stop name/stop ID. - * @li @em count: The number of departures/arrivals to get. - * - * @subsection script_functions_getstopsuggestions getStopSuggestions( values ) - * Gets called to request stop suggestions. Since it may be called very often it should be fast, - * ie. the downloaded data should be as small as possible. - * @note There are two types of stop requests: - * stop suggestions get requested with the parameters @em stop, @em city and @em count, - * while stops by geolocation get requested with @em longitude, @em latitude, - * @em distance and @em count and require the Enums::ProvidesStopsByGeoPosition feature to be - * returned by the features() script function. - * - * @li @em stop: A part of a stop name to get suggestions for. This is what users can type in, eg. - * the beginning of the complete stop name. - * @li @em city: If used, this contains the city name to get stop suggestions for. Only some - * service providers need a separate city value, most are happy with a part of the stop name. - * @li @em longitude: Used together with @em latitude and @em distance for stop requests by - * geolocation, which is given by @em longitude and @em latitude. - * @li @em latitude: Used together with @em longitude and @em distance for stop requests by - * geolocation, which is given by @em longitude and @em latitude. - * @li @em distance: The distance in meters around the geolocation given with @em longitude and - * @em latitude where to search for stops. - * @li @em count: The number of stop suggestions to get. - * - * @subsection script_functions_getjourneys getJourneys( values ) - * Gets called to request journeys (trips from stop A to stop B). - * @note Journey requests are also used to get more journeys after a previous request. In this - * case the properties @em moreItemsDirection and @em requestData are available and - * @em moreItemsDirection does not equal to @em PublicTransport.RequestedItems. - * The result currently also needs to include the first received set of journeys. - * - * The only argument contains information about the request with these properties: - * @li @em originStop: The name/ID of the start/origin stop, also available as 'stop' property. - * @li @em targetStop: The name/ID of the target/destination stop. - * @li @em originStopIsId: @c True, if @em originStop contains an ID, @c false otherwise. - * @li @em targetStopIsId: @c True, if @em targetStop contains an ID, @c false otherwise. - * @li @em dateTime: A Date object with the date and time of the earliest journey to get. - * @li @em dataType: This can be "journeys"/"journeysDep" (journeys departing at the given @em dateTime) - * or "journeysArr" (journeys arriving at the given @em dateTime). - * @li @em city: If used, this contains the city name to get journeys for. Only some - * service providers need a separate city value, most are happy with a stop name/stop ID. - * @li @em count: The number of journeys to get. - * @li @em moreItemsDirection If this is undefined or @em PublicTransport.RequestedItems this - * is a normal journey request. Otherwise this is a following journey request to get more - * journeys and is @em PublicTransport.EarlierItems or @em PublicTransport.LaterItems. - * See Enums::MoreItemsDirection. In this case the field @em requestData is also available. - * @li @em requestData This contains data that was stored by the script in the first journeys - * request as @em PublicTransport.RequestData. If this is not a following journey request - * this property is not used. It can be used to eg. store a request ID. - * - * @subsection script_functions_getadditionaldata getAdditionalData( values ) - * Gets called to request additional data for an already received timetable item (eg. a - * departure). The provider itself decides what additional data actually is. For example the - * HAFAS base script uses this to get route information for departures/arrivals, which are not - * available otherwise. The only argument contains information about the timetable item for which - * to get additional data for and has these properties: - * @li @em stop: The name of the stop, that was used to get the timetable item. - * @li @em city: If used, this contains the city name that was used to get the timetable item. - * Only some service providers need a separate city value, most are happy with a stop - * name/stop ID. - * @li @em dataType: The type of data that was requested to get the timetable item, eg. - * "departures", "arrivals", etc. - * @li @em dateTime: A Date object with the date and time of the timetable item, - * eg. it's departure. - * @li @em transportLine: The transport line of the timetable item. - * @li @em target: The target of the timetable item. - * @li @em routeDataUrl: An URL to a document that contains route information, ie. additional - * data. If this is empty it needs to be found out somehow. For example HAFAS providers load - * the departure board again but in a different format that includes these URLs (and caches - * URLs for multiple timetable items for later use). To do so and to find the correct timetable - * item in the other format, the other properties like @em dateTime, @em transportLine and - * @em target get used. - * - * @subsection script_functions_features features() - * Can be implemented to make the data engine aware of supported features. - * See Enums::ProviderFeature for a list of available features. Those enumerables are available - * to scripts in the @em PublicTransport object. - * - * Some features like @em PublicTransport.ProvidesJourneys are detected automatically - * (getJourneys() function implemented?). Others are only used to inform the user, - * eg. @em PublicTransport.ProvidesPricing. Some features are also required in the returned - * list of this function for those features to actually work, - * eg. @em PublicTransport.ProvidesStopGeoPosition. - * - * @n - * - * @section script_collecting_items Collecting Parsed Items - * The object @b result (ResultObject) gets used by scripts to collect parsed departures/ - * arrivals/journeys/stop suggestions. It provides a function ResultObject::addData(), which - * accepts an object with properties that have special names. A simple departure item can be added - * to the result object like this: - * @code - * result.addData({ DepartureDateTime: new Date(), - * VehicleType: PublicTransport.Bus, - * Target: "SomeTarget" }); - * @endcode - * - * Another possibility is to assign the properties when they get parsed, like this: - * @code - * var departure = {}; - * departure.DepartureDateTime = new Date(); - * departure.VehicleType = PublicTransport.Bus; - * departure.Target = "SomeTarget"; - * result.addData( departure ); - * @endcode - * - * You can also use enumerable values to store data (available in @em PublicTransport): - * @code - * var departure = {}; - * departure[ PublicTransport.DepartureDateTime ] = new Date(); - * departure[ PublicTransport.VehicleType ] = PublicTransport.Bus; - * departure[ PublicTransport.Target ] = "SomeTarget"; - * result.addData( departure ); - * @endcode - * - * The names of the properties are important, but upper or lower case does not matter. - * All entries in the Enums::TimetableInformation enumerable can be used to add information, look - * there fore more detailed information. This enumerable is a central point of the Public Transport - * data engine and gets used by all service provider plugin types to store information about results. - * - * @n - * @subsection script_collecting_items_departures Information Types Used for Departures/Arrivals - * @em DepartureDateTime, @em DepartureDate, @em DepartureTime, @em TypeOfVehicle, - * @em TransportLine, @em FlightNumber (alias for @em TransportLine), @em Target, - * @em TargetShortened, @em Platform, @em Delay, @em DelayReason, @em JourneyNews, - * @em JourneyNewsOther, @em JourneyNewsLink, @em Operator, @em Status, @em RouteStops, - * @em RouteStopsShortened, @em RouteTimes, @em RouteTimesDeparture, @em RouteTimesArrival, - * @em RouteExactStops, @em RouteTypesOfVehicles, @em RouteTransportLines, - * @em RoutePlatformsDeparture, @em RoutePlatformsArrival, @em RouteTimesDepartureDelay, - * @em RouteTimesArrivalDelay, @em IsNightLine (currently unused). - * @note At least these information types are needed to form a valid departure/arrival object: - * @em DepartureDateTime or @em DepartureTime (the date can be omitted, but that can produce - * wrong guessed dates), @em TypeOfVehicle and @em TransportLine. - * @note When arrivals are requested, @em DepartureDateTime, @em DepartureDate and - * @em DepartureTime stand actually for the arrival date/time. The names that start with @em Arrival are - * used for journeys only. - * @todo This might change, allowing both for arrivals. - * @see Enums::TimetableInformation - * - * @n - * @subsection script_collecting_items_journeys Information Types Used for Journeys - * @em DepartureDateTime, @em DepartureDate, @em DepartureTime, @em Duration, @em StartStopName, - * @em StartStopID, @em TargetStopName, @em TargetStopID, @em ArrivalDateTime, @em ArrivalDate, - * @em ArrivalTime, @em Changes, @em TypesOfVehicleInJourney, @em Pricing, @em RouteStops, - * @em RouteStopsShortened, @em RouteTimes, @em RouteTimesDeparture, @em RouteTimesArrival, - * @em RouteExactStops, @em RouteTypesOfVehicles, @em RouteTransportLines, - * @em RoutePlatformsDeparture, @em RoutePlatformsArrival, @em RouteTimesDepartureDelay, - * @em RouteTimesArrivalDelay. - * @note At least these information types are needed to form a valid journey object: - * @em DepartureDateTime or @em DepartureTime, @em ArrivalDateTime or @em ArrivalTime, - * @em StartStopName and @em TargetStopName. - * @see Enums::TimetableInformation - * - * @n - * @subsection script_collecting_items_journeys Information Types Used for Stop Suggestions - * @em StopName, @em StopID, @em StopWeight, @em StopCity, @em StopCountryCode. - * @note Only @em StopName is required to form a valid stop suggestion object. - * @see Enums::TimetableInformation - * - * @n - * @subsection script_collecting_items_vehicletypes Vehicle Types - * - * Vehicle types can be given as enumerable values or names (in @em TypeOfVehicle, - * @em RouteTypesOfVehicles, @em TypesOfVehicleInJourney), see @ref Enums::VehicleType. @n - * - * These are the enumerables of currently supported vehicle types (the names without - * "PublicTransport." can also be used as vehicle type):@n - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
PublicTransport.Unknown
@image html hi16-app-vehicle_type_tram.png - * PublicTransport.Tram
@image html hi16-app-vehicle_type_bus.png - * PublicTransport.Bus
@image html hi16-app-vehicle_type_subway.png - * PublicTransport.Subway
@image html hi16-app-vehicle_type_train_interurban.png - * PublicTransport.InterurbanTrain
@image html hi16-app-vehicle_type_metro.png - * PublicTransport.Metro
@image html hi16-app-vehicle_type_trolleybus.png - * PublicTransport.TrolleyBus
@image html hi16-app-vehicle_type_train_regional.png - * PublicTransport.RegionalTrain
@image html hi16-app-vehicle_type_train_regional.png - * PublicTransport.RegionalExpressTrain
@image html hi16-app-vehicle_type_train_interregional.png - * PublicTransport.InterregionalTrain
@image html hi16-app-vehicle_type_train_intercity.png - * PublicTransport.IntercityTrain
@image html hi16-app-vehicle_type_train_highspeed.png - * PublicTransport.HighSpeedTrain
@image html hi16-app-vehicle_type_feet.png - * PublicTransport.Footway" (for journeys to walk from one intermediate stop to the next)
@image html hi16-app-vehicle_type_ferry.png - * PublicTransport.Ferry
@image html hi16-app-vehicle_type_ferry.png - * PublicTransport.Ship
@image html hi16-app-vehicle_type_plane.png - * PublicTransport.Plane
- * - * @see Enums::TimetableInformation - * @see Enums::VehicleType - * - * @n - * - * @section script_advanced Advanced Features - * - * Scripts can use some additional features. For example the Storage can be used to store data - * that would otherwise have to be downloaded and parsed over and over again. Data can be stored - * in memory (for the current session only) or persistently on disk (using KConfig). - * @see Storage - * - * @n - * - * The ResultObject class has additional functions other than ResultObject::addData(). For example - * the ResultObject::publish() function can be used to tell the data engine to publish the items - * parsed so far to visualizations. A good use case is to call publish() when a document - * has been read but for more results another document needs to be downloaded first. - * @note By default data is automatically published after the first few items to provide - * visualizations with data as soon as possible. Use ResultObject::enableFeature() to change - * this behaviour. - * - * There is also a Hint enumeration to give hints to the data engine. Use ResultObject::giveHint() - * to give a hint. - **/ diff --git a/engine/script/scriptapi.cpp b/engine/script/scriptapi.cpp deleted file mode 100644 index 7c2d392..0000000 --- a/engine/script/scriptapi.cpp +++ /dev/null @@ -1,2379 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -// Header -#include "scriptapi.h" - -// Own includes -#include "config.h" -#include "global.h" -#include "serviceproviderglobal.h" - -// KDE includes - -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Other includes -#include -#include - -namespace ScriptApi { - -NetworkRequest::NetworkRequest( QObject *parent ) - : QObject(parent), m_mutex(new QMutex(QMutex::Recursive)), m_network(0), - m_isFinished(false), m_request(0), m_reply(0) -{ -} - -NetworkRequest::NetworkRequest( const QString &url, const QString &userUrl, - Network *network, QObject* parent ) - : QObject(parent), m_mutex(new QMutex(QMutex::Recursive)), m_url(url), - m_userUrl(userUrl.isEmpty() ? url : userUrl), m_network(network), - m_isFinished(false), m_request(new QNetworkRequest(url)), m_reply(0) -{ -} - -NetworkRequest::~NetworkRequest() -{ - abort(); - - delete m_request; - delete m_mutex; -} - -void NetworkRequest::abort() -{ - const bool timedOut = qobject_cast< QTimer* >( sender() ); - if ( !isRunning() ) { - if ( timedOut ) { - kDebug() << "Timeout, but request already finished"; - } - return; - } - - // isRunning() returned true => m_reply != 0 - m_mutex->lock(); - disconnect( m_reply, 0, this, 0 ); - m_reply->abort(); - m_reply->deleteLater(); - m_reply = 0; - const QString url = m_url; - const QVariant userData = m_userData; - m_mutex->unlock(); - - emit aborted( timedOut ); - emit finished( QByteArray(), true, i18nc("@info/plain", "The request was aborted"), - -1, 0, url, userData ); -} - -void NetworkRequest::slotReadyRead() -{ - m_mutex->lock(); - if ( !m_reply ) { - // Prevent crashes on exit - m_mutex->unlock(); - qWarning() << "Reply object already deleted, aborted?"; - return; - } - - // Read all data, decode it and give it to the script - QByteArray data = m_reply->readAll(); - m_data.append( data ); - - if ( data.isEmpty() ) { - qWarning() << "Error downloading" << m_url << m_reply->errorString(); - } - m_mutex->unlock(); - - emit readyRead( data ); -} - -QByteArray gzipDecompress( QByteArray compressData ) -{ - // Strip header and trailer - compressData.remove( 0, 10 ); - compressData.chop( 12 ); - - // FIXME Decompress in one chunk, because otherwise it fails with error -3 "distance too far back", - // estimate size of uncompressed data, expect that the data was compressed at best to 20% size, - // limit to 512KB - const int chunkSize = qMin( int(compressData.size() / 0.2), 512 * 1024 ); - kDebug() << "Chunk size:" << chunkSize; - if ( chunkSize == 512 * 1024 ) { - qWarning() << "Maximum chunk size for decompression reached, may fail"; - } - - unsigned char buffer[ chunkSize ]; - z_stream stream; - stream.next_in = (Bytef*)(compressData.data()); - stream.avail_in = compressData.size(); - stream.total_in = 0; - stream.total_out = 0; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.opaque = Z_NULL; - - if ( inflateInit2(&stream, -8) != Z_OK ) { - kDebug() << "cmpr_stream error!"; - return QByteArray(); - } - - QByteArray uncompressed; - do { - stream.next_out = buffer; - stream.avail_out = chunkSize; - int status = inflate( &stream, Z_SYNC_FLUSH ); - if ( status == Z_OK || status == Z_STREAM_END ) { - uncompressed.append( QByteArray::fromRawData( - (char*)buffer, chunkSize - stream.avail_out) ); - if ( status == Z_STREAM_END ) { - break; - } - } else { - qWarning() << "Error while decompressing" << status << stream.msg; - if ( status == Z_NEED_DICT || status == Z_DATA_ERROR || status == Z_MEM_ERROR ) { - inflateEnd( &stream ); - } - return QByteArray(); - } - } while( stream.avail_out == 0 ); - inflateEnd( &stream ); - return uncompressed; -} - -void NetworkRequest::slotFinished() -{ - m_mutex->lock(); - if ( !m_reply ) { - // Prevent crashes on exit - m_mutex->unlock(); - qWarning() << "Reply object already deleted, aborted?"; - return; - } - - if ( m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid() ) { - if ( m_redirectUrl.isValid() ) { - qWarning() << "Only one redirection allowed, from" << m_url << "to" << m_redirectUrl; - qWarning() << "New redirection to" - << m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); - } else { - m_redirectUrl = m_reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl(); - DEBUG_NETWORK("Redirection to" << m_redirectUrl); - - // Delete the reply - m_reply->deleteLater(); - m_reply = 0; - - delete m_request; - m_request = new QNetworkRequest( m_redirectUrl ); - const QUrl redirectUrl = m_redirectUrl; - m_mutex->unlock(); - - emit redirected( redirectUrl ); - return; - } - } - - const int size = m_reply->size(); - const int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - // Read all data, decode it and give it to the script - m_data.append( m_reply->readAll() ); - - if ( m_data.isEmpty() ) { - qWarning() << "Error downloading" << m_url - << (m_reply ? m_reply->errorString() : "Reply already deleted"); - } - - // Check if the data is compressed and was not decompressed by QNetworkAccessManager - if ( m_data.length() >= 2 && quint8(m_data[0]) == 0x1f && quint8(m_data[1]) == 0x8b ) { - // Data is compressed, uncompress it - // NOTE qUncompress() did not work here, "invalid zip" - m_data = gzipDecompress( m_data ); - DEBUG_NETWORK("Uncompressed data from" << size << "Bytes to" << m_data.size() - << "Bytes, ratio:" << (100 - (m_data.isEmpty() ? 100 : qRound(100 * size / m_data.size()))) << '%'); - } - - DEBUG_NETWORK("Request finished" << m_reply->url()); - if ( m_reply->url().isEmpty() ) { - qWarning() << "Empty URL in QNetworkReply!"; - } - const bool hasError = m_reply->error() != QNetworkReply::NoError; - const QString errorString = m_reply->errorString(); - m_reply->deleteLater(); - m_reply = 0; - - m_isFinished = true; - const QByteArray data = m_data; - const QString url = m_url; - const QVariant userData = m_userData; - m_mutex->unlock(); - - emit finished( data, hasError, errorString, statusCode, size, url, userData ); -} - -void NetworkRequest::started( QNetworkReply* reply, int timeout ) -{ - m_mutex->lock(); - if ( !m_network ) { - qWarning() << "Can't decode, no m_network given..."; - m_mutex->unlock(); - return; - } - m_data.clear(); - m_reply = reply; - - if ( timeout > 0 ) { - QTimer::singleShot( timeout, this, SLOT(abort()) ); - } - - // Connect to signals of reply only when the associated signals of this class are connected - if ( receivers(SIGNAL(readyRead(QByteArray))) > 0 ) { - connect( m_reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead()) ); - } - connect( m_reply, SIGNAL(finished()), this, SLOT(slotFinished()) ); - m_mutex->unlock(); - - emit started(); -} - -bool NetworkRequest::isValid() const -{ - QMutexLocker locker( m_mutex ); - if ( m_request ) { - return true; - } else { - // Default constructor used, not available to scripts - kDebug() << "Request is invalid"; - return false; - } -} - -QByteArray NetworkRequest::getCharset( const QString& charset ) const -{ - QMutexLocker locker( m_mutex ); - QByteArray baCharset; - if ( charset.isEmpty() ) { - // No charset given, use the one specified in the ContentType header - baCharset = m_request->header( QNetworkRequest::ContentTypeHeader ).toByteArray(); - - // No ContentType header, use utf8 - if ( baCharset.isEmpty() ) { - baCharset = "utf8"; - } - } else { - baCharset = charset.toUtf8(); - } - return baCharset; -} - -QByteArray NetworkRequest::postDataByteArray() const -{ - QMutexLocker locker( m_mutex ); - return m_postData; -} - -void NetworkRequest::setPostData( const QString& postData, const QString& charset ) -{ - if ( !isValid() ) { - return; - } - if ( isRunning() ) { - kDebug() << "Cannot set POST data for an already running request!"; - return; - } - - QByteArray baCharset = getCharset( charset ); - QTextCodec *codec = QTextCodec::codecForName( baCharset ); - QMutexLocker locker( m_mutex ); - if ( codec ) { - m_request->setHeader( QNetworkRequest::ContentTypeHeader, baCharset ); - m_postData = codec->fromUnicode( postData ); - } else { - kDebug() << "Codec" << baCharset << "couldn't be found to encode the data " - "to post, now using UTF-8"; - m_request->setHeader( QNetworkRequest::ContentTypeHeader, "utf8" ); - m_postData = postData.toUtf8(); - } -} - -QString NetworkRequest::header( const QString &header, const QString& charset ) const -{ - if ( !isValid() ) { - return QString(); - } - QByteArray baCharset = getCharset( charset ); - QTextCodec *codec = QTextCodec::codecForName( baCharset ); - - QMutexLocker locker( m_mutex ); - return m_request->rawHeader( codec ? codec->fromUnicode(header) : header.toUtf8() ); -} - -void NetworkRequest::setHeader( const QString& header, const QString& value, - const QString& charset ) -{ - if ( !isValid() ) { - return; - } - if ( isRunning() ) { - kDebug() << "Cannot set headers for an already running request!"; - return; - } - - QByteArray baCharset = getCharset( charset ); - QTextCodec *codec = QTextCodec::codecForName( baCharset ); - QMutexLocker locker( m_mutex ); - if ( codec ) { - m_request->setRawHeader( codec->fromUnicode(header), codec->fromUnicode(value) ); - } else { - kDebug() << "Codec" << baCharset << "couldn't be found to encode the data " - "to post, now using UTF-8"; - m_request->setRawHeader( header.toUtf8(), value.toUtf8() ); - } -} - -QNetworkRequest* NetworkRequest::request() const -{ - QMutexLocker locker( m_mutex ); - return m_request; -} - -Network::Network( const QByteArray &fallbackCharset, QObject* parent ) - : QObject(parent), m_mutex(new QMutex(QMutex::Recursive)), - m_fallbackCharset(fallbackCharset), m_manager(new QNetworkAccessManager()), - m_quit(false), m_synchronousRequestCount(0), m_lastDownloadAborted(false) -{ - qRegisterMetaType< NetworkRequest* >( "NetworkRequest*" ); - qRegisterMetaType< NetworkRequest::Ptr >( "NetworkRequest::Ptr" ); -} - -Network::~Network() -{ - // Quit event loop of possibly running synchronous requests - m_mutex->lock(); - m_quit = true; - m_mutex->unlock(); - - // Notify about the abort request to abort running synchronous requests - abortSynchronousRequests(); - qApp->processEvents(); // Handle the signal - - const QList< NetworkRequest::Ptr > _runningRequests = runningRequests(); - if ( !_runningRequests.isEmpty() ) { - qWarning() << "Deleting Network object with" << _runningRequests.count() - << "running requests"; - foreach ( const NetworkRequest::Ptr &request, _runningRequests ) { - request->abort(); - } - } - - delete m_mutex; - m_manager->deleteLater(); -} - -NetworkRequest* Network::createRequest( const QString& url, const QString &userUrl ) -{ - NetworkRequest *request = new NetworkRequest( url, userUrl, this, parent() ); - NetworkRequest::Ptr requestPtr( request ); - - m_mutex->lock(); - m_requests << requestPtr; - m_mutex->unlock(); - - connect( request, SIGNAL(started()), this, SLOT(slotRequestStarted()) ); - connect( request, SIGNAL(finished(QByteArray,bool,QString,int,int)), - this, SLOT(slotRequestFinished(QByteArray,bool,QString,int,int)) ); - connect( request, SIGNAL(aborted()), this, SLOT(slotRequestAborted()) ); - connect( request, SIGNAL(redirected(QUrl)), this, SLOT(slotRequestRedirected(QUrl)) ); - return request; -} - -NetworkRequest::Ptr Network::getSharedRequest( NetworkRequest *request ) const -{ - QMutexLocker locker( m_mutex ); - foreach ( const NetworkRequest::Ptr &sharedRequest, m_requests ) { - if ( sharedRequest.data() == request ) { - return sharedRequest; - } - } - return NetworkRequest::Ptr(); -} - -void Network::slotRequestStarted() -{ - NetworkRequest *request = qobject_cast< NetworkRequest* >( sender() ); - NetworkRequest::Ptr sharedRequest = getSharedRequest( request ); - Q_ASSERT( sharedRequest ); // This slot should only be connected to signals of NetworkRequest - - m_mutex->lock(); - m_lastDownloadAborted = false; - m_mutex->unlock(); - - emit requestStarted( sharedRequest ); -} - -void Network::slotRequestFinished( const QByteArray &data, bool error, const QString &errorString, - int statusCode, int size ) -{ - NetworkRequest *request = qobject_cast< NetworkRequest* >( sender() ); - NetworkRequest::Ptr sharedRequest = getSharedRequest( request ); - Q_ASSERT( sharedRequest ); // This slot should only be connected to signals of NetworkRequest - - // Remove finished request from request list and add it to the list of finished requests - // to not have it deleted here. The script might try to use the request object later, - // eg. because of connected slots, and will crash if the request object was already deleted. - // This way NetworkRequest objects stay alive at least as long as the Network object does. - m_mutex->lock(); - const QDateTime timestamp = QDateTime::currentDateTime(); - m_requests.removeOne( sharedRequest ); - m_finishedRequests << sharedRequest; - m_mutex->unlock(); - - emit requestFinished( sharedRequest, data, error, errorString, timestamp, statusCode, size ); - - if ( !hasRunningRequests() ) { - emit allRequestsFinished(); - } -} - -void Network::slotRequestRedirected( const QUrl &newUrl ) -{ - NetworkRequest *request = qobject_cast< NetworkRequest* >( sender() ); - NetworkRequest::Ptr sharedRequest = getSharedRequest( request ); - Q_ASSERT( sharedRequest ); // This slot should only be connected to signals of NetworkRequest - - m_mutex->lock(); - QNetworkReply *reply = m_manager->get( *request->request() ); - m_lastUrl = newUrl.toString(); - m_mutex->unlock(); - - request->started( reply ); - - DEBUG_NETWORK("Redirected to" << newUrl); - emit requestRedirected( sharedRequest, newUrl ); -} - -void Network::slotRequestAborted() -{ - m_mutex->lock(); - if ( m_quit ) { - m_mutex->unlock(); - return; - } - - NetworkRequest *request = qobject_cast< NetworkRequest* >( sender() ); - NetworkRequest::Ptr sharedRequest = getSharedRequest( request ); - if ( !sharedRequest ) { - qWarning() << "Network object already deleted?"; - } -// Q_ASSERT( sharedRequest ); // This slot should only be connected to signals of NetworkRequest - - DEBUG_NETWORK("Aborted" << request->url()); - m_lastDownloadAborted = true; - m_mutex->unlock(); - - emit requestAborted( sharedRequest ); -} - -bool Network::checkRequest( NetworkRequest* request ) -{ - // Wrong argument type from script or no argument - if ( !request ) { - kDebug() << "Need a NetworkRequest object as argument, create it with " - "'network.createRequest(url)'"; - return false; - } - - // The same request can not be executed more than once at a time - if ( request->isRunning() ) { - kDebug() << "Request is currently running" << request->url(); - return false; - } else if ( request->isFinished() ) { - kDebug() << "Request is already finished" << request->url(); - return false; - } - - return request->isValid(); -} - -void Network::get( NetworkRequest* request, int timeout ) -{ - if ( !checkRequest(request) ) { - return; - } - - // Create a get request - m_mutex->lock(); - QNetworkReply *reply = m_manager->get( *request->request() ); - m_lastUrl = request->url(); - m_lastUserUrl = request->userUrl(); - m_mutex->unlock(); - - request->started( reply, timeout ); -} - -void Network::head( NetworkRequest* request, int timeout ) -{ - if ( !checkRequest(request) ) { - return; - } - - // Create a head request - m_mutex->lock(); - QNetworkReply *reply = m_manager->head( *request->request() ); - m_lastUrl = request->url(); - m_lastUserUrl = request->userUrl(); - m_mutex->unlock(); - - request->started( reply, timeout ); -} - -void Network::post( NetworkRequest* request, int timeout ) -{ - if ( !checkRequest(request) ) { - return; - } - - // Create a head request - m_mutex->lock(); - QNetworkReply *reply = m_manager->post( *request->request(), request->postDataByteArray() ); - m_lastUrl = request->url(); - m_lastUserUrl = request->userUrl(); - m_mutex->unlock(); - - request->started( reply, timeout ); -} - -void Network::abortAllRequests() -{ - const QList< NetworkRequest::Ptr > requests = runningRequests(); - DEBUG_NETWORK("Abort" << requests.count() << "request(s)"); - for ( int i = requests.count() - 1; i >= 0; --i ) { - // Calling abort automatically removes the aborted request from m_createdRequests - requests[i]->abort(); - } - - // Notify about the abort request to abort running synchronous requests - abortSynchronousRequests(); -} - -void Network::abortSynchronousRequests() -{ - emit doAbortSynchronousRequests(); -} - -QByteArray Network::getSynchronous( const QString &url, const QString &userUrl, int timeout ) -{ - // Create a get request - QNetworkRequest request( url ); - DEBUG_NETWORK("Start synchronous request" << url); - - m_mutex->lock(); - QNetworkReply *reply = m_manager->get( request ); - ++m_synchronousRequestCount; - m_lastUrl = url; - m_lastUserUrl = userUrl.isEmpty() ? url : userUrl; - m_lastDownloadAborted = false; - m_mutex->unlock(); - - emit synchronousRequestStarted( url ); - const QTime start = QTime::currentTime(); - - int redirectCount = 0; - const int maxRedirections = 3; - forever { - // Use an event loop to wait for execution of the request, - // ie. make netAccess.download() synchronous for scripts - QEventLoop eventLoop; - connect( reply, SIGNAL(finished()), &eventLoop, SLOT(quit()) ); - connect( this, SIGNAL(doAbortSynchronousRequests()), &eventLoop, SLOT(quit()) ); - if ( timeout > 0 ) { - QTimer::singleShot( timeout, &eventLoop, SLOT(quit()) ); - } - if ( !reply->isFinished() ) { - eventLoop.exec( QEventLoop::ExcludeUserInputEvents ); - } - - m_mutex->lock(); - const bool quit = m_quit || m_lastDownloadAborted || reply->isRunning(); - m_mutex->unlock(); - - // Check if the timeout occured before the request finished - if ( quit ) { - DEBUG_NETWORK("Cancelled, destroyed or timeout while downloading" << url); - emitSynchronousRequestFinished( url, QByteArray(), true ); - return QByteArray(); - } - - if ( reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid() ) { - ++redirectCount; - if ( redirectCount > maxRedirections ) { - reply->deleteLater(); - emitSynchronousRequestFinished( url, QByteArray("Too many redirections"), true, - 200, start.msecsTo(QTime::currentTime()) ); - return QByteArray(); - } - - // Request the redirection target - const QUrl redirectUrl = - reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl(); - request.setUrl( redirectUrl ); - DEBUG_NETWORK("Redirected to" << redirectUrl); - - m_mutex->lock(); - delete reply; - reply = m_manager->get( request ); - m_lastUrl = redirectUrl.toString(); - m_mutex->unlock(); - - emit synchronousRequestRedirected( redirectUrl.toEncoded() ); - } else { - // No (more) redirection - break; - } - } - - const int time = start.msecsTo( QTime::currentTime() ); - const int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); - DEBUG_NETWORK("Waited" << (time / 1000.0) << "seconds for download of" - << url << "Status:" << statusCode); - - // Read all data, decode it and give it to the script - QByteArray data = reply->readAll(); - reply->deleteLater(); - if ( data.isEmpty() ) { - qWarning() << "Error downloading" << url << reply->errorString(); - emitSynchronousRequestFinished( url, QByteArray(), true, statusCode, time ); - return QByteArray(); - } else { - emitSynchronousRequestFinished( url, data, false, statusCode, time, data.size() ); - return data; - } -} - -void Network::emitSynchronousRequestFinished( const QString &url, const QByteArray &data, - bool cancelled, int statusCode, int waitTime, - int size ) -{ - m_mutex->lock(); - --m_synchronousRequestCount; - m_mutex->unlock(); - - emit synchronousRequestFinished( url, data, cancelled, statusCode, waitTime, size ); - - if ( !hasRunningRequests() ) { - emit allRequestsFinished(); - } -} - -ResultObject::ResultObject( QObject* parent ) - : QObject(parent), m_mutex(new QMutex()), m_features(DefaultFeatures), m_hints(NoHint) -{ -} - -ResultObject::~ResultObject() -{ - delete m_mutex; -} - -bool ResultObject::isHintGiven( ResultObject::Hint hint ) const -{ - QMutexLocker locker( m_mutex ); - return m_hints.testFlag( hint ); -} - -void ResultObject::giveHint( ResultObject::Hint hint, bool enable ) -{ - QMutexLocker locker( m_mutex ); - if ( enable ) { - // Remove incompatible flags of hint if any - if ( hint == CityNamesAreLeft && m_hints.testFlag(CityNamesAreRight) ) { - m_hints &= ~CityNamesAreRight; - } else if ( hint == CityNamesAreRight && m_hints.testFlag(CityNamesAreLeft) ) { - m_hints &= ~CityNamesAreLeft; - } - m_hints |= hint; - } else { - m_hints &= ~hint; - } -} - -bool ResultObject::isFeatureEnabled( ResultObject::Feature feature ) const -{ - QMutexLocker locker( m_mutex ); - return m_features.testFlag( feature ); -} - -void ResultObject::enableFeature( ResultObject::Feature feature, bool enable ) -{ - QMutexLocker locker( m_mutex ); - if ( enable ) { - m_features |= feature; - } else { - m_features &= ~feature; - } -} - -void ResultObject::dataList( const QList< TimetableData > &dataList, - PublicTransportInfoList *infoList, ParseDocumentMode parseMode, - Enums::VehicleType defaultVehicleType, const GlobalTimetableInfo *globalInfo, - ResultObject::Features features, ResultObject::Hints hints ) -{ // TODO Use features and hints - QDate curDate; - QTime lastTime; - int dayAdjustment = hints.testFlag(DatesNeedAdjustment) - ? QDate::currentDate().daysTo(globalInfo->requestDate) : 0; - if ( dayAdjustment != 0 ) { - kDebug() << "Dates get adjusted by" << dayAdjustment << "days"; - } - - // Find words at the beginning/end of target and route stop names that have many - // occurrences. These words are most likely the city names where the stops are in. - // But the timetable becomes easier to read and looks nicer, if not each stop name - // includes the same city name. - QHash< QString, int > firstWordCounts; // Counts occurrences of words at the beginning - QHash< QString, int > lastWordCounts; // Counts occurrences of words at the end - - // This is the range of occurrences of one word in stop names, - // that causes that word to be removed - const int minWordOccurrence = qMax( 2, dataList.count() ); - const int maxWordOccurrence = qMax( 3, dataList.count() / 2 ); - - // This regular expression gets used to search for word at the end, possibly including - // a colon before the last word - QRegExp rxLastWord( ",?\\s+\\S+$" ); - - // These strings store the words with the most occurrences in stop names at the beginning/end - QString removeFirstWord; - QString removeLastWord; - - // Read timetable data from the script - for ( int i = 0; i < dataList.count(); ++i ) { - TimetableData timetableData = dataList[ i ]; - - // Set default vehicle type if none is set - if ( !timetableData.contains(Enums::TypeOfVehicle) || - timetableData[Enums::TypeOfVehicle].toString().isEmpty() ) - { - timetableData[ Enums::TypeOfVehicle ] = static_cast( defaultVehicleType ); - } - - if ( parseMode != ParseForStopSuggestions ) { - QDateTime dateTime = timetableData[ Enums::DepartureDateTime ].toDateTime(); - QDate departureDate = timetableData[ Enums::DepartureDate ].toDate(); - QTime departureTime = timetableData[ Enums::DepartureTime ].toTime(); - - if ( !dateTime.isValid() && !departureTime.isValid() ) { - kDebug() << "No departure time given!" << timetableData[Enums::DepartureTime]; - kDebug() << "Use eg. helper.matchTime() to convert a string to a time object"; - } - - if ( !dateTime.isValid() ) { - if ( departureDate.isValid() ) { - dateTime = QDateTime( departureDate, departureTime ); - } else if ( curDate.isNull() ) { - // First departure - if ( QTime::currentTime().hour() < 3 && departureTime.hour() > 21 ) { - dateTime.setDate( QDate::currentDate().addDays(-1) ); - } else if ( QTime::currentTime().hour() > 21 && departureTime.hour() < 3 ) { - dateTime.setDate( QDate::currentDate().addDays(1) ); - } else { - dateTime.setDate( QDate::currentDate() ); - } - } else if ( lastTime.secsTo(departureTime) < -5 * 60 ) { - // Time too much ealier than last time, estimate it's tomorrow - dateTime.setDate( curDate.addDays(1) ); - } else { - dateTime.setDate( curDate ); - } - timetableData[ Enums::DepartureDateTime ] = dateTime; - } - - if ( dayAdjustment != 0 ) { - dateTime.setDate( dateTime.date().addDays(dayAdjustment) ); - timetableData[ Enums::DepartureDateTime ] = dateTime; - } - curDate = dateTime.date(); - lastTime = departureTime; - } - - // Create info object for the timetable data - PublicTransportInfoPtr info; - if ( parseMode == ParseForJourneysByDepartureTime || - parseMode == ParseForJourneysByArrivalTime ) - { - info = QSharedPointer( new JourneyInfo(timetableData) ); - } else if ( parseMode == ParseForDepartures || parseMode == ParseForArrivals ) { - info = QSharedPointer( new DepartureInfo(timetableData) ); - } else if ( parseMode == ParseForStopSuggestions ) { - info = QSharedPointer( new StopInfo(timetableData) ); - } - - if ( !info->isValid() ) { - info.clear(); - continue; - } - - // Find word to remove from beginning/end of stop names, if not already found - // TODO Use hint from the data engine.. - if ( features.testFlag(AutoRemoveCityFromStopNames) && - removeFirstWord.isEmpty() && removeLastWord.isEmpty() ) - { - // First count the first/last word of the target stop name - const QString target = info->value( Enums::Target ).toString(); - int pos = target.indexOf( ' ' ); - if ( pos > 0 && ++firstWordCounts[target.left(pos)] >= maxWordOccurrence ) { - removeFirstWord = target.left(pos); - } - if ( rxLastWord.indexIn(target) != -1 && - ++lastWordCounts[rxLastWord.cap()] >= maxWordOccurrence ) - { - removeLastWord = rxLastWord.cap(); - } - - // Check if route stop names are available - if ( info->contains(Enums::RouteStops) ) { - QStringList stops = info->value( Enums::RouteStops ).toStringList(); - QString target = info->value( Enums::Target ).toString(); - - // TODO Break if 70% or at least three of the route stop names - // begin/end with the same word -// int minCount = qMax( 3, int(stops.count() * 0.7) ); - foreach ( const QString &stop, stops ) { - // Test first word - pos = stop.indexOf( ' ' ); - const QString newFirstWord = stop.left( pos ); - if ( pos > 0 && ++firstWordCounts[newFirstWord] >= maxWordOccurrence ) { - removeFirstWord = newFirstWord; - break; - } - - // Test last word - if ( rxLastWord.indexIn(stop) != -1 && - ++lastWordCounts[rxLastWord.cap()] >= maxWordOccurrence ) - { - removeLastWord = rxLastWord.cap(); - break; - } - } - } - } - - infoList->append( info ); - } - - // Remove word with most occurrences from beginning/end of stop names - if ( features.testFlag(AutoRemoveCityFromStopNames) ) { - if ( removeFirstWord.isEmpty() && removeLastWord.isEmpty() ) { - // If no first/last word with at least maxWordOccurrence occurrences was found, - // find the word with the most occurrences - int max = 0; - - // Find word at the beginning with most occurrences - for ( QHash< QString, int >::ConstIterator it = firstWordCounts.constBegin(); - it != firstWordCounts.constEnd(); ++it ) - { - if ( it.value() > max ) { - max = it.value(); - removeFirstWord = it.key(); - } - } - - // Find word at the end with more occurrences - for ( QHash< QString, int >::ConstIterator it = lastWordCounts.constBegin(); - it != lastWordCounts.constEnd(); ++it ) - { - if ( it.value() > max ) { - max = it.value(); - removeLastWord = it.key(); - } - } - - if ( max < minWordOccurrence ) { - // The first/last word with the most occurrences has too few occurrences - // Do not remove any word - removeFirstWord.clear(); - removeLastWord.clear(); - } else if ( !removeLastWord.isEmpty() ) { - // removeLastWord has more occurrences than removeFirstWord - removeFirstWord.clear(); - } - } - - if ( !removeFirstWord.isEmpty() ) { - // Remove removeFirstWord from all stop names - for ( int i = 0; i < infoList->count(); ++i ) { - QSharedPointer info = infoList->at( i ); - QString target = info->value( Enums::Target ).toString(); - if ( target.startsWith(removeFirstWord) ) { - target = target.mid( removeFirstWord.length() + 1 ); - info->insert( Enums::TargetShortened, target ); - } - - QStringList stops = info->value( Enums::RouteStops ).toStringList(); - for ( int i = 0; i < stops.count(); ++i ) { - if ( stops[i].startsWith(removeFirstWord) ) { - stops[i] = stops[i].mid( removeFirstWord.length() + 1 ); - } - } - info->insert( Enums::RouteStopsShortened, stops ); - } - } else if ( !removeLastWord.isEmpty() ) { - // Remove removeLastWord from all stop names - for ( int i = 0; i < infoList->count(); ++i ) { - QSharedPointer info = infoList->at( i ); - QString target = info->value( Enums::Target ).toString(); - if ( target.endsWith(removeLastWord) ) { - target = target.left( target.length() - removeLastWord.length() ); - info->insert( Enums::TargetShortened, target ); - } - - QStringList stops = info->value( Enums::RouteStops ).toStringList(); - for ( int i = 0; i < stops.count(); ++i ) { - if ( stops[i].endsWith(removeLastWord) ) { - stops[i] = stops[i].left( stops[i].length() - removeLastWord.length() ); - } - } - info->insert( Enums::RouteStopsShortened, stops ); - } - } - } -} - -QString Helper::decodeHtmlEntities( const QString& html ) -{ - return Global::decodeHtmlEntities( html ); -} - -QString Helper::encodeHtmlEntities( const QString &html ) -{ - return Global::encodeHtmlEntities( html ); -} - -QString Helper::decodeHtml( const QByteArray &document, const QByteArray &fallbackCharset ) -{ - return Global::decodeHtml( document, fallbackCharset ); -} - -QString Helper::decode( const QByteArray &document, const QByteArray &charset ) -{ - return Global::decode( document, charset ); -} - -Helper::Helper( const QString &serviceProviderId, QObject *parent ) - : QObject(parent), m_mutex(new QMutex()), m_serviceProviderId(serviceProviderId), - m_errorMessageRepetition(0) -{ -} - -Helper::~Helper() -{ - emitRepeatedMessageWarning(); - delete m_mutex; -} - -void Helper::emitRepeatedMessageWarning() -{ - QMutexLocker locker( m_mutex ); - if ( m_errorMessageRepetition > 0 ) { - kDebug() << "(Last error message repeated" << m_errorMessageRepetition << "times)"; - m_errorMessageRepetition = 0; - emit messageReceived( i18nc("@info/plain", "Last error message repeated %1 times", - m_errorMessageRepetition), QScriptContextInfo() ); - } -} - -void Helper::messageReceived( const QString &message, const QString &failedParseText, - ErrorSeverity severity ) -{ - m_mutex->lock(); - if ( message == m_lastErrorMessage ) { - ++m_errorMessageRepetition; - m_mutex->unlock(); - return; - } - m_lastErrorMessage = message; - QScriptContextInfo info( context()->parentContext() ); - const QString serviceProviderId = m_serviceProviderId; - m_mutex->unlock(); - - emitRepeatedMessageWarning(); - emit messageReceived( message, info, failedParseText, severity ); - - // Output debug message and a maximum count of 200 characters of the text where the parsing failed - QString shortParseText = failedParseText.trimmed().left(350); - int diff = failedParseText.length() - shortParseText.length(); - if ( diff > 0 ) { - shortParseText.append(QString("... <%1 more chars>").arg(diff)); - } - shortParseText = shortParseText.replace('\n', "\n "); // Indent - -#ifdef ENABLE_DEBUG_SCRIPT_ERROR - m_mutex->lock(); - const QString name = severity == Information ? "Information" - : (severity == Warning ? "Warning" : "Fatal error"); - DEBUG_SCRIPT_ERROR( QString("%1 in %2-script, function %3(), file %4, line %5") - .arg(name) - .arg(m_serviceProviderId) - .arg(info.functionName().isEmpty() ? "[anonymous]" : info.functionName()) - .arg(QFileInfo(info.fileName()).fileName()) - .arg(info.lineNumber()) ); - m_mutex->unlock(); - DEBUG_SCRIPT_ERROR( message ); - if ( !shortParseText.isEmpty() ) { - DEBUG_SCRIPT_ERROR( QString("The text of the document where parsing failed is: \"%1\"") - .arg(shortParseText) ); - } -#endif - - // Log the complete message to the log file. - QString logFileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "plasma_engine_publictransport"; - logFileName.append( "serviceproviders.log" ); - - if ( !logFileName.isEmpty() ) { - QFile logFile(logFileName); - if ( logFile.size() > 1024 * 512 ) { // == 0.5 MB - if ( !logFile.remove() ) { - kDebug() << "Error: Couldn't delete old log file."; - } else { - kDebug() << "Deleted old log file, because it was getting too big."; - } - } - - if ( !logFile.open(QIODevice::Append | QIODevice::Text) ) { - kDebug() << "Couldn't open the log file in append mode" << logFileName << logFile.errorString(); - return; - } - - logFile.write( QString("%1 (%2, in function %3(), file %4, line %5):\n \"%6\"\n Failed while reading this text:\"%7\"\n-------------------------------------\n\n") - .arg(serviceProviderId) - .arg(QDateTime::currentDateTime().toString()) - .arg(info.functionName().isEmpty() ? "[anonymous]" : info.functionName()) - .arg(QFileInfo(info.fileName()).fileName()) - .arg(info.lineNumber()) - .arg(message) - .arg(failedParseText.trimmed()).toUtf8() ); - logFile.close(); - } -} - -void ResultObject::addData( const QVariantMap &item ) -{ - m_mutex->lock(); - TimetableData data; - for ( QVariantMap::ConstIterator it = item.constBegin(); it != item.constEnd(); ++it ) { - bool ok; - Enums::TimetableInformation info = - static_cast< Enums::TimetableInformation >( it.key().toInt(&ok) ); - if ( !ok || info == Enums::Nothing ) { - info = Global::timetableInformationFromString( it.key() ); - } - const QVariant value = it.value(); - if ( info == Enums::Nothing ) { - qWarning() << "Invalid property name" << it.key() << "with value" << value; - QString message; - if ( it.key() == QLatin1String("length") && value.canConvert(QVariant::Int) && - item.count() == value.toInt() + 1 ) // +1 for the "length" property - { - // Seems like a list was given and autoconverted to QVariantMap in this way - message = i18nc("@info/plain", "Invalid property name \"%1\" " - "with value \"%2\", seems like a list was passed to " - "result.addData() instead of an object with properties.", - it.key(), value.toString()); - context()->throwError( message ); - } else { - message = i18nc("@info/plain", "Invalid property name \"%1\" with value \"%2\"", - it.key(), value.toString()); - } - const int count = m_timetableData.count(); - m_mutex->unlock(); - emit invalidDataReceived( info, message, context()->parentContext(), count, item ); - m_mutex->lock(); - continue; - } else if ( value.isNull() ) { - // Null value received, simply leave the data empty - continue; - } else if ( !value.isValid() ) { - qWarning() << "Value for" << info << "is invalid or null" << value; - const QString message = i18nc("@info/plain", "Invalid value received for \"%1\"", - it.key()); - const int count = m_timetableData.count(); - m_mutex->unlock(); - emit invalidDataReceived( info, message, context()->parentContext(), count, item ); - m_mutex->lock(); - continue; - } else if ( info == Enums::TypeOfVehicle && - static_cast(value.toInt()) == Enums::InvalidVehicleType && - Global::vehicleTypeFromString(value.toString()) == Enums::InvalidVehicleType ) - { - qWarning() << "Invalid type of vehicle value" << value; - const QString message = i18nc("@info/plain", - "Invalid type of vehicle received: \"%1\"", value.toString()); - const int count = m_timetableData.count(); - m_mutex->unlock(); - emit invalidDataReceived( info, message, context()->parentContext(), count, item ); - m_mutex->lock(); - } else if ( info == Enums::TypesOfVehicleInJourney || info == Enums::RouteTypesOfVehicles ) { - const QVariantList types = value.toList(); - foreach ( const QVariant &type, types ) { - if ( static_cast(type.toInt()) == Enums::InvalidVehicleType && - Global::vehicleTypeFromString(type.toString()) == Enums::InvalidVehicleType ) - { - kDebug() << "Invalid type of vehicle value in" - << Global::timetableInformationToString(info) << value; - const QString message = i18nc("@info/plain", - "Invalid type of vehicle received in \"%1\": \"%2\"", - Global::timetableInformationToString(info), type.toString()); - const int count = m_timetableData.count(); - m_mutex->unlock(); - emit invalidDataReceived( info, message, context()->parentContext(), - count, item ); - m_mutex->lock(); - } - } - } else if ( info == Enums::JourneyNewsUrl ) { - QString url = value.toString(); - if ( url.startsWith('/') ) { - // Prepend provider URL to relative URLs - qWarning() << "Prepending provider URL to relative JourneyNewsUrls is not implemented"; -// url.prepend( m_ ); TODO - } - } else if ( info == Enums::JourneyNewsOther ) { - // DEPRECATED - qWarning() << "JourneyNewsOther is deprecated, use JourneyNews instead"; - info = Enums::JourneyNews; - } - - if ( m_features.testFlag(AutoDecodeHtmlEntities) ) { - if ( value.canConvert(QVariant::String) && - (info == Enums::StopName || info == Enums::Target || info == Enums::StartStopName || - info == Enums::TargetStopName || info == Enums::Operator || - info == Enums::TransportLine || info == Enums::Platform || - info == Enums::DelayReason || info == Enums::Status || info == Enums::Pricing) ) - { - // Decode HTML entities in string values - data[ info ] = Global::decodeHtmlEntities( value.toString() ).trimmed(); - } else if ( value.canConvert(QVariant::StringList) && - (info == Enums::RouteStops || info == Enums::RoutePlatformsDeparture || - info == Enums::RoutePlatformsArrival) ) - { - // Decode HTML entities in string list values - QStringList stops = value.toStringList(); - for ( QStringList::Iterator it = stops.begin(); it != stops.end(); ++it ) { - *it = Helper::trim( Global::decodeHtmlEntities(*it) ); - } - data[ info ] = stops; - } else { - // Other values don't need decoding - data[ info ] = value; - } - } else { - data[ info ] = value; - } - } - m_timetableData << data; - - if ( m_features.testFlag(AutoPublish) && m_timetableData.count() == 10 ) { - // Publish the first 10 data items automatically - m_mutex->unlock(); - emit publish(); - } else { - m_mutex->unlock(); - } -} - -QString Helper::trim( const QString& str ) -{ - return QString(str).trimmed() - .replace( QRegExp("^( )+|( )+$", Qt::CaseInsensitive), QString() ) - .trimmed(); -} - -QString Helper::simplify( const QString &str ) -{ - return QString(str).replace( QRegExp("( )+", Qt::CaseInsensitive), QString() ) - .simplified(); -} - -QString Helper::stripTags( const QString& str ) -{ - const QString attributePattern = "\\w+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\"'>\\s]+))?"; - QRegExp rx( QString("<\\/?\\w+(?:\\s+%1)*(?:\\s*/)?>").arg(attributePattern) ); - rx.setMinimal( true ); - return QString( str ).remove( rx ); -} - -QString Helper::camelCase( const QString& str ) -{ - QString ret = str.toLower(); - QRegExp rx( "(^\\w)|\\W(\\w)" ); - int pos = 0; - while ( (pos = rx.indexIn(ret, pos)) != -1 ) { - if ( rx.pos(2) == -1 || rx.pos(2) >= ret.length() ) { - ret[ rx.pos(1) ] = ret[ rx.pos(1) ].toUpper(); - } else { - ret[ rx.pos(2) ] = ret[ rx.pos(2) ].toUpper(); - } - pos += rx.matchedLength(); - } - return ret; -} - -QString Helper::extractBlock( const QString& str, const QString& beginString, - const QString& endString ) -{ - int pos = str.indexOf( beginString ); - if ( pos == -1 ) { - return ""; - } - - int end = str.indexOf( endString, pos + 1 ); - return str.mid( pos, end - pos ); -} - -QVariantMap Helper::matchTime( const QString& str, const QString& format ) -{ - QString pattern = QRegExp::escape( format ); - pattern = pattern.replace( "hh", "\\d{2}" ) - .replace( "h", "\\d{1,2}" ) - .replace( "mm", "\\d{2}" ) - .replace( "m", "\\d{1,2}" ) - .replace( "AP", "(AM|PM)" ) - .replace( "ap", "(am|pm)" ); - - QVariantMap ret; - QRegExp rx( pattern ); - if ( rx.indexIn(str) != -1 ) { - QTime time = QTime::fromString( rx.cap(), format ); - ret.insert( "hour", time.hour() ); - ret.insert( "minute", time.minute() ); - } else if ( format != "hh:mm" ) { - // Try default format if the one specified doesn't work - QRegExp rx2( "\\d{1,2}:\\d{2}" ); - if ( rx2.indexIn(str) != -1 ) { - QTime time = QTime::fromString( rx2.cap(), "hh:mm" ); - ret.insert( "hour", time.hour() ); - ret.insert( "minute", time.minute() ); - } else { - ret.insert( "error", true ); - DEBUG_SCRIPT_HELPER("Couldn't match time in" << str << pattern); - } - } else { - ret.insert( "error", true ); - DEBUG_SCRIPT_HELPER("Couldn't match time in" << str << pattern); - } - return ret; -} - -QDate Helper::matchDate( const QString& str, const QString& format ) -{ - QString pattern = QRegExp::escape( format ).replace( "d", "D" ); - pattern = pattern.replace( "DD", "\\d{2}" ) - .replace( "D", "\\d{1,2}" ) - .replace( "MM", "\\d{2}" ) - .replace( "M", "\\d{1,2}" ) - .replace( "yyyy", "\\d{4}" ) - .replace( "yy", "\\d{2}" ); - - QRegExp rx( pattern ); - QDate date; - if ( rx.indexIn(str) != -1 ) { - date = QDate::fromString( rx.cap(), format ); - } else if ( format != "yyyy-MM-dd" ) { - // Try default format if the one specified doesn't work - QRegExp rx2( "\\d{2,4}-\\d{2}-\\d{2}" ); - if ( rx2.indexIn(str) != -1 ) { - date = QDate::fromString( rx2.cap(), "yyyy-MM-dd" ); - } - } - if ( !date.isValid() ) { - DEBUG_SCRIPT_HELPER("Couldn't match date in" << str << pattern); - } - - // Adjust date, needed for formats with only two "yy" for year matching - // A year 12 means 2012, not 1912 - if ( date.year() < 1970 ) { - date.setDate( date.year() + 100, date.month(), date.day() ); - } - return date; -} - -QString Helper::formatTime( int hour, int minute, const QString& format ) -{ - return QTime( hour, minute ).toString( format ); -} - -QString Helper::formatDate( int year, int month, int day, const QString& format ) -{ - return QDate( year, month, day ).toString( format ); -} - -QString Helper::formatDateTime( const QDateTime& dateTime, const QString& format ) -{ - return dateTime.toString( format ); -} - -int Helper::duration( const QString& sTime1, const QString& sTime2, const QString& format ) -{ - QTime time1 = QTime::fromString( sTime1, format ); - QTime time2 = QTime::fromString( sTime2, format ); - if ( !time1.isValid() || !time2.isValid() ) { - return -1; - } - return time1.secsTo( time2 ) / 60; -} - -QString Helper::addMinsToTime( const QString& sTime, int minsToAdd, const QString& format ) -{ - QTime time = QTime::fromString( sTime, format ); - if ( !time.isValid() ) { - DEBUG_SCRIPT_HELPER("Couldn't parse the given time" << sTime << format); - return ""; - } - return time.addSecs( minsToAdd * 60 ).toString( format ); -} - -QString Helper::addDaysToDate( const QString& sDate, int daysToAdd, const QString& format ) -{ - QDate date = QDate::fromString( sDate, format ).addDays( daysToAdd ); - if ( !date.isValid() ) { - DEBUG_SCRIPT_HELPER("Couldn't parse the given date" << sDate << format); - return sDate; - } - return date.toString( format ); -} - -QDateTime Helper::addDaysToDate( const QDateTime& dateTime, int daysToAdd ) -{ - return dateTime.addDays( daysToAdd ); -} - -QStringList Helper::splitSkipEmptyParts( const QString& str, const QString& sep ) -{ - return str.split( sep, QString::SkipEmptyParts ); -} - -QVariantMap Helper::findFirstHtmlTag( const QString &str, const QString &tagName, - const QVariantMap &options ) -{ - // Set/overwrite maxCount option to match only the first tag using findHtmlTags() - QVariantMap _options = options; - _options[ "maxCount" ] = 1; - QVariantList tags = findHtmlTags( str, tagName, _options ); - - // Copy values of first matched tag (if any) to the result object - QVariantMap result; - result.insert( "found", !tags.isEmpty() ); - if ( !tags.isEmpty() ) { - const QVariantMap firstTag = tags.first().toMap(); - result.insert( "contents", firstTag["contents"] ); - result.insert( "position", firstTag["position"] ); - result.insert( "endPosition", firstTag["endPosition"] ); - result.insert( "attributes", firstTag["attributes"] ); - result.insert( "name", firstTag["name"] ); - } - return result; -} - -QVariantList Helper::findHtmlTags( const QString &str, const QString &tagName, - const QVariantMap &options ) -{ - const QVariantMap &attributes = options[ "attributes" ].toMap(); - const int maxCount = options.value( "maxCount", 0 ).toInt(); - const bool noContent = options.value( "noContent", false ).toBool(); - const bool noNesting = options.value( "noNesting", false ).toBool(); - const bool debug = options.value( "debug", false ).toBool(); - const QString contentsRegExpPattern = options.value( "contentsRegExp", QString() ).toString(); - const QVariantMap namePosition = options[ "namePosition" ].toMap(); - int position = options.value( "position", 0 ).toInt(); - - const bool namePositionIsAttribute = namePosition["type"].toString().toLower().compare( - QLatin1String("attribute"), Qt::CaseInsensitive ) == 0; - const QString namePositionRegExpPattern = namePosition.contains("regexp") - ? namePosition["regexp"].toString() : QString(); - - // Create regular expression that matches HTML elements with or without attributes. - // Since QRegExp offers no way to retreive multiple matches of the same capture group - // the whole attribute string gets matched here and then analyzed in another loop - // using attributeRegExp. - // Matching the attributes with all details here is required to prevent eg. having a match - // end after a ">" character in a string in an attribute. - const QString attributePattern = "\\w+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\"'>\\s]+))?"; - QRegExp htmlTagRegExp( noContent - ? QString("<%1((?:\\s+%2)*)(?:\\s*/)?>").arg(tagName).arg(attributePattern) - : QString("<%1((?:\\s+%2)*)>").arg(tagName).arg(attributePattern), - // TODO TEST does this need a "\\s*" before the ">"? -// : QString("<%1((?:\\s+%2)*)>%3").arg(tagName).arg(attributePattern) -// .arg(contentsRegExpPattern), - Qt::CaseInsensitive ); - QRegExp htmlCloseTagRegExp( QString("").arg(tagName), Qt::CaseInsensitive ); - QRegExp contentsRegExp( contentsRegExpPattern, Qt::CaseInsensitive ); - htmlTagRegExp.setMinimal( true ); - - // Match attributes with or without value, with single/double/not quoted value - QRegExp attributeRegExp( "(\\w+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\"'>\\s]+)))?", - Qt::CaseInsensitive ); - - QVariantList foundTags; - while ( (foundTags.count() < maxCount || maxCount <= 0) && - (position = htmlTagRegExp.indexIn(str, position)) != -1 ) - { - if ( debug ) { - kDebug() << "Test match at" << position << htmlTagRegExp.cap().left(500); - } - const QString attributeString = htmlTagRegExp.cap( 1 ); - QString tagContents; // = noContent ? QString() : htmlTagRegExp.cap( 2 ); - - QVariantMap foundAttributes; - int attributePos = 0; - while ( (attributePos = attributeRegExp.indexIn(attributeString, attributePos)) != -1 ) { - const int valueCap = attributeRegExp.cap(2).isEmpty() ? - (attributeRegExp.cap(3).isEmpty() ? 4 : 3) : 2; - foundAttributes.insert( attributeRegExp.cap(1), attributeRegExp.cap(valueCap) ); - attributePos += attributeRegExp.matchedLength(); - } - if ( debug ) { - kDebug() << "Found attributes" << foundAttributes << "in" << attributeString; - } - - // Test if the attributes match - bool attributesMatch = true; - for ( QVariantMap::ConstIterator it = attributes.constBegin(); - it != attributes.constEnd(); ++it ) - { - if ( !foundAttributes.contains(it.key()) ) { - // Did not find exact attribute name, try to use it as regular expression pattern - attributesMatch = false; - QRegExp attributeNameRegExp( it.key(), Qt::CaseInsensitive ); - foreach ( const QString &attributeName, foundAttributes.keys() ) { - if ( attributeNameRegExp.indexIn(attributeName) != -1 ) { - // Matched the attribute name - attributesMatch = true; - break; - } - } - - if ( !attributesMatch ) { - if ( debug ) { - kDebug() << "Did not find attribute" << it.key(); - } - break; - } - } - - // Attribute exists, test it's value - const QString value = foundAttributes[ it.key() ].toString(); - const QString valueRegExpPattern = it.value().toString(); - if ( !(value.isEmpty() && valueRegExpPattern.isEmpty()) ) { - QRegExp valueRegExp( valueRegExpPattern, Qt::CaseInsensitive ); - if ( valueRegExp.indexIn(value) == -1 ) { - // Attribute value regexp did not matched - attributesMatch = false; - if ( debug ) { - kDebug() << "Value" << value << "did not match pattern" << valueRegExpPattern; - } - break; - } else if ( valueRegExp.captureCount() > 0 ) { - // Attribute value regexp matched, store captures - foundAttributes[ it.key() ] = valueRegExp.capturedTexts(); - } - } - } - - // Search for new opening HTML tags (with same tag name) before the closing HTML tag - int endPosition = htmlTagRegExp.pos() + htmlTagRegExp.matchedLength(); - if ( !attributesMatch ) { - position = endPosition; - continue; - } - if ( !noContent ) { - if ( noNesting ) { - // "noNesting" option set, simply search for next closing tag, no matter if it is - // a nested tag or not - const int posClosing = htmlCloseTagRegExp.indexIn( str, endPosition ); - const int contentsBegin = htmlTagRegExp.pos() + htmlTagRegExp.matchedLength(); - tagContents = str.mid( contentsBegin, posClosing - contentsBegin ); - endPosition = htmlCloseTagRegExp.pos() + htmlCloseTagRegExp.matchedLength(); - } else { - // Find next closing tag, skipping nested tags. - // Get string after the opening HTML tag - const QString rest = str.mid( htmlTagRegExp.pos() + htmlTagRegExp.matchedLength() ); - - int posClosing = htmlCloseTagRegExp.indexIn( rest ); - if ( posClosing == -1 ) { - position = htmlTagRegExp.pos() + htmlTagRegExp.matchedLength(); - if ( debug ) { - kDebug() << "Closing tag" << tagName << "could not be found"; - } - position = endPosition; - continue; - } - - // Search for nested opening tags in between the main opening tag and the - // next closing tag - int posOpening = htmlTagRegExp.indexIn( rest.left(posClosing) ); - while ( posOpening != -1 ) { - // Found a nested tag, find the next closing tag - posClosing = htmlCloseTagRegExp.indexIn( rest, - posClosing + htmlCloseTagRegExp.matchedLength() ); - if ( posClosing == -1 ) { - position = htmlTagRegExp.pos() + htmlTagRegExp.matchedLength(); - if ( debug ) { - kDebug() << "Closing tag" << tagName << "could not be found"; - } - break; - } - - // Search for more nested opening tags - posOpening = htmlTagRegExp.indexIn( rest.left(posClosing), - posOpening + htmlTagRegExp.matchedLength() ); - } - - tagContents = rest.left( posClosing ); - endPosition += htmlCloseTagRegExp.pos() + htmlCloseTagRegExp.matchedLength(); - } - } - - // Match contents, only use regular expression if one was given in the options argument - if ( !contentsRegExpPattern.isEmpty() ) { - if ( contentsRegExp.indexIn(tagContents) == -1 ) { - if ( debug ) { - kDebug() << "Did not match tag contents" << tagContents.left(500); - } - position = endPosition; - continue; - } else { - // Use first matched group as contents string, if any - // Otherwise use the whole match as contents string - tagContents = contentsRegExp.cap( contentsRegExp.captureCount() <= 1 ? 0 : 1 ); - } - } else { - // No regexp pattern for contents, use complete contents, but trimmed - tagContents = tagContents.trimmed(); - } - - // Construct a result object - QVariantMap result; - result.insert( "contents", tagContents ); - result.insert( "position", position ); - result.insert( "endPosition", endPosition ); - result.insert( "attributes", foundAttributes ); - - // Find name if a "namePosition" option is given - if ( !namePosition.isEmpty() ) { - const QString name = getTagName( result, namePosition["type"].toString(), - namePositionRegExpPattern, - namePositionIsAttribute ? namePosition["name"].toString() : QString() ); - result.insert( "name", name ); - } - - if ( debug ) { - kDebug() << "Found HTML tag" << tagName << "at" << position << foundAttributes; - } - foundTags << result; - position = endPosition; - } - - if ( debug ) { - if ( foundTags.isEmpty() ) { - kDebug() << "Found no" << tagName << "HTML tags in HTML" << str; - } else { - kDebug() << "Found" << foundTags.count() << tagName << "HTML tags"; - } - } - return foundTags; -} - -QString Helper::getTagName( const QVariantMap &searchResult, const QString &type, - const QString ®Exp, const QString attributeName ) -{ - const bool namePositionIsAttribute = type.toLower().compare( - QLatin1String("attribute"), Qt::CaseInsensitive ) == 0; - QString name = trim( namePositionIsAttribute - ? searchResult["attributes"].toMap()[ attributeName ].toString() - : searchResult["contents"].toString() ); - if ( !regExp.isEmpty() ) { - // Use "regexp" property of namePosition to match the header name - QRegExp namePositionRegExp( regExp, Qt::CaseInsensitive ); - if ( namePositionRegExp.indexIn(name) != -1 ) { - name = namePositionRegExp.cap( qMin(1, namePositionRegExp.captureCount()) ); - } - } - return name; -} - -QVariantMap Helper::findNamedHtmlTags( const QString &str, const QString &tagName, - const QVariantMap &options ) -{ - QVariantMap namePosition; - if ( !options.contains("namePosition") ) { - namePosition[ "type" ] = "contents"; // Can be "attribute", "contents" - } else { - namePosition = options[ "namePosition" ].toMap(); - } - const bool namePositionIsAttribute = namePosition["type"].toString().toLower().compare( - QLatin1String("attribute"), Qt::CaseInsensitive ) == 0; - const QString namePositionRegExpPattern = namePosition.contains("regexp") - ? namePosition["regexp"].toString() : QString(); - const QString ambiguousNameResolution = options.contains("ambiguousNameResolution") - ? options["ambiguousNameResolution"].toString().toLower() : "replace"; - const bool debug = options.value( "debug", false ).toBool(); - - const QVariantList foundTags = findHtmlTags( str, tagName, options ); - QVariantMap foundTagsMap; - foreach ( const QVariant &foundTag, foundTags ) { - QString name = getTagName( foundTag.toMap(), namePosition["type"].toString(), - namePositionRegExpPattern, - namePositionIsAttribute ? namePosition["name"].toString() : QString() ); - if ( name.isEmpty() ) { - if ( debug ) { - kDebug() << "Empty name in" << str; - } - continue; - } - - // Check if the newly found name was already found - // and decide what to do based on the "ambiguousNameResolution" option - if ( ambiguousNameResolution == QLatin1String("addnumber") && foundTagsMap.contains(name) ) { - QRegExp rx( "(\\d+)$" ); - if ( rx.indexIn(name) != -1 ) { - name += QString::number( rx.cap(1).toInt() + 1 ); - } else { - name += "2"; - } - } - foundTagsMap[ name ] = foundTag; // TODO Use lists here? The same name could be found multiply times - } - - // Store list of names in the "names" property, therefore "names" should not be a found tag name - if ( !foundTagsMap.contains("names") ) { - foundTagsMap[ "names" ] = QVariant::fromValue( foundTagsMap.keys() ); - } else if ( debug ) { - kDebug() << "A tag with the name 'names' was found. Normally a property 'names' gets " - "added to the object returned by this functionm, which lists all found " - "names in a list."; - } - return foundTagsMap; -} - -const char* Storage::LIFETIME_ENTRYNAME_SUFFIX = "__expires__"; - -class StoragePrivate { -public: - StoragePrivate( const QString &serviceProvider ) - : readWriteLock(new QReadWriteLock(QReadWriteLock::Recursive)), - readWriteLockPersistent(new QReadWriteLock(QReadWriteLock::Recursive)), - serviceProvider(serviceProvider), lastLifetimeCheck(0), config(0) { - }; - - ~StoragePrivate() { - delete readWriteLock; - delete readWriteLockPersistent; - delete config; - }; - - void readPersistentData() { - // Delete already read config object - if ( config ) { - delete config; - } - - config = new KConfig( ServiceProviderGlobal::cacheFileName(), KConfig::SimpleConfig ); - }; - - KConfigGroup persistentGroup() { - if ( !config ) { - readPersistentData(); - } - return config->group( serviceProvider ).group( QLatin1String("storage") ); - }; - - quint16 checkLength( const QByteArray &data ) { - if ( data.length() > 65535 ) { - kDebug() << "Data is too long, only 65535 bytes are supported" << data.length(); - } - - return static_cast( data.length() ); - }; - - QReadWriteLock *readWriteLock; - QReadWriteLock *readWriteLockPersistent; - QVariantMap data; - const QString serviceProvider; - uint lastLifetimeCheck; // as time_t - KConfig *config; - KConfigGroup lastPersistentGroup; -}; - -Storage::Storage( const QString &serviceProviderId, QObject *parent ) - : QObject(parent), d(new StoragePrivate(serviceProviderId)) -{ - // Delete persistently stored data which lifetime has expired - checkLifetime(); -} - -Storage::~Storage() -{ - delete d; -} - -void Storage::write( const QVariantMap& data ) -{ - for ( QVariantMap::ConstIterator it = data.constBegin(); it != data.constEnd(); ++it ) { - write( it.key(), it.value() ); - } -} - -void Storage::write( const QString& name, const QVariant& data ) -{ - QWriteLocker locker( d->readWriteLock ); - d->data.insert( name, data ); -} - -QVariantMap Storage::read() -{ - QReadLocker locker( d->readWriteLock ); - return d->data; -} - -QVariant Storage::read( const QString& name, const QVariant& defaultData ) -{ - QReadLocker locker( d->readWriteLock ); - return d->data.contains(name) ? d->data[name] : defaultData; -} - -void Storage::remove( const QString& name ) -{ - QWriteLocker locker( d->readWriteLock ); - d->data.remove( name ); -} - -void Storage::clear() -{ - QWriteLocker locker( d->readWriteLock ); - d->data.clear(); -} - -int Storage::lifetime( const QString& name ) -{ - QReadLocker locker( d->readWriteLockPersistent ); - return lifetime( name, d->persistentGroup() ); -} - -int Storage::lifetime( const QString& name, const KConfigGroup& group ) -{ - QReadLocker locker( d->readWriteLockPersistent ); - return lifetimeNoLock( name, group ); -} - -int Storage::lifetimeNoLock( const QString &name, const KConfigGroup &group ) -{ - const uint lifetimeTime_t = group.readEntry( name + LIFETIME_ENTRYNAME_SUFFIX, 0 ); - return QDateTime::currentDateTime().daysTo( QDateTime::fromTime_t(lifetimeTime_t) ); -} - -void Storage::checkLifetime() -{ - QWriteLocker locker( d->readWriteLockPersistent ); - if ( QDateTime::currentDateTime().toTime_t() - d->lastLifetimeCheck < - MIN_LIFETIME_CHECK_INTERVAL * 60 ) - { - // Last lifetime check was less than 15 minutes ago - return; - } - - // Try to load script features from a cache file - KConfigGroup group = d->persistentGroup(); - QMap< QString, QString > data = group.entryMap(); - for ( QMap< QString, QString >::ConstIterator it = data.constBegin(); - it != data.constEnd(); ++it ) - { - if ( it.key().endsWith(LIFETIME_ENTRYNAME_SUFFIX) ) { - // Do not check lifetime of entries which store the lifetime of the real data entries - continue; - } - const int remainingLifetime = lifetimeNoLock( it.key(), group ); - if ( remainingLifetime <= 0 ) { - // Lifetime has expired - kDebug() << "Lifetime of storage data" << it.key() << "for" << d->serviceProvider - << "has expired" << remainingLifetime; - removePersistent( it.key(), group ); - } - } - - d->lastLifetimeCheck = QDateTime::currentDateTime().toTime_t(); -} - -bool Storage::hasData( const QString &name ) const -{ - QReadLocker locker( d->readWriteLock ); - return d->data.contains( name ); -} - -bool Storage::hasPersistentData( const QString &name ) const -{ - QReadLocker locker( d->readWriteLockPersistent ); - KConfigGroup group = d->persistentGroup(); - return group.hasKey( name ); -} - -QByteArray Storage::encodeData( const QVariant &data ) const -{ - // Store the type of the variant, because it is needed to read the QVariant with the correct - // type using KConfigGroup::readEntry() - const uchar type = static_cast( data.type() ); - if ( type >= QVariant::LastCoreType ) { - kDebug() << "Invalid data type, only QVariant core types are supported" << data.type(); - return QByteArray(); - } - - // Write type into the first byte - QByteArray encodedData; - encodedData[0] = type; - - // Write data - if ( data.canConvert(QVariant::ByteArray) ) { - encodedData += data.toByteArray(); - } else if ( data.canConvert(QVariant::String) ) { - encodedData += data.toString().toUtf8(); - } else { - switch ( data.type() ) { - case QVariant::StringList: - case QVariant::List: { - // Lists are stored like this (one entry after the other): - // "<2 Bytes: Value length>" - const QVariantList list = data.toList(); - foreach ( const QVariant &item, list ) { - // Encode current list item - QByteArray encodedItem = encodeData( item ); - - // Construct a QByteArray which contains the length in two bytes (0 .. 65535) - const quint16 length = d->checkLength( encodedItem ); - const QByteArray baLength( (const char*)&length, sizeof(length) ); - - // Use 2 bytes for the length of the data, append data - encodedData += baLength; - encodedData += encodedItem; - } - break; - } - case QVariant::Map: { - // Maps are stored like this (one entry after the other): - // "<2 Bytes: Key length><2 Bytes: Value length>" - const QVariantMap map = data.toMap(); - for ( QVariantMap::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it ) { - // Encode current key and value - QByteArray encodedKey = it.key().toUtf8(); - QByteArray encodedValue = encodeData( it.value() ); - - // Construct QByteArrays which contain the lengths in two bytes each (0 .. 65535) - const quint16 lengthKey = d->checkLength( encodedKey ); - const quint16 lengthValue = d->checkLength( encodedValue ); - const QByteArray baLengthKey( (const char*)&lengthKey, sizeof(lengthKey) ); - const QByteArray baLengthValue( (const char*)&lengthValue, sizeof(lengthValue) ); - - // Use 2 bytes for the length of the key, append key - encodedData += baLengthKey; - encodedData += encodedKey; - - // Use 2 bytes for the length of the value, append value - encodedData += baLengthValue; - encodedData += encodedValue; - } - break; - } - default: - kDebug() << "Cannot convert from type" << data.type(); - return QByteArray(); - } - } - - return encodedData; -} - -QVariant Storage::decodeData( const QByteArray &data ) const -{ - const QVariant::Type type = static_cast( data[0] ); - if ( type >= QVariant::LastCoreType ) { - kDebug() << "Invalid encoding for data" << data; - return QVariant(); - } - - const QByteArray encodedValue = data.mid( 1 ); - QVariant value( type ); - value.setValue( encodedValue ); - if ( value.canConvert( type ) ) { - value.convert( type ); - return value; - } else { - switch ( type ) { - case QVariant::Date: - // QVariant::toString() uses Qt::ISODate to convert QDate to string - return QDate::fromString( value.toString(), Qt::ISODate ); - case QVariant::Time: - return QTime::fromString( value.toString() ); - case QVariant::DateTime: - // QVariant::toString() uses Qt::ISODate to convert QDateTime to string - return QDateTime::fromString( value.toString(), Qt::ISODate ); - case QVariant::StringList: - case QVariant::List: { - // Lists are stored like this (one entry after the other): - // "<2 Bytes: Value length>" - QVariantList decoded; - QByteArray encoded = value.toByteArray(); - int pos = 0; - while ( pos + 2 < encoded.length() ) { - const quint16 length = *reinterpret_cast( encoded.mid(pos, 2).data() ); - if ( pos + 2 + length > encoded.length() ) { - kDebug() << "Invalid list data" << encoded; - return QVariant(); - } - const QByteArray encodedValue = encoded.mid( pos + 2, length ); - const QVariant decodedValue = decodeData( encodedValue ); - decoded << decodedValue; - - pos += 2 + length; - } - return decoded; - } - case QVariant::Map: { - // Maps are stored like this (one entry after the other): - // "<2 Bytes: Key length><2 Bytes: Value length>" - QVariantMap decoded; - const QByteArray encoded = value.toByteArray(); - int pos = 0; - while ( pos + 4 < encoded.length() ) { - // Decode two bytes to quint16, this is the key length - const quint16 keyLength = *reinterpret_cast( encoded.mid(pos, 2).data() ); - if ( pos + 4 + keyLength > encoded.length() ) { // + 4 => 2 Bytes for keyLength + 2 Bytes for valueLength - kDebug() << "Invalid map data" << encoded; - return QVariant(); - } - - // Extract key, starting after the two bytes for the key length - const QString key = encoded.mid( pos + 2, keyLength ); - pos += 2 + keyLength; - - // Decode two bytes to quint16, this is the value length - const quint16 valueLength = *reinterpret_cast( encoded.mid(pos, 2).data() ); - if ( pos + 2 + valueLength > encoded.length() ) { - kDebug() << "Invalid map data" << encoded; - return QVariant(); - } - - // Extract and decode value, starting after the two bytes for the value length - const QByteArray encodedValue = encoded.mid( pos + 2, valueLength ); - const QVariant decodedValue = decodeData( encodedValue ); - pos += 2 + valueLength; - - // Insert decoded value into the result map - decoded.insert( key, decodedValue ); - } - return decoded; - } - default: - kDebug() << "Cannot convert to type" << type; - return QVariant(); - } - } -} - -void Storage::writePersistent( const QVariantMap& data, uint lifetime ) -{ - QWriteLocker locker( d->readWriteLockPersistent ); - for ( QVariantMap::ConstIterator it = data.constBegin(); it != data.constEnd(); ++it ) { - writePersistent( it.key(), it.value(), lifetime ); - } -} - -void Storage::writePersistent( const QString& name, const QVariant& data, uint lifetime ) -{ - if ( lifetime > MAX_LIFETIME ) { - lifetime = MAX_LIFETIME; - } - - // Try to load script features from a cache file - QWriteLocker locker( d->readWriteLockPersistent ); - KConfigGroup group = d->persistentGroup(); - group.writeEntry( name + LIFETIME_ENTRYNAME_SUFFIX, - QDateTime::currentDateTime().addDays(lifetime).toTime_t() ); - group.writeEntry( name, encodeData(data) ); -} - -QVariant Storage::readPersistent( const QString& name, const QVariant& defaultData ) -{ - // Try to load script features from a cache file - QWriteLocker locker( d->readWriteLockPersistent ); - const QByteArray data = d->persistentGroup().readEntry( name, QByteArray() ); - return data.isEmpty() ? defaultData : decodeData(data); -} - -void Storage::removePersistent( const QString& name, KConfigGroup& group ) -{ - QWriteLocker locker( d->readWriteLockPersistent ); - group.deleteEntry( name + LIFETIME_ENTRYNAME_SUFFIX ); - group.deleteEntry( name ); -} - -void Storage::removePersistent( const QString& name ) -{ - // Try to load script features from a cache file - QWriteLocker locker( d->readWriteLockPersistent ); - KConfigGroup group = d->persistentGroup(); - removePersistent( name, group ); -} - -void Storage::clearPersistent() -{ - // Try to load script features from a cache file - QWriteLocker locker( d->readWriteLockPersistent ); - d->persistentGroup().deleteGroup(); -} - -QString Network::lastUrl() const -{ - QMutexLocker locker( m_mutex ); - return m_lastUrl; -} - -QString Network::lastUserUrl() const -{ - QMutexLocker locker( m_mutex ); - return m_lastUserUrl; -} - -void Network::clear() -{ - QMutexLocker locker( m_mutex ); - m_lastUrl.clear(); - m_lastUserUrl.clear(); -} - -bool Network::lastDownloadAborted() const -{ - QMutexLocker locker( m_mutex ); - return m_lastDownloadAborted; -} - -bool Network::hasRunningRequests() const -{ - QMutexLocker locker( m_mutex ); - if ( m_synchronousRequestCount > 0 ) { - // There is a synchronous request running - return true; - } - - foreach ( const NetworkRequest::Ptr &request, m_requests ) { - if ( request->isRunning() ) { - // Found a running asynchronous request - return true; - } - } - - // No running request found - return false; -} - -QList< NetworkRequest::Ptr > Network::runningRequests() const -{ - QMutexLocker locker( m_mutex ); - QList< NetworkRequest::Ptr > requests; - foreach ( const NetworkRequest::Ptr &request, m_requests ) { - if ( request->isRunning() ) { - requests << request; - } - } - return requests; -} - -int Network::runningRequestCount() const -{ - QMutexLocker locker( m_mutex ); - return runningRequests().count() + m_synchronousRequestCount; -} - -QByteArray Network::fallbackCharset() const -{ - QMutexLocker locker( m_mutex ); - return m_fallbackCharset; -} - -QList< TimetableData > ResultObject::data() const -{ - QMutexLocker locker( m_mutex ); - return m_timetableData; -} - -QVariant ResultObject::data( int index, Enums::TimetableInformation information ) const -{ - QMutexLocker locker( m_mutex ); - if ( index < 0 || index >= m_timetableData.count() ) { - context()->throwError( QScriptContext::RangeError, "Index out of range" ); - return QVariant(); - } - return m_timetableData[ index ][ information ]; -} - -bool ResultObject::hasData() const -{ - QMutexLocker locker( m_mutex ); - return !m_timetableData.isEmpty(); -} - -int ResultObject::count() const -{ - QMutexLocker locker( m_mutex ); - return m_timetableData.count(); -} - -ResultObject::Features ResultObject::features() const -{ - QMutexLocker locker( m_mutex ); - return m_features; -} - -ResultObject::Hints ResultObject::hints() const -{ - QMutexLocker locker( m_mutex ); - return m_hints; -} - -void ResultObject::clear() -{ - QMutexLocker locker( m_mutex ); - m_timetableData.clear(); -} - -QScriptValue constructStream( QScriptContext *context, QScriptEngine *engine ) -{ - QScriptValue argument = context->argument(0); - DataStreamPrototype *object; - if ( argument.instanceOf(context->callee()) ) { - object = new DataStreamPrototype( qscriptvalue_cast(argument) ); - } else if ( argument.isQObject() && qscriptvalue_cast(argument) ) { - object = new DataStreamPrototype( qscriptvalue_cast(argument) ); - } else if ( argument.isVariant() ) { - const QVariant variant = argument.toVariant(); - object = new DataStreamPrototype( variant.toByteArray() ); - } else { - return engine->undefinedValue(); - } - return engine->newQObject( object, QScriptEngine::ScriptOwnership ); -} - -QScriptValue dataStreamToScript( QScriptEngine *engine, const DataStreamPrototypePtr &stream ) -{ - return engine->newQObject( const_cast(stream), QScriptEngine::QtOwnership, - QScriptEngine::PreferExistingWrapperObject ); -} - -void dataStreamFromScript( const QScriptValue &object, DataStreamPrototypePtr &stream ) -{ - stream = qobject_cast< DataStreamPrototype* >( object.toQObject() ); -} - -DataStreamPrototype::DataStreamPrototype( QObject *parent ) - : QObject( parent ) -{ -} - -DataStreamPrototype::DataStreamPrototype( const QByteArray &byteArray, QObject *parent ) - : QObject( parent ) -{ - QBuffer *buffer = new QBuffer( const_cast(&byteArray), this ); - buffer->open( QIODevice::ReadOnly ); - m_dataStream = QSharedPointer< QDataStream >( new QDataStream(buffer) ); -} - -DataStreamPrototype::DataStreamPrototype( QIODevice *device, QObject *parent ) - : QObject( parent ) -{ - if ( !device->isOpen() ) { - qWarning() << "Device not opened"; - } - m_dataStream = QSharedPointer< QDataStream >( new QDataStream(device) ); -} - -DataStreamPrototype::DataStreamPrototype( DataStreamPrototype *other ) - : QObject(), m_dataStream(other->stream()) -{ -} - -QDataStream *DataStreamPrototype::thisDataStream() const -{ - return m_dataStream.data(); -} - -qint8 DataStreamPrototype::readInt8() -{ - qint8 i; - thisDataStream()->operator >>( i ); - return i; -} - -quint8 DataStreamPrototype::readUInt8() -{ - quint8 i; - thisDataStream()->operator >>( i ); - return i; -} - -quint16 DataStreamPrototype::readUInt16() -{ - quint16 i; - thisDataStream()->operator >>( i ); - return i; -} - -qint32 DataStreamPrototype::readInt32() -{ - qint32 i; - thisDataStream()->operator >>( i ); - return i; -} - -qint16 DataStreamPrototype::readInt16() -{ - qint16 i; - thisDataStream()->operator >>( i ); - return i; -} - -quint32 DataStreamPrototype::readUInt32() -{ - quint32 i; - thisDataStream()->operator >>( i ); - return i; -} - -QString DataStreamPrototype::readString() -{ - QString string; - char character; - while ( thisDataStream()->device()->getChar(&character) && character != 0 ) { - string.append( character ); - } - return string; -} - -QByteArray DataStreamPrototype::readBytes( uint byteCount ) -{ - char *chars = new char[ byteCount ]; - const uint bytesRead = thisDataStream()->readRawData( chars, byteCount ); - if ( bytesRead != byteCount ) { - qWarning() << "Did not read all requested bytes, read" << bytesRead << "of" << byteCount; - } - QByteArray bytes( chars ); - delete[] chars; - return bytes; -} - -QByteArray DataStreamPrototype::readBytesUntilZero() -{ - QByteArray bytes; - char character; - while ( thisDataStream()->device()->getChar(&character) && character != 0 ) { - bytes.append( character ); - } - return bytes; -} - -QString NetworkRequest::url() const -{ - QMutexLocker locker( m_mutex ); - return m_url; -} - -QString NetworkRequest::userUrl() const -{ - QMutexLocker locker( m_mutex ); - return m_userUrl; -} - -bool NetworkRequest::isRunning() const -{ - QMutexLocker locker( m_mutex ); - return m_reply; -} - -bool NetworkRequest::isFinished() const -{ - QMutexLocker locker( m_mutex ); - return m_isFinished; -} - -bool NetworkRequest::isRedirected() const -{ - QMutexLocker locker( m_mutex ); - return m_redirectUrl.isValid(); -} - -QUrl NetworkRequest::redirectedUrl() const -{ - QMutexLocker locker( m_mutex ); - return m_redirectUrl; -} - -QString NetworkRequest::postData() const -{ - QMutexLocker locker( m_mutex ); - return m_postData; -} - -quint64 NetworkRequest::uncompressedSize() const -{ - QMutexLocker locker( m_mutex ); - return m_uncompressedSize; -} - -void NetworkRequest::setUserData( const QVariant &userData ) -{ - QMutexLocker locker( m_mutex ); - m_userData = userData; -} - -QVariant NetworkRequest::userData() const -{ - QMutexLocker locker( m_mutex ); - return m_userData; -} - -#include "scriptapi.moc" - -} // namespace ScriptApi diff --git a/engine/script/scriptapi.h b/engine/script/scriptapi.h deleted file mode 100644 index b48d42e..0000000 --- a/engine/script/scriptapi.h +++ /dev/null @@ -1,1749 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -/** @file - * @brief This file contains helper classes to be used from service provider plugin scripts. - * - * @author Friedrich Pülz */ - -#ifndef SCRIPTING_HEADER -#define SCRIPTING_HEADER - -// Own includes -#include "enums.h" -#include "departureinfo.h" // TODO for eg. PublicTransportInfoList typedef - -// Qt includes -#include -#include // Base class -#include -#include - -class PublicTransportInfo; -class ServiceProviderData; -class KConfigGroup; -class QScriptContextInfo; -class QNetworkRequest; -class QReadWriteLock; -class QNetworkReply; -class QNetworkAccessManager; -class QMutex; - -/** @brief Stores information about a departure/arrival/journey/stop suggestion. */ -typedef QHash TimetableData; - -/** @brief Namespace for classes exposed to scripts. */ -namespace ScriptApi { - -class Network; - -/** @ingroup scriptApi - * @{ */ -/** - * @brief Represents one asynchronous request, created with Network::createRequest(). - * - * To get notified about new data, connect to either the finished() or the readyRead() signal. - **/ -class NetworkRequest : public QObject { - Q_OBJECT - Q_PROPERTY( QString url READ url ) - Q_PROPERTY( QUrl redirectedUrl READ redirectedUrl ) - Q_PROPERTY( bool isRunning READ isRunning ) - Q_PROPERTY( bool isFinished READ isFinished ) - Q_PROPERTY( bool isRedirected READ isRedirected ) - Q_PROPERTY( QString postData READ postData ) - friend class Network; - -public: - /** @brief Creates an invalid request object. Needed for Q_DECLARE_METATYPE. */ - NetworkRequest( QObject* parent = 0 ); - - /** @brief Create a new request object for @p url, managed by @p network. */ - explicit NetworkRequest( const QString &url, const QString &userUrl, - Network *network, QObject* parent = 0 ); - - /** @brief Destructor. */ - virtual ~NetworkRequest(); - - typedef QSharedPointer< NetworkRequest > Ptr; - - /** - * @brief The URL of this request. - * - * @note The URL can not be changed, a request object is only used for @em one request. - **/ - QString url() const; - - /** - * @brief An URL for this request that should be shown to users. - * This can be eg. an URL to an HTML document showing the data that is also available - * at the requested URL, but eg. in XML format. - * @see url() - **/ - QString userUrl() const; - - /** - * @brief Whether or not the request is currently running. - **/ - bool isRunning() const; - - /** - * @brief Whether or not the request is finished (successful or not), ie. was running. - **/ - bool isFinished() const; - - /** @brief Whether or not this request was redirected. */ - bool isRedirected() const; - - /** @brief Get the redirected URL of this request, if any. */ - QUrl redirectedUrl() const; - - /** - * @brief Set the data to sent to the server when using Network::post(). - * - * This function automatically sets the "ContentType" header of the request to the used - * charset. If you want to use another value for the "ContentType" header than the - * data is actually encoded in, you can change the header using setHeader() after calling - * this function. - * @note If the request is already started, no more POST data can be set and this function - * will do nothing. - * - * @param postData The data to be POSTed. - * @param charset The charset to encode @p postData with. If charset is an empty string (the - * default) the "ContentType" header gets used if it was set using setHeader(). Otherwise - * utf8 gets used. - * @see isRunning() - **/ - Q_INVOKABLE void setPostData( const QString &postData, const QString &charset = QString() ); - - /** @brief Get the data to sent to the server when using Network::post(). */ - QString postData() const; - - /** @brief Get the @p header decoded using @p charset. */ - Q_INVOKABLE QString header( const QString &header, const QString& charset ) const; - - /** - * @brief Set the @p header of this request to @p value. - * - * @note If the request is already started, no more headers can be set and this function - * will do nothing. - * - * @param header The name of the header to change. - * @param value The new value for the @p header. - * @param charset The charset to encode @p header and @p value with. If charset is an empty - * string (the default) the "ContentType" header gets used if it was set using setHeader(). - * Otherwise utf8 gets used. - * @see isRunning() - **/ - Q_INVOKABLE void setHeader( const QString &header, const QString &value, - const QString &charset = QString() ); - - quint64 uncompressedSize() const; - - /** - * @brief Set custom @p userData for the request. - * - * This data is available to slots connected to the finished() signal. - **/ - Q_INVOKABLE void setUserData( const QVariant &userData ); - - /** - * @brief Get custom user data stored with setUserData(). - **/ - Q_INVOKABLE QVariant userData() const; - -public Q_SLOTS: - /** - * @brief Aborts this (running) request. - * - * @note If the request has not already been started, it cannot be aborted, of course, - * and this function will do nothing. - **/ - void abort(); - -Q_SIGNALS: - /** - * @brief Emitted when this request was started. - **/ - void started(); - - /** - * @brief Emitted when this request got aborted or timed out. - * @param timedOut Whether or not the request was aborted because of a timeout. - **/ - void aborted( bool timedOut = false ); - - /** - * @brief Emitted when this request has finished. - * - * @param data The complete data downloaded for this request. - * @param error @c True, if there was an error executing the request, @c false otherwise. - * @param errorString A human readable description of the error if @p error is @c true. - * @param statusCode The HTTP status code that was received or -1 if there was an error. - * @param size The size in bytes of the received data. - * @param url The URL of the request. - * @param userData Custom data stored for the request, see setUserData(). - **/ - void finished( const QByteArray &data = QByteArray(), bool error = false, - const QString &errorString = QString(), int statusCode = -1, int size = 0, - const QString &url = QString(), const QVariant &userData = QVariant() ); - - /** - * @brief Emitted when new data is available for this request. - * - * @param data New downloaded data for this request. - **/ - void readyRead( const QByteArray &data ); - - void redirected( const QUrl &newUrl ); - -protected Q_SLOTS: - void slotFinished(); - void slotReadyRead(); - -protected: - void started( QNetworkReply* reply, int timeout = 0 ); - QByteArray postDataByteArray() const; - QNetworkRequest *request() const; - QByteArray getCharset( const QString &charset = QString() ) const; - bool isValid() const; - -private: - QMutex *m_mutex; - const QString m_url; - const QString m_userUrl; - QUrl m_redirectUrl; - Network *m_network; - bool m_isFinished; - QNetworkRequest *m_request; - QNetworkReply *m_reply; - QByteArray m_data; - QByteArray m_postData; - quint32 m_uncompressedSize; - QVariant m_userData; -}; -/** \} */ // @ingroup scriptApi - -/** @ingroup scriptApi - * @{ */ -/** - * @brief Provides network access to scripts. - * - * An instance of the Network class is available for scripts as @b network. It can be used to - * synchronously or asynchronously download data. It doesn't really matter what method the script - * uses, because every call of a script function from the data engine happens in it's own thread - * and won't block anything. For the threads ThreadWeaver gets used with jobs of type ScriptJob. - * - * To download a document synchronously simply call getSynchronous() with the URL to download. - * When the download is finished getSynchronous(this request using the POST method) returns and the script can start parsing the - * document. There is a default timeout of 30 seconds. If a requests takes more time it gets - * aborted. To define your own timeout you can give it as argument to getSynchronous(). - * @note downloadSynchronous() is an alias for getSynchronous(). - * - * @code - * var document = network.getSynchronous( url ); - * // Parse the downloaded document here - * @endcode - * @n - * - * To create a network request which should be executed asynchronously use createRequest() with - * the URL of the request as argument. The script should then connect to either the finished() - * or readyRead() signal of the network request object. The request gets started by calling - * get() / download(). To only get the headers use head(). - * The example below shows how to asynchronously request a document from within a script: - * @code - var request = network.createRequest( url ); - request.finished.connect( handler ); - network.get( request ); - - function handler( document ) { - // Parse the downloaded document here - }; - @endcode - * @n - * - * If a script needs to use the POST method to request data use post(). The data to be sent in - * a POST request can be set using NetworkRequest::setPostData(). - * - * @note One request object created with createRequest() can @em not be used multiple times in - * parallel. To start another request create a new request object. - * @note There is a global 60 seconds timeout for all network requests to finish. - **/ -class Network : public QObject, public QScriptable { - Q_OBJECT - Q_PROPERTY( QString lastUrl READ lastUrl ) - Q_PROPERTY( bool lastDownloadAborted READ lastDownloadAborted ) - Q_PROPERTY( QString fallbackCharset READ fallbackCharset ) - Q_PROPERTY( int runningRequestCount READ runningRequestCount ) - friend class NetworkRequest; - -public: - /** @brief The default timeout in milliseconds for network requests. */ - static const int DEFAULT_TIMEOUT = 30000; - - /** @brief Constructor. */ - explicit Network( const QByteArray &fallbackCharset = QByteArray(), QObject* parent = 0 ); - - /** @brief Destructor. */ - virtual ~Network(); - - /** - * @brief Get the last requested URL. - * - * The last URL gets updated every time a request gets started, eg. using get(), post(), - * getSynchronous(), download(), downloadSynchronous(), etc. - **/ - Q_INVOKABLE QString lastUrl() const; - - /** - * @brief Get an URL for the last requested URL that should be shown to users. - **/ - Q_INVOKABLE QString lastUserUrl() const; - - /** - * @brief Clears the last requested URL. - **/ - Q_INVOKABLE void clear(); - - /** - * @brief Returns @c true, if the last download was aborted before it was ready. - * - * Use lastUrl() to get the URL of the aborted download. Downloads may be aborted eg. by - * closing plasma. - **/ - Q_INVOKABLE bool lastDownloadAborted() const; - - /** - * @brief Download the document at @p url synchronously. - * - * @param url The URL to download. - * @param userUrl An URL for this request that should be shown to users. - * This can be eg. an URL to an HTML document showing the data that is also available - * at the requested URL, but eg. in XML format. - * @param timeout Maximum time in milliseconds to wait for the reply to finish. - * If smaller than 0, no timeout gets used. - * Note that there is a global 60 seconds timeout for all network requests to finish. - **/ - Q_INVOKABLE QByteArray getSynchronous( const QString &url, const QString &userUrl = QString(), - int timeout = DEFAULT_TIMEOUT ); - - /** - * @brief This is an alias for getSynchronous(). - **/ - Q_INVOKABLE inline QByteArray downloadSynchronous( const QString &url, - const QString &userUrl = QString(), - int timeout = DEFAULT_TIMEOUT ) - { - return getSynchronous(url, userUrl, timeout); - }; - - /** - * @brief Creates a new NetworkRequest for asynchronous network access. - * - * @note Each NetworkRequest object can only be used once for one download. - * @see get, download, post, head - **/ - Q_INVOKABLE NetworkRequest *createRequest( const QString &url, - const QString &userUrl = QString() ); - - /** - * @brief Perform the network @p request asynchronously. - * - * @param request The NetworkRequest object created with createRequest(). - * @param timeout Maximum time in milliseconds to wait for the reply to finish. - * If smaller than 0, no timeout gets used. - * Note that there is a global 60 seconds timeout for all network requests to finish. - **/ - Q_INVOKABLE void get( NetworkRequest *request, int timeout = DEFAULT_TIMEOUT ); - - /** - * @brief Perform the network @p request asynchronously using POST method. - * - * @param request The NetworkRequest object created with createRequest(). - * @param timeout Maximum time in milliseconds to wait for the reply to finish. - * If smaller than 0, no timeout gets used. - * Note that there is a global 60 seconds timeout for all network requests to finish. - **/ - Q_INVOKABLE void post( NetworkRequest *request, int timeout = DEFAULT_TIMEOUT ); - - /** - * @brief Perform the network @p request asynchronously, but only get headers. - * - * @param request The NetworkRequest object created with createRequest(). - * @param timeout Maximum time in milliseconds to wait for the reply to finish. - * If smaller than 0, no timeout gets used. - * Note that there is a global 60 seconds timeout for all network requests to finish. - **/ - Q_INVOKABLE void head( NetworkRequest *request, int timeout = DEFAULT_TIMEOUT ); - - /** - * @brief This is an alias for get(). - **/ - Q_INVOKABLE inline void download( NetworkRequest *request, int timeout = DEFAULT_TIMEOUT ) { - get( request, timeout ); - }; - - /** - * @brief Returns whether or not there are network requests running in the background. - * @see runningRequests - **/ - Q_INVOKABLE bool hasRunningRequests() const; - - /** - * @brief Get a list of all running asynchronous requests. - * - * If hasRunningRequests() returns @c false, this will return an empty list. - * If hasRunningRequests() returns @c true, this @em may return a non-empty list (maybe only - * a synchronous request is running). - * @see hasRunningRequests - **/ - Q_INVOKABLE QList< NetworkRequest::Ptr > runningRequests() const; - - /** - * @brief Returns the number of currently running requests. - * - * @note This includes running synchronous requests. The number of request objects returned - * by runningRequests() may be less than the number returned here, because it only returns - * running asynchronous requests. - **/ - Q_INVOKABLE int runningRequestCount() const; - - /** - * @brief Returns the charset to use for decoding documents, if it cannot be detected. - * - * The fallback charset can be selected in the XML file, as \-tag. - **/ - Q_INVOKABLE QByteArray fallbackCharset() const; - -Q_SIGNALS: - /** - * @brief Emitted when an asynchronous request has been started. - * - * This signal is @em not emitted if the network gets accessed synchronously. - * @param request The request that has been started. - * @see synchronousRequestStarted() - **/ - void requestStarted( const NetworkRequest::Ptr &request ); - - /** - * @brief Emitted when an asynchronous request has finished. - * - * This signal is @em not emitted if the network gets accessed synchronously. - * @param request The request that has finished. - * @param data Received data decoded to a string. - * @param error @c True, if there was an error executing the request, @c false otherwise. - * @param errorString A human readable description of the error if @p error is @c true. - * @param timestamp The date and time on which the request was finished. - * @param statusCode The HTTP status code received. - * @param size The size in bytes of the received data. - * @see synchronousRequestFinished() - **/ - void requestFinished( const NetworkRequest::Ptr &request, - const QByteArray &data = QByteArray(), - bool error = false, const QString &errorString = QString(), - const QDateTime ×tamp = QDateTime(), - int statusCode = 200, int size = 0 ); - - /** @brief Emitted when an asynchronous @p request was redirect to @p newUrl. */ - void requestRedirected( const NetworkRequest::Ptr &request, const QUrl &newUrl ); - - /** - * @brief Emitted when a synchronous request has been started. - * - * This signal is @em not emitted if the network gets accessed asynchronously. - * @param url The URL of the request that has been started. - * @see requestStarted() - **/ - void synchronousRequestStarted( const QString &url ); - - /** - * @brief Emitted when an synchronous request has finished. - * - * This signal is @em not emitted if the network gets accessed asynchronously. - * @param url The URL of the request that has finished. - * @param data Received data decoded to a string. - * @param cancelled Whether or not the request has been cancelled, eg. because of a timeout. - * @param statusCode The HTTP status code if @p cancelled is @c false. - * @param waitTime The time spent waiting for the download to finish. - * @param size The size in bytes of the received data. - * @see requestFinished() - **/ - void synchronousRequestFinished( const QString &url, const QByteArray &data = QByteArray(), - bool cancelled = false, int statusCode = 200, - int waitTime = 0, int size = 0 ); - - /** - * @brief Emitted when a synchronous request has been redirected. - * - * This signal is @em not emitted if the network gets accessed asynchronously. - * @param url The URL of the request that has been redirected. - * @see requestRedirected() - **/ - void synchronousRequestRedirected( const QString &url ); - - /** - * @brief Emitted when all running requests are finished. - * - * Emitted when a request finishes and there are no more running requests, - * ie. hasRunningRequests() returns @c false. - **/ - void allRequestsFinished(); - - /** - * @brief Emitted when an asynchronous request got aborted. - * - * This signal is @em not emitted if the network gets accessed synchronously. - * @param request The request that was aborted. - **/ - void requestAborted( const NetworkRequest::Ptr &request ); - - /** - * @brief Emitted to abort all running synchronous requests. - * - * This signal gets emitted by abortSynchronousRequests(). - **/ - void doAbortSynchronousRequests(); - -public Q_SLOTS: - /** - * @brief Abort all running requests (synchronous and asynchronous). - **/ - void abortAllRequests(); - - /** - * @brief Abort all running synchronous requests. - **/ - void abortSynchronousRequests(); - -protected Q_SLOTS: - void slotRequestStarted(); - void slotRequestFinished( const QByteArray &data = QByteArray(), bool error = false, - const QString &errorString = QString(), int statusCode = -1, - int size = 0 ); - void slotRequestAborted(); - void slotRequestRedirected( const QUrl &newUrl ); - -protected: - bool checkRequest( NetworkRequest *request ); - NetworkRequest::Ptr getSharedRequest( NetworkRequest *request ) const; - void emitSynchronousRequestFinished( const QString &url, const QByteArray &data = QByteArray(), - bool cancelled = false, int statusCode = 200, - int waitTime = 0, int size = 0 ); - -private: - QMutex *m_mutex; - const QByteArray m_fallbackCharset; - QNetworkAccessManager *m_manager; - bool m_quit; - int m_synchronousRequestCount; - QString m_lastUrl; - QString m_lastUserUrl; - bool m_lastDownloadAborted; - QList< NetworkRequest::Ptr > m_requests; - QList< NetworkRequest::Ptr > m_finishedRequests; -}; -/** \} */ // @ingroup scriptApi - -/** @ingroup scriptApi - * @{ */ -/** - * @brief A helper class for scripts. - * - * An instance of this class gets published to scripts as "helper". - * Scripts can use it's functions, like here: - * @code - * var stripped = helper.stripTags("
Test
"); - * // stripped == "Test" - * - * var timeValues = helper.matchTime( "15:28" ); - * // timeValues == { hour: 15, minutes: 28, error: false } - * - * var timeString = helper.formatTime( timeValues[0], timeValues[1] ); - * // timeString == "15:28" - * - * var duration = helper.duration("15:20", "15:45"); - * // duration == 25 - * - * var time2 = helper.addMinsToTime("15:20", duration); - * // time2 == "15:45" - * - * helper.debug("Debug message, eg. something unexpected happened"); - * @endcode - **/ -class Helper : public QObject, protected QScriptable { - Q_OBJECT - Q_ENUMS( ErrorSeverity ) - -public: - /** @brief The severity of an error. */ - enum ErrorSeverity { - Information, /**< The message is only an information. */ - Warning, /**< The message is a warning. */ - Fatal /**< The message describes a fatal error. */ - }; - - /** - * @brief Creates a new helper object. - * - * @param serviceProviderId The ID of the service provider this Helper object is created for. - * @param parent The parent object. - **/ - explicit Helper( const QString &serviceProviderId, QObject* parent = 0 ); - - virtual ~Helper(); - - /** - * @brief Prints an information @p message on stdout and logs it in a file. - * - * Logs the message with the given @p failedParseText string, eg. the HTML code where parsing - * failed. The message gets also send to stdout with a short version of the data. - * The log file is normally located at "~/.kde4/share/apps/plasma_engine_publictransport/serviceproviders.log". - * If the same message gets sent repeatedly, it gets sent only once. - * - * @param message The information message. - * @param failedParseText The text in the source document to which the message applies. - **/ - Q_INVOKABLE void information( const QString &message, const QString &failedParseText = QString() ) { - messageReceived(message, failedParseText, Information); }; - - /** - * @brief Prints a warning @p message on stdout and logs it in a file. - * - * Logs the message with the given @p failedParseText string, eg. the HTML code where parsing - * failed. The message gets also send to stdout with a short version of the data. - * The log file is normally located at "~/.kde4/share/apps/plasma_engine_publictransport/serviceproviders.log". - * If the same message gets sent repeatedly, it gets sent only once. - * - * @param message The warning message. - * @param failedParseText The text in the source document to which the message applies. - **/ - Q_INVOKABLE void warning( const QString &message, const QString &failedParseText = QString() ) { - messageReceived(message, failedParseText, Warning); }; - - /** - * @brief Prints an error @p message on stdout and logs it in a file. - * - * Logs the message with the given @p failedParseText string, eg. the HTML code where parsing - * failed. The message gets also send to stdout with a short version of the data. - * The log file is normally located at "~/.kde4/share/apps/plasma_engine_publictransport/serviceproviders.log". - * If the same message gets sent repeatedly, it gets sent only once. - * - * @note If the error is fatal consider using @c throw instead to create an exception and abort - * script execution. - * - * @param message The error message. - * @param failedParseText The text in the source document to which the message applies. - **/ - Q_INVOKABLE void error( const QString &message, const QString &failedParseText = QString() ) { - messageReceived(message, failedParseText, Fatal); }; - - /** - * @brief Decodes HTML entities in @p html. - * - * For example " " gets replaced by " ". - * HTML entities which include a charcode, eg. "d" are also replaced, in the example - * by the character for the charcode 100, ie. QChar(100). - * - * @param html The string to be decoded. - * @return @p html with decoded HTML entities. - **/ - Q_INVOKABLE static QString decodeHtmlEntities( const QString &html ); - - /** @brief Encodes HTML entities in @p html, e.g. "<" is replaced by "<". */ - Q_INVOKABLE static QString encodeHtmlEntities( const QString& html ); - - /** - * @brief Decodes the given HTML document. - * - * First it tries QTextCodec::codecForHtml(). - * If that doesn't work, it parses the document for the charset in a meta-tag. - **/ - Q_INVOKABLE static QString decodeHtml( const QByteArray& document, - const QByteArray& fallbackCharset = QByteArray() ); - - /** @brief Decode @p document using @p charset. */ - Q_INVOKABLE static QString decode( const QByteArray& document, - const QByteArray& charset = QByteArray() ); - - /** - * @brief Decode @p document using @p charset. - * This is needed for scripts that use a string as argument. If the @p charset argument gets - * converted to QByteArray from a script string value automatically, it will be empty. - **/ - Q_INVOKABLE static QString decode( const QByteArray& document, const QString& charset ) { - return decode( document, charset.toAscii() ); - }; - - /** - * @brief Trims spaces from the beginning and the end of the given string @p str. - * - * @note The HTML entitiy   is also trimmed. - * - * @param str The string to be trimmed. - * @return @p str without spaces at the beginning or end. - **/ - Q_INVOKABLE static QString trim( const QString &str ); - - /** - * @brief Like trim(), additionally removes double whitespace in the middle of @p str. - * - * @note All occurrences of the HTML entitiy   are also removes. - * - * @param str The string to be simplified. - * @return @p str without whitespace in the middle, at the beginning or end. - **/ - Q_INVOKABLE static QString simplify( const QString &str ); - - /** - * @brief Removes all HTML tags from str. - * - * This function works with attributes which contain a closing tab as strings. - * - * @param str The string from which the HTML tags should be removed. - * @return @p str without HTML tags. - **/ - Q_INVOKABLE static QString stripTags( const QString &str ); - - /** - * @brief Makes the first letter of each word upper case, all others lower case. - * - * @param str The input string. - * @return @p str in camel case. - **/ - Q_INVOKABLE static QString camelCase( const QString &str ); - - /** - * @brief Extracts a block from @p str, which begins at the first occurrence of @p beginString - * in @p str and end at the first occurrence of @p endString in @p str. - * - * @deprecated Does only work with fixed strings. Use eg. findFirstHtmlTag() instead. - * @bug The returned string includes beginString but not endString. - * - * @param str The input string. - * @param beginString A string to search for in @p str and to use as start position. - * @param endString A string to search for in @p str and to use as end position. - * @return The text block in @p str between @p beginString and @p endString. - **/ - Q_INVOKABLE static QString extractBlock( const QString &str, - const QString &beginString, const QString &endString ); - - /** - * @brief Get a map with the hour and minute values parsed from @p str using @p format. - * - * QVariantMap gets converted to an object in scripts. The result can be used in the script - * like this: - * @code - var time = matchTime( "15:23" ); - if ( !time.error ) { - var hour = time.hour; - var minute = time.minute; - } - @endcode - * - * @param str The string containing the time to be parsed, eg. "08:15". - * @param format The format of the time string in @p str. Default is "hh:mm". - * @return A map with two values: 'hour' and 'minute' parsed from @p str. On error it contains - * an 'error' value of @c true. - * @see formatTime - **/ - Q_INVOKABLE static QVariantMap matchTime( const QString &str, const QString &format = "hh:mm" ); - - /** - * @brief Get a date object parsed from @p str using @p format. - * - * @param str The string containing the date to be parsed, eg. "2010-12-01". - * @param format The format of the time string in @p str. Default is "YY-MM-dd". - * @return The matched date. - * @see formatDate - **/ - Q_INVOKABLE static QDate matchDate( const QString &str, const QString &format = "yyyy-MM-dd" ); - - /** - * @brief Formats the time given by the values @p hour and @p minute - * as string in the given @p format. - * - * @param hour The hour value of the time. - * @param minute The minute value of the time. - * @param format The format of the time string to return. Default is "hh:mm". - * @return The formatted time string. - * @see matchTime - **/ - Q_INVOKABLE static QString formatTime( int hour, int minute, const QString &format = "hh:mm" ); - - /** - * @brief Formats the time given by the values @p hour and @p minute - * as string in the given @p format. - * - * @param year The year value of the date. - * @param month The month value of the date. - * @param day The day value of the date. - * @param format The format of the date string to return. Default is "yyyy-MM-dd". - * @return The formatted date string. - * @see matchTime - **/ - Q_INVOKABLE static QString formatDate( int year, int month, int day, - const QString &format = "yyyy-MM-dd" ); - - /** - * @brief Formats @p dateTime using @p format. - **/ - Q_INVOKABLE static QString formatDateTime( const QDateTime &dateTime, - const QString &format = "yyyy-MM-dd" ); - - /** - * @brief Calculates the duration in minutes from the time in @p time1 until @p time2. - * - * @param time1 A string with the start time, in the given @p format. - * @param time2 A string with the end time, in the given @p format. - * @param format The format of @p time1 and @p time2. Default is "hh:mm". - * @return The number of minutes from @p time1 until @p time2. If @p time2 is earlier than - * @p time1 a negative value gets returned. - **/ - Q_INVOKABLE static int duration( const QString &time1, const QString &time2, - const QString &format = "hh:mm" ); - - /** - * @brief Adds @p minsToAdd minutes to the given @p time. - * - * @param time A string with the base time. - * @param minsToAdd The number of minutes to add to @p time. - * @param format The format of @p time. Default is "hh:mm". - * @return A time string formatted in @p format with the calculated time. - **/ - Q_INVOKABLE static QString addMinsToTime( const QString &time, int minsToAdd, - const QString &format = "hh:mm" ); - - /** - * @brief Adds @p daysToAdd days to the date in @p dateTime. - * - * @param dateTime A string with the base time. - * @param daysToAdd The number of minutes to add to @p time. - * @param format The format of @p time. Default is "hh:mm". - * @return A time string formatted in @p format with the calculated time. - **/ - Q_INVOKABLE static QDateTime addDaysToDate( const QDateTime &dateTime, int daysToAdd ); - - /** - * @brief Adds @p daysToAdd days to @p date. - * - * @param date A string with the base date. - * @param daysToAdd The number of days to add to @p date. - * @param format The format of @p date. Default is "yyyy-MM-dd". - * @return A date string formatted in @p format with the calculated date. - **/ - Q_INVOKABLE static QString addDaysToDate( const QString &date, int daysToAdd, - const QString &format = "yyyy-MM-dd" ); - - /** - * @brief Splits @p str at @p sep, but skips empty parts. - * - * @param string The string to split. - * @param separator The separator. - * @return A list of string parts. - **/ - Q_INVOKABLE static QStringList splitSkipEmptyParts( const QString &string, - const QString &separator ); - - /** - * @brief Finds the first occurrence of an HTML tag with @p tagName in @p str. - * - * @param str The string containing the HTML tag to be found. - * @param tagName The name of the HTML tag to be found, ie. <tagName>. - * @param options The same as in findHtmlTags(), "maxCount" will be set to 1. - * - * @b Example: - * @code - * // This matches the first <table> tag found in html - * // which has a class attribute with a value that matches - * // the regular expression pattern "test\\d+", - * // eg. "test1", "test2", ... - * var result = helper.findFirstHtmlTag( html, "table", - * {attributes: {"class": "test\\d+"}} ); - * @endcode - * - * @return A map with properties like in findHtmlTags(). Additionally these properties are - * returned: - * @li @b found: A boolean, @c true if the tag was found, @c false otherwise. - * @see findHtmlTags - **/ - Q_INVOKABLE static QVariantMap findFirstHtmlTag( const QString &str, const QString &tagName, - const QVariantMap &options = QVariantMap() ); - - /** - * @brief This is an overloaded function which expects a value for "tagName" in @p options. - * @overload QVariantMap findFirstHtmlTag(const QString&,const QString&,const QVariantMap&) - **/ - Q_INVOKABLE static inline QVariantMap findFirstHtmlTag( - const QString &str, const QVariantMap &options = QVariantMap() ) - { - return findFirstHtmlTag( str, options["tagName"].toString(), options ); - }; - - /** - * @brief Find all occurrences of (top level) HTML tags with @p tagName in @p str. - * - * Using this function avoids having to deal with various problems when matching HTML elements: - * @li Nested HTML elements with the same @p tagName. When simply searching for the first - * closing tag after the found opening tag, a nested closing tag gets matched. If you are - * sure that there are no nested tags or if you want to only match until the first nested - * closing tag set the option "noNesting" in @p options to @c true. - * @li Matching tags with specific attributes. This function extracts all attributes of a - * matched tag. They can have values, which can be put in single/double/no quotation marks. - * To only match tags with specific attributes, add them to the "attributes" option in - * @p options. Regular expressions can be used to match the attribute name and value - * independently. Attribute order does not matter. - * @li Matching HTML tags correctly. For example a ">" inside an attributes value could cause - * problems and have the tag cut off there. - * - * @note This function only returns found top level tags. These found tags may contain matching - * child tags. You can use this function again on the contents string of a found top level - * tag to find its child tags. - * - * @b Example: - * @code - * // This matches all <div> tags found in html which - * // have a class attribute with the value "test" and only - * // numbers as contents, eg. "
42
". - * var result = helper.findHtmlTags( html, "div", - * {attributes: {"class": "test"}, - * contentsRegExp: "\\d+"} ); - * @endcode - * - * @param str The string containing the HTML tags to be found. - * @param tagName The name of the HTML tags to be found, ie. <tagName>. - * @param options A map with these properties: - * @li @b attributes: A map containing all required attributes and it's values. The keys of that - * map are the names of required attributes and can be regular expressions. The values - * are the values of the required attributes and are also handled as regular expressions. - * @li @b contentsRegExp: A regular expression pattern which the contents of found HTML tags - * must match. If it does not match, that tag does not get returned as found. - * If no parenthesized subexpressions are present in this regular expression, the whole - * matching string gets used as contents. If more than one parenthesized subexpressions - * are found, only the first one gets used. The regular expression gets matched case - * insensitive. By default all content of the HTML tag gets matched. - * @li @b position: An integer, where to start the search for tags. If the position is inside - * a top-level matching tag, its child tags will be matched and other following top-level - * tags. This is 0 by default. - * @li @b noContent: A boolean, @c false by default. If @c true, HTML tags without any - * content are matched, eg. "br" or "img" tags. Otherwise tags need to be closed to get - * matched. - * @li @b noNesting: A boolean, @c false by default. If @c true, no checks will be made to - * ensure that the first found closing tag belongs to the opening tag. In this case the - * found contents always end after the first closing tag after the opening tag, no matter - * if the closing tag belongs to a nested tag or not. By setting this to @c true you can - * enhance performance. - * @li @b maxCount: The maximum number of HTML tags to match or 0 to match any number of HTML tags. - * @li @b debug: A boolean, @c false by default. If @c true, more debug output gets generated. - * - * @return A list of maps, each map represents one found tag and has these properties: - * @li @b contents: A string, the contents of the found tag (if found is @c true). - * @li @b position: An integer, the position of the first character of the found tag - * (ie. '<') in @p str (if found is @c true). - * @li @b endPosition: An integer, the position of the first character after the found end - * tag in @p str (if found is @c true). - * @li @b attributes: A map containing all found attributes of the tag and it's values (if - * found is @c true). The attribute names are the keys of the map, while the attribute - * values are the values of the map. - **/ - Q_INVOKABLE static QVariantList findHtmlTags( const QString &str, const QString &tagName, - const QVariantMap &options = QVariantMap() ); - - /** - * @brief This is an overloaded function which expects a value for "tagName" in @p options. - * @overload QVariantMap findHtmlTags(const QString&,const QString&,const QVariantMap&) - **/ - Q_INVOKABLE static inline QVariantList findHtmlTags( - const QString &str, const QVariantMap &options = QVariantMap() ) - { - return findHtmlTags( str, options["tagName"].toString(), options ); - }; - - /** - * @brief Finds all occurrences of HTML tags with @p tagName in @p str. - * - * This function uses findHtmlTags() to find the HTML tags and then extracts a name for each - * found tag from @p str. - * Instead of returning a list of all matched tags, a map is returned, with the found names as - * keys and the tag objects (as returned in a list by findHtmlTags()) as values. - * - * @b Example: - * @code - * // In this example the findNamedHtmlTags() function gets - * // used in a while loop to find columns (-tags) in rows - * // (-tags) and assign names to the found columns. This - * // makes it easy to parse HTML tables. - * - * // Initialize position value - * var tableRow = { position: -1 }; - * - * // Use findFirstHtmlTag() with the current position value to - * // find the next tag. The return value contains the - * // updated position and the boolean property "found" - * while ( (tableRow = helper.findFirstHtmlTag(html, "tr", - * {position: tableRow.position + 1})).found ) - * { - * // Find columns, ie. tags in a table row. - * // Each column gets a name assigned by findNamedHtmlTags(). - * // Ambiguous names are resolved by adding/increasing a - * // number after the name. The names get extracted from the - * // -tags class attributes, by matching the given - * // regular expression. - * var columns = helper.findNamedHtmlTags( tableRow.contents, "td", - * {ambiguousNameResolution: "addNumber", - * namePosition: {type: "attribute", name: "class", - * regexp: "([\\w]+)\\d+"}} ); - * - * // Check the "names" property of the return value, - * // should contain the names of all columns that are needed - * if ( !columns.names.contains("time") ) { - * // Notify the data engine about an error, - * // if a "time" column was expected - * helper.error("Didn't find all needed columns! " + - * "Found: " + columns.names, tableRow.contents); - * continue; - * } - * - * // Now read the contents of the columns - * var time = columns["time"].contents; - * - * // Read more and add data to the result set using result.addData() - * } - * @endcode - * - * @param str The string containing the HTML tag to be found. - * @param tagName The name of the HTML tag to be found, ie. <tagName>. - * @param options The same as in findHtmlTags(), but @em additionally these options can be used: - * @li @b namePosition: A map with more options, indicating the position of the name of tags: - * @li @em type: Can be @em "contents" (ie. use tag contents as name, the default) or - * @em "attribute" (ie. use a tag attribute value as name). If @em "attribute" is used - * for @em "type", the name of the attribute can be set as @em "name" property. - * Additionally a @em "regexp" property can be used to extract a string from the string - * that would otherwise be used as name as is. - * @li @em ambiguousNameResolution: Can be used to tell what should be done if the same name - * was found multiple times. This can currently be one of: @em "addNumber" (adds a - * number to the name, ie. "..1", "..2")., @em "replace" (a later match with an already - * matched name overwrites the old match, the default). - * - * @return A map with the found names as keys and the tag objects as values. @em Additionally - * these properties are returned: - * @li @b names: A list of all found tag names. - * @see findHtmlTags - **/ - Q_INVOKABLE static QVariantMap findNamedHtmlTags( const QString &str, const QString &tagName, - const QVariantMap &options = QVariantMap() ); - -signals: - /** - * @brief An error was received from the script. - * - * @param message The error message. - * @param failedParseText The text in the source document where parsing failed. - * @param severity The severity of the error. - **/ - void messageReceived( const QString &message, const QScriptContextInfo &contextInfo, - const QString &failedParseText = QString(), - Helper::ErrorSeverity severity = Warning ); - -private: - static QString getTagName( const QVariantMap &searchResult, const QString &type = "contents", - const QString ®Exp = QString(), const QString attributeName = QString() ); - void messageReceived( const QString &message, const QString &failedParseText, - Helper::ErrorSeverity severity ); - void emitRepeatedMessageWarning(); - - QMutex *m_mutex; - QString m_serviceProviderId; - QString m_lastErrorMessage; - int m_errorMessageRepetition; -}; -/** \} */ // @ingroup scriptApi - -/** @ingroup scriptApi - * @{ */ -/** - * @brief This class is used by scripts to store results in, eg. departures. - * - * An instance of this class gets published to scripts as @b result. - * Scripts can use it to add items to the result set, ie. departures/arrivals/journeys/ - * stop suggestions. Items can be added from scripts using addData(). - * @code - * // Add stop suggestion data to result set - * result.addData({ StopName: "Name" }); - * @endcode - **/ -class ResultObject : public QObject, protected QScriptable { - Q_OBJECT - Q_ENUMS( Feature Hint ) - Q_FLAGS( Features Hints ) - Q_PROPERTY( int count READ count ) - -public: - /** - * @brief Used to store enabled features. - * - * The meta object of ResultObject gets published to scripts under the name "enum" and contains - * this enumeration. - * - * @see enableFeature() - * @see isFeatureEnabled() - **/ - enum Feature { - NoFeature = 0x00, /**< No feature is enabled. */ - AutoPublish = 0x01, /**< Automatic publishing of the first few data items. Turn - * this off if you want to call publish() manually. */ - AutoDecodeHtmlEntities - = 0x02, /**< Automatic decoding of HTML entities in strings and string - * lists. If you are sure, that there are no HTML entities in the strings parsed - * from the downloaded documents, you can turn this feature off. You can also - * manually decode HTML entities using Helper::decodeHtmlEntities(). */ - AutoRemoveCityFromStopNames - = 0x04, /**< Automatic removing of city names from all stop names, ie. - * stop names in eg. RouteStops or Target). Scripts can help the data engine with - * this feature with the hints CityNamesAreLeft or CityNamesAreRight. - * @code - * result.giveHint( enum.CityNamesAreLeft ); - * result.giveHint( enum.CityNamesAreRight ); - * @endcode - * @see Hint */ - AllFeatures = AutoPublish | AutoDecodeHtmlEntities | AutoRemoveCityFromStopNames, - /**< All available features are enabled. */ - DefaultFeatures = AutoPublish | AutoDecodeHtmlEntities - /**< The default set of features gets enabled. */ - }; - Q_DECLARE_FLAGS( Features, Feature ) - - /** - * @brief Can be used by scripts to give hints to the data engine. - * - * The meta object of ResultObject gets published to scripts as @b enum and contains this - * enumeration. - * - * @see giveHint() - * @see isHintGiven() - **/ - enum Hint { - NoHint = 0x00, /**< No hints given. */ - DatesNeedAdjustment = 0x01, /**< Dates are set from today, not the requested date. They - * need to be adjusted by X days, where X is the difference in days between today - * and the requested date. */ - NoDelaysForStop = 0x02, /**< Delays are not available for the current stop, although - * delays are available for other stops. */ - CityNamesAreLeft = 0x04, /**< City names are most likely on the left of stop names. */ - CityNamesAreRight = 0x08 /**< City names are most likely on the right of stop names. */ - }; - Q_DECLARE_FLAGS( Hints, Hint ) - - /** - * @brief Creates a new ResultObject instance. - **/ - ResultObject( QObject* parent = 0 ); - - virtual ~ResultObject(); - - /** - * @brief Get the list of stored TimetableData objects. - * - * @return The list of stored TimetableData objects. - **/ - QList< TimetableData > data() const; - - Q_INVOKABLE inline QVariant data( int index, int information ) const { - return data( index, static_cast(information) ); - }; - Q_INVOKABLE QVariant data( int index, Enums::TimetableInformation information ) const; - - /** - * @brief Checks whether or not the list of TimetableData objects is empty. - * - * @return @c True, if the list of TimetableData objects isn't empty. @c False, otherwise. - **/ - Q_INVOKABLE bool hasData() const; - - /** - * @brief Returns the number of timetable elements currently in the resultset. - **/ - Q_INVOKABLE int count() const; - - /** - * @brief Whether or not @p feature is enabled. - * - * Script example: - * @code - * if ( result.isFeatureEnabled(enum.AutoPublish) ) { - * // Do something when the AutoPublish feature is enabled - * } - * @endcode - * - * By default all features are enabled. - * - * @param feature The feature to check. Scripts can access the Feature enumeration - * under the name "enum". - * - * @see Feature - **/ - Q_INVOKABLE bool isFeatureEnabled( Feature feature ) const; - - /** - * @brief Set whether or not @p feature is @p enabled. - * - * Script example: - * @code - * // Disable the AutoPublish feature - * result.enableFeature( enum.AutoPublish, false ); - * @endcode - * - * By default all features are enabled, disable unneeded features for better performance. - * - * @param feature The feature to enable/disable. Scripts can access the Feature enumeration - * under the name "enum". - * @param enable @c True to enable @p feature, @c false to disable it. - * - * @see Feature - **/ - Q_INVOKABLE void enableFeature( Feature feature, bool enable = true ); - - /** - * @brief Test if the given @p hint is set. - * - * Script example: - * @code - * if ( result.isHintGiven(enum.CityNamesAreLeft) ) { - * // Do something when the CityNamesAreLeft hint is given - * } - * @endcode - * - * By default no hints are set. - * - * @param hint The hint to check. Scripts can access the Hint enumeration - * under the name "enum". - */ - Q_INVOKABLE bool isHintGiven( Hint hint ) const; - - /** - * @brief Set the given @p hint to @p enable. - * - * Script example: - * @code - * // Remove the CityNamesAreLeft hint - * result.giveHint( enum.CityNamesAreLeft, false ); - * @endcode - * - * By default no hints are set. - * - * @param hint The hint to give. - * @param enable Whether the @p hint should be set or unset. - */ - Q_INVOKABLE void giveHint( Hint hint, bool enable = true ); - - /** - * @brief Get the currently enabled features. - * - * Scripts can access the Features enumeration like @verbatimenum.AutoPublish@endverbatim. - * By default this equals to DefaultFeatures. - */ - Features features() const; - - /** - * @brief Get the currently set hints. - * - * Scripts can access the Hints enumeration like @verbatimenum.CityNamesAreLeft@endverbatim. - * By default this equals to NoHints. - */ - Hints hints() const; - - static void dataList( const QList< TimetableData > &dataList, - PublicTransportInfoList *infoList, ParseDocumentMode parseMode, - Enums::VehicleType defaultVehicleType, const GlobalTimetableInfo *globalInfo, - ResultObject::Features features, ResultObject::Hints hints ); - -Q_SIGNALS: - /** - * @brief Can be called by scripts to trigger the data engine to publish collected data. - * - * This does not need to be called by scripts, the data engine will publish all collected data, - * when the script returns and all network requests are finished. After the first ten items - * have been added, this signal is emitted automatically, if the AutoPublish feature is - * enabled (the default). Use @verbatimresult.enableFeature(AutoPublish, false)@endverbatim to - * disable this feature. - * - * If collecting data takes too long, calling this signal causes the data collected so far - * to be published immediately. Good reasons to call this signal are eg. because additional - * documents need to be downloaded or because a very big document gets parsed. Visualizations - * connected to the data engine will then receive data not completely at once, but step by - * step. - * - * It also means that the first data items are published to visualizations faster. A good idea - * could be to only call publish() after the first few data items (similar to the AutoPublish - * feature). That way visualizations get the first dataset very quickly, eg. the data that - * fits into the current view. Remaining data will then be added after the script is finished. - * - * @note Do not call publish() too often, because it causes some overhead. Visualizations - * will get notified about the updated data source and process it at whole, ie. not only - * newly published items but also the already published items again. Publishing data in - * groups of less than ten items will be too much in most situations. But if eg. another - * document needs to be downloaded to make more data available, it is a good idea to call - * publish() before starting the download (even with less than ten items). - * Use count() to see how many items are collected so far. - * - * @see Feature - * @see setFeatureEnabled - **/ - void publish(); - - /** - * @brief Emitted when invalid data gets received through the addData() method. - * - * @param info The TimetableInformation which was invalid in @p map. - * @param errorMessage An error message explaining why the data for @p info in @p map - * is invalid. - * @param context The script context from which addData() was called. - * @param index The target index at which the data in @p map will be inserted into this result - * object. - * @param map The argument for addData(), which contained invalid data. - **/ - void invalidDataReceived( Enums::TimetableInformation info, const QString &errorMessage, - const QScriptContextInfo &context, - int index, const QVariantMap& map ); - -public Q_SLOTS: - /** - * @brief Clears the list of stored TimetableData objects. - **/ - void clear(); - - /** - * @brief Add @p timetableItem to the result set. - * - * This can be data for departures, arrivals, journeys, stop suggestions or additional data. - * See Enums::TimetableInformation for a list of the property names to use. - * - * @code - * result.addData( {DepartureDateTime: new Date(), Target: 'Test'} ); - * @endcode - * - * A predefined object can also be added like this: - * @code - * var departure = { DepartureDateTime: new Date() }; - * - * // Use "Target" as property name - * departure.Target = 'Test'; - * - * // Alternative: Use enumerable value - * departure[ PublicTransport.Target ] = 'Test'; - * - * result.addData( departure ); - * @endcode - * - * @param timetableItem A script object with data for a timetable item. Contains data in a set - * of properties, eg. the departure date and time for a departure gets stored as - * DepartureDateTime. All available properties can be found in Enums::TimetableInformation. - **/ - void addData( const QVariantMap &timetableItem ); - -// TODO -// /** @brief Whether or not @p info is contained in this TimetableData object. */ -// bool contains( TimetableInformation info ) const { return m_timetableData.contains(info); }; - -private: - QList< TimetableData > m_timetableData; - - // Protect data from concurrent access by the script in a separate thread and usage in C++ - QMutex *m_mutex; - Features m_features; - Hints m_hints; -}; -/** \} */ // @ingroup scriptApi -Q_DECLARE_OPERATORS_FOR_FLAGS( ResultObject::Features ) -Q_DECLARE_OPERATORS_FOR_FLAGS( ResultObject::Hints ) - -class StoragePrivate; -/** @ingroup scriptApi - * @{ */ -/** - * @brief Used by scripts to store data between calls. - * - * An object of this type gets created for each script (not for each call to the script). - * The data in the storage is therefore shared between calls to a script, but not between - * scripts for different service providers. - * - * This class distinguishes between memory storage and persistent storage, ie. on disk. - * Both storage types are protected by separate QReadWriteLock's, because scripts may run in - * parallel. - * - * @code - * // Write a single value and read it again - * storage.write( "name1", 123 ); - * var name1 = storage.read( "name1" ); // name1 == 123 - * - * // Write an object with multiple values at once and read it again - * var object = { value1: 445, value2: "test", - * otherValue: new Date() }; - * storage.write( object ); - * var readObject = storage.read(); - * // The object read from storage now contains both the single - * // value and the values of the object - * // readObject == { name1: 123, value1: 445, value2: "test", - * // otherValue: new Date() }; - * - * var value2 = storage.read( "value2" ); // value2 == "test" - * var other = storage.read( "other", 555 ); // other == 555, the default value - * storage.remove( "name1" ); // Remove a value from the storage - * storage.clear(); - * @endcode - * - * - * After the service provider plugin has been reloaded (eg. after a restart), the values stored - * like shown above are gone. To write data persistently to disk use readPersistent(), - * writePersistent() and removePersistent(). Persistently stored data has a lifetime which can be - * specified as argument to writePersistent() and defaults to one week. - * The maximum lifetime is one month. - * - * @code - * // Write a single value persistently and read it again - * storage.writePersistent( "name1", 123 ); // Using the default lifetime - * var name1 = storage.readPersistent( "name1" ); // name1 == 123 - * - * // Write an object with multiple values at once and read it again - * var object = { value1: 445, value2: "test", otherValue: new Date() }; - * storage.writePersistent( object ); - * var readObject = storage.readPersistent(); - * // The object read from storage now contains both the single - * // value and the values of the object - * // readObject == { name1: 123, value1: 445, value2: "test", - * // otherValue: new Date() }; - * - * // Using custom lifetimes (in days) - * // 1. Store value 66 for 30 days as "longNeeded" - * storage.writePersistent( "longNeeded", 66, 30 ); - * // 2. Lifetime can't be higher than 30 days - * storage.writePersistent( "veryLongNeeded", 66, 300 ); - * - * // Check the remaining lifetime of a persistently stored value - * var lifetimeLongNeeded = storage.lifetime( "longNeeded" ); // Now 30 - * var other = storage.readPersistent( "other", 555 ); // other == 555, default - * storage.removePersistent( "name1" ); // Remove a value from the storage - * storage.clearPersistent(); - * @endcode - * - * @warning Since the script can run multiple times simultanously in different threads which share - * the same Storage object, the stored values are also shared. If you want to store a value for - * the current job of the script only (eg. getting departures and remember a value after an - * asynchronous request), you should store the value in a global script variable instead. - * Otherwise one departure request job might use the value stored by another one, which is - * probably not what you want. Scripts can not not access the Storage object of other scripts - * (for other service providers). - **/ -class Storage : public QObject { - Q_OBJECT - -public: - /** - * @brief Create a new Storage instance. - * - * @param serviceProviderId Used to find the right place in the service provider cache file - * to read/write persistent data. - * @param parent The parent QObject. - **/ - Storage( const QString &serviceProviderId, QObject *parent = 0 ); - - /** @brief Destructor. */ - virtual ~Storage(); - - /** - * @brief The maximum lifetime in days for data written to disk. - * @see writePersistent() - **/ - static const uint MAX_LIFETIME = 30; - - /** - * @brief The default lifetime in days for data written to disk. - * @see writePersistent() - **/ - static const uint DEFAULT_LIFETIME = 7; - - /** - * @brief The suffix to use for lifetime data entries. - * - * This suffix gets appended to the name with which data gets written persistently to disk - * to get the name of the data entry storing the associated lifetime information. - * @see writePersistent() - **/ - static const char* LIFETIME_ENTRYNAME_SUFFIX; - - /** - * @brief The minimal interval in minutes to run checkLifetime(). - * @see checkLifetime() - **/ - static const int MIN_LIFETIME_CHECK_INTERVAL = 15; - - /** - * @brief Whether or not a data entry with @p name exists in memory. - **/ - Q_INVOKABLE bool hasData( const QString &name ) const; - - /** - * @brief Whether or not a data entry with @p name exists in persistent memory. - **/ - Q_INVOKABLE bool hasPersistentData( const QString &name ) const; - - /** - * @brief Reads all data stored in memory. - **/ - Q_INVOKABLE QVariantMap read(); - - /** - * @brief Reads data stored in memory with @p name. - **/ - Q_INVOKABLE QVariant read( const QString &name, const QVariant& defaultData = QVariant() ); - - /** - * @brief Reads the lifetime remaining for data written using writePersistent() with @p name. - **/ - Q_INVOKABLE int lifetime( const QString &name ); - - /** - * @brief Reads data stored on disk with @p name. - * - * @param name The name of the value to read. - * @param defaultData The value to return if no stored value was found under @p name. - * If you use another default value than the default invalid QVariant, the type must match - * the type of the stored value. Otherwise an invalid QVariant gets returned. - * @see lifetime() - **/ - Q_INVOKABLE QVariant readPersistent( const QString &name, - const QVariant& defaultData = QVariant() ); - - /** - * @brief Checks the lifetime of all persistent data entries. - * - * If the lifetime of a persistent data entry has expired, it gets deleted. - **/ - void checkLifetime(); - -public Q_SLOTS: - /** - * @brief Stores @p data in memory with @p name. - **/ - void write( const QString &name, const QVariant &data ); - - /** - * @brief Stores @p data in memory, eg. a script object. - * - * @param data The data to write to disk. This can be a script object. - * @overload - **/ - void write( const QVariantMap &data ); - - /** - * @brief Removes data stored in memory with @p name. - **/ - void remove( const QString &name ); - - /** - * @brief Clears all data stored in memory. - **/ - void clear(); - - /** - * @brief Stores @p data on disk with @p name. - * - * @param name A name to access the written data with. - * @param data The data to write to disk. The type of the data can also be QVariantMap (ie. - * script objects) or list types (gets encoded to QByteArray). Length of the data is limited - * to 65535 bytes. - * @param lifetime The lifetime in days of the data. Limited to 30 days and defaults to 7 days. - * - * @see lifetime - **/ - void writePersistent( const QString &name, const QVariant &data, - uint lifetime = DEFAULT_LIFETIME ); - - /** - * @brief Stores @p data on disk, eg. a script object. - * - * After @p lifetime days have passed, the written data will be deleted automatically. - * To prevent automatic deletion the data has to be written again. - * - * @param data The data to write to disk. This can be a script object. - * @param lifetime The lifetime in days of each entry in @p data. - * Limited to 30 days and defaults to 7 days. - * - * @see lifetime - * @overload - **/ - void writePersistent( const QVariantMap &data, uint lifetime = DEFAULT_LIFETIME ); - - /** - * @brief Removes data stored on disk with @p name. - * - * @note Scripts do not need to remove data written persistently, ie. to disk, because each - * data entry has a lifetime, which is currently limited to 30 days and defaults to 7 days. - **/ - void removePersistent( const QString &name ); - - /** - * @brief Clears all data stored persistently, ie. on disk. - * - * @note Scripts do not need to remove data written persistently, ie. to disk, because each - * data entry has a lifetime, which is currently limited to 30 days and defaults to 7 days. - **/ - void clearPersistent(); - -private: - int lifetime( const QString &name, const KConfigGroup &group ); - int lifetimeNoLock( const QString &name, const KConfigGroup &group ); - void removePersistent( const QString &name, KConfigGroup &group ); - QByteArray encodeData( const QVariant &data ) const; - QVariant decodeData( const QByteArray &data ) const; - - StoragePrivate *d; -}; -/** \} */ // @ingroup scriptApi - -/** @ingroup scriptApi - * @{ */ -/** - * @brief A data stream class to be used in scripts. - * - * This class gets made available to scripts under the name @c DataStream. - * It can be setup to read from @c data that was received from a network request like here: - * @code - * // data is a QByteArray as received from a network request - * // That data can now be decoded using helper.decode(), - * // but if it contains binary data DataStream should be used with a QBuffer - * var buffer = new QBuffer( data ); - * buffer.open( QIODevice.ReadOnly ); - * var stream = new DataStream( buffer ); - * // The stream is now ready - * - * // Seek to position 10, if possible - * if ( 10 < buffer.size() ) { - * stream.seek( 10 ); - * } - * - * // The current position is available in the 'pos' property - * var pos = stream.pos; - * - * // Skip some bytes - * stream.skip( 2 ); // Skip two bytes, equal to stream.seek(stream.pos + 2) - * - * // Read data - * var number = stream.readUInt32(); // Read a four byte unsigned integer - * var smallNumber = stream.readInt8(); // Read a one byte integer - * var string = stream.readString(); // Read a string, ends at the first found \0 - * - * // Close the buffer again - * buffer.close(); - * @endcode - * - * This class wraps a QDataStream, because it's read/write operators cannot be used from QtScript - * otherwise, ie. the operator>>(), operator<<() functions. This class offers some read functions - * to make this possible, ie readInt8(), readUInt8() or readString(). To read byte data use - * readBytes(). - **/ -class DataStreamPrototype: public QObject, protected QScriptable { - Q_OBJECT - Q_PROPERTY( quint64 pos READ pos WRITE seek ) - Q_PROPERTY( bool atEnd READ atEnd ) - -public: - DataStreamPrototype( QObject *parent = 0 ); - DataStreamPrototype( const QByteArray &byteArray, QObject *parent = 0 ); - DataStreamPrototype( QIODevice *device, QObject *parent = 0 ); - DataStreamPrototype( DataStreamPrototype *other ); - - /** @brief Reads an 8 bit integer. */ - Q_INVOKABLE qint8 readInt8(); - - /** @brief Reads an unsigned 8 bit integer. */ - Q_INVOKABLE quint8 readUInt8(); - - /** @brief Reads a 16 bit integer. */ - Q_INVOKABLE qint16 readInt16(); - - /** @brief Reads an unsigned 16 bit integer. */ - Q_INVOKABLE quint16 readUInt16(); - - /** @brief Reads a 32 bit integer. */ - Q_INVOKABLE qint32 readInt32(); - - /** @brief Reads an unsigned 32 bit integer. */ - Q_INVOKABLE quint32 readUInt32(); - - /** @brief Reads a string until the first \0 character is read. */ - Q_INVOKABLE QString readString(); - - /** @brief Reads bytes until the first \0 character is read. */ - Q_INVOKABLE QByteArray readBytesUntilZero(); - - /** @brief Reads @p bytes bytes. */ - Q_INVOKABLE QByteArray readBytes( uint bytes ); - - /** @brief Whether or not the stream is at it's end. */ - Q_INVOKABLE bool atEnd() const { return m_dataStream->atEnd(); }; - - /** @brief Get the current position in the streams device. */ - Q_INVOKABLE quint64 pos() const { return m_dataStream->device()->pos(); }; - - /** @brief Seek to @p pos in the streams device. */ - Q_INVOKABLE bool seek( quint64 pos ) const { return m_dataStream->device()->seek(pos); }; - - /** @brief Peek data with the given @p maxLength. */ - Q_INVOKABLE QByteArray peek( quint64 maxLength ) const { - return m_dataStream->device()->peek(maxLength); - }; - - /** @brief Skip @p bytes bytes. */ - Q_INVOKABLE int skip( int bytes ) const { return m_dataStream->skipRawData(bytes); }; - - /** @brief Return a human-readable description of the last device error that occured. */ - Q_INVOKABLE QString errorString() const { return m_dataStream->device()->errorString(); }; - - /** @brief Get the underlying QDataStream as a QSharedPointer. */ - QSharedPointer< QDataStream > stream() const { return m_dataStream; }; - -private: - QDataStream *thisDataStream() const; - QSharedPointer< QDataStream > m_dataStream; -}; -/** \} */ // @ingroup scriptApi - -typedef DataStreamPrototype* DataStreamPrototypePtr; -QScriptValue constructStream( QScriptContext *context, QScriptEngine *engine ); -QScriptValue dataStreamToScript( QScriptEngine *engine, const DataStreamPrototypePtr &stream ); -void dataStreamFromScript( const QScriptValue &object, DataStreamPrototypePtr &stream ); - -} // namespace ScriptApi - -Q_DECLARE_METATYPE(ScriptApi::Helper::ErrorSeverity) -Q_DECLARE_METATYPE(ScriptApi::ResultObject::Hint) -Q_DECLARE_METATYPE(ScriptApi::ResultObject::Hints) -Q_DECLARE_METATYPE(ScriptApi::ResultObject::Feature) -Q_DECLARE_METATYPE(ScriptApi::ResultObject::Features) - -Q_DECLARE_METATYPE(ScriptApi::NetworkRequest*) -Q_DECLARE_METATYPE(ScriptApi::NetworkRequest::Ptr) -Q_SCRIPT_DECLARE_QMETAOBJECT(ScriptApi::NetworkRequest, QObject*) - -Q_DECLARE_METATYPE(QIODevice*) -Q_DECLARE_METATYPE(QDataStream*) -Q_DECLARE_METATYPE(ScriptApi::DataStreamPrototype*) -Q_SCRIPT_DECLARE_QMETAOBJECT(ScriptApi::DataStreamPrototype, QObject*) - -#endif // Multiple inclusion guard diff --git a/engine/script/scriptobjects.cpp b/engine/script/scriptobjects.cpp deleted file mode 100644 index b555adc..0000000 --- a/engine/script/scriptobjects.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* -* Copyright 2012 Friedrich Pülz -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Library General Public License as -* published by the Free Software Foundation; either version 2 or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details -* -* You should have received a copy of the GNU Library General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -// Header -#include "scriptobjects.h" - -// Own includes -#include "script_thread.h" - -// KDE includes -#include -#include - -// Qt includes -#include -#include -#include - -ScriptObjects::ScriptObjects() - : storage(0), network(0), result(0), helper(0) -{ -} - -ScriptObjects::ScriptObjects( const ScriptObjects &other ) - : storage(other.storage), network(other.network), result(other.result), - helper(other.helper), lastError(other.lastError) -{ -} - -ScriptData::ScriptData() -{ -} - -ScriptData::ScriptData( const ScriptData &scriptData ) - : provider(scriptData.provider), program(scriptData.program) -{ -} - -ScriptData::ScriptData( const ServiceProviderData *data, - const QSharedPointer< QScriptProgram > &scriptProgram ) - : provider(data ? *data : ServiceProviderData()), program(scriptProgram) -{ -} - -void ScriptObjects::clear() -{ - storage = QSharedPointer< Storage >( 0 ); - helper = QSharedPointer< Helper >( 0 ); - network = QSharedPointer< Network >( 0 ); - result = QSharedPointer< ResultObject >( 0 ); -} - -void ScriptObjects::createObjects( const ServiceProviderData *data, - const QSharedPointer< QScriptProgram > &scriptProgram ) -{ - createObjects( ScriptData(data, scriptProgram) ); -} - -void ScriptObjects::createObjects( const ScriptData &data ) -{ - if ( !storage ) { - storage = QSharedPointer< Storage >( new Storage(data.provider.id()) ); - } - if ( !network ) { - network = QSharedPointer< Network >( new Network(data.provider.fallbackCharset()) ); - } - if ( !result ) { - result = QSharedPointer< ResultObject >( new ResultObject() ); - } - if ( !helper ) { - helper = QSharedPointer< Helper >( new Helper(data.provider.id()) ); - } -} - -ScriptData ScriptData::fromEngine( QScriptEngine *engine, - const QSharedPointer< QScriptProgram > &scriptProgram ) -{ - ServiceProviderData *data = qobject_cast< ServiceProviderData* >( - engine->globalObject().property("provider").toQObject() ); - return ScriptData( data, scriptProgram ); -} - -ScriptObjects ScriptObjects::fromEngine( QScriptEngine *engine ) -{ - ScriptObjects objects; - objects.helper = QSharedPointer< Helper >( qobject_cast< Helper* >( - engine->globalObject().property("helper").toQObject() ) ); - objects.network = QSharedPointer< Network >( qobject_cast< Network* >( - engine->globalObject().property("network").toQObject() ) ); - objects.result = QSharedPointer< ResultObject >( qobject_cast< ResultObject* >( - engine->globalObject().property("result").toQObject() ) ); - objects.storage = QSharedPointer< Storage >( qobject_cast< Storage* >( - engine->globalObject().property("storage").toQObject() ) ); - return objects; -} - -void ScriptObjects::moveToThread( QThread *thread ) -{ - if ( helper ) { - helper->moveToThread( thread ); - } - if ( network ) { - network->moveToThread( thread ); - } - if ( result ) { - result->moveToThread( thread ); - } - if ( storage ) { - storage->moveToThread( thread ); - } -} - -QThread *ScriptObjects::currentThread() const -{ - return helper ? helper->thread() : 0; -} - -bool ScriptObjects::attachToEngine( QScriptEngine *engine, const ScriptData &data ) -{ - if ( !isValid() ) { - kDebug() << "Attaching invalid objects" << helper << network << result << storage; - } else if ( !data.program ) { - kDebug() << "Attaching invalid data"; - } - - if ( engine->isEvaluating() ) { - qWarning() << "Cannot attach objects while evaluating"; - return false; - } - - // Register classes for use in the script - qScriptRegisterMetaType< DataStreamPrototypePtr >( engine, - dataStreamToScript, dataStreamFromScript ); - qScriptRegisterMetaType< NetworkRequestPtr >( engine, - networkRequestToScript, networkRequestFromScript ); - - const QScriptValue::PropertyFlags flags = QScriptValue::ReadOnly | QScriptValue::Undeletable; - - // Add a 'provider' object, clone a new ServiceProviderData object - // for the case that the ScriptData instance gets deleted - engine->globalObject().setProperty( "provider", - engine->newQObject(data.provider.clone(engine)), flags ); - - // Add an include() function - QScriptValue includeFunction = engine->globalObject().property("include"); - if ( !includeFunction.isValid() ) { - includeFunction = engine->newFunction( include, 1 ); - } - if ( data.program ) { - QVariantHash includeData; - includeData[ data.program->fileName() ] = maxIncludeLine( data.program->sourceCode() ); - includeFunction.setData( qScriptValueFromValue(engine, includeData) ); - } - engine->globalObject().setProperty( "include", includeFunction, flags ); - - QScriptValue streamConstructor = engine->newFunction( constructStream ); - QScriptValue streamMeta = engine->newQMetaObject( - &DataStreamPrototype::staticMetaObject, streamConstructor ); - engine->globalObject().setProperty( "DataStream", streamMeta, flags ); - - // Make the objects available to the script - engine->globalObject().setProperty( "helper", helper.isNull() - ? engine->undefinedValue() : engine->newQObject(helper.data()), flags ); - engine->globalObject().setProperty( "network", network.isNull() - ? engine->undefinedValue() : engine->newQObject(network.data()), flags ); - engine->globalObject().setProperty( "storage", storage.isNull() - ? engine->undefinedValue() : engine->newQObject(storage.data()), flags ); - engine->globalObject().setProperty( "result", result.isNull() - ? engine->undefinedValue() : engine->newQObject(result.data()), flags ); - engine->globalObject().setProperty( "enums", - engine->newQMetaObject(&ResultObject::staticMetaObject), flags ); - engine->globalObject().setProperty( "PublicTransport", - engine->newQMetaObject(&Enums::staticMetaObject), flags ); - - if ( !engine->globalObject().property("DataStream").isValid() ) { - DataStreamPrototype *dataStream = new DataStreamPrototype( engine ); - QScriptValue stream = engine->newQObject( dataStream ); - QScriptValue streamConstructor = engine->newFunction( constructStream ); - engine->setDefaultPrototype( qMetaTypeId(), stream ); - engine->globalObject().setProperty( "DataStream", streamConstructor, flags ); - } - - // Import extensions (from XML file,