339 lines
9.5 KiB
Python
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
|