blob: 40aa29051a6fcc4e5d953f4347f977b89f818f61 [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];
179 QVariant photo = GlobalInstances::pixmapManipulator().callPhoto(call, QSize(50,50));
180 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(photo))];
181
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400182 NSTextField* details = [result viewWithTag:DETAILS_TAG];
183 [details setStringValue:qIdx.data((int)Call::Role::FormattedDate).toString().toNSString()];
184 }
185
186 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
187 [displayName setStringValue:qIdx.data(Qt::DisplayRole).toString().toNSString()];
188
189 return result;
190}
191
192- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
193{
194 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
195 if(!qIdx.parent().isValid()) {
196 return 35.0;
197 } else {
198 return 48.0;
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500199 }
200}
201
Alexandre Lision4e280d62015-09-09 15:56:30 -0400202/* View Based OutlineView: See the delegate method -tableView:rowViewForRow: in NSTableView.
203 */
204- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500205{
Alexandre Lision4e280d62015-09-09 15:56:30 -0400206 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
207
208 HoverTableRowView* result = [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
209 if(!qIdx.parent().isValid()) {
210 [result setHighlightable:NO];
211 } else
212 [result setHighlightable:YES];
213
214 return result;
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500215}
216
Alexandre Lision2db8f472015-07-22 15:05:46 -0400217#pragma mark - ContextMenuDelegate
218
Alexandre Lision12946a72016-03-08 13:39:34 -0500219- (NSMenu*) contextualMenuForIndex:(NSTreeNode*) item
Alexandre Lision2db8f472015-07-22 15:05:46 -0400220{
Alexandre Lision12946a72016-03-08 13:39:34 -0500221
222 QModelIndex qIdx = [treeController toQIdx:item];
223 if (!qIdx.isValid()) {
224 return nil;
225 }
226
227 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
228 if (qIdx.parent().isValid() && var.isValid()) {
229 if (auto call = var.value<Call *>()) {
230 auto contactmethod = call->peerContactMethod();
231 if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
232 NSMenu *theMenu = [[NSMenu alloc]
233 initWithTitle:@""];
234 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
235 action:@selector(addToContact)
236 keyEquivalent:@"a"
237 atIndex:0];
238 return theMenu;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400239 }
240 }
241 }
242 return nil;
243}
244
245- (void) addToContact
246{
247 ContactMethod* contactmethod = nullptr;
248 if([[treeController selectedNodes] count] > 0) {
249 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
250 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
251 if (qIdx.parent().isValid() && var.isValid()) {
252 if (auto call = var.value<Call *>()) {
253 contactmethod = call->peerContactMethod();
254 }
255 }
256 }
257
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400258 if (addToContactPopover != nullptr) {
259 [addToContactPopover performClose:self];
260 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400261 } else if (contactmethod) {
Alexandre Lisionae840f22015-08-04 15:02:16 -0400262 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400263 [editorVC setMethodToLink:contactmethod];
264 [editorVC setContactLinkedDelegate:self];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400265 addToContactPopover = [[NSPopover alloc] init];
266 [addToContactPopover setContentSize:editorVC.view.frame.size];
267 [addToContactPopover setContentViewController:editorVC];
268 [addToContactPopover setAnimates:YES];
269 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
270 [addToContactPopover setDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400271
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400272 [addToContactPopover showRelativeToRect:[historyView frameOfOutlineCellAtRow:[historyView selectedRow]] ofView:historyView preferredEdge:NSMaxXEdge];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400273 }
274}
275
276#pragma mark - NSPopOverDelegate
277
278- (void)popoverDidClose:(NSNotification *)notification
279{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400280 if (addToContactPopover != nullptr) {
281 [addToContactPopover performClose:self];
282 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400283 }
284}
285
286#pragma mark - ContactLinkedDelegate
287
288- (void)contactLinked
289{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400290 if (addToContactPopover != nullptr) {
291 [addToContactPopover performClose:self];
292 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400293 }
294}
295
296#pragma mark - KeyboardShortcutDelegate
297
298- (void) onAddShortcut
299{
300 if([[treeController selectedNodes] count] > 0) {
301 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
302 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
303 if (qIdx.parent().isValid() && var.isValid()) {
304 if (auto call = var.value<Call *>()) {
305 auto contactmethod = call->peerContactMethod();
306 if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
307 [self addToContact];
308 }
309 }
310 }
311 }
312}
313
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500314@end