/*
* 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);
}
}