/* * This file is part of NixNote * Copyright 2009 Randy Baumgarte * * This file may be licensed under the terms of of the * GNU General Public License Version 2 (the ``GPL''). * * Software distributed under the License is distributed * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either * express or implied. See the GPL for the specific language * governing rights and limitations. * * You should have received a copy of the GPL along with this * program. If not, go to http://www.gnu.org/licenses/gpl.html * or write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ package cx.fbn.nevernote.gui; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.evernote.edam.type.Note; import com.evernote.edam.type.Notebook; import com.trolltech.qt.core.QByteArray; import com.trolltech.qt.core.QMimeData; import com.trolltech.qt.core.Qt; import com.trolltech.qt.core.Qt.SortOrder; import com.trolltech.qt.gui.QAbstractItemView; import com.trolltech.qt.gui.QAction; import com.trolltech.qt.gui.QBrush; import com.trolltech.qt.gui.QColor; import com.trolltech.qt.gui.QContextMenuEvent; import com.trolltech.qt.gui.QDragEnterEvent; import com.trolltech.qt.gui.QDragMoveEvent; import com.trolltech.qt.gui.QHeaderView; import com.trolltech.qt.gui.QIcon; import com.trolltech.qt.gui.QMenu; import com.trolltech.qt.gui.QMouseEvent; import com.trolltech.qt.gui.QTreeWidget; import com.trolltech.qt.gui.QTreeWidgetItem; import com.trolltech.qt.gui.QTreeWidgetItem.ChildIndicatorPolicy; import cx.fbn.nevernote.Global; import cx.fbn.nevernote.filters.NotebookCounter; import cx.fbn.nevernote.signals.NoteSignal; import cx.fbn.nevernote.sql.DatabaseConnection; public class NotebookTreeWidget extends QTreeWidget { private QAction deleteAction; private QAction addAction; private QAction editAction; private QAction iconAction; private QAction stackAction; private QAction publishAction; private QAction shareAction; public NoteSignal noteSignal; public Signal0 selectionSignal; private String selectedNotebook; private HashMap<String, QIcon> icons; private final DatabaseConnection db; private final HashMap<String, QTreeWidgetItem> stacks; private boolean rightButtonClicked; public void setAddAction(QAction a) { addAction = a; } public void setPublishAction(QAction p) { publishAction = p; } public void setShareAction(QAction s) { shareAction = s; } public void setDeleteAction(QAction d) { deleteAction = d; } public void setEditAction(QAction e) { editAction = e; } public void setStackAction(QAction e) { stackAction = e; } public void setIconAction(QAction e) { iconAction = e; } public NotebookTreeWidget(DatabaseConnection db) { noteSignal = new NoteSignal(); this.db = db; // setProperty("hideTree", true); List<String> labels = new ArrayList<String>(); labels.add(tr("Notebooks")); labels.add(""); setAcceptDrops(true); setDragEnabled(true); setColumnCount(2); header().setResizeMode(0, QHeaderView.ResizeMode.ResizeToContents); header().setResizeMode(1, QHeaderView.ResizeMode.Stretch); header().setMovable(false); header().setStyleSheet("QHeaderView::section {border: 0.0em;}"); setHeaderLabels(labels); setDragDropMode(QAbstractItemView.DragDropMode.DragDrop); // If we want to mimic Evernote's notebook selection if (Global.mimicEvernoteInterface) { setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection); } else setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection); stacks = new HashMap<String, QTreeWidgetItem>(); // int width = Global.getColumnWidth("notebookTreeName"); // if (width>0) // setColumnWidth(0, width); // previousMouseOver = new QTreeWidgetItem(); selectionSignal = new Signal0(); selectedNotebook = ""; rightButtonClicked = false; itemClicked.connect(this, "itemClicked()"); } public void selectNotebook(QTreeWidgetItem item) { QTreeWidgetItem root = invisibleRootItem(); QTreeWidgetItem child; for (int i=0; i<root.childCount(); i++) { child = root.child(i); if (child.text(2).equals(item.text(2))) { child.setSelected(true); return; } } } public boolean selectGuid(String guid) { QTreeWidgetItem root = invisibleRootItem(); QTreeWidgetItem child; for (int i=0; i<root.childCount(); i++) { child = root.child(i); if (child.text(2).equals(guid)) { child.setSelected(true); return true; } } return false; } public void setIcons(HashMap<String, QIcon> i) { icons = i; } public QIcon findDefaultIcon(String guid, String name, List<String> localBooks, boolean isPublished) { String iconPath = new String("classpath:cx/fbn/nevernote/icons/"); QIcon blueIcon = new QIcon(iconPath+"notebook-blue.png"); QIcon greenIcon = new QIcon(iconPath+"notebook-green.png"); QIcon redIcon = new QIcon(iconPath+"notebook-red.png"); QIcon yellowIcon = new QIcon(iconPath+"notebook-yellow.png"); QIcon orangeIcon = new QIcon(iconPath+"notebook-orange.png"); if (localBooks.contains(guid) && (name.equalsIgnoreCase("Conflicting Changes") || name.equalsIgnoreCase("Conflicting Changes (Local)"))) return redIcon; if (localBooks.contains(guid)) { return yellowIcon; } if (isPublished) return blueIcon; if (db.getNotebookTable().isLinked(guid)) return orangeIcon; return greenIcon; } public void load(List<Notebook> books, List<String> localBooks) { Notebook book; NTreeWidgetItem child; /* First, let's find out which stacks are expanded */ QTreeWidgetItem root = invisibleRootItem(); List<String> expandedStacks = new ArrayList<String>(); for (int i=0; i<root.childCount(); i++) { if (root.child(i).isExpanded()) expandedStacks.add(root.child(i).text(0)); } clear(); stacks.clear(); if (books == null) return; Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight); for (int i=0; i<books.size(); i++) { book = books.get(i); child = new NTreeWidgetItem(); child.setChildIndicatorPolicy(ChildIndicatorPolicy.DontShowIndicatorWhenChildless); child.setText(0, book.getName()); if (icons != null && !icons.containsKey(book.getGuid())) { QIcon icon = findDefaultIcon(book.getGuid(), book.getName(), localBooks, book.isPublished()); child.setIcon(0, icon); } else { child.setIcon(0, icons.get(book.getGuid())); } child.setTextAlignment(1, ra.value()); child.setText(2, book.getGuid()); if (book.getStack() == null || book.getStack().equalsIgnoreCase("")) addTopLevelItem(child); else { String stackName = book.getStack(); QTreeWidgetItem parent; if (!stacks.containsKey(stackName)) { parent = createStackIcon(stackName, ra); addTopLevelItem(parent); stacks.put(stackName, parent); } else parent = stacks.get(stackName); parent.addChild(child); } } sortItems(0, SortOrder.AscendingOrder); if (Global.mimicEvernoteInterface) { String iconPath = new String("classpath:cx/fbn/nevernote/icons/"); QIcon allIcon = db.getSystemIconTable().getIcon("All Notebooks", "ALLNOTEBOOK"); if (allIcon == null) allIcon = new QIcon(iconPath+"notebook-green.png"); child = new NTreeWidgetItem(); child.setIcon(0, allIcon); child.setText(0, tr("All Notebooks")); child.setText(2, ""); child.setTextAlignment(1, ra.value()); insertTopLevelItem(0,child); } resizeColumnToContents(0); resizeColumnToContents(1); // Finally, expand the stacks back out root = invisibleRootItem(); for (int i=0; i<root.childCount(); i++) { for (int j=0; j<expandedStacks.size(); j++) { if (root.child(i).text(0).equalsIgnoreCase(expandedStacks.get(j))) { expandItem(root.child(i)); j=expandedStacks.size(); } } } } // update the display with the current number of notes public void updateCounts(List<Notebook> books, List<NotebookCounter> counts) { QTreeWidgetItem root = invisibleRootItem(); QTreeWidgetItem child; QBrush blue = new QBrush(); QBrush black = new QBrush(); black.setColor(QColor.black); if (Global.tagBehavior().equalsIgnoreCase("ColorActive") && !Global.mimicEvernoteInterface) blue.setColor(QColor.blue); else blue.setColor(QColor.black); int total=0; int size = books.size(); if (Global.mimicEvernoteInterface) size++; for (int i=0; i<size; i++) { child = root.child(i); if (child != null && child.childCount() > 0) { int count = child.childCount(); QTreeWidgetItem parent = child; int localTotal = 0; for (int j=0; j<count; j++) { child = parent.child(j); int childCount = updateCounts(child, books, counts, blue, black); total = total+childCount; localTotal = localTotal+childCount; } parent.setText(1, new Integer(localTotal).toString()); } else total = total+updateCounts(child, books, counts, blue, black); } for (int i=0; i<size; i++) { child = root.child(i); if (child != null) { String guid = child.text(2); if (guid.equals("") && Global.mimicEvernoteInterface) child.setText(1, new Integer(total).toString()); } } } private int updateCounts(QTreeWidgetItem child, List<Notebook> books, List<NotebookCounter> counts, QBrush blue, QBrush black) { int total=0; if (child != null) { String guid = child.text(2); child.setText(1,"0"); child.setForeground(0, black); child.setForeground(1, black); for (int j=0; j<counts.size(); j++) { if (counts.get(j).getGuid().equals(guid)) { child.setText(1, new Integer(counts.get(j).getCount()).toString()); total = counts.get(j).getCount(); if (counts.get(j).getCount() > 0) { child.setForeground(0, blue); child.setForeground(1, blue); } } } } return total; } // Return a list of the notebook guids, ordered by the current display order. public List<String> getNotebookGuids() { List<String> names = new ArrayList<String>(); QTreeWidgetItem root = invisibleRootItem(); QTreeWidgetItem child; for (int i=0; i<root.childCount(); i++) { child = root.child(i); String text = child.text(2); names.add(text); } return names; } @Override public void contextMenuEvent(QContextMenuEvent event) { QMenu menu = new QMenu(this); menu.addAction(addAction); menu.addAction(editAction); menu.addAction(deleteAction); menu.addAction(stackAction); menu.addSeparator(); menu.addAction(publishAction); menu.addAction(shareAction); menu.addSeparator(); menu.addAction(iconAction); menu.exec(event.globalPos()); } @Override public void dragEnterEvent(QDragEnterEvent event) { if (event.mimeData().hasFormat("application/x-nevernote-note")) { event.accept(); return; } if (event.source() == this) { event.mimeData().setData("application/x-nevernote-notebook", new QByteArray(currentItem().text(2))); List<QTreeWidgetItem> selected = selectedItems(); for (int i=0; i<selected.size(); i++) { if (selected.get(i).text(2).equalsIgnoreCase("STACK") || selected.get(i).text(2).equals("")) { event.ignore(); return; } } event.accept(); return; } event.ignore(); } @Override protected void dragMoveEvent(QDragMoveEvent event) { // if (event.mimeData().hasFormat("text/plain") && //event.answerRect().intersects(dropFrame.geometry())) QTreeWidgetItem treeItem = itemAt(event.pos().x(), event.pos().y()); if (treeItem != null) { /* if (!previousMouseOver.text(0).equalsIgnoreCase(treeItem.text(0))) { previousMouseOver.setSelected(previousMouseOverWasSelected); previousMouseOverWasSelected = treeItem.isSelected(); previousMouseOver = treeItem; blockSignals(true); treeItem.setSelected(true); blockSignals(false); } */ } if (event.mimeData().hasFormat("application/x-nevernote-note")) { if (event.answerRect().intersects(childrenRect())) event.acceptProposedAction(); return; } } @Override public boolean dropMimeData(QTreeWidgetItem parent, int index, QMimeData data, Qt.DropAction action) { if (data.hasFormat("application/x-nevernote-notebook")) { return false; } // This is really dead code. it is the beginning of logic to create stacks by // dragging. if (data.hasFormat("application/x-nevernote-notebook")) { QByteArray d = data.data("application/x-nevernote-notebook"); String current = d.toString(); // If dropping to the top level, then remove the stack if (parent == null) { db.getNotebookTable().clearStack(current); return true; } // If trying to drop under the "All notebooks" then ignore if (parent.text(2).equals("")) return false; // If we are NOT droping directly onto the stack icon // we need to find the stack widget String stackName; QTreeWidgetItem stackItem; List<QTreeWidgetItem> currentItems = selectedItems(); if (!parent.text(2).equalsIgnoreCase("STACK")) { // If a parent stack exists, then use it. if (parent.parent() != null) { stackName = parent.parent().text(0); stackItem = parent.parent(); } else { currentItems.add(parent); // If a stack doesn't exist, then we need to create one stackName = "New Stack"; // Find a new stack name that isn't in use for (int i=1; i<101; i++) { if (stacks.containsKey(stackName)) stackName = "New Stack(" +new Integer(i).toString() + ")"; else break; } db.getNotebookTable().setStack(parent.text(2), stackName); Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight); stackItem = createStackIcon(stackName, ra); addTopLevelItem(stackItem); } } else { stackName = parent.text(0); stackItem = parent; } List<QTreeWidgetItem> newItems = new ArrayList<QTreeWidgetItem>(); for (int i=0; i<currentItems.size(); i++) { newItems.add(copyTreeItem(currentItems.get(i))); currentItems.get(i).setHidden(true); } db.getNotebookTable().setStack(current, stackName); stackItem.addChildren(newItems); return true; } // If we are dropping a note onto a notebook if (data.hasFormat("application/x-nevernote-note")) { // If we are dropping onto a read-only notebook, we are done. if (db.getNotebookTable().isReadOnly(parent.text(2))) return false; QByteArray d = data.data("application/x-nevernote-note"); String s = d.toString(); String noteGuidArray[] = s.split(" "); for (String element : noteGuidArray) { Note n = db.getNoteTable().getNote(element.trim(), false, false, false, false, true); // We need to be sure that... // 1.) We are not dropping onto the "All Notebooks" stack // 2.) We are not dropping onto a stack // 3.) We are actually dropping onto a different notebook. if (!parent.text(2).equalsIgnoreCase("") && !parent.text(2).equalsIgnoreCase(tr("STACK")) && !(n.getNotebookGuid().equalsIgnoreCase(parent.text(2)) )) { noteSignal.notebookChanged.emit(element.trim(), parent.text(2)); if (db.getNotebookTable().isLinked(parent.text(2))) { noteSignal.tagsChanged.emit(element.trim(), new ArrayList<String>()); } } } return true; } return false; } private QTreeWidgetItem createStackIcon(String stackName, Qt.Alignment ra) { String iconPath = new String("classpath:cx/fbn/nevernote/icons/"); QIcon stackIcon; stackIcon = db.getSystemIconTable().getIcon(stackName, "STACK"); if (stackIcon == null) stackIcon = new QIcon(iconPath+"books2.png"); QTreeWidgetItem parent = new QTreeWidgetItem(); stacks.put(stackName, parent); parent.setText(0, stackName); parent.setIcon(0, stackIcon); parent.setText(2, "STACK"); parent.setTextAlignment(1, ra.value()); return parent; } // Copy an individual item within the tree. I need to do this because // Qt doesn't call the dropMimeData on a move, just a copy. private QTreeWidgetItem copyTreeItem(QTreeWidgetItem source) { QTreeWidgetItem target = new QTreeWidgetItem(this); target.setText(0, source.text(0)); target.setIcon(0, source.icon(0)); target.setText(1, source.text(1)); target.setText(2, source.text(2)); Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight); target.setTextAlignment(1, ra.value()); source.setHidden(true); return target; } @SuppressWarnings("unused") private void itemClicked() { List<QTreeWidgetItem> selectedItem = selectedItems(); if (selectedItem.size() == 1) { if (selectedItem.get(0).text(0).equalsIgnoreCase(selectedNotebook) && !Global.mimicEvernoteInterface && !rightButtonClicked) { selectedNotebook = ""; clearSelection(); } else { selectedNotebook = selectedItem.get(0).text(0); } } selectionSignal.emit(); } @Override public void mousePressEvent(QMouseEvent e) { if (e.button() == Qt.MouseButton.RightButton) rightButtonClicked = true; else rightButtonClicked = false; super.mousePressEvent(e); } }