2 * This file is part of NixNote
\r
3 * Copyright 2009,2010 Randy Baumgarte
\r
4 * Copyright 2010 Hiroshi Miura
\r
6 * This file may be licensed under the terms of of the
\r
7 * GNU General Public License Version 2 (the ``GPL'').
\r
9 * Software distributed under the License is distributed
\r
10 * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
\r
11 * express or implied. See the GPL for the specific language
\r
12 * governing rights and limitations.
\r
14 * You should have received a copy of the GPL along with this
\r
15 * program. If not, go to http://www.gnu.org/licenses/gpl.html
\r
16 * or write to the Free Software Foundation, Inc.,
\r
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
\r
21 package cx.fbn.nevernote.gui;
\r
23 import java.util.ArrayList;
\r
24 import java.util.HashMap;
\r
25 import java.util.List;
\r
27 import com.evernote.edam.type.Note;
\r
28 import com.evernote.edam.type.Tag;
\r
29 import com.trolltech.qt.core.QByteArray;
\r
30 import com.trolltech.qt.core.QMimeData;
\r
31 import com.trolltech.qt.core.Qt;
\r
32 import com.trolltech.qt.core.Qt.MatchFlag;
\r
33 import com.trolltech.qt.core.Qt.MatchFlags;
\r
34 import com.trolltech.qt.core.Qt.SortOrder;
\r
35 import com.trolltech.qt.gui.QAbstractItemView;
\r
36 import com.trolltech.qt.gui.QAction;
\r
37 import com.trolltech.qt.gui.QBrush;
\r
38 import com.trolltech.qt.gui.QColor;
\r
39 import com.trolltech.qt.gui.QContextMenuEvent;
\r
40 import com.trolltech.qt.gui.QDragEnterEvent;
\r
41 import com.trolltech.qt.gui.QDragMoveEvent;
\r
42 import com.trolltech.qt.gui.QHeaderView;
\r
43 import com.trolltech.qt.gui.QIcon;
\r
44 import com.trolltech.qt.gui.QMenu;
\r
45 import com.trolltech.qt.gui.QMouseEvent;
\r
46 import com.trolltech.qt.gui.QTreeWidget;
\r
47 import com.trolltech.qt.gui.QTreeWidgetItem;
\r
49 import cx.fbn.nevernote.Global;
\r
50 import cx.fbn.nevernote.filters.TagCounter;
\r
51 import cx.fbn.nevernote.signals.NoteSignal;
\r
52 import cx.fbn.nevernote.signals.TagSignal;
\r
53 import cx.fbn.nevernote.sql.DatabaseConnection;
\r
55 public class TagTreeWidget extends QTreeWidget {
\r
56 private QAction editAction;
\r
57 private QAction deleteAction;
\r
58 private QAction addAction;
\r
59 private QAction iconAction;
\r
60 private QAction mergeAction;
\r
61 public TagSignal tagSignal;
\r
62 public NoteSignal noteSignal;
\r
63 private boolean showAllTags;
\r
64 private final DatabaseConnection db;
\r
65 private HashMap<String, QIcon> icons;
\r
66 public Signal0 selectionSignal;
\r
67 public String selectedTag;
\r
68 private boolean rightButtonClicked;
\r
69 private List<TagCounter> lastCount;
\r
72 public TagTreeWidget(DatabaseConnection d) {
\r
73 List<String> headers = new ArrayList<String>();
\r
74 headers.add(tr("Tags"));
\r
77 setAcceptDrops(true);
\r
78 setDragEnabled(true);
\r
80 header().setResizeMode(0, QHeaderView.ResizeMode.ResizeToContents);
\r
81 header().setResizeMode(1, QHeaderView.ResizeMode.Stretch);
\r
82 header().setMovable(false);
\r
83 header().setStyleSheet("QHeaderView::section {border: 0.0em;}");
\r
85 selectionSignal = new Signal0();
\r
86 tagSignal = new TagSignal();
\r
87 noteSignal = new NoteSignal();
\r
88 setDragDropMode(QAbstractItemView.DragDropMode.DragDrop);
\r
89 setHeaderLabels(headers);
\r
91 // setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection);
\r
92 setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection);
\r
95 itemClicked.connect(this, "itemClicked()");
\r
96 int width = Global.getColumnWidth("tagTreeName");
\r
98 setColumnWidth(0, width);
\r
103 public void setEditAction(QAction e) {
\r
106 public void setDeleteAction(QAction d) {
\r
109 public void setAddAction(QAction a) {
\r
112 public void setIconAction(QAction i) {
\r
115 public void setMergeAction(QAction i) {
\r
118 // Insert a new tag into the tree. This is used when we dynamically add a
\r
119 // new tag after the full tag tree has been built. It only adds to the
\r
121 public void insertTag(String name, String guid) {
\r
122 String iconPath = new String("classpath:cx/fbn/nevernote/icons/");
\r
123 QIcon icon = new QIcon(iconPath+"tag.png");
\r
124 NTreeWidgetItem child;
\r
125 Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);
\r
127 // Build new tag & add it
\r
128 child = new NTreeWidgetItem();
\r
129 child.setText(0, name);
\r
130 child.setIcon(0,icon);
\r
131 child.setText(2, guid);
\r
132 child.setTextAlignment(1, ra.value());
\r
133 addTopLevelItem(child);
\r
136 resizeColumnToContents(0);
\r
137 resizeColumnToContents(1);
\r
138 sortItems(0, SortOrder.AscendingOrder);
\r
141 private QIcon findDefaultIcon(String guid) {
\r
142 String iconPath = new String("classpath:cx/fbn/nevernote/icons/");
\r
143 QIcon icon = new QIcon(iconPath+"tag.png");
\r
144 QIcon linkedIcon = new QIcon(iconPath+"tag-orange.png");
\r
146 if (db.getTagTable().getNotebookGuid(guid) == null ||
\r
147 db.getTagTable().getNotebookGuid(guid).equals(""))
\r
153 List<String> findExpandedTags(QTreeWidgetItem item) {
\r
154 List<String> list = new ArrayList<String>();
\r
155 if (item.isExpanded())
\r
156 list.add(item.text(0));
\r
157 for (int i=0; i<item.childCount(); i++) {
\r
158 List<String> childrenList = findExpandedTags(item.child(i));
\r
159 for (int j=0; j<childrenList.size(); j++) {
\r
160 list.add(childrenList.get(j));
\r
167 void expandTags(QTreeWidgetItem item, List<String> expandedTags) {
\r
168 for (int i=0; i<item.childCount(); i++) {
\r
169 expandTags(item.child(i), expandedTags);
\r
172 for (int i=0; i<expandedTags.size(); i++) {
\r
173 if (expandedTags.get(i).equalsIgnoreCase(item.text(0))) {
\r
175 i=expandedTags.size();
\r
180 public void load(List<Tag> tags) {
\r
182 List<NTreeWidgetItem> index = new ArrayList<NTreeWidgetItem>();
\r
183 NTreeWidgetItem child;
\r
185 /* First, let's find out which stacks are expanded */
\r
186 QTreeWidgetItem root = invisibleRootItem();
\r
187 List<String> expandedTags = findExpandedTags(root);
\r
191 //Clear out the tree & reload
\r
193 String iconPath = new String("classpath:cx/fbn/nevernote/icons/");
\r
194 QIcon icon = new QIcon(iconPath+"tag.png");
\r
196 Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);
\r
198 // Create a copy. We delete them out as they are found
\r
199 List<Tag> tempList = new ArrayList<Tag>();
\r
200 for (int i=0; i<tags.size(); i++) {
\r
201 tempList.add(tags.get(i));
\r
204 while (tempList.size() > 0) {
\r
205 for (int i=0; i<tempList.size(); i++) {
\r
206 tag = tempList.get(i);
\r
207 if (tag.getParentGuid()==null || tag.getParentGuid().equals("")) {
\r
208 child = new NTreeWidgetItem();
\r
209 child.setText(0, tag.getName());
\r
210 if (icons != null && !icons.containsKey(tag.getGuid())) {
\r
211 child.setIcon(0, findDefaultIcon(tag.getGuid()));
\r
213 child.setIcon(0, icons.get(tag.getGuid()));
\r
216 child.setText(2, tag.getGuid());
\r
217 child.setTextAlignment(1, ra.value());
\r
219 addTopLevelItem(child);
\r
220 tempList.remove(i);
\r
222 // We need to find the parent
\r
223 for (int j=0; j<index.size(); j++) {
\r
224 if (index.get(j).text(2).equals(tag.getParentGuid())) {
\r
225 child = new NTreeWidgetItem();
\r
226 child.setText(0, tag.getName());
\r
227 child.setIcon(0, icon);
\r
228 child.setText(2, tag.getGuid());
\r
229 child.setTextAlignment(1, ra.value());
\r
230 if (icons != null && !icons.containsKey(tag.getGuid())) {
\r
231 child.setIcon(0, findDefaultIcon(tag.getGuid()));
\r
233 child.setIcon(0, icons.get(tag.getGuid()));
\r
235 tempList.remove(i);
\r
237 index.get(j).addChild(child);
\r
243 resizeColumnToContents(0);
\r
244 resizeColumnToContents(1);
\r
245 sortItems(0, SortOrder.AscendingOrder);
\r
247 expandTags(invisibleRootItem(), expandedTags);
\r
248 if (lastCount != null)
\r
249 updateCounts(lastCount);
\r
251 // Show (unhide) all tags
\r
252 public void showAllTags(boolean value) {
\r
253 showAllTags = value;
\r
256 // update the display with the current number of notes
\r
257 public void updateCounts(List<TagCounter> counts) {
\r
259 lastCount = counts;
\r
260 MatchFlags flags = new MatchFlags();
\r
261 flags.set(MatchFlag.MatchWildcard);
\r
262 flags.set(MatchFlag.MatchRecursive);
\r
263 // List<QTreeWidgetItem> children = new ArrayList<QTreeWidgetItem>();
\r
264 List <QTreeWidgetItem> children = findItems("*", flags);
\r
266 QBrush black = new QBrush();
\r
267 black.setColor(QColor.black);
\r
268 QBrush blue = new QBrush();
\r
269 blue.setColor(QColor.blue);
\r
270 if (!Global.tagBehavior().equalsIgnoreCase("ColorActive"))
\r
271 blue.setColor(QColor.black);
\r
273 for (int i=0; i<children.size(); i++) {
\r
274 children.get(i).setText(1,"0");
\r
275 children.get(i).setForeground(0, black);
\r
276 children.get(i).setForeground(1, black);
\r
277 if (!showAllTags && (Global.tagBehavior().equalsIgnoreCase("HideInactiveCount") || Global.tagBehavior().equalsIgnoreCase("NoHideInactiveCount")))
\r
278 children.get(i).setHidden(true);
\r
280 children.get(i).setHidden(false);
\r
281 if (children.get(i).isSelected())
\r
282 children.get(i).setHidden(false);
\r
284 for (int i=0; i<counts.size(); i++) {
\r
285 for (int j=0; j<children.size(); j++) {
\r
286 String guid = children.get(j).text(2);
\r
287 if (counts.get(i).getGuid().equals(guid)) {
\r
288 children.get(j).setText(1, new Integer(counts.get(i).getCount()).toString());
\r
289 if (counts.get(i).getCount() > 0 || children.get(j).isSelected()) {
\r
290 children.get(j).setForeground(0, blue);
\r
291 children.get(j).setForeground(1, blue);
\r
292 QTreeWidgetItem parent = children.get(j);
\r
293 while (parent != null) {
\r
294 parent.setForeground(0, blue);
\r
295 parent.setForeground(1, blue);
\r
296 parent.setHidden(false);
\r
297 parent = parent.parent();
\r
306 public boolean selectGuid(String guid) {
\r
307 MatchFlags flags = new MatchFlags();
\r
308 flags.set(MatchFlag.MatchWildcard);
\r
309 flags.set(MatchFlag.MatchRecursive);
\r
310 // List<QTreeWidgetItem> children = new ArrayList<QTreeWidgetItem>();
\r
311 List <QTreeWidgetItem> children = findItems("*", flags);
\r
313 for (int i=0; i<children.size(); i++) {
\r
314 if (children.get(i).text(2).equals(guid)) {
\r
315 children.get(i).setSelected(true);
\r
323 protected void dragMoveEvent(QDragMoveEvent event) {
\r
324 if (event.mimeData().hasFormat("application/x-nevernote-note")) {
\r
325 if (event.answerRect().intersects(childrenRect()))
\r
326 event.acceptProposedAction();
\r
333 public void dragEnterEvent(QDragEnterEvent event) {
\r
334 if (event.mimeData().hasFormat("application/x-nevernote-note")) {
\r
338 if (event.source() == this) {
\r
339 if (Global.tagBehavior().equals("HideInactiveCount")) {
\r
343 event.mimeData().setData("application/x-nevernote-tag", new QByteArray(currentItem().text(2)));
\r
351 public boolean dropMimeData(QTreeWidgetItem parent, int index, QMimeData data, Qt.DropAction action) {
\r
352 if (data.hasFormat("application/x-nevernote-tag")) {
\r
353 QByteArray d = data.data("application/x-nevernote-tag");
\r
354 String current = d.toString();
\r
356 // Check we don't do a dumb thing like move a parent to a child of itself
\r
357 if (!checkParent(parent, current))
\r
359 QTreeWidgetItem newChild;
\r
360 if (parent == null) {
\r
361 // tagSignal.changeParent.emit(current, "");
\r
362 db.getTagTable().updateTagParent(current, "");
\r
363 newChild = new QTreeWidgetItem(this);
\r
365 // tagSignal.changeParent.emit(current, parent.text(2));
\r
366 db.getTagTable().updateTagParent(current, parent.text(2));
\r
367 newChild = new QTreeWidgetItem(parent);
\r
369 copyTreeItem(currentItem(), newChild);
\r
370 currentItem().setHidden(true);
\r
371 sortItems(0, SortOrder.AscendingOrder);
\r
375 // If we are dropping a note
\r
376 if (data.hasFormat("application/x-nevernote-note")) {
\r
377 String notebookGuid = db.getTagTable().getNotebookGuid(parent.text(2));
\r
378 QByteArray d = data.data("application/x-nevernote-note");
\r
379 String s = d.toString();
\r
380 String noteGuidArray[] = s.split(" ");
\r
381 for (String element : noteGuidArray) {
\r
382 Note n = db.getNoteTable().getNote(element.trim(), false, false, false, false, false);
\r
385 // 1.) Check that tag isn't already assigned to that note
\r
386 // 2.) Check that that tag is valid for that notebook or the tag isn't notebook specific
\r
387 // 3.) Check that the notebook isn't read only.
\r
388 if (!db.getNoteTable().noteTagsTable.checkNoteNoteTags(element.trim(), parent.text(2)) &&
\r
389 (notebookGuid == null || n.getNotebookGuid().equalsIgnoreCase(notebookGuid) || notebookGuid.equals("")) &&
\r
390 !db.getNotebookTable().isReadOnly(n.getNotebookGuid())) {
\r
391 db.getNoteTable().noteTagsTable.saveNoteTag(element.trim(), parent.text(2), true);
\r
392 noteSignal.tagsAdded.emit(element.trim(), parent.text(2));
\r
395 //tagSignal.listChanged.emit();
\r
403 public void contextMenuEvent(QContextMenuEvent event) {
\r
404 QMenu menu = new QMenu(this);
\r
405 menu.addAction(addAction);
\r
406 menu.addAction(editAction);
\r
407 menu.addAction(deleteAction);
\r
408 menu.addAction(mergeAction);
\r
409 menu.addSeparator();
\r
410 menu.addAction(iconAction);
\r
411 menu.exec(event.globalPos());
\r
414 public void setIcons(HashMap<String, QIcon> i) {
\r
418 // Copy an individual item within the tree. I need to do this because
\r
419 // Qt doesn't call the dropMimeData on a move, just a copy.
\r
420 private void copyTreeItem(QTreeWidgetItem source, QTreeWidgetItem target) {
\r
421 target.setText(0, source.text(0));
\r
422 target.setIcon(0, source.icon(0));
\r
423 target.setText(1, source.text(1));
\r
424 target.setText(2, source.text(2));
\r
425 Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);
\r
426 target.setTextAlignment(1, ra.value());
\r
428 for (int i=0; i<source.childCount(); i++) {
\r
429 QTreeWidgetItem newChild = new QTreeWidgetItem(target);
\r
430 copyTreeItem(source.child(i), newChild);
\r
431 source.child(i).setHidden(true);
\r
436 // Check that we don't copy a parent as a child of a current child.
\r
437 private boolean checkParent(QTreeWidgetItem parent, String child) {
\r
438 if (parent != null)
\r
439 if (parent.text(2).equals(child))
\r
441 if (parent == null)
\r
443 return checkParent(parent.parent(), child);
\r
447 public void selectTag(QTreeWidgetItem item) {
\r
448 MatchFlags flags = new MatchFlags();
\r
449 flags.set(MatchFlag.MatchWildcard);
\r
450 flags.set(MatchFlag.MatchRecursive);
\r
451 List <QTreeWidgetItem> children = findItems("*", flags);
\r
453 for (int j=0; j<children.size(); j++) {
\r
454 String guid = children.get(j).text(2);
\r
455 if (item.text(2).equals(guid)) {
\r
456 children.get(j).setSelected(true);
\r
461 @SuppressWarnings("unused")
\r
462 private void itemClicked() {
\r
464 List<QTreeWidgetItem> selectedItem = selectedItems();
\r
465 if (selectedItem.size() == 1) {
\r
466 if (selectedItem.get(0).text(0).equalsIgnoreCase(selectedTag) && !rightButtonClicked) {
\r
470 selectedTag = selectedItem.get(0).text(0);
\r
474 selectionSignal.emit();
\r
479 public void mousePressEvent(QMouseEvent e) {
\r
480 if (e.button() == Qt.MouseButton.RightButton)
\r
481 rightButtonClicked = true;
\r
483 rightButtonClicked = false;
\r
484 super.mousePressEvent(e);
\r