blob: b04a39fcb92ec48e6d5a2d4a39a3f2a8ccd50dca [file] [log] [blame]
Alexandre Lision4dfcafc2015-08-20 12:43:23 -04001/*
2 * Copyright (C) 2015 Savoir-faire Linux Inc.
3 * 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.
18 */
19
20#import "SmartViewVC.h"
21
22//Qt
23#import <QtMacExtras/qmacfunctions.h>
24#import <QPixmap>
25#import <QIdentityProxyModel>
26#import <QItemSelectionModel>
27
28//LRC
29#import <recentmodel.h>
30#import <callmodel.h>
31#import <call.h>
Alexandre Lisiond14bda32015-10-13 11:34:29 -040032#import <itemdataroles.h>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040033#import <person.h>
34#import <contactmethod.h>
35#import <globalinstances.h>
36
37#import "QNSTreeController.h"
38#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040039#import "views/HoverTableRowView.h"
40#import "views/ContextualTableCellView.h"
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040041
42@interface SmartViewVC () <NSOutlineViewDelegate> {
43 BOOL isShowingContacts;
44 QNSTreeController *treeController;
45
46 //UI elements
47 __unsafe_unretained IBOutlet NSOutlineView *smartView;
48 __unsafe_unretained IBOutlet NSSearchField *searchField;
49 __unsafe_unretained IBOutlet NSButton *showContactsButton;
50 __unsafe_unretained IBOutlet NSButton *showHistoryButton;
51 __unsafe_unretained IBOutlet NSTabView *tabbar;
52}
53
54@end
55
56@implementation SmartViewVC
57
58// Tags for views
59NSInteger const IMAGE_TAG = 100;
60NSInteger const DISPLAYNAME_TAG = 200;
61NSInteger const DETAILS_TAG = 300;
62NSInteger const CALL_BUTTON_TAG = 400;
63NSInteger const TXT_BUTTON_TAG = 500;
64
65- (void)awakeFromNib
66{
67 NSLog(@"INIT SmartView VC");
68
69 isShowingContacts = false;
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040070 treeController = [[QNSTreeController alloc] initWithQModel:RecentModel::instance().peopleProxy()];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040071
72 [treeController setAvoidsEmptySelection:NO];
73 [treeController setChildrenKeyPath:@"children"];
74
75 [smartView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
76 [smartView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
77 [smartView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
78 [smartView setTarget:self];
79 [smartView setDoubleAction:@selector(placeCall:)];
80
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040081 QObject::connect(RecentModel::instance().peopleProxy(),
Alexandre Lisionee098462015-10-22 17:22:50 -040082 &QAbstractItemModel::dataChanged,
83 [self](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
84 for(int row = topLeft.row() ; row <= bottomRight.row() ; ++row)
85 {
86 [smartView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row]
87 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
88 }
89 });
90
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040091 [self.view setWantsLayer:YES];
92 [self.view setLayer:[CALayer layer]];
93 [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];
94
95 [searchField setWantsLayer:YES];
96 [searchField setLayer:[CALayer layer]];
97 [searchField.layer setBackgroundColor:[NSColor colorWithCalibratedRed:0.949 green:0.949 blue:0.949 alpha:0.9].CGColor];
98}
99
100- (void)placeCall:(id)sender
101{
102 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
103 ContactMethod* m = nil;
104
105 // Double click on an ongoing call
106 if (qIdx.parent().isValid()) {
107 return;
108 }
109
110 if([[treeController selectedNodes] count] > 0) {
111 QVariant var = qIdx.data((int)Call::Role::ContactMethod);
112 m = qvariant_cast<ContactMethod*>(var);
113 if (!m) {
114 // test if it is a person
115 QVariant var = qIdx.data((int)Person::Role::Object);
116 if (var.isValid()) {
117 Person *c = var.value<Person*>();
118 if (c->phoneNumbers().size() > 0) {
119 m = c->phoneNumbers().first();
120 }
121 }
122 }
123 }
124
125 // Before calling check if we properly extracted a contact method and that
126 // there is NOT already an ongoing call for this index (e.g: no children for this node)
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400127 if(m && !RecentModel::instance().peopleProxy()->index(0, 0, qIdx).isValid()){
128 Call* c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400129 c->setPeerContactMethod(m);
130 c << Call::Action::ACCEPT;
131
Alexandre Lision21666f32015-09-22 17:04:36 -0400132 [smartView deselectAll:nil];
133 [smartView selectRowIndexes:[[NSIndexSet alloc] initWithIndex:0] byExtendingSelection:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400134 }
135}
136
137- (IBAction)showHistory:(NSButton*)sender {
138 if (isShowingContacts) {
139 [showContactsButton setState:NO];
140 isShowingContacts = NO;
141 [tabbar selectTabViewItemAtIndex:1];
142 } else if ([sender state] == NSOffState) {
143 [tabbar selectTabViewItemAtIndex:0];
144 } else {
145 [tabbar selectTabViewItemAtIndex:1];
146 }
147}
148
149- (IBAction)showContacts:(NSButton*)sender {
150 if (isShowingContacts) {
151 [showContactsButton setState:NO];
152 [tabbar selectTabViewItemAtIndex:0];
153 } else {
154 [showHistoryButton setState:![sender state]];
155 [tabbar selectTabViewItemAtIndex:2];
156 }
157
158 isShowingContacts = [sender state];
159}
160
161#pragma mark - NSOutlineViewDelegate methods
162
163// -------------------------------------------------------------------------------
164// shouldSelectItem:item
165// -------------------------------------------------------------------------------
166- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
167{
168 return YES;
169}
170
171// -------------------------------------------------------------------------------
172// shouldEditTableColumn:tableColumn:item
173//
174// Decide to allow the edit of the given outline view "item".
175// -------------------------------------------------------------------------------
176- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
177{
178 return NO;
179}
180
181
182// -------------------------------------------------------------------------------
183// outlineViewSelectionDidChange:notification
184// -------------------------------------------------------------------------------
185- (void)outlineViewSelectionDidChange:(NSNotification *)notification
186{
187 if ([treeController selectedNodes].count <= 0) {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400188 CallModel::instance().selectionModel()->clearCurrentIndex();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400189 return;
190 }
191
192 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
193
194 // ask the tree controller for the current selection
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400195 if (auto selected = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(qIdx))) {
196 CallModel::instance().selectCall(selected);
197 } else if (auto selected = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(qIdx.parent()))){
198 CallModel::instance().selectCall(selected);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400199 } else {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400200 CallModel::instance().selectionModel()->clearCurrentIndex();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400201 }
202}
203
204/* View Based OutlineView: See the delegate method -tableView:viewForTableColumn:row: in NSTableView.
205 */
206- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
207{
208 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
209 NSTableCellView *result;
210 if (!qIdx.parent().isValid()) {
211 result = [outlineView makeViewWithIdentifier:@"MainCell" owner:outlineView];
212 NSTextField* details = [result viewWithTag:DETAILS_TAG];
213
Alexandre Lision4e280d62015-09-09 15:56:30 -0400214 [((ContextualTableCellView*) result) setContextualsControls:[NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]]];
215
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400216 if (auto call = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(qIdx))) {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400217 [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400218 [((ContextualTableCellView*) result) setActiveState:YES];
219 } else {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400220 [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400221 [((ContextualTableCellView*) result) setActiveState:NO];
222 }
223
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400224 } else {
225 result = [outlineView makeViewWithIdentifier:@"CallCell" owner:outlineView];
226 NSTextField* details = [result viewWithTag:DETAILS_TAG];
227
228 [details setStringValue:qIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
229 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400230
231 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
232 [displayName setStringValue:qIdx.data(Qt::DisplayRole).toString().toNSString()];
233 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
234 Person* p = qvariant_cast<Person*>(qIdx.data((int)Person::Role::Object));
Alexandre Lisionde0314b2015-09-02 15:45:21 -0400235 QVariant photo = GlobalInstances::pixmapManipulator().contactPhoto(p, QSize(40,40));
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400236 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(photo))];
237 return result;
238}
239
240- (IBAction)callClickedAtRow:(id)sender {
241 NSInteger row = [smartView rowForView:sender];
242 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
243 [self placeCall:nil];
244}
245
246- (IBAction)hangUpClickedAtRow:(id)sender {
247 NSInteger row = [smartView rowForView:sender];
248 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400249 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::REFUSE;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400250}
251
252/* View Based OutlineView: See the delegate method -tableView:rowViewForRow: in NSTableView.
Alexandre Lision4e280d62015-09-09 15:56:30 -0400253*/
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400254- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
255{
Alexandre Lision4e280d62015-09-09 15:56:30 -0400256 return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400257}
Alexandre Lision4e280d62015-09-09 15:56:30 -0400258
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400259
260/* View Based OutlineView: This delegate method can be used to know when a new 'rowView' has been added to the table. At this point, you can choose to add in extra views, or modify any properties on 'rowView'.
261 */
262- (void)outlineView:(NSOutlineView *)outlineView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
263{
264
265}
266
267/* View Based OutlineView: This delegate method can be used to know when 'rowView' has been removed from the table. The removed 'rowView' may be reused by the table so any additionally inserted views should be removed at this point. A 'row' parameter is included. 'row' will be '-1' for rows that are being deleted from the table and no longer have a valid row, otherwise it will be the valid row that is being removed due to it being moved off screen.
268 */
269- (void)outlineView:(NSOutlineView *)outlineView didRemoveRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
270{
271
272}
273
274- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
275{
276 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
277 return (((NSTreeNode*)item).indexPath.length == 1) ? 60.0 : 45.0;
278}
279
280- (void) placeCallFromSearchField
281{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400282 Call* c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400283 // check for a valid ring hash
284 NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
285 BOOL valid = [[[searchField stringValue] stringByTrimmingCharactersInSet:hexSet] isEqualToString:@""];
286
287 if(valid && searchField.stringValue.length == 40) {
288 c->setDialNumber(QString::fromNSString([NSString stringWithFormat:@"ring:%@",[searchField stringValue]]));
289 } else {
290 c->setDialNumber(QString::fromNSString([searchField stringValue]));
291 }
292
293 c << Call::Action::ACCEPT;
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400294
295 [searchField setStringValue:@""];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400296 RecentModel::instance().peopleProxy()->
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400297 setFilterRegExp(QRegExp(QString::fromNSString([searchField stringValue]), Qt::CaseInsensitive, QRegExp::FixedString));
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400298}
299
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400300#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400301
302- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
303{
304 if (commandSelector == @selector(insertNewline:)) {
305 if([[searchField stringValue] isNotEqualTo:@""]) {
306 [self placeCallFromSearchField];
307 return YES;
308 }
309 }
310
311 return NO;
312}
313
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400314- (void)controlTextDidChange:(NSNotification *) notification
315{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400316 RecentModel::instance().peopleProxy()->
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400317 setFilterRegExp(QRegExp(QString::fromNSString([searchField stringValue]), Qt::CaseInsensitive, QRegExp::FixedString));
318}
319
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400320@end