123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- /*
- * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
- *
- * See the LICENSE file for terms of use.
- */
- #include <fstream>
- #include <Wt/WApplication>
- #include <Wt/WComboBox>
- #include <Wt/WContainerWidget>
- #include <Wt/WDatePicker>
- #include <Wt/WDateValidator>
- #include <Wt/WDialog>
- #include <Wt/WEnvironment>
- #include <Wt/WIntValidator>
- #include <Wt/WItemDelegate>
- #include <Wt/WLabel>
- #include <Wt/WLineEdit>
- #include <Wt/WMessageBox>
- #include <Wt/WPushButton>
- #include <Wt/WRegExpValidator>
- #include <Wt/WGridLayout>
- #include <Wt/WPopupMenu>
- #include <Wt/WSortFilterProxyModel>
- #include <Wt/WStandardItem>
- #include <Wt/WStandardItemModel>
- #include <Wt/WTableView>
- #include <Wt/WTreeView>
- #include <Wt/WText>
- #include <Wt/WVBoxLayout>
- #include <Wt/Chart/WPieChart>
- #include "CsvUtil.h"
- #include "FolderView.h"
- using namespace Wt;
- /**
- * \defgroup treeviewdragdrop Drag and drop in WTreeView example
- */
- /*@{*/
- /*! \class FileModel
- * \brief A specialized standard item model which report a specific
- * drag and drop mime type.
- *
- * A specific drag and drop mime type instead of the generic abstract
- * item model is returned by the model.
- */
- class FileModel : public WStandardItemModel
- {
- public:
- /*! \brief Constructor.
- */
- FileModel(WObject *parent)
- : WStandardItemModel(parent) { }
- /*! \brief Return the mime type.
- */
- virtual std::string mimeType() const {
- return FolderView::FileSelectionMimeType;
- }
- /// Date display format.
- static WString dateDisplayFormat;
- /// Date edit format.
- static WString dateEditFormat;
- };
- WString FileModel::dateDisplayFormat(WString::fromUTF8("MMM dd, yyyy"));
- WString FileModel::dateEditFormat(WString::fromUTF8("dd-MM-yyyy"));
- /*! \class FileEditDialog
- * \brief A dialog for editing a 'file'.
- */
- class FileEditDialog : public WDialog
- {
- public:
- FileEditDialog(WAbstractItemModel *model, const WModelIndex& item)
- : WDialog("Edit..."),
- model_(model),
- item_(item)
- {
- int modelRow = item_.row();
- resize(300, WLength::Auto);
- /*
- * Create the form widgets, and load them with data from the model.
- */
- // name
- nameEdit_ = new WLineEdit(asString(model_->data(modelRow, 1)));
- // type
- typeEdit_ = new WComboBox();
- typeEdit_->addItem("Document");
- typeEdit_->addItem("Spreadsheet");
- typeEdit_->addItem("Presentation");
- typeEdit_->setCurrentIndex
- (typeEdit_->findText(asString(model_->data(modelRow, 2))));
- // size
- sizeEdit_ = new WLineEdit(asString(model_->data(modelRow, 3)));
- sizeEdit_->setValidator
- (new WIntValidator(0, std::numeric_limits<int>::max(), this));
- // created
- createdPicker_ = new WDatePicker();
- createdPicker_->lineEdit()->validator()->setMandatory(true);
- createdPicker_->setFormat(FileModel::dateEditFormat);
- createdPicker_->setDate(boost::any_cast<WDate>(model_->data(modelRow, 4)));
- // modified
- modifiedPicker_ = new WDatePicker();
- modifiedPicker_->lineEdit()->validator()->setMandatory(true);
- modifiedPicker_->setFormat(FileModel::dateEditFormat);
- modifiedPicker_->setDate(boost::any_cast<WDate>(model_->data(modelRow, 5)));
- /*
- * Use a grid layout for the labels and fields
- */
- WGridLayout *layout = new WGridLayout();
- WLabel *l;
- int row = 0;
- layout->addWidget(l = new WLabel("Name:"), row, 0);
- layout->addWidget(nameEdit_, row, 1);
- l->setBuddy(nameEdit_);
- ++row;
- layout->addWidget(l = new WLabel("Type:"), row, 0);
- layout->addWidget(typeEdit_, row, 1);
- l->setBuddy(typeEdit_);
- ++row;
- layout->addWidget(l = new WLabel("Size:"), row, 0);
- layout->addWidget(sizeEdit_, row, 1);
- l->setBuddy(sizeEdit_);
- ++row;
- layout->addWidget(l = new WLabel("Created:"), row, 0);
- layout->addWidget(createdPicker_->lineEdit(), row, 1);
- layout->addWidget(createdPicker_, row, 2);
- l->setBuddy(createdPicker_->lineEdit());
- ++row;
- layout->addWidget(l = new WLabel("Modified:"), row, 0);
- layout->addWidget(modifiedPicker_->lineEdit(), row, 1);
- layout->addWidget(modifiedPicker_, row, 2);
- l->setBuddy(modifiedPicker_->lineEdit());
- ++row;
- WPushButton *b;
- WContainerWidget *buttons = new WContainerWidget();
- buttons->addWidget(b = new WPushButton("Save"));
- b->clicked().connect(this, &WDialog::accept);
- contents()->enterPressed().connect(this, &WDialog::accept);
- buttons->addWidget(b = new WPushButton("Cancel"));
- b->clicked().connect(this, &WDialog::reject);
- /*
- * Focus the form widget that corresonds to the selected item.
- */
- switch (item.column()) {
- case 2:
- typeEdit_->setFocus(); break;
- case 3:
- sizeEdit_->setFocus(); break;
- case 4:
- createdPicker_->lineEdit()->setFocus(); break;
- case 5:
- modifiedPicker_->lineEdit()->setFocus(); break;
- default:
- nameEdit_->setFocus(); break;
- }
- layout->addWidget(buttons, row, 0, 0, 3, AlignCenter);
- layout->setColumnStretch(1, 1);
- contents()->setLayout(layout);
- finished().connect(this, &FileEditDialog::handleFinish);
- show();
- }
- private:
- WAbstractItemModel *model_;
- WModelIndex item_;
- WLineEdit *nameEdit_, *sizeEdit_;
- WComboBox *typeEdit_;
- WDatePicker *createdPicker_, *modifiedPicker_;
- void handleFinish(DialogCode result)
- {
- if (result == WDialog::Accepted) {
- /*
- * Update the model with data from the edit widgets.
- *
- * You will want to do some validation here...
- *
- * Note that we directly update the source model to avoid
- * problems caused by the dynamic sorting of the proxy model,
- * which reorders row numbers, and would cause us to switch to editing
- * the wrong data.
- */
- WAbstractItemModel *m = model_;
- int modelRow = item_.row();
- WAbstractProxyModel *proxyModel = dynamic_cast<WAbstractProxyModel *>(m);
- if (proxyModel) {
- m = proxyModel->sourceModel();
- modelRow = proxyModel->mapToSource(item_).row();
- }
- m->setData(modelRow, 1, boost::any(nameEdit_->text()));
- m->setData(modelRow, 2, boost::any(typeEdit_->currentText()));
- m->setData(modelRow, 3, boost::any(boost::lexical_cast<int>
- (sizeEdit_->text().toUTF8())));
- m->setData(modelRow, 4, boost::any(createdPicker_->date()));
- m->setData(modelRow, 5, boost::any(modifiedPicker_->date()));
- }
- delete this;
- }
- };
- /*! \class TreeViewDragDrop
- * \brief Main application class.
- */
- class TreeViewDragDrop : public WApplication
- {
- public:
- /*! \brief Constructor.
- */
- TreeViewDragDrop(const WEnvironment &env)
- : WApplication(env),
- popup_(0),
- popupActionBox_(0)
- {
- setCssTheme("polished");
- /*
- * Create the data models.
- */
- folderModel_ = new WStandardItemModel(0, 1, this);
- populateFolders();
- fileModel_ = new FileModel(this);
- populateFiles();
- /*
- The header items are also endered using an ItemDelegate, and thus
- support other data, e.g.:
- fileModel_->setHeaderFlags(0, Horizontal, HeaderIsUserCheckable);
- fileModel_->setHeaderData(0, Horizontal,
- std::string("icons/file.gif"),
- Wt::DecorationRole);
- */
- fileFilterModel_ = new WSortFilterProxyModel(this);
- fileFilterModel_->setSourceModel(fileModel_);
- fileFilterModel_->setDynamicSortFilter(true);
- fileFilterModel_->setFilterKeyColumn(0);
- fileFilterModel_->setFilterRole(UserRole);
- /*
- * Setup the user interface.
- */
- createUI();
- }
- virtual ~TreeViewDragDrop() {
- delete popup_;
- delete popupActionBox_;
- }
- private:
- /// The folder model (used by folderView_)
- WStandardItemModel *folderModel_;
- /// The file model (used by fileView_)
- WStandardItemModel *fileModel_;
- /// The sort filter proxy model that adapts fileModel_
- WSortFilterProxyModel *fileFilterModel_;
- /// Maps folder id's to folder descriptions.
- std::map<std::string, WString> folderNameMap_;
- /// The folder view.
- WTreeView *folderView_;
- /// The file view.
- WTableView *fileView_;
- /// Popup menu on the folder view
- WPopupMenu *popup_;
- /// Message box to confirm the poup menu action
- WMessageBox *popupActionBox_;
- /*! \brief Setup the user interface.
- */
- void createUI() {
- WContainerWidget *w = root();
- w->setStyleClass("maindiv");
- /*
- * The main layout is a 3x2 grid layout.
- */
- WGridLayout *layout = new WGridLayout();
- layout->addWidget(createTitle("Folders"), 0, 0);
- layout->addWidget(createTitle("Files"), 0, 1);
- layout->addWidget(folderView(), 1, 0);
- layout->setColumnResizable(0);
- // select the first folder
- folderView_->select(folderModel_->index(0, 0, folderModel_->index(0, 0)));
- WVBoxLayout *vbox = new WVBoxLayout();
- vbox->addWidget(fileView(), 1);
- vbox->addWidget(pieChart(), 1);
- vbox->setResizable(0);
- layout->addLayout(vbox, 1, 1);
- layout->addWidget(aboutDisplay(), 2, 0, 1, 2);
- /*
- * Let row 1 and column 1 take the excess space.
- */
- layout->setRowStretch(1, 1);
- layout->setColumnStretch(1, 1);
- w->setLayout(layout);
- }
- /*! \brief Creates a title widget.
- */
- WText *createTitle(const WString& title) {
- WText *result = new WText(title);
- result->setInline(false);
- result->setStyleClass("title");
- return result;
- }
- /*! \brief Creates the folder WTreeView
- */
- WTreeView *folderView() {
- WTreeView *treeView = new FolderView();
- /*
- * To support right-click, we need to disable the built-in browser
- * context menu.
- *
- * Note that disabling the context menu and catching the
- * right-click does not work reliably on all browsers.
- */
- treeView->setAttributeValue
- ("oncontextmenu",
- "event.cancelBubble = true; event.returnValue = false; return false;");
- treeView->setModel(folderModel_);
- treeView->resize(200, WLength::Auto);
- treeView->setSelectionMode(SingleSelection);
- treeView->expandToDepth(1);
- treeView->selectionChanged()
- .connect(this, &TreeViewDragDrop::folderChanged);
- treeView->mouseWentUp().connect(this, &TreeViewDragDrop::showPopup);
- folderView_ = treeView;
- return treeView;
- }
- /*! \brief Creates the file table view (a WTableView)
- */
- WTableView *fileView() {
- WTableView *tableView = new WTableView();
- tableView->setAlternatingRowColors(true);
- tableView->setModel(fileFilterModel_);
- tableView->setSelectionMode(ExtendedSelection);
- tableView->setDragEnabled(true);
- tableView->setColumnWidth(0, 100);
- tableView->setColumnWidth(1, 150);
- tableView->setColumnWidth(2, 100);
- tableView->setColumnWidth(3, 60);
- tableView->setColumnWidth(4, 100);
- tableView->setColumnWidth(5, 100);
- WItemDelegate *delegate = new WItemDelegate(this);
- delegate->setTextFormat(FileModel::dateDisplayFormat);
- tableView->setItemDelegateForColumn(4, delegate);
- tableView->setItemDelegateForColumn(5, delegate);
- tableView->setColumnAlignment(3, AlignRight);
- tableView->setColumnAlignment(4, AlignRight);
- tableView->setColumnAlignment(5, AlignRight);
- tableView->sortByColumn(1, AscendingOrder);
- tableView->doubleClicked().connect(this, &TreeViewDragDrop::editFile);
- fileView_ = tableView;
- return tableView;
- }
- /*! \brief Edit a particular row.
- */
- void editFile(const WModelIndex& item) {
- new FileEditDialog(fileView_->model(), item);
- }
- /*! \brief Creates the chart.
- */
- WWidget *pieChart() {
- using namespace Chart;
- WPieChart *chart = new WPieChart();
- // chart->setPreferredMethod(WPaintedWidget::PngImage);
- chart->setModel(fileFilterModel_);
- chart->setTitle("File sizes");
- chart->setLabelsColumn(1); // Name
- chart->setDataColumn(3); // Size
- chart->setPerspectiveEnabled(true, 0.2);
- chart->setDisplayLabels(Outside | TextLabel);
- if (!WApplication::instance()->environment().ajax()) {
- chart->resize(500, 200);
- chart->setMargin(WLength::Auto, Left | Right);
- WContainerWidget *w = new WContainerWidget();
- w->addWidget(chart);
- w->setStyleClass("about");
- return w;
- } else {
- chart->setStyleClass("about");
- return chart;
- }
- }
- /*! \brief Creates the hints text.
- */
- WWidget *aboutDisplay() {
- WText *result = new WText(WString::tr("about-text"));
- result->setStyleClass("about");
- return result;
- }
- /*! \brief Change the filter on the file view when the selected folder
- * changes.
- */
- void folderChanged() {
- if (folderView_->selectedIndexes().empty())
- return;
- WModelIndex selected = *folderView_->selectedIndexes().begin();
- boost::any d = selected.data(UserRole);
- if (!d.empty()) {
- std::string folder = boost::any_cast<std::string>(d);
- // For simplicity, we assume here that the folder-id does not
- // contain special regexp characters, otherwise these need to be
- // escaped -- or use the \Q \E qutoing escape regular expression
- // syntax (and escape \E)
- fileFilterModel_->setFilterRegExp(folder);
- }
- }
- /*! \brief Show a popup for a folder item.
- */
- void showPopup(const WModelIndex& item, const WMouseEvent& event) {
- if (event.button() == WMouseEvent::RightButton) {
- // Select the item, it was not yet selected.
- if (!folderView_->isSelected(item))
- folderView_->select(item);
- if (!popup_) {
- popup_ = new WPopupMenu();
- popup_->addItem("icons/folder_new.gif", "Create a New Folder");
- popup_->addItem("Rename this Folder")->setCheckable(true);
- popup_->addItem("Delete this Folder");
- popup_->addSeparator();
- popup_->addItem("Folder Details");
- popup_->addSeparator();
- popup_->addItem("Application Inventory");
- popup_->addItem("Hardware Inventory");
- popup_->addSeparator();
- WPopupMenu *subMenu = new WPopupMenu();
- subMenu->addItem("Sub Item 1");
- subMenu->addItem("Sub Item 2");
- popup_->addMenu("File Deployments", subMenu);
- /*
- * This is one method of executing a popup, which does not block a
- * thread for a reentrant event loop, and thus scales.
- *
- * Alternatively you could call WPopupMenu::exec(), which returns
- * the result, but while waiting for it, blocks the thread.
- */
- popup_->aboutToHide().connect(this, &TreeViewDragDrop::popupAction);
- }
- if (popup_->isHidden())
- popup_->popup(event);
- else
- popup_->hide();
- }
- }
- /** \brief Process the result of the popup menu
- */
- void popupAction() {
- if (popup_->result()) {
- /*
- * You could also bind extra data to an item using setData() and
- * check here for the action asked. For now, we just use the text.
- */
- WString text = popup_->result()->text();
- popup_->hide();
- popupActionBox_ = new WMessageBox("Sorry.","Action '" + text
- + "' is not implemented.", NoIcon, Ok);
- popupActionBox_->buttonClicked()
- .connect(this, &TreeViewDragDrop::dialogDone);
- popupActionBox_->show();
- } else {
- popup_->hide();
- }
- }
- /** \brief Process the result of the message box.
- */
- void dialogDone() {
- delete popupActionBox_;
- popupActionBox_ = 0;
- }
- /*! \brief Populate the files model.
- *
- * Data (and headers) is read from the CSV file data/files.csv. We
- * add icons to the first column, resolve the folder id to the
- * actual folder name, and configure item flags, and parse date
- * values.
- */
- void populateFiles() {
- fileModel_->invisibleRootItem()->setRowCount(0);
- std::ifstream f((appRoot() + "data/files.csv").c_str());
- if (!f)
- throw std::runtime_error("Could not read: data/files.csv");
- readFromCsv(f, fileModel_);
- for (int i = 0; i < fileModel_->rowCount(); ++i) {
- WStandardItem *item = fileModel_->item(i, 0);
- item->setFlags(item->flags() | ItemIsDragEnabled);
- item->setIcon("icons/file.gif");
- std::string folderId = item->text().toUTF8();
- item->setData(boost::any(folderId), UserRole);
- item->setText(folderNameMap_[folderId]);
- convertToNumber(fileModel_->item(i, 3));
- convertToDate(fileModel_->item(i, 4));
- convertToDate(fileModel_->item(i, 5));
- }
- }
- /*! \brief Convert a string to a date.
- */
- void convertToDate(WStandardItem *item) {
- WDate d = WDate::fromString(item->text(), FileModel::dateEditFormat);
- item->setData(boost::any(d), DisplayRole);
- }
- /*! \brief Convert a string to a number.
- */
- void convertToNumber(WStandardItem *item) {
- int i = boost::lexical_cast<int>(item->text());
- item->setData(boost::any(i), EditRole);
- }
- /*! \brief Populate the folders model.
- */
- void populateFolders() {
- WStandardItem *level1, *level2;
- folderModel_->appendRow(level1 = createFolderItem("San Fransisco"));
- level1->appendRow(level2 = createFolderItem("Investors", "sf-investors"));
- level1->appendRow(level2 = createFolderItem("Fellows", "sf-fellows"));
- folderModel_->appendRow(level1 = createFolderItem("Sophia Antipolis"));
- level1->appendRow(level2 = createFolderItem("R&D", "sa-r_d"));
- level1->appendRow(level2 = createFolderItem("Services", "sa-services"));
- level1->appendRow(level2 = createFolderItem("Support", "sa-support"));
- level1->appendRow(level2 = createFolderItem("Billing", "sa-billing"));
- folderModel_->appendRow(level1 = createFolderItem("New York"));
- level1->appendRow(level2 = createFolderItem("Marketing", "ny-marketing"));
- level1->appendRow(level2 = createFolderItem("Sales", "ny-sales"));
- level1->appendRow(level2 = createFolderItem("Advisors", "ny-advisors"));
- folderModel_->appendRow(level1 = createFolderItem
- (WString::fromUTF8("Frankfürt")));
- level1->appendRow(level2 = createFolderItem("Sales", "frank-sales"));
- folderModel_->setHeaderData(0, Horizontal,
- boost::any(std::string("SandBox")));
- }
- /*! \brief Create a folder item.
- *
- * Configures flags for drag and drop support.
- */
- WStandardItem *createFolderItem(const WString& location,
- const std::string& folderId = std::string())
- {
- WStandardItem *result = new WStandardItem(location);
- if (!folderId.empty()) {
- result->setData(boost::any(folderId));
- result->setFlags(result->flags() | ItemIsDropEnabled);
- folderNameMap_[folderId] = location;
- } else
- result->setFlags(result->flags().clear(ItemIsSelectable));
- result->setIcon("icons/folder.gif");
- return result;
- }
- };
- WApplication *createApplication(const WEnvironment& env)
- {
- WApplication *app = new TreeViewDragDrop(env);
- app->setTwoPhaseRenderingThreshold(0);
- app->setTitle("WTreeView Drag & Drop");
- app->useStyleSheet("styles.css");
- app->messageResourceBundle().use(WApplication::appRoot() + "about");
- app->refresh();
-
- return app;
- }
- int main(int argc, char **argv)
- {
- return WRun(argc, argv, &createApplication);
- }
- /*@}*/
|