524 lines
16 KiB
Python
524 lines
16 KiB
Python
import typing
|
|
|
|
from qtpy import QtCore as QC
|
|
from qtpy import QtWidgets as QW
|
|
|
|
from hydrus.core import HydrusConstants as HC
|
|
from hydrus.core import HydrusData
|
|
from hydrus.core import HydrusExceptions
|
|
from hydrus.core import HydrusGlobals as HG
|
|
from hydrus.core import HydrusText
|
|
from hydrus.core.networking import HydrusNetworking
|
|
|
|
from hydrus.client import ClientConstants as CC
|
|
from hydrus.client.gui import ClientGUIFunctions
|
|
from hydrus.client.gui import ClientGUIScrolledPanels
|
|
from hydrus.client.gui import ClientGUITime
|
|
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
|
|
from hydrus.client.gui import QtPorting as QP
|
|
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
|
|
from hydrus.client.gui.lists import ClientGUIListCtrl
|
|
from hydrus.client.gui.widgets import ClientGUICommon
|
|
|
|
class BandwidthRulesCtrl( ClientGUICommon.StaticBox ):
|
|
|
|
def __init__( self, parent, bandwidth_rules ):
|
|
|
|
ClientGUICommon.StaticBox.__init__( self, parent, 'bandwidth rules' )
|
|
|
|
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
|
|
|
|
# example for later:
|
|
'''
|
|
def sort_call( desired_columns, rule ):
|
|
|
|
( bandwidth_type, time_delta, max_allowed ) = rule
|
|
|
|
sort_time_delta = SafeNoneInt( time_delta )
|
|
|
|
result = {}
|
|
|
|
result[ CGLC.COLUMN_LIST_BANDWIDTH_RULES.MAX_ALLOWED ] = max_allowed
|
|
result[ CGLC.COLUMN_LIST_BANDWIDTH_RULES.EVERY ] = sort_time_delta
|
|
|
|
return result
|
|
|
|
|
|
def display_call( desired_columns, rule ):
|
|
|
|
( bandwidth_type, time_delta, max_allowed ) = rule
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
pretty_max_allowed = HydrusData.ToHumanBytes( max_allowed )
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
pretty_max_allowed = '{} requests'.format( HydrusData.ToHumanInt( max_allowed ) )
|
|
|
|
|
|
pretty_time_delta = HydrusData.TimeDeltaToPrettyTimeDelta( time_delta )
|
|
|
|
result = {}
|
|
|
|
result[ CGLC.COLUMN_LIST_BANDWIDTH_RULES.MAX_ALLOWED ] = pretty_max_allowed
|
|
result[ CGLC.COLUMN_LIST_BANDWIDTH_RULES.EVERY ] = pretty_time_delta
|
|
|
|
return result
|
|
|
|
'''
|
|
|
|
self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, CGLC.COLUMN_LIST_BANDWIDTH_RULES.ID, 8, self._ConvertRuleToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )
|
|
|
|
listctrl_panel.SetListCtrl( self._listctrl )
|
|
|
|
listctrl_panel.AddButton( 'add', self._Add )
|
|
listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
|
|
listctrl_panel.AddDeleteButton()
|
|
|
|
#
|
|
|
|
self._listctrl.AddDatas( bandwidth_rules.GetRules() )
|
|
|
|
self._listctrl.Sort()
|
|
|
|
#
|
|
|
|
self.Add( listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
|
|
def _Add( self ):
|
|
|
|
rule = ( HC.BANDWIDTH_TYPE_DATA, None, 1024 * 1024 * 100 )
|
|
|
|
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit rule' ) as dlg:
|
|
|
|
panel = self._EditPanel( dlg, rule )
|
|
|
|
dlg.SetPanel( panel )
|
|
|
|
if dlg.exec() == QW.QDialog.Accepted:
|
|
|
|
new_rule = panel.GetValue()
|
|
|
|
self._listctrl.AddDatas( ( new_rule, ) )
|
|
|
|
self._listctrl.Sort()
|
|
|
|
|
|
|
|
|
|
def _ConvertRuleToListCtrlTuples( self, rule ):
|
|
|
|
( bandwidth_type, time_delta, max_allowed ) = rule
|
|
|
|
pretty_time_delta = HydrusData.TimeDeltaToPrettyTimeDelta( time_delta )
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
pretty_max_allowed = HydrusData.ToHumanBytes( max_allowed )
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
pretty_max_allowed = HydrusData.ToHumanInt( max_allowed ) + ' requests'
|
|
|
|
|
|
sort_time_delta = ClientGUIListCtrl.SafeNoneInt( time_delta )
|
|
|
|
sort_tuple = ( max_allowed, sort_time_delta )
|
|
display_tuple = ( pretty_max_allowed, pretty_time_delta )
|
|
|
|
return ( display_tuple, sort_tuple )
|
|
|
|
|
|
def _Edit( self ):
|
|
|
|
selected_rules = self._listctrl.GetData( only_selected = True )
|
|
|
|
edited_datas = []
|
|
|
|
for rule in selected_rules:
|
|
|
|
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit rule' ) as dlg:
|
|
|
|
panel = self._EditPanel( dlg, rule )
|
|
|
|
dlg.SetPanel( panel )
|
|
|
|
if dlg.exec() == QW.QDialog.Accepted:
|
|
|
|
edited_rule = panel.GetValue()
|
|
|
|
self._listctrl.DeleteDatas( ( rule, ) )
|
|
|
|
self._listctrl.AddDatas( ( edited_rule, ) )
|
|
|
|
edited_datas.append( edited_rule )
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
self._listctrl.SelectDatas( edited_datas )
|
|
|
|
self._listctrl.Sort()
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
bandwidth_rules = HydrusNetworking.BandwidthRules()
|
|
|
|
for rule in self._listctrl.GetData():
|
|
|
|
( bandwidth_type, time_delta, max_allowed ) = rule
|
|
|
|
bandwidth_rules.AddRule( bandwidth_type, time_delta, max_allowed )
|
|
|
|
|
|
return bandwidth_rules
|
|
|
|
|
|
class _EditPanel( ClientGUIScrolledPanels.EditPanel ):
|
|
|
|
def __init__( self, parent, rule ):
|
|
|
|
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
|
|
|
|
self._bandwidth_type = ClientGUICommon.BetterChoice( self )
|
|
|
|
self._bandwidth_type.addItem( 'data', HC.BANDWIDTH_TYPE_DATA )
|
|
self._bandwidth_type.addItem( 'requests', HC.BANDWIDTH_TYPE_REQUESTS )
|
|
|
|
self._bandwidth_type.currentIndexChanged.connect( self._UpdateEnabled )
|
|
|
|
self._max_allowed_bytes = BytesControl( self )
|
|
self._max_allowed_requests = ClientGUICommon.BetterSpinBox( self, min=1, max=1048576 )
|
|
|
|
self._time_delta = ClientGUITime.TimeDeltaButton( self, min = 1, days = True, hours = True, minutes = True, seconds = True, monthly_allowed = True )
|
|
|
|
#
|
|
|
|
( bandwidth_type, time_delta, max_allowed ) = rule
|
|
|
|
self._bandwidth_type.SetValue( bandwidth_type )
|
|
|
|
self._time_delta.SetValue( time_delta )
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
self._max_allowed_bytes.SetValue( max_allowed )
|
|
|
|
else:
|
|
|
|
self._max_allowed_requests.setValue( max_allowed )
|
|
|
|
|
|
self._UpdateEnabled()
|
|
|
|
#
|
|
|
|
hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( hbox, self._max_allowed_bytes, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( hbox, self._max_allowed_requests, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( hbox, self._bandwidth_type, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,' every '), CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( hbox, self._time_delta, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
|
|
self.widget().setLayout( hbox )
|
|
|
|
|
|
def _UpdateEnabled( self ):
|
|
|
|
bandwidth_type = self._bandwidth_type.GetValue()
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
self._max_allowed_bytes.show()
|
|
self._max_allowed_requests.hide()
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
self._max_allowed_bytes.hide()
|
|
self._max_allowed_requests.show()
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
bandwidth_type = self._bandwidth_type.GetValue()
|
|
|
|
time_delta = self._time_delta.GetValue()
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
max_allowed = self._max_allowed_bytes.GetValue()
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
max_allowed = self._max_allowed_requests.value()
|
|
|
|
|
|
return ( bandwidth_type, time_delta, max_allowed )
|
|
|
|
|
|
|
|
class BytesControl( QW.QWidget ):
|
|
|
|
valueChanged = QC.Signal()
|
|
|
|
def __init__( self, parent, initial_value = 65536 ):
|
|
|
|
QW.QWidget.__init__( self, parent )
|
|
|
|
self._spin = ClientGUICommon.BetterSpinBox( self, min=0, max=1048576 )
|
|
|
|
self._unit = ClientGUICommon.BetterChoice( self )
|
|
|
|
self._unit.addItem( 'B', 1 )
|
|
self._unit.addItem( 'KB', 1024 )
|
|
self._unit.addItem( 'MB', 1024 * 1024 )
|
|
self._unit.addItem( 'GB', 1024 * 1024 * 1024 )
|
|
|
|
#
|
|
|
|
self.SetValue( initial_value )
|
|
|
|
#
|
|
|
|
hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( hbox, self._spin, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( hbox, self._unit, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
|
|
self.setLayout( hbox )
|
|
|
|
min_width = ClientGUIFunctions.ConvertTextToPixelWidth( self._unit, 8 )
|
|
|
|
self._unit.setMinimumWidth( min_width )
|
|
|
|
self._spin.valueChanged.connect( self._HandleValueChanged )
|
|
self._unit.currentIndexChanged.connect( self._HandleValueChanged )
|
|
|
|
|
|
def _HandleValueChanged( self, val ):
|
|
|
|
self.valueChanged.emit()
|
|
|
|
|
|
def GetSeparatedValue( self ):
|
|
|
|
return (self._spin.value(), self._unit.GetValue())
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
return self._spin.value() * self._unit.GetValue()
|
|
|
|
|
|
def SetSeparatedValue( self, value, unit ):
|
|
|
|
return (self._spin.setValue( value ), self._unit.SetValue( unit ))
|
|
|
|
|
|
def SetValue( self, value: int ):
|
|
|
|
max_unit = 1024 * 1024 * 1024
|
|
|
|
unit = 1
|
|
|
|
while value % 1024 == 0 and unit < max_unit:
|
|
|
|
value //= 1024
|
|
|
|
unit *= 1024
|
|
|
|
|
|
self._spin.setValue( value )
|
|
self._unit.SetValue( unit )
|
|
|
|
|
|
class NoneableBytesControl( QW.QWidget ):
|
|
|
|
valueChanged = QC.Signal()
|
|
|
|
def __init__( self, parent, initial_value = 65536, none_label = 'no limit' ):
|
|
|
|
QW.QWidget.__init__( self, parent )
|
|
|
|
self._bytes = BytesControl( self )
|
|
|
|
self._none_checkbox = QW.QCheckBox( none_label, self )
|
|
|
|
#
|
|
|
|
self.SetValue( initial_value )
|
|
|
|
#
|
|
|
|
hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( hbox, self._bytes, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
QP.AddToLayout( hbox, self._none_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
|
|
self.setLayout( hbox )
|
|
|
|
#
|
|
|
|
self._none_checkbox.clicked.connect( self._UpdateEnabled )
|
|
|
|
self._bytes.valueChanged.connect( self._HandleValueChanged )
|
|
self._none_checkbox.clicked.connect( self._HandleValueChanged )
|
|
|
|
|
|
def _UpdateEnabled( self ):
|
|
|
|
if self._none_checkbox.isChecked():
|
|
|
|
self._bytes.setEnabled( False )
|
|
|
|
else:
|
|
|
|
self._bytes.setEnabled( True )
|
|
|
|
|
|
|
|
def _HandleValueChanged( self ):
|
|
|
|
self.valueChanged.emit()
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
if self._none_checkbox.isChecked():
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return self._bytes.GetValue()
|
|
|
|
|
|
|
|
def setToolTip( self, text ):
|
|
|
|
QW.QWidget.setToolTip( self, text )
|
|
|
|
for c in self.children():
|
|
|
|
if isinstance( c, QW.QWidget ):
|
|
|
|
c.setToolTip( text )
|
|
|
|
|
|
|
|
def SetValue( self, value ):
|
|
|
|
if value is None:
|
|
|
|
self._none_checkbox.setChecked( True )
|
|
|
|
else:
|
|
|
|
self._none_checkbox.setChecked( False )
|
|
|
|
self._bytes.SetValue( value )
|
|
|
|
|
|
self._UpdateEnabled()
|
|
|
|
|
|
class TextAndPasteCtrl( QW.QWidget ):
|
|
|
|
def __init__( self, parent, add_callable, allow_empty_input = False ):
|
|
|
|
self._add_callable = add_callable
|
|
self._allow_empty_input = allow_empty_input
|
|
|
|
QW.QWidget.__init__( self, parent )
|
|
|
|
self._text_input = QW.QLineEdit( self )
|
|
self._text_input.installEventFilter( ClientGUICommon.TextCatchEnterEventFilter( self._text_input, self.EnterText ) )
|
|
|
|
self._paste_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().paste, self._Paste )
|
|
self._paste_button.setToolTip( 'Paste multiple inputs from the clipboard. Assumes the texts are newline-separated.' )
|
|
|
|
self.setFocusProxy( self._text_input )
|
|
|
|
#
|
|
|
|
hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( hbox, self._text_input, CC.FLAGS_EXPAND_BOTH_WAYS )
|
|
QP.AddToLayout( hbox, self._paste_button, CC.FLAGS_CENTER_PERPENDICULAR )
|
|
|
|
self.setLayout( hbox )
|
|
|
|
|
|
def _Paste( self ):
|
|
|
|
try:
|
|
|
|
raw_text = HG.client_controller.GetClipboardText()
|
|
|
|
except HydrusExceptions.DataMissing as e:
|
|
|
|
QW.QMessageBox.critical( self, 'Error', str(e) )
|
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
texts = [ text for text in HydrusText.DeserialiseNewlinedTexts( raw_text ) ]
|
|
|
|
if not self._allow_empty_input:
|
|
|
|
texts = [ text for text in texts if text != '' ]
|
|
|
|
|
|
if len( texts ) > 0:
|
|
|
|
self._add_callable( texts )
|
|
|
|
|
|
except:
|
|
|
|
QW.QMessageBox.critical( self, 'Error', 'I could not understand what was in the clipboard' )
|
|
|
|
|
|
|
|
def EnterText( self ):
|
|
|
|
text = self._text_input.text()
|
|
|
|
text = HydrusText.StripIOInputLine( text )
|
|
|
|
if text == '' and not self._allow_empty_input:
|
|
|
|
return
|
|
|
|
|
|
self._add_callable( ( text, ) )
|
|
|
|
self._text_input.clear()
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
return self._text_input.text()
|
|
|
|
|
|
def setPlaceholderText( self, text ):
|
|
|
|
self._text_input.setPlaceholderText( text )
|
|
|
|
|
|
def SetValue( self, text ):
|
|
|
|
self._text_input.setText( text )
|
|
|
|
|