blob: 5fae480e180fa6c71d76e4a37e109d1a78657b73 [file] [log] [blame]
Alexandre Lision8521baa2015-03-13 11:08:00 -04001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lision8521baa2015-03-13 11:08:00 -04003 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Alexandre Lision8521baa2015-03-13 11:08:00 -040018 */
Alexandre Lision4ba18022015-04-23 12:17:40 -040019#import "HistoryVC.h"
Alexandre Lision5855b6a2015-02-03 11:31:05 -050020
Alexandre Lision34e738e2015-09-24 14:13:23 -040021//Qt
Alexandre Lision7837e4f2015-03-23 09:58:12 -040022#import <QSortFilterProxyModel>
Alexandre Lisiond5229f32015-11-16 11:17:41 -050023#import <QtMacExtras/qmacfunctions.h>
24#import <QPixmap>
Alexandre Lision34e738e2015-09-24 14:13:23 -040025
26//LRC
27#import <categorizedhistorymodel.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050028#import <callmodel.h>
29#import <call.h>
Alexandre Lision2db8f472015-07-22 15:05:46 -040030#import <person.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050031#import <contactmethod.h>
Alexandre Lisiond5229f32015-11-16 11:17:41 -050032#import <globalinstances.h>
Alexandre Lision5855b6a2015-02-03 11:31:05 -050033
Alexandre Lisionc5148052015-03-04 15:10:35 -050034#import "QNSTreeController.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040035#import "PersonLinkerVC.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040036#import "views/HoverTableRowView.h"
Alexandre Lisiond5229f32015-11-16 11:17:41 -050037#import "delegates/ImageManipulationDelegate.h"
Alexandre Lisionc5148052015-03-04 15:10:35 -050038
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040039@interface HistoryVC() <NSPopoverDelegate, KeyboardShortcutDelegate, ContactLinkedDelegate> {
Alexandre Lision5855b6a2015-02-03 11:31:05 -050040
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040041 QNSTreeController *treeController;
42 IBOutlet RingOutlineView *historyView;
43 QSortFilterProxyModel *historyProxyModel;
44 NSPopover* addToContactPopover;
45}
Alexandre Lision2db8f472015-07-22 15:05:46 -040046
Alexandre Lisionc5148052015-03-04 15:10:35 -050047@end
Alexandre Lision5855b6a2015-02-03 11:31:05 -050048
Alexandre Lision4ba18022015-04-23 12:17:40 -040049@implementation HistoryVC
Alexandre Lision5855b6a2015-02-03 11:31:05 -050050
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040051// Tags for Views
Alexandre Lisiond5229f32015-11-16 11:17:41 -050052NSInteger const DIRECTION_TAG = 100;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040053NSInteger const DISPLAYNAME_TAG = 200;
54NSInteger const DETAILS_TAG = 300;
Alexandre Lisiond5229f32015-11-16 11:17:41 -050055NSInteger const PHOTO_TAG = 400;
Alexandre Lision5855b6a2015-02-03 11:31:05 -050056
Alexandre Lision5855b6a2015-02-03 11:31:05 -050057- (void)awakeFromNib
58{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040059 NSLog(@"INIT HVC");
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040060 historyProxyModel = new QSortFilterProxyModel(&CategorizedHistoryModel::instance());
61 historyProxyModel->setSourceModel(&CategorizedHistoryModel::instance());
Alexandre Lision7837e4f2015-03-23 09:58:12 -040062 historyProxyModel->setSortRole(static_cast<int>(Call::Role::Date));
63 historyProxyModel->sort(0,Qt::DescendingOrder);
64 treeController = [[QNSTreeController alloc] initWithQModel:historyProxyModel];
Alexandre Lision5855b6a2015-02-03 11:31:05 -050065
66 [treeController setAvoidsEmptySelection:NO];
67 [treeController setChildrenKeyPath:@"children"];
68
Alexandre Lisionc5148052015-03-04 15:10:35 -050069 [historyView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
70 [historyView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
71 [historyView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
72 [historyView setTarget:self];
Alexandre Lision3b0bd332015-03-15 18:43:07 -040073 [historyView setDoubleAction:@selector(placeHistoryCall:)];
Alexandre Lision2db8f472015-07-22 15:05:46 -040074 [historyView setContextMenuDelegate:self];
75 [historyView setShortcutsDelegate:self];
Alexandre Lision5855b6a2015-02-03 11:31:05 -050076
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040077 QObject::connect(&CallModel::instance(),
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040078 &CategorizedHistoryModel::dataChanged,
79 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
80 [historyView reloadDataForRowIndexes:
81 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(topLeft.row(), bottomRight.row() + 1)]
82 columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, historyView.tableColumns.count)]];
83 });
Alexandre Lision5855b6a2015-02-03 11:31:05 -050084}
85
Alexandre Lision54b0fae2015-08-04 15:19:01 -040086- (void) dealloc
87{
88 delete historyProxyModel;
89}
90
Alexandre Lision3b0bd332015-03-15 18:43:07 -040091- (void)placeHistoryCall:(id)sender
Alexandre Lisionc5148052015-03-04 15:10:35 -050092{
93 if([[treeController selectedNodes] count] > 0) {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040094 auto item = [treeController selectedNodes][0];
95 QModelIndex qIdx = [treeController toQIdx:item];
Alexandre Lision2db8f472015-07-22 15:05:46 -040096 if (!qIdx.parent().isValid()) {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040097 if ([historyView isItemExpanded:item]) {
98 [[historyView animator] collapseItem:item];
99 } else
100 [[historyView animator] expandItem:item];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400101 return;
102 }
Alexandre Lision7837e4f2015-03-23 09:58:12 -0400103 QVariant var = historyProxyModel->data(qIdx, (int)Call::Role::ContactMethod);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500104 ContactMethod* m = qvariant_cast<ContactMethod*>(var);
105 if(m){
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500106 auto c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400107 c->setPeerContactMethod(m);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500108 c << Call::Action::ACCEPT;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500109 CallModel::instance().selectCall(c);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500110 }
111 }
112}
113
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500114#pragma mark - NSOutlineViewDelegate methods
115
116// -------------------------------------------------------------------------------
117// shouldSelectItem:item
118// -------------------------------------------------------------------------------
119- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
120{
121 return YES;
122}
123
124// -------------------------------------------------------------------------------
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500125// shouldEditTableColumn:tableColumn:item
126//
127// Decide to allow the edit of the given outline view "item".
128// -------------------------------------------------------------------------------
129- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
130{
131 return NO;
132}
133
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400134- (NSImage*) image:(NSImage*) img withTintedWithColor:(NSColor *)tint
135{
136 if (tint) {
137 [img lockFocus];
138 [tint set];
139 NSRect imageRect = {NSZeroPoint, [img size]};
140 NSRectFillUsingOperation(imageRect, NSCompositeSourceAtop);
141 [img unlockFocus];
142 }
143 return img;
144}
145
146/* View Based OutlineView: See the delegate method -tableView:viewForTableColumn:row: in NSTableView.
147 */
148- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500149{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500150 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500151
Alexandre Lision4e280d62015-09-09 15:56:30 -0400152 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400153 if(!qIdx.parent().isValid()) {
154 result = [outlineView makeViewWithIdentifier:@"CategoryCell" owner:outlineView];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400155
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400156 } else {
157 result = [outlineView makeViewWithIdentifier:@"HistoryCell" owner:outlineView];
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500158 NSImageView* directionView = [result viewWithTag:DIRECTION_TAG];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400159
160 if (qvariant_cast<Call::Direction>(qIdx.data((int)Call::Role::Direction)) == Call::Direction::INCOMING) {
161 if (qvariant_cast<Boolean>(qIdx.data((int) Call::Role::Missed))) {
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500162 [directionView setImage:[self image:[NSImage imageNamed:@"ic_call_missed"] withTintedWithColor:[NSColor redColor]]];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400163 } else {
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500164 [directionView setImage:[self image:[NSImage imageNamed:@"ic_call_received"]
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400165 withTintedWithColor:[NSColor colorWithCalibratedRed:116/255.0 green:179/255.0 blue:93/255.0 alpha:1.0]]];
166 }
167 } else {
168 if (qvariant_cast<Boolean>(qIdx.data((int) Call::Role::Missed))) {
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500169 [directionView setImage:[self image:[NSImage imageNamed:@"ic_call_missed"] withTintedWithColor:[NSColor redColor]]];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400170 } else {
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500171 [directionView setImage:[self image:[NSImage imageNamed:@"ic_call_made"]
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400172 withTintedWithColor:[NSColor colorWithCalibratedRed:116/255.0 green:179/255.0 blue:93/255.0 alpha:1.0]]];
173 }
174 }
175
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500176 auto call = qvariant_cast<Call*>(qIdx.data((int)Call::Role::Object));
177
178 NSImageView* photoView = [result viewWithTag:PHOTO_TAG];
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400179 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(qIdx.data(Qt::DecorationRole)))];
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500180
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400181 NSTextField* details = [result viewWithTag:DETAILS_TAG];
182 [details setStringValue:qIdx.data((int)Call::Role::FormattedDate).toString().toNSString()];
183 }
184
185 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
186 [displayName setStringValue:qIdx.data(Qt::DisplayRole).toString().toNSString()];
187
188 return result;
189}
190
191- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
192{
193 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
194 if(!qIdx.parent().isValid()) {
195 return 35.0;
196 } else {
197 return 48.0;
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500198 }
199}
200
Alexandre Lision4e280d62015-09-09 15:56:30 -0400201/* View Based OutlineView: See the delegate method -tableView:rowViewForRow: in NSTableView.
202 */
203- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500204{
Alexandre Lision4e280d62015-09-09 15:56:30 -0400205 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
206
207 HoverTableRowView* result = [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
208 if(!qIdx.parent().isValid()) {
209 [result setHighlightable:NO];
210 } else
211 [result setHighlightable:YES];
212
213 return result;
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500214}
215
Alexandre Lision2db8f472015-07-22 15:05:46 -0400216#pragma mark - ContextMenuDelegate
217
Alexandre Lision12946a72016-03-08 13:39:34 -0500218- (NSMenu*) contextualMenuForIndex:(NSTreeNode*) item
Alexandre Lision2db8f472015-07-22 15:05:46 -0400219{
Alexandre Lision12946a72016-03-08 13:39:34 -0500220
221 QModelIndex qIdx = [treeController toQIdx:item];
222 if (!qIdx.isValid()) {
223 return nil;
224 }
225
226 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
227 if (qIdx.parent().isValid() && var.isValid()) {
228 if (auto call = var.value<Call *>()) {
229 auto contactmethod = call->peerContactMethod();
230 if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
231 NSMenu *theMenu = [[NSMenu alloc]
232 initWithTitle:@""];
233 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
234 action:@selector(addToContact)
235 keyEquivalent:@"a"
236 atIndex:0];
237 return theMenu;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400238 }
239 }
240 }
241 return nil;
242}
243
244- (void) addToContact
245{
246 ContactMethod* contactmethod = nullptr;
247 if([[treeController selectedNodes] count] > 0) {
248 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
249 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
250 if (qIdx.parent().isValid() && var.isValid()) {
251 if (auto call = var.value<Call *>()) {
252 contactmethod = call->peerContactMethod();
253 }
254 }
255 }
256
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400257 if (addToContactPopover != nullptr) {
258 [addToContactPopover performClose:self];
259 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400260 } else if (contactmethod) {
Alexandre Lisionae840f22015-08-04 15:02:16 -0400261 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400262 [editorVC setMethodToLink:contactmethod];
263 [editorVC setContactLinkedDelegate:self];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400264 addToContactPopover = [[NSPopover alloc] init];
265 [addToContactPopover setContentSize:editorVC.view.frame.size];
266 [addToContactPopover setContentViewController:editorVC];
267 [addToContactPopover setAnimates:YES];
268 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
269 [addToContactPopover setDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400270
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400271 [addToContactPopover showRelativeToRect:[historyView frameOfOutlineCellAtRow:[historyView selectedRow]] ofView:historyView preferredEdge:NSMaxXEdge];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400272 }
273}
274
275#pragma mark - NSPopOverDelegate
276
277- (void)popoverDidClose:(NSNotification *)notification
278{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400279 if (addToContactPopover != nullptr) {
280 [addToContactPopover performClose:self];
281 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400282 }
283}
284
285#pragma mark - ContactLinkedDelegate
286
287- (void)contactLinked
288{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400289 if (addToContactPopover != nullptr) {
290 [addToContactPopover performClose:self];
291 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400292 }
293}
294
295#pragma mark - KeyboardShortcutDelegate
296
297- (void) onAddShortcut
298{
299 if([[treeController selectedNodes] count] > 0) {
300 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
301 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
302 if (qIdx.parent().isValid() && var.isValid()) {
303 if (auto call = var.value<Call *>()) {
304 auto contactmethod = call->peerContactMethod();
305 if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
306 [self addToContact];
307 }
308 }
309 }
310 }
311}
312
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500313@end