Wt examples 3.1.10
/build/buildd/witty-3.1.10/examples/treeview-dragdrop/TreeViewDragDrop.C
Go to the documentation of this file.
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 

Generated on Wed Jul 27 2011 for the C++ Web Toolkit (Wt) by doxygen 1.7.4