hydrus/hydrus/client/gui/ClientGUIDragDrop.py

339 lines
9.5 KiB
Python

import os
from qtpy import QtCore as QC
from qtpy import QtGui as QG
from qtpy import QtWidgets as QW
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.core import HydrusText
from hydrus.client import ClientExporting
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import QtPorting as QP
# we do this because some programs like discord will disallow exports with additional custom mimetypes (like 'application/hydrus-files')
# as this is only ever an internal transfer, and as the python mimedata object is preserved through the dnd, we can just tack this info on with a subclass and python variables
class QMimeDataHydrusFiles( QC.QMimeData ):
def __init__( self ):
QC.QMimeData.__init__( self )
self._hydrus_files = None
def hydrusFiles( self ):
return self._hydrus_files
def setHydrusFiles( self, page_key, hashes ):
self._hydrus_files = ( page_key, hashes )
def DoFileExportDragDrop( window, page_key, media, alt_down ):
drop_source = QG.QDrag( window )
data_object = QMimeDataHydrusFiles()
#
new_options = HG.client_controller.new_options
do_secret_discord_dnd_fix = new_options.GetBoolean( 'secret_discord_dnd_fix' ) and alt_down
#
client_files_manager = HG.client_controller.client_files_manager
original_paths = []
media_and_original_paths = []
total_size = 0
for m in media:
hash = m.GetHash()
mime = m.GetMime()
total_size += m.GetSize()
original_path = client_files_manager.GetFilePath( hash, mime, check_file_exists = False )
original_paths.append( original_path )
media_and_original_paths.append( ( m, original_path ) )
#
discord_dnd_fix_possible = new_options.GetBoolean( 'discord_dnd_fix' ) and len( original_paths ) <= 50 and total_size < 200 * 1048576
temp_dir = HG.client_controller.temp_dir
if do_secret_discord_dnd_fix:
dnd_paths = original_paths
flags = QC.Qt.MoveAction
elif discord_dnd_fix_possible and os.path.exists( temp_dir ):
fallback_filename_terms = ClientExporting.ParseExportPhrase( '{hash}' )
try:
filename_pattern = new_options.GetString( 'discord_dnd_filename_pattern' )
filename_terms = ClientExporting.ParseExportPhrase( filename_pattern )
if len( filename_terms ) == 0:
raise Exception()
except:
filename_terms = fallback_filename_terms
dnd_paths = []
for ( m, original_path ) in media_and_original_paths:
filename = ClientExporting.GenerateExportFilename( temp_dir, m, filename_terms )
if filename == HC.mime_ext_lookup[ m.GetMime() ]:
filename = ClientExporting.GenerateExportFilename( temp_dir, m, fallback_filename_terms )
dnd_path = os.path.join( temp_dir, filename )
if not os.path.exists( dnd_path ):
HydrusPaths.MirrorFile( original_path, dnd_path )
dnd_paths.append( dnd_path )
flags = QC.Qt.MoveAction | QC.Qt.CopyAction
else:
dnd_paths = original_paths
flags = QC.Qt.CopyAction
uri_list = []
for path in dnd_paths:
uri_list.append( QC.QUrl.fromLocalFile( path ) )
data_object.setUrls( uri_list )
#
hashes = [ m.GetHash() for m in media ]
data_object.setHydrusFiles( page_key, hashes )
# old way of doing this that makes some external programs (discord) reject it
'''
if page_key is None:
encoded_page_key = None
else:
encoded_page_key = page_key.hex()
data_obj = ( encoded_page_key, [ hash.hex() for hash in hashes ] )
data_str = json.dumps( data_obj )
data_bytes = bytes( data_str, 'utf-8' )
data_object.setData( 'application/hydrus-media', data_bytes )
'''
#
drop_source.setMimeData( data_object )
result = drop_source.exec_( flags, QC.Qt.CopyAction )
return result
class FileDropTarget( QC.QObject ):
def __init__( self, parent, filenames_callable = None, url_callable = None, media_callable = None ):
QC.QObject.__init__( self, parent )
self._parent = parent
if parent:
parent.setAcceptDrops( True )
self._filenames_callable = filenames_callable
self._url_callable = url_callable
self._media_callable = media_callable
def eventFilter( self, object, event ):
if event.type() == QC.QEvent.Drop:
if self.OnDrop( event.pos().x(), event.pos().y() ):
event.setDropAction( self.OnData( event.mimeData(), event.proposedAction() ) )
event.accept()
elif event.type() == QC.QEvent.DragEnter:
event.accept()
return False
def OnData( self, mime_data, result ):
media_dnd = isinstance( mime_data, QMimeDataHydrusFiles )
urls_dnd = mime_data.hasUrls()
text_dnd = mime_data.hasText()
if media_dnd and self._media_callable is not None:
result = mime_data.hydrusFiles()
if result is not None:
( page_key, hashes ) = result
if page_key is not None:
QP.CallAfter( self._media_callable, page_key, hashes ) # callafter so we can terminate dnd event now
result = QC.Qt.MoveAction
# old way of doing it that messed up discord et al
'''
elif mime_data.formats().count( 'application/hydrus-media' ) and self._media_callable is not None:
mview = mime_data.data( 'application/hydrus-media' )
data_bytes = mview.data()
data_str = str( data_bytes, 'utf-8' )
(encoded_page_key, encoded_hashes) = json.loads( data_str )
if encoded_page_key is not None:
page_key = bytes.fromhex( encoded_page_key )
hashes = [ bytes.fromhex( encoded_hash ) for encoded_hash in encoded_hashes ]
QP.CallAfter( self._media_callable, page_key, hashes ) # callafter so we can terminate dnd event now
result = QC.Qt.MoveAction
'''
elif urls_dnd or text_dnd:
paths = []
urls = []
if urls_dnd:
dnd_items = mime_data.urls()
for dnd_item in dnd_items:
if dnd_item.isLocalFile():
paths.append( os.path.normpath( dnd_item.toLocalFile() ) )
else:
urls.append( dnd_item.url() )
else:
text = mime_data.text()
text_lines = HydrusText.DeserialiseNewlinedTexts( text )
for text_line in text_lines:
if text_line.startswith( 'http' ):
urls.append( text_line )
# ignore 'paths'
if self._filenames_callable is not None:
if len( paths ) > 0:
QP.CallAfter( self._filenames_callable, paths ) # callafter to terminate dnd event now
if self._url_callable is not None:
if len( urls ) > 0:
for url in urls:
QP.CallAfter( self._url_callable, url ) # callafter to terminate dnd event now
result = QC.Qt.IgnoreAction
else:
result = QC.Qt.IgnoreAction
return result
def OnDrop( self, x, y ):
screen_position = ClientGUIFunctions.ClientToScreen( self._parent, QC.QPoint( x, y ) )
drop_tlw = QW.QApplication.topLevelAt( screen_position )
my_tlw = self._parent.window()
if drop_tlw == my_tlw:
return True
else:
return False
# setting OnDragOver to return copy gives Linux trouble with page tab drops with shift held down