conversation: support files drag and drop

Change-Id: If97d5b8602ab162b605b1df02c9528d6609d4f0c
diff --git a/src/ConversationVC.h b/src/ConversationVC.h
index d0bce13..7f5bab6 100644
--- a/src/ConversationVC.h
+++ b/src/ConversationVC.h
@@ -43,4 +43,6 @@
 
 -(NSViewController*) getMessagesView;
 
+-(void)callFinished;
+
 @end
diff --git a/src/ConversationVC.mm b/src/ConversationVC.mm
index 6b01470..dd3a2c3 100644
--- a/src/ConversationVC.mm
+++ b/src/ConversationVC.mm
@@ -93,6 +93,10 @@
     return messagesViewVC;
 }
 
+-(void)callFinished {
+    [messagesViewVC callFinished];
+}
+
 -(void) clearData {
     cachedConv_ = nil;
     convUid_ = "";
diff --git a/src/MessagesVC.h b/src/MessagesVC.h
index 94fdbc9..e92be95 100644
--- a/src/MessagesVC.h
+++ b/src/MessagesVC.h
@@ -22,9 +22,16 @@
 #import <api/conversation.h>
 #import <api/avmodel.h>
 #import "RecordFileVC.h"
+#import "views/DraggingDestinationView.h"
 
+@interface PendingFile: NSObject
+@property (retain) NSString* name;
+@property (retain) NSString* size;
+@property (retain) NSImage* preview;
+@property (retain) NSURL* fileUrl;
+@end
 
-@interface MessagesVC : NSViewController <RecordingViewDelegate>
+@interface MessagesVC : NSViewController <RecordingViewDelegate, DraggingDestinationDelegate>
 
 -(void)setConversationUid:(const QString&)convUid model:(lrc::api::ConversationModel*)model;
 -(void)clearData;
@@ -36,7 +43,21 @@
  */
 @property (retain) NSString* message;
 
+/**
+ * This is a KVO method to bind the pending files collection view visibility
+ */
+@property BOOL hideFilesCollection;
+
+/**
+ * This is a KVO method to bind the enable state of send button
+ */
+@property BOOL enableSendButton;
+
 -(void) setAVModel: (lrc::api::AVModel*) avmodel;
 -(void) checkIfcomposingMsg;
 
++ (NSMutableDictionary *) pendingFiles;
+
+-(void)callFinished;
+
 @end
diff --git a/src/MessagesVC.mm b/src/MessagesVC.mm
index ce0c926..9ecbd65 100644
--- a/src/MessagesVC.mm
+++ b/src/MessagesVC.mm
@@ -30,6 +30,7 @@
 #import "views/IMTableCellView.h"
 #import "views/MessageBubbleView.h"
 #import "views/NSImage+Extensions.h"
+#import "views/FileToSendCollectionItem.h"
 #import "delegates/ImageManipulationDelegate.h"
 #import "utils.h"
 #import "views/NSColor+RingTheme.h"
@@ -41,10 +42,14 @@
 
 #import "RecordFileVC.h"
 
+@implementation PendingFile
+@end
 
-@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource, NSTextViewDelegate> {
+@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource, NSTextViewDelegate, NSCollectionViewDataSource> {
 
     __unsafe_unretained IBOutlet NSTableView* conversationView;
+    __unsafe_unretained IBOutlet DraggingDestinationView* draggingDestinationView;
+    __unsafe_unretained IBOutlet NSCollectionView* pendingFilesCollectionView;
     __unsafe_unretained IBOutlet NSView* containerView;
     __unsafe_unretained IBOutlet TextViewWithPlaceholder* messageView;
     __unsafe_unretained IBOutlet IconButton *sendFileButton;
@@ -52,7 +57,6 @@
     __unsafe_unretained IBOutlet IconButton *recordAudioButton;
     __unsafe_unretained IBOutlet NSLayoutConstraint* sendPanelHeight;
     __unsafe_unretained IBOutlet NSLayoutConstraint* messageHeight;
-    __unsafe_unretained IBOutlet NSLayoutConstraint* messagesBottomMargin;
     __unsafe_unretained IBOutlet NSLayoutConstraint* textBottomConstraint;
     IBOutlet NSPopover *recordMessagePopover;
 
@@ -71,7 +75,7 @@
     QMetaObject::Connection lastDisplayedChanged_;
     NSString* previewImage;
     NSMutableDictionary *pendingMessagesToSend;
-    RecordFileVC * recordingController;
+    RecordFileVC* recordingController;
 }
 
 @end
@@ -126,6 +130,25 @@
     [[conversationView.enclosingScrollView contentView] setCopiesOnScroll:NO];
     [messageView setFont: [NSFont systemFontOfSize: 14 weight: NSFontWeightLight]];
     [conversationView setWantsLayer:YES];
+    draggingDestinationView.draggingDestinationDelegate = self;
+}
+
+-(void)callFinished {
+    [self reloadPendingFiles];
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [conversationView scrollToEndOfDocument:nil];
+        [messageView.window makeFirstResponder: messageView];
+    });
+}
+
++(NSMutableDictionary*)pendingFiles
+{
+    static NSMutableDictionary* files = nil;
+    static dispatch_once_t oncePredicate;
+    dispatch_once(&oncePredicate, ^{
+        files = [[NSMutableDictionary alloc] init];
+    });
+    return files;
 }
 
 - (instancetype)initWithCoder:(NSCoder *)coder
@@ -341,25 +364,10 @@
     dispatch_async(dispatch_get_main_queue(), ^{
         [messageView.window makeFirstResponder: messageView];
     });
-    conversationView.alphaValue = 0.0;
-    [conversationView reloadData];
-    [conversationView scrollToEndOfDocument:nil];
-    CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
-    fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
-    fadeIn.toValue = [NSNumber numberWithFloat:1.0];
-    fadeIn.duration = 0.4f;
-
-    [conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
-    conversationView.alphaValue = 1;
     auto* conv = [self getCurrentConversation];
 
     if (conv == nil)
         return;
-    try {
-        [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
-    } catch (std::out_of_range& e) {
-        NSLog(@"contact out of range");
-    }
     NSString* name = bestNameForConversation(*conv, *convModel_);
     NSString *placeholder = [NSString stringWithFormat:@"%@%@", @"Write to ", name];
 
@@ -371,6 +379,22 @@
                                nil];
     NSAttributedString* attributedPlaceholder = [[NSAttributedString alloc] initWithString: placeholder attributes:nameAttrs];
     messageView.placeholderAttributedString = attributedPlaceholder;
+    [self reloadPendingFiles];
+    conversationView.alphaValue = 0.0;
+    [conversationView reloadData];
+    [conversationView scrollToEndOfDocument:nil];
+    CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
+    fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
+    fadeIn.toValue = [NSNumber numberWithFloat:1.0];
+    fadeIn.duration = 0.4f;
+
+    [conversationView.layer addAnimation:fadeIn forKey:fadeIn.keyPath];
+    conversationView.alphaValue = 1;
+    try {
+        [sendFileButton setEnabled:(convModel_->owner.contactModel->getContact(conv->participants[0]).profileInfo.type != lrc::api::profile::Type::SIP)];
+    } catch (std::out_of_range& e) {
+        NSLog(@"contact out of range");
+    }
 }
 
 #pragma mark - configure cells
@@ -960,7 +984,6 @@
     if (MESSAGE_VIEW_DEFAULT_HEIGHT != lineHeight) {
         MESSAGE_VIEW_DEFAULT_HEIGHT = lineHeight;
     }
-    messagesBottomMargin.constant = newSendPanelHeight;
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
         [self scrollToBottom];
         messageHeight.constant = msgHeight;
@@ -1068,6 +1091,14 @@
     unichar separatorChar = NSLineSeparatorCharacter;
     NSString *separatorString = [NSString stringWithCharacters:&separatorChar length:1];
     text = [text stringByReplacingOccurrencesOfString: separatorString withString: @"\n"];
+    // send files
+    NSMutableArray* files = [MessagesVC pendingFiles][convUid_.toNSString()];
+    for (PendingFile* file : files) {
+        convModel_->sendFile(convUid_, QString::fromNSString(file.fileUrl.path), QString::fromNSString(file.name));
+    }
+    [MessagesVC.pendingFiles removeObjectForKey: convUid_.toNSString()];
+    [self reloadPendingFiles];
+
     if (text && text.length > 0) {
         auto* conv = [self getCurrentConversation];
         if (conv == nil)
@@ -1086,7 +1117,6 @@
     if(messageHeight.constant != MESSAGE_VIEW_DEFAULT_HEIGHT) {
         sendPanelHeight.constant = SEND_PANEL_DEFAULT_HEIGHT;
         messageHeight.constant = MESSAGE_VIEW_DEFAULT_HEIGHT;
-        messagesBottomMargin.constant = SEND_PANEL_DEFAULT_HEIGHT;
         textBottomConstraint.constant = BOTTOM_MARGIN;
         [self scrollToBottom];
     }
@@ -1143,18 +1173,10 @@
     NSOpenPanel* filePicker = [NSOpenPanel openPanel];
     [filePicker setCanChooseFiles:YES];
     [filePicker setCanChooseDirectories:NO];
-    [filePicker setAllowsMultipleSelection:NO];
+    [filePicker setAllowsMultipleSelection:YES];
 
     if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
-        if ([[filePicker URLs] count] == 1) {
-            NSURL* url = [[filePicker URLs] objectAtIndex:0];
-            const char* fullPath = [url fileSystemRepresentation];
-            NSString* fileName = [url lastPathComponent];
-            if (convModel_) {
-                auto* conv = [self getCurrentConversation];
-                convModel_->sendFile(convUid_, QString::fromStdString(fullPath), QString::fromNSString(fileName));
-            }
-        }
+        [self filesDragged: [filePicker URLs]];
     }
 }
 
@@ -1163,7 +1185,7 @@
 
 - (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
     if (commandSelector == @selector(insertNewline:)) {
-        if(self.message.length > 0) {
+        if(self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0) {
             [self sendMessage: nil];
             return YES;
         }
@@ -1173,8 +1195,9 @@
     return NO;
 }
 
--(void) textDidChange:(NSNotification *)notification {
+-(void)textDidChange:(NSNotification *)notification {
     [self checkIfComposingMsg];
+    self.enableSendButton = self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0;
 }
 
 - (void) checkIfComposingMsg {
@@ -1226,7 +1249,6 @@
     }
 }
 
-
 - (void)removeFromResponderChain {
     if (conversationView.window &&
         [[conversationView.window nextResponder] isEqual:self]) {
@@ -1235,4 +1257,78 @@
     }
 }
 
+#pragma mark - NSCollectionViewDataSource
+
+- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
+    return [(NSMutableArray*)MessagesVC.pendingFiles[convUid_.toNSString()] count];
+}
+- (NSCollectionViewItem*)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
+    FileToSendCollectionItem* fileCell = [collectionView makeItemWithIdentifier:@"FileToSendCollectionItem" forIndexPath:indexPath];
+    PendingFile* file = MessagesVC.pendingFiles[convUid_.toNSString()][indexPath.item];
+    fileCell.filePreview.image = file.preview;
+    fileCell.fileName.stringValue = file.name;
+    fileCell.fileName.toolTip = file.name;
+    fileCell.fileSize.stringValue = file.size;
+    [fileCell.closeButton setAction:@selector(removePendingFile:)];
+    fileCell.closeButton.tag = indexPath.item;
+    [fileCell.closeButton setTarget:self];
+    return fileCell;
+}
+
+#pragma mark - DraggingDestinationDelegate
+
+-(void)filesDragged:(NSArray*)urls {
+    [self prepareFilesToSend: urls];
+}
+
+-(NSString*)convertBytedToString:(double)bytes {
+    if (bytes <= 1000) {
+        return [NSString stringWithFormat:@"%.2f%@", bytes, @" B"];
+    } else if (bytes <= 1e6) {
+        return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-3), @" KB"];
+    } else if (bytes <= 1e9) {
+       return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-6), @" MB"];
+    }
+    return [NSString stringWithFormat:@"%.2f%@",(bytes * 1e-9), @" GB"];
+}
+
+-(void)prepareFilesToSend:(NSArray*)urls {
+    NSMutableArray* files = [[NSMutableArray alloc] init];
+    NSMutableArray* existingFiles = MessagesVC.pendingFiles[convUid_.toNSString()];
+    [files addObjectsFromArray: existingFiles];
+    for (NSURL* url : urls) {
+        NSString* filePath = [url path];
+        NSImage* preview = [[NSImage alloc] initWithContentsOfFile: filePath];
+        NSString* name = [url lastPathComponent];
+        NSData* documentBytes = [[NSData alloc] initWithContentsOfFile: filePath];
+        PendingFile* file = [[PendingFile alloc] init];
+        file.name = name;
+        file.size = [self convertBytedToString: documentBytes.length];
+        file.preview = preview;
+        file.fileUrl = url;
+        [files addObject: file];
+    }
+    MessagesVC.pendingFiles[convUid_.toNSString()] = files;
+    [self reloadPendingFiles];
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+        [self scrollToBottom];
+    });
+}
+
+- (void)removePendingFile:(id)sender {
+    NSButton* closeButton = (NSButton*)sender;
+    NSInteger index = closeButton.tag;
+    if(index < 0) {
+        return;
+    }
+    [MessagesVC.pendingFiles[convUid_.toNSString()] removeObjectAtIndex:index];
+    [self reloadPendingFiles];
+}
+
+- (void)reloadPendingFiles {
+    self.hideFilesCollection = [(NSMutableArray*)MessagesVC .pendingFiles[convUid_.toNSString()] count]== 0;
+    self.enableSendButton = self.message.length > 0 || [(NSMutableArray*)MessagesVC. pendingFiles[convUid_.toNSString()] count] > 0;
+    [pendingFilesCollectionView reloadData];
+}
+
 @end
diff --git a/src/RingWindowController.mm b/src/RingWindowController.mm
index 7b1d5de..8998ff0 100644
--- a/src/RingWindowController.mm
+++ b/src/RingWindowController.mm
@@ -649,6 +649,7 @@
 
 -(void) callFinished {
     [self changeViewTo:SHOW_CONVERSATION_SCREEN];
+    [conversationVC callFinished];
 }
 
 -(void) showConversation:(NSString* )conversationId forAccount:(NSString*)accountId {
diff --git a/src/views/DraggingDestinationView.h b/src/views/DraggingDestinationView.h
new file mode 100644
index 0000000..452991d
--- /dev/null
+++ b/src/views/DraggingDestinationView.h
@@ -0,0 +1,43 @@
+/*
+*  Copyright (C) 2021 Savoir-faire Linux Inc.
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+
+#import <Cocoa/Cocoa.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol DraggingDestinationDelegate
+-(void) filesDragged:(NSArray*)urls;
+@end
+
+@interface DraggingDestinationView : NSView <NSDraggingDestination>
+{
+/**
+ * Indicate when dragged file enter drop zone
+ */
+BOOL highlight;
+}
+
+/**
+ *  Delegate to inform about files to process
+ */
+@property (nonatomic) id <DraggingDestinationDelegate> draggingDestinationDelegate;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/views/DraggingDestinationView.mm b/src/views/DraggingDestinationView.mm
new file mode 100644
index 0000000..f53c157
--- /dev/null
+++ b/src/views/DraggingDestinationView.mm
@@ -0,0 +1,89 @@
+/*
+*  Copyright (C) 2021 Savoir-faire Linux Inc.
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+
+#import "DraggingDestinationView.h"
+
+@implementation DraggingDestinationView
+- (id)initWithFrame:(NSRect)frame
+{
+    if (self = [super initWithFrame: frame])
+        [self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeURL, nil]];
+    return self;
+}
+
+-(void)drawRect:(NSRect)rect
+{
+    [super drawRect:rect];
+
+    if (highlight) {
+        [[[NSColor blackColor] colorWithAlphaComponent:0.8] set];
+        [NSBezierPath fillRect: rect];
+    }
+
+    NSRect rectText = rect;
+    NSDictionary* attributes = nil;
+
+    NSString* title = highlight ?
+    NSLocalizedString(@"Drop files to send", @"drop files") : @"";
+    NSMutableParagraphStyle* style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
+    [style setLineBreakMode:NSLineBreakByWordWrapping];
+    [style setAlignment:NSCenterTextAlignment];
+    NSFont* font= [NSFont systemFontOfSize: 32 weight: NSFontWeightSemibold];
+    attributes = [[NSDictionary alloc] initWithObjectsAndKeys:
+                  style, NSParagraphStyleAttributeName,
+                  font,NSFontAttributeName,
+                  [NSColor whiteColor],
+                  NSForegroundColorAttributeName, nil];
+    rectText.size = [title sizeWithAttributes: attributes];
+    rectText.origin.x = floor( NSMidX(rect) - rectText.size.width / 2);
+    rectText.origin.y = floor( NSMidY([self bounds]) - rectText.size.height / 2 );
+    [title drawInRect:rectText withAttributes: attributes];
+}
+
+#pragma mark - Destination Operations
+
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+    highlight = true;
+    [self setNeedsDisplay: true];
+    return NSDragOperationCopy;
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender
+{
+    highlight = false;
+    [self setNeedsDisplay: true];
+}
+
+- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+    highlight = false;
+    [self setNeedsDisplay: true];
+    return true;
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+    NSArray* classArray = [NSArray arrayWithObject:[NSURL class]];
+    NSArray* arrayOfURLs = [[sender draggingPasteboard] readObjectsForClasses:classArray options:nil];
+    [self.draggingDestinationDelegate filesDragged: arrayOfURLs];
+    return true;
+}
+
+@end
diff --git a/src/views/FileToSendCollectionItem.h b/src/views/FileToSendCollectionItem.h
new file mode 100644
index 0000000..9eff5ae
--- /dev/null
+++ b/src/views/FileToSendCollectionItem.h
@@ -0,0 +1,34 @@
+/*
+*  Copyright (C) 2021 Savoir-faire Linux Inc.
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+
+#import <Cocoa/Cocoa.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FileToSendCollectionItem : NSCollectionViewItem
+
+@property (nonatomic, strong) IBOutlet NSImageView* filePreview;
+@property (nonatomic, strong) IBOutlet NSImageView* placeholderPreview;
+@property (nonatomic, strong) IBOutlet NSTextField* fileName;
+@property (nonatomic, strong) IBOutlet NSTextField* fileSize;
+@property (nonatomic, strong) IBOutlet NSButton* closeButton;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/views/FileToSendCollectionItem.mm b/src/views/FileToSendCollectionItem.mm
new file mode 100644
index 0000000..7bd3bb4
--- /dev/null
+++ b/src/views/FileToSendCollectionItem.mm
@@ -0,0 +1,47 @@
+/*
+*  Copyright (C) 2021 Savoir-faire Linux Inc.
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+
+#import "FileToSendCollectionItem.h"
+#import "NSColor+RingTheme.h"
+
+@interface FileToSendCollectionItem ()
+
+@end
+
+@implementation FileToSendCollectionItem
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    self.placeholderPreview.image = [NSColor image: [NSImage imageNamed:@"ic_file.png"] tintedWithColor: [NSColor windowFrameTextColor]];
+    self.closeButton.image = [NSColor image: [NSImage imageNamed:@"ic_action_cancel.png"] tintedWithColor: [NSColor windowFrameTextColor]];
+    [NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];
+}
+
+-(void) deinit {
+    [NSDistributedNotificationCenter.defaultCenter removeObserver:self];
+}
+
+-(void)themeChanged:(NSNotification *) notification {
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+        self.placeholderPreview.image = [NSColor image: [NSImage imageNamed:@"ic_file.png"] tintedWithColor: [NSColor windowFrameTextColor]];
+        self.closeButton.image = [NSColor image: [NSImage imageNamed:@"ic_action_cancel.png"] tintedWithColor: [NSColor windowFrameTextColor]];
+    });
+}
+
+@end