1400 lines
40 KiB
Python
1400 lines
40 KiB
Python
import os
|
|
import sys
|
|
import traceback
|
|
|
|
from qtpy import QtCore as QC
|
|
from qtpy import QtWidgets as QW
|
|
|
|
from hydrus.core import HydrusData
|
|
from hydrus.core import HydrusGlobals as HG
|
|
|
|
from hydrus.client import ClientConstants as CC
|
|
from hydrus.client import ClientData
|
|
from hydrus.client import ClientThreading
|
|
from hydrus.client.gui import ClientGUIDialogsQuick
|
|
from hydrus.client.gui import ClientGUIFunctions
|
|
from hydrus.client.gui import ClientGUITopLevelWindows
|
|
from hydrus.client.gui import QtPorting as QP
|
|
from hydrus.client.gui.networking import ClientGUINetworkJobControl
|
|
from hydrus.client.gui.widgets import ClientGUICommon
|
|
|
|
class PopupWindow( QW.QFrame ):
|
|
|
|
def __init__( self, parent, manager ):
|
|
|
|
QW.QFrame.__init__( self, parent )
|
|
|
|
self.setFrameStyle( QW.QFrame.Box | QW.QFrame.Plain )
|
|
|
|
self._manager = manager
|
|
|
|
self._widget_event_filter = QP.WidgetEventFilter( self )
|
|
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
|
|
|
|
def TryToDismiss( self ):
|
|
|
|
self._manager.Dismiss( self )
|
|
|
|
|
|
def EventDismiss( self, event ):
|
|
|
|
self.TryToDismiss()
|
|
|
|
|
|
class PopupMessage( PopupWindow ):
|
|
|
|
TEXT_CUTOFF = 1024
|
|
|
|
def __init__( self, parent, manager, job_key: ClientThreading.JobKey ):
|
|
|
|
PopupWindow.__init__( self, parent, manager )
|
|
|
|
self.setSizePolicy( QW.QSizePolicy.MinimumExpanding, QW.QSizePolicy.Fixed )
|
|
|
|
self._job_key = job_key
|
|
|
|
vbox = QP.VBoxLayout()
|
|
|
|
self._title = ClientGUICommon.BetterStaticText( self )
|
|
self._title.setAlignment( QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
|
|
|
|
font = self._title.font()
|
|
font.setBold( True )
|
|
self._title.setFont( font )
|
|
|
|
popup_message_character_width = HG.client_controller.new_options.GetInteger( 'popup_message_character_width' )
|
|
|
|
popup_char_width = ClientGUIFunctions.ConvertTextToPixelWidth( self._title, popup_message_character_width )
|
|
|
|
if HG.client_controller.new_options.GetBoolean( 'popup_message_force_min_width' ):
|
|
|
|
#QP.SetMinClientSize( self, ( wrap_width, -1 ) )
|
|
self.setFixedWidth( popup_char_width )
|
|
|
|
else:
|
|
|
|
self.setMaximumWidth( popup_char_width )
|
|
|
|
|
|
self._title.setWordWrap( True )
|
|
self._title_ev = QP.WidgetEventFilter( self._title )
|
|
self._title_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._title.hide()
|
|
|
|
self._text_1 = ClientGUICommon.BetterStaticText( self )
|
|
self._text_1.setWordWrap( True )
|
|
self._text_1_ev = QP.WidgetEventFilter( self._text_1 )
|
|
self._text_1_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._text_1.hide()
|
|
|
|
self._gauge_1 = ClientGUICommon.Gauge( self )
|
|
self._gauge_1_ev = QP.WidgetEventFilter( self._gauge_1 )
|
|
self._gauge_1_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._gauge_1.setMinimumWidth( int( popup_char_width * 0.9 ) )
|
|
self._gauge_1.hide()
|
|
|
|
self._text_2 = ClientGUICommon.BetterStaticText( self )
|
|
self._text_2.setWordWrap( True )
|
|
self._text_2_ev = QP.WidgetEventFilter( self._text_2 )
|
|
self._text_2_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._text_2.hide()
|
|
|
|
self._gauge_2 = ClientGUICommon.Gauge( self )
|
|
self._gauge_2_ev = QP.WidgetEventFilter( self._gauge_2 )
|
|
self._gauge_2_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._gauge_2.setMinimumWidth( int( popup_char_width * 0.9 ) )
|
|
self._gauge_2.hide()
|
|
|
|
self._text_yes_no = ClientGUICommon.BetterStaticText( self )
|
|
self._text_yes_no_ev = QP.WidgetEventFilter( self._text_yes_no )
|
|
self._text_yes_no_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._text_yes_no.hide()
|
|
|
|
self._yes = ClientGUICommon.BetterButton( self, 'yes', self._YesButton )
|
|
self._yes.hide()
|
|
|
|
self._no = ClientGUICommon.BetterButton( self, 'no', self._NoButton )
|
|
self._no.hide()
|
|
|
|
self._network_job_ctrl = ClientGUINetworkJobControl.NetworkJobControl( self )
|
|
self._network_job_ctrl.hide()
|
|
|
|
self._copy_to_clipboard_button = ClientGUICommon.BetterButton( self, 'copy to clipboard', self.CopyToClipboard )
|
|
self._copy_to_clipboard_button_ev = QP.WidgetEventFilter( self._copy_to_clipboard_button )
|
|
self._copy_to_clipboard_button_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._copy_to_clipboard_button.hide()
|
|
|
|
self._show_files_button = ClientGUICommon.BetterButton( self, 'show files', self.ShowFiles )
|
|
self._show_files_button_ev = QP.WidgetEventFilter( self._show_files_button )
|
|
self._show_files_button_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._show_files_button.hide()
|
|
|
|
self._user_callable_button = ClientGUICommon.BetterButton( self, 'run command', self.CallUserCallable )
|
|
self._user_callable_button_ev = QP.WidgetEventFilter( self._user_callable_button )
|
|
self._user_callable_button_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._user_callable_button.hide()
|
|
|
|
self._show_tb_button = ClientGUICommon.BetterButton( self, 'show traceback', self.ShowTB )
|
|
self._show_tb_button_ev = QP.WidgetEventFilter( self._show_tb_button )
|
|
self._show_tb_button_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._show_tb_button.hide()
|
|
|
|
self._tb_text = ClientGUICommon.BetterStaticText( self )
|
|
self._tb_text_ev = QP.WidgetEventFilter( self._tb_text )
|
|
self._tb_text_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._tb_text.setWordWrap( True )
|
|
self._tb_text.hide()
|
|
|
|
self._copy_tb_button = ClientGUICommon.BetterButton( self, 'copy traceback information', self.CopyTB )
|
|
self._copy_tb_button_ev = QP.WidgetEventFilter( self._copy_tb_button )
|
|
self._copy_tb_button_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._copy_tb_button.hide()
|
|
|
|
self._pause_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().pause, self.PausePlay )
|
|
self._pause_button_ev = QP.WidgetEventFilter( self._pause_button )
|
|
self._pause_button_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._pause_button.hide()
|
|
|
|
self._cancel_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().stop, self.Cancel )
|
|
self._cancel_button_ev = QP.WidgetEventFilter( self._cancel_button )
|
|
self._cancel_button_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
|
self._cancel_button.hide()
|
|
|
|
hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( hbox, self._pause_button, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( hbox, self._cancel_button, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
|
|
yes_no_hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( yes_no_hbox, self._yes, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( yes_no_hbox, self._no, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
|
|
QP.AddToLayout( vbox, self._title, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._text_1, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._gauge_1, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._text_2, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._gauge_2, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._text_yes_no, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, yes_no_hbox )
|
|
QP.AddToLayout( vbox, self._network_job_ctrl )
|
|
QP.AddToLayout( vbox, self._copy_to_clipboard_button )
|
|
QP.AddToLayout( vbox, self._show_files_button )
|
|
QP.AddToLayout( vbox, self._user_callable_button )
|
|
QP.AddToLayout( vbox, self._show_tb_button )
|
|
QP.AddToLayout( vbox, self._tb_text )
|
|
QP.AddToLayout( vbox, self._copy_tb_button )
|
|
QP.AddToLayout( vbox, hbox, CC.FLAGS_ON_RIGHT )
|
|
|
|
self.setLayout( vbox )
|
|
|
|
|
|
def _NoButton( self ):
|
|
|
|
self._job_key.SetVariable( 'popup_yes_no_answer', False )
|
|
|
|
self._job_key.Delete()
|
|
|
|
self._yes.hide()
|
|
self._no.hide()
|
|
|
|
|
|
def _ProcessText( self, text ):
|
|
|
|
text = str( text )
|
|
|
|
if len( text ) > self.TEXT_CUTOFF:
|
|
|
|
new_text = 'The text is too long to display here. Here is the start of it (the rest is printed to the log):'
|
|
|
|
new_text += os.linesep * 2
|
|
|
|
new_text += text[ : self.TEXT_CUTOFF ]
|
|
|
|
text = new_text
|
|
|
|
|
|
return text
|
|
|
|
|
|
def _YesButton( self ):
|
|
|
|
self._job_key.SetVariable( 'popup_yes_no_answer', True )
|
|
|
|
self._job_key.Delete()
|
|
|
|
self._yes.hide()
|
|
self._no.hide()
|
|
|
|
|
|
def CallUserCallable( self ):
|
|
|
|
user_callable = self._job_key.GetUserCallable()
|
|
|
|
if user_callable is not None:
|
|
|
|
user_callable()
|
|
|
|
|
|
|
|
def Cancel( self ):
|
|
|
|
self._job_key.Cancel()
|
|
|
|
self._pause_button.setEnabled( False )
|
|
self._cancel_button.setEnabled( False )
|
|
|
|
|
|
def CopyTB( self ):
|
|
|
|
HG.client_controller.pub( 'clipboard', 'text', self._job_key.ToString() )
|
|
|
|
|
|
def CopyToClipboard( self ):
|
|
|
|
result = self._job_key.GetIfHasVariable( 'popup_clipboard' )
|
|
|
|
if result is not None:
|
|
|
|
( title, text ) = result
|
|
|
|
HG.client_controller.pub( 'clipboard', 'text', text )
|
|
|
|
|
|
|
|
def PausePlay( self ):
|
|
|
|
self._job_key.PausePlay()
|
|
|
|
if self._job_key.IsPaused():
|
|
|
|
ClientGUIFunctions.SetBitmapButtonBitmap( self._pause_button, CC.global_pixmaps().play )
|
|
|
|
else:
|
|
|
|
ClientGUIFunctions.SetBitmapButtonBitmap( self._pause_button, CC.global_pixmaps().pause )
|
|
|
|
|
|
|
|
def ShowFiles( self ):
|
|
|
|
result = self._job_key.GetIfHasVariable( 'popup_files' )
|
|
|
|
if result is not None:
|
|
|
|
( popup_files, popup_files_name ) = result
|
|
|
|
HG.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_hashes = popup_files, page_name = popup_files_name )
|
|
|
|
|
|
|
|
def ShowTB( self ):
|
|
|
|
if self._tb_text.isVisible():
|
|
|
|
self._show_tb_button.setText( 'show traceback' )
|
|
|
|
self._tb_text.hide()
|
|
|
|
else:
|
|
|
|
self._show_tb_button.setText( 'hide traceback' )
|
|
|
|
self._tb_text.show()
|
|
|
|
|
|
self._manager.MakeSureEverythingFits()
|
|
|
|
|
|
def GetJobKey( self ):
|
|
|
|
return self._job_key
|
|
|
|
|
|
def IsDeleted( self ):
|
|
|
|
return self._job_key.IsDeleted()
|
|
|
|
|
|
def TryToDismiss( self ):
|
|
|
|
if self._job_key.IsPausable() or self._job_key.IsCancellable():
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
PopupWindow.TryToDismiss( self )
|
|
|
|
|
|
|
|
def UpdateMessage( self ):
|
|
|
|
title = self._job_key.GetStatusTitle()
|
|
|
|
if title is not None:
|
|
|
|
self._title.show()
|
|
|
|
self._title.setText( title )
|
|
|
|
else:
|
|
|
|
self._title.hide()
|
|
|
|
|
|
#
|
|
|
|
paused = self._job_key.IsPaused()
|
|
|
|
popup_text_1 = self._job_key.GetIfHasVariable( 'popup_text_1' )
|
|
|
|
if popup_text_1 is not None or paused:
|
|
|
|
if paused:
|
|
|
|
text = 'paused'
|
|
|
|
else:
|
|
|
|
text = popup_text_1
|
|
|
|
|
|
self._text_1.show()
|
|
|
|
self._text_1.setText( text )
|
|
|
|
else:
|
|
|
|
self._text_1.hide()
|
|
|
|
|
|
#
|
|
|
|
popup_gauge_1 = self._job_key.GetIfHasVariable( 'popup_gauge_1' )
|
|
|
|
if popup_gauge_1 is not None and not paused:
|
|
|
|
( gauge_value, gauge_range ) = popup_gauge_1
|
|
|
|
self._gauge_1.SetRange( gauge_range )
|
|
self._gauge_1.SetValue( gauge_value )
|
|
|
|
self._gauge_1.show()
|
|
|
|
else:
|
|
|
|
self._gauge_1.hide()
|
|
|
|
|
|
#
|
|
|
|
popup_text_2 = self._job_key.GetIfHasVariable( 'popup_text_2' )
|
|
|
|
if popup_text_2 is not None and not paused:
|
|
|
|
text = popup_text_2
|
|
|
|
self._text_2.setText( self._ProcessText( text ) )
|
|
|
|
self._text_2.show()
|
|
|
|
else:
|
|
|
|
self._text_2.hide()
|
|
|
|
|
|
#
|
|
|
|
popup_gauge_2 = self._job_key.GetIfHasVariable( 'popup_gauge_2' )
|
|
|
|
if popup_gauge_2 is not None and not paused:
|
|
|
|
( gauge_value, gauge_range ) = popup_gauge_2
|
|
|
|
self._gauge_2.SetRange( gauge_range )
|
|
self._gauge_2.SetValue( gauge_value )
|
|
|
|
self._gauge_2.show()
|
|
|
|
else:
|
|
|
|
self._gauge_2.hide()
|
|
|
|
|
|
#
|
|
|
|
popup_yes_no_question = self._job_key.GetIfHasVariable( 'popup_yes_no_question' )
|
|
|
|
if popup_yes_no_question is not None and not paused:
|
|
|
|
text = popup_yes_no_question
|
|
|
|
self._text_yes_no.setText( self._ProcessText( text ) )
|
|
|
|
self._text_yes_no.show()
|
|
|
|
self._yes.show()
|
|
self._no.show()
|
|
|
|
else:
|
|
|
|
self._text_yes_no.hide()
|
|
self._yes.hide()
|
|
self._no.hide()
|
|
|
|
|
|
#
|
|
|
|
network_job = self._job_key.GetNetworkJob()
|
|
|
|
if network_job is None:
|
|
|
|
self._network_job_ctrl.ClearNetworkJob()
|
|
|
|
self._network_job_ctrl.hide()
|
|
|
|
else:
|
|
|
|
self._network_job_ctrl.SetNetworkJob( network_job )
|
|
|
|
self._network_job_ctrl.show()
|
|
|
|
|
|
#
|
|
|
|
popup_clipboard = self._job_key.GetIfHasVariable( 'popup_clipboard' )
|
|
|
|
if popup_clipboard is not None:
|
|
|
|
( title, text ) = popup_clipboard
|
|
|
|
if self._copy_to_clipboard_button.text() != title:
|
|
|
|
self._copy_to_clipboard_button.setText( title )
|
|
|
|
|
|
self._copy_to_clipboard_button.show()
|
|
|
|
else:
|
|
|
|
self._copy_to_clipboard_button.hide()
|
|
|
|
|
|
#
|
|
|
|
result = self._job_key.GetIfHasVariable( 'popup_files' )
|
|
|
|
if result is not None:
|
|
|
|
( popup_files, popup_files_name ) = result
|
|
|
|
hashes = popup_files
|
|
|
|
text = popup_files_name + ' - show ' + HydrusData.ToHumanInt( len( hashes ) ) + ' files'
|
|
|
|
if self._show_files_button.text() != text:
|
|
|
|
self._show_files_button.setText( text )
|
|
|
|
|
|
self._show_files_button.show()
|
|
|
|
else:
|
|
|
|
self._show_files_button.hide()
|
|
|
|
|
|
#
|
|
|
|
user_callable = self._job_key.GetUserCallable()
|
|
|
|
if user_callable is None:
|
|
|
|
self._user_callable_button.hide()
|
|
|
|
else:
|
|
|
|
self._user_callable_button.setText( user_callable.GetLabel() )
|
|
|
|
self._user_callable_button.show()
|
|
|
|
|
|
#
|
|
|
|
popup_traceback = self._job_key.GetTraceback()
|
|
|
|
if popup_traceback is not None:
|
|
|
|
self._copy_tb_button.show()
|
|
self._show_tb_button.show()
|
|
|
|
text = popup_traceback
|
|
|
|
self._tb_text.setText( self._ProcessText( text ) )
|
|
|
|
# do not show automatically--that is up to the show button
|
|
|
|
else:
|
|
|
|
self._copy_tb_button.hide()
|
|
self._show_tb_button.hide()
|
|
self._tb_text.hide()
|
|
|
|
|
|
#
|
|
|
|
if self._job_key.IsPausable():
|
|
|
|
self._pause_button.show()
|
|
|
|
else:
|
|
|
|
self._pause_button.hide()
|
|
|
|
|
|
if self._job_key.IsCancellable():
|
|
|
|
self._cancel_button.show()
|
|
|
|
else:
|
|
|
|
self._cancel_button.hide()
|
|
|
|
# Dirty hack to reduce unnecessary resizing
|
|
|
|
if self.minimumWidth() < self.sizeHint().width():
|
|
|
|
self.setMinimumWidth( self.sizeHint().width() )
|
|
|
|
|
|
|
|
class PopupMessageManager( QW.QWidget ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
QW.QWidget.__init__( self, parent )
|
|
|
|
self.setWindowFlags( QC.Qt.Tool | QC.Qt.FramelessWindowHint )
|
|
|
|
self.setAttribute( QC.Qt.WA_ShowWithoutActivating )
|
|
|
|
self.setSizePolicy( QW.QSizePolicy.MinimumExpanding, QW.QSizePolicy.Preferred )
|
|
|
|
self._last_best_size_i_fit_on = ( 0, 0 )
|
|
|
|
self._max_messages_to_display = 10
|
|
|
|
vbox = QP.VBoxLayout()
|
|
|
|
self._message_panel = QW.QWidget( self )
|
|
|
|
self._message_vbox = QP.VBoxLayout( margin = 0 )
|
|
|
|
vbox.setSizeConstraint( QW.QLayout.SetFixedSize )
|
|
|
|
self._message_panel.setLayout( self._message_vbox )
|
|
self._message_panel.setSizePolicy( QW.QSizePolicy.MinimumExpanding, QW.QSizePolicy.Preferred )
|
|
|
|
self._summary_bar = PopupMessageSummaryBar( self, self )
|
|
self._summary_bar.setSizePolicy( QW.QSizePolicy.MinimumExpanding, QW.QSizePolicy.Preferred )
|
|
|
|
QP.AddToLayout( vbox, self._message_panel )
|
|
QP.AddToLayout( vbox, self._summary_bar )
|
|
|
|
self.setLayout( vbox )
|
|
|
|
self._pending_job_keys = []
|
|
|
|
self._gui_event_filter = QP.WidgetEventFilter( parent )
|
|
self._gui_event_filter.EVT_SIZE( self.EventParentMovedOrResized )
|
|
self._gui_event_filter.EVT_MOVE( self.EventParentMovedOrResized )
|
|
|
|
HG.client_controller.sub( self, 'AddMessage', 'message' )
|
|
|
|
self._old_excepthook = sys.excepthook
|
|
self._old_show_exception = HydrusData.ShowException
|
|
self._old_show_exception_tuple = HydrusData.ShowExceptionTuple
|
|
self._old_show_text = HydrusData.ShowText
|
|
|
|
sys.excepthook = ClientData.CatchExceptionClient
|
|
HydrusData.ShowException = ClientData.ShowExceptionClient
|
|
HydrusData.ShowExceptionTuple = ClientData.ShowExceptionTupleClient
|
|
HydrusData.ShowText = ClientData.ShowTextClient
|
|
|
|
job_key = ClientThreading.JobKey()
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'initialising popup message manager\u2026' )
|
|
|
|
self._update_job = HG.client_controller.CallRepeatingQtSafe( self, 0.25, 0.5, 'repeating popup message update', self.REPEATINGUpdate )
|
|
|
|
self._summary_bar.expandCollapse.connect( self.ExpandCollapse )
|
|
|
|
HG.client_controller.CallLaterQtSafe( self, 0.5, 'initialise message', self.AddMessage, job_key )
|
|
|
|
HG.client_controller.CallLaterQtSafe( self, 1.0, 'delete initial message', job_key.Delete )
|
|
|
|
|
|
def _CheckPending( self ):
|
|
|
|
self._pending_job_keys = [ job_key for job_key in self._pending_job_keys if not job_key.IsDeleted() ]
|
|
|
|
while len( self._pending_job_keys ) > 0 and self._message_vbox.count() < self._max_messages_to_display:
|
|
|
|
job_key = self._pending_job_keys.pop( 0 )
|
|
|
|
window = PopupMessage( self._message_panel, self, job_key )
|
|
|
|
window.UpdateMessage()
|
|
|
|
QP.AddToLayout( self._message_vbox, window )
|
|
|
|
|
|
total_messages = len( self._pending_job_keys ) + self._message_vbox.count()
|
|
|
|
self._summary_bar.SetNumMessages( total_messages )
|
|
|
|
if self._NeedsSizeOrShow():
|
|
|
|
self._SizeAndPositionAndShow()
|
|
|
|
|
|
|
|
def _DisplayingError( self ):
|
|
|
|
for i in range( self._message_vbox.count() ):
|
|
|
|
sizer_item = self._message_vbox.itemAt( i )
|
|
|
|
message_window = sizer_item.widget()
|
|
|
|
if not message_window:
|
|
|
|
continue
|
|
|
|
|
|
job_key = message_window.GetJobKey()
|
|
|
|
if job_key.HadError():
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
def _DoDebugHide( self ):
|
|
|
|
if not QP.isValid( self ):
|
|
|
|
return
|
|
|
|
|
|
parent = self.parentWidget()
|
|
|
|
possibly_on_hidden_virtual_desktop = not ClientGUIFunctions.MouseIsOnMyDisplay( parent )
|
|
|
|
going_to_bug_out_at_hide_or_show = possibly_on_hidden_virtual_desktop
|
|
|
|
new_options = HG.client_controller.new_options
|
|
|
|
if new_options.GetBoolean( 'hide_message_manager_on_gui_iconise' ) and not self._DisplayingError():
|
|
|
|
if parent.isMinimized():
|
|
|
|
self.hide()
|
|
|
|
return
|
|
|
|
|
|
|
|
current_focus_tlw = QW.QApplication.activeWindow()
|
|
|
|
main_gui_is_active = current_focus_tlw in ( self, parent )
|
|
|
|
if new_options.GetBoolean( 'hide_message_manager_on_gui_deactive' ) and not self._DisplayingError():
|
|
|
|
if not main_gui_is_active:
|
|
|
|
if not going_to_bug_out_at_hide_or_show:
|
|
|
|
self.hide()
|
|
|
|
|
|
|
|
|
|
|
|
def _NeedsSizeOrShow( self ):
|
|
|
|
num_messages_displayed = self._message_vbox.count()
|
|
|
|
there_is_stuff_to_display = num_messages_displayed > 0
|
|
|
|
is_shown = self.isVisible()
|
|
|
|
if there_is_stuff_to_display:
|
|
|
|
if not is_shown:
|
|
|
|
return True
|
|
|
|
|
|
best_size = self.sizeHint()
|
|
|
|
if best_size != self.size():
|
|
|
|
return True
|
|
|
|
|
|
else:
|
|
|
|
if is_shown:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
def _SizeAndPositionAndShow( self ):
|
|
|
|
try:
|
|
|
|
gui_frame = self.parentWidget()
|
|
|
|
gui_is_hidden = not gui_frame.isVisible()
|
|
|
|
going_to_bug_out_at_hide_or_show = gui_is_hidden
|
|
|
|
current_focus_tlw = QW.QApplication.activeWindow()
|
|
|
|
self_is_active = current_focus_tlw == self
|
|
|
|
main_gui_or_child_window_is_active = ClientGUIFunctions.TLWOrChildIsActive( gui_frame )
|
|
|
|
num_messages_displayed = self._message_vbox.count()
|
|
|
|
there_is_stuff_to_display = num_messages_displayed > 0
|
|
|
|
if there_is_stuff_to_display:
|
|
|
|
parent_size = gui_frame.size()
|
|
|
|
my_size = self.size()
|
|
|
|
my_x = ( parent_size.width() - my_size.width() ) - 20
|
|
my_y = ( parent_size.height() - my_size.height() ) - 25
|
|
|
|
if gui_frame.isVisible():
|
|
|
|
my_position = ClientGUIFunctions.ClientToScreen( gui_frame, QC.QPoint( my_x, my_y ) )
|
|
|
|
if my_position != self.pos():
|
|
|
|
self.move( my_position )
|
|
|
|
|
|
|
|
# Unhiding tends to raise the main gui tlw in some window managers, which is annoying if a media viewer window has focus
|
|
show_is_not_annoying = main_gui_or_child_window_is_active or self._DisplayingError()
|
|
|
|
ok_to_show = show_is_not_annoying and not going_to_bug_out_at_hide_or_show
|
|
|
|
if ok_to_show:
|
|
|
|
self.show()
|
|
|
|
|
|
else:
|
|
|
|
if not going_to_bug_out_at_hide_or_show:
|
|
|
|
self.hide()
|
|
|
|
|
|
|
|
except:
|
|
|
|
text = 'The popup message manager experienced a fatal error and will now stop working! Please restart the client as soon as possible! If this keeps happening, please email the details and your client.log to the hydrus developer.'
|
|
|
|
HydrusData.Print( text )
|
|
|
|
HydrusData.Print( traceback.format_exc() )
|
|
|
|
QW.QMessageBox.critical( gui_frame, 'Error', text )
|
|
|
|
self._update_job.Cancel()
|
|
|
|
self.CleanBeforeDestroy()
|
|
|
|
self.deleteLater()
|
|
|
|
|
|
|
|
def _GetAllMessageJobKeys( self ):
|
|
|
|
job_keys = []
|
|
|
|
for i in range( self._message_vbox.count() ):
|
|
|
|
sizer_item = self._message_vbox.itemAt( i )
|
|
|
|
message_window = sizer_item.widget()
|
|
|
|
if not message_window: continue
|
|
|
|
job_key = message_window.GetJobKey()
|
|
|
|
job_keys.append( job_key )
|
|
|
|
|
|
job_keys.extend( self._pending_job_keys )
|
|
|
|
return job_keys
|
|
|
|
|
|
def _OKToAlterUI( self ):
|
|
|
|
if not QP.isValid( self ): return
|
|
|
|
main_gui = self.parentWidget()
|
|
|
|
gui_is_hidden = not main_gui.isVisible()
|
|
|
|
if gui_is_hidden:
|
|
|
|
return False
|
|
|
|
|
|
if HG.client_controller.new_options.GetBoolean( 'freeze_message_manager_when_mouse_on_other_monitor' ):
|
|
|
|
# test both because when user uses a shortcut to send gui to a diff monitor, we can't chase it
|
|
# this may need a better test for virtual display dismissal
|
|
# this is also a proxy for hidden/virtual displays, which is really what it is going on about
|
|
on_my_monitor = ClientGUIFunctions.MouseIsOnMyDisplay( main_gui ) or ClientGUIFunctions.MouseIsOnMyDisplay( self )
|
|
|
|
if not on_my_monitor:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if HG.client_controller.new_options.GetBoolean( 'freeze_message_manager_when_main_gui_minimised' ):
|
|
|
|
main_gui_up = not main_gui.isMinimized()
|
|
|
|
if not main_gui_up:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
def _TryToMergeMessage( self, job_key ):
|
|
|
|
if not job_key.HasVariable( 'popup_files_mergable' ):
|
|
|
|
return False
|
|
|
|
|
|
result = job_key.GetIfHasVariable( 'popup_files' )
|
|
|
|
if result is not None:
|
|
|
|
( hashes, name ) = result
|
|
|
|
existing_job_keys = self._GetAllMessageJobKeys()
|
|
|
|
for existing_job_key in existing_job_keys:
|
|
|
|
if existing_job_key.HasVariable( 'popup_files_mergable' ):
|
|
|
|
result = existing_job_key.GetIfHasVariable( 'popup_files' )
|
|
|
|
if result is not None:
|
|
|
|
( existing_hashes, existing_name ) = result
|
|
|
|
if existing_name == name:
|
|
|
|
if isinstance( existing_hashes, list ):
|
|
|
|
existing_hashes.extend( hashes )
|
|
|
|
elif isinstance( existing_hashes, set ):
|
|
|
|
existing_hashes.update( hashes )
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
def _Update( self ):
|
|
|
|
if HG.view_shutdown:
|
|
|
|
self._update_job.Cancel()
|
|
|
|
self.CleanBeforeDestroy()
|
|
|
|
self.deleteLater()
|
|
|
|
return
|
|
|
|
|
|
for i in range( self._message_vbox.count() ):
|
|
|
|
sizer_item = self._message_vbox.itemAt( i )
|
|
|
|
message_window = sizer_item.widget()
|
|
|
|
if message_window:
|
|
|
|
if message_window.IsDeleted():
|
|
|
|
message_window.TryToDismiss()
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
message_window.UpdateMessage()
|
|
|
|
|
|
|
|
|
|
|
|
def AddMessage( self, job_key ):
|
|
|
|
try:
|
|
|
|
was_merged = self._TryToMergeMessage( job_key )
|
|
|
|
if was_merged:
|
|
|
|
return
|
|
|
|
|
|
self._pending_job_keys.append( job_key )
|
|
|
|
if self._OKToAlterUI():
|
|
|
|
self._CheckPending()
|
|
|
|
|
|
except:
|
|
|
|
HydrusData.Print( traceback.format_exc() )
|
|
|
|
|
|
|
|
def CleanBeforeDestroy( self ):
|
|
|
|
for job_key in self._pending_job_keys:
|
|
|
|
if job_key.IsCancellable():
|
|
|
|
job_key.Cancel()
|
|
|
|
|
|
|
|
for i in range( self._message_vbox.count() ):
|
|
|
|
message_window = self._message_vbox.itemAt( i ).widget()
|
|
|
|
if not message_window: continue
|
|
|
|
job_key = message_window.GetJobKey()
|
|
|
|
if job_key.IsCancellable():
|
|
|
|
job_key.Cancel()
|
|
|
|
|
|
|
|
sys.excepthook = self._old_excepthook
|
|
|
|
HydrusData.ShowException = self._old_show_exception
|
|
HydrusData.ShowExceptionTuple = self._old_show_exception_tuple
|
|
HydrusData.ShowText = self._old_show_text
|
|
|
|
|
|
def Dismiss( self, window ):
|
|
|
|
self._message_vbox.removeWidget( window )
|
|
|
|
window.deleteLater()
|
|
|
|
if self._OKToAlterUI():
|
|
|
|
self._CheckPending()
|
|
|
|
|
|
|
|
def DismissAll( self ):
|
|
|
|
self._pending_job_keys = [ job_key for job_key in self._pending_job_keys if job_key.IsPausable() or job_key.IsCancellable() ]
|
|
|
|
items = []
|
|
|
|
for i in range( self._message_vbox.count() ):
|
|
|
|
items.append( self._message_vbox.itemAt( i ) )
|
|
|
|
|
|
for item in items:
|
|
|
|
message_window = item.widget()
|
|
|
|
if not message_window: continue
|
|
|
|
message_window.TryToDismiss()
|
|
|
|
|
|
self._CheckPending()
|
|
|
|
|
|
def ExpandCollapse( self ):
|
|
|
|
if self._message_panel.isVisible():
|
|
|
|
self._message_panel.setVisible( False )
|
|
|
|
else:
|
|
|
|
self._message_panel.show()
|
|
|
|
|
|
self.MakeSureEverythingFits()
|
|
|
|
|
|
def resizeEvent( self, event ):
|
|
|
|
if not self or not QP.isValid( self ): # funny runtime error caused this
|
|
|
|
return
|
|
|
|
|
|
if self._OKToAlterUI():
|
|
|
|
self._SizeAndPositionAndShow()
|
|
|
|
|
|
event.ignore()
|
|
|
|
|
|
def EventParentMovedOrResized( self, event ):
|
|
|
|
if not self or not QP.isValid( self ): # funny runtime error caused this
|
|
|
|
return
|
|
|
|
|
|
if self._OKToAlterUI():
|
|
|
|
self._SizeAndPositionAndShow()
|
|
|
|
|
|
return True # was: event.ignore()
|
|
|
|
|
|
def MakeSureEverythingFits( self ):
|
|
|
|
if self._OKToAlterUI():
|
|
|
|
self._SizeAndPositionAndShow()
|
|
|
|
|
|
|
|
def REPEATINGUpdate( self ):
|
|
|
|
try:
|
|
|
|
self._DoDebugHide()
|
|
|
|
if self._OKToAlterUI():
|
|
|
|
self._Update()
|
|
|
|
self._CheckPending()
|
|
|
|
|
|
except:
|
|
|
|
self._update_job.Cancel()
|
|
|
|
raise
|
|
|
|
|
|
|
|
# This was originally a reviewpanel subclass which is a scroll area subclass, but having it in a scroll area didn't work out with dynamically updating size as the widget contents change.
|
|
class PopupMessageDialogPanel( QW.QWidget ):
|
|
|
|
def __init__( self, parent, job_key, hide_main_gui = False ):
|
|
|
|
QW.QWidget.__init__( self, parent )
|
|
|
|
self._yesno_open = False
|
|
|
|
self._hide_main_gui = hide_main_gui
|
|
|
|
self._job_key = job_key
|
|
|
|
self._message_window = PopupMessage( self, self, self._job_key )
|
|
|
|
vbox = QP.VBoxLayout()
|
|
|
|
QP.AddToLayout( vbox, self._message_window )
|
|
|
|
self.setLayout( vbox )
|
|
|
|
self._windows_hidden = []
|
|
|
|
self._HideOtherWindows()
|
|
|
|
self._message_pubbed = False
|
|
|
|
self._update_job = HG.client_controller.CallRepeatingQtSafe( self, 0.25, 0.5, 'repeating popup dialog update', self.REPEATINGUpdate )
|
|
|
|
|
|
def CleanBeforeDestroy( self ):
|
|
|
|
pass
|
|
|
|
|
|
def _HideOtherWindows( self ):
|
|
|
|
from hydrus.client.gui import ClientGUI
|
|
|
|
for tlw in QW.QApplication.topLevelWidgets():
|
|
|
|
if isinstance( tlw, ClientGUI.FrameGUI ):
|
|
|
|
pass
|
|
|
|
|
|
if tlw == self.window():
|
|
|
|
continue
|
|
|
|
|
|
if not isinstance( tlw, ( ClientGUITopLevelWindows.Frame, ClientGUITopLevelWindows.MainFrame, ClientGUITopLevelWindows.NewDialog ) ):
|
|
|
|
continue
|
|
|
|
|
|
if ClientGUIFunctions.IsQtAncestor( self, tlw, through_tlws = True ):
|
|
|
|
continue
|
|
|
|
|
|
if isinstance( tlw, ClientGUI.FrameGUI ) and not self._hide_main_gui:
|
|
|
|
continue
|
|
|
|
|
|
if not tlw.isVisible() or tlw.isMinimized():
|
|
|
|
continue
|
|
|
|
|
|
tlw.hide()
|
|
|
|
self._windows_hidden.append( tlw )
|
|
|
|
|
|
|
|
def _ReleaseMessage( self ):
|
|
|
|
if not self._message_pubbed:
|
|
|
|
HG.client_controller.pub( 'message', self._job_key )
|
|
|
|
self._message_pubbed = True
|
|
|
|
self._RestoreOtherWindows()
|
|
|
|
|
|
|
|
def _RestoreOtherWindows( self ):
|
|
|
|
for tlw in self._windows_hidden:
|
|
|
|
tlw.show()
|
|
|
|
|
|
self._windows_hidden = []
|
|
|
|
|
|
def _Update( self ):
|
|
|
|
self._message_window.UpdateMessage()
|
|
|
|
# Resize the window after updates
|
|
# The problem is that when the window is created, the initial size is too small because all widgets are empty before the first update,
|
|
# but after it updates it doesn't want to resize the window, rather it just adds scrollbars.
|
|
# This is a manual fix. Need to find a better solution...
|
|
|
|
self.layout().update()
|
|
self._message_window.adjustSize()
|
|
self.adjustSize()
|
|
self.window().adjustSize()
|
|
|
|
|
|
def Dismiss( self, window ):
|
|
|
|
return
|
|
|
|
|
|
def CheckValid( self ):
|
|
|
|
return True
|
|
|
|
|
|
def UserIsOKToOK( self ):
|
|
|
|
return self.UserIsOKToCancel()
|
|
|
|
|
|
def UserIsOKToCancel( self ):
|
|
|
|
if self._job_key.IsDone():
|
|
|
|
self._ReleaseMessage()
|
|
|
|
elif self._job_key.IsCancellable():
|
|
|
|
text = 'Cancel/stop job?'
|
|
|
|
self._yesno_open = True
|
|
|
|
try:
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, text )
|
|
|
|
finally:
|
|
|
|
self._yesno_open = False
|
|
|
|
|
|
if result == QW.QDialog.Accepted:
|
|
|
|
self._job_key.Cancel()
|
|
|
|
self._ReleaseMessage()
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
else:
|
|
|
|
QW.QMessageBox.warning( self, 'Warning', 'Unfortunately, this job cannot be cancelled. If it really is taking too long, please kill the client through task manager.' )
|
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
def REPEATINGUpdate( self ):
|
|
|
|
try:
|
|
|
|
if self._job_key.IsDone():
|
|
|
|
if not self._yesno_open: # don't close while a child dialog open m8
|
|
|
|
parent = self.parentWidget()
|
|
|
|
if parent.isModal(): # event sometimes fires after modal done
|
|
|
|
parent.DoOK()
|
|
|
|
|
|
|
|
else:
|
|
|
|
self._Update()
|
|
|
|
|
|
except:
|
|
|
|
self._update_job.Cancel()
|
|
|
|
raise
|
|
|
|
|
|
|
|
class PopupMessageSummaryBar( PopupWindow ):
|
|
|
|
expandCollapse = QC.Signal()
|
|
|
|
def __init__( self, parent, manager ):
|
|
|
|
PopupWindow.__init__( self, parent, manager )
|
|
|
|
hbox = QP.HBoxLayout()
|
|
|
|
self._text = ClientGUICommon.BetterStaticText( self )
|
|
|
|
self._expand_collapse = ClientGUICommon.BetterButton( self, '\u25bc', self.ExpandCollapse )
|
|
|
|
dismiss_all = ClientGUICommon.BetterButton( self, 'dismiss all', self._manager.DismissAll )
|
|
|
|
QP.AddToLayout( hbox, self._text, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
hbox.addStretch( 1 )
|
|
QP.AddToLayout( hbox, dismiss_all, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( hbox, self._expand_collapse, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
|
|
self.setLayout( hbox )
|
|
|
|
|
|
def ExpandCollapse( self ):
|
|
|
|
self.expandCollapse.emit()
|
|
|
|
current_text = self._expand_collapse.text()
|
|
|
|
if current_text == '\u25bc':
|
|
|
|
new_text = '\u25b2'
|
|
|
|
else:
|
|
|
|
new_text = '\u25bc'
|
|
|
|
|
|
self._expand_collapse.setText( new_text )
|
|
|
|
|
|
def TryToDismiss( self ):
|
|
|
|
pass
|
|
|
|
|
|
def SetNumMessages( self, num_messages_pending ):
|
|
|
|
if num_messages_pending == 1:
|
|
|
|
self._text.setText( '1 message' )
|
|
|
|
else:
|
|
|
|
self._text.setText( HydrusData.ToHumanInt(num_messages_pending)+' messages' )
|
|
|
|
|