Wt examples  3.2.2
TreeViewDragDrop.C
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 #include <fstream>
7 
8 #include <Wt/WApplication>
9 #include <Wt/WComboBox>
10 #include <Wt/WContainerWidget>
11 #include <Wt/WDatePicker>
12 #include <Wt/WDateValidator>
13 #include <Wt/WDialog>
14 #include <Wt/WEnvironment>
15 #include <Wt/WIntValidator>
16 #include <Wt/WItemDelegate>
17 #include <Wt/WLabel>
18 #include <Wt/WLineEdit>
19 #include <Wt/WMessageBox>
20 #include <Wt/WPushButton>
21 #include <Wt/WRegExpValidator>
22 #include <Wt/WGridLayout>
23 #include <Wt/WPopupMenu>
24 #include <Wt/WSortFilterProxyModel>
25 #include <Wt/WStandardItem>
26 #include <Wt/WStandardItemModel>
27 #include <Wt/WTableView>
28 #include <Wt/WTreeView>
29 #include <Wt/WText>
30 #include <Wt/WVBoxLayout>
31 
32 #include <Wt/Chart/WPieChart>
33 
34 #include "CsvUtil.h"
35 #include "FolderView.h"
36 
37 using namespace Wt;
38 
43 
52 {
53 public:
56  FileModel(WObject *parent)
57  : WStandardItemModel(parent) { }
58 
61  virtual std::string mimeType() const {
63  }
64 
67 
70 };
71 
74 
78 class FileEditDialog : public WDialog
79 {
80 public:
82  : WDialog("Edit..."),
83  model_(model),
84  item_(item)
85  {
86  int modelRow = item_.row();
87 
88  resize(300, WLength::Auto);
89 
90  /*
91  * Create the form widgets, and load them with data from the model.
92  */
93 
94  // name
95  nameEdit_ = new WLineEdit(asString(model_->data(modelRow, 1)));
96 
97  // type
98  typeEdit_ = new WComboBox();
99  typeEdit_->addItem("Document");
100  typeEdit_->addItem("Spreadsheet");
101  typeEdit_->addItem("Presentation");
102  typeEdit_->setCurrentIndex
103  (typeEdit_->findText(asString(model_->data(modelRow, 2))));
104 
105  // size
106  sizeEdit_ = new WLineEdit(asString(model_->data(modelRow, 3)));
107  sizeEdit_->setValidator
108  (new WIntValidator(0, std::numeric_limits<int>::max(), this));
109 
110  // created
111  createdPicker_ = new WDatePicker();
112  createdPicker_->lineEdit()->validator()->setMandatory(true);
113  createdPicker_->setFormat(FileModel::dateEditFormat);
114  createdPicker_->setDate(boost::any_cast<WDate>(model_->data(modelRow, 4)));
115 
116  // modified
117  modifiedPicker_ = new WDatePicker();
118  modifiedPicker_->lineEdit()->validator()->setMandatory(true);
119  modifiedPicker_->setFormat(FileModel::dateEditFormat);
120  modifiedPicker_->setDate(boost::any_cast<WDate>(model_->data(modelRow, 5)));
121 
122  /*
123  * Use a grid layout for the labels and fields
124  */
125  WGridLayout *layout = new WGridLayout();
126 
127  WLabel *l;
128  int row = 0;
129 
130  layout->addWidget(l = new WLabel("Name:"), row, 0);
131  layout->addWidget(nameEdit_, row, 1);
132  l->setBuddy(nameEdit_);
133  ++row;
134 
135  layout->addWidget(l = new WLabel("Type:"), row, 0);
136  layout->addWidget(typeEdit_, row, 1);
137  l->setBuddy(typeEdit_);
138  ++row;
139 
140  layout->addWidget(l = new WLabel("Size:"), row, 0);
141  layout->addWidget(sizeEdit_, row, 1);
142  l->setBuddy(sizeEdit_);
143  ++row;
144 
145  layout->addWidget(l = new WLabel("Created:"), row, 0);
146  layout->addWidget(createdPicker_->lineEdit(), row, 1);
147  layout->addWidget(createdPicker_, row, 2);
148  l->setBuddy(createdPicker_->lineEdit());
149  ++row;
150 
151  layout->addWidget(l = new WLabel("Modified:"), row, 0);
152  layout->addWidget(modifiedPicker_->lineEdit(), row, 1);
153  layout->addWidget(modifiedPicker_, row, 2);
154  l->setBuddy(modifiedPicker_->lineEdit());
155  ++row;
156 
157  WPushButton *b;
158  WContainerWidget *buttons = new WContainerWidget();
159  buttons->addWidget(b = new WPushButton("Save"));
160  b->clicked().connect(this, &WDialog::accept);
161  contents()->enterPressed().connect(this, &WDialog::accept);
162  buttons->addWidget(b = new WPushButton("Cancel"));
163  b->clicked().connect(this, &WDialog::reject);
164 
165  /*
166  * Focus the form widget that corresonds to the selected item.
167  */
168  switch (item.column()) {
169  case 2:
170  typeEdit_->setFocus(); break;
171  case 3:
172  sizeEdit_->setFocus(); break;
173  case 4:
174  createdPicker_->lineEdit()->setFocus(); break;
175  case 5:
176  modifiedPicker_->lineEdit()->setFocus(); break;
177  default:
178  nameEdit_->setFocus(); break;
179  }
180 
181  layout->addWidget(buttons, row, 0, 0, 3, AlignCenter);
182  layout->setColumnStretch(1, 1);
183 
184  contents()->setLayout(layout);
185 
186  finished().connect(this, &FileEditDialog::handleFinish);
187 
188  show();
189  }
190 
191 private:
194 
195  WLineEdit *nameEdit_, *sizeEdit_;
197  WDatePicker *createdPicker_, *modifiedPicker_;
198 
199  void handleFinish(DialogCode result)
200  {
201  if (result == WDialog::Accepted) {
202  /*
203  * Update the model with data from the edit widgets.
204  *
205  * You will want to do some validation here...
206  *
207  * Note that we directly update the source model to avoid
208  * problems caused by the dynamic sorting of the proxy model,
209  * which reorders row numbers, and would cause us to switch to editing
210  * the wrong data.
211  */
212  WAbstractItemModel *m = model_;
213  int modelRow = item_.row();
214 
215  WAbstractProxyModel *proxyModel = dynamic_cast<WAbstractProxyModel *>(m);
216  if (proxyModel) {
217  m = proxyModel->sourceModel();
218  modelRow = proxyModel->mapToSource(item_).row();
219  }
220 
221  m->setData(modelRow, 1, boost::any(nameEdit_->text()));
222  m->setData(modelRow, 2, boost::any(typeEdit_->currentText()));
223  m->setData(modelRow, 3, boost::any(boost::lexical_cast<int>
224  (sizeEdit_->text().toUTF8())));
225  m->setData(modelRow, 4, boost::any(createdPicker_->date()));
226  m->setData(modelRow, 5, boost::any(modifiedPicker_->date()));
227  }
228 
229  delete this;
230  }
231 
232 };
233 
238 {
239 public:
243  : WApplication(env),
244  popup_(0),
245  popupActionBox_(0)
246  {
247  setCssTheme("polished");
248 
249  /*
250  * Create the data models.
251  */
252  folderModel_ = new WStandardItemModel(0, 1, this);
253  populateFolders();
254 
255  fileModel_ = new FileModel(this);
256  populateFiles();
257 
258  /*
259  The header items are also endered using an ItemDelegate, and thus
260  support other data, e.g.:
261 
262  fileModel_->setHeaderFlags(0, Horizontal, HeaderIsUserCheckable);
263  fileModel_->setHeaderData(0, Horizontal,
264  std::string("icons/file.gif"),
265  Wt::DecorationRole);
266  */
267  fileFilterModel_ = new WSortFilterProxyModel(this);
268  fileFilterModel_->setSourceModel(fileModel_);
269  fileFilterModel_->setDynamicSortFilter(true);
270  fileFilterModel_->setFilterKeyColumn(0);
271  fileFilterModel_->setFilterRole(UserRole);
272 
273  /*
274  * Setup the user interface.
275  */
276  createUI();
277  }
278 
279  virtual ~TreeViewDragDrop() {
280  delete popup_;
281  delete popupActionBox_;
282  }
283 
284 private:
287 
290 
293 
295  std::map<std::string, WString> folderNameMap_;
296 
299 
302 
305 
308 
311  void createUI() {
312  WContainerWidget *w = root();
313  w->setStyleClass("maindiv");
314 
315  /*
316  * The main layout is a 3x2 grid layout.
317  */
318  WGridLayout *layout = new WGridLayout();
319  layout->addWidget(createTitle("Folders"), 0, 0);
320  layout->addWidget(createTitle("Files"), 0, 1);
321  layout->addWidget(folderView(), 1, 0);
322  layout->setColumnResizable(0);
323 
324  // select the first folder
325  folderView_->select(folderModel_->index(0, 0, folderModel_->index(0, 0)));
326 
327  WVBoxLayout *vbox = new WVBoxLayout();
328  vbox->addWidget(fileView(), 1);
329  vbox->addWidget(pieChart(), 1);
330  vbox->setResizable(0);
331 
332  layout->addLayout(vbox, 1, 1);
333 
334  layout->addWidget(aboutDisplay(), 2, 0, 1, 2);
335 
336  /*
337  * Let row 1 and column 1 take the excess space.
338  */
339  layout->setRowStretch(1, 1);
340  layout->setColumnStretch(1, 1);
341 
342  w->setLayout(layout);
343  }
344 
347  WText *createTitle(const WString& title) {
348  WText *result = new WText(title);
349  result->setInline(false);
350  result->setStyleClass("title");
351 
352  return result;
353  }
354 
358  WTreeView *treeView = new FolderView();
359 
360  /*
361  * To support right-click, we need to disable the built-in browser
362  * context menu.
363  *
364  * Note that disabling the context menu and catching the
365  * right-click does not work reliably on all browsers.
366  */
367  treeView->setAttributeValue
368  ("oncontextmenu",
369  "event.cancelBubble = true; event.returnValue = false; return false;");
370  treeView->setModel(folderModel_);
371  treeView->resize(200, WLength::Auto);
373  treeView->expandToDepth(1);
374  treeView->selectionChanged()
376 
377  treeView->mouseWentUp().connect(this, &TreeViewDragDrop::showPopup);
378 
379  folderView_ = treeView;
380 
381  return treeView;
382  }
383 
387  WTableView *tableView = new WTableView();
388 
389  tableView->setAlternatingRowColors(true);
390 
391  tableView->setModel(fileFilterModel_);
393  tableView->setDragEnabled(true);
394 
395  tableView->setColumnWidth(0, 100);
396  tableView->setColumnWidth(1, 150);
397  tableView->setColumnWidth(2, 100);
398  tableView->setColumnWidth(3, 60);
399  tableView->setColumnWidth(4, 100);
400  tableView->setColumnWidth(5, 100);
401 
402  WItemDelegate *delegate = new WItemDelegate(this);
404  tableView->setItemDelegateForColumn(4, delegate);
405  tableView->setItemDelegateForColumn(5, delegate);
406 
407  tableView->setColumnAlignment(3, AlignRight);
408  tableView->setColumnAlignment(4, AlignRight);
409  tableView->setColumnAlignment(5, AlignRight);
410 
411  tableView->sortByColumn(1, AscendingOrder);
412 
413  tableView->doubleClicked().connect(this, &TreeViewDragDrop::editFile);
414 
415  fileView_ = tableView;
416 
417  return tableView;
418  }
419 
422  void editFile(const WModelIndex& item) {
423  new FileEditDialog(fileView_->model(), item);
424  }
425 
429  using namespace Chart;
430 
431  WPieChart *chart = new WPieChart();
432  chart->setModel(fileFilterModel_);
433  chart->setTitle("File sizes");
434 
435  chart->setLabelsColumn(1); // Name
436  chart->setDataColumn(3); // Size
437 
438  chart->setPerspectiveEnabled(true, 0.2);
439  chart->setDisplayLabels(Outside | TextLabel);
440 
441  if (!WApplication::instance()->environment().ajax()) {
442  chart->resize(500, 200);
443  chart->setMargin(WLength::Auto, Left | Right);
445  w->addWidget(chart);
446  w->setStyleClass("about");
447  return w;
448  } else {
449  chart->setStyleClass("about");
450  return chart;
451  }
452  }
453 
457  WText *result = new WText(WString::tr("about-text"));
458  result->setStyleClass("about");
459  return result;
460  }
461 
465  void folderChanged() {
466  if (folderView_->selectedIndexes().empty())
467  return;
468 
469  WModelIndex selected = *folderView_->selectedIndexes().begin();
470  boost::any d = selected.data(UserRole);
471  if (!d.empty()) {
472  std::string folder = boost::any_cast<std::string>(d);
473 
474  // For simplicity, we assume here that the folder-id does not
475  // contain special regexp characters, otherwise these need to be
476  // escaped -- or use the \Q \E qutoing escape regular expression
477  // syntax (and escape \E)
478  fileFilterModel_->setFilterRegExp(folder);
479  }
480  }
481 
484  void showPopup(const WModelIndex& item, const WMouseEvent& event) {
485  if (event.button() == WMouseEvent::RightButton) {
486  // Select the item, it was not yet selected.
487  if (!folderView_->isSelected(item))
488  folderView_->select(item);
489 
490  if (!popup_) {
491  popup_ = new WPopupMenu();
492  popup_->addItem("icons/folder_new.gif", "Create a New Folder");
493  popup_->addItem("Rename this Folder")->setCheckable(true);
494  popup_->addItem("Delete this Folder");
495  popup_->addSeparator();
496  popup_->addItem("Folder Details");
497  popup_->addSeparator();
498  popup_->addItem("Application Inventory");
499  popup_->addItem("Hardware Inventory");
500  popup_->addSeparator();
501 
502  WPopupMenu *subMenu = new WPopupMenu();
503  subMenu->addItem("Sub Item 1");
504  subMenu->addItem("Sub Item 2");
505  popup_->addMenu("File Deployments", subMenu);
506 
507  /*
508  * This is one method of executing a popup, which does not block a
509  * thread for a reentrant event loop, and thus scales.
510  *
511  * Alternatively you could call WPopupMenu::exec(), which returns
512  * the result, but while waiting for it, blocks the thread.
513  */
514  popup_->aboutToHide().connect(this, &TreeViewDragDrop::popupAction);
515  }
516 
517  if (popup_->isHidden())
518  popup_->popup(event);
519  else
520  popup_->hide();
521  }
522  }
523 
526  void popupAction() {
527  if (popup_->result()) {
528  /*
529  * You could also bind extra data to an item using setData() and
530  * check here for the action asked. For now, we just use the text.
531  */
532  WString text = popup_->result()->text();
533  popup_->hide();
534 
535  popupActionBox_ = new WMessageBox("Sorry.","Action '" + text
536  + "' is not implemented.", NoIcon, Ok);
537  popupActionBox_->buttonClicked()
538  .connect(this, &TreeViewDragDrop::dialogDone);
539  popupActionBox_->show();
540  } else {
541  popup_->hide();
542  }
543  }
544 
547  void dialogDone() {
548  delete popupActionBox_;
549  popupActionBox_ = 0;
550  }
551 
559  void populateFiles() {
560  fileModel_->invisibleRootItem()->setRowCount(0);
561 
562  std::ifstream f((appRoot() + "data/files.csv").c_str());
563 
564  if (!f)
565  throw std::runtime_error("Could not read: data/files.csv");
566 
567  readFromCsv(f, fileModel_);
568 
569  for (int i = 0; i < fileModel_->rowCount(); ++i) {
570  WStandardItem *item = fileModel_->item(i, 0);
571  item->setFlags(item->flags() | ItemIsDragEnabled);
572  item->setIcon("icons/file.gif");
573 
574  std::string folderId = item->text().toUTF8();
575 
576  item->setData(boost::any(folderId), UserRole);
577  item->setText(folderNameMap_[folderId]);
578 
579  convertToDate(fileModel_->item(i, 4));
580  convertToDate(fileModel_->item(i, 5));
581  }
582  }
583 
588  item->setData(boost::any(d), DisplayRole);
589  }
590 
594  WStandardItem *level1, *level2;
595 
596  folderModel_->appendRow(level1 = createFolderItem("San Fransisco"));
597  level1->appendRow(level2 = createFolderItem("Investors", "sf-investors"));
598  level1->appendRow(level2 = createFolderItem("Fellows", "sf-fellows"));
599 
600  folderModel_->appendRow(level1 = createFolderItem("Sophia Antipolis"));
601  level1->appendRow(level2 = createFolderItem("R&D", "sa-r_d"));
602  level1->appendRow(level2 = createFolderItem("Services", "sa-services"));
603  level1->appendRow(level2 = createFolderItem("Support", "sa-support"));
604  level1->appendRow(level2 = createFolderItem("Billing", "sa-billing"));
605 
606  folderModel_->appendRow(level1 = createFolderItem("New York"));
607  level1->appendRow(level2 = createFolderItem("Marketing", "ny-marketing"));
608  level1->appendRow(level2 = createFolderItem("Sales", "ny-sales"));
609  level1->appendRow(level2 = createFolderItem("Advisors", "ny-advisors"));
610 
611  folderModel_->appendRow(level1 = createFolderItem
612  (WString::fromUTF8("Frankfürt")));
613  level1->appendRow(level2 = createFolderItem("Sales", "frank-sales"));
614 
615  folderModel_->setHeaderData(0, Horizontal,
616  boost::any(std::string("SandBox")));
617  }
618 
623  WStandardItem *createFolderItem(const WString& location,
624  const std::string& folderId = std::string())
625  {
626  WStandardItem *result = new WStandardItem(location);
627 
628  if (!folderId.empty()) {
629  result->setData(boost::any(folderId));
630  result->setFlags(result->flags() | ItemIsDropEnabled);
631  folderNameMap_[folderId] = location;
632  } else
633  result->setFlags(result->flags().clear(ItemIsSelectable));
634 
635  result->setIcon("icons/folder.gif");
636 
637  return result;
638  }
639 
640 };
641 
643 {
644  WApplication *app = new TreeViewDragDrop(env);
646  app->setTitle("WTreeView Drag & Drop");
647  app->useStyleSheet("styles.css");
648  app->messageResourceBundle().use(WApplication::appRoot() + "about");
649  app->refresh();
650 
651  return app;
652 }
653 
654 int main(int argc, char **argv)
655 {
656  return WRun(argc, argv, &createApplication);
657 }
658 

Generated on Tue Oct 30 2012 for the C++ Web Toolkit (Wt) by doxygen 1.8.1.2