1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include <fstream>
8 #include <iostream>
9 
10 #include <boost/lexical_cast.hpp>
11 #include <boost/tokenizer.hpp>
12 #include <boost/algorithm/string.hpp>
13 
14 #include <Wt/WAnchor.h>
15 #include <Wt/WApplication.h>
16 #include <Wt/WEnvironment.h>
17 #include <Wt/WLogger.h>
18 #include <Wt/WMenu.h>
19 #include <Wt/WPushButton.h>
20 #include <Wt/WStackedWidget.h>
21 #include <Wt/WTabWidget.h>
22 #include <Wt/WTable.h>
23 #include <Wt/WTableCell.h>
24 #include <Wt/WTemplate.h>
25 #include <Wt/WText.h>
26 #include <Wt/WViewWidget.h>
27 #include <Wt/WVBoxLayout.h>
28 
29 #include "Home.h"
30 #include "view/BlogView.h"
31 
32 using namespace Wt;
33 
34 static const std::string SRC_INTERNAL_PATH = "src";
35 
~Home()36 Home::~Home()
37 {
38 }
39 
Home(const WEnvironment & env,Dbo::SqlConnectionPool & blogDb,const std::string & title,const std::string & resourceBundle,const std::string & cssPath)40 Home::Home(const WEnvironment& env,
41 	   Dbo::SqlConnectionPool& blogDb,
42 	   const std::string& title, const std::string& resourceBundle,
43 	   const std::string& cssPath)
44   : WApplication(env),
45     blogDb_(blogDb),
46     homePage_(0),
47     sourceViewer_(0)
48 {
49   messageResourceBundle().use(appRoot() + resourceBundle, false);
50 
51   useStyleSheet(cssPath + "/wt.css");
52   useStyleSheet(cssPath + "/wt_ie.css", "lt IE 7", "all");
53   useStyleSheet("css/home.css");
54   useStyleSheet("css/sourceview.css");
55   useStyleSheet("css/chatwidget.css");
56   useStyleSheet("css/chatwidget_ie6.css", "lt IE 7", "all");
57   setTitle(title);
58 
59   setLocale("");
60   language_ = 0;
61 }
62 
init()63 void Home::init()
64 {
65   internalPathChanged().connect(this, &Home::setup);
66   internalPathChanged().connect(this, &Home::setLanguageFromPath);
67   internalPathChanged().connect(this, &Home::logInternalPath);
68 
69   setup();
70 
71   setLanguageFromPath();
72 }
73 
setup()74 void Home::setup()
75 {
76   /*
77    * This function switches between the two major components of the homepage,
78    * depending on the internal path:
79    * /src -> source viewer
80    * /... -> homepage
81    *
82    * FIXME: we should take into account language /cn/src ...
83    */
84   std::string base = internalPathNextPart("/");
85 
86   if (base == SRC_INTERNAL_PATH) {
87     if (!sourceViewer_) {
88       root()->removeChild(homePage_);
89       homePage_ = nullptr;
90 
91       root()->clear();
92 
93       auto layout = std::make_unique<WVBoxLayout>();
94       layout->setContentsMargins(0, 0, 0, 0);
95       auto source = sourceViewer("/" + SRC_INTERNAL_PATH + "/");
96       sourceViewer_ = source.get();
97       layout->addWidget(std::move(source));
98 
99       root()->setLayout(std::move(layout));
100     }
101   } else {
102     if (!homePage_) {
103       root()->removeChild(sourceViewer_);
104       sourceViewer_ = nullptr;
105       root()->clear();
106 
107       createHome();
108 
109       setLanguageFromPath();
110     }
111   }
112 }
113 
createHome()114 void Home::createHome()
115 {
116   WTemplate *result = root()->addWidget(std::make_unique<WTemplate>(tr("template")));
117   homePage_ = result;
118 
119   auto languagesDiv = std::make_unique<WContainerWidget>();
120   languagesDiv->setId("top_languages");
121 
122   for (unsigned i = 0; i < languages.size(); ++i) {
123     if (i != 0)
124       languagesDiv->addWidget(std::make_unique<WText>("- "));
125 
126     const Lang& l = languages[i];
127 
128     languagesDiv->addWidget(std::make_unique<WAnchor>(WLink(LinkType::InternalPath, l.path_), l.longDescription_));
129   }
130 
131   auto contents = std::make_unique<WStackedWidget>();
132   WAnimation fade(AnimationEffect::Fade, TimingFunction::Linear, 250);
133   contents->setTransitionAnimation(fade);
134   contents->setId("main_page");
135 
136   auto mainMenu = std::make_unique<WMenu>(contents.get());
137   mainMenu_ = mainMenu.get();
138   mainMenu_->addItem
139     (tr("introduction"), introduction())->setPathComponent("");
140 
141   mainMenu_->addItem
142     (tr("blog"), deferCreate(std::bind(&Home::blog, this)));
143 
144   mainMenu_->addItem
145     (tr("features"), wrapView(&Home::features),
146      ContentLoading::Eager);
147 
148   mainMenu_->addItem
149     (tr("documentation"), wrapView(&Home::documentation),
150      ContentLoading::Eager);
151 
152   mainMenu_->addItem
153     (tr("examples"), examples(),
154      ContentLoading::Eager)->setPathComponent("examples/");
155 
156   mainMenu_->addItem
157     (tr("download"), deferCreate(std::bind(&Home::download, this)),
158      ContentLoading::Eager);
159 
160   mainMenu_->addItem
161     (tr("community"), wrapView(&Home::community),
162      ContentLoading::Eager);
163 
164   mainMenu_->addItem
165     (tr("other-language"), wrapView(&Home::otherLanguage),
166      ContentLoading::Eager);
167 
168   mainMenu_->itemSelectRendered().connect(this, &Home::updateTitle);
169 
170   mainMenu_->itemSelected().connect(this, &Home::googleAnalyticsLogger);
171 
172   // Make the menu be internal-path aware.
173   mainMenu_->setInternalPathEnabled("/");
174 
175   sideBarContent_ = std::make_unique<WContainerWidget>();
176 
177   result->bindWidget("languages", std::move(languagesDiv));
178   result->bindWidget("menu", std::move(mainMenu));
179   result->bindWidget("contents", std::move(contents));
180   result->bindWidget("sidebar", std::move(sideBarContent_));
181 }
182 
setLanguage(int index)183 void Home::setLanguage(int index)
184 {
185   if (homePage_) {
186     const Lang& l = languages[index];
187 
188     setLocale(l.code_);
189 
190     std::string langPath = l.path_;
191     mainMenu_->setInternalBasePath(langPath);
192     examplesMenu_->setInternalBasePath(langPath + "examples");
193     BlogView *blog = dynamic_cast<BlogView *>(findWidget("blog"));
194     if (blog)
195       blog->setInternalBasePath(langPath + "blog/");
196     updateTitle();
197 
198     language_ = index;
199   }
200 }
201 
linkSourceBrowser(const std::string & example)202 std::unique_ptr<WWidget> Home::linkSourceBrowser(const std::string& example)
203 {
204   /*
205    * Instead of using a WAnchor, which will not progress properly because
206    * it is wrapped with wrapView() (-- should we not fix that?), we use
207    * a WText which contains an anchor, and enable internal path encoding.
208    */
209   std::string path = "#/" + SRC_INTERNAL_PATH + "/" + example;
210   std::unique_ptr<WText> a(std::make_unique<WText>(tr("source-browser-link").arg(path)));
211   a->setInternalPathEncoding(true);
212   return std::move(a);
213 }
214 
setLanguageFromPath()215 void Home::setLanguageFromPath()
216 {
217   std::string langPath = internalPathNextPart("/");
218 
219   if (langPath.empty())
220     langPath = '/';
221   else
222     langPath = '/' + langPath + '/';
223 
224   int newLanguage = 0;
225 
226   for (unsigned i = 0; i < languages.size(); ++i) {
227     if (languages[i].path_ == langPath) {
228       newLanguage = i;
229       break;
230     }
231   }
232 
233   if (newLanguage != language_)
234     setLanguage(newLanguage);
235 }
236 
updateTitle()237 void Home::updateTitle()
238 {
239   if (mainMenu_->currentItem()) {
240     setTitle(tr("wt") + " - " + mainMenu_->currentItem()->text());
241   }
242 }
243 
logInternalPath(const std::string & path)244 void Home::logInternalPath(const std::string& path)
245 {
246   // simulate an access log for the interal paths
247   log("path") << path;
248 
249   // If this goes to /src, we need to invoke google analytics method too
250   if (path.size() >= 4 && path.substr(0, 4) == "/src") {
251     googleAnalyticsLogger();
252   }
253 }
254 
introduction()255 std::unique_ptr<WWidget> Home::introduction()
256 {
257   return std::make_unique<WText>(tr("home.intro"));
258 }
259 
blog()260 std::unique_ptr<WWidget> Home::blog()
261 {
262   const Lang& l = languages[language_];
263   std::string langPath = l.path_;
264   std::unique_ptr<BlogView> blog
265       = std::make_unique<BlogView>(langPath + "blog/",
266                                 blogDb_, "/wt/blog/feed/");
267   blog->setObjectName("blog");
268 
269   if (!blog->user().empty())
270     chatSetUser(blog->user());
271 
272   blog->userChanged().connect(std::bind(&Home::chatSetUser, this, std::placeholders::_1));
273 
274   return std::move(blog);
275 }
276 
chatSetUser(const WString & userName)277 void Home::chatSetUser(const WString& userName)
278 {
279   WApplication::instance()->doJavaScript
280     ("if (window.chat && window.chat.emit) {"
281      """try {"
282      ""  "window.chat.emit(window.chat, 'login', "
283      ""                    "" + userName.jsStringLiteral() + "); "
284      """} catch (e) {"
285      ""  "window.chatUser=" + userName.jsStringLiteral() + ";"
286      """}"
287      "} else "
288      """window.chatUser=" + userName.jsStringLiteral() + ";");
289 }
290 
status()291 std::unique_ptr<WWidget> Home::status()
292 {
293   return std::make_unique<WText>(tr("home.status"));
294 }
295 
features()296 std::unique_ptr<WWidget> Home::features()
297 {
298   return std::unique_ptr<WText>(std::make_unique<WText>(tr("home.features")));
299 }
300 
documentation()301 std::unique_ptr<WWidget> Home::documentation()
302 {
303   std::unique_ptr<WText> result
304       = std::make_unique<WText>(tr("home.documentation"));
305   result->setInternalPathEncoding(true);
306   return std::move(result);
307 }
308 
otherLanguage()309 std::unique_ptr<WWidget> Home::otherLanguage()
310 {
311   return std::unique_ptr<WText>(std::make_unique<WText>(tr("home.other-language")));
312 }
313 
wrapView(std::unique_ptr<WWidget> (Home::* createWidget)())314 std::unique_ptr<WWidget> Home::wrapView(std::unique_ptr<WWidget> (Home::*createWidget)())
315 {
316   return makeStaticModel(std::bind(createWidget, this));
317 }
318 
href(const std::string & url,const std::string & description)319 std::string Home::href(const std::string& url, const std::string& description)
320 {
321   return "<a href=\"" + url + "\" target=\"_blank\">" + description + "</a>";
322 }
323 
community()324 std::unique_ptr<WWidget> Home::community()
325 {
326   return std::make_unique<WText>(tr("home.community"));
327 }
328 
readReleases(WTable * releaseTable)329 void Home::readReleases(WTable *releaseTable)
330 {
331   std::ifstream f((filePrefix() + "releases.txt").c_str());
332 
333   releaseTable->clear();
334 
335   releaseTable->elementAt(0, 0)
336     ->addWidget(std::make_unique<WText>(tr("home.download.version")));
337   releaseTable->elementAt(0, 1)
338     ->addWidget(std::make_unique<WText>(tr("home.download.date")));
339   releaseTable->elementAt(0, 2)
340     ->addWidget(std::make_unique<WText>(tr("home.download.description")));
341 
342   releaseTable->elementAt(0, 0)->resize(WLength(15, LengthUnit::FontEx),
343 					WLength::Auto);
344   releaseTable->elementAt(0, 1)->resize(WLength(15, LengthUnit::FontEx),
345 					WLength::Auto);
346 
347   int row = 1;
348 
349   while (f) {
350     std::string line;
351     getline(f, line);
352 
353     if (f) {
354       typedef boost::tokenizer<boost::escaped_list_separator<char> >
355 	CsvTokenizer;
356       CsvTokenizer tok(line);
357 
358       CsvTokenizer::iterator i=tok.begin();
359 
360       std::string fileName = *i;
361       std::string description = *(++i);
362       releaseTable->elementAt(row, 1)->addWidget(std::make_unique<WText>(*(++i)));
363       releaseTable->elementAt(row, 2)->addWidget(std::make_unique<WText>(*(++i)));
364 
365       ++i;
366       std::string url = "http://prdownloads.sourceforge.net/witty/"
367 	+ fileName + "?download";
368       if (i != tok.end())
369 	url = *i;
370 
371       releaseTable->elementAt(row, 0)->addWidget
372         (std::make_unique<WText>(href(url, description)));
373 
374       ++row;
375     }
376   }
377 }
378 
379 #ifdef WT_EMWEB_BUILD
quoteForm()380 std::unique_ptr<WWidget> Home::quoteForm()
381 {
382   auto result = std::make_unique<WContainerWidget>();
383   result->setStyleClass("quote");
384 
385   WTemplate *requestTemplate =
386       result->addWidget(std::make_unique<WTemplate>(tr("quote.request")));
387 
388   auto quoteButton = std::make_unique<WPushButton>(tr("quote.requestbutton"));
389   auto quoteButtonPtr = requestTemplate->bindWidget("button", std::move(quoteButton));
390 
391   auto quoteForm = result->addWidget(std::move(createQuoteForm()));
392 
393   quoteButtonPtr->clicked().connect(quoteForm, &WWidget::show);
394   quoteButtonPtr->clicked().connect(requestTemplate, &WWidget::hide);
395 
396   quoteForm->hide();
397 
398   return result;
399 }
400 #endif // WT_EMWEB_BUILD
401 
download()402 std::unique_ptr<WWidget> Home::download()
403 {
404   auto result = std::make_unique<WContainerWidget>();
405   result->addWidget(std::make_unique<WText>(tr("home.download")));
406 
407   result->addWidget(std::make_unique<WText>(tr("home.download.license")));
408 
409 #ifdef WT_EMWEB_BUILD
410   result->addWidget(std::move(quoteForm()));
411 #endif // WT_EMWEB_BUILD
412 
413   result->addWidget(std::make_unique<WText>(tr("home.download.packages")));
414 
415   auto releases = std::make_unique<WTable>();
416   readReleases(releases.get());
417   releases_ = result->addWidget(std::move(releases));
418 
419   result->addWidget(std::make_unique<WText>(tr("home.download.other")));
420 
421   return std::move(result);
422 }
423 
424 
tr(const char * key)425 WString Home::tr(const char *key)
426 {
427   return WString::tr(key);
428 }
429 
googleAnalyticsLogger()430 void Home::googleAnalyticsLogger()
431 {
432   doJavaScript("if (window.ga) ga('send','pageview',"
433 	       + WWebWidget::jsStringLiteral(environment().deploymentPath()
434 					     + internalPath()) + ");");
435 }
436 
437