OSDN Git Service

Evernote APIの帯域制限超過時にエラーメッセージを表示するようにした
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / gui / RensoNoteList.java
1 /*
2  * This file is part of NeighborNote
3  * Copyright 2013 Yuki Takahashi
4  * 
5  * This file may be licensed under the terms of of the
6  * GNU General Public License Version 2 (the ``GPL'').
7  *
8  * Software distributed under the License is distributed
9  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
10  * express or implied. See the GPL for the specific language
11  * governing rights and limitations.
12  *
13  * You should have received a copy of the GPL along with this
14  * program. If not, go to http://www.gnu.org/licenses/gpl.html
15  * or write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18 */
19
20 // ICHANGED
21 package cx.fbn.nevernote.gui;
22
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Set;
28
29 import com.evernote.edam.type.Note;
30 import com.trolltech.qt.QThread;
31 import com.trolltech.qt.core.QSize;
32 import com.trolltech.qt.core.Qt.MouseButton;
33 import com.trolltech.qt.gui.QAction;
34 import com.trolltech.qt.gui.QApplication;
35 import com.trolltech.qt.gui.QContextMenuEvent;
36 import com.trolltech.qt.gui.QListWidget;
37 import com.trolltech.qt.gui.QListWidgetItem;
38 import com.trolltech.qt.gui.QMenu;
39
40 import cx.fbn.nevernote.Global;
41 import cx.fbn.nevernote.NeverNote;
42 import cx.fbn.nevernote.sql.DatabaseConnection;
43 import cx.fbn.nevernote.threads.ENRelatedNotesRunner;
44 import cx.fbn.nevernote.threads.SyncRunner;
45 import cx.fbn.nevernote.utilities.ApplicationLogger;
46 import cx.fbn.nevernote.utilities.Pair;
47
48 public class RensoNoteList extends QListWidget {
49         private final DatabaseConnection conn;
50         private final ApplicationLogger logger;
51         private final HashMap<QListWidgetItem, String> rensoNoteListItems;
52         private final List<RensoNoteListItem> rensoNoteListTrueItems;
53         private String rensoNotePressedItemGuid;
54         private final QAction openNewTabAction;
55         private final QAction starAction;
56         private final QAction unstarAction;
57         private final QAction excludeNoteAction;
58         private final NeverNote parent;
59         private final QMenu menu;
60         private HashMap<String, Integer> mergedHistory;                         // マージされた操作履歴
61         private final SyncRunner syncRunner;
62         private final ENRelatedNotesRunner enRelatedNotesRunner;
63         private final QThread enRelatedNotesThread;
64         private final HashMap<String, List<String>> enRelatedNotesCache;        // Evernote関連ノートのキャッシュ<guid, 関連ノートリスト>
65         private String guid;
66         private int allPointSum;
67
68         public RensoNoteList(DatabaseConnection c, NeverNote p, SyncRunner syncRunner, ApplicationLogger logger) {
69                 this.logger = logger;
70                 this.logger.log(this.logger.HIGH, "Setting up rensoNoteList");
71                 allPointSum = 0;
72
73                 this.conn = c;
74                 this.parent = p;
75                 this.syncRunner = syncRunner;
76                 
77                 this.guid = new String();
78                 mergedHistory = new HashMap<String, Integer>();
79                 enRelatedNotesCache = new HashMap<String, List<String>>();
80                 this.enRelatedNotesRunner = new ENRelatedNotesRunner(this.syncRunner, this.logger);
81                 this.enRelatedNotesRunner.enRelatedNotesSignal.getENRelatedNotesFinished.connect(this, "enRelatedNotesComplete()");
82                 this.enRelatedNotesRunner.limitSignal.rateLimitReached.connect(parent, "informRateLimit(Integer)");
83                 this.enRelatedNotesThread = new QThread(enRelatedNotesRunner, "ENRelatedNotes Thread");
84                 this.getEnRelatedNotesThread().start();
85                 
86                 rensoNoteListItems = new HashMap<QListWidgetItem, String>();
87                 rensoNoteListTrueItems = new ArrayList<RensoNoteListItem>();
88                 
89                 this.itemPressed.connect(this, "rensoNoteItemPressed(QListWidgetItem)");
90                 
91                 // コンテキストメニュー作成
92                 menu = new QMenu(this);
93                 // 新しいタブで開くアクション生成
94                 openNewTabAction = new QAction(tr("Open in New Tab"), this);
95                 openNewTabAction.setToolTip(tr("Open this note in new tab"));
96                 openNewTabAction.triggered.connect(parent, "openNewTabFromRNL()");
97                 // スターをつけるアクション生成
98                 starAction = new QAction(tr("Add Star"), this);
99                 starAction.setToolTip(tr("Add Star to this item"));
100                 starAction.triggered.connect(parent, "starNote()");
101                 // スターを外すアクション生成
102                 unstarAction = new QAction(tr("Remove Star"), this);
103                 unstarAction.setToolTip(tr("Remove Star from this item"));
104                 unstarAction.triggered.connect(parent, "unstarNote()");
105                 // このノートを除外するアクション生成
106                 excludeNoteAction = new QAction(tr("Exclude"), this);
107                 excludeNoteAction.setToolTip(tr("Exclude this note from RensoNoteList"));
108                 excludeNoteAction.triggered.connect(parent, "excludeNote()");
109                 // コンテキストメニューに登録
110                 menu.addAction(openNewTabAction);
111                 menu.addAction(excludeNoteAction);
112                 menu.aboutToHide.connect(this, "contextMenuHidden()");
113                 
114                 this.logger.log(this.logger.HIGH, "rensoNoteList setup complete");
115         }
116         
117         // オーバーロード
118         // 現在開いているノートの連想ノートリストをリフレッシュ
119         public void refreshRensoNoteList() {
120                 refreshRensoNoteList(guid);
121         }
122
123         // 連想ノートリストをリフレッシュ
124         public void refreshRensoNoteList(String guid) {
125                 logger.log(logger.HIGH, "Entering RensoNoteList.refreshRensoNoteList guid = " + guid);
126
127                 this.clear();
128                 rensoNoteListItems.clear();
129                 rensoNoteListTrueItems.clear();
130                 mergedHistory = new HashMap<String, Integer>();
131
132                 if (!this.isEnabled()) {
133                         return;
134                 }
135                 if (guid == null || guid.equals("")) {
136                         return;
137                 }
138                 
139                 this.guid = guid;
140                 // すでにEvernote関連ノートがキャッシュされているか確認
141                 boolean isCached;
142                 isCached = enRelatedNotesCache.containsKey(guid);
143                 if (!isCached) {        // キャッシュ無し
144                         // Evernoteの関連ノートを別スレッドで取得させる
145                         enRelatedNotesRunner.addGuid(guid);
146                 } else {                        // キャッシュ有り
147                         List<String> relatedNoteGuids = enRelatedNotesCache.get(guid);
148                         addENRelatedNotes(relatedNoteGuids);
149                 }
150                 
151                 calculateHistory(guid);
152                 repaintRensoNoteList(false);
153
154                 logger.log(logger.HIGH, "Leaving RensoNoteList.refreshRensoNoteList");
155         }
156         
157         // 操作履歴をデータベースから取得してノートごとの関連度を算出、その後mergedHistoryに追加
158         private void calculateHistory(String guid) {
159                 logger.log(logger.EXTREME, "Entering RensoNoteList.calculateHistory guid = " + guid);
160                 
161                 // browseHistory<guid, 回数(ポイント)>
162                 HashMap<String, Integer> browseHistory = conn.getHistoryTable().getBehaviorHistory("browse", guid);
163                 addWeight(browseHistory, Global.getBrowseWeight());
164                 mergedHistory = mergeHistory(browseHistory, mergedHistory);
165                 
166                 // copy&pasteHistory<guid, 回数(ポイント)>
167                 HashMap<String, Integer> copyAndPasteHistory = conn.getHistoryTable().getBehaviorHistory("copy & paste", guid);
168                 addWeight(copyAndPasteHistory, Global.getCopyPasteWeight());
169                 mergedHistory = mergeHistory(copyAndPasteHistory, mergedHistory);
170                 
171                 // addNewNoteHistory<guid, 回数(ポイント)>
172                 HashMap<String, Integer> addNewNoteHistory = conn.getHistoryTable().getBehaviorHistory("addNewNote", guid);
173                 addWeight(addNewNoteHistory, Global.getAddNewNoteWeight());
174                 mergedHistory = mergeHistory(addNewNoteHistory, mergedHistory);
175                 
176                 // rensoItemClickHistory<guid, 回数(ポイント)>
177                 HashMap<String, Integer> rensoItemClickHistory = conn.getHistoryTable().getBehaviorHistory("rensoItemClick", guid);
178                 addWeight(rensoItemClickHistory, Global.getRensoItemClickWeight());
179                 mergedHistory = mergeHistory(rensoItemClickHistory, mergedHistory);
180                 
181                 // sameTagHistory<guid, 回数(ポイント)>
182                 HashMap<String, Integer> sameTagHistory = conn.getHistoryTable().getBehaviorHistory("sameTag", guid);
183                 addWeight(sameTagHistory, Global.getSameTagWeight());
184                 mergedHistory = mergeHistory(sameTagHistory, mergedHistory);
185                 
186                 // sameNotebookNoteHistory<guid, 回数(ポイント)>
187                 HashMap<String, Integer> sameNotebookHistory = conn.getHistoryTable().getBehaviorHistory("sameNotebook", guid);
188                 addWeight(sameNotebookHistory, Global.getSameNotebookWeight());
189                 mergedHistory = mergeHistory(sameNotebookHistory, mergedHistory);
190                 logger.log(logger.EXTREME, "Leaving RensoNoteList.calculateHistory");
191         }
192         
193         // 操作回数に重み付けする
194         private void addWeight(HashMap<String, Integer> history, int weight){
195                 logger.log(logger.EXTREME, "Entering RensoNoteList.addWeight");
196                 
197                 Set<String> keySet = history.keySet();
198                 Iterator<String> hist_iterator = keySet.iterator();
199                 while(hist_iterator.hasNext()){
200                         String key = hist_iterator.next();
201                         history.put(key, history.get(key) * weight);
202                 }
203                 
204                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addWeight");
205         }
206         
207         // 連想ノートリストを再描画
208         private void repaintRensoNoteList(boolean needClear) {
209                 logger.log(logger.EXTREME, "Entering RensoNoteList.repaintRensoNoteList");
210                 
211                 if (needClear) {
212                         this.clear();
213                         rensoNoteListItems.clear();
214                         rensoNoteListTrueItems.clear();
215                 }
216                 
217                 if (!this.isEnabled()) {
218                         return;
219                 }
220                 
221                 // すべての関連ポイントの合計を取得(関連度のパーセント算出に利用)
222                 allPointSum = 0;
223                 for (int p : mergedHistory.values()) {
224                         allPointSum += p;
225                 }
226                 
227                 addRensoNoteList(mergedHistory);
228                 
229                 logger.log(logger.EXTREME, "Leaving RensoNoteList.repaintRensoNoteList");
230         }
231         
232         // 引数1と引数2をマージしたハッシュマップを返す
233         private HashMap<String, Integer> mergeHistory(HashMap<String, Integer> History1, HashMap<String, Integer> History2){
234                 logger.log(logger.EXTREME, "Entering RensoNoteList.mergeHistory");
235                 
236                 HashMap<String, Integer> mergedHistory = new HashMap<String, Integer>();
237                 
238                 mergedHistory.putAll(History1);
239                 
240                 Set<String> keySet = History2.keySet();
241                 Iterator<String> hist2_iterator = keySet.iterator();
242                 while(hist2_iterator.hasNext()){
243                         String key = hist2_iterator.next();
244                         if(mergedHistory.containsKey(key)){
245                                 mergedHistory.put(key, mergedHistory.get(key) + History2.get(key));
246                         }else {
247                                 mergedHistory.put(key, History2.get(key));
248                         }
249                 }
250                 
251                 logger.log(logger.EXTREME, "Leaving RensoNoteList.mergeHistory");
252                 return mergedHistory;
253         }
254         
255         // 連想ノートリストにハッシュマップのデータを追加
256         private void addRensoNoteList(HashMap<String, Integer> History){
257                 logger.log(logger.EXTREME, "Entering RensoNoteList.addRensoNoteList");
258                 
259                 String currentNoteGuid = new String(parent.getCurrentNoteGuid());
260                 
261                 // スター付きノートとスター無しノートを分ける
262                 HashMap<String, Integer> staredNotes = new HashMap<String, Integer>();  // スター付きノートのマップ
263                 HashMap<String, Integer> normalNotes = new HashMap<String, Integer>();  // スター無しノートのマップ
264                 for (String nextGuid: History.keySet()) {
265                         int relationPoint = History.get(nextGuid);
266                         boolean isStared = conn.getStaredTable().existNote(currentNoteGuid, nextGuid);
267                         if (isStared) {
268                                 staredNotes.put(nextGuid, relationPoint);
269                         } else {
270                                 normalNotes.put(nextGuid, relationPoint);
271                         }
272                 }
273                 
274                 // 連想ノートリストアイテムの最大表示数まで繰り返す
275                 for (int i = 0; i < Global.getRensoListItemMaximum(); i++) {
276                         // スター付きノートがあれば先に処理する
277                         HashMap<String, Integer> tmpMap = new HashMap<String, Integer>();
278                         if (!staredNotes.isEmpty()) {
279                                 tmpMap = staredNotes;
280                         }else if (!normalNotes.isEmpty()) {
281                                 tmpMap = normalNotes;
282                         }
283                         
284                         // 操作回数が多い順に取り出して連想ノートリストに追加する
285                         if (!tmpMap.isEmpty()) {
286                                 int maxNum = -1;
287                                 String maxGuid = new String();
288                                 
289                                 for (String nextGuid: tmpMap.keySet()) {
290                                         int relationPoint = tmpMap.get(nextGuid);
291                                         
292                                         // 最大ノート探索する
293                                         if (relationPoint > maxNum) {
294                                                 maxNum = relationPoint;
295                                                 maxGuid = nextGuid;
296                                         }
297                                 }
298                                 
299                                 // 次の最大値探索で邪魔なので最大値をHashMapから削除
300                                 tmpMap.remove(maxGuid);
301         
302                                 // 関連度最大のノートがアクティブか確認
303                                 Note maxNote = conn.getNoteTable().getNote(maxGuid, true, false, false, false, true);
304                                 boolean isNoteActive = false;
305                                 if(maxNote != null) {
306                                         isNoteActive = maxNote.isActive();
307                                 }
308                                 
309                                 // 存在していて、かつ関連度0でなければノート情報を取得して連想ノートリストに追加
310                                 if (isNoteActive && maxNum > 0) {
311                                         // スター付きか確認
312                                         boolean isStared;
313                                         isStared = conn.getStaredTable().existNote(currentNoteGuid, maxGuid);
314                                         
315                                         QListWidgetItem item = new QListWidgetItem();
316                                         RensoNoteListItem myItem = new RensoNoteListItem(maxNote, maxNum, isStared, allPointSum, conn, this);
317                                         item.setSizeHint(new QSize(0, 90));
318                                         this.addItem(item);
319                                         this.setItemWidget(item, myItem);
320                                         rensoNoteListItems.put(item, maxGuid);
321                                         rensoNoteListTrueItems.add(myItem);
322                                 } else {
323                                         break;
324                                 }
325                         }
326                 }
327                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addRensoNoteList");
328         }
329
330         // リストのアイテムから対象ノートのguidを取得
331         public String getNoteGuid(QListWidgetItem item) {
332                 return rensoNoteListItems.get(item);
333         }
334         
335         // 関連ノートリストの右クリックメニュー
336         @Override
337         public void contextMenuEvent(QContextMenuEvent event){
338                 logger.log(logger.EXTREME, "Entering RensoNoteList.contextMenuEvent");
339                 
340                 if (rensoNotePressedItemGuid == null || rensoNotePressedItemGuid.equals("")) {
341                         return;
342                 }
343                 
344                 // STAR, UNSTARがあれば、一度消す
345                 List<QAction> menuActions = new ArrayList<QAction>(menu.actions());
346                 if (menuActions.contains(starAction)) {
347                         menu.removeAction(starAction);
348                 }
349                 if (menuActions.contains(unstarAction)) {
350                         menu.removeAction(unstarAction);
351                 }
352                 
353                 // 対象アイテムがスター付きなら「UNSTAR」、スター無しなら「STAR」を追加
354                 String currentNoteGuid = parent.getCurrentNoteGuid();
355                 boolean isExist = conn.getStaredTable().existNote(currentNoteGuid, rensoNotePressedItemGuid);
356                 if (isExist) {
357                         menu.insertAction(excludeNoteAction, unstarAction);
358                 } else {
359                         menu.insertAction(excludeNoteAction, starAction);
360                 }
361                 
362                 // コンテキストメニューを表示
363                 menu.exec(event.globalPos());
364                 
365                 rensoNotePressedItemGuid = null;
366                 
367                 logger.log(logger.EXTREME, "Leaving RensoNoteList.contextMenuEvent");
368         }
369         
370         // コンテキストメニューが表示されているかどうか
371         public boolean isContextMenuVisible() {
372                 return menu.isVisible();
373         }
374         
375         // コンテキストメニューが閉じられた時
376         @SuppressWarnings("unused")
377         private void contextMenuHidden() {
378                 for (int i = 0; i < rensoNoteListTrueItems.size(); i++) {
379                         RensoNoteListItem item = rensoNoteListTrueItems.get(i);
380                         item.setDefaultBackground();
381                 }
382         }
383         
384         // ユーザが連想ノートリストのアイテムを選択した時の処理
385         @SuppressWarnings("unused")
386         private void rensoNoteItemPressed(QListWidgetItem current) {
387                 logger.log(logger.HIGH, "Entering RensoNoteList.rensoNoteItemPressed");
388                 
389                 rensoNotePressedItemGuid = null;
390                 // 右クリックだったときの処理
391                 if (QApplication.mouseButtons().isSet(MouseButton.RightButton)) {
392                         rensoNotePressedItemGuid = getNoteGuid(current);
393                 }
394                 
395                 logger.log(logger.HIGH, "Leaving RensoNoteList.rensoNoteItemPressed");
396         }
397         
398         // Evernoteの関連ノートの取得が完了
399         @SuppressWarnings("unused")
400         private void enRelatedNotesComplete() {
401                 logger.log(logger.HIGH, "Entering RensoNoteList.enRelatedNotesComplete");
402                 
403                 Pair<String, List<String>> enRelatedNoteGuidPair = enRelatedNotesRunner.getENRelatedNoteGuids();        // <元ノートguid, 関連ノートguidリスト>
404                 
405                 if (enRelatedNoteGuidPair == null) {
406                         return;
407                 }
408                 
409                 String sourceGuid = enRelatedNoteGuidPair.getFirst();
410                 List<String> enRelatedNoteGuids = enRelatedNoteGuidPair.getSecond();
411                 
412                 
413                 if (sourceGuid != null && !sourceGuid.equals("") && enRelatedNoteGuids != null) {       // Evernote関連ノートがnullでなければ
414                         // まずキャッシュに追加
415                         enRelatedNotesCache.put(sourceGuid, enRelatedNoteGuids);
416                         
417                         if (!enRelatedNoteGuids.isEmpty()) {    // Evernote関連ノートが存在していて
418                                 if (sourceGuid.equals(this.guid)) {     // 取得したデータが今開いているノートの関連ノートなら
419                                         // mergedHistoryにEvernote関連ノートを追加してから再描画
420                                         addENRelatedNotes(enRelatedNoteGuids);
421                                         repaintRensoNoteList(true);
422                                 }
423                         }
424                 }
425                 
426                 logger.log(logger.HIGH, "Leaving RensoNoteList.enRelatedNotesComplete");
427         }
428         
429         // Evernote関連ノートの関連度情報をmergedHistoryに追加
430         private void addENRelatedNotes(List<String> relatedNoteGuids) {
431                 logger.log(logger.EXTREME, "Entering RensoNoteList.addENRelatedNotes");
432                 
433                 // Evernote関連ノート<guid, 関連ポイント>
434                 HashMap<String, Integer> enRelatedNotes = new HashMap<String, Integer>();
435                 
436                 for (String relatedGuid : relatedNoteGuids) {
437                         enRelatedNotes.put(relatedGuid, Global.getENRelatedNotesWeight());
438                 }
439                 
440                 mergedHistory = mergeHistory(enRelatedNotes, mergedHistory);
441                 
442                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addENRelatedNotes");
443         }
444         
445         // Evernoteの関連ノート取得スレッドを終了させる
446         public boolean stopThread() {
447                 logger.log(logger.HIGH, "Entering RensoNoteList.stopThread");
448                 
449                 if (enRelatedNotesRunner.addStop()) {
450                         logger.log(logger.HIGH, "RensoNoteList.stopThread succeeded");
451                         return true;
452                 }
453                 logger.log(logger.HIGH, "RensoNoteList.stopThread failed");
454                 return false;
455         }
456
457         public QThread getEnRelatedNotesThread() {
458                 return enRelatedNotesThread;
459         }
460         
461         public String getGuid() {
462                 return guid;
463         }
464 }