From 993c38c309f08de661db2e78ca168026d7f8aeb3 Mon Sep 17 00:00:00 2001 From: Koen Deforche Date: Thu, 8 Aug 2013 20:50:29 +0200 Subject: [PATCH] Several fixes and improvements: Wt::Render: fixed matching of Selector containing 3 or more identical simple selectors Fix #2029: responsive navigation bar without animations, also provide a good solution for animations+layouts interference Fix #2084: missing dllimport/export for WAbstractProxyModel::BaseItem Build: Blacklist boost 1.54 on Windows (httpd data corruption), added C++11 build option Signals: Support signals2 as signals is being dropped in newer boost versions Wt::Dbo: add updateAuthToken() to update an existing auth token (keeping its expiration time) Implement #1914: Wt::Json::serialize() functions Wt::Render: account for margin, padding, borders for minimum width calculations Fix #2071: WStringStream: make compiler bwarf on unsupported types Fix #2078, #2079 internal path redirect in progressive bootstrap application Fix #1952: WCompositeWidget::setObjectName() Fix #1948 (patch from Pierluigi Vicinanza) Wt::Render: improve table rendering: support border-collapse collapse and honor set widths Fix #2002: WTabWidget::setTabEnabled() partially working Fix #2080: Timing issue with document.body being available in progressive bootstrap WString: resolve using WServer::localizedStrings if no WApplication context is available Implemented #1602: Wt::Mail::Message::setDate() Implemented #1390: Adding support for local date/time (WLocalDateTime) implemented #1179: interpret ItemIsSelectable for enabling/disabling options in combo-box/selection box Fix #2074: inOneSecond issue (Bruce Toll) Layouts: fix problem requiring to reapply whenever a parent widget changes size Fix #2039: segfault (vector subscript out of range) in WSortFilterProxyModel Fix #2018: Wt::Http::Client actually does not append port numbers to the "Host" header Fix #2016: Wt::Http::Client::parseUrl cannot parse URLs containing username and password Fix #2067 WDateEdit: on click event not received when setEmptyText is used Config: remove the xhtml mime-type option, viva HTML5 --- CMakeLists.txt | 48 + WConfig.h.in | 2 - cmake/WtFindFcgi.txt | 2 + examples/CMakeLists.txt | 15 +- examples/blog/model/BlogUserDatabase.C | 18 + examples/blog/model/BlogUserDatabase.h | 3 + examples/widgetgallery/CMakeLists.txt | 4 - examples/widgetgallery/approot/src.xml | 911 +++++++++--------- examples/widgetgallery/approot/wt_config.xml | 2 +- .../widgetgallery/examples/ComboBoxModel.cpp | 15 +- examples/widgetgallery/examples/DateEdit.cpp | 13 +- resources/themes/bootstrap/wt.css | 9 + resources/themes/bootstrap/wt.less | 10 + resources/themes/default/wt.css | 3 +- resources/themes/polished/wt.css | 1 + src/CMakeLists.txt | 2 + src/Wt/Auth/AbstractUserDatabase | 12 + src/Wt/Auth/AbstractUserDatabase.C | 9 + src/Wt/Auth/AuthModel | 6 + src/Wt/Auth/AuthModel.C | 22 +- src/Wt/Auth/AuthService | 12 +- src/Wt/Auth/AuthService.C | 34 +- src/Wt/Auth/AuthWidget.C | 2 +- src/Wt/Auth/Dbo/AuthInfo | 4 + src/Wt/Auth/Dbo/UserDatabase | 16 + src/Wt/Auth/User | 7 + src/Wt/Auth/User.C | 8 + src/Wt/Dbo/backend/Sqlite3.C | 2 + src/Wt/Http/Client | 10 + src/Wt/Http/Client.C | 23 +- src/Wt/Json/Serializer | 39 + src/Wt/Json/Serializer.C | 121 +++ src/Wt/Json/Value.C | 26 +- src/Wt/Mail/Client.C | 16 +- src/Wt/Mail/Message | 14 +- src/Wt/Mail/Message.C | 8 + src/Wt/Render/Block.C | 621 ++++++++++-- src/Wt/Render/Block.h | 60 +- src/Wt/Render/CssData.C | 54 +- src/Wt/Render/CssData.h | 32 +- src/Wt/Render/CssParser.C | 68 +- src/Wt/StdGridLayoutImpl2.C | 23 +- src/Wt/StdGridLayoutImpl2.h | 3 +- src/Wt/StdLayoutImpl.h | 3 +- src/Wt/StdWidgetItemImpl.C | 74 -- src/Wt/WAbstractItemModel | 2 +- src/Wt/WAbstractItemView.C | 6 - src/Wt/WAbstractProxyModel | 2 +- src/Wt/WAbstractSpinBox.C | 10 +- src/Wt/WApplication | 8 +- src/Wt/WApplication.C | 2 +- src/Wt/WBoostAny.C | 26 + src/Wt/WComboBox.C | 4 + src/Wt/WCompositeWidget | 1 + src/Wt/WCompositeWidget.C | 5 + src/Wt/WContainerWidget | 1 + src/Wt/WContainerWidget.C | 55 +- src/Wt/WDate | 49 +- src/Wt/WDate.C | 132 ++- src/Wt/WDateEdit.C | 6 +- src/Wt/WDateTime | 57 +- src/Wt/WDateTime.C | 82 +- src/Wt/WDoubleSpinBox.C | 3 +- src/Wt/WEnvironment | 13 + src/Wt/WEnvironment.C | 13 +- src/Wt/WGlobal | 1 + src/Wt/WInteractWidget.C | 5 +- src/Wt/WJavaScript | 23 +- src/Wt/WLocalDateTime | 197 ++++ src/Wt/WLocalDateTime.C | 249 +++++ src/Wt/WLocale | 60 +- src/Wt/WLocale.C | 28 +- src/Wt/WNavigationBar | 2 + src/Wt/WNavigationBar.C | 65 +- src/Wt/WObject | 110 ++- src/Wt/WServer | 16 + src/Wt/WServer.C | 8 + src/Wt/WSignal | 58 +- src/Wt/WSortFilterProxyModel.C | 18 +- src/Wt/WSpinBox.C | 3 +- src/Wt/WString | 4 +- src/Wt/WString.C | 36 +- src/Wt/WStringListModel | 13 +- src/Wt/WStringListModel.C | 41 +- src/Wt/WStringStream | 4 +- src/Wt/WTabWidget.C | 1 - src/Wt/WTextEdit.C | 2 +- src/Wt/WTime | 8 +- src/Wt/WTime.C | 29 +- src/Wt/WWebWidget | 1 + src/Wt/WWebWidget.C | 22 +- src/Wt/WWidget.C | 2 + src/fcgi/CMakeLists.txt | 2 +- src/http/CMakeLists.txt | 9 + src/http/Connection.C | 6 +- src/http/Connection.h | 3 +- src/http/SslConnection.C | 3 +- src/http/TcpConnection.C | 3 +- src/js/StdGridLayoutImpl2.js | 67 +- src/js/StdGridLayoutImpl2.min.js | 60 +- src/js/WDateEdit.js | 2 +- src/js/WDateEdit.min.js | 2 +- src/js/WDateValidator.js | 7 +- src/js/WDateValidator.min.js | 2 +- src/js/WWebWidget.js | 2 +- src/js/WWebWidget.min.js | 4 +- src/web/DomElement.C | 8 +- src/web/DomElement.h | 1 + src/web/WebRenderer.C | 21 +- src/web/WebSession.C | 2 +- src/web/skeleton/Boot.js | 33 +- src/web/skeleton/Boot.min.js | 19 +- test/CMakeLists.txt | 1 + test/json/JsonSerializerTest.C | 113 +++ test/json/UTF-8-test2.json | 12 + test/mail/MailClientTest.C | 2 + test/render/CssParserTest.C | 2 - test/render/CssSelectorTest.C | 20 + test/wdatetime/WDateTimeTest.C | 90 ++ 119 files changed, 3172 insertions(+), 1194 deletions(-) create mode 100644 src/Wt/Json/Serializer create mode 100644 src/Wt/Json/Serializer.C create mode 100644 src/Wt/WLocalDateTime create mode 100644 src/Wt/WLocalDateTime.C create mode 100644 test/json/JsonSerializerTest.C create mode 100644 test/json/UTF-8-test2.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f47357ac5..822df47650 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,33 @@ OPTION(ENABLE_QT4 "Build Qt4 interworking library (libwtwithqt" ON) OPTION(WT_NO_STD_LOCALE "Build Wt to run on a system without std::locale support" OFF) OPTION(WT_NO_STD_WSTRING "Build Wt to run on a system without std::wstring support" OFF) +# C++11 vs C++98 +# Binary compatibility is not guaranteed. We give our users the choice on +# how to compile Wt +# We're not sure yet if you can safely link Wt compiled in 03 mode against +# an application compiled in 11 mode. Boost probably causes problems here +# (specially boost.signals2) +IF(NOT MSVC) + # For now, don't auto-detect + #IF(CMAKE_COMPILER_IS_GNUCXX) + # execute_process(COMMAND ${CMAKE_C_COMPILER} "-dumpversion" + # OUTPUT_VARIABLE GCC_VERSION) + # IF(${GCC_VERSION} VERSION_GREATER "4.5.99") + # SET(HAS_CXX11 ON) + # ENDIF(${GCC_VERSION} VERSION_GREATER "4.5.99") + #ENDIF(CMAKE_COMPILER_IS_GNUCXX) + SET(WT_CPP_11_MODE "" CACHE STRING "C++ mode to compile Wt in (leave empty for your compiler's default, or set to -std=c++11, -std=c++0x, ...)") + IF(WT_CPP_11_MODE) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WT_CPP_11_MODE}") + SET(HAS_CXX11 ON) + ENDIF(WT_CPP_11_MODE) +ELSE(NOT MSVC) + # For once, msvs is easier than gcc/llvm + IF(MSVC_VERSION GREATER 1600) + SET(HAS_CXX11 ON) + ENDIF(MSVC_VERSION GREATER 1600) +ENDIF(NOT MSVC) + IF(APPLE) OPTION(USE_BOOST_FRAMEWORK "Uses a Boost framework" OFF) ENDIF(APPLE) @@ -351,6 +378,27 @@ ELSE(BOOST_WT_MT_FOUND) ADD_DEFINITIONS(-DBOOST_DISABLE_THREADS -DSQLITE_THREADSAFE=0) ENDIF(BOOST_WT_MT_FOUND) +# decide on signals vs signals2 +# boost 1.54 deprecated boost signals -> use signals2 +IF (Boost_VERSION GREATER 105300) + MESSAGE(STATUS "Boost ${Boost_VERSION} > 1.53, WT_SIGNALS_IMPLEMENTATION = boost.signals2 recommended") + SET(DEFAULT_WT_SIGNALS_IMPLEMENTATION "boost.signals2") +ELSE (Boost_VERSION GREATER 105300) + SET(DEFAULT_WT_SIGNALS_IMPLEMENTATION "boost.signals") + MESSAGE(STATUS "Boost ${Boost_VERSION} < 1.54, WT_SIGNALS_IMPLEMENTATION = boost.signals recommended") +ENDIF (Boost_VERSION GREATER 105300) +SET(WT_SIGNALS_IMPLEMENTATION ${DEFAULT_WT_SIGNALS_IMPLEMENTATION} CACHE STRING "Select what implementation should be used for Wt signals") +SET_PROPERTY(CACHE WT_SIGNALS_IMPLEMENTATION PROPERTY STRINGS boost.signals boost.signals2) + +IF ("${WT_SIGNALS_IMPLEMENTATION}" STREQUAL "boost.signals") + MESSAGE(STATUS "Selecting boost.signals") + SET(WT_USE_BOOST_SIGNALS ON) + SET(WT_USE_BOOST_SIGNALS2 OFF) +ELSEIF ("${WT_SIGNALS_IMPLEMENTATION}" STREQUAL "boost.signals2") + MESSAGE(STATUS "Selecting boost.signals2") + SET(WT_USE_BOOST_SIGNALS OFF) + SET(WT_USE_BOOST_SIGNALS2 ON) +ENDIF ("${WT_SIGNALS_IMPLEMENTATION}" STREQUAL "boost.signals") FIND_PACKAGE(Doxygen) diff --git a/WConfig.h.in b/WConfig.h.in index fb94d17786..ffed1a5705 100644 --- a/WConfig.h.in +++ b/WConfig.h.in @@ -39,10 +39,8 @@ #cmakedefine WT_NO_STD_WSTRING #cmakedefine WT_DEBUG_ENABLED -// Only one of the two below should be selected #cmakedefine WT_USE_BOOST_SIGNALS #cmakedefine WT_USE_BOOST_SIGNALS2 -#define WT_USE_BOOST_SIGNALS #endif diff --git a/cmake/WtFindFcgi.txt b/cmake/WtFindFcgi.txt index e92cba5521..8e5261b39c 100644 --- a/cmake/WtFindFcgi.txt +++ b/cmake/WtFindFcgi.txt @@ -17,12 +17,14 @@ FIND_PATH(FCGI_INCLUDE_DIR FIND_LIBRARY(FCGI_LIB fcgi ${FCGI_PREFIX}/lib /usr/lib + /usr/lib64 /usr/local/lib ) FIND_LIBRARY(FCGIPP_LIB fcgi++ ${FCGI_PREFIX}/lib /usr/lib + /usr/lib64 /usr/local/lib ) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e4aa48318b..733ea967f4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -174,23 +174,10 @@ SUBDIRS( wtwithqt ) -IF(MSVC_VERSION) - IF(MSVC_VERSION GREATER 1600) - SET(HAS_CXX11 ON) - ENDIF(MSVC_VERSION GREATER 1600) -ENDIF(MSVC_VERSION) -IF(CMAKE_COMPILER_IS_GNUCXX) - execute_process(COMMAND ${CMAKE_C_COMPILER} "-dumpversion" - OUTPUT_VARIABLE GCC_VERSION) - IF(${GCC_VERSION} VERSION_GREATER "4.5.99") - SET(HAS_CXX11 ON) - ENDIF(${GCC_VERSION} VERSION_GREATER "4.5.99") -ENDIF(CMAKE_COMPILER_IS_GNUCXX) - IF(HAS_CXX11) SUBDIRS( widgetgallery ) ELSE(HAS_CXX11) - MESSAGE("Not building widget gallery; a C++11 compiler is required (gcc > 4.6 or MSVS 2012)") + MESSAGE("*** Not building widget gallery; C++11 required (gcc > 4.6 + set WT_CPP_11_MODE=-std=c++0x or MSVS >= 2012)") ENDIF(HAS_CXX11) diff --git a/examples/blog/model/BlogUserDatabase.C b/examples/blog/model/BlogUserDatabase.C index dbedba024b..48455d010a 100644 --- a/examples/blog/model/BlogUserDatabase.C +++ b/examples/blog/model/BlogUserDatabase.C @@ -195,6 +195,24 @@ void BlogUserDatabase::addAuthToken(const Auth::User& user, (dbo::ptr(new Token(token.hash(), token.expirationTime()))); } +int BlogUserDatabase::updateAuthToken(const Auth::User& user, + const std::string& hash, + const std::string& newHash) +{ + WithUser find(*this, user); + + for (Tokens::const_iterator i = user_->authTokens.begin(); + i != user_->authTokens.end(); ++i) { + if ((*i)->value == hash) { + dbo::ptr p = *i; + p.modify()->value = newHash; + return std::max(Wt::WDateTime::currentDateTime().secsTo(p->expires), 0); + } + } + + return 0; +} + void BlogUserDatabase::removeAuthToken(const Auth::User& user, const std::string& hash) { diff --git a/examples/blog/model/BlogUserDatabase.h b/examples/blog/model/BlogUserDatabase.h index 2cc400468f..c50f900769 100644 --- a/examples/blog/model/BlogUserDatabase.h +++ b/examples/blog/model/BlogUserDatabase.h @@ -51,6 +51,9 @@ class BlogUserDatabase : public Wt::Auth::AbstractUserDatabase virtual void addAuthToken(const Wt::Auth::User& user, const Wt::Auth::Token& token); + virtual int updateAuthToken(const Wt::Auth::User& user, + const std::string& hash, + const std::string& newHash); virtual void removeAuthToken(const Wt::Auth::User& user, const std::string& hash); virtual Wt::Auth::User findWithAuthToken(const std::string& hash) const; diff --git a/examples/widgetgallery/CMakeLists.txt b/examples/widgetgallery/CMakeLists.txt index 24103f44e1..be966147e3 100644 --- a/examples/widgetgallery/CMakeLists.txt +++ b/examples/widgetgallery/CMakeLists.txt @@ -39,10 +39,6 @@ IF (HAVE_HARU) INCLUDE_DIRECTORIES(${HARU_INCLUDE_DIRS}) ENDIF(HAVE_HARU) -IF(CMAKE_COMPILER_IS_GNUCXX) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") -ENDIF(CMAKE_COMPILER_IS_GNUCXX) - # # If you have Wt installed somehwere, you should use the # installed Wt header files for your own Wt projects. diff --git a/examples/widgetgallery/approot/src.xml b/examples/widgetgallery/approot/src.xml index 8862beabe6..15c0a9e367 100644 --- a/examples/widgetgallery/approot/src.xml +++ b/examples/widgetgallery/approot/src.xml @@ -129,83 +129,6 @@ item = new Wt::WText(Wt::WString(cell).arg("Center")); item->setStyleClass("green-box"); layout->addWidget(item, Wt::WBorderLayout::Center); - - - -
#include <Wt/WApplication>
-#include <Wt/WContainerWidget>
-#include <Wt/WPushButton>
-#include <Wt/WText>
-#include <Wt/WTable>
-
-// Add an external style sheet to the application.
-Wt::WApplication::instance()->useStyleSheet("style/CSSexample.css");
-
-Wt::WContainerWidget *container = new Wt::WContainerWidget();
-// The style sheet should be applied to this container only.
-// The class .CSS-example is used as selector.
-container->setStyleClass("CSS-example");
-
-Wt::WPushButton *allB = new Wt::WPushButton("Set all classes", container);
-
-Wt::WPushButton *removeB = new Wt::WPushButton("Remove info class", container);
-removeB->setMargin(10, Wt::Left | Wt::Right);
-removeB->disable();
-
-Wt::WPushButton *toggleB = new Wt::WPushButton("Toggle condensed", container);
-toggleB->disable();
-
-Wt::WText *text = new Wt::WText(container);
-text->setText("<p>These are the most import API classes and methods for"
-              " working with CSS:</p>");
-
-Wt::WTable *table = new Wt::WTable(container);
-table->setHeaderCount(1);
-table->elementAt(0, 0)->addWidget(new Wt::WText("Method"));
-table->elementAt(0, 1)->addWidget(new Wt::WText("Description"));
-table->elementAt(1, 0)->addWidget(
-                        new Wt::WText("WApplication::useStyleSheet()"));
-table->elementAt(1, 1)->addWidget(
-                        new Wt::WText("Adds an external style sheet"));
-table->elementAt(2, 0)->addWidget(
-                        new Wt::WText("WWidget::setStyleClass()"));
-table->elementAt(2, 1)->addWidget(
-                        new Wt::WText("Sets (one or more) CSS style classes"));
-table->elementAt(3, 0)->addWidget(
-                        new Wt::WText("WWidget::removeStyleClass()"));
-table->elementAt(3, 1)->addWidget(
-                        new Wt::WText("Removes a CSS style class"));
-table->elementAt(4, 0)->addWidget(
-                        new Wt::WText("WWidget::toggleStyleClass()"));
-table->elementAt(4, 1)->addWidget(
-                        new Wt::WText("Toggles a CSS style class"));
-
-allB->clicked().connect(std::bind([=] () {
-    // Set style classes for the complete table.
-    table->setStyleClass("table table-bordered");
-    // Set the info style class for the first row after the header.
-    table->rowAt(1)->setStyleClass("info");
-    // Set a style class for the methods (first column, the header excluded).
-    for (int i=1; i<table->rowCount(); i++)
-        table->elementAt(i,0)->setStyleClass("code");
-    removeB->enable();
-    toggleB->enable();
-}));
-
-removeB->clicked().connect(std::bind([=] () {
-    table->rowAt(1)->removeStyleClass("info");
-    removeB->disable();
-}));
-
-toggleB->clicked().connect(std::bind([=] () {
-    if (toggleB->text() == "Toggle condensed") {
-        table->toggleStyleClass("table-condensed", true);
-        toggleB->setText("Toggle expanded");
-    } else {
-        table->toggleStyleClass("table-condensed", false);
-        toggleB->setText("Toggle condensed");
-    }
-}));
 
@@ -298,9 +221,9 @@ */ Wt::WTableView *table = new Wt::WTableView(container); table->setModel(model); -table->setSortingEnabled(true); -table->setColumnResizeEnabled(true); -table->setAlternatingRowColors(true); +table->setSortingEnabled(true); +table->setColumnResizeEnabled(true); +table->setAlternatingRowColors(true); table->setHeaderAlignment(0, Wt::AlignCenter); table->setColumnAlignment(0, Wt::AlignCenter); table->setRowHeight(28); @@ -339,7 +262,7 @@ Wt::Chart::WCartesianChart *chart = new Wt::Chart::WCartesianChart(container); chart->setModel(model); chart->setXSeriesColumn(0); -chart->setLegendEnabled(true); +chart->setLegendEnabled(true); /* * Provide ample space for the title, the X and Y axis and the legend. @@ -368,7 +291,7 @@ Wt::WCheckBox *cb; cb = new Wt::WCheckBox("Check me!", result); -cb->setChecked(true); +cb->setChecked(true); cb = new Wt::WCheckBox("Check me too!", result); @@ -385,25 +308,16 @@ Wt::WCheckBox *cb; cb = new Wt::WCheckBox("Check me!", result); -cb->setInline(false); -cb->setChecked(true); +cb->setInline(false); +cb->setChecked(true); cb = new Wt::WCheckBox("Check me too!", result); -cb->setInline(false); +cb->setInline(false); cb = new Wt::WCheckBox("Check me, I'm tristate!", result); -cb->setInline(false); +cb->setInline(false); cb->setTristate(); cb->setCheckState(Wt::PartiallyChecked); - - - -
#include <Wt/WComboBox>
-
-Wt::WComboBox *cb = new Wt::WComboBox();
-cb->addItem("Heavy");   // 'Heavy' (index 0) is shown by default.
-cb->addItem("Medium");
-cb->addItem("Light");
 
@@ -426,11 +340,21 @@ out->setText(Wt::WString::fromUTF8("You selected {1}.") .arg(cb->currentText())); })); + + + +
#include <Wt/WComboBox>
+
+Wt::WComboBox *cb = new Wt::WComboBox();
+cb->addItem("Heavy");   // 'Heavy' (index 0) is shown by default.
+cb->addItem("Medium");
+cb->addItem("Light");
 
#include <Wt/WComboBox>
 #include <Wt/WContainerWidget>
+#include <Wt/WStringListModel>
 #include <Wt/WText>
 
 Wt::WContainerWidget *container = new Wt::WContainerWidget();
@@ -438,17 +362,19 @@
 Wt::WComboBox *cb = new Wt::WComboBox(container);
 cb->setMargin(10, Wt::Right);
 
-Wt::WAbstractItemModel *model = cb->model();
+Wt::WStringListModel *model = new Wt::WStringListModel(cb);
 
-model->insertRows(0, 4);
-model->setData(0, 0, std::string("Belgium"), Wt::DisplayRole);
+model->addString("Belgium");
 model->setData(0, 0, std::string("BE"), Wt::UserRole);
-model->setData(1, 0, std::string("Netherlands"), Wt::DisplayRole);
+model->addString("Netherlands");
 model->setData(1, 0, std::string("NL"), Wt::UserRole);
-model->setData(2, 0, std::string("United Kingdom"), Wt::DisplayRole);
+model->addString("United Kingdom");
 model->setData(2, 0, std::string("UK"), Wt::UserRole);
-model->setData(3, 0, std::string("United States"), Wt::DisplayRole);
+model->addString("United States");
 model->setData(3, 0, std::string("US"), Wt::UserRole);
+model->setFlags(3, 0);
+
+cb->setModel(model);
 
 Wt::WText *out = new Wt::WText(container);
 
@@ -475,6 +401,83 @@
     // the last constructor argument.
     new Wt::WText(Wt::WString::fromUTF8("<p>Text {1}</p>").arg(i), container);
 }
+
+
+ +
#include <Wt/WApplication>
+#include <Wt/WContainerWidget>
+#include <Wt/WPushButton>
+#include <Wt/WText>
+#include <Wt/WTable>
+
+// Add an external style sheet to the application.
+Wt::WApplication::instance()->useStyleSheet("style/CSSexample.css");
+
+Wt::WContainerWidget *container = new Wt::WContainerWidget();
+// The style sheet should be applied to this container only.
+// The class .CSS-example is used as selector.
+container->setStyleClass("CSS-example");
+
+Wt::WPushButton *allB = new Wt::WPushButton("Set all classes", container);
+
+Wt::WPushButton *removeB = new Wt::WPushButton("Remove info class", container);
+removeB->setMargin(10, Wt::Left | Wt::Right);
+removeB->disable();
+
+Wt::WPushButton *toggleB = new Wt::WPushButton("Toggle condensed", container);
+toggleB->disable();
+
+Wt::WText *text = new Wt::WText(container);
+text->setText("<p>These are the most import API classes and methods for"
+              " working with CSS:</p>");
+
+Wt::WTable *table = new Wt::WTable(container);
+table->setHeaderCount(1);
+table->elementAt(0, 0)->addWidget(new Wt::WText("Method"));
+table->elementAt(0, 1)->addWidget(new Wt::WText("Description"));
+table->elementAt(1, 0)->addWidget(
+                        new Wt::WText("WApplication::useStyleSheet()"));
+table->elementAt(1, 1)->addWidget(
+                        new Wt::WText("Adds an external style sheet"));
+table->elementAt(2, 0)->addWidget(
+                        new Wt::WText("WWidget::setStyleClass()"));
+table->elementAt(2, 1)->addWidget(
+                        new Wt::WText("Sets (one or more) CSS style classes"));
+table->elementAt(3, 0)->addWidget(
+                        new Wt::WText("WWidget::removeStyleClass()"));
+table->elementAt(3, 1)->addWidget(
+                        new Wt::WText("Removes a CSS style class"));
+table->elementAt(4, 0)->addWidget(
+                        new Wt::WText("WWidget::toggleStyleClass()"));
+table->elementAt(4, 1)->addWidget(
+                        new Wt::WText("Toggles a CSS style class"));
+
+allB->clicked().connect(std::bind([=] () {
+    // Set style classes for the complete table.
+    table->setStyleClass("table table-bordered");
+    // Set the info style class for the first row after the header.
+    table->rowAt(1)->setStyleClass("info");
+    // Set a style class for the methods (first column, the header excluded).
+    for (int i=1; i<table->rowCount(); i++)
+        table->elementAt(i,0)->setStyleClass("code");
+    removeB->enable();
+    toggleB->enable();
+}));
+
+removeB->clicked().connect(std::bind([=] () {
+    table->rowAt(1)->removeStyleClass("info");
+    removeB->disable();
+}));
+
+toggleB->clicked().connect(std::bind([=] () {
+    if (toggleB->text() == "Toggle condensed") {
+        table->toggleStyleClass("table-condensed", true);
+        toggleB->setText("Toggle expanded");
+    } else {
+        table->toggleStyleClass("table-condensed", false);
+        toggleB->setText("Toggle condensed");
+    }
+}));
 
@@ -525,7 +528,7 @@ if (de1->date() == de2->date()) out->setText("<p>It's fine to take holiday just for one day!" "</p>"); - else if (de1->date() < de2->date()) { + else if (de1->date() < de2->date()) { out->setText("<p>So, you want to take holiday for a period of " + boost::lexical_cast<std::string>(days) + " days?...</p>"); @@ -585,7 +588,7 @@ if (dp1->date() == dp2->date()) out->setText("<p>It's fine to take holiday just for one day!" "</p>"); - else if (dp1->date() < dp2->date()) { + else if (dp1->date() < dp2->date()) { out->setText("<p>So, you want to take holiday for a period of " + boost::lexical_cast<std::string>(days) + " days?...</p>"); @@ -758,7 +761,7 @@ class UserFormModel : public Wt::WFormModel { -public: +public: // Associate each field with a unique string literal. // In C++11, it's nicer to put these inside the UserFormModel class // like this: @@ -858,7 +861,7 @@ typedef std::map< std::string, std::vector<std::string> > CityMap; typedef std::map<std::string, std::string> CountryMap; -private: +private: static const CityMap cities_; static const CountryMap countries_; Wt::WStandardItemModel *countryModel_, *cityModel_; @@ -866,7 +869,7 @@ static const int MAX_LENGTH = 25; static const int MAX_CHILDREN = 15; - void initializeModels() { + void initializeModels() { // Create a country model. unsigned countryModelRows = countries_.size() + 1; const unsigned countryModelColumns = 1; @@ -898,7 +901,7 @@ Wt::WValidator *createNameValidator(const std::string& field) { Wt::WLengthValidator *v = new Wt::WLengthValidator(); - v->setMandatory(true); + v->setMandatory(true); v->setMinimumLength(1); v->setMaximumLength(MAX_LENGTH); v->setInvalidBlankText("A " + field + " is mandatory!"); @@ -909,14 +912,14 @@ Wt::WValidator *createCountryValidator() { Wt::WLengthValidator *v = new Wt::WLengthValidator(); - v->setMandatory(true); + v->setMandatory(true); v->setInvalidBlankText("A choice for the country is mandatory!"); return v; } Wt::WValidator *createCityValidator() { Wt::WLengthValidator *v = new Wt::WLengthValidator(); - v->setMandatory(true); + v->setMandatory(true); v->setInvalidBlankText("A choice for the city is mandatory!"); return v; } @@ -926,13 +929,13 @@ v->setBottom(Wt::WDate(1900, 1, 1)); v->setTop(Wt::WDate::currentDate()); v->setFormat("dd/MM/yyyy"); - v->setMandatory(true); + v->setMandatory(true); return v; } Wt::WValidator *createChildrenValidator() { Wt::WIntValidator *v = new Wt::WIntValidator(0, MAX_CHILDREN); - v->setMandatory(true); + v->setMandatory(true); v->setInvalidBlankText("Set the number of children!"); v->setInvalidTooSmallText( Wt::WString("Enter a value between 0 and {1}!").arg(MAX_CHILDREN)); @@ -1011,7 +1014,7 @@ class UserFormView : public Wt::WTemplateFormView { -public: +public: // inline constructor UserFormView() { model_ = new UserFormModel(this); @@ -1105,7 +1108,7 @@ updateView(model_); } -private: +private: void process() { updateModel(model_); @@ -1141,7 +1144,7 @@ class GitModel : public Wt::WAbstractItemModel { -public: +public: /* * A custom role for the file contents of a Git BLOB object. */ @@ -1175,7 +1178,7 @@ return Wt::WModelIndex(); // treeData_[0] is the tree root } else { const Tree& item = treeData_[index.internalId()]; - return createIndex(item.index(), 0, item.parentId()); + return createIndex(item.index(), 0, item.parentId()); } } @@ -1222,7 +1225,7 @@ Git::Object object = getObject(index); switch (index.column()) { - case 0: + case 0: if (role == Wt::DisplayRole) { if (object.type == Git::Tree) return object.name + '/'; @@ -1231,7 +1234,7 @@ } else if (role == Wt::DecorationRole) { if (object.type == Git::Blob) return std::string("icons/git-blob.png"); - else if (object.type == Git::Tree) + else if (object.type == Git::Tree) return std::string("icons/git-tree.png"); } else if (role == ContentsRole) { if (object.type == Git::Blob) @@ -1239,7 +1242,7 @@ } break; - case 1: + case 1: if (role == Wt::DisplayRole) { if (object.type == Git::Tree) return std::string("Folder"); @@ -1248,18 +1251,18 @@ if (suffix == "C" || suffix == "cpp") return std::string("C++ Source"); - else if (suffix == "h" || + else if (suffix == "h" || (suffix == "" && !topLevel(index))) return std::string("C++ Header"); - else if (suffix == "css") + else if (suffix == "css") return std::string("CSS Stylesheet"); - else if (suffix == "js") + else if (suffix == "js") return std::string("JavaScript Source"); - else if (suffix == "md") + else if (suffix == "md") return std::string("Markdown"); - else if (suffix == "png" || suffix == "gif") + else if (suffix == "png" || suffix == "gif") return std::string("Image"); - else if (suffix == "txt") + else if (suffix == "txt") return std::string("Text"); else return boost::any(); @@ -1275,18 +1278,18 @@ int role = Wt::DisplayRole) const { if (orientation == Wt::Horizontal && role == Wt::DisplayRole) { switch (section) { - case 0: + case 0: return std::string("File"); - case 1: + case 1: return std::string("Type"); - default: + default: return boost::any(); } } else return boost::any(); } -private: +private: Git git_; /* @@ -1301,9 +1304,9 @@ bool operator< (const ChildIndex& other) const { if (parentId < other.parentId) - return true; + return true; else if (parentId > other.parentId) - return false; + return false; else return index < other.index; } }; @@ -1312,7 +1315,7 @@ * Data to be stored for an (expanded) folder */ class Tree { - public: + public: Tree(int parentId, int index, const Git::ObjectId& object, int rowCount) : index_(parentId, index), treeObject_(object), @@ -1324,7 +1327,7 @@ const Git::ObjectId& treeObject() const { return treeObject_; } int rowCount() const { return rowCount_; } - private: + private: ChildIndex index_; Git::ObjectId treeObject_; int rowCount_; @@ -1345,7 +1348,7 @@ /* * Gets or allocates an id for a folder. */ - int getTreeId(int parentId, int childIndex) const { + int getTreeId(int parentId, int childIndex) const { ChildIndex index(parentId, childIndex); ChildPointerMap::const_iterator i = childPointer_.find(index); @@ -1372,7 +1375,7 @@ } static std::string getSuffix(const std::string& fileName) { - std::size_t dot = fileName.rfind('.'); + std::size_t dot = fileName.rfind('.'); if (dot == std::string::npos) return ""; else @@ -1397,7 +1400,7 @@ class GoogleMapExample : public Wt::WContainerWidget { -public: +public: GoogleMapExample(Wt::WContainerWidget *parent = 0) : Wt::WContainerWidget(parent) { @@ -1466,7 +1469,7 @@ returnToPosition_ = new Wt::WPushButton("Return to saved position"); controls->bindWidget("return-to-saved-position", returnToPosition_); - returnToPosition_->setEnabled(false); + returnToPosition_->setEnabled(false); returnToPosition_->clicked().connect(std::bind([=] () { map_->returnToSavedPosition(); @@ -1494,7 +1497,7 @@ Wt::WCheckBox *draggingCB = new Wt::WCheckBox("Enable dragging"); controls->bindWidget("dragging-cb", draggingCB); - draggingCB->setChecked(true); + draggingCB->setChecked(true); map_->enableDragging(); draggingCB->checked().connect(std::bind([=] () { @@ -1508,7 +1511,7 @@ Wt::WCheckBox *enableDoubleClickZoomCB = new Wt::WCheckBox("Enable double click zoom"); controls->bindWidget("double-click-zoom-cb", enableDoubleClickZoomCB); - enableDoubleClickZoomCB->setChecked(false); + enableDoubleClickZoomCB->setChecked(false); map_->disableDoubleClickZoom(); enableDoubleClickZoomCB->checked().connect(std::bind([=] () { @@ -1522,7 +1525,7 @@ Wt::WCheckBox *enableScrollWheelZoomCB = new Wt::WCheckBox("Enable scroll wheel zoom"); controls->bindWidget("scroll-wheel-zoom-cb", enableScrollWheelZoomCB); - enableScrollWheelZoomCB->setChecked(true); + enableScrollWheelZoomCB->setChecked(true); map_->enableScrollWheelZoom(); enableScrollWheelZoomCB->checked().connect(std::bind([=] () { @@ -1556,13 +1559,13 @@ }, std::placeholders::_1)); } -private: +private: void panToEmWeb() { map_->panTo(Wt::WGoogleMap::Coordinate(50.9082, 4.66056)); } void savePosition() { - returnToPosition_->setEnabled(true); + returnToPosition_->setEnabled(true); map_->savePosition(); } @@ -1658,7 +1661,7 @@ << c.latitude() << "," << c.longitude() << ")"; } -private: +private: Wt::WGoogleMap *map_; Wt::WAbstractItemModel *mapTypeModel_; @@ -1747,29 +1750,6 @@ item = new Wt::WText("Item 2"); item->setStyleClass("blue-box"); hbox->addWidget(item); - - - -
#include <Wt/WContainerWidget>
-#include <Wt/WImage>
-#include <Wt/WLink>
-#include <Wt/WText>
-
-Wt::WContainerWidget *container = new Wt::WContainerWidget();
-
-Wt::WImage *image = new Wt::WImage(Wt::WLink("icons/wt_powered.jpg"),
-                                   container);
-image->setAlternateText("Wt logo");
-
-Wt::WText *out = new Wt::WText(container);
-out->setMargin(10, Wt::Left);
-
-image->clicked().connect(std::bind([=] (const Wt::WMouseEvent& e) {
-    out->setText("You clicked the Wt logo at "
-		 "(" + boost::lexical_cast<std::string>(e.widget().x) +
-		 "," + boost::lexical_cast<std::string>(e.widget().y) +
-		 ").");
-}, std::placeholders::_1));
 
@@ -1841,7 +1821,30 @@ }, std::placeholders::_1)); - + +
#include <Wt/WContainerWidget>
+#include <Wt/WImage>
+#include <Wt/WLink>
+#include <Wt/WText>
+
+Wt::WContainerWidget *container = new Wt::WContainerWidget();
+
+Wt::WImage *image = new Wt::WImage(Wt::WLink("icons/wt_powered.jpg"),
+                                   container);
+image->setAlternateText("Wt logo");
+
+Wt::WText *out = new Wt::WText(container);
+out->setMargin(10, Wt::Left);
+
+image->clicked().connect(std::bind([=] (const Wt::WMouseEvent& e) {
+    out->setText("You clicked the Wt logo at "
+		 "(" + boost::lexical_cast<std::string>(e.widget().x) +
+		 "," + boost::lexical_cast<std::string>(e.widget().y) +
+		 ").");
+}, std::placeholders::_1));
+
+
+
#include <Wt/WContainerWidget>
 #include <Wt/WInPlaceEdit>
 #include <Wt/WText>
@@ -1851,18 +1854,15 @@
 Wt::WText *out = new Wt::WText(container);
 out->setMargin(10, Wt::Right);
 
-Wt::WInPlaceEdit *ipe
-    = new Wt::WInPlaceEdit("This is editable text", container);
-
+Wt::WInPlaceEdit *ipe = new Wt::WInPlaceEdit("This is editable text", container);
 ipe->setEmptyText("You deleted the text!");
-ipe->setButtonsEnabled(false);
 
 ipe->valueChanged().connect(std::bind([=] () {
     out->setText("In-place edit is set to... ");
 }));
 
- +
#include <Wt/WContainerWidget>
 #include <Wt/WInPlaceEdit>
 #include <Wt/WText>
@@ -1872,8 +1872,11 @@
 Wt::WText *out = new Wt::WText(container);
 out->setMargin(10, Wt::Right);
 
-Wt::WInPlaceEdit *ipe = new Wt::WInPlaceEdit("This is editable text", container);
+Wt::WInPlaceEdit *ipe
+    = new Wt::WInPlaceEdit("This is editable text", container);
+
 ipe->setEmptyText("You deleted the text!");
+ipe->setButtonsEnabled(false);
 
 ipe->valueChanged().connect(std::bind([=] () {
     out->setText("In-place edit is set to... ");
@@ -1886,7 +1889,7 @@
 
 class ReportResource : public Wt::WResource
 {
-public:
+public:
   ReportResource(Wt::WObject *parent = 0);
 };
 
@@ -1910,8 +1913,8 @@
 tableView->setModel(new VirtualModel(10000, 50, tableView));
 
 tableView->setRowHeaderCount(1); // treat first column as 'fixed' row headers
-tableView->setSortingEnabled(false);
-tableView->setAlternatingRowColors(true);
+tableView->setSortingEnabled(false);
+tableView->setAlternatingRowColors(true);
 tableView->setRowHeight(28);
 tableView->setHeaderHeight(28);
 tableView->setSelectionMode(Wt::ExtendedSelection);
@@ -2088,7 +2091,7 @@
 	 "<p>Launch the rocket immediately?</p>",
 	 Wt::Information, Wt::Yes | Wt::No);
 
-    messageBox->setModal(false);
+    messageBox->setModal(false);
 
     messageBox->buttonClicked().connect(std::bind([=] () {
 	if (messageBox->buttonResult() == Wt::Yes)
@@ -2144,7 +2147,7 @@
 Wt::WNavigationBar *navigation = new Wt::WNavigationBar(container);
 navigation->setTitle("Corpy Inc.",
 		     "http://www.google.com/search?q=corpy+inc");
-navigation->setResponsive(true);
+navigation->setResponsive(true);
 
 Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget(container);
 contentsStack->addStyleClass("contents");
@@ -2250,7 +2253,7 @@
 
 class PaintBrush : public Wt::WPaintedWidget
 {
-public:
+public:
     PaintBrush(int width, int height, Wt::WContainerWidget *parent = 0)
 	: Wt::WPaintedWidget(parent)
     {
@@ -2275,7 +2278,7 @@
 	color_ = c;
     }
 
-protected:
+protected:
     virtual void paintEvent(Wt::WPaintDevice *paintDevice) {
 	Wt::WPainter painter(paintDevice);
 	painter.setRenderHint(Wt::WPainter::Antialiasing);
@@ -2291,27 +2294,27 @@
 	path_ = Wt::WPainterPath(path_.currentPosition());
     }
 
-private:
+private:
     Wt::WPainterPath path_;
     Wt::WColor color_;
 
-    void mouseDown(const Wt::WMouseEvent& e) {
+    void mouseDown(const Wt::WMouseEvent& e) {
 	Wt::Coordinates c = e.widget();
 	path_ = Wt::WPainterPath(Wt::WPointF(c.x, c.y));
     }
 
-    void mouseDrag(const Wt::WMouseEvent& e) {
+    void mouseDrag(const Wt::WMouseEvent& e) {
 	Wt::Coordinates c = e.widget();
 	path_.lineTo(c.x, c.y);
 	update(Wt::PaintUpdate);
     }
 
-    void touchStart(const Wt::WTouchEvent& e) {
+    void touchStart(const Wt::WTouchEvent& e) {
 	Wt::Coordinates c = e.touches()[0].widget();
 	path_ = Wt::WPainterPath(Wt::WPointF(c.x, c.y));
     }
 
-    void touchMove(const Wt::WTouchEvent& e) {
+    void touchMove(const Wt::WTouchEvent& e) {
 	Wt::Coordinates c = e.touches()[0].widget();
 	path_.lineTo(c.x, c.y);
 	update(Wt::PaintUpdate);
@@ -2327,7 +2330,7 @@
     Wt::WPushButton *button = new Wt::WPushButton();
     button->setTextFormat(Wt::XHTMLText);
     button->setText("&nbsp;");
-    button->setCheckable(true);
+    button->setCheckable(true);
     button->addStyleClass(className);
     button->setWidth(30);
     button->checked().connect(std::bind([=] () {
@@ -2411,14 +2414,14 @@
 
 class ClippingWidget : public Wt::WPaintedWidget
 {
-public:
+public:
     ClippingWidget(Wt::WContainerWidget *parent = 0)
 	: Wt::WPaintedWidget(parent)
     {
 	resize(310, 150);  // Provide a default size.
     }
 
-protected:
+protected:
     void paintEvent(Wt::WPaintDevice *paintDevice) {
 	Wt::WPainter painter(paintDevice);
 
@@ -2440,7 +2443,7 @@
 	}
   }
 
-private:
+private:
     void drawStar(Wt::WPainter& painter, double radius) {
 	painter.save();
 	Wt::WPainterPath circlePath = Wt::WPainterPath();
@@ -2467,7 +2470,7 @@
 
 Wt::WContainerWidget *container = new Wt::WContainerWidget();
 
-new ClippingWidget(container);
+new ClippingWidget(container);
 
@@ -2479,7 +2482,7 @@ class MyPaintedWidget : public Wt::WPaintedWidget { -public: +public: MyPaintedWidget(Wt::WContainerWidget *parent = 0) : Wt::WPaintedWidget(parent), end_(100) { @@ -2491,14 +2494,14 @@ update(); // Trigger a repaint. } -protected: +protected: void paintEvent(Wt::WPaintDevice *paintDevice) { Wt::WPainter painter(paintDevice); painter.setBrush(Wt::WBrush(Wt::WBrush(Wt::blue))); painter.drawRect(0, 0 ,end_, 50); } -private: +private: int end_; }; @@ -2525,14 +2528,14 @@ class PaintingImagesWidget : public Wt::WPaintedWidget { -public: +public: PaintingImagesWidget(Wt::WContainerWidget *parent = 0) : Wt::WPaintedWidget(parent) { resize(639, 1310); // Provide a default size. } -protected: +protected: void paintEvent(Wt::WPaintDevice *paintDevice) { Wt::WPainter painter(paintDevice); @@ -2582,7 +2585,7 @@ Wt::WContainerWidget *container = new Wt::WContainerWidget(); -new PaintingImagesWidget(container); +new PaintingImagesWidget(container); @@ -2599,14 +2602,14 @@ class ShapesWidget : public Wt::WPaintedWidget { -public: +public: ShapesWidget(Wt::WContainerWidget *parent = 0) : Wt::WPaintedWidget(parent) { resize(310, 400); // Provide a default size. } -protected: +protected: void paintEvent(Wt::WPaintDevice *paintDevice) { Wt::WPainter painter(paintDevice); painter.setPen(Wt::red); @@ -2697,7 +2700,7 @@ Wt::WContainerWidget *container = new Wt::WContainerWidget(); -new ShapesWidget(container); +new ShapesWidget(container); @@ -2710,14 +2713,14 @@ class StyleWidget : public Wt::WPaintedWidget { -public: +public: StyleWidget(Wt::WContainerWidget *parent = 0) : Wt::WPaintedWidget(parent) { resize(310, 1140); // Provide a default size. } -protected: +protected: void paintEvent(Wt::WPaintDevice *paintDevice) { Wt::WPainter painter(paintDevice); @@ -2911,7 +2914,7 @@ Wt::WContainerWidget *container = new Wt::WContainerWidget(); -new StyleWidget(container); +new StyleWidget(container); @@ -2924,14 +2927,14 @@ class TransformationsWidget : public Wt::WPaintedWidget { -public: +public: TransformationsWidget(Wt::WContainerWidget *parent = 0) : Wt::WPaintedWidget(parent) { resize(300, 500); // Provide a default size. } -protected: +protected: void paintEvent(Wt::WPaintDevice *paintDevice) { Wt::WPainter painter(paintDevice); painter.setPen(Wt::red); @@ -2989,7 +2992,7 @@ painter.restore(); } -private: +private: void drawFilledPolygon(Wt::WPainter &painter, const Wt::WColor& color) { painter.setBrush(color); const Wt::WPointF points[] @@ -3003,17 +3006,7 @@ Wt::WContainerWidget *container = new Wt::WContainerWidget(); -new TransformationsWidget(container); - - - -
#include <Wt/WPanel>
-#include <Wt/WText>
-
-Wt::WPanel *panel = new Wt::WPanel();
-panel->addStyleClass("centered-example");
-panel->setTitle("Terrific panel");
-panel->setCentralWidget(new Wt::WText("This is a panel with a title."));
+new TransformationsWidget(container);
 
@@ -3024,7 +3017,7 @@ Wt::WPanel *panel = new Wt::WPanel(); panel->setTitle("Collapsible panel"); panel->addStyleClass("centered-example"); -panel->setCollapsible(true); +panel->setCollapsible(true); Wt::WAnimation animation(Wt::WAnimation::SlideInFromTop, Wt::WAnimation::EaseOut, @@ -3034,21 +3027,23 @@ panel->setCentralWidget(new Wt::WText("This panel can be collapsed.")); - +
#include <Wt/WPanel>
 #include <Wt/WText>
 
 Wt::WPanel *panel = new Wt::WPanel();
 panel->addStyleClass("centered-example");
-panel->setCentralWidget(new Wt::WText("This is a default panel."));
+panel->setTitle("Terrific panel");
+panel->setCentralWidget(new Wt::WText("This is a panel with a title."));
 
- -
#include <Wt/WLink>
-#include <Wt/WPushButton>
+  
+
#include <Wt/WPanel>
+#include <Wt/WText>
 
-Wt::WPushButton *button = new Wt::WPushButton("Next");
-button->setLink(Wt::WLink(Wt::WLink::InternalPath, "/navigation/anchor"));
+Wt::WPanel *panel = new Wt::WPanel();
+panel->addStyleClass("centered-example");
+panel->setCentralWidget(new Wt::WText("This is a default panel."));
 
@@ -3066,7 +3061,7 @@ if (app->internalPath() == "/navigation/shop") out->setText("<p>Currently shopping.</p>"); - else if (app->internalPath() == "/navigation/eat") + else if (app->internalPath() == "/navigation/eat") out->setText("<p>Needed some food, eating now!</p>"); else out->setText("<p><i>Idle.</i></p>"); @@ -3088,7 +3083,7 @@ * Handle the internal path events. */ Wt::WText *out = new Wt::WText(container); -out->setInline(false); +out->setInline(false); Wt::WApplication *app = Wt::WApplication::instance(); @@ -3100,15 +3095,23 @@
- -
#include <Wt/WPainter>
+  
+
#include <Wt/WLink>
+#include <Wt/WPushButton>
+
+Wt::WPushButton *button = new Wt::WPushButton("Next");
+button->setLink(Wt::WLink(Wt::WLink::InternalPath, "/navigation/anchor"));
+
+
+ +
#include <Wt/WPainter>
 #include <Wt/WPdfImage>
 #include <Wt/WPushButton>
 #include <Wt/WResource>
 
 class SamplePdfResource : public Wt::WPdfImage
 {
-public:
+public:
     SamplePdfResource(Wt::WObject *parent = 0)
         : Wt::WPdfImage(400, 300, parent)
     {
@@ -3116,7 +3119,7 @@
         paint();
     }
 
-private:
+private:
     void paint() {
         Wt::WPainter painter(this);
 
@@ -3137,6 +3140,21 @@
 
 Wt::WPushButton *button = new Wt::WPushButton("Create pdf", container);
 button->setLink(pdf);
+
+
+ +
#include <Wt/Chart/WCartesianChart>
+#include <Wt/WPdfImage>
+
+Wt::Chart::WCartesianChart *chart = new Wt::Chart::WCartesianChart(...);
+
+Wt::WPdfImage pdfImage("4cm", "3cm");
+{
+    Wt::WPainter p(&pdfImage);
+    chart->paint(p);
+}
+std::ofstream f("chart.pdf", std::ios::out | std::ios::binary);
+pdfImage.write(f);
 
@@ -3159,7 +3177,7 @@ class ReportResource : public Wt::WResource { -public: +public: ReportResource(Wt::WObject *parent = 0) : Wt::WResource(parent) { @@ -3187,7 +3205,7 @@ delete[] buf; } -private: +private: void renderReport(HPDF_Doc pdf) { renderPdf(Wt::WString::tr("report.example"), pdf); } @@ -3200,8 +3218,6 @@ Wt::Render::WPdfRenderer renderer(pdf, page); renderer.setMargin(2.54); renderer.setDpi(96); - renderer.useStylesheet(Wt::WApplication::instance()->appRoot() - + "pdf_special.css"); renderer.render(html); } }; @@ -3267,7 +3283,7 @@ table->setMargin(10, Wt::Top | Wt::Bottom); table->setMargin(Wt::WLength::Auto, Wt::Left | Wt::Right); -table->setSortingEnabled(true); +table->setSortingEnabled(true); table->setModel(model); table->setColumnWidth(1, 100); table->setRowHeight(28); @@ -3295,8 +3311,8 @@ Wt::Chart::TextPercentage); // Enable a 3D and shadow effect. -chart->setPerspectiveEnabled(true, 0.2); -chart->setShadowEnabled(true); +chart->setPerspectiveEnabled(true, 0.2); +chart->setShadowEnabled(true); chart->setExplode(0, 0.3); // Explode the first item. chart->resize(800, 300); // WPaintedWidget must be given an explicit size. @@ -3392,7 +3408,7 @@ })); Wt::WMenuItem *item = popup->addItem("Don't disturb"); -item->setCheckable(true); +item->setCheckable(true); item->triggered().connect(std::bind([=] () { out->setText(Wt::WString::fromUTF8("<p>{1} item is {2}.</p>") @@ -3500,23 +3516,6 @@ startButton->disable(); } })); -
-
- -
#include <Wt/WPushButton>
-#include <Wt/WTemplate>
-
-
-Wt::WTemplate *result = new Wt::WTemplate();
-
-result->setTemplateText("<div> ${pb1} ${pb2} </div>");
-
-Wt::WPushButton *pb = new Wt::WPushButton("Click me!");
-result->bindWidget("pb1", pb);  // By default the button is enabled.
-
-pb = new Wt::WPushButton("Try to click me...");
-result->bindWidget("pb2", pb);
-pb->setEnabled(false);          // The second button is disabled.
 
@@ -3573,6 +3572,23 @@ button = new Wt::WPushButton("Link"); button->setStyleClass("btn-link"); result->bindWidget("button-link", button); + + + +
#include <Wt/WPushButton>
+#include <Wt/WTemplate>
+
+
+Wt::WTemplate *result = new Wt::WTemplate();
+
+result->setTemplateText("<div> ${pb1} ${pb2} </div>");
+
+Wt::WPushButton *pb = new Wt::WPushButton("Click me!");
+result->bindWidget("pb1", pb);  // By default the button is enabled.
+
+pb = new Wt::WPushButton("Try to click me...");
+result->bindWidget("pb2", pb);
+pb->setEnabled(false);          // The second button is disabled.
 
@@ -3602,7 +3618,6 @@ Wt::WPushButton *appendedDropdownButton = new Wt::WPushButton("Action"); result->bindWidget("appendedButton", appendedDropdownButton); appendedDropdownButton->setMenu(popup); -popup->show(); @@ -3631,7 +3646,6 @@ Wt::WPushButton *prependedDropdownButton = new Wt::WPushButton("Action"); result->bindWidget("prependedButton", prependedDropdownButton); prependedDropdownButton->setMenu(popup); -popup->show(); @@ -3709,31 +3723,6 @@ button = new Wt::WRadioButton("Nono, radio me!", container); group->addButton(button); -group->setSelectedButtonIndex(0); // Select the first button by default. - - - -
#include <Wt/WButtonGroup>
-#include <Wt/WContainerWidget>
-#include <Wt/WRadioButton>
-
-Wt::WContainerWidget *container = new Wt::WContainerWidget();
-
-Wt::WButtonGroup *group = new Wt::WButtonGroup(container);
-Wt::WRadioButton *button;
-
-button = new Wt::WRadioButton("Radio me!", container);
-button->setInline(false);
-group->addButton(button);
-
-button = new Wt::WRadioButton("No, radio me!", container);
-button->setInline(false);
-group->addButton(button);
-
-button = new Wt::WRadioButton("Nono, radio me!", container);
-button->setInline(false);
-group->addButton(button);
-
 group->setSelectedButtonIndex(0); // Select the first button by default.
 
@@ -3750,19 +3739,19 @@ Wt::WRadioButton *rb; rb = new Wt::WRadioButton("sleeping", container); -rb->setInline(false); +rb->setInline(false); group->addButton(rb, 1); rb = new Wt::WRadioButton("eating", container); -rb->setInline(false); +rb->setInline(false); group->addButton(rb, 2); rb = new Wt::WRadioButton("driving", container); -rb->setInline(false); +rb->setInline(false); group->addButton(rb, 3); rb = new Wt::WRadioButton("learning Wt", container); -rb->setInline(false); +rb->setInline(false); group->addButton(rb, 4); group->setSelectedButtonIndex(0); // Select the first button by default. @@ -3773,15 +3762,15 @@ Wt::WString text; switch (group->id(selection)) { - case 1: text = Wt::WString::fromUTF8("You checked button {1}.") + case 1: text = Wt::WString::fromUTF8("You checked button {1}.") .arg(group->checkedId()); break; - case 2: text = Wt::WString::fromUTF8("You selected button {1}.") + case 2: text = Wt::WString::fromUTF8("You selected button {1}.") .arg(group->checkedId()); break; - case 3: text = Wt::WString::fromUTF8("You clicked button {1}.") + case 3: text = Wt::WString::fromUTF8("You clicked button {1}.") .arg(group->checkedId()); break; } @@ -3804,6 +3793,31 @@ new Wt::WRadioButton("Radio me!", container); new Wt::WRadioButton("Radio me too!", container); + +
+ +
#include <Wt/WButtonGroup>
+#include <Wt/WContainerWidget>
+#include <Wt/WRadioButton>
+
+Wt::WContainerWidget *container = new Wt::WContainerWidget();
+
+Wt::WButtonGroup *group = new Wt::WButtonGroup(container);
+Wt::WRadioButton *button;
+
+button = new Wt::WRadioButton("Radio me!", container);
+button->setInline(false);
+group->addButton(button);
+
+button = new Wt::WRadioButton("No, radio me!", container);
+button->setInline(false);
+group->addButton(button);
+
+button = new Wt::WRadioButton("Nono, radio me!", container);
+button->setInline(false);
+group->addButton(button);
+
+group->setSelectedButtonIndex(0); // Select the first button by default.
 
@@ -3816,7 +3830,7 @@ class MyResource : public Wt::WResource { -public: +public: MyResource(Wt::WObject *parent = 0) : Wt::WResource(parent) { @@ -3840,6 +3854,16 @@ Wt::WAnchor *anchor = new Wt::WAnchor(Wt::WLink(textResource), "Download file", container); anchor->setTarget(Wt::TargetNewWindow); + + + +
#include <Wt/WContainerWidget>
+#include <Wt/WText>
+
+Wt::WContainerWidget *container = new Wt::WContainerWidget();
+
+Wt::WText *out = new Wt::WText(container);
+out->setText("<p>Resources</p>");
 
@@ -3855,16 +3879,6 @@ //Wt::WResource *resource = new SamplePdfResource(container); //Wt::WServer::addResource(resource, "/media/static-resource"); - - - -
#include <Wt/WContainerWidget>
-#include <Wt/WText>
-
-Wt::WContainerWidget *container = new Wt::WContainerWidget();
-
-Wt::WText *out = new Wt::WText(container);
-out->setText("<p>Resources</p>");
 
@@ -3893,7 +3907,7 @@ Wt::Chart::WCartesianChart *chart = new Wt::Chart::WCartesianChart(container); chart->setModel(model); // Set the model. chart->setXSeriesColumn(0); // Set the column that holds the X data. -chart->setLegendEnabled(true); // Enable the legend. +chart->setLegendEnabled(true); // Enable the legend. chart->setType(Wt::Chart::ScatterPlot); // Set type to ScatterPlot. @@ -3954,9 +3968,9 @@ // Renders the data in a table. Wt::WTableView *table = new Wt::WTableView(container); table->setModel(model); -table->setSortingEnabled(false); -table->setColumnResizeEnabled(true); -table->setAlternatingRowColors(true); +table->setSortingEnabled(false); +table->setColumnResizeEnabled(true); +table->setAlternatingRowColors(true); table->setColumnAlignment(0, Wt::AlignCenter); table->setHeaderAlignment(0, Wt::AlignCenter); table->setRowHeight(28); @@ -3981,7 +3995,7 @@ chart->setBackground(Wt::WColor(220, 220, 220)); chart->setModel(model); chart->setXSeriesColumn(0); -chart->setLegendEnabled(true); +chart->setLegendEnabled(true); chart->setType(Wt::Chart::ScatterPlot); chart->axis(Wt::Chart::XAxis).setScale(Wt::Chart::DateScale); @@ -4252,10 +4266,10 @@ tableView->setModel(csvToModel(Wt::WApplication::appRoot() + "table.csv", tableView)); -tableView->setColumnResizeEnabled(false); +tableView->setColumnResizeEnabled(false); tableView->setColumnAlignment(0, Wt::AlignCenter); tableView->setHeaderAlignment(0, Wt::AlignCenter); -tableView->setAlternatingRowColors(true); +tableView->setAlternatingRowColors(true); tableView->setRowHeight(28); tableView->setHeaderHeight(28); tableView->setSelectionMode(Wt::SingleSelection); @@ -4306,6 +4320,7 @@ #include <Wt/WText> Wt::WContainerWidget *container = new Wt::WContainerWidget(); +container->addStyleClass("control-group"); new Wt::WText("Enter a number between 0 and 100:", container); @@ -4318,10 +4333,14 @@ Wt::WText *out = new Wt::WText("", container); -sb->valueChanged().connect(std::bind([=] (double d) { - out->setText(Wt::WString::fromUTF8("Spin box value changed to {1}.") - .arg(d)); -}, std::placeholders::_1)); +sb->changed().connect(std::bind([=] () { + if (sb->validate() == Wt::WValidator::Valid) { + out->setText(Wt::WString::fromUTF8("Spin box value changed to {1}") + .arg(sb->text())); + } else { + out->setText(Wt::WString::fromUTF8("Invalid spin box value!")); + } +})); @@ -4406,10 +4425,10 @@ }; - void addOptionToggle(Wt::WWidget *widget, const char *option, + void addOptionToggle(Wt::WWidget *widget, const char *option, const char *styleClass, Wt::WContainerWidget *parent) { Wt::WCheckBox *checkBox = new Wt::WCheckBox(option, parent); - checkBox->setInline(false); + checkBox->setInline(false); checkBox->changed().connect(std::bind([=] () { widget->toggleStyleClass(styleClass, checkBox->isChecked()); })); @@ -4472,7 +4491,7 @@ = tabW->addTab(new Wt::WTextArea("You can close this tab" " by clicking on the close icon."), "Close"); -tab->setCloseable(true); +tab->setCloseable(true); tabW->setStyleClass("tabwidget"); @@ -4521,8 +4540,7 @@ Wt::WContainerWidget *container = new Wt::WContainerWidget(); Wt::WTextEdit *edit = new Wt::WTextEdit(container); -edit->setWidth(200); -edit->setHeight(200); +edit->setHeight(300); edit->setText("<p>" "<span style=\"font-family: 'courier new', courier; font-size: medium;\">" "<strong>WTextEdit</strong></span></p>" @@ -4539,7 +4557,7 @@ "<p>don't have style.</p>"); Wt::WPushButton *button = new Wt::WPushButton("Get text", container); -button->setMargin(10, Wt::Top); +button->setMargin(10, Wt::Top | Wt::Bottom); Wt::WText *out = new Wt::WText(container); out->setStyleClass("xhtml-output"); @@ -4560,7 +4578,7 @@ le->setEmptyText("Edit me"); Wt::WLineEdit *out = new Wt::WLineEdit(container); -out->setReadOnly(true); +out->setReadOnly(true); le->keyWentUp().connect(std::bind([=] () { out->setText("Line edit: key up event"); @@ -4668,6 +4686,27 @@ "wich is filtered by the XSS filter.</p>" "<script>alert(\"XSS Attack!\");</script>" "<p>A warning is printed in the logs.</p>"); + + + +
#include <Wt/WApplication>
+#include <Wt/WEnvironment>
+
+// Main application class
+class ThemeExample : public Wt::WApplication
+{
+public:
+    // Constructor
+    ThemeExample(const Wt::WEnvironment &env)
+	: Wt::WApplication(env)
+    {
+	setCssTheme("polished");
+
+	/*
+	 *  Create UI, Set initial values, ...
+	 */
+    }
+}
 
@@ -4723,7 +4762,7 @@ Wt::WIconPair *folderIcon = new Wt::WIconPair("icons/yellow-folder-closed.png", - "icons/yellow-folder-open.png", false); + "icons/yellow-folder-open.png", false); Wt::WTreeNode *node = new Wt::WTreeNode("Furniture", folderIcon); @@ -4811,73 +4850,9 @@ treeView->setRowHeight(24); treeView->setHeaderHeight(24); -treeView->setSortingEnabled(false); +treeView->setSortingEnabled(false); treeView->setSelectionMode(Wt::SingleSelection); treeView->setEditTriggers(Wt::WAbstractItemView::NoEditTrigger); - - - -
#include <Wt/WContainerWidget>
-#include <Wt/WText>
-#include <Wt/WVBoxLayout>
-
-Wt::WContainerWidget *container = new Wt::WContainerWidget();
-container->resize(150, 150);
-container->setStyleClass("yellow-box centered");
-
-Wt::WVBoxLayout *vbox = new Wt::WVBoxLayout();
-container->setLayout(vbox);
-
-Wt::WText *item = new Wt::WText("Item 1");
-item->setStyleClass("green-box");
-vbox->addWidget(item);
-  
-item = new Wt::WText("Item 2");
-item->setStyleClass("blue-box");
-vbox->addWidget(item);
-
-
- -
#include <Wt/WContainerWidget>
-#include <Wt/WText>
-#include <Wt/WVBoxLayout>
-
-Wt::WContainerWidget *container = new Wt::WContainerWidget();
-container->resize(150, 150);
-container->setStyleClass("yellow-box centered");
-
-Wt::WVBoxLayout *vbox = new Wt::WVBoxLayout();
-container->setLayout(vbox);
-
-Wt::WText *item = new Wt::WText("Item 1");
-item->setStyleClass("green-box");
-vbox->addWidget(item, 1);
-  
-item = new Wt::WText("Item 2");
-item->setStyleClass("blue-box");
-vbox->addWidget(item);
-
-
- -
#include <Wt/WContainerWidget>
-#include <Wt/WText>
-#include <Wt/WVBoxLayout>
-
-
-Wt::WContainerWidget *container = new Wt::WContainerWidget();
-container->resize(150, 150);
-container->setStyleClass("yellow-box centered");
-
-Wt::WVBoxLayout *vbox = new Wt::WVBoxLayout();
-container->setLayout(vbox);
-
-Wt::WText *item = new Wt::WText("Item 1");
-item->setStyleClass("green-box");
-vbox->addWidget(item, 1);
-  
-item = new Wt::WText("Item 2");
-item->setStyleClass("blue-box");
-vbox->addWidget(item);
 
@@ -4897,7 +4872,7 @@ // Create a validator that accepts integer values between 0 and 150. Wt::WIntValidator *validator = new Wt::WIntValidator(0, 150); -validator->setMandatory(true); +validator->setMandatory(true); ageEdit->setValidator(validator); Wt::WPushButton *button = new Wt::WPushButton("Save"); @@ -4942,7 +4917,7 @@ dv->setBottom(Wt::WDate(1900, 1, 1)); dv->setTop(Wt::WDate::currentDate()); dv->setFormat("dd/MM/yyyy"); -dv->setMandatory(true); +dv->setMandatory(true); dv->setInvalidBlankText("A birthdate is mandatory!"); dv->setInvalidNotADateText("You should enter a date in the format " "\"dd/MM/yyyy\"!"); @@ -4987,7 +4962,7 @@ class AgeFormModel : public Wt::WFormModel { -public: +public: // in C++11: // static constexpr Field AgeField = "age"; static Field AgeField; @@ -5000,7 +4975,7 @@ setValue(AgeField, std::string()); } -private: +private: Wt::WValidator *createAgeValidator() { Wt::WIntValidator *v = new Wt::WIntValidator(0, 150); return v; @@ -5011,7 +4986,7 @@ class AgeFormView : public Wt::WTemplateFormView { -public: +public: // inline constructor AgeFormView() { model_ = new AgeFormModel(this); @@ -5028,7 +5003,7 @@ updateView(model_); } -private: +private: void process() { updateModel(model_); if (model_->validate()) { @@ -5052,6 +5027,70 @@ AgeFormView *view = new AgeFormView(); + + + +
#include <Wt/WContainerWidget>
+#include <Wt/WText>
+#include <Wt/WVBoxLayout>
+
+Wt::WContainerWidget *container = new Wt::WContainerWidget();
+container->resize(150, 150);
+container->setStyleClass("yellow-box centered");
+
+Wt::WVBoxLayout *vbox = new Wt::WVBoxLayout();
+container->setLayout(vbox);
+
+Wt::WText *item = new Wt::WText("Item 1");
+item->setStyleClass("green-box");
+vbox->addWidget(item);
+  
+item = new Wt::WText("Item 2");
+item->setStyleClass("blue-box");
+vbox->addWidget(item);
+
+
+ +
#include <Wt/WContainerWidget>
+#include <Wt/WText>
+#include <Wt/WVBoxLayout>
+
+Wt::WContainerWidget *container = new Wt::WContainerWidget();
+container->resize(150, 150);
+container->setStyleClass("yellow-box centered");
+
+Wt::WVBoxLayout *vbox = new Wt::WVBoxLayout();
+container->setLayout(vbox);
+
+Wt::WText *item = new Wt::WText("Item 1");
+item->setStyleClass("green-box");
+vbox->addWidget(item, 1);
+  
+item = new Wt::WText("Item 2");
+item->setStyleClass("blue-box");
+vbox->addWidget(item);
+
+
+ +
#include <Wt/WContainerWidget>
+#include <Wt/WText>
+#include <Wt/WVBoxLayout>
+
+
+Wt::WContainerWidget *container = new Wt::WContainerWidget();
+container->resize(150, 150);
+container->setStyleClass("yellow-box centered");
+
+Wt::WVBoxLayout *vbox = new Wt::WVBoxLayout();
+container->setLayout(vbox);
+
+Wt::WText *item = new Wt::WText("Item 1");
+item->setStyleClass("green-box");
+vbox->addWidget(item, 1);
+  
+item = new Wt::WText("Item 2");
+item->setStyleClass("blue-box");
+vbox->addWidget(item);
 
@@ -5157,7 +5196,7 @@ class VirtualModel : public Wt::WAbstractTableModel { -public: +public: VirtualModel(int rows, int columns, Wt::WObject *parent = 0) : Wt::WAbstractTableModel(parent), rows_(rows), @@ -5183,13 +5222,13 @@ virtual boost::any data(const Wt::WModelIndex& index, int role = Wt::DisplayRole) const { switch (role) { - case Wt::DisplayRole: + case Wt::DisplayRole: if (index.column() == 0) return Wt::WString("Row {1}").arg(index.row()); else return Wt::WString("Item row {1}, col {2}") .arg(index.row()).arg(index.column()); - default: + default: return boost::any(); } } @@ -5200,16 +5239,16 @@ { if (orientation == Wt::Horizontal) { switch (role) { - case Wt::DisplayRole: + case Wt::DisplayRole: return Wt::WString("Column {1}").arg(section); - default: + default: return boost::any(); } } else return boost::any(); } -private: +private: int rows_, columns_; }; @@ -5230,42 +5269,6 @@ new Wt::WText("<p>Text " + boost::lexical_cast<std::string>(i) + "</p>", container); } - - - -
#include <Wt/Chart/WCartesianChart>
-#include <Wt/WPdfImage>
-
-Wt::Chart::WCartesianChart *chart = new Wt::Chart::WCartesianChart(...);
-
-Wt::WPdfImage pdfImage("4cm", "3cm");
-{
-    Wt::WPainter p(&pdfImage);
-    chart->paint(p);
-}
-std::ofstream f("chart.pdf", std::ios::out | std::ios::binary);
-pdfImage.write(f);
-
-
- -
#include <Wt/WApplication>
-#include <Wt/WEnvironment>
-
-// Main application class
-class ThemeExample : public Wt::WApplication
-{
-public:
-    // Constructor
-    ThemeExample(const Wt::WEnvironment &env)
-	: Wt::WApplication(env)
-    {
-	setCssTheme("polished");
-
-	/*
-	 *  Create UI, Set initial values, ...
-	 */
-    }
-}
 
@@ -5298,6 +5301,20 @@ editElement.value = modifiedEditValue; editElement.selectionStart = edit.selectionEnd = modifiedPos; } + + + +
.combo-red {
+    color:red
+}
+
+.combo-green {
+    color:green
+}
+
+.combo-blue {
+    color:blue
+}
 
@@ -5327,20 +5344,6 @@ .CSS-example .info { background-color: #D9EDF7; } - - - -
.combo-red {
-    color:red
-}
-
-.combo-green {
-    color:green
-}
-
-.combo-blue {
-    color:blue
-}
 
@@ -5603,11 +5606,6 @@ margin: 0px auto; } -.example .text-edit-example button { - margin-top: 10px; - margin-bottom: 10px; -} - .example .text-edit-example .out { margin-top: 10px; margin-left: 100px; @@ -5635,19 +5633,6 @@ .dl-horizontal.no-clear:after { clear: none; } - - - -
span.special
-{
-  color: #FF0000;
-  font-weight: bold;
-}
-
-#main_table .special /*Note the space!*/
-{
-  color: #FF0000;
-}
 
diff --git a/examples/widgetgallery/approot/wt_config.xml b/examples/widgetgallery/approot/wt_config.xml index 51981f777f..f224e5d1d8 100644 --- a/examples/widgetgallery/approot/wt_config.xml +++ b/examples/widgetgallery/approot/wt_config.xml @@ -18,7 +18,7 @@ .*AhrefsBot.* - false + true false * -debug -info:WebRequest IE8=IE7 diff --git a/examples/widgetgallery/examples/ComboBoxModel.cpp b/examples/widgetgallery/examples/ComboBoxModel.cpp index 814cd2becb..8bc5c23615 100644 --- a/examples/widgetgallery/examples/ComboBoxModel.cpp +++ b/examples/widgetgallery/examples/ComboBoxModel.cpp @@ -1,5 +1,6 @@ #include #include +#include #include SAMPLE_BEGIN(ComboBoxModel) @@ -8,17 +9,19 @@ Wt::WContainerWidget *container = new Wt::WContainerWidget(); Wt::WComboBox *cb = new Wt::WComboBox(container); cb->setMargin(10, Wt::Right); -Wt::WAbstractItemModel *model = cb->model(); +Wt::WStringListModel *model = new Wt::WStringListModel(cb); -model->insertRows(0, 4); -model->setData(0, 0, std::string("Belgium"), Wt::DisplayRole); +model->addString("Belgium"); model->setData(0, 0, std::string("BE"), Wt::UserRole); -model->setData(1, 0, std::string("Netherlands"), Wt::DisplayRole); +model->addString("Netherlands"); model->setData(1, 0, std::string("NL"), Wt::UserRole); -model->setData(2, 0, std::string("United Kingdom"), Wt::DisplayRole); +model->addString("United Kingdom"); model->setData(2, 0, std::string("UK"), Wt::UserRole); -model->setData(3, 0, std::string("United States"), Wt::DisplayRole); +model->addString("United States"); model->setData(3, 0, std::string("US"), Wt::UserRole); +model->setFlags(3, 0); + +cb->setModel(model); Wt::WText *out = new Wt::WText(container); diff --git a/examples/widgetgallery/examples/DateEdit.cpp b/examples/widgetgallery/examples/DateEdit.cpp index 053194e688..cb1b777efb 100644 --- a/examples/widgetgallery/examples/DateEdit.cpp +++ b/examples/widgetgallery/examples/DateEdit.cpp @@ -20,6 +20,7 @@ Wt::WLabel *de2Label = new Wt::WLabel("To (format \"dd MM yyyy\")", container); Wt::WDateEdit *de2 = new Wt::WDateEdit(container); de2->setFormat("dd MM yyyy"); // Apply a different date format. de2->calendar()->setHorizontalHeaderFormat(Wt::WCalendar::SingleLetterDayNames); +de2->setBottom(de1->date()); de2Label->setBuddy(de2); new Wt::WText("

", container); @@ -29,13 +30,17 @@ Wt::WText *out = new Wt::WText(container); out->setMargin(10, Wt::Left); de1->changed().connect(std::bind([=] () { - de2->setBottom(de1->date()); - out->setText("

Date picker 1 is changed.

"); + if (de1->validate() == Wt::WValidator::Valid) { + de2->setBottom(de1->date()); + out->setText("

Date picker 1 is changed.

"); + } })); de2->changed().connect(std::bind([=] () { - de1->setTop(de2->date()); - out->setText("

Date picker 2 is changed.

"); + if (de1->validate() == Wt::WValidator::Valid) { + de1->setTop(de2->date()); + out->setText("

Date picker 2 is changed.

"); + } })); button->clicked().connect(std::bind([=] () { diff --git a/resources/themes/bootstrap/wt.css b/resources/themes/bootstrap/wt.css index 74a7d2e197..7e929774e3 100644 --- a/resources/themes/bootstrap/wt.css +++ b/resources/themes/bootstrap/wt.css @@ -83,6 +83,11 @@ .navbar .nav-collapse { display: block !important; } +@media (max-width: 979px) { + .nav-collapse .dropdown-menu { + display: block !important; + } +} .navbar .navbar-inner .container { margin-left: 0px; margin-right: 0px; @@ -168,6 +173,9 @@ .Wt-cal table.dlong { width: 560px; } +.Wt-cal .Wt-cal-navbutton { + display: block; +} .Wt-cal .days.d1 td { width: 20px; } @@ -217,6 +225,7 @@ .Wt-cal .days td .Wt-cal-sel, .Wt-cal .days td .Wt-cal-sel:hover { color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); diff --git a/resources/themes/bootstrap/wt.less b/resources/themes/bootstrap/wt.less index a7572ed43f..763e6adf7a 100644 --- a/resources/themes/bootstrap/wt.less +++ b/resources/themes/bootstrap/wt.less @@ -71,6 +71,12 @@ display: block !important; } +@media (max-width: 979px) { + .nav-collapse .dropdown-menu { + display: block !important; + } +} + .navbar .navbar-inner .container { margin-left: 0px; margin-right: 0px; @@ -175,6 +181,10 @@ } } + .Wt-cal-navbutton { + display: block; + } + .days.d1 td { width: 20px; } diff --git a/resources/themes/default/wt.css b/resources/themes/default/wt.css index 569f19935a..f3e7e76c13 100644 --- a/resources/themes/default/wt.css +++ b/resources/themes/default/wt.css @@ -128,8 +128,9 @@ span.Wt-disabled, fieldset.Wt-disabled legend { color: #FFFFFF; background-color:#6699CC; cursor: pointer; cursor: hand; - padding: 0px 2px 2px; + margin: 3px; vertical-align: middle; + display: block; } .Wt-cal-year { diff --git a/resources/themes/polished/wt.css b/resources/themes/polished/wt.css index 31915e0fa4..d471759c14 100644 --- a/resources/themes/polished/wt.css +++ b/resources/themes/polished/wt.css @@ -123,6 +123,7 @@ span.Wt-disabled, fieldset.Wt-disabled legend { cursor: pointer; cursor: hand; font: bold 20px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; vertical-align: middle; + display: block; } .Wt-cal caption select { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9bbea738ce..6f4ed2c3be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -126,6 +126,7 @@ Wt/WIntValidator.C Wt/WInteractWidget.C Wt/WIOService.C Wt/WLocale.C +Wt/WLocalDateTime.C Wt/WItemDelegate.C Wt/WItemSelectionModel.C Wt/WJavaScript.C @@ -271,6 +272,7 @@ Wt/Chart/WStandardPalette.C Wt/Json/Array.C Wt/Json/Object.C Wt/Json/Parser.C +Wt/Json/Serializer.C Wt/Json/Value.C Wt/Http/HttpUtils.C Wt/Http/Client.C diff --git a/src/Wt/Auth/AbstractUserDatabase b/src/Wt/Auth/AbstractUserDatabase index 3abf4ab9b6..648b2b2374 100644 --- a/src/Wt/Auth/AbstractUserDatabase +++ b/src/Wt/Auth/AbstractUserDatabase @@ -317,6 +317,18 @@ public: * exists. */ virtual User findWithAuthToken(const std::string& hash) const; + + /*! \brief Updates the authentication token with a new hash. + * + * If successful, returns the validity of the updated token in seconds. + * + * Returns 0 if the token could not be updated because it wasn't found + * or is expired. + * + * Returns -1 if not implemented. + */ + virtual int updateAuthToken(const User& user, const std::string& oldhash, + const std::string& newhash); //@} /** @name Authenticaton attempt throttling diff --git a/src/Wt/Auth/AbstractUserDatabase.C b/src/Wt/Auth/AbstractUserDatabase.C index 8f435772aa..6a68c234a8 100644 --- a/src/Wt/Auth/AbstractUserDatabase.C +++ b/src/Wt/Auth/AbstractUserDatabase.C @@ -161,6 +161,15 @@ void AbstractUserDatabase::removeAuthToken(const User& user, LOG_ERROR(Require("removeAuthToken()", AUTH_TOKEN).what()); } +int AbstractUserDatabase::updateAuthToken(const User& user, + const std::string& hash, + const std::string& newHash) +{ + LOG_WARN(Require("updateAuthToken()", AUTH_TOKEN).what()); + + return -1; +} + User AbstractUserDatabase::findWithAuthToken(const std::string& hash) const { LOG_ERROR(Require("findWithAuthToken()", AUTH_TOKEN).what()); diff --git a/src/Wt/Auth/AuthModel b/src/Wt/Auth/AuthModel index cbbb58d2f7..f52b0d86a4 100644 --- a/src/Wt/Auth/AuthModel +++ b/src/Wt/Auth/AuthModel @@ -105,6 +105,12 @@ public: */ virtual bool login(Login& login); + /*! \brief Logs the user out. + * + * This also removes the remember-me cookie for the user. + */ + virtual void logout(Login& login); + /*! \brief Processes an email token. * * This simply calls AuthService::processEmailToken(). diff --git a/src/Wt/Auth/AuthModel.C b/src/Wt/Auth/AuthModel.C index 212aebef00..e047851bc8 100644 --- a/src/Wt/Auth/AuthModel.C +++ b/src/Wt/Auth/AuthModel.C @@ -191,6 +191,23 @@ bool AuthModel::login(Login& login) return false; } +void AuthModel::logout(Login& login) +{ + if (login.loggedIn()) { + if (baseAuth()->authTokensEnabled()) { + WApplication *app = WApplication::instance(); + app->removeCookie(baseAuth()->authTokenCookieName()); + + /* + * FIXME: it would be nice if we also delete the relevant token + * from the database! + */ + } + + login.logout(); + } +} + EmailTokenResult AuthModel::processEmailToken(const std::string& token) { return baseAuth()->processEmailToken(token, users()); @@ -210,8 +227,11 @@ User AuthModel::processAuthToken() switch(result.result()) { case AuthTokenResult::Valid: + /* + * Only extend the validity from what we had currently. + */ app->setCookie(baseAuth()->authTokenCookieName(), result.newToken(), - baseAuth()->authTokenValidity() * 60); + result.newTokenValidity()); return result.user(); case AuthTokenResult::Invalid: diff --git a/src/Wt/Auth/AuthService b/src/Wt/Auth/AuthService index 5ed22487b8..8cf69cae7e 100644 --- a/src/Wt/Auth/AuthService +++ b/src/Wt/Auth/AuthService @@ -171,7 +171,8 @@ public: * Creates an authentication token result. */ AuthTokenResult(Result result, const User& user = User(), - const std::string& newToken = std::string()); + const std::string& newToken = std::string(), + int newTokenValidity = -1); /*! \brief Returns the result. */ @@ -192,10 +193,19 @@ public: */ std::string newToken() const; + /*! \brief Returns the validity of the new token. + * + * This returns the token validity in seconds. + * + * \sa newToken() + */ + int newTokenValidity() const; + private: Result result_; User user_; std::string newToken_; + int newTokenValidity_; }; /*! \class AuthService Wt/Auth/AuthService Wt/Auth/AuthService diff --git a/src/Wt/Auth/AuthService.C b/src/Wt/Auth/AuthService.C index dc655337a4..121a1bcaa3 100644 --- a/src/Wt/Auth/AuthService.C +++ b/src/Wt/Auth/AuthService.C @@ -86,10 +86,12 @@ const User& EmailTokenResult::user() const } AuthTokenResult::AuthTokenResult(Result result, const User& user, - const std::string& newToken) + const std::string& newToken, + int newTokenValidity) : result_(result), user_(user), - newToken_(newToken) + newToken_(newToken), + newTokenValidity_(newTokenValidity) { } const User& AuthTokenResult::user() const @@ -108,6 +110,14 @@ std::string AuthTokenResult::newToken() const throw WException("AuthTokenResult::newToken() invalid"); } +int AuthTokenResult::newTokenValidity() const +{ + if (user_.isValid()) + return newTokenValidity_; + else + throw WException("AuthTokenResult::newTokenValidity() invalid"); +} + AuthService::AuthService() : identityPolicy_(LoginNameIdentity), minimumLoginNameLength_(4), @@ -225,7 +235,7 @@ std::string AuthService::createAuthToken(const User& user) const } AuthTokenResult AuthService::processAuthToken(const std::string& token, - AbstractUserDatabase& users) const + AbstractUserDatabase& users) const { std::auto_ptr t(users.startTransaction()); @@ -234,13 +244,23 @@ AuthTokenResult AuthService::processAuthToken(const std::string& token, User user = users.findWithAuthToken(hash); if (user.isValid()) { - user.removeAuthToken(hash); - - std::string newToken = createAuthToken(user); + std::string newToken = WRandom::generateId(tokenLength_); + std::string newHash = tokenHashFunction()->compute(newToken, std::string()); + int validity = user.updateAuthToken(hash, newHash); + + if (validity < 0) { + /* + * Old API, this is bad since we always extend the lifetime of the + * token. + */ + user.removeAuthToken(hash); + newToken = createAuthToken(user); + validity = authTokenValidity_ * 60; + } if (t.get()) t->commit(); - return AuthTokenResult(AuthTokenResult::Valid, user, newToken); + return AuthTokenResult(AuthTokenResult::Valid, user, newToken, validity); } else { if (t.get()) t->commit(); diff --git a/src/Wt/Auth/AuthWidget.C b/src/Wt/Auth/AuthWidget.C index b9d3386f2c..3824c41bac 100644 --- a/src/Wt/Auth/AuthWidget.C +++ b/src/Wt/Auth/AuthWidget.C @@ -216,7 +216,7 @@ WDialog *AuthWidget::createPasswordPromptDialog(Login& login) void AuthWidget::logout() { - login_.logout(); + model_->logout(login_); } void AuthWidget::displayError(const WString& m) diff --git a/src/Wt/Auth/Dbo/AuthInfo b/src/Wt/Auth/Dbo/AuthInfo index 280bb1bb3e..5c819a9adc 100644 --- a/src/Wt/Auth/Dbo/AuthInfo +++ b/src/Wt/Auth/Dbo/AuthInfo @@ -310,6 +310,10 @@ public: */ const std::string& value() const { return value_; } + /*! \brief Sets the token value. + */ + void setValue(const std::string& value) { value_ = value; } + /*! \brief Returns the token expiry date. */ const Wt::WDateTime& expires() const { return expires_; } diff --git a/src/Wt/Auth/Dbo/UserDatabase b/src/Wt/Auth/Dbo/UserDatabase index 1c6bede4f5..2af4b39fd7 100644 --- a/src/Wt/Auth/Dbo/UserDatabase +++ b/src/Wt/Auth/Dbo/UserDatabase @@ -293,6 +293,22 @@ public: } } + virtual int updateAuthToken(const User& user, const std::string& hash, + const std::string& newHash) { + WithUser find(*this, user); + + for (typename AuthTokens::const_iterator i = user_->authTokens().begin(); + i != user_->authTokens().end(); ++i) { + Wt::Dbo::ptr t = *i; + if (t->value() == hash) { + t.modify()->setValue(newHash); + return std::max(0, WDateTime::currentDateTime().secsTo(t->expires())); + } + } + + return 0; + } + virtual User findWithAuthToken(const std::string& hash) const { Wt::Dbo::Transaction t(session_); setUser(session_.query< Wt::Dbo::ptr > diff --git a/src/Wt/Auth/User b/src/Wt/Auth/User index 0b1987c348..0c22615711 100644 --- a/src/Wt/Auth/User +++ b/src/Wt/Auth/User @@ -214,6 +214,13 @@ public: */ void removeAuthToken(const std::string& hash) const; + /*! \brief Updates an authentication token. + * + * \sa AbstractUserDatabase::updateAuthToken() + */ + int updateAuthToken(const std::string& hash, const std::string& newHash) + const; + /*! \brief Logs the result of an authentication attempt. * * This changes the number of failed login attempts, and stores the diff --git a/src/Wt/Auth/User.C b/src/Wt/Auth/User.C index 90540b0016..ccd17156e2 100644 --- a/src/Wt/Auth/User.C +++ b/src/Wt/Auth/User.C @@ -143,6 +143,14 @@ void User::removeAuthToken(const std::string& token) const db_->removeAuthToken(*this, token); } +int User::updateAuthToken(const std::string& hash, + const std::string& newHash) const +{ + checkValid(); + + return db_->updateAuthToken(*this, hash, newHash); +} + int User::failedLoginAttempts() const { return db_->failedLoginAttempts(*this); diff --git a/src/Wt/Dbo/backend/Sqlite3.C b/src/Wt/Dbo/backend/Sqlite3.C index d3020b5c41..3467ffeb7a 100644 --- a/src/Wt/Dbo/backend/Sqlite3.C +++ b/src/Wt/Dbo/backend/Sqlite3.C @@ -358,6 +358,8 @@ public: { Sqlite3::DateTimeStorage storageType = db_.dateTimeStorage(type); + *value = boost::posix_time::not_a_date_time; + switch (storageType) { case Sqlite3::ISO8601AsText: case Sqlite3::PseudoISO8601AsText: { diff --git a/src/Wt/Http/Client b/src/Wt/Http/Client index be7de6723d..9c3973471b 100644 --- a/src/Wt/Http/Client +++ b/src/Wt/Http/Client @@ -90,6 +90,14 @@ class WIOService; * context of the application that created the client. WServer::post() * is used for this. * + *

Basic acces authentication

+ * When you want to add authentication information in the %URL, this can be done + * as https://username:password@www.example.com/. When doing this, + * make sure that the username and password string are URL-encoded + * (\ref Wt::Utils::urlEncode). For example, + * https://username:pass word@www.example.com/ should be passed as + * https://username:pass%20word@www.example.com/. + * * \ingroup http */ class WT_API Client : public WObject @@ -305,6 +313,8 @@ public: struct URL { //! The protocol (e.g. "http") std::string protocol; + //! Authentication + std::string auth; //! The host (e.g. "www.webtoolkit.eu") std::string host; //! The port number (e.g. 80) diff --git a/src/Wt/Http/Client.C b/src/Wt/Http/Client.C index 829bf87a4d..1eedc4db05 100644 --- a/src/Wt/Http/Client.C +++ b/src/Wt/Http/Client.C @@ -13,6 +13,7 @@ #include "Wt/WEnvironment" #include "Wt/WLogger" #include "Wt/WServer" +#include "Wt/Utils" #include #include @@ -63,12 +64,18 @@ public: maximumResponseSize_ = bytes; } - void request(const std::string& method, const std::string& server, int port, - const std::string& path, const Message& message) + void request(const std::string& method, const std::string& auth, + const std::string& server, int port, const std::string& path, + const Message& message) { std::ostream request_stream(&requestBuf_); request_stream << method << " " << path << " HTTP/1.0\r\n"; - request_stream << "Host: " << server << "\r\n"; + request_stream << "Host: " << server << ":" + << boost::lexical_cast(port) << "\r\n"; + + if (!auth.empty()) + request_stream << "Authorization: Basic " + << Wt::Utils::base64Encode(auth) << "\r\n"; bool haveContentLength = false; for (unsigned i = 0; i < message.headers().size(); ++i) { @@ -634,6 +641,7 @@ bool Client::request(Http::Method method, const std::string& url, LOG_DEBUG(methodNames_[method] << " " << url); impl_->request(methodNames_[method], + parsedUrl.auth, parsedUrl.host, parsedUrl.port, parsedUrl.path, @@ -657,6 +665,15 @@ bool Client::parseUrl(const std::string &url, URL &parsedUrl) parsedUrl.protocol = url.substr(0, i); std::string rest = url.substr(i + 3); + // find auth + std::size_t l = rest.find('@'); + if (l != std::string::npos) { + parsedUrl.auth = rest.substr(0, l); + parsedUrl.auth = Wt::Utils::urlDecode(parsedUrl.auth); + rest = rest.substr(l+1); + } + + // find host std::size_t j = rest.find('/'); if (j == std::string::npos) diff --git a/src/Wt/Json/Serializer b/src/Wt/Json/Serializer new file mode 100644 index 0000000000..506f46f8b3 --- /dev/null +++ b/src/Wt/Json/Serializer @@ -0,0 +1,39 @@ +// This may look like C code, but it's really -*- C++ -*- +/* + * Copyright (C) 2013 Emweb bvba, Kessel-Lo, Belgium. + * + * See the LICENSE file for terms of use. + */ +#ifndef WT_JSON_SERIALIZER_H +#define WT_JSON_SERIALIZER_H + +#include + +namespace Wt { + namespace Json { + +class Object; +class Array; + +/*! \brief Serialization function for a Json::Object. + * + * Serializes a Json::Object into a string. All unicode in the object is + * UTF-8 encoded in the output. The output is indented to make it readable. + * The indentation argument to this function is used in recursive call and + * should not be set. + */ +std::string serialize(const Object& obj, int indentation = 1); + +/*! \brief Serialization function for a Json::Array. + * + * Serializes a Json::Array into a string. All unicode in the object is + * UTF-8 encoded in the output. The output is indented to make it readable. + * The indentation argument to this function is used in recursive call and + * should not be set. + */ +std::string serialize(const Array& arr, int indentation = 1); + + } +} + +#endif diff --git a/src/Wt/Json/Serializer.C b/src/Wt/Json/Serializer.C new file mode 100644 index 0000000000..9e2710e5f0 --- /dev/null +++ b/src/Wt/Json/Serializer.C @@ -0,0 +1,121 @@ +#include "Wt/Json/Serializer" + +#include "Wt/Json/Object" +#include "Wt/Json/Array" +#include "Wt/Json/Value" + +#include +#include "boost/lexical_cast.hpp" + +namespace Wt { + namespace Json { + +std::string serialize(const Object& obj, int indentation) +{ + std::string result("{\n"); + std::set< std::string > keys = obj.names(); + + for (std::set::iterator it = keys.begin(); it != keys.end(); + it++) { + // indent values + for (int i=0; i(v_) == + boost::any_cast(other.v_); + else if (typeid(v_) == typeid(Json::Array)) + return boost::any_cast(v_) == + boost::any_cast(other.v_); + else if (typeid(v_) == typeid(bool)) + return boost::any_cast(v_) == boost::any_cast(other.v_); + else if (typeid(v_) == typeid(int)) + return boost::any_cast(v_) == boost::any_cast(other.v_); + else if (typeid(v_) == typeid(long long)) + return boost::any_cast(v_) == + boost::any_cast(other.v_); + else if (typeid(v_) == typeid(double)) + return boost::any_cast(v_) == boost::any_cast(other.v_); + else + throw WException("Value::operator== : unknown value type\n"); } bool Value::operator!= (const Value& other) const { - return v_ != other.v_; + return !(*this == other); } -*/ Type Value::type() const { diff --git a/src/Wt/Mail/Client.C b/src/Wt/Mail/Client.C index 613237c8cd..0acabe3a4d 100644 --- a/src/Wt/Mail/Client.C +++ b/src/Wt/Mail/Client.C @@ -78,13 +78,15 @@ public: ~Impl() { - try { - send("QUIT\r\n"); - failIfReplyCodeNot(Bye); - } catch (std::exception& e) { - socket_.close(); - LOG_ERROR(e.what()); - return; + if (good()) { + try { + send("QUIT\r\n"); + failIfReplyCodeNot(Bye); + } catch (std::exception& e) { + socket_.close(); + LOG_ERROR(e.what()); + return; + } } } diff --git a/src/Wt/Mail/Message b/src/Wt/Mail/Message index ae39fd9c69..40c44c5ada 100644 --- a/src/Wt/Mail/Message +++ b/src/Wt/Mail/Message @@ -7,6 +7,7 @@ #ifndef WT_MAIL_MESSAGE_H_ #define WT_MAIL_MESSAGE_H_ +#include #include namespace Wt { @@ -128,7 +129,17 @@ public: * * \sa setSubject() */ - const WString& subject() const; + const WString& subject() const { return subject_; } + + /*! \brief Sets a date. + * + * According to RFC 2822, the date should express local time. + */ + void setDate(const WLocalDateTime& date); + + /*! \brief Returns the date. + */ + WLocalDateTime date() const { return date_; } /*! \brief Sets the plain text body. * @@ -212,6 +223,7 @@ private: std::vector attachments_; WString subject_, body_, htmlBody_; + WLocalDateTime date_; static std::string generateBoundary(); static void encodeAttachment(const Attachment& attachment, diff --git a/src/Wt/Mail/Message.C b/src/Wt/Mail/Message.C index ee76289182..96aff38ab6 100644 --- a/src/Wt/Mail/Message.C +++ b/src/Wt/Mail/Message.C @@ -110,6 +110,11 @@ void Message::setHeader(const std::string& name, const std::string& value) addHeader(name, value); } +void Message::setDate(const Wt::WLocalDateTime& date) +{ + date_ = date; +} + void Message::addHeader(const std::string& name, const std::string& value) { headers_.push_back(Header(name, value)); @@ -173,6 +178,9 @@ void Message::write(std::ostream& out) const from_.write("From", out); + if (!date_.isNull()) + out << "Date: " << date_.toString("ddd, dd MMM yyyy HH:mm:ss Z") << "\r\n"; + if (!replyTo_.empty()) replyTo_.write("Reply-To", out); diff --git a/src/Wt/Render/Block.C b/src/Wt/Render/Block.C index 1eb8eab757..8d7c86194b 100644 --- a/src/Wt/Render/Block.C +++ b/src/Wt/Render/Block.C @@ -4,7 +4,7 @@ * See the LICENSE file for terms of use. */ -// #define DEBUG_LAYOUT +//#define DEBUG_LAYOUT #include "Wt/WFontMetrics" #include "Wt/WLogger" @@ -149,6 +149,7 @@ void Block::setStyleSheet(StyleSheet* styleSheet) void Block::determineDisplay() { std::string fl = cssProperty(PropertyStyleFloat); + if (!fl.empty()) { if (fl == "left") float_ = Left; @@ -157,7 +158,7 @@ void Block::determineDisplay() else { unsupportedCssValue(PropertyStyleFloat, fl); } - } else if (type_ == DomElement_IMG || type_ == DomElement_TABLE) { + } else if (type_ == DomElement_IMG || isTable()) { std::string align = attributeValue("align"); if (!align.empty()) { if (align == "left") @@ -242,6 +243,18 @@ void Block::determineDisplay() } else inline_ = false; } + + if (type_ == DomElement_TABLE) { + std::vector rowSpan; + + int row = numberTableCells(0, rowSpan); + int maxRowSpan = 0; + for (unsigned i = 0; i < rowSpan.size(); ++i) + maxRowSpan = std::max(maxRowSpan, rowSpan[i]); + + tableRowCount_ = row + maxRowSpan; + tableColCount_ = rowSpan.size(); + } } /* @@ -273,6 +286,54 @@ bool Block::normalizeWhitespace(bool haveWhitespace, return haveWhitespace; } +int Block::numberTableCells(int row, std::vector& rowSpan) +{ + if ( type_ == DomElement_TABLE + || type_ == DomElement_TBODY + || type_ == DomElement_THEAD + || type_ == DomElement_TFOOT) { + for (unsigned i = 0; i < children_.size(); ++i) { + Block *c = children_[i]; + + row = c->numberTableCells(row, rowSpan); + } + } else if (type_ == DomElement_TR) { + int col = 0; + + for (unsigned i = 0; i < children_.size(); ++i) { + Block *c = children_[i]; + + if (c->isTableCell()) { + /* skip overspanned cells by previous rows */ + while (col < (int)rowSpan.size() && rowSpan[col] > 0) + ++col; + + c->cellCol_ = col; + c->cellRow_ = row; + + int rs = c->attributeValue("rowspan", 1); + int cs = c->attributeValue("colspan", 1); + + while ((int)rowSpan.size() <= col + cs - 1) + rowSpan.push_back(1); + + for (int k = 0; k < cs; ++k) + rowSpan[col + k] = rs; + + col += cs; + } + } + + for (unsigned i = 0; i < rowSpan.size(); ++i) + if (rowSpan[i] > 0) + rowSpan[i] = rowSpan[i] - 1; + + ++row; + } + + return row; +} + bool Block::inlineChildren() const { if (inline_) @@ -352,7 +413,7 @@ double Block::cssPadding(Side side, double fontScale) const if (!result.defined) { if (isTableCell()) - return 4; + return 1; else if ((type_ == DomElement_UL || type_ == DomElement_OL) && side == Left) return 40; } @@ -414,7 +475,213 @@ bool Block::isInside(DomElementType type) const return false; } +double Block::cssBorderSpacing(double fontScale) const +{ + if (tableCollapseBorders()) + return 0; + + std::string spacingStr = cssProperty(PropertyStyleBorderSpacing); + + if (!spacingStr.empty()) { + WLength l(spacingStr.c_str()); + return l.toPixels(cssFontSize(fontScale)); + } else + return attributeValue("cellspacing", 2); +} + +Block *Block::table() const +{ + Block *result = parent_; + while (result && !result->isTable()) + result = result->parent_; + return result; +} + +bool Block::tableCollapseBorders() const +{ + return cssProperty(PropertyStyleBorderCollapse) == "collapse"; +} + double Block::cssBorderWidth(Side side, double fontScale) const +{ + if (isTableCell()) { + Block *t = table(); + if (t && t->tableCollapseBorders()) + return collapsedBorderWidth(side, fontScale); + else + return rawCssBorderWidth(side, fontScale); + } else if (isTable() && tableCollapseBorders()) + return collapsedBorderWidth(side, fontScale); + else + return rawCssBorderWidth(side, fontScale); +} + +Block *Block::findTableCell(int row, int col) const +{ + if ( type_ == DomElement_TABLE + || type_ == DomElement_TBODY + || type_ == DomElement_THEAD + || type_ == DomElement_TFOOT) { + for (unsigned i = 0; i < children_.size(); ++i) { + Block *c = children_[i]; + + Block *result = c->findTableCell(row, col); + if (result) + return result; + } + + return 0; + } else if (type_ == DomElement_TR) { + for (unsigned i = 0; i < children_.size(); ++i) { + Block *c = children_[i]; + + if (c->isTableCell()) { + int rs = c->attributeValue("rowspan", 1); + int cs = c->attributeValue("colspan", 1); + + if (row >= c->cellRow_ && row < c->cellRow_ + rs && + col >= c->cellCol_ && col < c->cellCol_ + cs) + return c; + } + } + + return 0; + } + + return 0; +} + +Block *Block::siblingTableCell(Side side) const +{ + Block *t = table(); + + switch (side) { + case Left: + if (cellCol_ == 0) + return 0; + else + return t->findTableCell(cellRow_, cellCol_ - 1); + case Right: { + int nextCol = cellCol_ + attributeValue("colspan", 1); + if (nextCol >= t->tableColCount_) + return 0; + else + return t->findTableCell(cellRow_, nextCol); + } + case Top: + if (cellRow_ == 0) + return 0; + else + return t->findTableCell(cellRow_ - 1, cellCol_); + case Bottom: { + int nextRow = cellRow_ + attributeValue("rowspan", 1); + if (nextRow >= t->tableRowCount_) + return 0; + else + return t->findTableCell(nextRow, cellCol_); + } + default: + break; + } + + return 0; +} + +Block::BorderElement Block::collapseCellBorders(Side side) const +{ + std::vector elements; + elements.reserve(2); + + Block *s = siblingTableCell(side); + Block *t = table(); + + switch (side) { + case Left: + if (s) { + elements.push_back(BorderElement(s, Right)); + elements.push_back(BorderElement(this, Left)); + } else { + elements.push_back(BorderElement(this, Left)); + elements.push_back(BorderElement(t, Left)); + } + + break; + case Top: + if (s) { + elements.push_back(BorderElement(s, Bottom)); + elements.push_back(BorderElement(this, Top)); + } else { + elements.push_back(BorderElement(this, Top)); + elements.push_back(BorderElement(t, Top)); + } + + break; + case Right: + elements.push_back(BorderElement(this, Right)); + if (s) + elements.push_back(BorderElement(s, Left)); + else + elements.push_back(BorderElement(t, Right)); + + break; + case Bottom: + elements.push_back(BorderElement(this, Bottom)); + if (s) + elements.push_back(BorderElement(s, Top)); + else + elements.push_back(BorderElement(t, Bottom)); + default: + break; + } + + double borderWidth = 0; + BorderElement result; + + for (unsigned i = 0; i < elements.size(); ++i) { + double c = elements[i].block->rawCssBorderWidth(elements[i].side, 1, true); + if (c == -1) + return elements[i]; + + if (c > borderWidth) { + result = elements[i]; + borderWidth = std::max(borderWidth, c); + } + } + + if (!result.block) { + /* + Disabled because I can't understand when a browser does this or not, + and tinyMCE annoyingly alwasy puts this border attribute. + + bool tableDefault = t->attributeValue("border", 0) > 0; + if (tableDefault) + return BorderElement(t, side); + else + + */ + return elements[0]; + } else + return result; +} + +double Block::collapsedBorderWidth(Side side, double fontScale) const +{ + /* 17.6.2.1 border conflict resolution */ + assert (isTable() || isTableCell()); + + if (isTable()) { + // we should examine all bordering cells and take the widest to + // accurately reflect the table width/height, but let's not bother (yet) + + return 0; + } + + BorderElement be = collapseCellBorders(side); + return be.block->rawCssBorderWidth(be.side, fontScale); +} + +double Block::rawCssBorderWidth(Side side, double fontScale, + bool indicateHidden) const { if (!node_) return 0; @@ -429,23 +696,29 @@ double Block::cssBorderWidth(Side side, double fontScale) const std::vector values; boost::split(values, borderStr, boost::is_any_of(" ")); + if (values.size() > 1 && values[1] == "hidden") { + if (indicateHidden) + return -1; + else + return 0; + } + WLength l(values[0].c_str()); result = l.toPixels(cssFontSize(fontScale)); } if (result == 0) { - if (type_ == DomElement_TABLE) { - result = attributeValue("border", 0); + if (isTable()) { + result = attributeValue("border", 0) ? 1 : 0; } else if (isTableCell()) { /* - * If the table has a border, then we have a default border of 1px + * If the table has a 'border' itself, then we have a default + * border of 1px (note Firefox and Chrome disagree on the color + * of that border) */ - Block *table = parent_; - while (table && table->type_ != DomElement_TABLE) - table = table->parent_; - - if (table) - result = table->attributeValue("border", 0) ? 1 : 0; + Block *t = table(); + if (t && !t->tableCollapseBorders()) + result = t->attributeValue("border", 0) ? 1 : 0; } } @@ -453,6 +726,34 @@ double Block::cssBorderWidth(Side side, double fontScale) const } WColor Block::cssBorderColor(Side side) const +{ + if (isTableCell()) { + Block *t = table(); + if (t && t->tableCollapseBorders()) + return collapsedBorderColor(side); + else + return rawCssBorderColor(side); + } else if (isTable() && tableCollapseBorders()) + return collapsedBorderColor(side); + else + return rawCssBorderColor(side); +} + +WColor Block::collapsedBorderColor(Side side) const +{ + /* 17.6.2.1 border conflict resolution */ + assert (isTable() || isTableCell()); + + if (isTable()) { + /* do not actually render the border around the table */ + return WColor(); + } + + BorderElement be = collapseCellBorders(side); + return be.block->rawCssBorderColor(be.side); +} + +WColor Block::rawCssBorderColor(Side side) const { int index = sideToIndex(side); Property property = (Property)(PropertyStyleBorderTop + index); @@ -974,19 +1275,26 @@ void Block::layoutTable(PageState &ps, std::vector minimumColumnWidths; std::vector maximumColumnWidths; + std::vector setColumnWidths; for (unsigned i = 0; i < children_.size(); ++i) { Block *c = children_[i]; - c->tableComputeColumnWidths(minimumColumnWidths, maximumColumnWidths, - renderer, this); + setColumnWidths, renderer, this); } currentWidth_ = 0; unsigned colCount = minimumColumnWidths.size(); - int cellSpacing = attributeValue("cellspacing", 2); + for (unsigned i = 0; i < colCount; ++i) { + if (setColumnWidths[i] >= 0) { + setColumnWidths[i] = std::max(setColumnWidths[i], minimumColumnWidths[i]); + maximumColumnWidths[i] = minimumColumnWidths[i] = setColumnWidths[i]; + } + } + + double cellSpacing = cssBorderSpacing(renderer.fontScale()); double totalSpacing = (colCount + 1) * cellSpacing; double totalMinWidth = sum(minimumColumnWidths) + totalSpacing; double totalMaxWidth = sum(maximumColumnWidths) + totalSpacing; @@ -1044,17 +1352,37 @@ void Block::layoutTable(PageState &ps, if (width > totalMaxWidth) { widths = maximumColumnWidths; - double factor = width / totalMaxWidth; - for (unsigned i = 0; i < widths.size(); ++i) - widths[i] *= factor; + double rWidth = width - totalSpacing; + double rTotalMaxWidth = totalMaxWidth - totalSpacing; + for (unsigned i = 0; i < colCount; ++i) { + if (setColumnWidths[i] >= 0) { + rWidth -= widths[i]; + rTotalMaxWidth -= widths[i]; + } + } + + if (rTotalMaxWidth <= 0) { + rWidth = width - totalSpacing; + rTotalMaxWidth = totalMaxWidth - totalSpacing; + + for (unsigned i = 0; i < setColumnWidths[i]; ++i) + setColumnWidths[i] = -1.0; + } + + double factor = rWidth / rTotalMaxWidth; + + for (unsigned i = 0; i < widths.size(); ++i) { + if (setColumnWidths[i] == -1) + widths[i] *= factor; + } } else if (width > totalMinWidth) { double totalStretch = 0; for (unsigned i = 0; i < widths.size(); ++i) totalStretch += maximumColumnWidths[i] - minimumColumnWidths[i]; - double room = width - totalMinWidth; + double room = width - totalStretch - totalMinWidth; double factor = room / totalStretch; for (unsigned i = 0; i < widths.size(); ++i) { @@ -1103,7 +1431,7 @@ void Block::layoutTable(PageState &ps, ps.y += cellSpacing; } -void Block::tableDoLayout(double x, PageState &ps, int cellSpacing, +void Block::tableDoLayout(double x, PageState &ps, double cellSpacing, const std::vector& widths, bool protectRows, Block *repeatHead, const WTextRenderer& renderer) @@ -1176,7 +1504,7 @@ void Block::tableDoLayout(double x, PageState &ps, int cellSpacing, } void Block::tableRowDoLayout(double x, PageState &ps, - int cellSpacing, + double cellSpacing, const std::vector& widths, const WTextRenderer& renderer, double rowHeight) @@ -1213,6 +1541,8 @@ void Block::tableRowDoLayout(double x, PageState &ps, double collapseMarginBottom = 0; double collapseMarginTop = std::numeric_limits::max(); + std::string s = c->cssProperty(PropertyStyleBackgroundColor); + collapseMarginBottom = c->layoutBlock(cellPs, false, renderer, collapseMarginTop, collapseMarginBottom, @@ -1242,6 +1572,7 @@ void Block::tableRowDoLayout(double x, PageState &ps, void Block::tableComputeColumnWidths(std::vector& minima, std::vector& maxima, + std::vector& asSet, const WTextRenderer& renderer, Block *table) { @@ -1255,7 +1586,7 @@ void Block::tableComputeColumnWidths(std::vector& minima, for (unsigned i = 0; i < children_.size(); ++i) { Block *c = children_[i]; - c->tableComputeColumnWidths(minima, maxima, renderer, table); + c->tableComputeColumnWidths(minima, maxima, asSet, renderer, table); } } else if (type_ == DomElement_TR) { int col = 0; @@ -1264,8 +1595,10 @@ void Block::tableComputeColumnWidths(std::vector& minima, Block *c = children_[i]; if (c->isTableCell()) { - c->cellComputeColumnWidths(col, false, minima, renderer, table); - col = c->cellComputeColumnWidths(col, true, maxima, renderer, table); + c->cellComputeColumnWidths(col, AsSetWidth, asSet, renderer, table); + c->cellComputeColumnWidths(col, MinimumWidth, minima, renderer, table); + col = c->cellComputeColumnWidths(col, MaximumWidth, maxima, renderer, + table); } } } @@ -1280,7 +1613,7 @@ int Block::attributeValue(const char *attribute, int defaultValue) const return defaultValue; } -int Block::cellComputeColumnWidths(int col, bool maximum, +int Block::cellComputeColumnWidths(int col, WidthType type, std::vector& values, const WTextRenderer& renderer, Block *table) @@ -1289,36 +1622,48 @@ int Block::cellComputeColumnWidths(int col, bool maximum, int colSpan = attributeValue("colspan", 1); + double defaultWidth = 0; + if (type == AsSetWidth) + defaultWidth = -1; + while (col + colSpan > (int)values.size()) - values.push_back(0.0); + values.push_back(defaultWidth); for (int i = 0; i < colSpan; ++i) currentWidth += values[col + i]; double width = currentWidth; - PageState ps; - ps.y = 0; - ps.page = 0; - ps.minX = 0; - ps.maxX = width; + switch (type) { + case AsSetWidth: + width = cssWidth(renderer.fontScale()); + break; + case MinimumWidth: + case MaximumWidth: + { + PageState ps; + ps.y = 0; + ps.page = 0; + ps.minX = 0; + ps.maxX = width; - double origTableWidth = table->currentWidth_; - if (!maximum) - table->currentWidth_ = 0; + double origTableWidth = table->currentWidth_; + if (type == MinimumWidth) + table->currentWidth_ = 0; - layoutBlock(ps, maximum, renderer, 0, 0); + layoutBlock(ps, type == MaximumWidth, renderer, 0, 0); - table->currentWidth_ = origTableWidth; + table->currentWidth_ = origTableWidth; - width = ps.maxX; + width = ps.maxX; + } + } if (width > currentWidth) { double extraPerColumn = (width - currentWidth) / colSpan; - for (int i = 0; i < colSpan; ++i) { + for (int i = 0; i < colSpan; ++i) values[col + i] += extraPerColumn; - } } return col + colSpan; @@ -1595,7 +1940,7 @@ double Block::layoutBlock(PageState &ps, double cssSetWidth = cssWidth(renderer.fontScale()); - if (type_ == DomElement_TABLE) { + if (isTable()) { /* * A table will apply the width to it's border box, unlike a block level * element which applies the width to it's padding box ! @@ -1679,10 +2024,20 @@ double Block::layoutBlock(PageState &ps, // FIXME this should be reviewed for margin/border implications ps.maxX = std::max(ps.minX + width, ps.maxX); } else { + double borderFactor = 1.0; + + if (isTableCell()) { + Block *t = table(); + if (t && t->tableCollapseBorders()) + borderFactor = 0.5; + } + double cMinX = ps.minX + cssPadding(Left, renderer.fontScale()) - + cssBorderWidth(Left, renderer.fontScale()); + + borderFactor * cssBorderWidth(Left, renderer.fontScale()); double cMaxX = ps.maxX - cssPadding(Right, renderer.fontScale()) - - cssBorderWidth(Right, renderer.fontScale()); + - borderFactor * cssBorderWidth(Right, renderer.fontScale()); + + cMaxX = std::max(cMaxX, cMinX); currentWidth_ = cMaxX - cMinX; @@ -1768,7 +2123,7 @@ double Block::layoutBlock(PageState &ps, } ps.maxX = cMaxX + cssPadding(Right, renderer.fontScale()) - + cssBorderWidth(Right, renderer.fontScale()); + + borderFactor * cssBorderWidth(Right, renderer.fontScale()); advance(ps, spacerBottom, renderer); @@ -1800,8 +2155,8 @@ double Block::layoutBlock(PageState &ps, advance(ps, height, renderer); - if (type_ == DomElement_TABLE) { - // A table will overflow if necessary + if (isTable() || isTableCell()) { + // A table or table cell will overflow if necessary if (prevPage > ps.page || (prevPage == ps.page && prevY > ps.y)) { ps.page = prevPage; @@ -2369,7 +2724,7 @@ AlignmentFlag Block::cssTextAlign() const if (node_ && !isInline()) { std::string s = cssProperty(PropertyStyleTextAlign); - if (s.empty() && type_ != DomElement_TABLE) + if (s.empty() && !isTable()) s = attributeValue("align"); if (s.empty() || s == "inherit") { @@ -2752,6 +3107,29 @@ LayoutBox Block::toBorderBox(const LayoutBox& bb, double fontScale) const return result; } +double Block::maxBorderWidth(Block *b1, Side s1, + Block *b2, Side s2, + Block *b3, Side s3, + Block *b4, Side s4, + double fontScale) +{ + double result = 0; + + if (b1) + result = std::max(result, b1->collapsedBorderWidth(s1, fontScale)); + + if (b2) + result = std::max(result, b2->collapsedBorderWidth(s2, fontScale)); + + if (b3) + result = std::max(result, b3->collapsedBorderWidth(s3, fontScale)); + + if (b4) + result = std::max(result, b4->collapsedBorderWidth(s4, fontScale)); + + return result; +} + void Block::renderBorders(const LayoutBox& bb, WTextRenderer& renderer, WPainter &painter, WFlags verticals) { @@ -2772,41 +3150,142 @@ void Block::renderBorders(const LayoutBox& bb, WTextRenderer& renderer, borderColor[i] = cssBorderColor(sides[i]); } - WPen borderPen; - borderPen.setCapStyle(FlatCap); + double offsetFactor = 1; + + /* + * For a table-collapsed cell, do not add the borderWidth offsets + */ + if (isTableCell()) { + Block *t = table(); + if (t && t->tableCollapseBorders()) + offsetFactor = 0; + } + + double cornerMaxWidth[4] = { 0, 0, 0, 0 }; + + if (offsetFactor == 0) { + Block *siblings[4]; + for (unsigned i = 0; i < 4; ++i) + siblings[i] = siblingTableCell(sides[i]); + + cornerMaxWidth[TopLeft] = maxBorderWidth(siblings[3], Top, + this, Top, + siblings[0], Left, + this, Left, + renderer.fontScale()); + + cornerMaxWidth[TopRight] = maxBorderWidth(siblings[1], Top, + this, Top, + siblings[0], Right, + this, Right, + renderer.fontScale()); + + cornerMaxWidth[BottomLeft] = maxBorderWidth(siblings[3], Bottom, + this, Bottom, + siblings[2], Left, + this, Left, + renderer.fontScale()); + + cornerMaxWidth[BottomRight] = maxBorderWidth(siblings[1], Bottom, + this, Bottom, + siblings[2], Right, + this, Right, + renderer.fontScale()); + } for (unsigned i = 0; i < 4; ++i) { if (borderWidth[i] != 0) { + WPen borderPen; + borderPen.setCapStyle(FlatCap); borderPen.setWidth(borderWidth[i]); borderPen.setColor(borderColor[i]); painter.setPen(borderPen); switch (sides[i]) { case Top: - if (verticals & Top) - painter.drawLine(left, - top + borderWidth[0]/2, - right, - top + borderWidth[0]/2); + if (verticals & Top) { + double leftOffset = 0, rightOffset = 0; + + if (borderWidth[i] < cornerMaxWidth[TopLeft]) + leftOffset = cornerMaxWidth[TopLeft] / 2; + else if (offsetFactor == 0) + leftOffset = -borderWidth[i] / 2; + + if (borderWidth[i] < cornerMaxWidth[TopRight]) + rightOffset = cornerMaxWidth[TopRight] / 2; + else if (offsetFactor == 0) + rightOffset = -borderWidth[i] / 2; + + painter.drawLine(left + leftOffset, + top + offsetFactor * borderWidth[i]/2, + right - rightOffset, + top + offsetFactor * borderWidth[i]/2); + + } + break; case Right: - painter.drawLine(right - borderWidth[1]/2, - top, - right - borderWidth[1]/2, - bottom); + { + double topOffset = 0, bottomOffset = 0; + + if (borderWidth[i] < cornerMaxWidth[TopRight]) + topOffset = cornerMaxWidth[TopRight] / 2; + else if (offsetFactor == 0) + topOffset = -borderWidth[i] / 2; + + if (borderWidth[i] < cornerMaxWidth[BottomRight]) + bottomOffset = cornerMaxWidth[BottomRight] / 2; + else if (offsetFactor == 0) + bottomOffset = -borderWidth[i] / 2; + + painter.drawLine(right - offsetFactor * borderWidth[i]/2, + top + topOffset, + right - offsetFactor * borderWidth[i]/2, + bottom - bottomOffset); + } + break; case Bottom: - if (verticals & Bottom) - painter.drawLine(left, - bottom - borderWidth[2]/2, - right, - bottom - borderWidth[2]/2); + if (verticals & Bottom) { + double leftOffset = 0, rightOffset = 0; + + if (borderWidth[i] < cornerMaxWidth[BottomLeft]) + leftOffset = cornerMaxWidth[BottomLeft] / 2; + else if (offsetFactor == 0) + leftOffset = -borderWidth[i] / 2; + + if (borderWidth[i] < cornerMaxWidth[TopRight]) + rightOffset = cornerMaxWidth[BottomRight] / 2; + else if (offsetFactor == 0) + rightOffset = -borderWidth[i] / 2; + + painter.drawLine(left + leftOffset, + bottom - offsetFactor * borderWidth[i]/2, + right - rightOffset, + bottom - offsetFactor * borderWidth[i]/2); + } + break; case Left: - painter.drawLine(left + borderWidth[3]/2, - top, - left + borderWidth[3]/2, - bottom); + { + double topOffset = 0, bottomOffset = 0; + + if (borderWidth[i] < cornerMaxWidth[TopLeft]) + topOffset = cornerMaxWidth[TopLeft] / 2; + else if (offsetFactor == 0) + topOffset = -borderWidth[i] / 2; + + if (borderWidth[i] < cornerMaxWidth[BottomLeft]) + bottomOffset = cornerMaxWidth[BottomLeft] / 2; + else if (offsetFactor == 0) + bottomOffset = -borderWidth[i] / 2; + + painter.drawLine(left + offsetFactor * borderWidth[i]/2, + top + topOffset, + left + offsetFactor * borderWidth[i]/2, + bottom - bottomOffset); + } + break; default: break; @@ -2889,12 +3368,10 @@ void Block::fillinStyle(const std::string& style, if (count == 0) { LOG_ERROR("Strange aggregate CSS length property: '" << v << "'"); } else if (count == 1) { - std::string v0 = Wt::Utils::splitEntryToString(allvalues[0]); - - updateAggregateProperty(n, "-top", specificity, v0); - updateAggregateProperty(n, "-right", specificity, v0); - updateAggregateProperty(n, "-bottom", specificity, v0); - updateAggregateProperty(n, "-left", specificity, v0); + updateAggregateProperty(n, "-top", specificity, v); + updateAggregateProperty(n, "-right", specificity, v); + updateAggregateProperty(n, "-bottom", specificity, v); + updateAggregateProperty(n, "-left", specificity, v); } else if (count == 2) { std::string v1 = Wt::Utils::splitEntryToString(allvalues[0]); updateAggregateProperty(n, "-top", specificity, v1); diff --git a/src/Wt/Render/Block.h b/src/Wt/Render/Block.h index 858d9460c6..8d21ac340f 100644 --- a/src/Wt/Render/Block.h +++ b/src/Wt/Render/Block.h @@ -70,6 +70,15 @@ class Block AlignmentFlag verticalAlignment() const; Side floatSide() const { return float_; } + bool isTableCell() const + { return type_ == DomElement_TD || type_ == DomElement_TH; } + + bool isTable() const + { return type_ == DomElement_TABLE; } + + Block *table() const; + bool tableCollapseBorders() const; + double layoutBlock(PageState &ps, bool canIncreaseWidth, const WTextRenderer& renderer, @@ -123,6 +132,23 @@ class Block Specificity s_; }; + enum Corner { TopLeft, TopRight, BottomLeft, BottomRight }; + + enum WidthType { + AsSetWidth, + MinimumWidth, + MaximumWidth + }; + + struct BorderElement { + const Block *block; + Side side; + + BorderElement() : block(0), side(Left) { } + BorderElement(const Block *aBlock, Side aSide) + : block(aBlock), side(aSide) { } + }; + rapidxml::xml_node<> *node_; Block *parent_; BlockList offsetChildren_; @@ -137,6 +163,12 @@ class Block mutable std::map css_; StyleSheet* styleSheet_; + /* For table */ + int tableRowCount_, tableColCount_; + + /* For table cell */ + int cellRow_, cellCol_; + int attributeValue(const char *attribute, int defaultValue) const; void updateAggregateProperty(const std::string& property, @@ -152,8 +184,16 @@ class Block CssLength cssLength(Property top, Side side, double fontScale) const; double cssMargin(Side side, double fontScale) const; double cssPadding(Side side, double fontScale) const; + double cssBorderSpacing(double fontScale) const; + double cssBorderWidth(Side side, double fontScale) const; + double collapsedBorderWidth(Side side, double fontScale) const; + double rawCssBorderWidth(Side side, double fontScale, + bool indicateHidden = false) const; WColor cssBorderColor(Side side) const; + WColor collapsedBorderColor(Side side) const; + WColor rawCssBorderColor(Side side) const; + WColor cssColor() const; AlignmentFlag cssTextAlign() const; double cssBoxMargin(Side side, double fontScale) const; @@ -194,20 +234,27 @@ class Block const WTextRenderer& renderer); void layoutAbsolute(const WTextRenderer& renderer); - void tableDoLayout(double x, PageState &ps, int cellSpacing, + void tableDoLayout(double x, PageState &ps, double cellSpacing, const std::vector& widths, bool protectRows, Block *repeatHead, const WTextRenderer& renderer); void tableRowDoLayout(double x, PageState &ps, - int cellSpacing, + double cellSpacing, const std::vector& widths, const WTextRenderer& renderer, double rowHeight); void tableComputeColumnWidths(std::vector& minima, std::vector& maxima, + std::vector& asSet, const WTextRenderer& renderer, Block *table); - int cellComputeColumnWidths(int col, bool maximum, + + BorderElement collapseCellBorders(Side side) const; + int numberTableCells(int row, std::vector& rowSpan); + Block *findTableCell(int row, int col) const; + Block *siblingTableCell(Side side) const; + + int cellComputeColumnWidths(int col, WidthType type, std::vector& values, const WTextRenderer& renderer, Block *table); @@ -255,8 +302,11 @@ class Block static bool isAggregate(const std::string& cssProperty); - bool isTableCell() const - { return type_ == DomElement_TD || type_ == DomElement_TH; } + static double maxBorderWidth(Block *b1, Side s1, + Block *b2, Side s2, + Block *b3, Side s3, + Block *b4, Side s4, + double fontScale); friend class Line; }; diff --git a/src/Wt/Render/CssData.C b/src/Wt/Render/CssData.C index 588608a7c4..d5b0860b8a 100644 --- a/src/Wt/Render/CssData.C +++ b/src/Wt/Render/CssData.C @@ -9,56 +9,15 @@ using namespace Wt::Render; -void Term::setUnit(Unit u) +void Wt::Render::Term::setValue(const std::string& value) { - unit_ = u; - if(u <= Ex) - type_ = Font; - else if(u <= Pc) - type_ = Length; - else if(u <= Grad) - type_ = Angle; - else if(u <= Seconds) - type_ = Time; - else if(u <= Khz) - type_ = Frequency; - else if(u <= Percentage) - type_ = OtherNumber; - else - type_ = Invalid; + value_ = value; } -void Term::setQuotedString(const std::string& s) -{ - quotedString_ = s; - type_ = QuotedString; -} - -void Term::setIdentifier(const std::string& id) -{ - identifier_ = id; - type_ = Identifier; -} - -void Term::setHash (const std::string& hash) -{ - hash_ = hash; - type_ = Hash; -} - -void Term::setUri(const std::string& uri) -{ - uri_ = uri; - type_ = Uri; -} - - /////////////////////////////////////////////////////////////////////////////// ///// isMatch ///// /////////////////////////////////////////////////////////////////////////////// - - bool Wt::Render::Match::isMatch(const Block* block, const SimpleSelector& s) { const DomElementType tag = block->type(); @@ -110,15 +69,16 @@ Specificity Wt::Render::Match::isMatch(const Block* block, const Selector& selec const Block* parent = block->parent(); for(int i = selector.size()-2; i >= 0; --i) { + bool matchFound; while(parent) { - if(isMatch(parent, selector.at(i))) + matchFound = isMatch(parent, selector.at(i)); + parent = parent->parent(); + if(matchFound) break; - else - parent = parent->parent(); } - if(!parent) + if(!matchFound && !parent) return Specificity(false); } return selector.specificity(); diff --git a/src/Wt/Render/CssData.h b/src/Wt/Render/CssData.h index 47f1598141..281352f2f9 100644 --- a/src/Wt/Render/CssData.h +++ b/src/Wt/Render/CssData.h @@ -31,35 +31,9 @@ class Selector class Term { public: - enum Type { - Font, Length, Angle, Time, Frequency, OtherNumber, - QuotedString, Identifier, Hash, Uri, Invalid }; - enum Unit { - Em, Ex, // Font - Px, Cm, Mm, In, Pt, Pc, // Length - Deg, Rad, Grad, // Angle - Ms, Seconds, // Time - Hz, Khz, // Frequency - Percentage, // OtherNumber - InvalidUnit - }; - - Term() : unit_(InvalidUnit){} - void setUnit (Unit u); - void setQuotedString(const std::string& s); - void setIdentifier (const std::string& id); - void setHash (const std::string& hash); - void setUri (const std::string& uri); - - Type type() const; - - double value_; - std::string quotedString_; - std::string identifier_; - std::string hash_; - std::string uri_; - Unit unit_; - Type type_; + Term() { } + void setValue(const std::string& s); + std::string value_; }; class DeclarationBlock diff --git a/src/Wt/Render/CssParser.C b/src/Wt/Render/CssParser.C index 570040229f..4734d1d806 100644 --- a/src/Wt/Render/CssParser.C +++ b/src/Wt/Render/CssParser.C @@ -57,12 +57,14 @@ BOOST_FUSION_ADAPT_STRUCT( (std::vector, simpleSelectors_) ) +/* BOOST_FUSION_ADAPT_STRUCT( Term, (double, value_) (Term::Unit, unit_) (Term::Type, type_) ) +*/ typedef std::map justForBoostFusion; BOOST_FUSION_ADAPT_STRUCT( @@ -144,9 +146,8 @@ class CssGrammer : qi::grammar > qi::rule simple_selector_; qi::rule - class_, HASH_, IDENT_, STRING_, nonascii_, h_, nmstart_, subset_, ws_, - element_name_, hexcolor_, escape_, nmchar_, URI_, subset1_, subset2_, - hexcolor6_, hexcolor3_; + class_, HASH_, IDENT_, STRING_, nonstring_, nonascii_, nmstart_, + element_name_, escape_, nmchar_, subset1_, subset2_; Iterator begin_; std::string error_; }; @@ -278,33 +279,10 @@ CssGrammer::CssGrammer() = lit('/') | lit(','); term_ - = (doubleWithoutExponent_ - [phoenix::bind(&Term::value_, _val) = _1] - > (-( no_case[ - lit("EM") [phoenix::bind(&Term::setUnit, _val, Term::Em)] - | lit("EX") [phoenix::bind(&Term::setUnit, _val, Term::Ex)] - | lit("PX") [phoenix::bind(&Term::setUnit, _val, Term::Px)] - | lit("CM") [phoenix::bind(&Term::setUnit, _val, Term::Cm)] - | lit("MM") [phoenix::bind(&Term::setUnit, _val, Term::Mm)] - | lit("IN") [phoenix::bind(&Term::setUnit, _val, Term::In)] - | lit("PT") [phoenix::bind(&Term::setUnit, _val, Term::Pt)] - | lit("PC") [phoenix::bind(&Term::setUnit, _val, Term::Pc)] - | lit("DEG") [phoenix::bind(&Term::setUnit, _val, Term::Deg)] - | lit("RAD") [phoenix::bind(&Term::setUnit, _val, Term::Rad)] - | lit("GRAD")[phoenix::bind(&Term::setUnit, _val, Term::Grad)] - | lit("MS") [phoenix::bind(&Term::setUnit, _val, Term::Ms)] - | lit("S") [phoenix::bind(&Term::setUnit, _val, Term::Seconds)] - | lit("HZ") [phoenix::bind(&Term::setUnit, _val, Term::Hz)] - | lit("KHZ") [phoenix::bind(&Term::setUnit, _val, Term::Khz)] - | lit("%") [phoenix::bind(&Term::setUnit, _val, Term::Percentage)] - ])) - ) // TODO: missing DIMENSION - | URI_ - | STRING_ [phoenix::bind(&Term::quotedString_, _val) = _1] - | IDENT_ [phoenix::bind(&Term::identifier_, _val) = _1] - | hexcolor_ [phoenix::bind(&Term::hash_, _val) = _1] - ; + = +(STRING_ | nonstring_) [phoenix::bind(&Term::setValue, _val, _1)]; + nonstring_ = ~char_("\"';}"); + element_name_ = IDENT_ | lit('*') @@ -315,13 +293,6 @@ CssGrammer::CssGrammer() > IDENT_ ; - hexcolor6_ = repeat(6)[char_("0-9a-fA-F")]; - hexcolor3_ = repeat(3)[char_("0-9a-fA-F")]; - - hexcolor_ - = '#' > ( hexcolor6_ - | hexcolor3_ ); - HASH_ = '#' > IDENT_ @@ -330,7 +301,7 @@ CssGrammer::CssGrammer() // TODO The following is a bit of a mess, but I couldn't get it // in 1 rule. subset1_ = ~char_("\n\r\f\\\""); - subset2_ = ~char_("\n\r\f\\\'"); + subset2_ = ~char_("\n\r\f\\'"); STRING_ = (lit('\"') > *(escape_ | subset1_ | qi::string("\\\n")) > lit('\"')) | (lit('\'') > *(escape_ | subset2_ | qi::string("\\\n")) > lit('\'')) @@ -340,10 +311,6 @@ CssGrammer::CssGrammer() = char_("\xA0-\xFF") ; - h_ - = char_("0-9a-fA-F") - ; - escape_ = char_("\\") > ~char_("\r\n\f0-9a-fA-F") @@ -367,19 +334,6 @@ CssGrammer::CssGrammer() > *nmchar_ ; - ws_ - = +char_(" \t\r\n\f"); - - URI_ - = "url(" - > -ws_ - > ( - STRING_ - | *( char_("!#$%&*-~") | nonascii_ | escape_ ) - ) - > -ws_ - > ")"; - rulesetArray_.name("stylesheet"); ruleset_.name("ruleset"); selector_.name("selector"); @@ -392,18 +346,14 @@ CssGrammer::CssGrammer() HASH_.name("HASH"); IDENT_.name("IDENT"); STRING_.name("STRING"); + nonstring_.name("nonstring"); nonascii_.name("nonascii"); - h_.name("h"); nmstart_.name("nmstart"); element_name_.name("element_name"); - hexcolor_.name("hexcolor"); escape_.name("escape"); nmchar_.name("nmchar"); - URI_.name("URI"); subset1_.name("double_quoted_string"); subset2_.name("single_quoted_string"); - hexcolor6_.name("hexcolor6"); - hexcolor3_.name("hexcolor3"); phoenix::function > error_report(this); diff --git a/src/Wt/StdGridLayoutImpl2.C b/src/Wt/StdGridLayoutImpl2.C index bf149b7511..e6cd56739e 100644 --- a/src/Wt/StdGridLayoutImpl2.C +++ b/src/Wt/StdGridLayoutImpl2.C @@ -4,8 +4,6 @@ * See the LICENSE file for terms of use. */ -#ifndef OLD_LAYOUT - #include #include @@ -37,6 +35,7 @@ StdGridLayoutImpl2::StdGridLayoutImpl2(WLayout *layout, Impl::Grid& grid) : StdLayoutImpl(layout), grid_(grid), needAdjust_(false), + needRemeasure_(false), needConfigUpdate_(false) { const char *THIS_JS = "js/StdGridLayoutImpl2.js"; @@ -77,6 +76,15 @@ bool StdGridLayoutImpl2::itemResized(WLayoutItem *item) return false; } +bool StdGridLayoutImpl2::parentResized() +{ + if (!needRemeasure_) { + needRemeasure_ = true; + return true; + } else + return false; +} + int StdGridLayoutImpl2::nextRowWithItem(int row, int c) const { for (row += grid_.items_[row][c].rowSpan_; row < (int)grid_.rows_.size(); @@ -160,6 +168,13 @@ void StdGridLayoutImpl2::updateDom(DomElement& parent) app->doJavaScript(js.str()); } + if (needRemeasure_) { + needRemeasure_ = false; + WStringStream js; + js << app->javaScriptClass() << ".layouts2.setDirty('" << id() << "');"; + app->doJavaScript(js.str()); + } + if (needAdjust_) { needAdjust_ = false; @@ -451,7 +466,7 @@ int StdGridLayoutImpl2::pixelSize(const WLength& size) DomElement *StdGridLayoutImpl2::createDomElement(bool fitWidth, bool fitHeight, WApplication *app) { - needAdjust_ = needConfigUpdate_ = false; + needAdjust_ = needConfigUpdate_ = needRemeasure_ = false; addedItems_.clear(); removedItems_.clear(); @@ -757,5 +772,3 @@ DomElement *StdGridLayoutImpl2::createDomElement(bool fitWidth, bool fitHeight, } } - -#endif // OLD_LAYOUT diff --git a/src/Wt/StdGridLayoutImpl2.h b/src/Wt/StdGridLayoutImpl2.h index 2f90978f06..4eabcc529e 100644 --- a/src/Wt/StdGridLayoutImpl2.h +++ b/src/Wt/StdGridLayoutImpl2.h @@ -40,13 +40,14 @@ class StdGridLayoutImpl2 : public StdLayoutImpl static const char* childrenResizeJS(); virtual bool itemResized(WLayoutItem *item); + virtual bool parentResized(); protected: virtual void containerAddWidgets(WContainerWidget *container); private: Impl::Grid& grid_; - bool needAdjust_, needConfigUpdate_; + bool needAdjust_, needRemeasure_, needConfigUpdate_; std::vector addedItems_; std::vector removedItems_; diff --git a/src/Wt/StdLayoutImpl.h b/src/Wt/StdLayoutImpl.h index 514507e3a2..3d8affcf6b 100644 --- a/src/Wt/StdLayoutImpl.h +++ b/src/Wt/StdLayoutImpl.h @@ -26,9 +26,10 @@ class StdLayoutImpl : public StdLayoutItemImpl // Returns whether updateDom() is needed virtual bool itemResized(WLayoutItem *item) = 0; + virtual bool parentResized() = 0; virtual WContainerWidget *container() const; - virtual WLayoutItem *layoutItem() const; + virtual WLayoutItem *layoutItem() const; protected: virtual void containerAddWidgets(WContainerWidget *container); diff --git a/src/Wt/StdWidgetItemImpl.C b/src/Wt/StdWidgetItemImpl.C index 41741693c5..682d7cdb15 100644 --- a/src/Wt/StdWidgetItemImpl.C +++ b/src/Wt/StdWidgetItemImpl.C @@ -113,79 +113,6 @@ DomElement *StdWidgetItemImpl::createDomElement(bool fitWidth, bool fitHeight, DomElement *d = w->createSDomElement(app); DomElement *result = d; -#ifdef OLD_LAYOUT - int marginRight = 0, marginBottom = 0; - - // Note to self: we should support actual IE8 since this version has - // box-sizing too. - bool boxSizing = !app->environment().agentIsIE(); - - if (!boxSizing) { - if (fitWidth) - marginRight = (w->boxPadding(Horizontal) + w->boxBorder(Horizontal)) * 2; - - if (fitHeight) - marginBottom = (w->boxPadding(Vertical) + w->boxBorder(Vertical)) * 2; - - bool forceDiv - = (fitHeight && d->type() == DomElement_SELECT - && d->getAttribute("size").empty()); - - if (marginRight || marginBottom || forceDiv) { - result = DomElement::createNew(DomElement_DIV); - result->setProperty(PropertyClass, "Wt-wrapdiv"); - std::stringstream style; - - if (app->environment().agentIsIElt(9) && !forceDiv) { - style << "margin-top:-1px;"; - marginBottom -= 1; - } - - if (marginRight) - style << (app->layoutDirection() == LeftToRight - ? "margin-right:" : "margin-left:") - << marginRight << "px;"; - - if (marginBottom) - style << "margin-bottom:" << marginBottom << "px;"; - - result->setProperty(PropertyStyle, style.str()); - } - } - - /* - * Known issues: - * - textarea does not interpret height 100%, and thus it does not - * work inside the wrapped div, on IE6/7 -> fixed in the JavaScript code - * - select does not interpret height that is set on IE6 - * it does work on IE7 ! - * - webkit gets entirely confused by 100% on a div, and possibly - * also on other elements ?? - */ - if (fitHeight && d->getProperty(PropertyStyleHeight).empty()) - if ( (d->type() == DomElement_DIV && !app->environment().agentIsWebKit()) - || d->type() == DomElement_UL - || d->type() == DomElement_INPUT - || d->type() == DomElement_TABLE - || d->type() == DomElement_TEXTAREA) - d->setProperty(PropertyStyleHeight, "100%"); - - // on IE, a select is reduced to width 0 when setting width: 100% when nothing - // else in that column takes up space: that is a very bad thing... - if (fitWidth && d->getProperty(PropertyStyleWidth).empty()) { - if ((d->type() == DomElement_BUTTON - || (d->type() == DomElement_INPUT - && d->getAttribute("type") != "radio" - && d->getAttribute("type") != "checkbox") - || (d->type() == DomElement_SELECT - && !app->environment().agentIsIE()) - || d->type() == DomElement_TEXTAREA)) - d->setProperty(PropertyStyleWidth, "100%"); - } - - if (result != d) - result->addChild(d); -#else if (app->environment().agentIsIElt(9) && (d->type() == DomElement_TEXTAREA || d->type() == DomElement_SELECT || d->type() == DomElement_INPUT || d->type() == DomElement_BUTTON)) { @@ -198,7 +125,6 @@ DomElement *StdWidgetItemImpl::createDomElement(bool fitWidth, bool fitHeight, d->type() != DomElement_TABLE /* buggy in Chrome, see #1856 */ && app->theme()->canBorderBoxElement(*d)) d->setProperty(PropertyStyleBoxSizing, "border-box"); -#endif return result; } diff --git a/src/Wt/WAbstractItemModel b/src/Wt/WAbstractItemModel index df57cbe94d..0c6b748d76 100644 --- a/src/Wt/WAbstractItemModel +++ b/src/Wt/WAbstractItemModel @@ -54,7 +54,7 @@ namespace Wt { * Wt's standard view classes can display (Wt::DisplayRole) the following data: * * - strings of type WString or std::string - * - WDate, WTime, WDateTime + * - WDate, WTime, WDateTime, WLocalDateTime * - standard C++ numeric types (int, double, etc...) * - bool * diff --git a/src/Wt/WAbstractItemView.C b/src/Wt/WAbstractItemView.C index 30dee152fb..a8557d5aec 100644 --- a/src/Wt/WAbstractItemView.C +++ b/src/Wt/WAbstractItemView.C @@ -250,12 +250,6 @@ WAbstractItemView::WAbstractItemView(WContainerWidget *parent) WApplication *app = WApplication::instance(); -#ifdef OLD_LAYOUT - // FIXME Not needed for new layout managers ? - if (app->environment().agentIsChrome()) - impl_->setMargin(1, Right); // Chrome WTF ? #452 -#endif - typedef WAbstractItemView Self; headerClickedMapper_ = new WSignalMapper(this); diff --git a/src/Wt/WAbstractProxyModel b/src/Wt/WAbstractProxyModel index 911435b064..4700039191 100644 --- a/src/Wt/WAbstractProxyModel +++ b/src/Wt/WAbstractProxyModel @@ -124,7 +124,7 @@ protected: * * \sa createIndex() */ - struct BaseItem { + struct WT_API BaseItem { /*! \brief The source model index. * * The source model index for this item. diff --git a/src/Wt/WAbstractSpinBox.C b/src/Wt/WAbstractSpinBox.C index 7e056c2836..17a061f63b 100644 --- a/src/Wt/WAbstractSpinBox.C +++ b/src/Wt/WAbstractSpinBox.C @@ -70,12 +70,18 @@ bool WAbstractSpinBox::nativeControl() const void WAbstractSpinBox::setPrefix(const WString& prefix) { - prefix_ = prefix; + if (prefix_ != prefix) { + prefix_ = prefix; + setText(textFromValue()); + } } void WAbstractSpinBox::setSuffix(const WString& suffix) { - suffix_ = suffix; + if (suffix_ != suffix) { + suffix_ = suffix; + setText(textFromValue()); + } } void WAbstractSpinBox::render(WFlags flags) diff --git a/src/Wt/WApplication b/src/Wt/WApplication index cb8243ce5e..246e348f63 100644 --- a/src/Wt/WApplication +++ b/src/Wt/WApplication @@ -2341,15 +2341,19 @@ extern int WTCONNECTOR_API WRun(int argc, char** argv, */ #define wApp Wt::WApplication::instance() +#ifndef WT_CNOR +extern void WT_API WtEmitBindSignal(const boost::shared_ptr< Wt::Signals::signal >& s); +#else extern void WT_API WtEmitBindSignal(const boost::shared_ptr< Wt::Signals::signal0 >& s); +#endif #ifndef WT_CNOR template boost::function Wt::WApplication::bind(const F& f) { - typedef boost::shared_ptr< Wt::Signals::signal0 > SignalPtr; + typedef boost::shared_ptr< Wt::Signals::signal > SignalPtr; + SignalPtr s(new Wt::Signals::signal()); - SignalPtr s(new Wt::Signals::signal0()); s->connect(f); return boost::bind(&WtEmitBindSignal, s); diff --git a/src/Wt/WApplication.C b/src/Wt/WApplication.C index f20b712260..23ff8f64a5 100644 --- a/src/Wt/WApplication.C +++ b/src/Wt/WApplication.C @@ -1620,7 +1620,7 @@ void WApplication::resumeRendering() #endif // WT_TARGET_JAVA #ifndef WT_CNOR -void WtEmitBindSignal(const boost::shared_ptr< Wt::Signals::signal0 >& s) +void WtEmitBindSignal(const boost::shared_ptr< Wt::Signals::signal >& s) { (*s)(); } diff --git a/src/Wt/WBoostAny.C b/src/Wt/WBoostAny.C index 7988c8f60e..f1fcf870a2 100644 --- a/src/Wt/WBoostAny.C +++ b/src/Wt/WBoostAny.C @@ -17,6 +17,7 @@ #include "Wt/WBoostAny" #include "Wt/WDate" #include "Wt/WDateTime" +#include "Wt/WLocalDateTime" #include "Wt/WException" #include "Wt/WLocale" #include "Wt/WTime" @@ -178,6 +179,19 @@ std::string asJSLiteral(const boost::any& v, TextFormat textFormat) const WDate& d = dt.date(); const WTime& t = dt.time(); + return "new Date(" + boost::lexical_cast(d.year()) + + ',' + boost::lexical_cast(d.month() - 1) + + ',' + boost::lexical_cast(d.day()) + + ',' + boost::lexical_cast(t.hour()) + + ',' + boost::lexical_cast(t.minute()) + + ',' + boost::lexical_cast(t.second()) + + ',' + boost::lexical_cast(t.msec()) + + ')'; + } else if (v.type() == typeid(WLocalDateTime)) { + const WLocalDateTime& dt = boost::any_cast(v); + const WDate& d = dt.date(); + const WTime& t = dt.time(); + return "new Date(" + boost::lexical_cast(d.year()) + ',' + boost::lexical_cast(d.month() - 1) + ',' + boost::lexical_cast(d.day()) @@ -236,6 +250,9 @@ boost::any updateFromJS(const boost::any& v, std::string s) else if (v.type() == typeid(WDateTime)) return boost::any(WDateTime::fromString(WString::fromUTF8(s), "ddd MMM d yyyy HH:mm:ss")); + else if (v.type() == typeid(WLocalDateTime)) + return boost::any(WLocalDateTime::fromString(WString::fromUTF8(s), + "ddd MMM d yyyy HH:mm:ss")); #define ELSE_LEXICAL_ANY(TYPE) \ else if (v.type() == typeid(TYPE)) \ return boost::any(boost::lexical_cast(s)) @@ -287,6 +304,7 @@ int compare(const boost::any& d1, const boost::any& d2) ELSE_COMPARE_ANY(std::string) ELSE_COMPARE_ANY(WDate) ELSE_COMPARE_ANY(WDateTime) + ELSE_COMPARE_ANY(WLocalDateTime) ELSE_COMPARE_ANY(boost::posix_time::ptime) ELSE_COMPARE_ANY(boost::posix_time::time_duration) ELSE_COMPARE_ANY(WTime) @@ -352,6 +370,9 @@ WString asString(const boost::any& v, const WT_USTRING& format) return dt.toString(format.empty() ? "dd/MM/yy HH:mm:ss" : format); + } else if (v.type() == typeid(WLocalDateTime)) { + const WLocalDateTime& dt = boost::any_cast(v); + return dt.toString(); } else if (v.type() == typeid(WTime)) { const WTime& t = boost::any_cast(v); return t.toString(format.empty() ? "HH:mm:ss" : format); @@ -482,6 +503,9 @@ double asNumber(const boost::any& v) else if (v.type() == typeid(WDateTime)) { const WDateTime& dt = boost::any_cast(v); return static_cast(dt.toTime_t()); + } else if (v.type() == typeid(WLocalDateTime)) { + const WLocalDateTime& dt = boost::any_cast(v); + return static_cast(dt.toUTC().toTime_t()); } else if (v.type() == typeid(WTime)) { const WTime& t = boost::any_cast(v); return static_cast(WTime(0, 0).msecsTo(t)); @@ -550,6 +574,8 @@ extern WT_API boost::any convertAnyToAny(const boost::any& v, } else if (type == typeid(WDateTime)) { return WDateTime::fromString (s, format.empty() ? "dd/MM/yy HH:mm:ss" : format); + } else if (type == typeid(WLocalDateTime)) { + return WLocalDateTime::fromString(s); } else if (type == typeid(WTime)) { return WTime::fromString (s, format.empty() ? "HH:mm:ss" : format); diff --git a/src/Wt/WComboBox.C b/src/Wt/WComboBox.C index 22023beb0d..e0a6714742 100644 --- a/src/Wt/WComboBox.C +++ b/src/Wt/WComboBox.C @@ -225,6 +225,10 @@ void WComboBox::updateDom(DomElement& element, bool all) item->setProperty(PropertyInnerHTML, escapeText(asString(model_->data(i, modelColumn_))) .toUTF8()); + + if (!(model_->flags(model_->index(i, modelColumn_)) & ItemIsSelectable)) + item->setProperty(PropertyDisabled, "true"); + if (isSelected(i)) item->setProperty(PropertySelected, "true"); diff --git a/src/Wt/WCompositeWidget b/src/Wt/WCompositeWidget index 12e9cb7e1c..aac0b31d2e 100644 --- a/src/Wt/WCompositeWidget +++ b/src/Wt/WCompositeWidget @@ -62,6 +62,7 @@ public: using WWidget::removeChild; + virtual void setObjectName(const std::string& name); virtual const std::string id() const; virtual void setPositionScheme(PositionScheme scheme); diff --git a/src/Wt/WCompositeWidget.C b/src/Wt/WCompositeWidget.C index 8d644fe2ad..b0b6b3e150 100644 --- a/src/Wt/WCompositeWidget.C +++ b/src/Wt/WCompositeWidget.C @@ -43,6 +43,11 @@ WCompositeWidget::~WCompositeWidget() delete impl_; } +void WCompositeWidget::setObjectName(const std::string& name) +{ + impl_->setObjectName(name); +} + const std::string WCompositeWidget::id() const { return impl_->id(); diff --git a/src/Wt/WContainerWidget b/src/Wt/WContainerWidget index 0566649eb4..6f01e2ca1e 100644 --- a/src/Wt/WContainerWidget +++ b/src/Wt/WContainerWidget @@ -381,6 +381,7 @@ protected: virtual int firstChildIndex() const; virtual void childResized(WWidget *child, WFlags directions); + virtual void parentResized(WWidget *parent, WFlags directions); virtual void getDomChanges(std::vector& result, WApplication *app); DomElement *createDomElement(WApplication *app, bool addChildren); diff --git a/src/Wt/WContainerWidget.C b/src/Wt/WContainerWidget.C index 1476ddab56..515424091f 100644 --- a/src/Wt/WContainerWidget.C +++ b/src/Wt/WContainerWidget.C @@ -4,14 +4,7 @@ * See the LICENSE file for terms of use. */ #include "StdWidgetItemImpl.h" - -#ifdef OLD_LAYOUT -#include "StdGridLayoutImpl.h" -#define GridLayoutImpl StdGridLayoutImpl -#else #include "StdGridLayoutImpl2.h" -#define GridLayoutImpl StdGridLayoutImpl2 -#endif #include "Wt/WApplication" #include "Wt/WBorderLayout" @@ -90,7 +83,6 @@ void WContainerWidget::setLayout(WLayout *layout, if (layout_ && layout != layout_) delete layout_; -#ifndef OLD_LAYOUT AlignmentFlag hAlign = alignment & AlignHorizontalMask; AlignmentFlag vAlign = alignment & AlignVerticalMask; @@ -99,7 +91,6 @@ void WContainerWidget::setLayout(WLayout *layout, "longer have the special meaning it used to have). Use spacers " "or CSS instead to control alignment"); } -#endif contentAlignment_ = alignment; @@ -108,7 +99,6 @@ void WContainerWidget::setLayout(WLayout *layout, flags_.set(BIT_LAYOUT_NEEDS_RERENDER); if (layout) { - containsLayout(); WWidget::setLayout(layout); layoutImpl()->setContainer(this); } @@ -123,27 +113,31 @@ void WContainerWidget::childResized(WWidget *child, { #ifndef WT_NO_LAYOUT if (layout_) { -#ifdef OLD_LAYOUT - AlignmentFlag vAlign = contentAlignment_ & AlignVerticalMask; - bool setUpdate - = (directions & Vertical) && (vAlign == 0) - && !flags_.test(BIT_LAYOUT_NEEDS_UPDATE); -#else - bool setUpdate = true; -#endif - if (setUpdate) { - WWidgetItem *item = layout_->findWidgetItem(child); - if (item) { - if (dynamic_cast(item->parentLayout()->impl()) - ->itemResized(item)) { - flags_.set(BIT_LAYOUT_NEEDS_UPDATE); - repaint(); - } + WWidgetItem *item = layout_->findWidgetItem(child); + if (item) { + if (dynamic_cast(item->parentLayout()->impl()) + ->itemResized(item)) { + flags_.set(BIT_LAYOUT_NEEDS_UPDATE); + repaint(); } } } else -#endif WInteractWidget::childResized(child, directions); +#endif +} + +void WContainerWidget::parentResized(WWidget *parent, + WFlags directions) +{ +#ifndef WT_NO_LAYOUT + if (layout_) { + if (dynamic_cast(layout_->impl())->parentResized()) { + flags_.set(BIT_LAYOUT_NEEDS_UPDATE); + repaint(); + } + } else + WInteractWidget::parentResized(parent, directions); +#endif } WLayoutItemImpl *WContainerWidget::createLayoutItemImpl(WLayoutItem *item) @@ -158,19 +152,19 @@ WLayoutItemImpl *WContainerWidget::createLayoutItemImpl(WLayoutItem *item) { WBorderLayout *l = dynamic_cast(item); if (l) - return new GridLayoutImpl(l, l->grid()); + return new StdGridLayoutImpl2(l, l->grid()); } { WBoxLayout *l = dynamic_cast(item); if (l) - return new GridLayoutImpl(l, l->grid()); + return new StdGridLayoutImpl2(l, l->grid()); } { WGridLayout *l = dynamic_cast(item); if (l) - return new GridLayoutImpl(l, l->grid()); + return new StdGridLayoutImpl2(l, l->grid()); } #endif @@ -668,6 +662,7 @@ void WContainerWidget::createDomChildren(DomElement& parent, WApplication *app) { if (layout_) { #ifndef WT_NO_LAYOUT + containsLayout(); bool fitWidth = contentAlignment_ & AlignJustify; bool fitHeight = !(contentAlignment_ & AlignVerticalMask); diff --git a/src/Wt/WDate b/src/Wt/WDate index 06bc0b9d56..be4faaec57 100644 --- a/src/Wt/WDate +++ b/src/Wt/WDate @@ -21,9 +21,11 @@ namespace boost { namespace Wt { /*! \class InvalidDateException Wt/WDate Wt/WDate - * \brief Exception thrown when calculating with an invalid date. + * \brief Exception thrown when calculating with an invalid date (deprecated). * * \sa WDate + * + * \deprecated This excepteion is no longer thrown */ class WT_API InvalidDateException : public WException { @@ -54,8 +56,7 @@ public: * same format syntax is supported by both methods. * * Simple operations are supported to compare dates, or to calculate - * with dates. These operations throw InvalidDateException when one of - * the dates is invalid. + * with dates. * *

i18n

* @@ -102,7 +103,7 @@ public: /*! \brief Sets the date by year, month, and day. * * The \p month has range 1-12 and the \p day has range 1-31. - * When the new date is invalid, isValid() is set to \c false. + * When the new date is invalid, isValid() will return \c false. * * \sa WDate(int year, int month, int day), year(), month(), day() */ @@ -118,6 +119,9 @@ public: * date. Negative values for \p ndays will result in a date that * is as many days earlier. * + * Returns a null date if the current date is invalid or the new + * date is out of range. + * * \sa addMonths(), addYears() */ WDate addDays(int ndays) const; @@ -129,6 +133,9 @@ public: * \p nmonths will result in a date that is as many months * earlier. * + * Returns a null date if the current date is invalid or the new + * date is out of range. + * * \sa addDays(), addYears() */ WDate addMonths(int nmonths) const; @@ -139,6 +146,9 @@ public: * date. Negative values for \p nyears will result in a date * that is as many years earlier. * + * Returns a null date if the current date is invalid or the new + * date is out of range. + * * \sa addDays(), addMonths() */ WDate addYears(int nyears) const; @@ -149,37 +159,49 @@ public: * * \sa isValid(), WDate() */ - bool isNull() const; + bool isNull() const { return ymd_ == 0; } /*! \brief Returns if this date is valid. * * \sa isNull(), WDate(int, int, int), setDate() */ - bool isValid() const { return valid_; } + bool isValid() const { return ymd_ > 1; } /*! \brief Returns the year. + * + * Returns 0 if the date is invalid. */ - int year() const { return year_; } + int year() const { return (ymd_ >> 16); } /*! \brief Returns the month (1-12). + * + * Returns 0 if the date is invalid. */ - int month() const { return month_; } + int month() const { return (ymd_ >> 8) & 0xFF; } /*! \brief Returns the day of month (1-31). + * + * Returns 0 if the date is invalid. */ - int day() const { return day_; } + int day() const { return ymd_ & 0xFF; } /*! \brief Returns the day of week (1-7). * * Returns the day of week, from Monday (=1) to Sunday (=7). + * + * Returns 0 if the date is invalid. */ int dayOfWeek() const; /*! \brief Returns the difference between two dates (in days). + * + * Returns 0 if either date is invalid. */ int daysTo(const WDate& date) const; /*! \brief Converts the date to a Julian day. + * + * Returns 0 if the date is invalid. * * \sa fromJulianDay() */ @@ -317,8 +339,6 @@ public: * * This method uses browser information to retrieve the date that is * configured in the client. - * - * \note Not Yet Implemented ! */ static WDate currentDate(); @@ -405,10 +425,9 @@ public: static RegExpInfo formatToRegExp(const WT_USTRING& format); private: - bool valid_; - int year_; - int month_; - int day_; + unsigned ymd_; + + void setYmd(int year, int month, int day); struct ParseState { int d, M, y; diff --git a/src/Wt/WDate.C b/src/Wt/WDate.C index b108e782c4..94aa5d5d40 100644 --- a/src/Wt/WDate.C +++ b/src/Wt/WDate.C @@ -7,6 +7,7 @@ #include "Wt/WApplication" #include "Wt/WDate" #include "Wt/WException" +#include "Wt/WLocalDateTime" #include "Wt/WLogger" #include "WebUtils.h" @@ -24,14 +25,11 @@ namespace Wt { LOGGER("WDate"); InvalidDateException::InvalidDateException() - : WException("Error: Attempted operation on an invalid WDate") + : WException("Error: Attempted operation on an invalid WDate") { } WDate::WDate() - : valid_(false), - year_(-1), - month_(0), - day_(0) + : ymd_(0) { } WDate::WDate(const date& date) @@ -46,63 +44,71 @@ WDate::WDate(int year, int month, int day) void WDate::setGregorianDate(const date& date) { - valid_ = !date.is_special(); + bool valid = !date.is_special(); - if (valid_) { - year_ = date.year(); - month_ = date.month(); - day_ = date.day(); - } + if (valid) + setYmd(date.year(), date.month(), date.day()); + else + ymd_ = 1; // not null and not valid } WDate WDate::addDays(int ndays) const { - if (valid_) { - date d(year_, month_, day_); + if (isValid()) { + date d(year(), month(), day()); d += date_duration(ndays); - + if (d.year() > 9999 || d.year() < 1400) + return WDate(); return WDate(d.year(), d.month(), d.day()); } else - return *this; + return WDate(); } WDate WDate::addMonths(int nmonths) const { - if (valid_) { - date d(year_, month_, day_); + if (isValid()) { + date d(year(), month(), day()); d += months(nmonths); - + if (d.year() > 9999 || d.year() < 1400) + return WDate(); return WDate(d.year(), d.month(), d.day()); } else - return *this; + return WDate(); } WDate WDate::addYears(int nyears) const { - if (valid_) { - date d(year_, month_, day_); + if (isValid()) { + date d(year(), month(), day()); d += years(nyears); - + if (d.year() > 9999 || d.year() < 1400) + return WDate(); return WDate(d.year(), d.month(), d.day()); } else - return *this; + return WDate(); } void WDate::setDate(int year, int month, int day) { - year_ = year; - month_ = month; - day_ = day; - valid_ = false; - try { date d(year, month, day); - valid_ = true; + if (d.year() > 9999 || d.year() < 1400) { + LOG_WARN("Invalid date: not in range 1400 .. 9999"); + ymd_ = 1; + return; + } + setYmd(year, month, day); } catch (std::out_of_range& e) { LOG_WARN("Invalid date: " << e.what()); + ymd_ = 1; } } +void WDate::setYmd(int y, int m, int d) +{ + ymd_ = (y << 16) | ((m & 0xFF) << 8) | (d & 0xFF); +} + bool WDate::isLeapYear(int year) { return gregorian_calendar::is_leap_year(year); @@ -111,22 +117,20 @@ bool WDate::isLeapYear(int year) int WDate::dayOfWeek() const { if (!isValid()) - throw InvalidDateException(); - - date d(year_, month_, day_); + return 0; + date d(year(), month(), day()); int dow = d.day_of_week().as_number(); - return (dow == 0 ? 7 : dow); } int WDate::daysTo(const WDate& other) const { if (!isValid() || !other.isValid()) - throw InvalidDateException(); + return 0; - date dthis(year_, month_, day_); - date dother(other.year_, other.month_, other.day_); + date dthis(year(), month(), day()); + date dother(other.year(), other.month(), other.day()); date_duration dd = dother - dthis; return dd.days(); @@ -135,9 +139,9 @@ int WDate::daysTo(const WDate& other) const int WDate::toJulianDay() const { if (!isValid()) - return -1; + return 0; else { - date dthis(year_, month_, day_); + date dthis(year(), month(), day()); return dthis.julian_day(); } } @@ -145,20 +149,14 @@ int WDate::toJulianDay() const boost::gregorian::date WDate::toGregorianDate() const { if (isValid()) - return date(year_, month_, day_); + return date(year(), month(), day()); else return date(not_a_date_time); } -bool WDate::isNull() const -{ - return year_ == -1; -} - bool WDate::isValid(int year, int month, int day) { WDate d(year, month, day); - return d.isValid(); } @@ -169,22 +167,7 @@ bool WDate::operator> (const WDate& other) const bool WDate::operator< (const WDate& other) const { - if (!isValid() || !other.isValid()) - throw InvalidDateException(); - - if (year_ < other.year_) - return true; - else - if (year_ == other.year_) { - if (month_ < other.month_) - return true; - else - if (month_ == other.month_) - return day_ < other.day_; - else - return false; - } else - return false; + return ymd_ < other.ymd_; } bool WDate::operator!= (const WDate& other) const @@ -194,10 +177,7 @@ bool WDate::operator!= (const WDate& other) const bool WDate::operator== (const WDate& other) const { - if ((!isValid() && !isNull()) || (!other.isValid() && !other.isNull())) - throw InvalidDateException(); - - return (year_ == other.year_ && month_ == other.month_ && day_ == other.day_); + return ymd_ == other.ymd_; } bool WDate::operator<= (const WDate& other) const @@ -219,7 +199,7 @@ WDate WDate::currentServerDate() WDate WDate::currentDate() { - return currentServerDate(); // FIXME + return WLocalDateTime::currentDateTime().date(); } WString WDate::shortDayName(int weekday, bool localized) @@ -612,10 +592,10 @@ WString WDate::toString() const WString WDate::toString(const WString& format) const { - return WDateTime::toString(this, 0, format); + return WDateTime::toString(this, 0, format, 0, true); } -bool WDate::writeSpecial(const std::string& f, unsigned&i, +bool WDate::writeSpecial(const std::string& f, unsigned& i, std::stringstream& result, bool localized) const { char buf[30]; @@ -636,11 +616,11 @@ bool WDate::writeSpecial(const std::string& f, unsigned&i, } else { // 2 d's i += 1; - result << Utils::pad_itoa(day_, 2, buf); + result << Utils::pad_itoa(day(), 2, buf); } } else { // 1 d - result << Utils::itoa(day_, buf); + result << Utils::itoa(day(), buf); } return true; @@ -650,20 +630,20 @@ bool WDate::writeSpecial(const std::string& f, unsigned&i, if (f[i + 3] == 'M') { // 4 M's i += 3; - result << longMonthName(month_, localized).toUTF8(); + result << longMonthName(month(), localized).toUTF8(); } else { // 3 M's i += 2; - result << shortMonthName(month_, localized).toUTF8(); + result << shortMonthName(month(), localized).toUTF8(); } } else { // 2 M's i += 1; - result << Utils::pad_itoa(month_, 2, buf); + result << Utils::pad_itoa(month(), 2, buf); } } else { // 1 M - result << Utils::itoa(month_, buf); + result << Utils::itoa(month(), buf); } return true; @@ -672,13 +652,13 @@ bool WDate::writeSpecial(const std::string& f, unsigned&i, if (f[i + 2] == 'y' && f[i + 3] == 'y') { // 4 y's i += 3; - result << Utils::itoa(year_, buf); + result << Utils::itoa(year(), buf); return true; } else { // 2 y's i += 1; - result << Utils::pad_itoa(year_ % 100, 2, buf); + result << Utils::pad_itoa(year() % 100, 2, buf); return true; } diff --git a/src/Wt/WDateEdit.C b/src/Wt/WDateEdit.C index a1beb9a235..50a3933b39 100644 --- a/src/Wt/WDateEdit.C +++ b/src/Wt/WDateEdit.C @@ -171,9 +171,9 @@ void WDateEdit::connectJavaScript(Wt::EventSignalBase& s, const std::string& methodName) { std::string jsFunction = - "function(obj, event) {" - """var o = jQuery.data(" + jsRef() + ", 'obj');" - """if (o) o." + methodName + "(obj, event);" + "function(dobj, event) {" + """var o = jQuery.data(" + jsRef() + ", 'dobj');" + """if (o) o." + methodName + "(dobj, event);" "}"; s.connect(jsFunction); diff --git a/src/Wt/WDateTime b/src/Wt/WDateTime index 59a71ae817..77bd7e1d00 100644 --- a/src/Wt/WDateTime +++ b/src/Wt/WDateTime @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -17,9 +18,11 @@ namespace Wt { /*! \class InvalidDateTimeException Wt/WDateTime Wt/WDateTime - * \brief Exception thrown when calculating with an invalid date. + * \brief Exception thrown when calculating with an invalid date (deprecated) * * \sa WDateTime + * + * \deprecated Is never thrown */ class WT_API InvalidDateTimeException : public WException { @@ -27,19 +30,15 @@ public: InvalidDateTimeException(); }; -class WDate; -class WTime; - /*! \class WDateTime Wt/WDateTime Wt/WDateTime * \brief A calendar date and clock time. * * The date time class combines the functionality of a WDate (for a * calendar date) and WTime (for clock time) into a single class. * - * Conventionally date time is assumed in UTC, which affects the behaviour - * of the following methods: - * - toTime_t(), setTime_t() - * - currentDateTime() + * This class stores and represents the date time in UTC. To deal with local + * (wall clock) time, see WLocalDateTime. To convert from UTC to local time + * use toUTC(). * *

i18n

* @@ -61,7 +60,6 @@ class WTime; * - Wt.WDateTime.seconds: {1} seconds * The placeholder {1} will be replaced by the actual number of seconds. The * same keys also exist for minutes, hours, days, weeks, monts and years. - * */ class WT_API WDateTime { @@ -88,6 +86,10 @@ public: */ WDateTime(const WDate& date, const WTime& time); + /*! \brief Creates a date time. + */ + explicit WDateTime(const boost::posix_time::ptime time); + /*! \brief Sets the time in seconds from the Epoch. * * The \p time is the number of seconds since the Epoch (00:00:00 @@ -106,6 +108,9 @@ public: * Returns a datetime that is \p ms milliseconds later than this * datetime. Negative values for \p ms will result in a datetime that * is as many milliseconds earlier. + * + * Returns a null date if the current date time is invalid or the new + * date time is out of range. */ WDateTime addMSecs(int ms) const; @@ -114,6 +119,9 @@ public: * Returns a datetime that is \p s seconds later than this * datetime. Negative values for \p s will result in a datetime that * is as many seconds earlier. + * + * Returns a null date if the current date time is invalid or the new + * date time is out of range. */ WDateTime addSecs(int s) const; @@ -123,6 +131,9 @@ public: * datetime. Negative values for \p ndays will result in a datetime * that is as many days earlier. * + * Returns a null date if the current date time is invalid or the new + * date time is out of range. + * * \sa addMonths(), addYears() */ WDateTime addDays(int ndays) const; @@ -133,6 +144,9 @@ public: * nmonths later than this date. Negative values for \p nmonths will * result in a datetime that is as many months earlier. * + * Returns a null date if the current date time is invalid or the new + * date time is out of range. + * * \sa addDays(), addYears() */ WDateTime addMonths(int nmonths) const; @@ -143,6 +157,9 @@ public: * datetime. Negative values for \p nyears will result in a datetime * that is as many years earlier. * + * Returns a null date if the current date time is invalid or the new + * date time is out of range. + * * \sa addDays(), addMonths() */ WDateTime addYears(int nyears) const; @@ -205,10 +222,20 @@ public: */ boost::posix_time::ptime toPosixTime() const; + /*! \brief Converts to a local time. + * + * The conversion is based on the fact that WDateTime represents UTC time. + * + * This is the reverse of WLocalDateTime::toUTC() + */ + WLocalDateTime toLocalTime(const WLocale& locale = WLocale::currentLocale()); + /*! \brief Returns the difference between two datetime values (in seconds). * * The result is negative if other is earlier than this. * + * Returns 0 if either date is invalid. + * * \sa daysTo() */ int secsTo(const WDateTime& other) const; @@ -217,6 +244,8 @@ public: * * The result is negative if other is earlier than this. * + * Returns 0 if either date is invalid. + * * \sa secsTo() */ int daysTo(const WDateTime& other) const; @@ -229,6 +258,8 @@ public: * minutes, hours, days, weeks, months, or years, using the coarsest * unit that is more than \p minValue. * + * Returns an empty string if either date is invalid. + * * \sa daysTo(), secsTo() */ WString timeTo(const WDateTime& other, int minValue = 1) const; @@ -291,7 +322,7 @@ public: */ static WDateTime fromString(const WT_USTRING& s, const WT_USTRING& format); - /*! \brief Reports the current UTC datetime. + /*! \brief Reports the current datetime (UTC clock). * * This method returns the datetime as indicated by the system clock * of the server, in the UTC timezone. @@ -315,15 +346,15 @@ private: enum CharState { CharUnhandled, CharHandled, CharInvalid }; - WDateTime(boost::posix_time::ptime datetime); - static void fromString(WDate *date, WTime *time, const WString& s, const WString& format); static WString toString(const WDate *date, const WTime *time, - const WString& format, bool localized = true); + const WString& format, bool localized, + int zoneOffset); friend class WDate; friend class WTime; + friend class WLocalDateTime; }; } diff --git a/src/Wt/WDateTime.C b/src/Wt/WDateTime.C index e00e1110e6..41cb5207cf 100644 --- a/src/Wt/WDateTime.C +++ b/src/Wt/WDateTime.C @@ -9,6 +9,7 @@ #include "Wt/WApplication" #include "Wt/WDateTime" #include "Wt/WDate" +#include "Wt/WLocalDateTime" #include "Wt/WTime" #ifndef DOXYGEN_ONLY @@ -16,10 +17,6 @@ namespace posix = boost::posix_time; namespace gregorian = boost::gregorian; -/* - * TODO: -isValid() versus !isNull() - */ - namespace Wt { namespace { @@ -46,24 +43,23 @@ WDateTime::WDateTime() WDateTime::WDateTime(const WDate& date) { if (date.isValid()) { - gregorian::date d(date.year(), date.month(), date.day()); + gregorian::date d = date.toGregorianDate(); posix::time_duration t(0, 0, 0, 0); datetime_ = posix::ptime(d, t); - } + } else + datetime_ = posix::ptime(posix::neg_infin); } WDateTime::WDateTime(const WDate& date, const WTime& time) { if (date.isValid() && time.isValid()) { - gregorian::date d(date.year(), date.month(), date.day()); - posix::time_duration::fractional_seconds_type ticks_per_msec = - posix::time_duration::ticks_per_second() / 1000; - posix::time_duration t(time.hour(), time.minute(), - time.second(), time.msec() * ticks_per_msec); + gregorian::date d = date.toGregorianDate(); + posix::time_duration t = time.toTimeDuration(); datetime_ = posix::ptime(d, t); - } + } else + datetime_ = posix::ptime(posix::neg_infin); } WDateTime::WDateTime(posix::ptime dt) @@ -82,29 +78,25 @@ void WDateTime::setPosixTime(const posix::ptime& dt) void WDateTime::setDate(const WDate& date) { - if (isValid()) { + if (isValid()) *this = WDateTime(date, time()); - } else { + else *this = WDateTime(date, WTime(0, 0)); - } } const WDate WDateTime::date() const { if (isValid()) { gregorian::date d = datetime_.date(); - return WDate(d.year(), d.month(), d.day()); + return WDate(d); } else return WDate(); } void WDateTime::setTime(const WTime& time) { - if (isValid()) { + if (isValid()) *this = WDateTime(date(), time); - } else { - // FIXME: without a valid date, what to do ?? - } } const WTime WDateTime::time() const @@ -126,28 +118,36 @@ WDateTime WDateTime::addMSecs(int ms) const if (isValid()) { posix::time_duration::fractional_seconds_type ticks_per_msec = posix::time_duration::ticks_per_second() / 1000; - posix::ptime dt = datetime_ + posix::time_duration(0, 0, 0, ms * ticks_per_msec); + posix::ptime dt = datetime_ + posix::time_duration(0, 0, 0, + ms * ticks_per_msec); + if (dt.is_not_a_date_time()) + dt = posix::ptime(posix::neg_infin); + return WDateTime(dt); } else - return *this; + return WDateTime(); } WDateTime WDateTime::addSecs(int s) const { if (isValid()) { posix::ptime dt = datetime_ + posix::time_duration(0, 0, s, 0); + if (dt.is_not_a_date_time()) + dt = posix::ptime(posix::neg_infin); return WDateTime(dt); } else - return *this; + return WDateTime(); } WDateTime WDateTime::addDays(int ndays) const { if (isValid()) { posix::ptime dt = datetime_ + gregorian::days(ndays); + if (dt.is_not_a_date_time()) + dt = posix::ptime(posix::neg_infin); return WDateTime(dt); } else - return *this; + return WDateTime(); } WDateTime WDateTime::addMonths(int nmonths) const @@ -157,7 +157,7 @@ WDateTime WDateTime::addMonths(int nmonths) const WTime t = time(); return WDateTime(d, t); } else - return *this; + return WDateTime(); } WDateTime WDateTime::addYears(int nyears) const @@ -167,23 +167,26 @@ WDateTime WDateTime::addYears(int nyears) const WTime t = time(); return WDateTime(d, t); } else - return *this; + return WDateTime(); } bool WDateTime::isNull() const { - return !isValid(); + return datetime_.is_not_a_date_time(); } bool WDateTime::isValid() const { - return !datetime_.is_not_a_date_time(); + return !datetime_.is_special(); } std::time_t WDateTime::toTime_t() const { - return (datetime_ - posix::ptime(gregorian::date(1970, 1, 1))) - .total_seconds(); + if (isValid()) + return (datetime_ - posix::ptime(gregorian::date(1970, 1, 1))) + .total_seconds(); + else + return (std::time_t) 0; } posix::ptime WDateTime::toPosixTime() const @@ -191,10 +194,17 @@ posix::ptime WDateTime::toPosixTime() const return datetime_; } +WLocalDateTime WDateTime::toLocalTime(const WLocale& locale) +{ + return WLocalDateTime + (boost::local_time::local_date_time(datetime_, locale.time_zone_ptr()), + locale.dateTimeFormat()); +} + int WDateTime::secsTo(const WDateTime& other) const { if (!isValid() || !other.isValid()) - throw InvalidDateTimeException(); + return 0; return (int)other.toTime_t() - (int)toTime_t(); } @@ -206,6 +216,9 @@ int WDateTime::daysTo(const WDateTime& other) const WString WDateTime::timeTo(const WDateTime& other, int minValue) const { + if (!isValid() || !other.isValid()) + return WString::Empty; + int secs = secsTo(other); if (abs(secs) < 1) @@ -440,11 +453,12 @@ WString WDateTime::toString(const WString& format, bool localized) const WDate d = date(); WTime t = time(); - return toString(&d, &t, format, localized); + return toString(&d, &t, format, localized, 0); } WString WDateTime::toString(const WDate *date, const WTime *time, - const WString& format, bool localized) + const WString& format, bool localized, + int zoneOffset) { if ((date && !date->isValid()) || (time && !time->isValid())) { if (WApplication::instance()) { @@ -516,7 +530,7 @@ WString WDateTime::toString(const WDate *date, const WTime *time, if (date) handled = date->writeSpecial(f, i, result, localized); if (!handled && time) - handled = time->writeSpecial(f, i, result, useAMPM); + handled = time->writeSpecial(f, i, result, useAMPM, zoneOffset); if (!handled) { if (f[i] == '\'') { diff --git a/src/Wt/WDoubleSpinBox.C b/src/Wt/WDoubleSpinBox.C index d6b872119b..cf438ec4c3 100644 --- a/src/Wt/WDoubleSpinBox.C +++ b/src/Wt/WDoubleSpinBox.C @@ -18,7 +18,8 @@ WDoubleSpinBox::WDoubleSpinBox(WContainerWidget *parent) min_(0.0), max_(99.99), step_(1.0), - precision_(2) + precision_(2), + valueChanged_(this) { setValidator(createValidator()); setValue(0.0); diff --git a/src/Wt/WEnvironment b/src/Wt/WEnvironment index 45673cb572..338b1f22ff 100644 --- a/src/Wt/WEnvironment +++ b/src/Wt/WEnvironment @@ -278,6 +278,18 @@ public: */ const WLocale& locale() const { return locale_; } + /*! \brief Returns the time zone offset as reported by the client. + * + * This returns the time offset that the client has relative to + * UTC. A positive value thus means that the local time is ahead of + * UTC. + * + * This requires JavaScript support. + * + * \sa WLocalDateTime::timeZoneOffset() + */ + int timeZoneOffset() const { return timeZoneOffset_; } + /*! \brief Returns the server host name that is used by the client. * * The hostname is the unresolved host name with optional port number, @@ -642,6 +654,7 @@ protected: CookieMap cookies_; WLocale locale_; + int timeZoneOffset_; std::string host_; std::string userAgent_; std::string urlScheme_; diff --git a/src/Wt/WEnvironment.C b/src/Wt/WEnvironment.C index 2151d75200..3520fc38b2 100644 --- a/src/Wt/WEnvironment.C +++ b/src/Wt/WEnvironment.C @@ -28,7 +28,8 @@ WEnvironment::WEnvironment() doesAjax_(false), doesCookies_(false), hashInternalPaths_(false), - dpiScale_(1) + dpiScale_(1), + timeZoneOffset_(0) #ifndef WT_TARGET_JAVA , sslInfo_(0) #endif @@ -39,7 +40,8 @@ WEnvironment::WEnvironment(WebSession *session) doesAjax_(false), doesCookies_(false), hashInternalPaths_(false), - dpiScale_(1) + dpiScale_(1), + timeZoneOffset_(0) #ifndef WT_TARGET_JAVA , sslInfo_(0) #endif @@ -196,6 +198,13 @@ void WEnvironment::enableAjax(const WebRequest& request) dpiScale_ = 1; } + const std::string *tzE = request.getParameter("tz"); + + try { + timeZoneOffset_ = tzE ? boost::lexical_cast(*tzE) : 0; + } catch (boost::bad_lexical_cast &e) { + } + const std::string *hashE = request.getParameter("_"); // the internal path, when present as an anchor (#), is only diff --git a/src/Wt/WGlobal b/src/Wt/WGlobal index c9132d438b..12396f5817 100644 --- a/src/Wt/WGlobal +++ b/src/Wt/WGlobal @@ -99,6 +99,7 @@ namespace Wt { class WLineF; class WLink; class WLocale; + class WLocalDateTime; class WLoadingIndicator; class WLocalizedStrings; class WLogEntry; diff --git a/src/Wt/WInteractWidget.C b/src/Wt/WInteractWidget.C index b1c6e8b889..97d1998c39 100644 --- a/src/Wt/WInteractWidget.C +++ b/src/Wt/WInteractWidget.C @@ -251,8 +251,9 @@ void WInteractWidget::updateDom(DomElement& element, bool all) = (mouseUp && mouseUp->needsUpdate(all)) || updateMouseMove; - std::string CheckDisabled = "if($(o).hasClass('Wt-disabled')){" - WT_CLASS ".cancelEvent(e);return;}"; + std::string CheckDisabled = "if($(o).hasClass('" + + app->theme()->disabledClass() + + "')){" WT_CLASS ".cancelEvent(e);return;}"; if (updateMouseDown) { /* diff --git a/src/Wt/WJavaScript b/src/Wt/WJavaScript index 1c5e1dc6f0..28548a3c2f 100644 --- a/src/Wt/WJavaScript +++ b/src/Wt/WJavaScript @@ -208,7 +208,7 @@ public: * * When the receiver function is an object method, the signal will * automatically be disconnected when the object is deleted, as long as the - * object inherits from WObject or Wt::Signals::trackable. + * object inherits from WObject (or Wt::Signals::trackable). * * The function may leave 1 parameters unbound (e.g. using * boost::bind placeholders _1) that may be bound to the event @@ -358,25 +358,10 @@ private: std::string name_; mutable std::string senderId_; void processDynamic(const JavaScriptEvent& e); -#ifndef _MSC_VER - // Up to 6 arguments allowed - typedef boost::signal6 BoostSignalType; +#ifdef WT_CNOR + typedef Wt::Signals::signal6 BoostSignalType; #else -# if _MSC_VER >= 1700 - // MSVS 2012's std::bind implementation may support only 5 arguments - // http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx -# if defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 6) - // default MSVS 2012: defaults to 5. We assume no-one will lower this. - typedef Wt::Signals::signal5 BoostSignalType; -# define A6_MUST_BE_NOCLASS -# else - // You can reconfigure this, and MS promised to raise _VARIADIC_MAX - // in future compilers - typedef boost::signal6 BoostSignalType; -# endif -# else - typedef boost::signal6 BoostSignalType; -# endif + typedef Wt::Signals::signal BoostSignalType; #endif BoostSignalType *impl_; diff --git a/src/Wt/WLocalDateTime b/src/Wt/WLocalDateTime new file mode 100644 index 0000000000..cc55fce5ff --- /dev/null +++ b/src/Wt/WLocalDateTime @@ -0,0 +1,197 @@ +// This may look like C code, but it's really -*- C++ -*- +/* + * Copyright (C) 2013 Emweb bvba, Kessel-Lo, Belgium. + * + * See the LICENSE file for terms of use. + */ +#ifndef WLOCAL_DATE_TIME_H_ +#define WLOCAL_DATE_TIME_H_ + +#include +#include +#include +#include +#include + +#include + +namespace Wt { + +/*! \class WLocalDateTime Wt/WLocalDateTime Wt/WLocalDateTime + * \brief A localized calendar date and clock time. + * + * This presents a localized date time, which represents a date time + * in a given locale. + * + * It is recommended to only use this to display timestamps to the + * user, and use WDateTime (which stores UTC time) throughout your + * business logic. For this reason, this class does not contain any + * methods to calculate with time. + * + * \sa WDateTime::toLocalTime() + */ +class WT_API WLocalDateTime +{ +public: + /*! \brief Creates a Null datetime. + * + * A time for which isNull() returns \c true. A Null datetime + * is also invalid. + * + * \sa isValid(), isNull() + */ + WLocalDateTime(const WLocale& locale = WLocale::currentLocale()); + + /*! \brief Creates a datetime given a date and time. + * + * The datetime is valid if both \p date and \p time are valid and the + * given time could correctly be interpreted in the local time zone: + * - the time exists (on the day that the time changes from 2AM to 3AM the time 2.30AM does not exist) + * - the time is unambiguous (on the day that time changes from 3AM to 2AM, the time 2.30AM is not unambigous). In the latter case, setDateTime() may be used + * indicating whether the time is day light savings time or not. + */ + WLocalDateTime(const WDate& date, const WTime& time, + const WLocale& locale = WLocale::currentLocale()); + + /*! \brief Returns if this datetime is Null. + * + * A null time is also invalid. + * + * \sa isValid(), WDateTime() + */ + bool isNull() const; + + /*! \brief Returns if this datetime is valid. + * + * A date time is only valid if its date and time parts are valid. + */ + bool isValid() const; + + /*! \brief Sets the local date and time. + * + * The datetime is valid if both \p date and \p time are valid and the + * given time could correctly be interpreted in the local time zone: + * - the time exists (on the day that the time changes from 2AM to + * 3AM the time 2.30AM does not exist) + * - the time is unambiguous (on the day that time changes from 3AM to 2AM. + * the time 2.30AM is not unambigous). In the latter case, + * setDateTime(const WDate&, const WTime, bool) may still be used. + */ + void setDateTime(const WDate& date, const WTime& time); + + /*! \brief Sets the local date and time, indicating day-light savings. + * + * The datetime is valid if both \p date and \p time are valid and the + * given time could correctly be interpreted in the local time zone with + * the indicated day light savings indication. + */ + void setDateTime(const WDate& date, const WTime& time, bool dst); + + /*! \brief Sets the date part. + * + * Changes the date part part, leaving the time unmodified. If no time + * was set, it is set to 00:00. + * + * \sa setTime() + */ + void setDate(const WDate& date); + + /*! \brief Returns the date part. + * + * Returns the date part. + * + * \sa time() + */ + const WDate date() const; + + /*! \brief Sets the time part. + * + * If no valid date is set, the time is not set either. + * + * \sa setDate() + */ + void setTime(const WTime& time); + + /*! \brief Returns the time part. + * + * \sa setTime() + */ + const WTime time() const; + + /*! \brief Returns the time zone offset. + * + * This returns the time zone offset to UTC in minutes. A positive value + * thus means that the local time is ahead of UTC. + */ + int timeZoneOffset() const; + + /*! \brief Returns the time zone. + * + * \sa WLocale::setTimeZone() + */ + std::string timeZone() const; + + /*! \brief Converts to UTC. + * + * This is the reverse of WDateTime::toLocalTime() + */ + WDateTime toUTC() const; + + /*! \brief Formats this datetime to a string using the locale format. + * + * \sa WLocale::dateTimeFormat() + */ + WT_USTRING toString() const; + + /*! \brief Formats this datetime to a string in a custom format. + * + * \sa WLocale::dateTimeFormat() + */ + WT_USTRING toString(const WT_USTRING& format) const; + + /*! \brief Parses a string to a time using the locale format. + * + * \sa WLocale::dateTimeFormat() + */ + static WLocalDateTime fromString(const WT_USTRING& s, + const WLocale& locale = WLocale::currentLocale()); + + /*! \brief Reports the current datetime. + * + * This method returns the current datetime in the user locale. + */ + static WLocalDateTime currentDateTime(const WLocale& locale = WLocale::currentLocale()); + + /*! \brief Reports the current local server time. + */ + static WLocalDateTime currentServerDateTime(); + + /*! \brief Compares two values. + */ + bool operator< (const WLocalDateTime& other) const; + + /*! \brief Compares two datetime values. + */ + bool operator== (const WLocalDateTime& other) const; + + /*! \brief Compares two datetime values. + */ + bool operator!= (const WLocalDateTime& other) const; + +private: + boost::local_time::local_date_time datetime_; + WT_USTRING format_; + + WLocalDateTime(const boost::local_time::local_date_time& dt, + const WT_USTRING& format); + + friend class WDateTime; + + void setInvalid(); + + static WLocalDateTime currentTime(int offset); +}; + +} + +#endif // WLOCAL_DATE_TIME_H_ diff --git a/src/Wt/WLocalDateTime.C b/src/Wt/WLocalDateTime.C new file mode 100644 index 0000000000..2539201f0f --- /dev/null +++ b/src/Wt/WLocalDateTime.C @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2013 Emweb bvba, Kessel-Lo, Belgium. + * + * See the LICENSE file for terms of use. + */ + +#include "Wt/WApplication" +#include "Wt/WEnvironment" +#include "Wt/WStringStream" +#include "Wt/WLocalDateTime" +#include "Wt/WLogger" +#include "Wt/WDateTime" +#include "Wt/WDate" +#include "Wt/WTime" + +namespace Wt { + +LOGGER("WDateTime"); + +WLocalDateTime::WLocalDateTime(const boost::local_time::local_date_time& dt, + const WT_USTRING& format) + : datetime_(dt), + format_(format) +{ } + +WLocalDateTime::WLocalDateTime(const WLocale& locale) + : datetime_(boost::posix_time::ptime(), locale.time_zone_ptr()), + format_(locale.dateTimeFormat()) +{ } + +/* + * Todo, add overload which indicates DST + */ +WLocalDateTime::WLocalDateTime(const WDate& date, const WTime& time, + const WLocale& locale) + : datetime_(boost::posix_time::ptime(), locale.time_zone_ptr()), + format_(locale.dateTimeFormat()) +{ + setDateTime(date, time); +} + +bool WLocalDateTime::isNull() const +{ + return datetime_.is_not_a_date_time(); +} + +bool WLocalDateTime::isValid() const +{ + return !datetime_.is_special(); +} + +void WLocalDateTime::setDateTime(const WDate& date, const WTime& time) +{ + if (date.isValid() && time.isValid()) { + datetime_ = boost::local_time::local_date_time + (date.toGregorianDate(), time.toTimeDuration(), + datetime_.zone(), + boost::local_time::local_date_time::NOT_DATE_TIME_ON_ERROR); + + if (datetime_.is_not_a_date_time()) { + LOG_WARN("Invalid local date time (" + << date.toString() << " " + << time.toString() << ") in zone " + << (datetime_.zone() ? + datetime_.zone()->to_posix_string() : + "")); + + setInvalid(); + } + } else + setInvalid(); +} + +void WLocalDateTime::setInvalid() +{ + datetime_ + = boost::local_time::local_date_time + (boost::posix_time::ptime(boost::posix_time::neg_infin), + datetime_.zone()); +} + +void WLocalDateTime::setDateTime(const WDate& date, const WTime& time, + bool dst) +{ + if (date.isValid() && time.isValid()) { + try { + datetime_ = boost::local_time::local_date_time + (date.toGregorianDate(), time.toTimeDuration(), + datetime_.zone(), dst); + if (datetime_.is_not_a_date_time()) { + LOG_WARN("Invalid local date time (" + << date.toString() << " " + << time.toString() << " " + << "dst=" << dst << ") in zone " + << (datetime_.zone() ? + datetime_.zone()->to_posix_string() : + "")); + setInvalid(); + } + } catch (std::exception& e) { + setInvalid(); + } + } else + setInvalid(); +} + +void WLocalDateTime::setDate(const WDate& date) +{ + if (isValid()) + setDateTime(date, time()); + else + setDateTime(date, WTime(0, 0)); +} + +const WDate WLocalDateTime::date() const +{ + if (isValid()) { + boost::gregorian::date d = datetime_.local_time().date(); + return WDate(d); + } else + return WDate(); +} + +void WLocalDateTime::setTime(const WTime& time) +{ + if (isValid()) + setDateTime(date(), time); +} + +const WTime WLocalDateTime::time() const +{ + if (isValid()) { + boost::posix_time::time_duration d = datetime_.local_time().time_of_day(); + boost::posix_time::time_duration::fractional_seconds_type ticks_per_msec = + boost::posix_time::time_duration::ticks_per_second() / 1000; + boost::posix_time::time_duration::fractional_seconds_type msec = + d.fractional_seconds(); + msec = msec / ticks_per_msec; + return WTime(d.hours(), d.minutes(), d.seconds(), (int)msec); + } else + return WTime(); +} + +WDateTime WLocalDateTime::toUTC() const +{ + if (isValid()) + return WDateTime(datetime_.utc_time()); + else + return WDateTime(); +} + +WT_USTRING WLocalDateTime::toString() const +{ + return toString(format_); +} + +int WLocalDateTime::timeZoneOffset() const +{ + return (datetime_.local_time() - datetime_.utc_time()) + .total_seconds() / 60; +} + +std::string WLocalDateTime::timeZone() const +{ + if (datetime_.zone()) + return datetime_.zone()->to_posix_string(); + else + return std::string(); +} + +WT_USTRING WLocalDateTime::toString(const WT_USTRING& format) const +{ + WDateTime dt(datetime_.local_time()); + WDate d = dt.date(); + WTime t = dt.time(); + + return WDateTime::toString(&d, &t, format, true, timeZoneOffset()); +} + +WLocalDateTime WLocalDateTime::fromString(const WT_USTRING& s, + const WLocale& locale) +{ + WDateTime t = WDateTime::fromString(s, locale.dateTimeFormat()); + + return WLocalDateTime(t.date(), t.time(), locale); +} + +WLocalDateTime WLocalDateTime::currentDateTime(const WLocale& locale) +{ + WApplication *app = WApplication::instance(); + + if (locale.timeZone().empty() && app) + return currentTime(app->environment().timeZoneOffset()); + else + return WDateTime::currentDateTime().toLocalTime(locale); +} + +WLocalDateTime WLocalDateTime::currentTime(int offset) +{ + /* + * Fabricate a time zone that reflects the time zone offset. + * This is entirely accurate for the 'current' date time. + */ + WStringStream tz; + tz << "LOC"; + if (offset < 0) { + offset = -offset; + tz << '-'; + } + + WTime t(0, 0, 0); + t = t.addSecs(offset * 60); + tz << t.hour(); + if (t.minute() != 0) + tz << ':' << t.minute(); + + WLocale tmpLocale; + tmpLocale.setTimeZone(tz.str()); + return WDateTime::currentDateTime().toLocalTime(tmpLocale); +} + +WLocalDateTime WLocalDateTime::currentServerDateTime() +{ + boost::posix_time::ptime utcNow + = boost::posix_time::second_clock::universal_time(); + boost::posix_time::ptime localNow + = boost::posix_time::second_clock::local_time(); + + int offset = (localNow - utcNow).total_seconds() / 60; + + return currentTime(offset); +} + +bool WLocalDateTime::operator==(const WLocalDateTime& other) const +{ + return datetime_ == other.datetime_; +} + +bool WLocalDateTime::operator!=(const WLocalDateTime& other) const +{ + return datetime_ != other.datetime_; +} + +bool WLocalDateTime::operator<(const WLocalDateTime& other) const +{ + return datetime_ < other.datetime_; +} + +} diff --git a/src/Wt/WLocale b/src/Wt/WLocale index 23eb1d643a..a0a6ff3a9b 100644 --- a/src/Wt/WLocale +++ b/src/Wt/WLocale @@ -10,6 +10,8 @@ #include #include +#include + namespace Wt { /*! \class WLocale Wt/WLocale Wt/WLocale @@ -85,10 +87,46 @@ public: */ WT_UCHAR groupSeparator() const { return groupSeparator_; } + /*! \brief Sets the time zone. + * + * This sets the time zone (used by the client). The \p + * posixTimeZone must be an IEEE Std 1003.1 zone string in the form + * of: "std offset dst [offset],start[/time],end[/time]" and + * specifies all aspects of UTC to local time conversion for the + * user, including taking into account daylight savings time. + * + * e.g. "EST-5EDT,M4.1.0,M10.5.0" represents the time zone configuration + * suitable for USA EastCoast (NYC). + * + * The time zone is not provided by the browser and therefore you + * will need to ask the user to select an appropriate time + * zone. This can be done using for example + * boost::local_time::tz_database by asking the user to select his + * region from this database, and retrieving the corresponding time + * zone string. You may want to propose a suitable default time zone + * by using WEnvironment::timeZoneOffset() and pre-selecting a + * region that matches this offset. + * + * The default value is empty. + * + * The timezone is used by WLocalDateTime. + * + * \sa WEnvironment::timeZoneOffset() + */ + void setTimeZone(const std::string& posixTimeZone); + + /*! \brief Returns the user's time zone. + * + * \sa setTimeZone() + */ + std::string timeZone() const; + /*! \brief Sets the date format. * * Sets the default format for date entry, e.g. as used in * WDateValidator. See WDate::toString() for the supported syntax. + * + * The default date format is "yyyy-MM-dd". */ void setDateFormat(const WT_USTRING& format); @@ -98,6 +136,22 @@ public: */ WT_USTRING dateFormat() const { return dateFormat_; } + /*! \brief Sets the date/time format. + * + * Sets the format for a localized time stamp (using + * WLocalDateTime::toString()). See WDateTime::toString() for the + * supported syntax. + * + * The default date/time format is "yyyy-MM-dd HH:mm:ss". + */ + void setDateTimeFormat(const WT_USTRING& format); + + /*! \brief Returns the date/time format. + * + * Returns the date/time format. + */ + WT_USTRING dateTimeFormat() const { return dateTimeFormat_; } + /*! \brief Returns the locale name. * * This is the name of the locale that was set through the @@ -150,10 +204,14 @@ public: */ WT_USTRING toString(double value) const; + boost::local_time::time_zone_ptr time_zone_ptr() const { return time_zone_; } + private: std::string name_; WT_UCHAR decimalPoint_, groupSeparator_; - WT_USTRING dateFormat_; + WT_USTRING dateFormat_, dateTimeFormat_; + + boost::local_time::time_zone_ptr time_zone_; bool isDefaultNumberLocale() const; diff --git a/src/Wt/WLocale.C b/src/Wt/WLocale.C index 4a105d8b73..ce814a7188 100644 --- a/src/Wt/WLocale.C +++ b/src/Wt/WLocale.C @@ -15,28 +15,35 @@ const WLocale systemLocale; WLocale::WLocale() : decimalPoint_("."), groupSeparator_(""), - dateFormat_("yyyy-MM-dd") + dateFormat_("yyyy-MM-dd"), + dateTimeFormat_("yyyy-MM-dd HH:mm:ss") { } WLocale::WLocale(const WLocale& other) : name_(other.name_), decimalPoint_(other.decimalPoint()), groupSeparator_(other.groupSeparator()), - dateFormat_(other.dateFormat()) + dateFormat_(other.dateFormat()), + dateTimeFormat_(other.dateTimeFormat()), + time_zone_(other.time_zone_) { } WLocale::WLocale(const std::string& name) : name_(name), decimalPoint_(systemLocale.decimalPoint()), groupSeparator_(systemLocale.groupSeparator()), - dateFormat_(systemLocale.dateFormat()) + dateFormat_(systemLocale.dateFormat()), + dateTimeFormat_(systemLocale.dateTimeFormat()), + time_zone_(systemLocale.time_zone_) { } WLocale::WLocale(const char *name) : name_(name), decimalPoint_(systemLocale.decimalPoint()), groupSeparator_(systemLocale.groupSeparator()), - dateFormat_(systemLocale.dateFormat()) + dateFormat_(systemLocale.dateFormat()), + dateTimeFormat_(systemLocale.dateTimeFormat()), + time_zone_(systemLocale.time_zone_) { } void WLocale::setDecimalPoint(WT_UCHAR c) @@ -54,6 +61,19 @@ void WLocale::setDateFormat(const WT_USTRING& format) dateFormat_ = format; } +void WLocale::setTimeZone(const std::string& posixTimeZone) +{ + time_zone_.reset(new boost::local_time::posix_time_zone(posixTimeZone)); +} + +std::string WLocale::timeZone() const +{ + if (time_zone_) + return time_zone_->to_posix_string(); + else + return std::string(); +} + const WLocale& WLocale::currentLocale() { WApplication *app = WApplication::instance(); diff --git a/src/Wt/WNavigationBar b/src/Wt/WNavigationBar index db451b14b0..4a552da9c8 100644 --- a/src/Wt/WNavigationBar +++ b/src/Wt/WNavigationBar @@ -86,6 +86,8 @@ private: void addWrapped(WWidget *widget, AlignmentFlag alignment, const char *wrapClass); void align(WWidget *widget, AlignmentFlag alignment); + + bool animatedResponsive() const; }; } diff --git a/src/Wt/WNavigationBar.C b/src/Wt/WNavigationBar.C index 7ddecd337b..16e25cd9d0 100644 --- a/src/Wt/WNavigationBar.C +++ b/src/Wt/WNavigationBar.C @@ -4,6 +4,8 @@ * See the LICENSE file for terms of use. */ +#include "Wt/WApplication" +#include "Wt/WEnvironment" #include "Wt/WLineEdit" #include "Wt/WMenu" #include "Wt/WNavigationBar" @@ -13,6 +15,29 @@ namespace Wt { LOGGER("WNavigationBar"); +class NavContainer : public WContainerWidget +{ +public: + NavContainer(WContainerWidget *parent = 0) + : WContainerWidget(parent) + { } + + virtual void setHidden(bool hidden, + const WAnimation& animation = WAnimation()) + { + if (animation.empty()) { + /* Comply with bootstrap responsive CSS assumptions */ + /* When animations are used, this is actually done in wtAnimatedHidden */ + if (hidden) + setHeight(0); + else + setHeight(WLength::Auto); + } + + WContainerWidget::setHidden(hidden, animation); + } +}; + WNavigationBar::WNavigationBar(WContainerWidget *parent) : WTemplate(tr("Wt.WNavigationBar.template"), parent) { @@ -21,7 +46,7 @@ WNavigationBar::WNavigationBar(WContainerWidget *parent) bindEmpty("collapse-button"); bindEmpty("expand-button"); bindEmpty("title-link"); - bindWidget("contents", new WContainerWidget()); + bindWidget("contents", new NavContainer()); implementStateless(&WNavigationBar::collapseContents, &WNavigationBar::undoExpandContents); @@ -66,6 +91,7 @@ void WNavigationBar::setResponsive(bool responsive) } contents->addStyleClass("nav-collapse"); + contents->hide(); /* Comply with bootstrap responsive CSS assumptions */ contents->setJavaScriptMember @@ -144,11 +170,15 @@ void WNavigationBar::collapseContents() collapseButton->hide(); expandButton->show(); - if (canOptimizeUpdates()) - contents->show(); /* We are collapsed only in appearance */ - else - contents->animateHide(WAnimation(WAnimation::SlideInFromTop, - WAnimation::Ease)); + if (!animatedResponsive()) + contents->hide(); + else { + if (canOptimizeUpdates()) + contents->show(); /* We are collapsed only in appearance */ + else + contents->animateHide(WAnimation(WAnimation::SlideInFromTop, + WAnimation::Ease)); + } } void WNavigationBar::expandContents() @@ -162,11 +192,15 @@ void WNavigationBar::expandContents() collapseButton->show(); expandButton->hide(); - if (canOptimizeUpdates()) + if (!animatedResponsive()) contents->show(); - else - contents->animateShow(WAnimation(WAnimation::SlideInFromTop, - WAnimation::Ease)); + else { + if (canOptimizeUpdates()) + contents->show(); + else + contents->animateShow(WAnimation(WAnimation::SlideInFromTop, + WAnimation::Ease)); + } } void WNavigationBar::undoExpandContents() @@ -180,7 +214,10 @@ void WNavigationBar::undoExpandContents() collapseButton->hide(); expandButton->show(); - contents->show(); /* We are collapsed only in appearance */ + if (!animatedResponsive()) + contents->hide(); + else + contents->show(); /* We are collapsed only in appearance */ } WInteractWidget *WNavigationBar::createExpandButton() @@ -196,4 +233,10 @@ WInteractWidget *WNavigationBar::createCollapseButton() return createExpandButton(); } +bool WNavigationBar::animatedResponsive() const +{ + return WApplication::instance()->environment().supportsCss3Animations() && + WApplication::instance()->environment().ajax(); +} + } diff --git a/src/Wt/WObject b/src/Wt/WObject index b3c606f33c..db92b09402 100644 --- a/src/Wt/WObject +++ b/src/Wt/WObject @@ -11,6 +11,105 @@ #include #include +namespace Wt { + /*! \brief Namespace for signal/slot implementation + * + * Namespace Wt::Signals offers classes for connecting signals to handlers + * of those signals. The underlying implementation is configurable, and + * some details may be different from one configuration to the other, but + * the common basis is documented here. + * + * For now, %Wt's signals implementations are based on boost's signals + * implementation. %Wt wraps these implementations in its own Wt::Signal + * class, so you don't interact immediately with this object. Refer to + * the documentation of Wt::Signal for the API description. Connection + * lifetime management however depends on the underlying signals + * implementation. + * + * A call to Wt::Signal::connect() returns a class of type + * Wt::Signals::connection. This class allows for manual connection + * management: if you ever want to disconnect a signal, keep a copy of the + * connection object and invoke connection.disconnect(). Usually, you will + * use automatic connection management as described below. + * + * %Wt's signal/slot implementation offers automatic connection management + * through object lifetime tracking. With automatic connection management, + * a signal will be disconnected when the signal's target object is + * deleted. This lifetime tracking works, provided that the classes whose + * lifetime has to be tracked inherit from WObject, and that the + * Wt::WSignal class is able to detect the presence of the WObject + * class. + * + * Since WObject is a base class for WWidget, the first condition + * is fullfilled for all of %Wt's widgets, classes inheriting from widgets, + * and other classes that inherit from WObject. For your own classes, you may + * inherit from WObject in order to activate automatic + * lifeteime tracking. + * + * The second condition, the ability for the signals library to detect this + * WObject class, you have to know for which situations lifetime tracking + * is supported - for all other cases, assume it's not supported. The ability + * for the signals implementation to detect the lifetime tracked objects, + * depends on how you invoke the connect() method. + * + * Any connect call where you pass the receiver of the signal directly + * to the Wt::WSignal::connect() method as first parameter, is ok. If the + * receiver inherits from WObject, the signal will be disconnected when + * the receiver is deleted. + * + * In general, connection tracking does not work for the + * Wt::WSignal::connect(const F &function) method. The only exception is + * when the function pointer object was created by boost::bind, in which case + * the receiver object (inheriting from WObject) will be tracked. + * + * Practical guidelines: + * - use WSignal::connect with an explicit target parameter for connecting + * to slots which are member methods and for which no binding is required. + * This first parameter is a class derived from WObject (or inheriting + * from Wt::Signals::trackable, for boost signal/slot implementations) + * - use std::bind for lambda functions, as these are not as good supported by + * boost::bind as std::bind. Remember that automatic connection management + * will not work - connecting to the signals of the receiver itself or a + * signal of one of its signals is generally safe, but connecting to a + * signal from outside your own descendants likely requires additional + * measurements to ensure proper lifetime tracking. + * - use boost::bind for all other bindings, as this will offer you automatic + * lifetime management. + * + * %Wt used boost.signal (v1) as underlying implementation for its signal/slot + * system. Since boost 1.54, boost.signal has been marked deprecated, being + * replaced by boost.signals2. The WT_SIGNALS_IMPLEMENTATION cmake defines + * allows you to switch between the available implementation for Wt::Signals. + * There may be other signal/slot libraries supported in the future. With + * the boost implementations, %Wt relies on boost::trackable for the + * implementation of object lifetime tracking for connection management, + * meaning that WObject inherits from boost.trackable. + * + * The classes of Wt::Signals are to be considered as not thread safe. Since + * Wt has a per-session locking mechanism, under the form of the + * WApplication::UpdateLock, this is hardly an issue. The boost.signals2 + * implementation offers some degree of thread-safety; please ensure to + * understand its details before relying on it. + * + * \ingroup signalslot + */ + namespace Signals { +#ifdef DOXYGEN_ONLY + /*! \brief Base class for lifetime-tracked objects + * + */ + typedef implementation_defined trackable; + + /*! \brief Implementation-defined class representing a connection + * + * This object can be used to manually disconnect a signal-slot connection. + * For boost signals, the disconnect() member disconnects the connection. + */ + typedef implementation_defined connection; +#endif + } +} + #if defined(WT_USE_BOOST_SIGNALS) #include @@ -37,7 +136,14 @@ namespace Wt { #include #include namespace Wt { - namespace Signals = ::boost::signals2; + namespace Signals { + using boost::signals2::signal; + using boost::signals2::trackable; + using boost::signals2::connection; + using boost::signals2::at_front; + // Note: signal0-6 are not available in signals2 if the compiler supports + // variadic templates + } } #endif @@ -189,7 +295,7 @@ public: * * \sa id() */ - void setObjectName(const std::string& name); + virtual void setObjectName(const std::string& name); /*! \brief Returns the object name. * diff --git a/src/Wt/WServer b/src/Wt/WServer index f4ad9b089a..876ae89710 100644 --- a/src/Wt/WServer +++ b/src/Wt/WServer @@ -379,6 +379,21 @@ public: const std::string& value); #endif // WT_TARGET_JAVA + /*! \brief Sets the resource object that provides localized strings. + * + * This is used only for WString::tr() used from within static + * resources. + * + * The default value is 0. + */ + void setLocalizedStrings(WLocalizedStrings *stringResolver); + + /*! \brief Sets the resource object that provides localized strings. + * + * \sa setLocalizedStrings() + */ + WLocalizedStrings *localizedStrings() { return localizedStrings_; } + #ifndef WT_TARGET_JAVA WT_API WLogger& logger() { return logger_; } WT_API WLogEntry log(const std::string& type) const; @@ -402,6 +417,7 @@ private: std::string application_, configurationFile_, appRoot_, description_; Configuration *configuration_; + WLocalizedStrings *localizedStrings_; bool ownsIOService_; WIOService *ioService_; diff --git a/src/Wt/WServer.C b/src/Wt/WServer.C index 5523b4af1c..1009df68cf 100644 --- a/src/Wt/WServer.C +++ b/src/Wt/WServer.C @@ -45,6 +45,7 @@ void WServer::init(const std::string& wtApplicationPath, ioService_ = 0; webController_ = 0; configuration_ = 0; + localizedStrings_ = 0; logger_.addField("datetime", false); logger_.addField("app", false); @@ -64,10 +65,17 @@ void WServer::destroy() delete webController_; delete configuration_; + delete localizedStrings_; instance_ = 0; } +void WServer::setLocalizedStrings(WLocalizedStrings *stringResolver) +{ + delete localizedStrings_; + localizedStrings_ = stringResolver; +} + void WServer::setIOService(WIOService& ioService) { if (ioService_) { diff --git a/src/Wt/WSignal b/src/Wt/WSignal index 0a78b297c7..46fabeeb31 100644 --- a/src/Wt/WSignal +++ b/src/Wt/WSignal @@ -62,12 +62,24 @@ class JavaScriptEvent; signals. To react to one of these events, the programmer connects a self-defined or already existing slot to such a signal. - To connect a signal from multiple senders to a single slot, the - \link WSignalMapper WSignalMapper \endlink class may be - convenient. Using that class you can still identify the sender, - using an arbitrary property of the sender which you define when - making the connection. That property is passed as an additional - slot argument. + To connect a signal from multiple senders to a single slot, we + recommend the use of boost::bind() to identify the sender (or + otherwise the intention) of the signal. The use of class + \link WSignalMapper WSignalMapper \endlink is discouraged. + + \if cpp + Usage example: + \code + std::vector buttons = ...; + for(unsigned i = 0; i < buttons.size(); ++i) { + buttons[i]->clicked().connect(boost::bind(&Keyboard::handleClick, i)); + } + + void Keyboard::handleClick(int i) { + t->setText(WString("You pressed button {1}").args(i)); + } + \endcode + \endif */ /*! \brief Abstract base class of a signal. @@ -252,7 +264,7 @@ public: * * When the receiver function is an object method, the signal will * automatically be disconnected when the object is deleted, as long as the - * object inherits from WObject or Wt::Signals::trackable. + * object inherits from WObject (or Wt::Signals::trackable). * * The function may leave up to N parameters unbound * (e.g. using boost::bind placeholders _1 to _N) that may be bound @@ -398,26 +410,12 @@ public: private: Signal(const Signal&); -#ifndef _MSC_VER - // Up to 6 arguments allowed - typedef boost::signal6 BoostSignalType; +#ifdef WT_CNOR + typedef Wt::Signals::signal6 BoostSignalType; #else -# if _MSC_VER >= 1700 - // MSVS 2012's std::bind implementation may support only 5 arguments - // http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx -# if defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 6) - // default MSVS 2012: defaults to 5. We assume no-one will lower this. - typedef Wt::Signals::signal5 BoostSignalType; -# define A6_MUST_BE_NOCLASS -# else - // You can reconfigure this, and MS promised to raise _VARIADIC_MAX - // in future compilers - typedef boost::signal6 BoostSignalType; -# endif -# else - typedef boost::signal6 BoostSignalType; -# endif + typedef Wt::Signals::signal BoostSignalType; #endif + BoostSignalType *impl_; }; @@ -562,7 +560,11 @@ protected: * Dummy signal used for knowing if stateless connections are still * connected. */ +#ifndef WT_CNOR + Wt::Signals::signal dummy_; +#else Wt::Signals::signal0 dummy_; +#endif EventSignalBase(const char *name, WObject *sender, bool autoLearn); @@ -639,7 +641,7 @@ public: * * When the receiver function is an object method, the signal will * automatically be disconnected when the object is deleted, as long as the - * object inherits from WObject or Wt::Signals::trackable. + * object inherits from WObject (or Wt::Signals::trackable). * * The function may leave 1 parameters unbound (e.g. using * boost::bind placeholders _1) that may be bound to the event @@ -739,7 +741,11 @@ public: WObject::Method method); private: +#ifndef WT_CNOR + typedef Wt::Signals::signal BoostSignalType; +#else typedef Wt::Signals::signal1 BoostSignalType; +#endif BoostSignalType dynamic_; void processDynamic(const JavaScriptEvent& e); diff --git a/src/Wt/WSortFilterProxyModel.C b/src/Wt/WSortFilterProxyModel.C index b85f3033bb..974e93a559 100644 --- a/src/Wt/WSortFilterProxyModel.C +++ b/src/Wt/WSortFilterProxyModel.C @@ -456,7 +456,11 @@ void WSortFilterProxyModel::sourceRowsAboutToBeInserted * at all in changes to a node which he has not yet 'opened' ..., but * strictly spoken we are obliged to propagate these changes ! */ - itemFromIndex(mapFromSource(parent)); + WModelIndex pparent = mapFromSource(parent); + // distinguish between invalid parent being root item or being filtered out + if (parent.isValid() && !pparent.isValid()) + return; + itemFromIndex(pparent); } void WSortFilterProxyModel::sourceRowsInserted(const WModelIndex& parent, @@ -470,6 +474,9 @@ void WSortFilterProxyModel::sourceRowsInserted(const WModelIndex& parent, int count = end - start + 1; WModelIndex pparent = mapFromSource(parent); + // distinguish between invalid parent being root item or being filtered out + if (parent.isValid() && !pparent.isValid()) + return; Item *item = itemFromIndex(pparent); // Shift existing entries in proxyRowMap, and reserve place in sourceRowMap @@ -501,6 +508,9 @@ void WSortFilterProxyModel::sourceRowsAboutToBeRemoved (const WModelIndex& parent, int start, int end) { WModelIndex pparent = mapFromSource(parent); + // distinguish between invalid parent being root item or being filtered out + if (parent.isValid() && !pparent.isValid()) + return; Item *item = itemFromIndex(pparent); for (int row = start; row <= end; ++row) { @@ -523,6 +533,9 @@ void WSortFilterProxyModel::sourceRowsRemoved(const WModelIndex& parent, shiftModelIndexes(parent, start, -count, mappedIndexes_); WModelIndex pparent = mapFromSource(parent); + // distinguish between invalid parent being root item or being filtered out + if (parent.isValid() && !pparent.isValid()) + return; Item *item = itemFromIndex(pparent); // Shift existing entries in proxyRowMap, and remove entries in sourceRowMap @@ -547,6 +560,9 @@ void WSortFilterProxyModel::sourceDataChanged(const WModelIndex& topLeft, && sortKeyColumn_ <= bottomRight.column()); WModelIndex parent = mapFromSource(topLeft.parent()); + // distinguish between invalid parent being root item or being filtered out + if (topLeft.parent().isValid() && !parent.isValid()) + return; Item *item = itemFromIndex(parent); for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { diff --git a/src/Wt/WSpinBox.C b/src/Wt/WSpinBox.C index 43a7d72506..d425c51a65 100644 --- a/src/Wt/WSpinBox.C +++ b/src/Wt/WSpinBox.C @@ -16,7 +16,8 @@ WSpinBox::WSpinBox(WContainerWidget *parent) value_(-1), min_(0), max_(99), - step_(1) + step_(1), + valueChanged_(this) { setValidator(createValidator()); setValue(0); diff --git a/src/Wt/WString b/src/Wt/WString index 0c56d8cce2..60cf1fc0cb 100644 --- a/src/Wt/WString +++ b/src/Wt/WString @@ -503,12 +503,12 @@ private: std::string utf8_; - void resolveKey(const std::string& key, std::string& result) const; + std::string resolveKey() const; void makeLiteral(); struct Impl { - std::string key_; + std::string key_; std::vector arguments_; ::int64_t n_; diff --git a/src/Wt/WString.C b/src/Wt/WString.C index a4b1b73b02..5c19d7d7b7 100644 --- a/src/Wt/WString.C +++ b/src/Wt/WString.C @@ -10,6 +10,7 @@ #include #include "Wt/WApplication" +#include "Wt/WServer" #include "Wt/WString" #include "Wt/WStringUtil" #include "Wt/WWebWidget" @@ -219,21 +220,32 @@ void WString::checkUTF8Encoding(std::string& value) } } -void WString::resolveKey(const std::string& key, std::string& result) const +std::string WString::resolveKey() const { - bool resolved = false; + std::string result; + WLocalizedStrings *ls = 0; WApplication *app = WApplication::instance(); - if (app) { - if (impl_->n_ == -1) - resolved = app->localizedStrings_->resolveKey(impl_->key_, result); - else - resolved = app->localizedStrings_->resolvePluralKey(impl_->key_, result, - impl_->n_); + if (app) + ls = app->localizedStrings_; + + if (!ls) { + WServer *server = WServer::instance(); + if (server) + ls = server->localizedStrings(); + } + + if (ls) { + if (impl_->n_ == -1) { + if (ls->resolveKey(impl_->key_, result)) + return result; + } else { + if (ls->resolvePluralKey(impl_->key_, result, impl_->n_)) + return result; + } } - if (!resolved) - result = "??" + key + "??"; + return "??" + impl_->key_ + "??"; } std::string WString::toUTF8() const @@ -242,7 +254,7 @@ std::string WString::toUTF8() const std::string result = utf8_; if (!impl_->key_.empty()) - resolveKey(impl_->key_, result); + result = resolveKey(); for (unsigned i = 0; i < impl_->arguments_.size(); ++i) { std::string key = '{' + boost::lexical_cast(i+1) + '}'; @@ -533,7 +545,7 @@ bool operator!= (const wchar_t *lhs, const WString& rhs) void WString::makeLiteral() { if (!literal()) { - resolveKey(impl_->key_, utf8_); + utf8_ = resolveKey(); impl_->key_ = std::string(); } } diff --git a/src/Wt/WStringListModel b/src/Wt/WStringListModel index 3006557264..ad52c9fb97 100644 --- a/src/Wt/WStringListModel +++ b/src/Wt/WStringListModel @@ -75,13 +75,19 @@ public: */ const std::vector& stringList() const { return displayData_; } - /*! \brief Returns the flags for an item. + /*! \brief Sets model flags for an item. * - * This method is reimplemented to return \link Wt::ItemIsSelectable + * The default item flags are \link Wt::ItemIsSelectable * ItemIsSelectable\endlink | \link Wt::ItemIsEditable * ItemIsEditable\endlink. + */ + void setFlags(int index, WFlags flags); + + /*! \brief Returns the flags for an item. + * + * This method is reimplemented to return flags set in setFlags(). * - * \sa Wt::ItemFlag + * \sa setFlags() */ virtual WFlags flags(const WModelIndex& index) const; @@ -111,6 +117,7 @@ private: std::vector displayData_; std::vector *otherData_; + std::vector > flags_; }; } diff --git a/src/Wt/WStringListModel.C b/src/Wt/WStringListModel.C index 99b72d3aaf..a244fe449b 100644 --- a/src/Wt/WStringListModel.C +++ b/src/Wt/WStringListModel.C @@ -77,6 +77,8 @@ void WStringListModel::setStringList(const std::vector& strings) beginRemoveRows(WModelIndex(), newSize, currentSize - 1); displayData_ = strings; + flags_.clear(); + delete otherData_; otherData_ = 0; @@ -144,9 +146,21 @@ bool WStringListModel::setData(const WModelIndex& index, return true; } +void WStringListModel::setFlags(int row, WFlags flags) +{ + if (flags_.empty()) + flags_.insert(flags_.begin(), rowCount(), + ItemIsSelectable | ItemIsEditable); + + flags_[row] = flags; +} + WFlags WStringListModel::flags(const WModelIndex& index) const { - return ItemIsSelectable | ItemIsEditable; + if (flags_.empty()) + return ItemIsSelectable | ItemIsEditable; + else + return flags_[index.row()]; } bool WStringListModel::insertRows(int row, int count, const WModelIndex& parent) @@ -154,6 +168,9 @@ bool WStringListModel::insertRows(int row, int count, const WModelIndex& parent) if (!parent.isValid()) { beginInsertRows(parent, row, row + count - 1); displayData_.insert(displayData_.begin() + row, count, WString()); + if (!flags_.empty()) + flags_.insert(flags_.begin() + row, count, + ItemIsSelectable | ItemIsEditable); if (otherData_) otherData_->insert(otherData_->begin() + row, count, DataMap()); endInsertRows(); @@ -169,6 +186,8 @@ bool WStringListModel::removeRows(int row, int count, const WModelIndex& parent) beginRemoveRows(parent, row, row + count - 1); displayData_.erase(displayData_.begin() + row, displayData_.begin() + row + count); + if (!flags_.empty()) + flags_.erase(flags_.begin() + row, flags_.begin() + row + count); if (otherData_) otherData_->erase(otherData_->begin() + row, otherData_->begin() + row + count); @@ -183,7 +202,7 @@ void WStringListModel::sort(int column, SortOrder order) { layoutAboutToBeChanged().emit(); - if (!otherData_) { + if (!otherData_ && flags_.empty()) { if (order == AscendingOrder) Utils::sort(displayData_); else @@ -203,17 +222,29 @@ void WStringListModel::sort(int column, SortOrder order) std::vector displayData; displayData.resize(rowCount()); - std::vector *otherData = new std::vector(); - otherData->resize(rowCount()); + + std::vector > flags; + if (!flags_.empty()) + flags.resize(rowCount()); + + std::vector *otherData = 0; + if (otherData_) { + otherData = new std::vector(); + otherData->resize(rowCount()); + } for (unsigned i = 0; i < permutation.size(); ++i) { displayData[i] = displayData_[permutation[i]]; - (*otherData)[i] = (*otherData_)[permutation[i]]; + if (otherData) + (*otherData)[i] = (*otherData_)[permutation[i]]; + if (!flags.empty()) + flags[i] = flags_[permutation[i]]; } displayData_ = displayData; delete otherData_; otherData_ = otherData; + flags_ = flags; } layoutChanged().emit(); diff --git a/src/Wt/WStringStream b/src/Wt/WStringStream index a2c2d205e3..2cc246e598 100644 --- a/src/Wt/WStringStream +++ b/src/Wt/WStringStream @@ -95,8 +95,8 @@ public: */ template inline WStringStream& operator<< (T t) { - int please_cast_to_a_supported_type = t; - please_cast_to_a_supported_type++; // silence compiler for normal case + WStringStream please_cast_to_a_supported_type = t; + please_cast_to_a_supported_type << 'a'; // silence compiler for normal case return *this; } diff --git a/src/Wt/WTabWidget.C b/src/Wt/WTabWidget.C index 41fa38bd8e..6274859d7b 100644 --- a/src/Wt/WTabWidget.C +++ b/src/Wt/WTabWidget.C @@ -1,4 +1,3 @@ - /* * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium. * diff --git a/src/Wt/WTextEdit.C b/src/Wt/WTextEdit.C index 22a582bf68..178b83a565 100644 --- a/src/Wt/WTextEdit.C +++ b/src/Wt/WTextEdit.C @@ -141,7 +141,7 @@ std::string WTextEdit::renderRemoveJs() int WTextEdit::getTinyMCEVersion() { - std::string version = "4"; + std::string version = "3"; WApplication::readConfigurationProperty("tinyMCEVersion", version); return boost::lexical_cast(version); } diff --git a/src/Wt/WTime b/src/Wt/WTime index 1f94d62502..a0c61e7944 100644 --- a/src/Wt/WTime +++ b/src/Wt/WTime @@ -11,6 +11,8 @@ #include #include +#include + namespace Wt { /*! \class InvalidTimeException Wt/WTime Wt/WTime @@ -195,6 +197,8 @@ public: PM * ap or ause am/pm display: affects h or hh display and is replaced itself by am/pm pm + * Zthe timezone in RFC 822 format (e.g. -0800) + +0000 * * * Any other text is kept literally. String content between single @@ -260,6 +264,8 @@ public: static RegExpInfo formatToRegExp(const WT_USTRING& format); + boost::posix_time::time_duration toTimeDuration() const; + private: bool valid_; long time_; @@ -282,7 +288,7 @@ private: const WString& format); bool writeSpecial(const std::string& f, unsigned& i, std::stringstream& result, - bool useAMPM) const; + bool useAMPM, int zoneOffset) const; int pmhour() const; diff --git a/src/Wt/WTime.C b/src/Wt/WTime.C index 7b4dc283a8..655f35d57e 100644 --- a/src/Wt/WTime.C +++ b/src/Wt/WTime.C @@ -368,7 +368,19 @@ WString WTime::toString() const WString WTime::toString(const WString& format) const { - return WDateTime::toString(0, this, format); + return WDateTime::toString(0, this, format, true, 0); +} + +boost::posix_time::time_duration WTime::toTimeDuration() const +{ + if (isValid()) { + boost::posix_time::time_duration::fractional_seconds_type ticks_per_msec = + boost::posix_time::time_duration::ticks_per_second() / 1000; + return boost::posix_time::time_duration(hour(), minute(), + second(), + msec() * ticks_per_msec); + } else + return boost::posix_time::time_duration(boost::date_time::not_a_date_time); } int WTime::pmhour() const @@ -378,7 +390,8 @@ int WTime::pmhour() const } bool WTime::writeSpecial(const std::string& f, unsigned& i, - std::stringstream& result, bool useAMPM) const + std::stringstream& result, bool useAMPM, + int zoneOffset) const { char buf[30]; @@ -415,6 +428,18 @@ bool WTime::writeSpecial(const std::string& f, unsigned& i, result << Utils::itoa(second(), buf); return true; + case 'Z': { + int hours = zoneOffset / 60; + int minutes = zoneOffset % 60; + if (zoneOffset >= 0) + result << '+'; + else + result << '-'; + result << Utils::pad_itoa(hours, 2, buf); + result << Utils::pad_itoa(minutes, 2, buf); + + return true; + } case 'z': if (f.substr(i + 1, 2) == "zz") { i += 2; diff --git a/src/Wt/WWebWidget b/src/Wt/WWebWidget index 22914df133..4d2c3b3fc0 100644 --- a/src/Wt/WWebWidget +++ b/src/Wt/WWebWidget @@ -477,6 +477,7 @@ protected: void updateSignalConnection(DomElement& element, EventSignalBase& signal, const char *name, bool all); + virtual void parentResized(WWidget *parent, WFlags directions); void containsLayout(); /* diff --git a/src/Wt/WWebWidget.C b/src/Wt/WWebWidget.C index 4f2ce7cb9f..f834f12e6c 100644 --- a/src/Wt/WWebWidget.C +++ b/src/Wt/WWebWidget.C @@ -625,12 +625,6 @@ void WWebWidget::gotParent() { if (isPopup()) calcZIndex(); - - if (flags_.test(BIT_CONTAINS_LAYOUT)) { - WWebWidget *p = parentWebWidget(); - if (p) - p->containsLayout(); - } } WWebWidget *WWebWidget::parentWebWidget() const @@ -1019,13 +1013,21 @@ void WWebWidget::setHidden(bool hidden, const WAnimation& animation) WApplication::instance() ->session()->renderer().updateFormObjects(this, true); - if (!hidden && flags_.test(BIT_CONTAINS_LAYOUT)) { - WApplication::instance()->session()->renderer().updateLayout(); - } - repaint(RepaintSizeAffected); } +void WWebWidget::parentResized(WWidget *parent, WFlags directions) +{ + if (flags_.test(BIT_CONTAINS_LAYOUT)) { + if (children_) + for (unsigned i = 0; i < children_->size(); ++i) { + WWidget *c = (*children_)[i]; + if (!c->isHidden()) + c->webWidget()->parentResized(parent, directions); + } + } +} + void WWebWidget::containsLayout() { if (!flags_.test(BIT_CONTAINS_LAYOUT)) { diff --git a/src/Wt/WWidget.C b/src/Wt/WWidget.C index e8693b7b34..9a6a3afdf6 100644 --- a/src/Wt/WWidget.C +++ b/src/Wt/WWidget.C @@ -135,6 +135,8 @@ void WWidget::scheduleRerender(bool laterOnly, WFlags flags) !flags_.test(BIT_NEED_RERENDER_SIZE_CHANGE)) { flags_.set(BIT_NEED_RERENDER_SIZE_CHANGE); + webWidget()->parentResized(this, Vertical); + /* * A size change to an absolutely positioned widget will not affect * a layout computation diff --git a/src/fcgi/CMakeLists.txt b/src/fcgi/CMakeLists.txt index 91c6d356f3..1af6847803 100644 --- a/src/fcgi/CMakeLists.txt +++ b/src/fcgi/CMakeLists.txt @@ -2,7 +2,7 @@ IF(CONNECTOR_FCGI) IF(NOT FCGI_FOUND) MESSAGE("** Disabling FCGI connector: requires libfcgi ") - MESSAGE(" Indicate the location of libfcgi using -DUSERLIB_ROOT=..., or omit this connector using -DCONNECTOR_FCGI=OFF") + MESSAGE(" Indicate the location of libfcgi using -DFCGI_PREFIX=..., or omit this connector using -DCONNECTOR_FCGI=OFF") ELSE(NOT FCGI_FOUND) MESSAGE("** Enabling FastCGI connector.") diff --git a/src/http/CMakeLists.txt b/src/http/CMakeLists.txt index db4bf4c0c3..cce1bd6301 100644 --- a/src/http/CMakeLists.txt +++ b/src/http/CMakeLists.txt @@ -23,6 +23,15 @@ IF(CONNECTOR_HTTP) OPTION(HTTP_WITH_ZLIB "Support for zlib (http compression)" ${ZLIB_FOUND}) + IF(WIN32) +# boost asio has a serious bug causing subtle data corruption +# https://svn.boost.org/trac/boost/ticket/8935 +# https://svn.boost.org/trac/boost/ticket/8933 + IF (Boost_VERSION EQUAL 105400) + MESSAGE(FATAL_ERROR "** boost 1.54 is blacklisted for httpd on Windows (boost ticket #8935)") + ENDIF (Boost_VERSION EQUAL 105400) + ENDIF(WIN32) + IF (HAVE_SSL) SET(MY_SSL_LIBS ${SSL_LIBRARIES}) ADD_DEFINITIONS(-DHTTP_WITH_SSL) diff --git a/src/http/Connection.C b/src/http/Connection.C index 63ef63a8b5..2cd96ceaac 100644 --- a/src/http/Connection.C +++ b/src/http/Connection.C @@ -363,9 +363,11 @@ void Connection::handleWriteResponse() } } -void Connection::handleWriteResponse(const asio_error_code& e) +void Connection::handleWriteResponse(const asio_error_code& e, + std::size_t bytes_transferred) { - LOG_DEBUG(socket().native() << ": handleWriteResponse(): " << e.message()); + LOG_DEBUG(socket().native() << ": handleWriteResponse(): " + << bytes_transferred << " ; " << e.message()); cancelWriteTimer(); diff --git a/src/http/Connection.h b/src/http/Connection.h index d527b62e5d..b7a2cee735 100644 --- a/src/http/Connection.h +++ b/src/http/Connection.h @@ -73,7 +73,8 @@ class Connection #endif public: // huh? - void handleWriteResponse(const asio_error_code& e); + void handleWriteResponse(const asio_error_code& e, + std::size_t bytes_transferred); void handleWriteResponse(); void startWriteResponse(); void handleReadRequest(const asio_error_code& e, diff --git a/src/http/SslConnection.C b/src/http/SslConnection.C index f9767d7ff8..6415770ad5 100644 --- a/src/http/SslConnection.C +++ b/src/http/SslConnection.C @@ -186,7 +186,8 @@ void SslConnection::startAsyncWriteResponse strand_.wrap (boost::bind(&Connection::handleWriteResponse, sft, - asio::placeholders::error))); + asio::placeholders::error, + asio::placeholders::bytes_transferred))); } } // namespace server diff --git a/src/http/TcpConnection.C b/src/http/TcpConnection.C index b267fd78f9..7cd7dc548e 100644 --- a/src/http/TcpConnection.C +++ b/src/http/TcpConnection.C @@ -115,7 +115,8 @@ void TcpConnection::startAsyncWriteResponse strand_.wrap (boost::bind(&Connection::handleWriteResponse, sft, - asio::placeholders::error))); + asio::placeholders::error, + asio::placeholders::bytes_transferred))); } } // namespace server diff --git a/src/js/StdGridLayoutImpl2.js b/src/js/StdGridLayoutImpl2.js index 778ca79d23..c11108df5b 100644 --- a/src/js/StdGridLayoutImpl2.js +++ b/src/js/StdGridLayoutImpl2.js @@ -46,9 +46,9 @@ WT_DECLARE_WT_MEMBER var self = this; var config = conf; - var itemDirty = false; /* one or more items (with .dirty=true) need to + var itemDirty = true; /* one or more items (with .dirty=true) need to be remeasured */ - var layoutDirty = true; /* all items dirty need to be remeasured */ + var layoutDirty = true; /* the parent size may have changed */ var topLevel = false, parent = null, parentItemWidget = null, parentInitialized = false, parentMargin = [], parentWithWtPS = false; @@ -337,8 +337,18 @@ WT_DECLARE_WT_MEMBER otherCount = OC.config.length, maxSize = DC.maxSize; - if (!itemDirty && !layoutDirty) - return; + var prevMeasures = measures.slice(); + if (prevMeasures.length == 5) { + prevMeasures[PREFERRED_SIZE] = prevMeasures[PREFERRED_SIZE].slice(); + prevMeasures[MINIMUM_SIZE] = prevMeasures[MINIMUM_SIZE].slice(); + } + + /* + * If only layoutDirty then we do not need to remeasure children, but simply + * propagate up again the total preferred size and reapply the layout as the + * container may have changed in size. + */ + if (itemDirty) { if (container && typeof DC.minSize == 'undefined') { DC.minSize = WT.px(container, 'min' + DC.Size); @@ -346,12 +356,6 @@ WT_DECLARE_WT_MEMBER DC.minSize -= sizePadding(container, dir); } - var prevMeasures = measures.slice(); - if (prevMeasures.length == 5) { - prevMeasures[PREFERRED_SIZE] = prevMeasures[PREFERRED_SIZE].slice(); - prevMeasures[MINIMUM_SIZE] = prevMeasures[MINIMUM_SIZE].slice(); - } - var preferredSize = [], minimumSize = [], totalPreferredSize = 0, totalMinSize = 0, di, oi; @@ -428,7 +432,7 @@ WT_DECLARE_WT_MEMBER console.log("measure " + dir + " " + item.id + ': ' + item.ps[0] + ',' + item.ps[1]); - if (item.dirty || layoutDirty) { + if (item.dirty) { var wMinimum; if (item.dirty > 1) { calcMinimumSize(item.w, dir); @@ -664,6 +668,7 @@ WT_DECLARE_WT_MEMBER totalMinSize, totalMargin ]; + } // itemDirty if (layoutDirty || (prevMeasures[TOTAL_PREFERRED_SIZE] != @@ -1326,6 +1331,19 @@ WT_DECLARE_WT_MEMBER return id; }; + this.setElDirty = function(el) { + var i, il; + + for (i = 0, il = config.items.length; i < il; ++i) { + var item = config.items[i]; + if (item && item.id == el.id) { + item.dirty = 2; + itemDirty = true; + return; + } + } + } + this.setItemsDirty = function(items) { var i, il; @@ -1498,13 +1516,32 @@ WT_DECLARE_APP_MEMBER return jQuery.data(document.getElementById(id), 'layout'); }; - this.setDirty = function(layoutEl) { - var layout = jQuery.data(layoutEl, 'layout'); - - if (layout) + this.setDirty = function(id) { + var layout = this.find(id); + if (layout) { layout.setDirty(); + self.scheduleAdjust(); + } }; + this.setElementDirty = function(el) { + /* + * We need to find the first parent of el that is a layout item, and + * then mark it dirty. + */ + var item = el; + el = el.parentNode; + while (el && el != document.body) { + var layout = jQuery.data(el, 'layout'); + if (layout) { + layout.setElDirty(item); + self.scheduleAdjust(); + } + item = el; + el = el.parentNode; + } + } + this.setChildLayoutsDirty = function(layout, itemWidget) { var i, il; diff --git a/src/js/StdGridLayoutImpl2.min.js b/src/js/StdGridLayoutImpl2.min.js index 987cb1cde5..758fced210 100644 --- a/src/js/StdGridLayoutImpl2.min.js +++ b/src/js/StdGridLayoutImpl2.min.js @@ -1,30 +1,30 @@ -WT_DECLARE_WT_MEMBER(1,JavaScriptConstructor,"StdLayout2",function(C,G,J,O,P,L,U,u,H,A,x){function y(a,c,b,g){function h(m){return m=="visible"||m=="none"}var k=s[c],j=c?a.scrollHeight:a.scrollWidth,r,d;if(c==0&&j+e.pxself(a,k.left)>=g.clientWidth){r=a.style[k.left];v(a,k.left,"-1000000px");j=c?a.scrollHeight:a.scrollWidth}g=c?a.clientHeight:a.clientWidth;if(e.isGecko&&c==0&&h(e.css(a,"overflow"))){d=a.style[k.size];v(a,k.size,"")}c=c?a.offsetHeight:a.offsetWidth;r&&v(a,k.left,r);d&&v(a,k.size,d); -if(g>=1E6)g-=1E6;if(j>=1E6)j-=1E6;if(c>=1E6)c-=1E6;if(j===0){j=e.pxself(a,k.size);if(j!==0&&!e.isOpera&&!e.isGecko)j-=e.px(a,"border"+k.Left+"Width")+e.px(a,"border"+k.Right+"Width")}if(e.isIE&&(e.hasTag(a,"BUTTON")||e.hasTag(a,"TEXTAREA")||e.hasTag(a,"INPUT")||e.hasTag(a,"SELECT")))j=g;if(j>c)if(e.pxself(a,k.size)==0)j=0;else{var n=false;$(a).find(".Wt-popup").each(function(){if(this.style.display!=="none")n=true});if(n)j=0}if(b)return j;e.isOpera||(j+=e.px(a,"border"+k.Left+"Width")+e.px(a,"border"+ -k.Right+"Width"));j+=e.px(a,"margin"+k.Left)+e.px(a,"margin"+k.Right);if(!e.boxSizing(a)&&!e.isIE)j+=e.px(a,"padding"+k.Left)+e.px(a,"padding"+k.Right);if(j0)j=Math.min(a,j);return Math.round(j)}function D(a,c){c=s[c];if(a.style.display==="none")return 0;else if(a["layoutMin"+c.Size])return a["layoutMin"+c.Size];else{var b=e.px(a,"min"+c.Size);e.boxSizing(a)||(b+=e.px(a,"padding"+c.Left)+e.px(a,"padding"+c.Right));return b}}function w(a,c){c=s[c];var b=e.px(a,"margin"+ -c.Left)+e.px(a,"margin"+c.Right);if(!e.boxSizing(a)&&!(e.isIE&&!e.isIElt9&&e.hasTag(a,"BUTTON")))b+=e.px(a,"border"+c.Left+"Width")+e.px(a,"border"+c.Right+"Width")+e.px(a,"padding"+c.Left)+e.px(a,"padding"+c.Right);return b}function K(a,c){c=s[c];return e.px(a,"padding"+c.Left)+e.px(a,"padding"+c.Right)}function I(a,c){if(e.boxSizing(a)){c=s[c];return e.px(a,"border"+c.Left+"Width")+e.px(a,"border"+c.Right+"Width")+e.px(a,"padding"+c.Left)+e.px(a,"padding"+c.Right)}else return 0}function V(a,c){c= -s[c];return Math.round(e.px(a,"border"+c.Left+"Width")+e.px(a,"border"+c.Right+"Width")+e.px(a,"margin"+c.Left)+e.px(a,"margin"+c.Right)+e.px(a,"padding"+c.Left)+e.px(a,"padding"+c.Right))}function Q(a,c,b){a.dirty=Math.max(a.dirty,c);W=true;b&&C.layouts2.scheduleAdjust()}function v(a,c,b){if(a.style[c]!==b){a.style[c]=b;return true}else return false}function da(a,c,b){var g=s[a],h=g.measures,k=g.config.length,j=s[a^1].config.length;if(W||M){if(b&&typeof g.minSize=="undefined"){g.minSize=e.px(b,"min"+ -g.Size);if(g.minSize>0)g.minSize-=I(b,a)}h=h.slice();if(h.length==5){h[0]=h[0].slice();h[1]=h[1].slice()}var r=[],d=[],n=0,m=0,i,E,p=false;for(i=0;i1){var o=$("#"+f.id),B=o.get(0);if(!B){g.setItem(i,E,null);continue}if(B!=f.w){f.w=B;o.find("img").add(o.filter("img")).bind("load",{item:f},function(ea){Q(ea.data.item,1,true)})}}if(!L&&f.w.style.position!="absolute"){f.w.style.position="absolute"; -f.w.style.visibility="hidden"}if(!f.ps)f.ps=[];if(!f.ms)f.ms=[];if(!f.size)f.size=[];if(!f.psize)f.psize=[];if(!f.fs)f.fs=[];if(!f.margin)f.margin=[];if($(f.w).hasClass("Wt-hidden"))f.ps[a]=f.ms[a]=0;else{o=!f.set;if(!f.set)f.set=[false,false];if(f.w){if(e.isIE)f.w.style.visibility="";if(f.dirty||M){var z;if(f.dirty>1){D(f.w,a);f.ms[a]=z}else z=f.ms[a];if(z>t)t=z;if(f.dirty>1)f.margin[a]=w(f.w,a);if(!f.set[a])if(a==0||!o){o=e.pxself(f.w,g.size);f.fs[a]=o?o+f.margin[a]:0}else{o=Math.round(e.px(f.w, -g.size));f.fs[a]=o>Math.max(I(f.w,a),z)?o+f.margin[a]:0}o=f.fs[a];if(f.layout){if(o==0)o=f.ps[a]}else{if(f.wasLayout){f.wasLayout=false;f.set=[false,false];f.ps=[];f.w.wtResize&&f.w.wtResize(f.w,-1,-1,true);v(f.w,s[1].size,"")}B=y(f.w,a,false,c);var N=f.set[a];if(N)if(f.psize[a]>8)N=B>=f.psize[a]-4&&B<=f.psize[a]+4;var Z=typeof f.ps[a]!=="undefined"&&g.config[i][0]>0&&f.set[a];o=N||Z?Math.max(o,f.ps[a]):Math.max(o,B)}f.ps[a]=o;if(!f.span||f.span[a]==1){if(o>l)l=o}else p=true}else if(!f.span||f.span[a]== -1){if(f.ps[a]>l)l=f.ps[a];if(f.ms[a]>t)t=f.ms[a]}else p=true;if(!(f.w.style.display==="none"&&!f.w.ed)&&(!f.span||f.span[a]==1))q=false}}}}if(q)t=l=-1;else if(t>l)l=t;r[i]=l;d[i]=t;if(t>-1){n+=l;m+=t}}if(p)for(i=0;i1){c=f.ps[a];for(si=p=z=0;si0)p+=g.config[i+si][0]}}if(c>0)if(z>0){if(p>0)z=p;for(si=0;si0?g.config[i+si][0]:1;if(l> -0){t=Math.round(c/l);c-=t;z-=l;r[i+si]+=t}}}}else r[i]=c}j=0;o=true;E=false;for(i=0;i-1){if(o){j+=g.margins[1];o=false}else{j+=g.margins[0];if(E)j+=4}E=g.config[i][1]!==0}o||(j+=g.margins[2]);n+=j;m+=j;g.measures=[r,d,n,m,j];if(M||h[2]!=g.measures[2])R.updateSizeInParent(a);b&&g.minSize==0&&h[3]!=g.measures[3]&&b.parentNode.className!="Wt-domRoot"&&v(b,"min"+g.Size,g.measures[3]+"px")&&R.ancestor&&R.ancestor.setContentsDirty(b);b&&a==0&&b&&e.hasTag(b,"TD")&&v(b,g.size,g.measures[2]+ -"px")}}function fa(a,c,b){a=s[a];if(X)b=-b;if(a.config[c][0]>0&&a.config[c+1][0]==0){++c;b=-b}a.fixedSize[c]=a.sizes[c]+b;C.layouts2.scheduleAdjust()}function ga(a,c,b){var g=c.di,h=s[a],k=s[a^1],j,r=e.getElement(G),d;for(d=g-1;d>=0;--d)if(h.sizes[d]>=0){j=-(h.sizes[d]-h.measures[1][d]);break}g=h.sizes[g]-h.measures[1][g];if(X){var n=j;j=-g;g=-n}new e.SizeHandle(e,h.resizeDir,e.pxself(c,h.size),e.pxself(c,k.size),j,g,h.resizerClass,function(m){fa(a,d,m)},c,r,b,0,0)}function ha(a,c){var b=s[a],g=s[a^ -1],h=b.measures,k=0,j=false,r=false,d=false,n=aa?c.parentNode:null;if(b.maxSize===0)if(n){var m=e.css(n,"position");if(m==="absolute")k=e.pxself(n,b.size);if(k===0){if(!b.initialized){if(a===0&&(m==="absolute"||m==="fixed")){n.style.display="none";k=n.clientWidth;n.style.display=""}k=a?n.clientHeight:n.clientWidth;j=true;if(a==0&&k==0&&e.isIElt9){k=n.offsetWidth;j=false}var i;if((e.hasTag(n,"TD")||e.hasTag(n,"TH"))&&!(e.isIE&&!e.isIElt9)){d=0;i=1}else{d=b.minSize?b.minSize:h[3];i=0}function E(N,Z){return Math.abs(N- -Z)<1}if(e.isIElt9&&E(k,i)||E(k,d+K(n,a)))b.maxSize=999999}if(k===0&&b.maxSize===0){k=a?n.clientHeight:n.clientWidth;j=true}}}else{k=e.pxself(c,b.size);r=true}else if(b.sizeSet){k=e.pxself(n,b.size);r=true}var p=0;if(n&&n.wtGetPS&&a==1)p=n.wtGetPS(n,c,a,0);d=h[2];if(d=h[3]-p){p=k-h[4];i=[];var l=[0,0],t=[0,0],q=0;for(d=0;d-1){m=-1;if(typeof b.fixedSize[d]!=="undefined"&&(d+1==n||h[1][d+1]>-1))m=b.fixedSize[d];else if(b.config[d][1]!==0&&b.config[d][1][0]>=0){m=b.config[d][1][0];if(b.config[d][1][1])m=(k-h[4])* -m/100}if(m>=0){i[d]=-1;j[d]=m;p-=j[d]}else{if(b.config[d][0]>0){m=1;i[d]=b.config[d][0];q+=i[d]}else{m=0;i[d]=0}l[m]+=h[1][d];t[m]+=h[0][d];j[d]=h[0][d]}}else{i[d]=-2;j[d]=-1}if(q==0){for(d=0;dt[0]+l[1]){p-=t[0];if(p>t[1]){if(b.fitSize){p-=t[1];p=p/q;for(d=0;d0){j[d]+=Math.round(i[d]*p);b.stretched[d]=true}}}else{m=1;if(p0?(p-l[m])/(t[m]-l[m]):0;for(d=0;d0){l=h[0][d]-h[1][d]; -j[d]=h[1][d]+Math.round(l*p)}}}else{for(d=0;d0)j[d]=h[1][d];p-=l[1];m=0;if(p0?(p-l[m])/(t[m]-l[m]):0;for(d=0;d-1){var f=l;if(l){i=G+"-rs"+a+"-"+d;l=e.getElement(i);if(!l){l=document.createElement("div");l.setAttribute("id",i);l.di=d;l.style.position="absolute";l.style[g.left]=g.margins[1]+"px";l.style[b.size]=b.margins[0]+ -"px";if(g.cSize)l.style[g.size]=g.cSize-g.margins[2]-g.margins[1]+"px";l.className=b.handleClass;c.insertBefore(l,c.firstChild);l.onmousedown=l.ontouchstart=function(N){ga(a,this,N||window.event)}}h+=2;v(l,b.left,h+"px");h+=2}l=b.config[d][1]!==0;if(p)p=false;else h+=b.margins[0]}for(t=0;t=j.length)break;if(B)m+=4;B=b.config[d+o][1]!==0;if(j[d+o-1]>-1&&j[d+o]>-1)m+=b.margins[0];m+=j[d+ -o]}}var z;v(i,"visibility","");B=q.align>>b.alignBits&15;o=q.ps[a];if(m=o&&q.set[a]){v(i,b.size,o+"px")&&Q(q,1);q.set[a]=false}q.size[a]=o;q.psize[a]=o}else{B=Math.max(0,m-q.margin[a]);if(!e.isIE&&e.hasTag(i,"TEXTAREA"))B=m;z=false;if(e.isIE&&e.hasTag(i,"BUTTON"))z=true;if(!(i.style.display==="none"&&!i.ed)&&(z||m!=o||q.layout)){v(i, -b.size,B+"px")&&Q(q,1);q.set[a]=true}else if(q.fs[a])a==0&&v(i,b.size,q.fs[a]+"px");else{v(i,b.size,"")&&Q(q,1);q.set[a]=false}z=h;q.size[a]=B;q.psize[a]=m}if(L)if(f){v(i,b.left,"4px");m=e.css(i,"position");if(m!=="absolute")i.style.position="relative"}else v(i,b.left,"0px");else v(i,b.left,z+"px");if(a==1){if(i.wtResize)i.wtResize(i,q.set[0]?Math.round(q.size[0]):-1,q.set[1]?Math.round(q.size[1]):-1,true);q.dirty=0}}if(j[d]>-1)h+=j[d]}$(c).children("."+g.handleClass).css(b.size,k-b.margins[2]-b.margins[1]+ -"px")}}var e=C.WT;this.ancestor=null;this.descendants=[];var R=this,F=x,W=false,M=true,aa=false,Y=null,S=null,ba=false,T=[],ca=false,X=$(document.body).hasClass("Wt-rtl"),s=[{initialized:false,config:F.cols,margins:H,maxSize:U,measures:[],sizes:[],stretched:[],fixedSize:[],Left:X?"Right":"Left",left:X?"right":"left",Right:X?"Left":"Right",Size:"Width",size:"width",alignBits:0,getItem:function(a,c){return F.items[c*s[0].config.length+a]},setItem:function(a,c,b){F.items[c*s[0].config.length+a]=b},handleClass:"Wt-vrh2", -resizeDir:"h",resizerClass:"Wt-hsh2",fitSize:O},{initialized:false,config:F.rows,margins:A,maxSize:u,measures:[],sizes:[],stretched:[],fixedSize:[],Left:"Top",left:"top",Right:"Bottom",Size:"Height",size:"height",alignBits:4,getItem:function(a,c){return F.items[a*s[0].config.length+c]},setItem:function(a,c,b){F.items[a*s[0].config.length+c]=b},handleClass:"Wt-hrh2",resizeDir:"v",resizerClass:"Wt-vsh2",fitSize:P}];jQuery.data(document.getElementById(G),"layout",this);this.updateSizeInParent=function(a){if(Y){var c= -s[a],b=c.measures[2];if(c.maxSize>0)b=Math.min(c.maxSize,b);if(ca){c=e.getElement(G);if(!c)return;for(var g=c,h=g.parentNode;;){if(h.wtGetPS)b=h.wtGetPS(h,g,a,b);b+=V(h,a);if(h==S)break;if(a==1&&h==c.parentNode&&!h.lh&&h.offsetHeight>b)b=h.offsetHeight;g=h;h=g.parentNode}}else b+=T[a];Y.setChildSize(S,a,b)}};this.setConfig=function(a){var c=F;F=a;s[0].config=F.cols;s[1].config=F.rows;s[0].stretched=[];s[1].stretched=[];var b;a=0;for(b=c.items.length;a>h.alignBits&15||!h.stretched[a]){if(!r.ps)r.ps=[];r.ps[c]=b}r.layout=true;Q(r,1);break}}};this.measure=function(a){var c=e.getElement(G);if(c)if(!e.isHidden(c)){if(!ba){ba=true;if(aa=J==null){var b=c;b=b.parentNode;for(T=[0,0];;){T[0]+=V(b,0);T[1]+=V(b,1);if(b.wtGetPS)ca=true;var g=jQuery.data(b.parentNode,"layout");if(g){Y=g;S=b;break}b=b;b=b.parentNode;if(b.childNodes.length!=1&&!b.wtGetPS)break}b= -c.parentNode;for(g=0;g<2;++g)s[g].sizeSet=e.pxself(b,s[g].size)!=0}else{Y=jQuery.data(document.getElementById(J),"layout");S=c;T[0]=V(S,0);T[1]=V(S,1)}}if(W||M){b=aa?c.parentNode:null;da(a,c,b)}if(a==1)W=M=false}};this.setMaxSize=function(a,c){s[0].maxSize=a;s[1].maxSize=c};this.apply=function(a){var c=e.getElement(G);if(!c)return false;if(e.isHidden(c))return true;ha(a,c);return true};this.contains=function(a){var c=e.getElement(G);a=e.getElement(a.getId());return c&&a?e.contains(c,a):false};this.WT= -e});WT_DECLARE_WT_MEMBER(2,JavaScriptPrototype,"StdLayout2.prototype.initResize",function(){this.resizeInitialized=true}); -WT_DECLARE_APP_MEMBER(1,JavaScriptObject,"layouts2",new (function(){var C=[],G=false,J=this,O=false;this.find=function(u){return jQuery.data(document.getElementById(u),"layout")};this.setDirty=function(u){(u=jQuery.data(u,"layout"))&&u.setDirty()};this.setChildLayoutsDirty=function(u,H){var A,x;A=0;for(x=u.descendants.length;A=e.clientWidth){t=a.style[j.left];w(a,j.left,"-1000000px");k=c?a.scrollHeight:a.scrollWidth}e=c?a.clientHeight:a.clientWidth;if(f.isGecko&&c==0&&h(f.css(a,"overflow"))){d=a.style[j.size];w(a,j.size,"")}c=c?a.offsetHeight:a.offsetWidth;t&&w(a,j.left,t);d&&w(a,j.size,d); +if(e>=1E6)e-=1E6;if(k>=1E6)k-=1E6;if(c>=1E6)c-=1E6;if(k===0){k=f.pxself(a,j.size);if(k!==0&&!f.isOpera&&!f.isGecko)k-=f.px(a,"border"+j.Left+"Width")+f.px(a,"border"+j.Right+"Width")}if(f.isIE&&(f.hasTag(a,"BUTTON")||f.hasTag(a,"TEXTAREA")||f.hasTag(a,"INPUT")||f.hasTag(a,"SELECT")))k=e;if(k>c)if(f.pxself(a,j.size)==0)k=0;else{var n=false;$(a).find(".Wt-popup").each(function(){if(this.style.display!=="none")n=true});if(n)k=0}if(b)return k;f.isOpera||(k+=f.px(a,"border"+j.Left+"Width")+f.px(a,"border"+ +j.Right+"Width"));k+=f.px(a,"margin"+j.Left)+f.px(a,"margin"+j.Right);if(!f.boxSizing(a)&&!f.isIE)k+=f.px(a,"padding"+j.Left)+f.px(a,"padding"+j.Right);if(k0)k=Math.min(a,k);return Math.round(k)}function C(a,c){c=u[c];if(a.style.display==="none")return 0;else if(a["layoutMin"+c.Size])return a["layoutMin"+c.Size];else{var b=f.px(a,"min"+c.Size);f.boxSizing(a)||(b+=f.px(a,"padding"+c.Left)+f.px(a,"padding"+c.Right));return b}}function x(a,c){c=u[c];var b=f.px(a,"margin"+ +c.Left)+f.px(a,"margin"+c.Right);if(!f.boxSizing(a)&&!(f.isIE&&!f.isIElt9&&f.hasTag(a,"BUTTON")))b+=f.px(a,"border"+c.Left+"Width")+f.px(a,"border"+c.Right+"Width")+f.px(a,"padding"+c.Left)+f.px(a,"padding"+c.Right);return b}function K(a,c){c=u[c];return f.px(a,"padding"+c.Left)+f.px(a,"padding"+c.Right)}function J(a,c){if(f.boxSizing(a)){c=u[c];return f.px(a,"border"+c.Left+"Width")+f.px(a,"border"+c.Right+"Width")+f.px(a,"padding"+c.Left)+f.px(a,"padding"+c.Right)}else return 0}function V(a,c){c= +u[c];return Math.round(f.px(a,"border"+c.Left+"Width")+f.px(a,"border"+c.Right+"Width")+f.px(a,"margin"+c.Left)+f.px(a,"margin"+c.Right)+f.px(a,"padding"+c.Left)+f.px(a,"padding"+c.Right))}function P(a,c,b){a.dirty=Math.max(a.dirty,c);Q=true;b&&B.layouts2.scheduleAdjust()}function w(a,c,b){if(a.style[c]!==b){a.style[c]=b;return true}else return false}function da(a,c,b){var e=u[a],h=e.config.length,j=u[a^1].config.length,k=e.measures.slice();if(k.length==5){k[0]=k[0].slice();k[1]=k[1].slice()}if(Q){if(b&& +typeof e.minSize=="undefined"){e.minSize=f.px(b,"min"+e.Size);if(e.minSize>0)e.minSize-=J(b,a)}var t=[],d=[],n=0,m=0,i,D,q=false;for(i=0;i1){var o=$("#"+g.id),A=o.get(0);if(!A){e.setItem(i,D,null);continue}if(A!=g.w){g.w=A;o.find("img").add(o.filter("img")).bind("load",{item:g},function(ea){P(ea.data.item,1,true)})}}if(!L&&g.w.style.position!="absolute"){g.w.style.position="absolute";g.w.style.visibility= +"hidden"}if(!g.ps)g.ps=[];if(!g.ms)g.ms=[];if(!g.size)g.size=[];if(!g.psize)g.psize=[];if(!g.fs)g.fs=[];if(!g.margin)g.margin=[];if($(g.w).hasClass("Wt-hidden"))g.ps[a]=g.ms[a]=0;else{o=!g.set;if(!g.set)g.set=[false,false];if(g.w){if(f.isIE)g.w.style.visibility="";if(g.dirty){var G;if(g.dirty>1){C(g.w,a);g.ms[a]=G}else G=g.ms[a];if(G>r)r=G;if(g.dirty>1)g.margin[a]=x(g.w,a);if(!g.set[a])if(a==0||!o){o=f.pxself(g.w,e.size);g.fs[a]=o?o+g.margin[a]:0}else{o=Math.round(f.px(g.w,e.size));g.fs[a]=o>Math.max(J(g.w, +a),G)?o+g.margin[a]:0}o=g.fs[a];if(g.layout){if(o==0)o=g.ps[a]}else{if(g.wasLayout){g.wasLayout=false;g.set=[false,false];g.ps=[];g.w.wtResize&&g.w.wtResize(g.w,-1,-1,true);w(g.w,u[1].size,"")}A=z(g.w,a,false,c);var M=g.set[a];if(M)if(g.psize[a]>8)M=A>=g.psize[a]-4&&A<=g.psize[a]+4;var Z=typeof g.ps[a]!=="undefined"&&e.config[i][0]>0&&g.set[a];o=M||Z?Math.max(o,g.ps[a]):Math.max(o,A)}g.ps[a]=o;if(!g.span||g.span[a]==1){if(o>l)l=o}else q=true}else if(!g.span||g.span[a]==1){if(g.ps[a]>l)l=g.ps[a];if(g.ms[a]> +r)r=g.ms[a]}else q=true;if(!(g.w.style.display==="none"&&!g.w.ed)&&(!g.span||g.span[a]==1))p=false}}}}if(p)r=l=-1;else if(r>l)l=r;t[i]=l;d[i]=r;if(r>-1){n+=l;m+=r}}if(q)for(i=0;i1){c=g.ps[a];for(si=l=q=0;si0)l+=e.config[i+si][0]}}if(c>0)if(q>0){if(l>0)q=l;for(si=0;si0?e.config[i+si][0]:1;if(r>0){p=Math.round(c/r);c-=p;q-=r;t[i+ +si]+=p}}}}else t[i]=c}j=0;o=true;D=false;for(i=0;i-1){if(o){j+=e.margins[1];o=false}else{j+=e.margins[0];if(D)j+=4}D=e.config[i][1]!==0}o||(j+=e.margins[2]);n+=j;m+=j;e.measures=[t,d,n,m,j]}if(W||k[2]!=e.measures[2])R.updateSizeInParent(a);b&&e.minSize==0&&k[3]!=e.measures[3]&&b.parentNode.className!="Wt-domRoot"&&w(b,"min"+e.Size,e.measures[3]+"px")&&R.ancestor&&R.ancestor.setContentsDirty(b);b&&a==0&&b&&f.hasTag(b,"TD")&&w(b,e.size,e.measures[2]+"px")}function fa(a,c,b){a=u[a];if(X)b= +-b;if(a.config[c][0]>0&&a.config[c+1][0]==0){++c;b=-b}a.fixedSize[c]=a.sizes[c]+b;B.layouts2.scheduleAdjust()}function ga(a,c,b){var e=c.di,h=u[a],j=u[a^1],k,t=f.getElement(H),d;for(d=e-1;d>=0;--d)if(h.sizes[d]>=0){k=-(h.sizes[d]-h.measures[1][d]);break}e=h.sizes[e]-h.measures[1][e];if(X){var n=k;k=-e;e=-n}new f.SizeHandle(f,h.resizeDir,f.pxself(c,h.size),f.pxself(c,j.size),k,e,h.resizerClass,function(m){fa(a,d,m)},c,t,b,0,0)}function ha(a,c){var b=u[a],e=u[a^1],h=b.measures,j=0,k=false,t=false,d= +false,n=aa?c.parentNode:null;if(b.maxSize===0)if(n){var m=f.css(n,"position");if(m==="absolute")j=f.pxself(n,b.size);if(j===0){if(!b.initialized){if(a===0&&(m==="absolute"||m==="fixed")){n.style.display="none";j=n.clientWidth;n.style.display=""}j=a?n.clientHeight:n.clientWidth;k=true;if(a==0&&j==0&&f.isIElt9){j=n.offsetWidth;k=false}var i;if((f.hasTag(n,"TD")||f.hasTag(n,"TH"))&&!(f.isIE&&!f.isIElt9)){d=0;i=1}else{d=b.minSize?b.minSize:h[3];i=0}function D(M,Z){return Math.abs(M-Z)<1}if(f.isIElt9&& +D(j,i)||D(j,d+K(n,a)))b.maxSize=999999}if(j===0&&b.maxSize===0){j=a?n.clientHeight:n.clientWidth;k=true}}}else{j=f.pxself(c,b.size);t=true}else if(b.sizeSet){j=f.pxself(n,b.size);t=true}var q=0;if(n&&n.wtGetPS&&a==1)q=n.wtGetPS(n,c,a,0);d=h[2];if(d=h[3]-q){q=j-h[4];i=[];var l=[0,0],r=[0,0],p=0;for(d=0;d-1){m=-1;if(typeof b.fixedSize[d]!=="undefined"&&(d+1==n||h[1][d+1]>-1))m=b.fixedSize[d];else if(b.config[d][1]!==0&&b.config[d][1][0]>=0){m=b.config[d][1][0];if(b.config[d][1][1])m=(j-h[4])*m/100}if(m>= +0){i[d]=-1;k[d]=m;q-=k[d]}else{if(b.config[d][0]>0){m=1;i[d]=b.config[d][0];p+=i[d]}else{m=0;i[d]=0}l[m]+=h[1][d];r[m]+=h[0][d];k[d]=h[0][d]}}else{i[d]=-2;k[d]=-1}if(p==0){for(d=0;dr[0]+l[1]){q-=r[0];if(q>r[1]){if(b.fitSize){q-=r[1];q=q/p;for(d=0;d0){k[d]+=Math.round(i[d]*q);b.stretched[d]=true}}}else{m=1;if(q0?(q-l[m])/(r[m]-l[m]):0;for(d=0;d0){l=h[0][d]-h[1][d];k[d]=h[1][d]+ +Math.round(l*q)}}}else{for(d=0;d0)k[d]=h[1][d];q-=l[1];m=0;if(q0?(q-l[m])/(r[m]-l[m]):0;for(d=0;d-1){var g=l;if(l){i=H+"-rs"+a+"-"+d;l=f.getElement(i);if(!l){l=document.createElement("div");l.setAttribute("id",i);l.di=d;l.style.position="absolute";l.style[e.left]=e.margins[1]+"px";l.style[b.size]=b.margins[0]+"px"; +if(e.cSize)l.style[e.size]=e.cSize-e.margins[2]-e.margins[1]+"px";l.className=b.handleClass;c.insertBefore(l,c.firstChild);l.onmousedown=l.ontouchstart=function(M){ga(a,this,M||window.event)}}h+=2;w(l,b.left,h+"px");h+=2}l=b.config[d][1]!==0;if(q)q=false;else h+=b.margins[0]}for(r=0;r=k.length)break;if(A)m+=4;A=b.config[d+o][1]!==0;if(k[d+o-1]>-1&&k[d+o]>-1)m+=b.margins[0];m+=k[d+o]}}var G; +w(i,"visibility","");A=p.align>>b.alignBits&15;o=p.ps[a];if(m=o&&p.set[a]){w(i,b.size,o+"px")&&P(p,1);p.set[a]=false}p.size[a]=o;p.psize[a]=o}else{A=Math.max(0,m-p.margin[a]);if(!f.isIE&&f.hasTag(i,"TEXTAREA"))A=m;G=false;if(f.isIE&&f.hasTag(i,"BUTTON"))G=true;if(!(i.style.display==="none"&&!i.ed)&&(G||m!=o||p.layout)){w(i,b.size,A+ +"px")&&P(p,1);p.set[a]=true}else if(p.fs[a])a==0&&w(i,b.size,p.fs[a]+"px");else{w(i,b.size,"")&&P(p,1);p.set[a]=false}G=h;p.size[a]=A;p.psize[a]=m}if(L)if(g){w(i,b.left,"4px");m=f.css(i,"position");if(m!=="absolute")i.style.position="relative"}else w(i,b.left,"0px");else w(i,b.left,G+"px");if(a==1){if(i.wtResize)i.wtResize(i,p.set[0]?Math.round(p.size[0]):-1,p.set[1]?Math.round(p.size[1]):-1,true);p.dirty=0}}if(k[d]>-1)h+=k[d]}$(c).children("."+e.handleClass).css(b.size,j-b.margins[2]-b.margins[1]+ +"px")}}var f=B.WT;this.ancestor=null;this.descendants=[];var R=this,E=y,Q=true,W=true,aa=false,Y=null,S=null,ba=false,T=[],ca=false,X=$(document.body).hasClass("Wt-rtl"),u=[{initialized:false,config:E.cols,margins:F,maxSize:U,measures:[],sizes:[],stretched:[],fixedSize:[],Left:X?"Right":"Left",left:X?"right":"left",Right:X?"Left":"Right",Size:"Width",size:"width",alignBits:0,getItem:function(a,c){return E.items[c*u[0].config.length+a]},setItem:function(a,c,b){E.items[c*u[0].config.length+a]=b},handleClass:"Wt-vrh2", +resizeDir:"h",resizerClass:"Wt-hsh2",fitSize:N},{initialized:false,config:E.rows,margins:v,maxSize:s,measures:[],sizes:[],stretched:[],fixedSize:[],Left:"Top",left:"top",Right:"Bottom",Size:"Height",size:"height",alignBits:4,getItem:function(a,c){return E.items[a*u[0].config.length+c]},setItem:function(a,c,b){E.items[a*u[0].config.length+c]=b},handleClass:"Wt-hrh2",resizeDir:"v",resizerClass:"Wt-vsh2",fitSize:O}];jQuery.data(document.getElementById(H),"layout",this);this.updateSizeInParent=function(a){if(Y){var c= +u[a],b=c.measures[2];if(c.maxSize>0)b=Math.min(c.maxSize,b);if(ca){c=f.getElement(H);if(!c)return;for(var e=c,h=e.parentNode;;){if(h.wtGetPS)b=h.wtGetPS(h,e,a,b);b+=V(h,a);if(h==S)break;if(a==1&&h==c.parentNode&&!h.lh&&h.offsetHeight>b)b=h.offsetHeight;e=h;h=e.parentNode}}else b+=T[a];Y.setChildSize(S,a,b)}};this.setConfig=function(a){var c=E;E=a;u[0].config=E.cols;u[1].config=E.rows;u[0].stretched=[];u[1].stretched=[];var b;a=0;for(b=c.items.length;a>h.alignBits&15||!h.stretched[a]){if(!t.ps)t.ps=[];t.ps[c]=b}t.layout=true;P(t,1);break}}};this.measure=function(a){var c=f.getElement(H);if(c)if(!f.isHidden(c)){if(!ba){ba=true;if(aa=I==null){var b=c;b=b.parentNode;for(T=[0,0];;){T[0]+=V(b,0);T[1]+=V(b, +1);if(b.wtGetPS)ca=true;var e=jQuery.data(b.parentNode,"layout");if(e){Y=e;S=b;break}b=b;b=b.parentNode;if(b.childNodes.length!=1&&!b.wtGetPS)break}b=c.parentNode;for(e=0;e<2;++e)u[e].sizeSet=f.pxself(b,u[e].size)!=0}else{Y=jQuery.data(document.getElementById(I),"layout");S=c;T[0]=V(S,0);T[1]=V(S,1)}}if(Q||W){b=aa?c.parentNode:null;da(a,c,b)}if(a==1)Q=W=false}};this.setMaxSize=function(a,c){u[0].maxSize=a;u[1].maxSize=c};this.apply=function(a){var c=f.getElement(H);if(!c)return false;if(f.isHidden(c))return true; +ha(a,c);return true};this.contains=function(a){var c=f.getElement(H);a=f.getElement(a.getId());return c&&a?f.contains(c,a):false};this.WT=f});WT_DECLARE_WT_MEMBER(2,JavaScriptPrototype,"StdLayout2.prototype.initResize",function(){this.resizeInitialized=true}); +WT_DECLARE_APP_MEMBER(1,JavaScriptObject,"layouts2",new (function(){var B=[],H=false,I=this,N=false;this.find=function(s){return jQuery.data(document.getElementById(s),"layout")};this.setDirty=function(s){if(s=this.find(s)){s.setDirty();I.scheduleAdjust()}};this.setElementDirty=function(s){var F=s;for(s=s.parentNode;s&&s!=document.body;){var v=jQuery.data(s,"layout");if(v){v.setElDirty(F);I.scheduleAdjust()}F=s;s=s.parentNode}};this.setChildLayoutsDirty=function(s,F){var v,y;v=0;for(y=s.descendants.length;v< +y;++v){var z=s.descendants[v];if(F){var C=s.WT.getElement(z.getId());if(C&&!s.WT.contains(F,C))continue}z.setDirty()}};this.add=function(s){function F(v,y){var z,C;z=0;for(C=v.length;za.offsetWidth-40)c.addClass("hover");else c.hasClass("hover")&& +WT_DECLARE_WT_MEMBER(1,JavaScriptConstructor,"WDateEdit",function(g,a,h){function f(){return!!a.getAttribute("readonly")}function i(){var b=$("#"+h.id).get(0);return jQuery.data(b,"popup")}function j(){c.removeClass("active")}function k(){var b=i();b.bindHide(j);b.show(a,e.Vertical)}jQuery.data(a,"dobj",this);var e=g.WT,c=$(a);this.mouseOut=function(){c.removeClass("hover")};this.mouseMove=function(b,d){if(!f())if(e.widgetCoordinates(a,d).x>a.offsetWidth-40)c.addClass("hover");else c.hasClass("hover")&& c.removeClass("hover")};this.mouseDown=function(b,d){if(!f())if(e.widgetCoordinates(a,d).x>a.offsetWidth-40){e.cancelEvent(d);c.addClass("unselectable").addClass("active")}};this.mouseUp=function(b,d){c.removeClass("unselectable");e.widgetCoordinates(a,d).x>a.offsetWidth-40&&k()}}); diff --git a/src/js/WDateValidator.js b/src/js/WDateValidator.js index 02f8dd9010..777a495d04 100644 --- a/src/js/WDateValidator.js +++ b/src/js/WDateValidator.js @@ -50,9 +50,10 @@ WT_DECLARE_WT_MEMBER if (dt.getDate() != d || dt.getMonth() != m-1 || - dt.getFullYear() != y) - return { valid: false, massage: formatError}; - + dt.getFullYear() != y || + dt.getFullYear() < 1400) { + return { valid: false, message: formatError }; + } if (bottom) if (dt.getTime() < bottom.getTime()) diff --git a/src/js/WDateValidator.min.js b/src/js/WDateValidator.min.js index 6b38385110..06e606507f 100644 --- a/src/js/WDateValidator.min.js +++ b/src/js/WDateValidator.min.js @@ -1,2 +1,2 @@ WT_DECLARE_WT_MEMBER(1,JavaScriptConstructor,"WDateValidator",function(l,i,j,k,m,f,n,o){this.validate=function(a){if(a.length==0)return l?{valid:false,message:m}:{valid:true};for(var b,c=-1,d=-1,g=-1,h=0,p=i.length;h31||c<=0||c>12)return{valid:false,message:f};a=new Date(g,c-1,d);if(a.getDate()!=d||a.getMonth()!=c-1||a.getFullYear()!= -g)return{valid:false,massage:f};if(j)if(a.getTime()k.getTime())return{valid:false,message:o};return{valid:true}}}); +g||a.getFullYear()<1400)return{valid:false,message:f};if(j)if(a.getTime()k.getTime())return{valid:false,message:o};return{valid:true}}}); diff --git a/src/js/WWebWidget.js b/src/js/WWebWidget.js index a375265f89..5986d2e56f 100644 --- a/src/js/WWebWidget.js +++ b/src/js/WWebWidget.js @@ -88,7 +88,7 @@ WT_DECLARE_WT_MEMBER // FIXME: APP instead of Wt if (Wt.layouts2) - Wt.layouts2.scheduleAdjust(true); + Wt.layouts2.setElementDirty(el); } function show() { diff --git a/src/js/WWebWidget.min.js b/src/js/WWebWidget.min.js index e9df938fb6..0862c77d00 100644 --- a/src/js/WWebWidget.min.js +++ b/src/js/WWebWidget.min.js @@ -2,5 +2,5 @@ WT_DECLARE_WT_MEMBER(1,JavaScriptFunction,"animateDisplay",function(B,C,D,E,F){v (k==5?"pop ":"")+(g?"out":"in");if(l&256)c+=" fade";g||t();f.addClass(c);f.one(I,function(){f.removeClass(c);if(g)a.style.display=m;b(a,h)})}function J(){x("width",k==1?"left":"right",k==1,"X")}function K(){x("height",k==4?"top":"bottom",k==4,"Y")}function x(c,i,j,d){g||t();c=o.px(a,c);i=(o.px(a,i)+c)*(j?-1:1);var e;if(g){b(a,{transform:"translate"+d+"(0px)"},h);e=i}else{b(a,{transform:"translate"+d+"("+i+"px)"},h);e=0}if(l&256)b(a,{opacity:g?1:0},h);setTimeout(function(){b(a,{transition:"all "+n+ "ms "+u,transform:"translate"+d+"("+e+"px)"},h);if(l&256)b(a,{opacity:g?0:1});f.one(s,function(){if(g)a.style.display=m;b(a,h);y()})},0)}function L(){var c,i,j={},d;if(g){i=f.height()+"px";b(a,{height:i,overflow:"hidden"},h);if(k==4&&a.childNodes.length==1){d=a.firstChild;b(d,{transform:"translateY(0)"},j);o.hasTag(d,"TABLE")||b(d,{display:"block"},j)}c="0px"}else{var e=$(p),z={};b(p,{height:e.height()+"px",overflow:"hidden"},z);t();if(f.height()==0)a.style.height="auto";c=f.height()+"px";b(a,{height:"0px", overflow:"hidden"},h);b(p,z);if(k==4){b(a,{WebkitBackfaceVisibility:"visible"},h);a.scrollTop=1E3}}if(l&256)b(a,{opacity:g?1:0},h);setTimeout(function(){b(a,{transition:"all "+n+"ms "+u,height:c},h);if(l&256)b(a,{opacity:g?0:1});d&&b(d,{transition:"all "+n+"ms "+u,transform:"translateY(-"+i+")"},j);f.one(s,function(){if(g)a.style.display=m;b(a,h);if(k==4){a.scrollTop=0;d&&b(d,j)}y()})},0)}function t(){a.style.display=m;a.wtPosition&&a.wtPosition();window.onshow&&window.onshow()}function y(){a.wtAnimatedHidden&& -a.wtAnimatedHidden(g);f.removeClass("animating");Wt.layouts2&&Wt.layouts2.scheduleAdjust(true)}function b(c,i,j){var d;for(d in i){var e=d;if(e=="transform"||e=="transition"||e=="animationDuration")e=r+e.substring(0,1).toUpperCase()+e.substring(1);if(j&&typeof j[e]==="undefined")j[e]=c.style[e];c.style[e]=i[d]}}if(f.hasClass("animating"))$(a).one(s,function(){A(v,l,q,n,m)});else{f.addClass("animating");var k=l&255,g=m==="none",u=G[g?H[q]:q],h={};setTimeout(function(){var c=f.css("position");c=c=== -"absolute"||c==="fixed";switch(k){case 4:case 3:c?K():L();break;case 1:case 2:c?J():w();break;case 0:case 5:w();break}},0)}}}};A(B,C,D,E,F)});WT_DECLARE_WT_MEMBER(2,JavaScriptFunction,"animateVisible",function(){}); +a.wtAnimatedHidden(g);f.removeClass("animating");Wt.layouts2&&Wt.layouts2.setElementDirty(a)}function b(c,i,j){var d;for(d in i){var e=d;if(e=="transform"||e=="transition"||e=="animationDuration")e=r+e.substring(0,1).toUpperCase()+e.substring(1);if(j&&typeof j[e]==="undefined")j[e]=c.style[e];c.style[e]=i[d]}}if(f.hasClass("animating"))$(a).one(s,function(){A(v,l,q,n,m)});else{f.addClass("animating");var k=l&255,g=m==="none",u=G[g?H[q]:q],h={};setTimeout(function(){var c=f.css("position");c=c==="absolute"|| +c==="fixed";switch(k){case 4:case 3:c?K():L();break;case 1:case 2:c?J():w();break;case 0:case 5:w();break}},0)}}}};A(B,C,D,E,F)});WT_DECLARE_WT_MEMBER(2,JavaScriptFunction,"animateVisible",function(){}); diff --git a/src/web/DomElement.C b/src/web/DomElement.C index e120881ebf..ed0fad050c 100644 --- a/src/web/DomElement.C +++ b/src/web/DomElement.C @@ -87,7 +87,9 @@ std::string cssNames_[] = "font-weight", "font-size", "background-color", "background-image", "background-repeat", "background-attachment", "background-position", - "text-decoration", "white-space", "table-layout", "border-spacing", + "text-decoration", "white-space", + "table-layout", "border-spacing", + "border-collapse", "page-break-before", "page-break-after", "zoom", "visibility", "display", "box-sizing"}; @@ -114,7 +116,9 @@ std::string cssCamelNames_[] = "fontWeight", "fontSize", "backgroundColor", "backgroundImage", "backgroundRepeat", "backgroundAttachment", "backgroundPosition", - "textDecoration", "whiteSpace", "tableLayout", "borderSpacing", + "textDecoration", "whiteSpace", + "tableLayout", "borderSpacing", + "border-collapse", "pageBreakBefore", "pageBreakAfter", "zoom", "visibility", "display", "boxSizing" diff --git a/src/web/DomElement.h b/src/web/DomElement.h index 0524caed8a..b1c48fac02 100644 --- a/src/web/DomElement.h +++ b/src/web/DomElement.h @@ -65,6 +65,7 @@ enum Property { PropertyInnerHTML, PropertyAddedInnerHTML, PropertyStyleBackgroundPosition, PropertyStyleTextDecoration, PropertyStyleWhiteSpace, PropertyStyleTableLayout, PropertyStyleBorderSpacing, + PropertyStyleBorderCollapse, PropertyStylePageBreakBefore, PropertyStylePageBreakAfter, PropertyStyleZoom, PropertyStyleVisibility, PropertyStyleDisplay, diff --git a/src/web/WebRenderer.C b/src/web/WebRenderer.C index 95c7cec292..ee5f9aee2e 100644 --- a/src/web/WebRenderer.C +++ b/src/web/WebRenderer.C @@ -140,6 +140,7 @@ bool WebRenderer::isDirty() const || !session_.app()->afterLoadJavaScript_.empty() || session_.app()->serverPushChanged_ || session_.app()->styleSheetsAdded_ + || session_.app()->internalPathIsChanged_ || !collectedJS1_.empty() || !collectedJS2_.empty() || !invisibleJS_.empty(); @@ -333,7 +334,7 @@ void WebRenderer::streamBootContent(WebResponse& response, safeJsStringLiteral(session_.ajaxCanonicalUrl(response))); bootJs.setVar("APP_CLASS", "Wt"); bootJs.setVar("PATH_INFO", WWebWidget::jsStringLiteral - (session_.env().pathInfo_)); + (session_.pagePathInfo_)); bootJs.setCondition("COOKIE_CHECKS", conf.cookieChecks()); bootJs.setCondition("SPLIT_SCRIPT", conf.splitScript()); @@ -549,6 +550,9 @@ void WebRenderer::serveJavaScriptUpdate(WebResponse& response) if (!rendered_) { serveMainAjax(out); } else { + if (response.isWebSocketRequest() || response.isWebSocketMessage()) + setJSSynced(false); + collectJavaScript(); addResponseAckPuzzle(out); @@ -1242,9 +1246,18 @@ void WebRenderer::serveMainpage(WebResponse& response) || */(app->internalPathIsChanged_ && app->oldInternalPath_ != app->newInternalPath_))) { app->oldInternalPath_ = app->newInternalPath_; - session_.redirect - (session_.fixRelativeUrl - (session_.mostRelativeUrl(app->newInternalPath_))); + + if (session_.state() == WebSession::JustCreated && + conf.progressiveBoot()) { + session_.redirect + (session_.fixRelativeUrl + (session_.bookmarkUrl(app->newInternalPath_))); + session_.kill(); + } else { + session_.redirect + (session_.fixRelativeUrl + (session_.mostRelativeUrl(app->newInternalPath_))); + } } std::string redirect = session_.getRedirect(); diff --git a/src/web/WebSession.C b/src/web/WebSession.C index af84f46b3c..7355587b9e 100644 --- a/src/web/WebSession.C +++ b/src/web/WebSession.C @@ -1592,7 +1592,7 @@ void WebSession::handleWebSocketRequest(Handler& handler) void WebSession::handleWebSocketMessage(boost::weak_ptr session, WebRequest::ReadEvent event) { - // LOG_DEBUG("handleWebSocketMessage: " << (int)event); + //LOG_DEBUG("handleWebSocketMessage: " << (int)event); #ifndef WT_TARGET_JAVA boost::shared_ptr lock = session.lock(); diff --git a/src/web/skeleton/Boot.js b/src/web/skeleton/Boot.js index a6e9bca772..ab2b2b515c 100644 --- a/src/web/skeleton/Boot.js +++ b/src/web/skeleton/Boot.js @@ -68,6 +68,18 @@ function delayClick(e) { e.returnValue = false; return false; } + +function setupDelayClick() { + var db = document.body; + if (!db) + setTimeout(setupDelayClick, 1); + else { + if (db.addEventListener) + db.addEventListener('click', delayClick, true); + else + db.attachEvent('onclick', delayClick); + } +} _$_$endif_$_(); (function() { @@ -168,6 +180,9 @@ var deployPathInfo = '&deployPath=' + encodeURIComponent(deployPath); var ajax = (win.XMLHttpRequest || win.ActiveXObject); var no_replace = _$_RELOAD_IS_NEWSESSION_$_; +var inOneSecond = new Date(); +inOneSecond.setTime(inOneSecond.getTime() + 1000); + _$_$if_COOKIE_CHECKS_$_(); // client-side cookie support var testcookie='jscookietest=valid'; @@ -177,8 +192,6 @@ no_replace = no_replace || doc.cookie=testcookie+';expires=Thu, 01 Jan 1970 00:00:00 GMT'; // server-side cookie support -var inOneSecond = new Date(); -inOneSecond.setTime(inOneSecond.getTime() + 1000); doc.cookie='WtTestCookie=ok;path=/;expires=' + inOneSecond.toGMTString(); _$_$endif_$_(); @@ -196,9 +209,9 @@ if ((ua.indexOf("gecko") == -1) || (ua.indexOf("webkit") != -1)) hash = unescape(hash); // scale (VML) -var scaleInfo = ""; +var otherInfo = ""; if (screen.deviceXDPI != screen.logicalXDPI) - scaleInfo = "&scale=" + screen.deviceXDPI / screen.logicalXDPI; + otherInfo = "&scale=" + screen.deviceXDPI / screen.logicalXDPI; // determine url var selfUrl = _$_SELF_URL_$_ + '&sid=' + _$_SCRIPT_ID_$_; @@ -207,6 +220,10 @@ var selfUrl = _$_SELF_URL_$_ + '&sid=' + _$_SCRIPT_ID_$_; var htmlHistory = !!(window.history && window.history.pushState), htmlHistoryInfo = htmlHistory ? "&htmlHistory=true" : ""; +// determine time zone offset +var tzOffset = (new Date()).getTimezoneOffset(); +otherInfo += "&tz=" + (-tzOffset); + var needSessionInUrl = !no_replace || !ajax; if (needSessionInUrl) { @@ -255,14 +272,10 @@ _$_$if_PROGRESS_$_(); Make sure that we are not processing click events while progressing. Instead, delay them. */ - var db = doc.body; - if (db.addEventListener) - db.addEventListener('click', delayClick, true); - else - db.attachEvent('onclick', delayClick); + setupDelayClick(); _$_$endif_$_(); - var allInfo = hashInfo + scaleInfo + htmlHistoryInfo + deployPathInfo; + var allInfo = hashInfo + otherInfo + htmlHistoryInfo + deployPathInfo; _$_$ifnot_SPLIT_SCRIPT_$_(); loadScript(selfUrl + allInfo + '&request=script&rand=' + rand(), null); diff --git a/src/web/skeleton/Boot.min.js b/src/web/skeleton/Boot.min.js index 70357ec725..2b63bfb561 100644 --- a/src/web/skeleton/Boot.min.js +++ b/src/web/skeleton/Boot.min.js @@ -1,10 +1,11 @@ window.onresize=function(){}; -function loadScript(a,k){var r=document.getElementsByTagName("head")[0],s=/firefox\/(\d+)\./.exec(navigator.userAgent.toLowerCase());if(s&&s[1]>=20){var l=new XMLHttpRequest;l.open("GET",a,true);l.onreadystatechange=function(){if(l.readyState==4){var t=document.createElement("script");t.type="text/javascript";t.innerHTML=l.responseText;r.appendChild(t);k&&k()}};l.send(null)}else{var f=document.createElement("script");if(k)if(f.readyState)f.onreadystatechange=function(){if(f.readyState=="loaded"|| -f.readyState=="complete"){f.onreadystatechange=null;k()}};else f.onload=function(){k()};f.setAttribute("src",a);r.appendChild(f)}}_$_$if_PROGRESS_$_();var delayedClicks=[]; -function delayClick(a){delayedClicks.push({bubbles:a.bubbles,cancelable:a.cancelable,detail:a.detail,screenX:a.screenX,screenY:a.screenY,clientX:a.clientX,clientY:a.clientY,ctrlKey:a.ctrlKey,altKey:a.altKey,shiftKey:a.shiftKey,metaKey:a.metaKey,button:a.button,targetId:(a.target||a.srcElement).id});a.stopPropagation&&a.stopPropagation();a.preventDefault&&a.preventDefault();a.cancelBubble=true;return a.returnValue=false}_$_$endif_$_(); -(function(){function a(){function k(){return Math.round(Math.random()*1E6)+_$_RANDOMSEED_$_}function r(c){if(g.location.replace)g.location.replace(c);else g.location.href=c}function s(){var c=m.getElementById("Wt-form");if(c!=null)c.style.visibility="hidden";else setTimeout(s,10)}function l(){var c=window.location.search;if(c.length>1&&c.charAt(0)=="?")c=c.substr(1);return c.split("&")}function f(c){var q,i,e,n;i=l();q=0;for(n=i.length;q=2)if(e[0]===c)return unescape(e[1])}return null} -function t(c,q){var i,e,n,x,y=false;e=l();i=0;for(x=e.length;i=2)if(n[0]===c){n[1]=escape(q);e[i]=n.join("=");y=true;break}}y||e.push(c+"="+escape(q));return"?"+e.join("&")+window.location.hash}var m=document,g=window;try{m.execCommand("BackgroundImageCache",false,true)}catch(A){}g.opera&&g.opera.setOverrideHistoryNavigationMode("compatible");var h=_$_PATH_INFO_$_,d=g.location.pathname;g.opera||(d=decodeURIComponent(d));if(h.length>0){var b=d.lastIndexOf(h);if(b!= --1)d=d.substr(0,b)+d.substr(b+h.length)}h="&deployPath="+encodeURIComponent(d);var o=g.XMLHttpRequest||g.ActiveXObject,j=_$_RELOAD_IS_NEWSESSION_$_;_$_$if_COOKIE_CHECKS_$_();m.cookie="jscookietest=valid";j=j||_$_USE_COOKIES_$_&&m.cookie.indexOf("jscookietest=valid")!=-1;m.cookie="jscookietest=valid;expires=Thu, 01 Jan 1970 00:00:00 GMT";d=new Date;d.setTime(d.getTime()+1E3);m.cookie="WtTestCookie=ok;path=/;expires="+d.toGMTString();_$_$endif_$_();b=g.location.hash;if(b.length>0)b=b.substr(1);var p= -b.indexOf("?");if(p!=-1)b=b.substr(0,p);p=navigator.userAgent.toLowerCase();if(p.indexOf("gecko")==-1||p.indexOf("webkit")!=-1)b=unescape(b);p="";if(screen.deviceXDPI!=screen.logicalXDPI)p="&scale="+screen.deviceXDPI/screen.logicalXDPI;var u=_$_SELF_URL_$_+"&sid="+_$_SCRIPT_ID_$_,v=!!(window.history&&window.history.pushState),z=v?"&htmlHistory=true":"";if(j=!j||!o)if(f("wtd")==="_$_SESSION_ID_$_")j=false;if(j)if(v)r(t("wtd","_$_SESSION_ID_$_"));else{h=b.length>1&&b.charAt(0)=="/"?b:_$_INTERNAL_PATH_$_; -if(h.length>0)u+="#"+h;r(u)}else if(o){o=_$_AJAX_CANONICAL_URL_$_;j="";if(!v&&o.length>1){_$_$if_HYBRID_$_();h="WtInternalPath="+escape(_$_INTERNAL_PATH_$_)+";path=/;expires="+d.toGMTString();m.cookie=h;_$_$endif_$_();if(o.charAt(0)=="#")o="../"+o;r(o)}else{if(b.length>1&&b.charAt(0)=="/"){j="&_="+encodeURIComponent(b);_$_$if_HYBRID_$_();b!=_$_INTERNAL_PATH_$_&&setTimeout(s,10);_$_$endif_$_()}_$_$if_PROGRESS_$_();d=m.body;d.addEventListener?d.addEventListener("click",delayClick,true):d.attachEvent("onclick", -delayClick);_$_$endif_$_();var w=j+p+z+h;_$_$ifnot_SPLIT_SCRIPT_$_();loadScript(u+w+"&request=script&rand="+k(),null);_$_$endif_$_();_$_$if_SPLIT_SCRIPT_$_();loadScript(u+w+"&request=script&skeleton=true",function(){loadScript(u+w+"&request=script&rand="+k(),null)});_$_$endif_$_()}}}setTimeout(a,0)})(); +function loadScript(a,l){var r=document.getElementsByTagName("head")[0],s=/firefox\/(\d+)\./.exec(navigator.userAgent.toLowerCase());if(s&&s[1]>=20){var m=new XMLHttpRequest;m.open("GET",a,true);m.onreadystatechange=function(){if(m.readyState==4){var t=document.createElement("script");t.type="text/javascript";t.innerHTML=m.responseText;r.appendChild(t);l&&l()}};m.send(null)}else{var f=document.createElement("script");if(l)if(f.readyState)f.onreadystatechange=function(){if(f.readyState=="loaded"|| +f.readyState=="complete"){f.onreadystatechange=null;l()}};else f.onload=function(){l()};f.setAttribute("src",a);r.appendChild(f)}}_$_$if_PROGRESS_$_();var delayedClicks=[]; +function delayClick(a){delayedClicks.push({bubbles:a.bubbles,cancelable:a.cancelable,detail:a.detail,screenX:a.screenX,screenY:a.screenY,clientX:a.clientX,clientY:a.clientY,ctrlKey:a.ctrlKey,altKey:a.altKey,shiftKey:a.shiftKey,metaKey:a.metaKey,button:a.button,targetId:(a.target||a.srcElement).id});a.stopPropagation&&a.stopPropagation();a.preventDefault&&a.preventDefault();a.cancelBubble=true;return a.returnValue=false} +function setupDelayClick(){var a=document.body;if(a)a.addEventListener?a.addEventListener("click",delayClick,true):a.attachEvent("onclick",delayClick);else setTimeout(setupDelayClick,1)}_$_$endif_$_(); +(function(){function a(){function l(){return Math.round(Math.random()*1E6)+_$_RANDOMSEED_$_}function r(c){if(g.location.replace)g.location.replace(c);else g.location.href=c}function s(){var c=p.getElementById("Wt-form");if(c!=null)c.style.visibility="hidden";else setTimeout(s,10)}function m(){var c=window.location.search;if(c.length>1&&c.charAt(0)=="?")c=c.substr(1);return c.split("&")}function f(c){var q,i,e,n;i=m();q=0;for(n=i.length;q=2)if(e[0]===c)return unescape(e[1])}return null} +function t(c,q){var i,e,n,x,y=false;e=m();i=0;for(x=e.length;i=2)if(n[0]===c){n[1]=escape(q);e[i]=n.join("=");y=true;break}}y||e.push(c+"="+escape(q));return"?"+e.join("&")+window.location.hash}var p=document,g=window;try{p.execCommand("BackgroundImageCache",false,true)}catch(B){}g.opera&&g.opera.setOverrideHistoryNavigationMode("compatible");var h=_$_PATH_INFO_$_,d=g.location.pathname;g.opera||(d=decodeURIComponent(d));if(h.length>0){var b=d.lastIndexOf(h);if(b!= +-1)d=d.substr(0,b)+d.substr(b+h.length)}h="&deployPath="+encodeURIComponent(d);var o=g.XMLHttpRequest||g.ActiveXObject,j=_$_RELOAD_IS_NEWSESSION_$_;d=new Date;d.setTime(d.getTime()+1E3);_$_$if_COOKIE_CHECKS_$_();p.cookie="jscookietest=valid";j=j||_$_USE_COOKIES_$_&&p.cookie.indexOf("jscookietest=valid")!=-1;p.cookie="jscookietest=valid;expires=Thu, 01 Jan 1970 00:00:00 GMT";p.cookie="WtTestCookie=ok;path=/;expires="+d.toGMTString();_$_$endif_$_();b=g.location.hash;if(b.length>0)b=b.substr(1);var k= +b.indexOf("?");if(k!=-1)b=b.substr(0,k);k=navigator.userAgent.toLowerCase();if(k.indexOf("gecko")==-1||k.indexOf("webkit")!=-1)b=unescape(b);k="";if(screen.deviceXDPI!=screen.logicalXDPI)k="&scale="+screen.deviceXDPI/screen.logicalXDPI;var u=_$_SELF_URL_$_+"&sid="+_$_SCRIPT_ID_$_,v=!!(window.history&&window.history.pushState),z=v?"&htmlHistory=true":"",A=(new Date).getTimezoneOffset();k+="&tz="+-A;if(j=!j||!o)if(f("wtd")==="_$_SESSION_ID_$_")j=false;if(j)if(v)r(t("wtd","_$_SESSION_ID_$_"));else{h= +b.length>1&&b.charAt(0)=="/"?b:_$_INTERNAL_PATH_$_;if(h.length>0)u+="#"+h;r(u)}else if(o){o=_$_AJAX_CANONICAL_URL_$_;j="";if(!v&&o.length>1){_$_$if_HYBRID_$_();h="WtInternalPath="+escape(_$_INTERNAL_PATH_$_)+";path=/;expires="+d.toGMTString();p.cookie=h;_$_$endif_$_();if(o.charAt(0)=="#")o="../"+o;r(o)}else{if(b.length>1&&b.charAt(0)=="/"){j="&_="+encodeURIComponent(b);_$_$if_HYBRID_$_();b!=_$_INTERNAL_PATH_$_&&setTimeout(s,10);_$_$endif_$_()}_$_$if_PROGRESS_$_();setupDelayClick();_$_$endif_$_(); +var w=j+k+z+h;_$_$ifnot_SPLIT_SCRIPT_$_();loadScript(u+w+"&request=script&rand="+l(),null);_$_$endif_$_();_$_$if_SPLIT_SCRIPT_$_();loadScript(u+w+"&request=script&skeleton=true",function(){loadScript(u+w+"&request=script&rand="+l(),null)});_$_$endif_$_()}}}setTimeout(a,0)})(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 875264f8ad..9be0ca6109 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,7 @@ SET(TEST_SOURCES auth/SHA1Test.C chart/WChartTest.C json/JsonParserTest.C + json/JsonSerializerTest.C http/HttpClientTest.C mail/MailClientTest.C models/WBatchEditProxyModelTest.C diff --git a/test/json/JsonSerializerTest.C b/test/json/JsonSerializerTest.C new file mode 100644 index 0000000000..02b340b4e2 --- /dev/null +++ b/test/json/JsonSerializerTest.C @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013 Emweb bvba, Kessel-Lo, Belgium. + * + * See the LICENSE file for terms of use. + */ +#include +#include + +#include +#include +#include +#include + +#include +#include + +#if !defined(WT_NO_SPIRIT) && BOOST_VERSION >= 104100 +# define JSON_PARSER +#endif + +#ifdef JSON_PARSER + +using namespace Wt; + +BOOST_AUTO_TEST_CASE( json_generate_object ) +{ + Json::Value initial; + Json::parse("{" + " \"first\" : 1," + " \"second\" : true," + " \"third\" : null," + " \"fourth\" : false," + " \"fifth\" : 2.7182818," + " \"sixth\" : 1.54e99," + " \"seventh\" : 9.87E88," + " \"eight\" : \"a string type value\"," + " \"ninth\" : {" + " \"sub-first\" : 1," + " \"sub-second\" : 2" + " }," + " \"tenth\" : [" + " true," + " false," + " null," + " 666" + " ]" + "}" + , + initial); + + Json::Object obj = initial; + std::string generated = Json::serialize(obj); + + Json::Value reconstructed; + Json::parse(generated, reconstructed); + + BOOST_REQUIRE(initial == reconstructed); +} + +BOOST_AUTO_TEST_CASE( json_generate_array ) +{ + Json::Value initial; + Json::parse("[" + " \"string1\"," + " \"string2 (after string1)\"," + " true," + " false," + " null," + " 10," + " 3.141592," + " 0.0000369," + " 1.23e4," + " {" + " \"first\" : \"it works\"," + " \"second\" : true" + " }," + " [" + " 10," + " 20," + " 30" + " ]" + "]" + , + initial); + + Json::Array arr = initial; + std::string generated = Json::serialize(arr); + + Json::Value reconstructed; + Json::parse(generated, reconstructed); + + BOOST_REQUIRE(initial == reconstructed); +} + +BOOST_AUTO_TEST_CASE( json_generate_UTF8 ) +{ + std::ifstream t("json/UTF-8-test2.json", std::ios::in | std::ios::binary); + BOOST_REQUIRE(t.good()); + std::string str((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + + Json::Object initial; + Json::parse(str, initial); + + std::string generated = Json::serialize(initial); + + Json::Object reconstructed; + Json::parse(generated, reconstructed); + + BOOST_REQUIRE(initial == reconstructed); +} + +#endif // JSON_PARSER diff --git a/test/json/UTF-8-test2.json b/test/json/UTF-8-test2.json new file mode 100644 index 0000000000..ec54e00122 --- /dev/null +++ b/test/json/UTF-8-test2.json @@ -0,0 +1,12 @@ +{ +"kosme": "κόσμε", +"2 bytes (U-00000080)": "€", +"3 bytes (U-00000800)": "ࠀ", +"4 bytes (U-00010000)": "𐀀", +"1 byte (U-0000007F)": "", +"2 bytes (U-000007FF)": "߿", +"3 bytes (U-0000FFFF)": "￿", +"U-0000D7FF = ed 9f bf": "퟿", +"U-0000E000 = ee 80 80": "", +"U-0000FFFD = ef bf bd": "�" +} diff --git a/test/mail/MailClientTest.C b/test/mail/MailClientTest.C index d9a9575029..c9f72e280b 100644 --- a/test/mail/MailClientTest.C +++ b/test/mail/MailClientTest.C @@ -9,6 +9,7 @@ #include #include +#include using namespace Wt; using namespace Wt::Mail; @@ -50,6 +51,7 @@ BOOST_AUTO_TEST_CASE( mail_test2 ) { Message m; m.setFrom(Mailbox("bas@kode.be", "Bas Deforche")); + m.setDate(WLocalDateTime::currentServerDateTime()); m.addRecipient(To, Mailbox("koen@emweb.be", "Koen Deforche")); m.addRecipient(Bcc, Mailbox("koen.deforche@gmail.com", diff --git a/test/render/CssParserTest.C b/test/render/CssParserTest.C index d6938c1607..c3f5e63bca 100644 --- a/test/render/CssParserTest.C +++ b/test/render/CssParserTest.C @@ -83,9 +83,7 @@ BOOST_AUTO_TEST_CASE( CssParser_test1 ) // Test hex color BOOST_REQUIRE( isValid(parser.parse("h1{color:#123}")) ); BOOST_REQUIRE( isValid(parser.parse("h1{color:#a11}")) ); - BOOST_REQUIRE( !isValid(parser.parse("h1{color:#11}")) ); BOOST_REQUIRE( isValid(parser.parse("h1{color:#123456}")) ); - BOOST_REQUIRE( !isValid(parser.parse("h1{color:#ggg}")) ); // Test multi term expressions BOOST_REQUIRE( isValid(parser.parse("h1{test: .1px}")) ); diff --git a/test/render/CssSelectorTest.C b/test/render/CssSelectorTest.C index 9a00f29579..ae63d4173b 100644 --- a/test/render/CssSelectorTest.C +++ b/test/render/CssSelectorTest.C @@ -170,4 +170,24 @@ BOOST_AUTO_TEST_CASE( CssSelector_testSpecificity ) delete style; } +BOOST_AUTO_TEST_CASE( CssSelector_test5 ) +{ + rapidxml::xml_document<>* doc = createXHtml( + "

"); + + Wt::Render::StyleSheet* style = Wt::Render::CssParser().parse( + "h1 h1 h1{}"); + + BOOST_REQUIRE( style ); + + + Wt::Render::Block b(doc, 0); + // Sanity check, match h1/h1/h1 to "h1 h1 h1" + BOOST_REQUIRE( Match::isMatch(childBlock(&b, list_of(0)(0)(0)), + style->rulesetAt(0).selector() ) ); + // FAIL h1/h1 to "h1 h1 h1" + BOOST_REQUIRE( !Match::isMatch(childBlock(&b, list_of(0)(0)), + style->rulesetAt(0).selector() ) ); +} + #endif // CSS_PARSER diff --git a/test/wdatetime/WDateTimeTest.C b/test/wdatetime/WDateTimeTest.C index 04fd8f9c1a..43e26e53ac 100644 --- a/test/wdatetime/WDateTimeTest.C +++ b/test/wdatetime/WDateTimeTest.C @@ -8,11 +8,36 @@ #include #include #include +#include BOOST_AUTO_TEST_CASE( WDateTime_test_WDate ) { Wt::WDate wd(2009, 10, 1); BOOST_REQUIRE(wd.toString() == "Thu Oct 1 2009"); + BOOST_REQUIRE(wd.isValid()); + BOOST_REQUIRE(!wd.isNull()); +} + +BOOST_AUTO_TEST_CASE( WDateTime_test_WDate2 ) +{ + Wt::WDate wd; + BOOST_REQUIRE(!wd.isValid()); + BOOST_REQUIRE(wd.isNull()); + + wd.setDate(40, 50, 2000); + + BOOST_REQUIRE(!wd.isValid()); + BOOST_REQUIRE(!wd.isNull()); + + wd = Wt::WDate::fromString("31/07/9999", "dd/MM/yyyy"); + + BOOST_REQUIRE(wd.isValid()); + BOOST_REQUIRE(!wd.isNull()); + + wd = wd.addDays(360); + + BOOST_REQUIRE(!wd.isValid()); + BOOST_REQUIRE(wd.isNull()); } BOOST_AUTO_TEST_CASE( WDateTime_test_WTime ) @@ -27,6 +52,9 @@ BOOST_AUTO_TEST_CASE( WDateTime_test_WDateTime ) Wt::WTime wt(12, 11, 31, 499); Wt::WDateTime wdt(wd, wt); + BOOST_REQUIRE(wdt.isValid()); + BOOST_REQUIRE(!wdt.isNull()); + BOOST_REQUIRE(wdt.toString() == "Thu Oct 1 12:11:31 2009"); BOOST_REQUIRE(wdt.toString("ddd MMM d HH:mm:ss:zzz yyyy") == "Thu Oct 1 12:11:31:499 2009"); @@ -127,3 +155,65 @@ BOOST_AUTO_TEST_CASE( WDateTime_test_WDateTime ) BOOST_REQUIRE(d.toString("ddd, MMM dd, yyyy; hh:mm:ss") == "Wed, Jun 14, 2000; 13:05:12"); } + +BOOST_AUTO_TEST_CASE( WDateTime_testspecial_WDateTime ) +{ + Wt::WDateTime wdt; + + BOOST_REQUIRE(!wdt.isValid()); + BOOST_REQUIRE(wdt.isNull()); + + wdt = Wt::WDateTime(Wt::WDate(), Wt::WTime()); + + BOOST_REQUIRE(!wdt.isValid()); + BOOST_REQUIRE(!wdt.isNull()); + + wdt = Wt::WDateTime(Wt::WDate(20, 30, 40), Wt::WTime()); + + BOOST_REQUIRE(!wdt.isValid()); + BOOST_REQUIRE(!wdt.isNull()); +} + +BOOST_AUTO_TEST_CASE( WDateTime_test_WLocalDateTime ) +{ + Wt::WDate wd(2009, 10, 1); + Wt::WTime wt(12, 11, 31, 499); + Wt::WDateTime wdt(wd, wt); + + Wt::WLocale loc; + loc.setTimeZone("EST-5EDT,M4.1.0,M10.5.0"); + + Wt::WLocalDateTime wldt = wdt.toLocalTime(loc); + + BOOST_REQUIRE(wldt.toString() == "2009-10-01 08:11:31"); + + Wt::WDateTime utc = wldt.toUTC(); + + BOOST_REQUIRE(utc == wdt); +} + +BOOST_AUTO_TEST_CASE( WDateTime_testspecial_WLocalDateTime ) +{ + Wt::WDateTime wdt; + + Wt::WLocale loc; + loc.setTimeZone("EST-5EDT,M4.1.0,M10.5.0"); + + Wt::WLocalDateTime wldt = wdt.toLocalTime(loc); + + BOOST_REQUIRE(!wldt.isValid()); + BOOST_REQUIRE(wldt.isNull()); + + Wt::WDateTime utc = wldt.toUTC(); + + BOOST_REQUIRE(utc == wdt); + + wldt.setDateTime(Wt::WDate(1976,6,14), Wt::WTime(3,0,0)); + + BOOST_REQUIRE(wldt.isValid()); + BOOST_REQUIRE(!wldt.isNull()); + + utc = wldt.toUTC(); + + std::cerr << utc.toString() << std::endl; +}