OSDN Git Service

Retain expanded state of notebooks & tags across sync.
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / gui / NotebookTreeWidget.java
1 /*\r
2  * This file is part of NeverNote \r
3  * Copyright 2009 Randy Baumgarte\r
4  * \r
5  * This file may be licensed under the terms of of the\r
6  * GNU General Public License Version 2 (the ``GPL'').\r
7  *\r
8  * Software distributed under the License is distributed\r
9  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either\r
10  * express or implied. See the GPL for the specific language\r
11  * governing rights and limitations.\r
12  *\r
13  * You should have received a copy of the GPL along with this\r
14  * program. If not, go to http://www.gnu.org/licenses/gpl.html\r
15  * or write to the Free Software Foundation, Inc.,\r
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\r
17  *\r
18 */\r
19 \r
20 package cx.fbn.nevernote.gui;\r
21 \r
22 import java.util.ArrayList;\r
23 import java.util.HashMap;\r
24 import java.util.List;\r
25 \r
26 import com.evernote.edam.type.Note;\r
27 import com.evernote.edam.type.Notebook;\r
28 import com.trolltech.qt.core.QByteArray;\r
29 import com.trolltech.qt.core.QMimeData;\r
30 import com.trolltech.qt.core.Qt;\r
31 import com.trolltech.qt.core.Qt.SortOrder;\r
32 import com.trolltech.qt.gui.QAbstractItemView;\r
33 import com.trolltech.qt.gui.QAction;\r
34 import com.trolltech.qt.gui.QBrush;\r
35 import com.trolltech.qt.gui.QColor;\r
36 import com.trolltech.qt.gui.QContextMenuEvent;\r
37 import com.trolltech.qt.gui.QDragEnterEvent;\r
38 import com.trolltech.qt.gui.QDragMoveEvent;\r
39 import com.trolltech.qt.gui.QHeaderView;\r
40 import com.trolltech.qt.gui.QIcon;\r
41 import com.trolltech.qt.gui.QMenu;\r
42 import com.trolltech.qt.gui.QMouseEvent;\r
43 import com.trolltech.qt.gui.QTreeWidget;\r
44 import com.trolltech.qt.gui.QTreeWidgetItem;\r
45 import com.trolltech.qt.gui.QTreeWidgetItem.ChildIndicatorPolicy;\r
46 \r
47 import cx.fbn.nevernote.Global;\r
48 import cx.fbn.nevernote.filters.NotebookCounter;\r
49 import cx.fbn.nevernote.signals.NoteSignal;\r
50 import cx.fbn.nevernote.sql.DatabaseConnection;\r
51 \r
52 public class NotebookTreeWidget extends QTreeWidget {\r
53         private QAction                                 deleteAction;\r
54         private QAction                                 addAction;\r
55         private QAction                                 editAction;\r
56         private QAction                                 iconAction;\r
57         private QAction                                 stackAction;\r
58         private QAction                                 publishAction;\r
59         private QAction                                 shareAction;\r
60         public NoteSignal                               noteSignal;\r
61         public Signal0                                  selectionSignal;\r
62         private String                                  selectedNotebook;\r
63         private HashMap<String, QIcon>  icons;\r
64         private final DatabaseConnection                db;\r
65         private final HashMap<String, QTreeWidgetItem>  stacks;\r
66         private boolean rightButtonClicked;\r
67         \r
68         public void setAddAction(QAction a) {\r
69                 addAction = a;\r
70         }\r
71         \r
72         public void setPublishAction(QAction p) {\r
73                 publishAction = p;\r
74         }\r
75         \r
76         public void setShareAction(QAction s) {\r
77                 shareAction = s;\r
78         }\r
79         \r
80         public void setDeleteAction(QAction d) {\r
81                 deleteAction = d;\r
82         }\r
83         \r
84         public void setEditAction(QAction e) {\r
85                 editAction = e;\r
86         }\r
87         \r
88         public void setStackAction(QAction e) {\r
89                 stackAction = e;\r
90         }\r
91         \r
92         public void setIconAction(QAction e) {\r
93                 iconAction = e;\r
94         }\r
95         \r
96         public NotebookTreeWidget(DatabaseConnection db) {\r
97                 noteSignal = new NoteSignal();\r
98                 this.db = db;\r
99 //              setProperty("hideTree", true);\r
100                 List<String> labels = new ArrayList<String>();\r
101                 labels.add("Notebooks");\r
102                 labels.add("");\r
103                 setAcceptDrops(true);\r
104                 setDragEnabled(true);\r
105                 setColumnCount(2);\r
106                 header().setResizeMode(0, QHeaderView.ResizeMode.ResizeToContents);\r
107                 header().setResizeMode(1, QHeaderView.ResizeMode.Stretch);\r
108                 header().setMovable(false);\r
109                 setHeaderLabels(labels);\r
110                 setDragDropMode(QAbstractItemView.DragDropMode.DragDrop);\r
111                 // If we want to mimic Evernote's notebook selection\r
112                 if (Global.mimicEvernoteInterface) {\r
113                         setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection);\r
114                 } else\r
115                         setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection);\r
116 \r
117                 stacks = new HashMap<String, QTreeWidgetItem>();\r
118 //      int width = Global.getColumnWidth("notebookTreeName");\r
119 //              if (width>0)\r
120 //                      setColumnWidth(0, width);\r
121 //              previousMouseOver = new QTreeWidgetItem();\r
122                 selectionSignal = new Signal0();\r
123                 selectedNotebook = "";\r
124                 rightButtonClicked = false;\r
125                 itemClicked.connect(this, "itemClicked()");\r
126         }\r
127         \r
128         public void selectNotebook(QTreeWidgetItem item) {\r
129                 QTreeWidgetItem root = invisibleRootItem();\r
130                 QTreeWidgetItem child;\r
131                 \r
132                 for (int i=0; i<root.childCount(); i++) {\r
133                         child = root.child(i); \r
134                         if (child.text(2).equals(item.text(2))) {\r
135                                 child.setSelected(true);\r
136                                 return;\r
137                         }\r
138                 }\r
139                 \r
140         }\r
141         \r
142         public boolean selectGuid(String guid) {\r
143                 QTreeWidgetItem root = invisibleRootItem();\r
144                 QTreeWidgetItem child;\r
145 \r
146                 for (int i=0; i<root.childCount(); i++) {\r
147                         child = root.child(i);\r
148                         if (child.text(2).equals(guid)) {\r
149                                 child.setSelected(true);\r
150                                 return true;\r
151                         }\r
152                 }\r
153                 return false;\r
154         }\r
155         \r
156         public void setIcons(HashMap<String, QIcon> i) {\r
157                 icons = i;\r
158         }\r
159         \r
160         public QIcon findDefaultIcon(String guid, String name, List<String> localBooks, boolean isPublished) {\r
161         String iconPath = new String("classpath:cx/fbn/nevernote/icons/");\r
162         QIcon blueIcon = new QIcon(iconPath+"notebook-blue.png");\r
163         QIcon greenIcon = new QIcon(iconPath+"notebook-green.png");\r
164         QIcon redIcon = new QIcon(iconPath+"notebook-red.png");\r
165         QIcon yellowIcon = new QIcon(iconPath+"notebook-yellow.png");\r
166         QIcon orangeIcon = new QIcon(iconPath+"notebook-orange.png");\r
167                 \r
168                 if (localBooks.contains(guid) && \r
169                         (name.equalsIgnoreCase("Conflicting Changes") ||\r
170                          name.equalsIgnoreCase("Conflicting Changes (Local)")))\r
171                                 return redIcon;\r
172                 if (localBooks.contains(guid)) {\r
173                         return yellowIcon;\r
174                 }\r
175                 if (isPublished)\r
176                         return blueIcon;\r
177                 \r
178                 if (db.getNotebookTable().isLinked(guid))\r
179                         return orangeIcon;\r
180 \r
181                 return greenIcon;\r
182         }\r
183         \r
184         public void load(List<Notebook> books, List<String> localBooks) {\r
185         Notebook book;\r
186         QTreeWidgetItem child;\r
187         \r
188         /* First, let's find out which stacks are expanded */\r
189         QTreeWidgetItem root =  invisibleRootItem();\r
190         List<String> expandedStacks = new ArrayList<String>();\r
191         for (int i=0; i<root.childCount(); i++) {\r
192                 if (root.child(i).isExpanded())\r
193                         expandedStacks.add(root.child(i).text(0));\r
194         }\r
195         \r
196         clear();\r
197         stacks.clear();\r
198         \r
199         if (books == null)\r
200                 return;\r
201         Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);\r
202         for (int i=0; i<books.size(); i++) {\r
203                         book = books.get(i);\r
204                         child = new QTreeWidgetItem();\r
205                         child.setChildIndicatorPolicy(ChildIndicatorPolicy.DontShowIndicatorWhenChildless);\r
206                         child.setText(0, book.getName());\r
207                 if (icons != null && !icons.containsKey(book.getGuid())) {\r
208                         QIcon icon = findDefaultIcon(book.getGuid(), book.getName(), localBooks, book.isPublished());\r
209                         child.setIcon(0, icon);\r
210                 } else {\r
211                         child.setIcon(0, icons.get(book.getGuid()));\r
212                 }\r
213                 child.setTextAlignment(1, ra.value());\r
214                 child.setText(2, book.getGuid());\r
215                 if (book.getStack() == null || book.getStack().equalsIgnoreCase(""))\r
216                         addTopLevelItem(child); \r
217                 else {\r
218                         String stackName = book.getStack();\r
219                         QTreeWidgetItem parent;\r
220                         if (!stacks.containsKey(stackName)) {\r
221                                 parent = createStackIcon(stackName, ra);\r
222                                 addTopLevelItem(parent);\r
223                                 stacks.put(stackName, parent);\r
224                         } else\r
225                                 parent = stacks.get(stackName);\r
226                         parent.addChild(child);\r
227                         \r
228                 }\r
229         }\r
230 \r
231         sortItems(0, SortOrder.AscendingOrder); \r
232         if (Global.mimicEvernoteInterface) {\r
233                 String iconPath = new String("classpath:cx/fbn/nevernote/icons/");\r
234                 QIcon allIcon = db.getSystemIconTable().getIcon("All Notebooks", "ALLNOTEBOOK");\r
235                 \r
236                 if (allIcon == null)\r
237                         allIcon = new QIcon(iconPath+"notebook-green.png");\r
238                 \r
239                 child = new QTreeWidgetItem();\r
240                 child.setIcon(0, allIcon);\r
241                 child.setText(0, "All Notebooks");\r
242                 child.setText(2, "");\r
243                 child.setTextAlignment(1, ra.value());\r
244                 insertTopLevelItem(0,child);\r
245         }\r
246         resizeColumnToContents(0);\r
247         resizeColumnToContents(1);\r
248         \r
249         // Finally, expand the stacks back out\r
250         root = invisibleRootItem();\r
251         for (int i=0; i<root.childCount(); i++) {\r
252                 for (int j=0; j<expandedStacks.size(); j++) {\r
253                         if (root.child(i).text(0).equalsIgnoreCase(expandedStacks.get(j))) {\r
254                                 expandItem(root.child(i));\r
255                                 j=expandedStacks.size();\r
256                         }\r
257                 }\r
258         }\r
259         }\r
260 \r
261 \r
262         // update the display with the current number of notes\r
263         public void updateCounts(List<Notebook> books, List<NotebookCounter> counts) {\r
264                 QTreeWidgetItem root = invisibleRootItem();\r
265                 QTreeWidgetItem child;\r
266                 \r
267                 QBrush blue = new QBrush();\r
268                 QBrush black = new QBrush();\r
269                 black.setColor(QColor.black);\r
270                 if (Global.tagBehavior().equalsIgnoreCase("ColorActive") && !Global.mimicEvernoteInterface)\r
271                         blue.setColor(QColor.blue);\r
272                 else\r
273                         blue.setColor(QColor.black);\r
274                 int total=0;\r
275                 \r
276                 \r
277                 int size = books.size();\r
278                 if (Global.mimicEvernoteInterface)\r
279                         size++;\r
280                 \r
281                 for (int i=0; i<size; i++) {\r
282                         child = root.child(i);\r
283                         if (child != null && child.childCount() > 0) {\r
284                                 int count = child.childCount();\r
285                                 QTreeWidgetItem parent = child;\r
286                                 int localTotal = 0;\r
287                                 for (int j=0; j<count; j++) {\r
288                                         child = parent.child(j);\r
289                                         int childCount = updateCounts(child, books, counts, blue, black);\r
290                                         total = total+childCount;\r
291                                         localTotal = localTotal+childCount;\r
292                                 }\r
293                                 parent.setText(1, new Integer(localTotal).toString());\r
294                         } else\r
295                                 total = total+updateCounts(child, books, counts, blue, black);\r
296                 }\r
297                 \r
298                 for (int i=0; i<size; i++) {\r
299                         child = root.child(i); \r
300                         if (child != null) {\r
301                                 String guid = child.text(2);\r
302                                 if (guid.equals("") && Global.mimicEvernoteInterface) \r
303                                         child.setText(1, new Integer(total).toString());\r
304                         }\r
305                 }\r
306         }\r
307         \r
308         private int updateCounts(QTreeWidgetItem child, List<Notebook> books, List<NotebookCounter> counts, QBrush blue, QBrush black) {\r
309                 int total=0;\r
310                 if (child != null) {\r
311                         String guid = child.text(2);\r
312                         child.setText(1,"0");\r
313                         child.setForeground(0, black);\r
314                         child.setForeground(1, black);\r
315                         for (int j=0; j<counts.size(); j++) {\r
316                                 if (counts.get(j).getGuid().equals(guid)) {\r
317                                         child.setText(1, new Integer(counts.get(j).getCount()).toString());\r
318                                         total = counts.get(j).getCount();\r
319                                         if (counts.get(j).getCount() > 0) {\r
320                                                 child.setForeground(0, blue);\r
321                                                 child.setForeground(1, blue);\r
322                                         }\r
323                                 }\r
324                         }\r
325                 }\r
326                 return total;\r
327         }\r
328         \r
329         // Return a list of the notebook guids, ordered by the current display order.\r
330         public List<String> getNotebookGuids() {\r
331                 List<String> names = new ArrayList<String>();\r
332                 QTreeWidgetItem root = invisibleRootItem();\r
333                 QTreeWidgetItem child;\r
334                 for (int i=0; i<root.childCount(); i++) {\r
335                         child = root.child(i);\r
336                         String text = child.text(2);\r
337                         names.add(text);\r
338                 }\r
339                 return names;\r
340         }\r
341         \r
342         @Override\r
343         public void contextMenuEvent(QContextMenuEvent event) {\r
344                 QMenu menu = new QMenu(this);\r
345                 menu.addAction(addAction);\r
346                 menu.addAction(editAction);\r
347                 menu.addAction(deleteAction);\r
348                 menu.addAction(stackAction);\r
349                 menu.addSeparator();\r
350                 menu.addAction(publishAction);\r
351                 menu.addAction(shareAction);\r
352                 menu.addSeparator();\r
353                 menu.addAction(iconAction);\r
354                 menu.exec(event.globalPos());\r
355         }\r
356         \r
357         \r
358         @Override\r
359         public void dragEnterEvent(QDragEnterEvent event) {\r
360                 if (event.mimeData().hasFormat("application/x-nevernote-note")) {\r
361                         event.accept();\r
362                         return;\r
363                 }\r
364                 if (event.source() == this) {\r
365                         event.mimeData().setData("application/x-nevernote-notebook", new QByteArray(currentItem().text(2)));\r
366                         List<QTreeWidgetItem> selected = selectedItems();\r
367                         for (int i=0; i<selected.size(); i++) {\r
368                                 if (selected.get(i).text(2).equalsIgnoreCase("STACK") || \r
369                                         selected.get(i).text(2).equals("")) {\r
370                                                 event.ignore();\r
371                                                 return;\r
372                                         }\r
373                         }\r
374                         event.accept();\r
375                         return;\r
376                 }\r
377                 event.ignore();\r
378         }\r
379         \r
380         \r
381          @Override\r
382         protected void dragMoveEvent(QDragMoveEvent event) {\r
383                // if (event.mimeData().hasFormat("text/plain") &&\r
384                      //event.answerRect().intersects(dropFrame.geometry()))\r
385                         QTreeWidgetItem treeItem = itemAt(event.pos().x(), event.pos().y());\r
386                         if (treeItem != null) {\r
387 /*                              if (!previousMouseOver.text(0).equalsIgnoreCase(treeItem.text(0))) {\r
388                                         previousMouseOver.setSelected(previousMouseOverWasSelected);\r
389                                         previousMouseOverWasSelected = treeItem.isSelected();\r
390                                         previousMouseOver = treeItem;\r
391                                         blockSignals(true);\r
392                                         treeItem.setSelected(true);\r
393                                         blockSignals(false);\r
394                                 }\r
395 */                              \r
396                         }\r
397                         if (event.mimeData().hasFormat("application/x-nevernote-note")) {\r
398                                 if (event.answerRect().intersects(childrenRect()))\r
399                                         event.acceptProposedAction();\r
400                                 return;\r
401                         }\r
402             }\r
403 \r
404         \r
405         @Override\r
406         public boolean dropMimeData(QTreeWidgetItem parent, int index, QMimeData data, Qt.DropAction action) {\r
407                 if (data.hasFormat("application/x-nevernote-notebook")) {\r
408                         return false;\r
409                 }\r
410                 \r
411                 // This is really dead code.  it is the beginning of logic to create stacks by\r
412                 // dragging.\r
413                 if (data.hasFormat("application/x-nevernote-notebook")) {\r
414                         QByteArray d = data.data("application/x-nevernote-notebook");\r
415                         String current = d.toString();\r
416                         \r
417                         // If dropping to the top level, then remove the stack\r
418                         if (parent == null) {\r
419                                 db.getNotebookTable().clearStack(current);\r
420                                 return true;\r
421                         } \r
422                         \r
423                         // If trying to drop under the "All notebooks" then ignore\r
424                         if (parent.text(2).equals(""))\r
425                                 return false;\r
426                         \r
427                         \r
428                         // If we are NOT droping directly onto the stack icon\r
429                         // we need to find the stack widget\r
430                         String stackName;\r
431                         QTreeWidgetItem stackItem;\r
432                         List<QTreeWidgetItem> currentItems = selectedItems();\r
433                         if (!parent.text(2).equalsIgnoreCase("STACK")) {\r
434                                 \r
435                                 // If a parent stack exists, then use it.\r
436                                 if (parent.parent() != null) {\r
437                                         stackName = parent.parent().text(0);\r
438                                         stackItem = parent.parent();\r
439                                 } else {\r
440                                         \r
441                                         currentItems.add(parent);\r
442                                         // If a stack doesn't exist, then we need to create one\r
443                                         stackName = "New Stack";        \r
444                                         // Find a new stack name that isn't in use\r
445                                         for (int i=1; i<101; i++) {\r
446                                                 if (stacks.containsKey(stackName))\r
447                                                         stackName = "New Stack(" +new Integer(i).toString() + ")";\r
448                                                 else\r
449                                                         break;\r
450                                         }\r
451                                         db.getNotebookTable().setStack(parent.text(2), stackName);\r
452                                         Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);\r
453                                         stackItem = createStackIcon(stackName, ra);\r
454                                         addTopLevelItem(stackItem);\r
455                                 }\r
456                         } else {\r
457                                 stackName = parent.text(0);\r
458                                 stackItem = parent;\r
459                         }\r
460                         \r
461                         List<QTreeWidgetItem> newItems = new ArrayList<QTreeWidgetItem>();\r
462                         for (int i=0; i<currentItems.size(); i++) {\r
463                                 newItems.add(copyTreeItem(currentItems.get(i)));\r
464                                 currentItems.get(i).setHidden(true);\r
465                         }\r
466                         db.getNotebookTable().setStack(current, stackName);             \r
467                         stackItem.addChildren(newItems);\r
468                         \r
469                         return true;\r
470                 }\r
471                 \r
472                 \r
473                 // If we are dropping a note onto a notebook\r
474                 if (data.hasFormat("application/x-nevernote-note")) {\r
475                         // If we are dropping onto a read-only notebook, we are done.\r
476                         if (db.getNotebookTable().isReadOnly(parent.text(2)))\r
477                                         return false;\r
478                         \r
479                         QByteArray d = data.data("application/x-nevernote-note");\r
480                         String s = d.toString();\r
481                         String noteGuidArray[] = s.split(" ");\r
482                         for (String element : noteGuidArray) {\r
483                                 Note n = db.getNoteTable().getNote(element.trim(), false, false, false, false, true);\r
484                                 \r
485                                 // We  need to be sure that...\r
486                                 // 1.) We are not dropping onto the "All Notebooks" stack\r
487                                 // 2.) We are not dropping onto a stack\r
488                                 // 3.) We are actually dropping onto a different notebook.\r
489                                 if (!parent.text(2).equalsIgnoreCase("") && \r
490                                                 !parent.text(2).equalsIgnoreCase(tr("STACK")) &&\r
491                                                 !(n.getNotebookGuid().equalsIgnoreCase(parent.text(2))\r
492                                         )) {\r
493                                         noteSignal.notebookChanged.emit(element.trim(), parent.text(2));\r
494                                         if (db.getNotebookTable().isLinked(parent.text(2))) {\r
495                                                 noteSignal.tagsChanged.emit(element.trim(), new ArrayList<String>());\r
496                                         }\r
497                                 }\r
498                         }\r
499                         return true;\r
500                 }\r
501                 return false;\r
502         }\r
503         \r
504 \r
505         private QTreeWidgetItem createStackIcon(String stackName, Qt.Alignment ra) {\r
506                 String iconPath = new String("classpath:cx/fbn/nevernote/icons/");\r
507                 QIcon stackIcon;\r
508                 stackIcon = db.getSystemIconTable().getIcon(stackName, "STACK");\r
509                 \r
510                 if (stackIcon == null)\r
511                         stackIcon = new QIcon(iconPath+"books2.png");\r
512                 QTreeWidgetItem parent = new QTreeWidgetItem();\r
513                 stacks.put(stackName, parent);\r
514                 parent.setText(0, stackName);\r
515                 parent.setIcon(0, stackIcon);\r
516                 parent.setText(2, "STACK");\r
517                 parent.setTextAlignment(1, ra.value());\r
518                 return parent;\r
519         }\r
520 \r
521         \r
522         \r
523         // Copy an individual item within the tree.  I need to do this because\r
524         // Qt doesn't call the dropMimeData on a move, just a copy.\r
525         private QTreeWidgetItem copyTreeItem(QTreeWidgetItem source) {\r
526                 QTreeWidgetItem target = new QTreeWidgetItem(this);\r
527                 target.setText(0, source.text(0));\r
528                 target.setIcon(0, source.icon(0));\r
529                 target.setText(1, source.text(1));\r
530                 target.setText(2, source.text(2));\r
531                 Qt.Alignment ra = new Qt.Alignment(Qt.AlignmentFlag.AlignRight);\r
532                 target.setTextAlignment(1, ra.value());\r
533                 source.setHidden(true);\r
534 \r
535                 return target;\r
536         }\r
537 \r
538         \r
539         @SuppressWarnings("unused")\r
540         private void itemClicked() {\r
541                 List<QTreeWidgetItem> selectedItem = selectedItems();\r
542                 if (selectedItem.size() == 1) {\r
543                         if (selectedItem.get(0).text(0).equalsIgnoreCase(selectedNotebook) && \r
544                                         !Global.mimicEvernoteInterface && !rightButtonClicked) {\r
545                                 selectedNotebook = "";\r
546                                 clearSelection();\r
547                         } else {\r
548                                 selectedNotebook = selectedItem.get(0).text(0);\r
549                         }\r
550                         \r
551                 }\r
552                 selectionSignal.emit();\r
553         }\r
554 \r
555         \r
556         @Override\r
557         public void mousePressEvent(QMouseEvent e) {\r
558                 if (e.button() == Qt.MouseButton.RightButton)\r
559                         rightButtonClicked = true;\r
560                 else\r
561                         rightButtonClicked = false;\r
562                 super.mousePressEvent(e);\r
563         }\r
564 }\r