/*
* This file is part of NixNote
* Copyright 2009,2010 Randy Baumgarte
* Copyright 2010 Hiroshi Miura
*
* 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.Tag;
import com.trolltech.qt.core.QByteArray;
import com.trolltech.qt.core.QMimeData;
import com.trolltech.qt.core.Qt;
import com.trolltech.qt.core.Qt.MatchFlag;
import com.trolltech.qt.core.Qt.MatchFlags;
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 cx.fbn.nevernote.Global;
import cx.fbn.nevernote.filters.TagCounter;
import cx.fbn.nevernote.signals.NoteSignal;
import cx.fbn.nevernote.signals.TagSignal;
import cx.fbn.nevernote.sql.DatabaseConnection;
public class TagTreeWidget extends QTreeWidget {
private QAction editAction;
private QAction deleteAction;
private QAction addAction;
private QAction iconAction;
private QAction mergeAction;
public TagSignal tagSignal;
public NoteSignal noteSignal;
private boolean showAllTags;
private final DatabaseConnection db;
private HashMap<String, QIcon> icons;
public Signal0 selectionSignal;
public String selectedTag;
private boolean rightButtonClicked;
private List<TagCounter> lastCount;
public TagTreeWidget(DatabaseConnection d) {
List<String> headers = new ArrayList<String>();
headers.add(tr("Tags"));
headers.add("");
showAllTags = true;
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;}");
db = d;
selectionSignal = new Signal0();
tagSignal = new TagSignal();
noteSignal = new NoteSignal();
setDragDropMode(QAbstractItemView.DragDropMode.DragDrop);
setHeaderLabels(headers);
// setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection);
setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection);
selectedTag = "";
itemClicked.connect(this, "itemClicked()");
int width = Global.getColumnWidth("tagTreeName");
if (width>0)
setColumnWidth(0, width);
}
public void setEditAction(QAction e) {
editAction = e;
}
public void setDeleteAction(QAction d) {
deleteAction = d;
}
public void setAddAction(QAction a) {
addAction = a;
}
public void setIconAction(QAction i) {
iconAction = i;
}
public void setMergeAction(QAction i) {
mergeAction = i; }
// Insert a new tag into the tree. This is used when we dynamically add a
// new tag after the full tag tree has been built. It only adds to the
// top level.
public void insertTag(String name, String guid) {
String iconPath = new String("classpath:cx/fbn/nevernote/icons/");
QIcon icon = new QIcon(iconPath+"tag.png");
NTreeWidgetItem child;
Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);
// Build new tag & add it
child = new NTreeWidgetItem();
child.setText(0, name);
child.setIcon(0,icon);
child.setText(2, guid);
child.setTextAlignment(1, ra.value());
addTopLevelItem(child);
// Resort the list
resizeColumnToContents(0);
resizeColumnToContents(1);
sortItems(0, SortOrder.AscendingOrder);
}
private QIcon findDefaultIcon(String guid) {
String iconPath = new String("classpath:cx/fbn/nevernote/icons/");
QIcon icon = new QIcon(iconPath+"tag.png");
QIcon linkedIcon = new QIcon(iconPath+"tag-orange.png");
if (db.getTagTable().getNotebookGuid(guid) == null ||
db.getTagTable().getNotebookGuid(guid).equals(""))
return icon;
else
return linkedIcon;
}
List<String> findExpandedTags(QTreeWidgetItem item) {
List<String> list = new ArrayList<String>();
if (item.isExpanded())
list.add(item.text(0));
for (int i=0; i<item.childCount(); i++) {
List<String> childrenList = findExpandedTags(item.child(i));
for (int j=0; j<childrenList.size(); j++) {
list.add(childrenList.get(j));
}
}
return list;
}
void expandTags(QTreeWidgetItem item, List<String> expandedTags) {
for (int i=0; i<item.childCount(); i++) {
expandTags(item.child(i), expandedTags);
}
for (int i=0; i<expandedTags.size(); i++) {
if (expandedTags.get(i).equalsIgnoreCase(item.text(0))) {
expandItem(item);
i=expandedTags.size();
}
}
}
public void load(List<Tag> tags) {
Tag tag;
List<NTreeWidgetItem> index = new ArrayList<NTreeWidgetItem>();
NTreeWidgetItem child;
/* First, let's find out which stacks are expanded */
QTreeWidgetItem root = invisibleRootItem();
List<String> expandedTags = findExpandedTags(root);
//Clear out the tree & reload
clear();
String iconPath = new String("classpath:cx/fbn/nevernote/icons/");
QIcon icon = new QIcon(iconPath+"tag.png");
Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);
// Create a copy. We delete them out as they are found
List<Tag> tempList = new ArrayList<Tag>();
for (int i=0; i<tags.size(); i++) {
tempList.add(tags.get(i));
}
while (tempList.size() > 0) {
for (int i=0; i<tempList.size(); i++) {
tag = tempList.get(i);
if (tag.getParentGuid()==null || tag.getParentGuid().equals("")) {
child = new NTreeWidgetItem();
child.setText(0, tag.getName());
if (icons != null && !icons.containsKey(tag.getGuid())) {
child.setIcon(0, findDefaultIcon(tag.getGuid()));
} else {
child.setIcon(0, icons.get(tag.getGuid()));
}
child.setText(2, tag.getGuid());
child.setTextAlignment(1, ra.value());
index.add(child);
addTopLevelItem(child);
tempList.remove(i);
} else {
// We need to find the parent
for (int j=0; j<index.size(); j++) {
if (index.get(j).text(2).equals(tag.getParentGuid())) {
child = new NTreeWidgetItem();
child.setText(0, tag.getName());
child.setIcon(0, icon);
child.setText(2, tag.getGuid());
child.setTextAlignment(1, ra.value());
if (icons != null && !icons.containsKey(tag.getGuid())) {
child.setIcon(0, findDefaultIcon(tag.getGuid()));
} else {
child.setIcon(0, icons.get(tag.getGuid()));
}
tempList.remove(i);
index.add(child);
index.get(j).addChild(child);
}
}
}
}
}
resizeColumnToContents(0);
resizeColumnToContents(1);
sortItems(0, SortOrder.AscendingOrder);
expandTags(invisibleRootItem(), expandedTags);
if (lastCount != null)
updateCounts(lastCount);
}
// Show (unhide) all tags
public void showAllTags(boolean value) {
showAllTags = value;
}
// update the display with the current number of notes
public void updateCounts(List<TagCounter> counts) {
lastCount = counts;
MatchFlags flags = new MatchFlags();
flags.set(MatchFlag.MatchWildcard);
flags.set(MatchFlag.MatchRecursive);
// List<QTreeWidgetItem> children = new ArrayList<QTreeWidgetItem>();
List <QTreeWidgetItem> children = findItems("*", flags);
QBrush black = new QBrush();
black.setColor(QColor.black);
QBrush blue = new QBrush();
blue.setColor(QColor.blue);
if (!Global.tagBehavior().equalsIgnoreCase("ColorActive"))
blue.setColor(QColor.black);
for (int i=0; i<children.size(); i++) {
children.get(i).setText(1,"0");
children.get(i).setForeground(0, black);
children.get(i).setForeground(1, black);
if (!showAllTags && (Global.tagBehavior().equalsIgnoreCase("HideInactiveCount") || Global.tagBehavior().equalsIgnoreCase("NoHideInactiveCount")))
children.get(i).setHidden(true);
else
children.get(i).setHidden(false);
if (children.get(i).isSelected())
children.get(i).setHidden(false);
}
for (int i=0; i<counts.size(); i++) {
for (int j=0; j<children.size(); j++) {
String guid = children.get(j).text(2);
if (counts.get(i).getGuid().equals(guid)) {
children.get(j).setText(1, new Integer(counts.get(i).getCount()).toString());
if (counts.get(i).getCount() > 0 || children.get(j).isSelected()) {
children.get(j).setForeground(0, blue);
children.get(j).setForeground(1, blue);
QTreeWidgetItem parent = children.get(j);
while (parent != null) {
parent.setForeground(0, blue);
parent.setForeground(1, blue);
parent.setHidden(false);
parent = parent.parent();
}
}
}
}
}
}
public boolean selectGuid(String guid) {
MatchFlags flags = new MatchFlags();
flags.set(MatchFlag.MatchWildcard);
flags.set(MatchFlag.MatchRecursive);
// List<QTreeWidgetItem> children = new ArrayList<QTreeWidgetItem>();
List <QTreeWidgetItem> children = findItems("*", flags);
for (int i=0; i<children.size(); i++) {
if (children.get(i).text(2).equals(guid)) {
children.get(i).setSelected(true);
return true;
}
}
return false;
}
@Override
protected void dragMoveEvent(QDragMoveEvent event) {
if (event.mimeData().hasFormat("application/x-nevernote-note")) {
if (event.answerRect().intersects(childrenRect()))
event.acceptProposedAction();
return;
}
}
@Override
public void dragEnterEvent(QDragEnterEvent event) {
if (event.mimeData().hasFormat("application/x-nevernote-note")) {
event.accept();
return;
}
if (event.source() == this) {
if (Global.tagBehavior().equals("HideInactiveCount")) {
event.ignore();
return;
}
event.mimeData().setData("application/x-nevernote-tag", new QByteArray(currentItem().text(2)));
event.accept();
return;
}
event.ignore();
}
@Override
public boolean dropMimeData(QTreeWidgetItem parent, int index, QMimeData data, Qt.DropAction action) {
if (data.hasFormat("application/x-nevernote-tag")) {
QByteArray d = data.data("application/x-nevernote-tag");
String current = d.toString();
// Check we don't do a dumb thing like move a parent to a child of itself
if (!checkParent(parent, current))
return false;
QTreeWidgetItem newChild;
if (parent == null) {
// tagSignal.changeParent.emit(current, "");
db.getTagTable().updateTagParent(current, "");
newChild = new QTreeWidgetItem(this);
} else {
// tagSignal.changeParent.emit(current, parent.text(2));
db.getTagTable().updateTagParent(current, parent.text(2));
newChild = new QTreeWidgetItem(parent);
}
copyTreeItem(currentItem(), newChild);
currentItem().setHidden(true);
sortItems(0, SortOrder.AscendingOrder);
return true;
}
// If we are dropping a note
if (data.hasFormat("application/x-nevernote-note")) {
String notebookGuid = db.getTagTable().getNotebookGuid(parent.text(2));
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, false);
// Check that...
// 1.) Check that tag isn't already assigned to that note
// 2.) Check that that tag is valid for that notebook or the tag isn't notebook specific
// 3.) Check that the notebook isn't read only.
if (!db.getNoteTable().noteTagsTable.checkNoteNoteTags(element.trim(), parent.text(2)) &&
(notebookGuid == null || n.getNotebookGuid().equalsIgnoreCase(notebookGuid) || notebookGuid.equals("")) &&
!db.getNotebookTable().isReadOnly(n.getNotebookGuid())) {
db.getNoteTable().noteTagsTable.saveNoteTag(element.trim(), parent.text(2), true);
noteSignal.tagsAdded.emit(element.trim(), parent.text(2));
}
}
//tagSignal.listChanged.emit();
return true;
}
return false;
}
@Override
public void contextMenuEvent(QContextMenuEvent event) {
QMenu menu = new QMenu(this);
menu.addAction(addAction);
menu.addAction(editAction);
menu.addAction(deleteAction);
menu.addAction(mergeAction);
menu.addSeparator();
menu.addAction(iconAction);
menu.exec(event.globalPos());
}
public void setIcons(HashMap<String, QIcon> i) {
icons = i;
}
// 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 void copyTreeItem(QTreeWidgetItem source, QTreeWidgetItem target) {
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());
for (int i=0; i<source.childCount(); i++) {
QTreeWidgetItem newChild = new QTreeWidgetItem(target);
copyTreeItem(source.child(i), newChild);
source.child(i).setHidden(true);
}
return;
}
// Check that we don't copy a parent as a child of a current child.
private boolean checkParent(QTreeWidgetItem parent, String child) {
if (parent != null)
if (parent.text(2).equals(child))
return false;
if (parent == null)
return true;
return checkParent(parent.parent(), child);
}
public void selectTag(QTreeWidgetItem item) {
MatchFlags flags = new MatchFlags();
flags.set(MatchFlag.MatchWildcard);
flags.set(MatchFlag.MatchRecursive);
List <QTreeWidgetItem> children = findItems("*", flags);
for (int j=0; j<children.size(); j++) {
String guid = children.get(j).text(2);
if (item.text(2).equals(guid)) {
children.get(j).setSelected(true);
}
}
}
@SuppressWarnings("unused")
private void itemClicked() {
List<QTreeWidgetItem> selectedItem = selectedItems();
if (selectedItem.size() == 1) {
if (selectedItem.get(0).text(0).equalsIgnoreCase(selectedTag) && !rightButtonClicked) {
selectedTag = "";
clearSelection();
} else {
selectedTag = 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);
}
}