Wt examples 3.1.10
|
00001 /* 00002 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium. 00003 * 00004 * See the LICENSE file for terms of use. 00005 */ 00006 #include <fstream> 00007 00008 #include <Wt/WApplication> 00009 #include <Wt/WComboBox> 00010 #include <Wt/WContainerWidget> 00011 #include <Wt/WDatePicker> 00012 #include <Wt/WDateValidator> 00013 #include <Wt/WDialog> 00014 #include <Wt/WEnvironment> 00015 #include <Wt/WIntValidator> 00016 #include <Wt/WItemDelegate> 00017 #include <Wt/WLabel> 00018 #include <Wt/WLineEdit> 00019 #include <Wt/WMessageBox> 00020 #include <Wt/WPushButton> 00021 #include <Wt/WRegExpValidator> 00022 #include <Wt/WGridLayout> 00023 #include <Wt/WPopupMenu> 00024 #include <Wt/WSortFilterProxyModel> 00025 #include <Wt/WStandardItem> 00026 #include <Wt/WStandardItemModel> 00027 #include <Wt/WTableView> 00028 #include <Wt/WTreeView> 00029 #include <Wt/WText> 00030 #include <Wt/WVBoxLayout> 00031 00032 #include <Wt/Chart/WPieChart> 00033 00034 #include "CsvUtil.h" 00035 #include "FolderView.h" 00036 00037 using namespace Wt; 00038 00043 00051 class FileModel : public WStandardItemModel 00052 { 00053 public: 00056 FileModel(WObject *parent) 00057 : WStandardItemModel(parent) { } 00058 00061 virtual std::string mimeType() const { 00062 return FolderView::FileSelectionMimeType; 00063 } 00064 00066 static WString dateDisplayFormat; 00067 00069 static WString dateEditFormat; 00070 }; 00071 00072 WString FileModel::dateDisplayFormat(WString::fromUTF8("MMM dd, yyyy")); 00073 WString FileModel::dateEditFormat(WString::fromUTF8("dd-MM-yyyy")); 00074 00078 class FileEditDialog : public WDialog 00079 { 00080 public: 00081 FileEditDialog(WAbstractItemModel *model, const WModelIndex& item) 00082 : WDialog("Edit..."), 00083 model_(model), 00084 item_(item) 00085 { 00086 int modelRow = item_.row(); 00087 00088 resize(300, WLength::Auto); 00089 00090 /* 00091 * Create the form widgets, and load them with data from the model. 00092 */ 00093 00094 // name 00095 nameEdit_ = new WLineEdit(asString(model_->data(modelRow, 1))); 00096 00097 // type 00098 typeEdit_ = new WComboBox(); 00099 typeEdit_->addItem("Document"); 00100 typeEdit_->addItem("Spreadsheet"); 00101 typeEdit_->addItem("Presentation"); 00102 typeEdit_->setCurrentIndex 00103 (typeEdit_->findText(asString(model_->data(modelRow, 2)))); 00104 00105 // size 00106 sizeEdit_ = new WLineEdit(asString(model_->data(modelRow, 3))); 00107 sizeEdit_->setValidator 00108 (new WIntValidator(0, std::numeric_limits<int>::max(), this)); 00109 00110 // created 00111 createdPicker_ = new WDatePicker(); 00112 createdPicker_->lineEdit()->validator()->setMandatory(true); 00113 createdPicker_->setFormat(FileModel::dateEditFormat); 00114 createdPicker_->setDate(boost::any_cast<WDate>(model_->data(modelRow, 4))); 00115 00116 // modified 00117 modifiedPicker_ = new WDatePicker(); 00118 modifiedPicker_->lineEdit()->validator()->setMandatory(true); 00119 modifiedPicker_->setFormat(FileModel::dateEditFormat); 00120 modifiedPicker_->setDate(boost::any_cast<WDate>(model_->data(modelRow, 5))); 00121 00122 /* 00123 * Use a grid layout for the labels and fields 00124 */ 00125 WGridLayout *layout = new WGridLayout(); 00126 00127 WLabel *l; 00128 int row = 0; 00129 00130 layout->addWidget(l = new WLabel("Name:"), row, 0); 00131 layout->addWidget(nameEdit_, row, 1); 00132 l->setBuddy(nameEdit_); 00133 ++row; 00134 00135 layout->addWidget(l = new WLabel("Type:"), row, 0); 00136 layout->addWidget(typeEdit_, row, 1, AlignTop); 00137 l->setBuddy(typeEdit_); 00138 ++row; 00139 00140 layout->addWidget(l = new WLabel("Size:"), row, 0); 00141 layout->addWidget(sizeEdit_, row, 1); 00142 l->setBuddy(sizeEdit_); 00143 ++row; 00144 00145 layout->addWidget(l = new WLabel("Created:"), row, 0); 00146 layout->addWidget(createdPicker_->lineEdit(), row, 1); 00147 layout->addWidget(createdPicker_, row, 2); 00148 l->setBuddy(createdPicker_->lineEdit()); 00149 ++row; 00150 00151 layout->addWidget(l = new WLabel("Modified:"), row, 0); 00152 layout->addWidget(modifiedPicker_->lineEdit(), row, 1); 00153 layout->addWidget(modifiedPicker_, row, 2); 00154 l->setBuddy(modifiedPicker_->lineEdit()); 00155 ++row; 00156 00157 WPushButton *b; 00158 WContainerWidget *buttons = new WContainerWidget(); 00159 buttons->addWidget(b = new WPushButton("Save")); 00160 b->clicked().connect(this, &WDialog::accept); 00161 contents()->enterPressed().connect(this, &WDialog::accept); 00162 buttons->addWidget(b = new WPushButton("Cancel")); 00163 b->clicked().connect(this, &WDialog::reject); 00164 00165 /* 00166 * Focus the form widget that corresonds to the selected item. 00167 */ 00168 switch (item.column()) { 00169 case 2: 00170 typeEdit_->setFocus(); break; 00171 case 3: 00172 sizeEdit_->setFocus(); break; 00173 case 4: 00174 createdPicker_->lineEdit()->setFocus(); break; 00175 case 5: 00176 modifiedPicker_->lineEdit()->setFocus(); break; 00177 default: 00178 nameEdit_->setFocus(); break; 00179 } 00180 00181 layout->addWidget(buttons, row, 0, 0, 3, AlignCenter); 00182 layout->setColumnStretch(1, 1); 00183 00184 contents()->setLayout(layout, AlignTop | AlignJustify); 00185 00186 finished().connect(this, &FileEditDialog::handleFinish); 00187 00188 show(); 00189 } 00190 00191 private: 00192 WAbstractItemModel *model_; 00193 WModelIndex item_; 00194 00195 WLineEdit *nameEdit_, *sizeEdit_; 00196 WComboBox *typeEdit_; 00197 WDatePicker *createdPicker_, *modifiedPicker_; 00198 00199 void handleFinish(DialogCode result) 00200 { 00201 if (result == WDialog::Accepted) { 00202 /* 00203 * Update the model with data from the edit widgets. 00204 * 00205 * You will want to do some validation here... 00206 * 00207 * Note that we directly update the source model to avoid 00208 * problems caused by the dynamic sorting of the proxy model, 00209 * which reorders row numbers, and would cause us to switch to editing 00210 * the wrong data. 00211 */ 00212 WAbstractItemModel *m = model_; 00213 int modelRow = item_.row(); 00214 00215 WAbstractProxyModel *proxyModel = dynamic_cast<WAbstractProxyModel *>(m); 00216 if (proxyModel) { 00217 m = proxyModel->sourceModel(); 00218 modelRow = proxyModel->mapToSource(item_).row(); 00219 } 00220 00221 m->setData(modelRow, 1, boost::any(nameEdit_->text())); 00222 m->setData(modelRow, 2, boost::any(typeEdit_->currentText())); 00223 m->setData(modelRow, 3, boost::any(boost::lexical_cast<int> 00224 (sizeEdit_->text().toUTF8()))); 00225 m->setData(modelRow, 4, boost::any(createdPicker_->date())); 00226 m->setData(modelRow, 5, boost::any(modifiedPicker_->date())); 00227 } 00228 00229 delete this; 00230 } 00231 00232 }; 00233 00237 class TreeViewDragDrop : public WApplication 00238 { 00239 public: 00242 TreeViewDragDrop(const WEnvironment &env) 00243 : WApplication(env), 00244 popup_(0), 00245 popupActionBox_(0) 00246 { 00247 setCssTheme("polished"); 00248 00249 /* 00250 * Create the data models. 00251 */ 00252 folderModel_ = new WStandardItemModel(0, 1, this); 00253 populateFolders(); 00254 00255 fileModel_ = new FileModel(this); 00256 populateFiles(); 00257 00258 /* 00259 The header items are also endered using an ItemDelegate, and thus 00260 support other data, e.g.: 00261 00262 fileModel_->setHeaderFlags(0, Horizontal, HeaderIsUserCheckable); 00263 fileModel_->setHeaderData(0, Horizontal, 00264 std::string("icons/file.gif"), 00265 Wt::DecorationRole); 00266 */ 00267 00268 fileFilterModel_ = new WSortFilterProxyModel(this); 00269 fileFilterModel_->setSourceModel(fileModel_); 00270 fileFilterModel_->setDynamicSortFilter(true); 00271 fileFilterModel_->setFilterKeyColumn(0); 00272 fileFilterModel_->setFilterRole(UserRole); 00273 00274 /* 00275 * Setup the user interface. 00276 */ 00277 createUI(); 00278 } 00279 00280 virtual ~TreeViewDragDrop() { 00281 delete popup_; 00282 delete popupActionBox_; 00283 } 00284 00285 private: 00287 WStandardItemModel *folderModel_; 00288 00290 WStandardItemModel *fileModel_; 00291 00293 WSortFilterProxyModel *fileFilterModel_; 00294 00296 std::map<std::string, WString> folderNameMap_; 00297 00299 WTreeView *folderView_; 00300 00302 WTableView *fileView_; 00303 00305 WPopupMenu *popup_; 00306 00308 WMessageBox *popupActionBox_; 00309 00312 void createUI() { 00313 WContainerWidget *w = root(); 00314 w->setStyleClass("maindiv"); 00315 00316 /* 00317 * The main layout is a 3x2 grid layout. 00318 */ 00319 WGridLayout *layout = new WGridLayout(); 00320 layout->addWidget(createTitle("Folders"), 0, 0); 00321 layout->addWidget(createTitle("Files"), 0, 1); 00322 layout->addWidget(folderView(), 1, 0); 00323 layout->setColumnResizable(0); 00324 00325 // select the first folder 00326 folderView_->select(folderModel_->index(0, 0, folderModel_->index(0, 0))); 00327 00328 WVBoxLayout *vbox = new WVBoxLayout(); 00329 vbox->addWidget(fileView(), 1); 00330 vbox->addWidget(pieChart(), 1); 00331 vbox->setResizable(0); 00332 00333 layout->addLayout(vbox, 1, 1); 00334 00335 layout->addWidget(aboutDisplay(), 2, 0, 1, 2, AlignTop); 00336 00337 /* 00338 * Let row 1 and column 1 take the excess space. 00339 */ 00340 layout->setRowStretch(1, 1); 00341 layout->setColumnStretch(1, 1); 00342 00343 w->setLayout(layout); 00344 } 00345 00348 WText *createTitle(const WString& title) { 00349 WText *result = new WText(title); 00350 result->setInline(false); 00351 result->setStyleClass("title"); 00352 00353 return result; 00354 } 00355 00358 WTreeView *folderView() { 00359 WTreeView *treeView = new FolderView(); 00360 00361 /* 00362 * To support right-click, we need to disable the built-in browser 00363 * context menu. 00364 * 00365 * Note that disabling the context menu and catching the 00366 * right-click does not work reliably on all browsers. 00367 */ 00368 treeView->setAttributeValue 00369 ("oncontextmenu", 00370 "event.cancelBubble = true; event.returnValue = false; return false;"); 00371 treeView->setModel(folderModel_); 00372 treeView->resize(200, WLength::Auto); 00373 treeView->setSelectionMode(SingleSelection); 00374 treeView->expandToDepth(1); 00375 treeView->selectionChanged() 00376 .connect(this, &TreeViewDragDrop::folderChanged); 00377 00378 treeView->mouseWentUp().connect(this, &TreeViewDragDrop::showPopup); 00379 00380 folderView_ = treeView; 00381 00382 return treeView; 00383 } 00384 00387 WTableView *fileView() { 00388 WTableView *tableView = new WTableView(); 00389 00390 tableView->setAlternatingRowColors(true); 00391 00392 tableView->setModel(fileFilterModel_); 00393 tableView->setSelectionMode(ExtendedSelection); 00394 tableView->setDragEnabled(true); 00395 00396 tableView->setColumnWidth(0, 100); 00397 tableView->setColumnWidth(1, 150); 00398 tableView->setColumnWidth(2, 100); 00399 tableView->setColumnWidth(3, 60); 00400 tableView->setColumnWidth(4, 100); 00401 tableView->setColumnWidth(5, 100); 00402 00403 WItemDelegate *delegate = new WItemDelegate(this); 00404 delegate->setTextFormat(FileModel::dateDisplayFormat); 00405 tableView->setItemDelegateForColumn(4, delegate); 00406 tableView->setItemDelegateForColumn(5, delegate); 00407 00408 tableView->setColumnAlignment(3, AlignRight); 00409 tableView->setColumnAlignment(4, AlignRight); 00410 tableView->setColumnAlignment(5, AlignRight); 00411 00412 tableView->sortByColumn(1, AscendingOrder); 00413 00414 tableView->doubleClicked().connect(this, &TreeViewDragDrop::editFile); 00415 00416 fileView_ = tableView; 00417 00418 return tableView; 00419 } 00420 00423 void editFile(const WModelIndex& item) { 00424 new FileEditDialog(fileView_->model(), item); 00425 } 00426 00429 WWidget *pieChart() { 00430 using namespace Chart; 00431 00432 WPieChart *chart = new WPieChart(); 00433 chart->setModel(fileFilterModel_); 00434 chart->setTitle("File sizes"); 00435 00436 chart->setLabelsColumn(1); // Name 00437 chart->setDataColumn(3); // Size 00438 00439 chart->setPerspectiveEnabled(true, 0.2); 00440 chart->setDisplayLabels(Outside | TextLabel); 00441 00442 if (!WApplication::instance()->environment().ajax()) { 00443 chart->resize(500, 200); 00444 chart->setMargin(WLength::Auto, Left | Right); 00445 WContainerWidget *w = new WContainerWidget(); 00446 w->addWidget(chart); 00447 w->setStyleClass("about"); 00448 return w; 00449 } else { 00450 chart->setStyleClass("about"); 00451 return chart; 00452 } 00453 } 00454 00457 WWidget *aboutDisplay() { 00458 WText *result = new WText(WString::tr("about-text")); 00459 result->setStyleClass("about"); 00460 return result; 00461 } 00462 00466 void folderChanged() { 00467 if (folderView_->selectedIndexes().empty()) 00468 return; 00469 00470 WModelIndex selected = *folderView_->selectedIndexes().begin(); 00471 boost::any d = selected.data(UserRole); 00472 if (!d.empty()) { 00473 std::string folder = boost::any_cast<std::string>(d); 00474 00475 // For simplicity, we assume here that the folder-id does not 00476 // contain special regexp characters, otherwise these need to be 00477 // escaped -- or use the \Q \E qutoing escape regular expression 00478 // syntax (and escape \E) 00479 fileFilterModel_->setFilterRegExp(folder); 00480 } 00481 } 00482 00485 void showPopup(const WModelIndex& item, const WMouseEvent& event) { 00486 if (event.button() == WMouseEvent::RightButton) { 00487 // Select the item, it was not yet selected. 00488 if (!folderView_->isSelected(item)) 00489 folderView_->select(item); 00490 00491 if (!popup_) { 00492 popup_ = new WPopupMenu(); 00493 popup_->addItem("icons/folder_new.gif", "Create a New Folder"); 00494 popup_->addItem("Rename this Folder")->setCheckable(true); 00495 popup_->addItem("Delete this Folder"); 00496 popup_->addSeparator(); 00497 popup_->addItem("Folder Details"); 00498 popup_->addSeparator(); 00499 popup_->addItem("Application Inventory"); 00500 popup_->addItem("Hardware Inventory"); 00501 popup_->addSeparator(); 00502 00503 WPopupMenu *subMenu = new WPopupMenu(); 00504 subMenu->addItem("Sub Item 1"); 00505 subMenu->addItem("Sub Item 2"); 00506 popup_->addMenu("File Deployments", subMenu); 00507 00508 /* 00509 * This is one method of executing a popup, which does not block a 00510 * thread for a reentrant event loop, and thus scales. 00511 * 00512 * Alternatively you could call WPopupMenu::exec(), which returns 00513 * the result, but while waiting for it, blocks the thread. 00514 */ 00515 popup_->aboutToHide().connect(this, &TreeViewDragDrop::popupAction); 00516 } 00517 00518 if (popup_->isHidden()) 00519 popup_->popup(event); 00520 else 00521 popup_->hide(); 00522 } 00523 } 00524 00527 void popupAction() { 00528 if (popup_->result()) { 00529 /* 00530 * You could also bind extra data to an item using setData() and 00531 * check here for the action asked. For now, we just use the text. 00532 */ 00533 WString text = popup_->result()->text(); 00534 popup_->hide(); 00535 00536 popupActionBox_ = new WMessageBox("Sorry.","Action '" + text 00537 + "' is not implemented.", NoIcon, Ok); 00538 popupActionBox_->buttonClicked() 00539 .connect(this, &TreeViewDragDrop::dialogDone); 00540 popupActionBox_->show(); 00541 } else { 00542 popup_->hide(); 00543 } 00544 } 00545 00548 void dialogDone() { 00549 delete popupActionBox_; 00550 popupActionBox_ = 0; 00551 } 00552 00560 void populateFiles() { 00561 fileModel_->invisibleRootItem()->setRowCount(0); 00562 00563 std::ifstream f((appRoot() + "data/files.csv").c_str()); 00564 00565 if (!f) 00566 throw std::runtime_error("Could not read: data/files.csv"); 00567 00568 readFromCsv(f, fileModel_); 00569 00570 for (int i = 0; i < fileModel_->rowCount(); ++i) { 00571 WStandardItem *item = fileModel_->item(i, 0); 00572 item->setFlags(item->flags() | ItemIsDragEnabled); 00573 item->setIcon("icons/file.gif"); 00574 00575 std::string folderId = item->text().toUTF8(); 00576 00577 item->setData(boost::any(folderId), UserRole); 00578 item->setText(folderNameMap_[folderId]); 00579 00580 convertToDate(fileModel_->item(i, 4)); 00581 convertToDate(fileModel_->item(i, 5)); 00582 } 00583 } 00584 00587 void convertToDate(WStandardItem *item) { 00588 WDate d = WDate::fromString(item->text(), FileModel::dateEditFormat); 00589 item->setData(boost::any(d), DisplayRole); 00590 } 00591 00594 void populateFolders() { 00595 WStandardItem *level1, *level2; 00596 00597 folderModel_->appendRow(level1 = createFolderItem("San Fransisco")); 00598 level1->appendRow(level2 = createFolderItem("Investors", "sf-investors")); 00599 level1->appendRow(level2 = createFolderItem("Fellows", "sf-fellows")); 00600 00601 folderModel_->appendRow(level1 = createFolderItem("Sophia Antipolis")); 00602 level1->appendRow(level2 = createFolderItem("R&D", "sa-r_d")); 00603 level1->appendRow(level2 = createFolderItem("Services", "sa-services")); 00604 level1->appendRow(level2 = createFolderItem("Support", "sa-support")); 00605 level1->appendRow(level2 = createFolderItem("Billing", "sa-billing")); 00606 00607 folderModel_->appendRow(level1 = createFolderItem("New York")); 00608 level1->appendRow(level2 = createFolderItem("Marketing", "ny-marketing")); 00609 level1->appendRow(level2 = createFolderItem("Sales", "ny-sales")); 00610 level1->appendRow(level2 = createFolderItem("Advisors", "ny-advisors")); 00611 00612 folderModel_->appendRow(level1 = createFolderItem 00613 (WString::fromUTF8("Frankfürt"))); 00614 level1->appendRow(level2 = createFolderItem("Sales", "frank-sales")); 00615 00616 folderModel_->setHeaderData(0, Horizontal, 00617 boost::any(std::string("SandBox"))); 00618 } 00619 00624 WStandardItem *createFolderItem(const WString& location, 00625 const std::string& folderId = std::string()) 00626 { 00627 WStandardItem *result = new WStandardItem(location); 00628 00629 if (!folderId.empty()) { 00630 result->setData(boost::any(folderId)); 00631 result->setFlags(result->flags() | ItemIsDropEnabled); 00632 folderNameMap_[folderId] = location; 00633 } else 00634 result->setFlags(result->flags().clear(ItemIsSelectable)); 00635 00636 result->setIcon("icons/folder.gif"); 00637 00638 return result; 00639 } 00640 00641 }; 00642 00643 WApplication *createApplication(const WEnvironment& env) 00644 { 00645 WApplication *app = new TreeViewDragDrop(env); 00646 app->setTwoPhaseRenderingThreshold(0); 00647 app->setTitle("WTreeView Drag & Drop"); 00648 app->useStyleSheet("styles.css"); 00649 app->messageResourceBundle().use(WApplication::appRoot() + "about"); 00650 app->refresh(); 00651 00652 return app; 00653 } 00654 00655 int main(int argc, char **argv) 00656 { 00657 return WRun(argc, argv, &createApplication); 00658 } 00659