d69043097525af30485f774147e95d0df28b0fe5
[vlc.git] / modules / gui / macosx / VLCBookmarksWindowController.m
1 /*****************************************************************************
2  * VLCBookmarksWindowController.m: MacOS X Bookmarks window
3  *****************************************************************************
4  * Copyright (C) 2005 - 2015 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24
25 /*****************************************************************************
26  * Note:
27  * the code used to bind with VLC's modules is heavily based upon
28  * ../wxwidgets/bookmarks.cpp, written by Gildas Bazin.
29  * (he is a member of the VideoLAN team)
30  *****************************************************************************/
31
32
33 /*****************************************************************************
34  * Preamble
35  *****************************************************************************/
36
37 #import "VLCBookmarksWindowController.h"
38 #import "CompatibilityFixes.h"
39
40 @interface VLCBookmarksWindowController() <NSTableViewDataSource, NSTableViewDelegate>
41 {
42     input_thread_t *p_old_input;
43 }
44 @end
45
46 @implementation VLCBookmarksWindowController
47
48 /*****************************************************************************
49  * GUI methods
50  *****************************************************************************/
51
52 - (id)init
53 {
54     self = [super initWithWindowNibName:@"Bookmarks"];
55
56     return self;
57 }
58
59 - (void)dealloc
60 {
61     if (p_old_input)
62         vlc_object_release(p_old_input);
63
64     [[NSNotificationCenter defaultCenter] removeObserver:self];
65 }
66
67 - (void)windowDidLoad
68 {
69     [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
70
71     _dataTable.dataSource = self;
72     _dataTable.delegate = self;
73     _dataTable.action = @selector(goToBookmark:);
74     _dataTable.target = self;
75
76     /* main window */
77     [self.window setTitle: _NS("Bookmarks")];
78     [_addButton setTitle: _NS("Add")];
79     [_clearButton setTitle: _NS("Clear")];
80     [_editButton setTitle: _NS("Edit")];
81     [_extractButton setTitle: _NS("Extract")];
82     [_removeButton setTitle: _NS("Remove")];
83     [[[_dataTable tableColumnWithIdentifier:@"description"] headerCell]
84      setStringValue: _NS("Description")];
85     [[[_dataTable tableColumnWithIdentifier:@"time_offset"] headerCell]
86      setStringValue: _NS("Time")];
87
88     /* edit window */
89     [_editOKButton setTitle: _NS("OK")];
90     [_editCancelButton setTitle: _NS("Cancel")];
91     [_editNameLabel setStringValue: _NS("Name")];
92     [_editTimeLabel setStringValue: _NS("Time")];
93
94     [[NSNotificationCenter defaultCenter] addObserver:self
95                                              selector:@selector(inputChangedEvent:)
96                                                  name:VLCInputChangedNotification
97                                                object:nil];
98 }
99
100 - (void)updateCocoaWindowLevel:(NSInteger)i_level
101 {
102     if (self.isWindowLoaded && [self.window isVisible] && [self.window level] != i_level)
103         [self.window setLevel: i_level];
104 }
105
106 - (void)showBookmarks
107 {
108     /* show the window, called from intf.m */
109     [self.window displayIfNeeded];
110     [self.window setLevel: [[[VLCMain sharedInstance] voutController] currentStatusWindowLevel]];
111     [self.window makeKeyAndOrderFront:nil];
112 }
113
114 -(void)inputChangedEvent:(NSNotification *)o_notification
115 {
116     [_dataTable reloadData];
117 }
118
119 - (IBAction)add:(id)sender
120 {
121     /* add item to list */
122     input_thread_t * p_input = pl_CurrentInput(getIntf());
123
124     if (!p_input)
125         return;
126
127     seekpoint_t bookmark;
128
129     if (!input_Control(p_input, INPUT_GET_BOOKMARK, &bookmark)) {
130         bookmark.psz_name = _("Untitled");
131         input_Control(p_input, INPUT_ADD_BOOKMARK, &bookmark);
132     }
133
134     vlc_object_release(p_input);
135
136     [_dataTable reloadData];
137 }
138
139 - (IBAction)clear:(id)sender
140 {
141     /* clear table */
142     input_thread_t * p_input = pl_CurrentInput(getIntf());
143
144     if (!p_input)
145         return;
146
147     input_Control(p_input, INPUT_CLEAR_BOOKMARKS);
148
149     vlc_object_release(p_input);
150
151     [_dataTable reloadData];
152 }
153
154 - (IBAction)edit:(id)sender
155 {
156     /* put values to the sheet's fields and show sheet */
157     /* we take the values from the core and not the table, because we cannot
158      * really trust it */
159     input_thread_t * p_input = pl_CurrentInput(getIntf());
160     seekpoint_t **pp_bookmarks;
161     int i_bookmarks;
162     int row;
163     row = [_dataTable selectedRow];
164
165     if (!p_input)
166         return;
167
168     if (row < 0) {
169         vlc_object_release(p_input);
170         return;
171     }
172
173     if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS) {
174         vlc_object_release(p_input);
175         return;
176     }
177
178     [_editNameTextField setStringValue: toNSStr(pp_bookmarks[row]->psz_name)];
179     [_editTimeTextField setStringValue:[self timeStringForBookmark:pp_bookmarks[row]]];
180
181     /* Just keep the pointer value to check if it
182      * changes. Note, we don't need to keep a reference to the object.
183      * so release it now. */
184     p_old_input = p_input;
185     vlc_object_release(p_input);
186
187     [NSApp beginSheet: _editBookmarksWindow modalForWindow: self.window modalDelegate: _editBookmarksWindow didEndSelector: nil contextInfo: nil];
188
189     // Clear the bookmark list
190     for (int i = 0; i < i_bookmarks; i++)
191         vlc_seekpoint_Delete(pp_bookmarks[i]);
192     free(pp_bookmarks);
193 }
194
195 - (IBAction)edit_cancel:(id)sender
196 {
197     /* close sheet */
198     [NSApp endSheet:_editBookmarksWindow];
199     [_editBookmarksWindow close];
200 }
201
202 - (IBAction)edit_ok:(id)sender
203 {
204     /* save field contents and close sheet */
205      seekpoint_t **pp_bookmarks;
206     int i_bookmarks, i;
207     input_thread_t * p_input = pl_CurrentInput(getIntf());
208
209     if (!p_input) {
210         NSBeginCriticalAlertSheet(_NS("No input"), _NS("OK"), @"", @"", self.window, nil, nil, nil, nil, @"%@",_NS("No input found. A stream must be playing or paused for bookmarks to work."));
211         return;
212     }
213     if (p_old_input != p_input) {
214         NSBeginCriticalAlertSheet(_NS("Input has changed"), _NS("OK"), @"", @"", self.window, nil, nil, nil, nil, @"%@",_NS("Input has changed, unable to save bookmark. Suspending playback with \"Pause\" while editing bookmarks to ensure to keep the same input."));
215         vlc_object_release(p_input);
216         return;
217     }
218
219     if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS) {
220         vlc_object_release(p_input);
221         return;
222     }
223
224     i = [_dataTable selectedRow];
225
226     free(pp_bookmarks[i]->psz_name);
227
228     pp_bookmarks[i]->psz_name = strdup([[_editNameTextField stringValue] UTF8String]);
229
230     NSArray * components = [[_editTimeTextField stringValue] componentsSeparatedByString:@":"];
231     NSUInteger componentCount = [components count];
232     if (componentCount == 1)
233         pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] floatValue]);
234     else if (componentCount == 2)
235         pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] longLongValue] * 60 + [[components objectAtIndex:1] longLongValue]);
236     else if (componentCount == 3)
237         pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] longLongValue] * 3600 + [[components objectAtIndex:1] longLongValue] * 60 + [[components objectAtIndex:2] floatValue]);
238     else {
239         msg_Err(getIntf(), "Invalid string format for time");
240         goto clear;
241     }
242
243     if (input_Control(p_input, INPUT_CHANGE_BOOKMARK, pp_bookmarks[i], i) != VLC_SUCCESS) {
244         msg_Warn(getIntf(), "Unable to change the bookmark");
245         goto clear;
246     }
247
248     [_dataTable reloadData];
249     vlc_object_release(p_input);
250
251     [NSApp endSheet: _editBookmarksWindow];
252     [_editBookmarksWindow close];
253
254 clear:
255     // Clear the bookmark list
256     for (int i = 0; i < i_bookmarks; i++)
257         vlc_seekpoint_Delete(pp_bookmarks[i]);
258     free(pp_bookmarks);
259 }
260
261 - (IBAction)extract:(id)sender
262 {
263 #warning this does not work anymore
264 #if 0
265     if ([_dataTable numberOfSelectedRows] < 2) {
266         NSBeginAlertSheet(_NS("Invalid selection"), _NS("OK"), @"", @"", self.window, nil, nil, nil, nil, @"%@",_NS("Two bookmarks have to be selected."));
267         return;
268     }
269     input_thread_t * p_input = pl_CurrentInput(getIntf());
270     if (!p_input) {
271         NSBeginCriticalAlertSheet(_NS("No input found"), _NS("OK"), @"", @"", self.window, nil, nil, nil, nil, @"%@",_NS("The stream must be playing or paused for bookmarks to work."));
272         return;
273     }
274
275     seekpoint_t **pp_bookmarks;
276     int i_bookmarks ;
277     int i_first = -1;
278     int i_second = -1;
279     int c = 0;
280     for (NSUInteger x = 0; c != 2; x++) {
281         if ([_dataTable isRowSelected:x]) {
282             if (i_first == -1) {
283                 i_first = x;
284                 c = 1;
285             } else if (i_second == -1) {
286                 i_second = x;
287                 c = 2;
288             }
289         }
290     }
291
292     if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS) {
293         vlc_object_release(p_input);
294         msg_Err(getIntf(), "already defined bookmarks couldn't be retrieved");
295         return;
296     }
297
298     char *psz_uri = input_item_GetURI(input_GetItem(p_input));
299     [[[VLCMain sharedInstance] wizard] initWithExtractValuesFrom: [NSString stringWithFormat:@"%lli", pp_bookmarks[i_first]->i_time_offset/1000000] to: [NSString stringWithFormat:@"%lli", pp_bookmarks[i_second]->i_time_offset/1000000] ofItem: toNSStr(psz_uri)];
300     free(psz_uri);
301     vlc_object_release(p_input);
302
303     // Clear the bookmark list
304     for (int i = 0; i < i_bookmarks; i++)
305         vlc_seekpoint_Delete(pp_bookmarks[i]);
306     free(pp_bookmarks);
307 #endif
308 }
309
310 - (IBAction)goToBookmark:(id)sender
311 {
312     input_thread_t * p_input = pl_CurrentInput(getIntf());
313
314     if (!p_input)
315         return;
316
317     input_Control(p_input, INPUT_SET_BOOKMARK, [_dataTable selectedRow]);
318
319     vlc_object_release(p_input);
320 }
321
322 - (IBAction)remove:(id)sender
323 {
324     input_thread_t * p_input = pl_CurrentInput(getIntf());
325
326     if (!p_input)
327         return;
328
329     int i_focused = [_dataTable selectedRow];
330
331     if (i_focused >= 0)
332         input_Control(p_input, INPUT_DEL_BOOKMARK, i_focused);
333
334     vlc_object_release(p_input);
335
336     [_dataTable reloadData];
337 }
338
339 - (NSString *)timeStringForBookmark:(seekpoint_t *)bookmark
340 {
341     assert(bookmark != NULL);
342
343     mtime_t total = bookmark->i_time_offset;
344     uint64_t hour = ( total / ( CLOCK_FREQ * 3600 ) );
345     uint64_t min = ( total % ( CLOCK_FREQ * 3600 ) ) / ( CLOCK_FREQ * 60 );
346     float    sec = ( total % ( CLOCK_FREQ * 60 ) ) / ( CLOCK_FREQ * 1. );
347
348     return [NSString stringWithFormat:@"%02llu:%02llu:%06.3f", hour, min, sec];
349 }
350
351 /*****************************************************************************
352  * data source methods
353  *****************************************************************************/
354
355 - (NSInteger)numberOfRowsInTableView:(NSTableView *)theDataTable
356 {
357     /* return the number of bookmarks */
358     input_thread_t * p_input = pl_CurrentInput(getIntf());
359     seekpoint_t **pp_bookmarks;
360     int i_bookmarks;
361
362     if (!p_input)
363         return 0;
364
365     int returnValue = input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks);
366     vlc_object_release(p_input);
367
368     if (returnValue != VLC_SUCCESS)
369         return 0;
370
371     for (int i = 0; i < i_bookmarks; i++)
372         vlc_seekpoint_Delete(pp_bookmarks[i]);
373     free(pp_bookmarks);
374
375     return i_bookmarks;
376 }
377
378 - (id)tableView:(NSTableView *)theDataTable objectValueForTableColumn: (NSTableColumn *)theTableColumn row: (NSInteger)row
379 {
380     /* return the corresponding data as NSString */
381     input_thread_t * p_input = pl_CurrentInput(getIntf());
382     seekpoint_t **pp_bookmarks;
383     int i_bookmarks;
384     id ret = @"";
385
386     if (!p_input)
387         return @"";
388     else if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS)
389         ret = @"";
390     else if (row >= i_bookmarks)
391         ret = @"";
392     else {
393         NSString * identifier = [theTableColumn identifier];
394         if ([identifier isEqualToString: @"description"])
395             ret = toNSStr(pp_bookmarks[row]->psz_name);
396                 else if ([identifier isEqualToString: @"time_offset"]) {
397             ret = [self timeStringForBookmark:pp_bookmarks[row]];
398         }
399
400         // Clear the bookmark list
401         for (int i = 0; i < i_bookmarks; i++)
402             vlc_seekpoint_Delete(pp_bookmarks[i]);
403         free(pp_bookmarks);
404     }
405     vlc_object_release(p_input);
406     return ret;
407 }
408
409 /*****************************************************************************
410  * delegate methods
411  *****************************************************************************/
412
413 - (void)tableViewSelectionDidChange:(NSNotification *)aNotification
414 {
415     /* check whether a row is selected and en-/disable the edit/remove buttons */
416     if ([_dataTable selectedRow] == -1) {
417         /* no row is selected */
418         [_editButton setEnabled: NO];
419         [_removeButton setEnabled: NO];
420         [_extractButton setEnabled: NO];
421     } else {
422         /* a row is selected */
423         [_editButton setEnabled: YES];
424         [_removeButton setEnabled: YES];
425         if ([_dataTable numberOfSelectedRows] == 2)
426             [_extractButton setEnabled: YES];
427     }
428 }
429
430 /* Called when the user hits CMD + C or copy is clicked in the edit menu
431  */
432 - (void) copy:(id)sender {
433     NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
434     NSIndexSet *selectionIndices = [_dataTable selectedRowIndexes];
435
436
437     input_thread_t *p_input = pl_CurrentInput(getIntf());
438     int i_bookmarks;
439     seekpoint_t **pp_bookmarks;
440
441     if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS)
442         return;
443
444     [pasteBoard clearContents];
445     NSUInteger index = [selectionIndices firstIndex];
446
447     while(index != NSNotFound) {
448         /* Get values */
449         if (index >= i_bookmarks)
450             break;
451         NSString *name = toNSStr(pp_bookmarks[index]->psz_name);
452         NSString *time = [self timeStringForBookmark:pp_bookmarks[index]];
453
454         NSString *message = [NSString stringWithFormat:@"%@ - %@", name, time];
455         [pasteBoard writeObjects:@[message]];
456
457         /* Get next index */
458         index = [selectionIndices indexGreaterThanIndex:index];
459     }
460
461     // Clear the bookmark list
462     for (int i = 0; i < i_bookmarks; i++)
463         vlc_seekpoint_Delete(pp_bookmarks[i]);
464     free(pp_bookmarks);
465 }
466
467 #pragma mark -
468 #pragma mark UI validation
469
470 /* Validate the copy menu item
471  */
472 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
473 {
474     SEL theAction = [anItem action];
475
476     if (theAction == @selector(copy:)) {
477         if ([[_dataTable selectedRowIndexes] count] > 0) {
478             return YES;
479         }
480         return NO;
481     }
482     /* Indicate that we handle the validation method,
483      * even if we don’t implement the action
484      */
485     return YES;
486 }
487
488 @end