ExampleSourceViewer.C 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. /*
  2. * Copyright (C) 2009 Emweb bvba
  3. *
  4. * See the LICENSE file for terms of use.
  5. */
  6. #include <iostream>
  7. #include <stdlib.h>
  8. #include <algorithm>
  9. #include <Wt/WApplication>
  10. #include <Wt/WContainerWidget>
  11. #include <Wt/WEnvironment>
  12. #include <Wt/WLineEdit>
  13. #include <Wt/WGridLayout>
  14. #include <Wt/WHBoxLayout>
  15. #include <Wt/WPushButton>
  16. #include <Wt/WTable>
  17. #include <Wt/WText>
  18. #include <Wt/WTreeView>
  19. #include <Wt/WVBoxLayout>
  20. #include <Wt/WViewWidget>
  21. #include <boost/filesystem/operations.hpp>
  22. #include <boost/filesystem/exception.hpp>
  23. #include <boost/filesystem/convenience.hpp>
  24. #include <boost/algorithm/string.hpp>
  25. #include "ExampleSourceViewer.h"
  26. #include "FileItem.h"
  27. using namespace Wt;
  28. namespace fs = boost::filesystem;
  29. // Same as p.filename() in latest boost::filesystem
  30. static std::string filename(const fs::path& p)
  31. {
  32. #if BOOST_FILESYSTEM_VERSION < 3
  33. return p.empty() ? std::string() : *--p.end();
  34. #else
  35. return p.empty() ? std::string() : (*--p.end()).string();
  36. #endif
  37. }
  38. // Same as p.stem() in latest boost::filesystem
  39. static std::string stem(const fs::path& p)
  40. {
  41. std::string fn = filename(p);
  42. std::size_t pos = fn.find('.');
  43. if (pos == std::string::npos)
  44. return fn;
  45. else
  46. return fn.substr(0, pos);
  47. }
  48. // Should be same as p.parent_path() in latest boost::filesystem
  49. // This is not entirely according to fs::path::parent_path() in 1.39.0
  50. fs::path parent_path(const fs::path& p)
  51. {
  52. std::string fn = filename(p);
  53. std::string path = p.string();
  54. return path.substr(0, path.length() - fn.length() - 1);
  55. }
  56. static bool comparePaths(const fs::path& p1, const fs::path& p2)
  57. {
  58. return filename(p1) > filename(p2);
  59. }
  60. ExampleSourceViewer::ExampleSourceViewer(const std::string& deployPath,
  61. const std::string& examplesRoot,
  62. const std::string& examplesType)
  63. : deployPath_(deployPath),
  64. examplesRoot_(examplesRoot),
  65. examplesType_(examplesType)
  66. {
  67. wApp->internalPathChanged().connect
  68. (this, &ExampleSourceViewer::handlePathChange);
  69. handlePathChange();
  70. }
  71. void ExampleSourceViewer::handlePathChange()
  72. {
  73. WApplication *app = wApp;
  74. if (app->internalPathMatches(deployPath_)) {
  75. std::string example = app->internalPathNextPart(deployPath_);
  76. if (example.find("..") != std::string::npos
  77. || example.find('/') != std::string::npos
  78. || example.find('\\') != std::string::npos) {
  79. app->setInternalPathValid(false);
  80. setExample("INVALID_DIR", "INVALID");
  81. } else
  82. setExample(examplesRoot_ + example, example);
  83. }
  84. }
  85. void ExampleSourceViewer::setExample(const std::string& exampleDir,
  86. const std::string& example)
  87. {
  88. clear();
  89. bool exists = false;
  90. try {
  91. exists = fs::exists(exampleDir);
  92. } catch (std::exception&) {
  93. }
  94. if (!exists) {
  95. WApplication::instance()->setInternalPathValid(false);
  96. addWidget(new WText("No such example: " + exampleDir));
  97. return;
  98. }
  99. model_ = new WStandardItemModel(0, 1, this);
  100. if (examplesType_ == "CPP") {
  101. cppTraverseDir(model_->invisibleRootItem(), exampleDir);
  102. } else if (examplesType_ == "JAVA") {
  103. javaTraverseDir(model_->invisibleRootItem(), exampleDir);
  104. }
  105. WApplication::instance()->setTitle(tr("srcview.title." + example));
  106. WText *title =
  107. new WText(tr("srcview.title." + examplesType_ + "." + example));
  108. title->setInternalPathEncoding(true);
  109. exampleView_ = new WTreeView();
  110. exampleView_->setHeaderHeight(0);
  111. exampleView_->resize(300, WLength::Auto);
  112. exampleView_->setSortingEnabled(false);
  113. exampleView_->setModel(model_);
  114. exampleView_->expandToDepth(1);
  115. exampleView_->setSelectionMode(SingleSelection);
  116. exampleView_->setAlternatingRowColors(false);
  117. exampleView_->selectionChanged().connect
  118. (this, &ExampleSourceViewer::showFile);
  119. sourceView_ = new SourceView(FileItem::FileNameRole,
  120. FileItem::ContentsRole,
  121. FileItem::FilePathRole);
  122. sourceView_->setStyleClass("source-view");
  123. /*
  124. * Expand path to first file, to show something in the source viewer
  125. */
  126. WStandardItem *w = model_->item(0);
  127. do {
  128. exampleView_->setExpanded(w->index(), true);
  129. if (w->rowCount() > 0)
  130. w = w->child(0);
  131. else {
  132. exampleView_->select(w->index(), Select);
  133. w = 0;
  134. }
  135. } while (w);
  136. WVBoxLayout *topLayout = new WVBoxLayout();
  137. topLayout->addWidget(title);
  138. WHBoxLayout *gitLayout = new WHBoxLayout();
  139. gitLayout->addWidget(exampleView_, 0);
  140. gitLayout->addWidget(sourceView_, 1);
  141. topLayout->addLayout(gitLayout, 1);
  142. gitLayout->setResizable(0);
  143. /*
  144. * FIXME, in plain HTML mode, we should set a minimum size to the source
  145. * view, and remove this in enableAjax() ?
  146. */
  147. // sourceView_->setHeight("100%");
  148. setLayout(topLayout);
  149. setStyleClass("maindiv");
  150. }
  151. /*
  152. * Return the companion implementation/header file for a C++ source file.
  153. */
  154. static fs::path getCompanion(const fs::path& path)
  155. {
  156. std::string ext = fs::extension(path);
  157. if (ext == ".h")
  158. return parent_path(path) / (stem(path) + ".C");
  159. else if (ext == ".C" || ext == ".cpp")
  160. return parent_path(path) / (stem(path) + ".h");
  161. else
  162. return fs::path();
  163. }
  164. void ExampleSourceViewer::cppTraverseDir(WStandardItem* parent,
  165. const fs::path& path)
  166. {
  167. static const char *supportedFiles[] = {
  168. ".C", ".cpp", ".h", ".css", ".xml", ".png", ".gif", ".csv", ".ico", 0
  169. };
  170. FileItem* dir = new FileItem("/icons/yellow-folder-open.png", filename(path),
  171. "");
  172. parent->appendRow(dir);
  173. parent = dir;
  174. try {
  175. std::set<fs::path> paths;
  176. fs::directory_iterator end_itr;
  177. for (fs::directory_iterator i(path); i != end_itr; ++i)
  178. paths.insert(*i);
  179. std::vector<FileItem*> classes, files;
  180. std::vector<fs::path> dirs;
  181. while (!paths.empty()) {
  182. fs::path p = *paths.begin();
  183. paths.erase(p);
  184. // skip symbolic links and other files
  185. if (fs::is_symlink(p))
  186. continue;
  187. // skip files with an extension we do not want to handle
  188. if (fs::is_regular(p)) {
  189. std::string ext = fs::extension(p);
  190. bool supported = false;
  191. for (const char **s = supportedFiles; *s != 0; ++s)
  192. if (*s == ext) {
  193. supported = true;
  194. break;
  195. }
  196. if (!supported)
  197. continue;
  198. }
  199. // see if we have one file of a class (.C, .h)
  200. fs::path companion = getCompanion(p);
  201. if (!companion.empty()) {
  202. std::set<fs::path>::iterator it_companion = paths.find(companion);
  203. if (it_companion != paths.end()) {
  204. std::string className = stem(p);
  205. escapeText(className);
  206. std::string label = "<i>class</i> " + className;
  207. FileItem *classItem =
  208. new FileItem("/icons/cppclass.png", label, std::string());
  209. classItem->setFlags(classItem->flags() | ItemIsXHTMLText);
  210. FileItem *header = new FileItem("/icons/document.png", filename(p),
  211. p.string());
  212. FileItem *cpp = new FileItem("/icons/document.png",
  213. filename(*it_companion),
  214. (*it_companion).string());
  215. classItem->appendRow(header);
  216. classItem->appendRow(cpp);
  217. classes.push_back(classItem);
  218. paths.erase(it_companion);
  219. } else {
  220. FileItem *file = new FileItem("/icons/document.png", filename(p),
  221. p.string());
  222. files.push_back(file);
  223. }
  224. } else if (fs::is_directory(p)) {
  225. dirs.push_back(p);
  226. } else {
  227. FileItem *file = new FileItem("/icons/document.png", filename(p),
  228. p.string());
  229. files.push_back(file);
  230. }
  231. }
  232. std::sort(dirs.begin(), dirs.end(), comparePaths);
  233. for (unsigned int i = 0; i < classes.size(); i++)
  234. parent->appendRow(classes[i]);
  235. for (unsigned int i = 0; i < files.size(); i++)
  236. parent->appendRow(files[i]);
  237. for (unsigned int i = 0; i < dirs.size(); i++)
  238. cppTraverseDir(parent, dirs[i]);
  239. } catch (fs::filesystem_error& e) {
  240. std::cerr << e.what() << std::endl;
  241. }
  242. }
  243. void ExampleSourceViewer::javaTraversePackages(WStandardItem *parent,
  244. const fs::path& srcPath,
  245. const std::string packageName)
  246. {
  247. fs::directory_iterator end_itr;
  248. FileItem *packageItem = 0;
  249. for (fs::directory_iterator i(srcPath); i != end_itr; ++i) {
  250. fs::path p = *i;
  251. if (fs::is_regular(p)) {
  252. if (!packageItem) {
  253. packageItem = new FileItem("/icons/package.png", packageName, "");
  254. parent->appendRow(packageItem);
  255. }
  256. FileItem *file = new FileItem("/icons/javaclass.png", filename(p),
  257. p.string());
  258. packageItem->appendRow(file);
  259. }
  260. }
  261. for (fs::directory_iterator i(srcPath); i != end_itr; ++i) {
  262. fs::path p = *i;
  263. if (fs::is_directory(p)) {
  264. std::string pn = packageName;
  265. if (!pn.empty())
  266. pn += ".";
  267. pn += filename(p);
  268. javaTraversePackages(parent, p, pn);
  269. }
  270. }
  271. }
  272. void ExampleSourceViewer::javaTraverseDir(WStandardItem* parent,
  273. const fs::path& path)
  274. {
  275. FileItem* dir = new FileItem("/icons/yellow-folder-open.png", filename(path),
  276. "");
  277. parent->appendRow(dir);
  278. parent = dir;
  279. std::vector<fs::path> files, dirs;
  280. fs::directory_iterator end_itr;
  281. for (fs::directory_iterator i(path); i != end_itr; ++i) {
  282. fs::path p = *i;
  283. if (fs::is_directory(p)) {
  284. if (filename(p) == "src") {
  285. FileItem* dir = new FileItem("/icons/package-folder-open.png",
  286. filename(p), "");
  287. parent->appendRow(dir);
  288. javaTraversePackages(dir, p, "");
  289. } else
  290. dirs.push_back(p);
  291. } else {
  292. files.push_back(p);
  293. }
  294. }
  295. std::sort(dirs.begin(), dirs.end(), comparePaths);
  296. std::sort(files.begin(), files.end(), comparePaths);
  297. for (unsigned int i = 0; i < dirs.size(); i++)
  298. javaTraverseDir(parent, dirs[i]);
  299. for (unsigned int i = 0; i < files.size(); i++) {
  300. FileItem *file = new FileItem("/icons/document.png", filename(files[i]),
  301. files[i].string());
  302. parent->appendRow(file);
  303. }
  304. }
  305. /*! \brief Displayed the currently selected file.
  306. */
  307. void ExampleSourceViewer::showFile() {
  308. if (exampleView_->selectedIndexes().empty())
  309. return;
  310. WModelIndex selected = *exampleView_->selectedIndexes().begin();
  311. // expand a folder when clicked
  312. if (exampleView_->model()->rowCount(selected) > 0
  313. && !exampleView_->isExpanded(selected))
  314. exampleView_->setExpanded(selected, true);
  315. // (for a file,) load data in source viewer
  316. sourceView_->setIndex(selected);
  317. }