OSDN Git Service

Added the ability to merge tags and corrected some shortcuts.
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / gui / TagTreeWidget.java
1 /*\r
2  * This file is part of NeverNote \r
3  * Copyright 2009,2010 Randy Baumgarte\r
4  * Copyright 2010 Hiroshi Miura\r
5  * \r
6  * This file may be licensed under the terms of of the\r
7  * GNU General Public License Version 2 (the ``GPL'').\r
8  *\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
13  *\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
18  *\r
19 */\r
20 \r
21 package cx.fbn.nevernote.gui;\r
22 \r
23 import java.util.ArrayList;\r
24 import java.util.HashMap;\r
25 import java.util.List;\r
26 \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
48 \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
54 \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         \r
70         \r
71         public TagTreeWidget(DatabaseConnection d) {\r
72                 List<String> headers = new ArrayList<String>();\r
73                 headers.add(tr("Tags"));\r
74                 headers.add("");\r
75                 showAllTags = true;\r
76                 setAcceptDrops(true);\r
77                 setDragEnabled(true);\r
78                 setColumnCount(2);\r
79                 header().setResizeMode(0, QHeaderView.ResizeMode.ResizeToContents);\r
80                 header().setResizeMode(1, QHeaderView.ResizeMode.Stretch);\r
81                 header().setMovable(false);\r
82                 header().setStyleSheet("QHeaderView::section {border: 0.0em;}");\r
83                 db = d;\r
84                 selectionSignal = new Signal0();\r
85                 tagSignal = new TagSignal();\r
86                 noteSignal = new NoteSignal();\r
87                 setDragDropMode(QAbstractItemView.DragDropMode.DragDrop);\r
88         setHeaderLabels(headers);\r
89 \r
90 //      setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection);\r
91         setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection);\r
92         \r
93         selectedTag = "";\r
94         itemClicked.connect(this, "itemClicked()");\r
95                 int width = Global.getColumnWidth("tagTreeName");\r
96                 if (width>0)\r
97                         setColumnWidth(0, width);\r
98 \r
99                 \r
100         }\r
101         \r
102         public void setEditAction(QAction e) {\r
103                 editAction = e;\r
104         }\r
105         public void setDeleteAction(QAction d) {\r
106                 deleteAction = d;\r
107         }\r
108         public void setAddAction(QAction a) {\r
109                 addAction = a;\r
110         }\r
111         public void setIconAction(QAction i) {\r
112                 iconAction = i;\r
113         }\r
114         public void setMergeAction(QAction i) { \r
115                 mergeAction = i; }\r
116         \r
117         // Insert a new tag into the tree.  This is used when we dynamically add a \r
118         // new tag after the full tag tree has been built.  It only adds to the\r
119         // top level.\r
120         public void insertTag(String name, String guid) {\r
121         String iconPath = new String("classpath:cx/fbn/nevernote/icons/");\r
122                 QIcon icon = new QIcon(iconPath+"tag.png");\r
123                 NTreeWidgetItem child;\r
124                 Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);\r
125                 \r
126                 // Build new tag & add it\r
127                 child = new NTreeWidgetItem();\r
128                 child.setText(0, name);\r
129                 child.setIcon(0,icon);\r
130                 child.setText(2, guid);\r
131                 child.setTextAlignment(1, ra.value());\r
132                 addTopLevelItem(child);\r
133                 \r
134                 // Resort the list\r
135                 resizeColumnToContents(0);\r
136         resizeColumnToContents(1);\r
137         sortItems(0, SortOrder.AscendingOrder);\r
138         }\r
139         \r
140         private QIcon findDefaultIcon(String guid) {\r
141         String iconPath = new String("classpath:cx/fbn/nevernote/icons/");\r
142                 QIcon icon = new QIcon(iconPath+"tag.png");\r
143                 QIcon linkedIcon = new QIcon(iconPath+"tag-orange.png");\r
144 \r
145                 if (db.getTagTable().getNotebookGuid(guid) == null || \r
146                                 db.getTagTable().getNotebookGuid(guid).equals(""))\r
147                         return icon;\r
148                 else\r
149                         return linkedIcon;\r
150         }\r
151         \r
152         List<String> findExpandedTags(QTreeWidgetItem item) {\r
153                 List<String> list = new ArrayList<String>();\r
154                 if (item.isExpanded()) \r
155                         list.add(item.text(0));\r
156                 for (int i=0; i<item.childCount(); i++) {\r
157                         List<String> childrenList = findExpandedTags(item.child(i));\r
158                         for (int j=0; j<childrenList.size(); j++) {\r
159                                 list.add(childrenList.get(j));\r
160                         }\r
161                 }\r
162                 \r
163                 return list;\r
164         }\r
165         \r
166         void expandTags(QTreeWidgetItem item, List<String> expandedTags) {\r
167                 for (int i=0; i<item.childCount(); i++) {\r
168                         expandTags(item.child(i), expandedTags);\r
169                 }\r
170                 \r
171                 for (int i=0; i<expandedTags.size(); i++) {\r
172                         if (expandedTags.get(i).equalsIgnoreCase(item.text(0))) {\r
173                                 expandItem(item);\r
174                                 i=expandedTags.size();\r
175                         }\r
176                 }\r
177         }\r
178         \r
179         public void load(List<Tag> tags) {\r
180         Tag tag;\r
181         List<NTreeWidgetItem> index = new ArrayList<NTreeWidgetItem>();\r
182         NTreeWidgetItem child;\r
183                         \r
184         /* First, let's find out which stacks are expanded */\r
185         QTreeWidgetItem root =  invisibleRootItem();\r
186         List<String> expandedTags = findExpandedTags(root);\r
187 \r
188 \r
189         \r
190         //Clear out the tree & reload\r
191         clear();\r
192         String iconPath = new String("classpath:cx/fbn/nevernote/icons/");\r
193                 QIcon icon = new QIcon(iconPath+"tag.png");\r
194         \r
195                 Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);\r
196         \r
197                 // Create a copy.  We delete them out as they are found\r
198                 List<Tag> tempList = new ArrayList<Tag>();\r
199                 for (int i=0; i<tags.size(); i++) {\r
200                         tempList.add(tags.get(i));\r
201                 }\r
202                 \r
203         while (tempList.size() > 0) {\r
204                 for (int i=0; i<tempList.size(); i++) {\r
205                         tag = tempList.get(i);\r
206                         if (tag.getParentGuid()==null || tag.getParentGuid().equals("")) {\r
207                                 child = new NTreeWidgetItem();\r
208                                 child.setText(0, tag.getName());\r
209                                 if (icons != null && !icons.containsKey(tag.getGuid())) {\r
210                                         child.setIcon(0, findDefaultIcon(tag.getGuid()));\r
211                                 } else {\r
212                                         child.setIcon(0, icons.get(tag.getGuid()));\r
213                                 }\r
214 \r
215                                 child.setText(2, tag.getGuid());\r
216                                 child.setTextAlignment(1, ra.value());\r
217                                 index.add(child);\r
218                                 addTopLevelItem(child);\r
219                                 tempList.remove(i);\r
220                         } else {\r
221                                 // We need to find the parent\r
222                                 for (int j=0; j<index.size(); j++) {\r
223                                         if (index.get(j).text(2).equals(tag.getParentGuid())) {\r
224                                         child = new NTreeWidgetItem();\r
225                                         child.setText(0, tag.getName());\r
226                                         child.setIcon(0, icon);\r
227                                         child.setText(2, tag.getGuid());\r
228                                         child.setTextAlignment(1, ra.value());\r
229                                         if (icons != null && !icons.containsKey(tag.getGuid())) {\r
230                                                 child.setIcon(0, findDefaultIcon(tag.getGuid()));\r
231                                         } else {\r
232                                                 child.setIcon(0, icons.get(tag.getGuid()));\r
233                                         }\r
234                                         tempList.remove(i);\r
235                                         index.add(child);                                               \r
236                                         index.get(j).addChild(child);\r
237                                         }\r
238                                 }\r
239                         }\r
240                 } \r
241         }\r
242         resizeColumnToContents(0);\r
243         resizeColumnToContents(1);\r
244         sortItems(0, SortOrder.AscendingOrder);\r
245         \r
246         expandTags(invisibleRootItem(), expandedTags);\r
247         }\r
248         // Show (unhide) all tags\r
249         public void showAllTags(boolean value) {\r
250                 showAllTags = value;\r
251         }\r
252         public void unhideAllTags() {\r
253                 MatchFlags flags = new MatchFlags();\r
254                 flags.set(MatchFlag.MatchWildcard);\r
255                 flags.set(MatchFlag.MatchRecursive);\r
256                 List <QTreeWidgetItem>  children = findItems("*", flags);\r
257                 for (int i=0; i<children.size(); i++) {\r
258                         children.get(i).setHidden(false);\r
259                 }\r
260         }\r
261         // update the display with the current number of notes\r
262         public void updateCounts(List<TagCounter> counts) {\r
263                                 \r
264                 MatchFlags flags = new MatchFlags();\r
265                 flags.set(MatchFlag.MatchWildcard);\r
266                 flags.set(MatchFlag.MatchRecursive);\r
267 //              List<QTreeWidgetItem> children = new ArrayList<QTreeWidgetItem>();\r
268                 List <QTreeWidgetItem>  children = findItems("*", flags);\r
269                 \r
270                 QBrush black = new QBrush();\r
271                 black.setColor(QColor.black);\r
272                 QBrush blue = new QBrush();\r
273                 blue.setColor(QColor.blue);\r
274                 if (!Global.tagBehavior().equalsIgnoreCase("ColorActive"))\r
275                         blue.setColor(QColor.black);\r
276                 \r
277                 for (int i=0; i<children.size(); i++) {\r
278                         children.get(i).setText(1,"0");\r
279                         children.get(i).setForeground(0, black);                        \r
280                         children.get(i).setForeground(1, black);\r
281                         if (!showAllTags && (Global.tagBehavior().equalsIgnoreCase("HideInactiveCount") || Global.tagBehavior().equalsIgnoreCase("NoHideInactiveCount")))\r
282                                 children.get(i).setHidden(true);\r
283                         else\r
284                                 children.get(i).setHidden(false);\r
285                         if (children.get(i).isSelected())\r
286                                 children.get(i).setHidden(false);\r
287                 }\r
288                 for (int i=0; i<counts.size(); i++) {\r
289                         for (int j=0; j<children.size(); j++) {\r
290                                 String guid = children.get(j).text(2);\r
291                                 if (counts.get(i).getGuid().equals(guid)) {\r
292                                         children.get(j).setText(1, new Integer(counts.get(i).getCount()).toString());\r
293                                         if (counts.get(i).getCount() > 0 || children.get(j).isSelected()) {\r
294                                                 children.get(j).setForeground(0, blue);                 \r
295                                                 children.get(j).setForeground(1, blue);\r
296                                                 QTreeWidgetItem parent = children.get(j);\r
297                                                 while (parent != null) {\r
298                                                         parent.setForeground(0, blue);                  \r
299                                                         parent.setForeground(1, blue);\r
300                                                         parent.setHidden(false);\r
301                                                         parent = parent.parent();\r
302                                                 }\r
303                                         }\r
304                                 }\r
305                         }\r
306                 }\r
307         }\r
308 \r
309         \r
310         public boolean selectGuid(String guid) {\r
311                 MatchFlags flags = new MatchFlags();\r
312                 flags.set(MatchFlag.MatchWildcard);\r
313                 flags.set(MatchFlag.MatchRecursive);\r
314 //              List<QTreeWidgetItem> children = new ArrayList<QTreeWidgetItem>();\r
315                 List <QTreeWidgetItem>  children = findItems("*", flags);\r
316 \r
317                 for (int i=0; i<children.size(); i++) {\r
318                         if (children.get(i).text(2).equals(guid)) {\r
319                                 children.get(i).setSelected(true);\r
320                                 return true;\r
321                         }\r
322                 }\r
323                 return false;\r
324         }\r
325         \r
326          @Override\r
327          protected void dragMoveEvent(QDragMoveEvent event) {\r
328                 if (event.mimeData().hasFormat("application/x-nevernote-note")) {\r
329                         if (event.answerRect().intersects(childrenRect()))\r
330                                 event.acceptProposedAction();\r
331                         return;\r
332                 }\r
333          }\r
334 \r
335         \r
336         @Override\r
337         public void dragEnterEvent(QDragEnterEvent event) {\r
338                 if (event.mimeData().hasFormat("application/x-nevernote-note")) {\r
339                         event.accept();\r
340                         return;\r
341                 }\r
342                 if (event.source() == this) {\r
343                         if (Global.tagBehavior().equals("HideInactiveCount")) {\r
344                                 event.ignore();\r
345                                 return;\r
346                         }\r
347                         event.mimeData().setData("application/x-nevernote-tag", new QByteArray(currentItem().text(2)));\r
348                         event.accept();\r
349                         return;\r
350                 }\r
351                 event.ignore();\r
352         }\r
353 \r
354         @Override\r
355         public boolean dropMimeData(QTreeWidgetItem parent, int index, QMimeData data, Qt.DropAction action) {\r
356                 if (data.hasFormat("application/x-nevernote-tag")) {\r
357                         QByteArray d = data.data("application/x-nevernote-tag");\r
358                         String current = d.toString();\r
359                         \r
360                         // Check we don't do a dumb thing like move a parent to a child of itself\r
361                         if (!checkParent(parent, current))\r
362                                 return false;\r
363                         QTreeWidgetItem newChild;\r
364                         if (parent == null) {\r
365 //                              tagSignal.changeParent.emit(current, "");\r
366                                 db.getTagTable().updateTagParent(current, "");\r
367                                 newChild = new QTreeWidgetItem(this);\r
368                         } else {\r
369 //                              tagSignal.changeParent.emit(current, parent.text(2));\r
370                                 db.getTagTable().updateTagParent(current, parent.text(2));\r
371                                 newChild = new QTreeWidgetItem(parent);\r
372                         }\r
373                         copyTreeItem(currentItem(), newChild);\r
374                         currentItem().setHidden(true);\r
375                         sortItems(0, SortOrder.AscendingOrder);\r
376                         return true;\r
377                 }\r
378                 \r
379                 // If we are dropping a note\r
380                 if (data.hasFormat("application/x-nevernote-note")) {\r
381                         String notebookGuid = db.getTagTable().getNotebookGuid(parent.text(2));\r
382                         QByteArray d = data.data("application/x-nevernote-note");\r
383                         String s = d.toString();\r
384                         String noteGuidArray[] = s.split(" ");\r
385                         for (String element : noteGuidArray) {\r
386                                 Note n = db.getNoteTable().getNote(element.trim(), false, false, false, false, false);\r
387                                 \r
388                                 // Check that...\r
389                                 // 1.) Check that tag isn't already assigned to that note\r
390                                 // 2.) Check that that tag is valid for that notebook or the tag isn't notebook specific\r
391                                 // 3.) Check that the notebook isn't read only.\r
392                                 if (!db.getNoteTable().noteTagsTable.checkNoteNoteTags(element.trim(), parent.text(2)) &&\r
393                                                 (notebookGuid == null || n.getNotebookGuid().equalsIgnoreCase(notebookGuid) || notebookGuid.equals("")) &&\r
394                                                 !db.getNotebookTable().isReadOnly(n.getNotebookGuid())) {\r
395                                         db.getNoteTable().noteTagsTable.saveNoteTag(element.trim(), parent.text(2));\r
396                                         noteSignal.tagsAdded.emit(element.trim(), parent.text(2));\r
397                                 }\r
398                         }\r
399                         //tagSignal.listChanged.emit();\r
400                         \r
401                         return true;\r
402                 }\r
403                 return false;\r
404         }\r
405         \r
406         @Override\r
407         public void contextMenuEvent(QContextMenuEvent event) {\r
408                 QMenu menu = new QMenu(this);\r
409                 menu.addAction(addAction);\r
410                 menu.addAction(editAction);\r
411                 menu.addAction(deleteAction);\r
412                 menu.addAction(mergeAction);\r
413                 menu.addSeparator();\r
414                 menu.addAction(iconAction);\r
415                 menu.exec(event.globalPos());\r
416         }\r
417         \r
418         public void setIcons(HashMap<String, QIcon> i) {\r
419                 icons = i;\r
420         }\r
421         \r
422         // Copy an individual item within the tree.  I need to do this because\r
423         // Qt doesn't call the dropMimeData on a move, just a copy.\r
424         private void copyTreeItem(QTreeWidgetItem source, QTreeWidgetItem target) {\r
425                 target.setText(0, source.text(0));\r
426                 target.setIcon(0, source.icon(0));\r
427                 target.setText(1, source.text(1));\r
428                 target.setText(2, source.text(2));\r
429                 Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);\r
430                 target.setTextAlignment(1, ra.value());\r
431                 \r
432                 for (int i=0; i<source.childCount(); i++) {\r
433                         QTreeWidgetItem newChild = new QTreeWidgetItem(target);\r
434                         copyTreeItem(source.child(i), newChild);\r
435                         source.child(i).setHidden(true);\r
436                 }\r
437                 return;\r
438         }\r
439         \r
440         // Check that we don't copy a parent as a child of a current child.\r
441         private boolean checkParent(QTreeWidgetItem parent, String child) {\r
442                 if (parent != null)\r
443                         if (parent.text(2).equals(child))\r
444                                 return false;\r
445                 if (parent == null)\r
446                         return true;\r
447                 return checkParent(parent.parent(), child);\r
448         }\r
449 \r
450 \r
451         public void selectSavedSearch(QTreeWidgetItem item) {\r
452                 MatchFlags flags = new MatchFlags();\r
453                 flags.set(MatchFlag.MatchWildcard);\r
454                 flags.set(MatchFlag.MatchRecursive);\r
455                 List <QTreeWidgetItem>  children = findItems("*", flags);\r
456                 \r
457                 for (int j=0; j<children.size(); j++) {\r
458                         String guid = children.get(j).text(2);\r
459                         if (item.text(2).equals(guid)) {\r
460                                 children.get(j).setSelected(true);\r
461                         }\r
462                 }\r
463         }\r
464 \r
465         @SuppressWarnings("unused")\r
466         private void itemClicked() {\r
467                 \r
468                 List<QTreeWidgetItem> selectedItem = selectedItems();\r
469                 if (selectedItem.size() == 1) {\r
470                         if (selectedItem.get(0).text(0).equalsIgnoreCase(selectedTag) && !rightButtonClicked) {\r
471                                 selectedTag = "";\r
472                                 clearSelection();\r
473                         } else {\r
474                                 selectedTag = selectedItem.get(0).text(0);\r
475                         }\r
476                         \r
477                 }\r
478                 selectionSignal.emit();\r
479         }\r
480 \r
481         \r
482         @Override\r
483         public void mousePressEvent(QMouseEvent e) {\r
484                 if (e.button() == Qt.MouseButton.RightButton)\r
485                         rightButtonClicked = true;\r
486                 else\r
487                         rightButtonClicked = false;\r
488                 super.mousePressEvent(e);\r
489         }\r
490 \r
491         \r
492 }\r