hydrus/hydrus/client/gui/parsing/ClientGUIParsingFormulae.py

1334 lines
49 KiB
Python

import os
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 HydrusTime
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientParsing
from hydrus.client import ClientPaths
from hydrus.client import ClientStrings
from hydrus.client.gui import ClientGUIDialogsQuick
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIScrolledPanels
from hydrus.client.gui import ClientGUIStringControls
from hydrus.client.gui import ClientGUIStringPanels
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.parsing import ClientGUIParsingTest
from hydrus.client.gui.widgets import ClientGUICommon
from hydrus.client.gui.widgets import ClientGUIMenuButton
class EditSpecificFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent: QW.QWidget, collapse_newlines: bool ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._collapse_newlines = collapse_newlines
def GetValue( self ):
raise NotImplementedError()
class EditCompoundFormulaPanel( EditSpecificFormulaPanel ):
def __init__( self, parent: QW.QWidget, collapse_newlines: bool, formula: ClientParsing.ParseFormulaCompound, test_data: ClientParsing.ParsingTestData ):
EditSpecificFormulaPanel.__init__( self, parent, collapse_newlines )
#
menu_items = []
page_func = HydrusData.Call( ClientPaths.LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'downloader_parsers_formulae.html#compound_formula' ) )
menu_items.append( ( 'normal', 'open the compound formula help', 'Open the help page for compound formulae in your web browser.', page_func ) )
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
test_panel = ClientGUICommon.StaticBox( self, 'test' )
self._test_panel = ClientGUIParsingTest.TestPanelFormula( test_panel, self.GetValue, test_data = test_data )
self._test_panel.SetCollapseNewlines( self._collapse_newlines )
#
edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
self._formulae = QW.QListWidget( edit_panel )
self._formulae.setSelectionMode( QW.QAbstractItemView.SingleSelection )
self._formulae.itemDoubleClicked.connect( self.Edit )
self._add_formula = ClientGUICommon.BetterButton( edit_panel, 'add', self.Add )
self._edit_formula = ClientGUICommon.BetterButton( edit_panel, 'edit', self.Edit )
self._move_formula_up = ClientGUICommon.BetterButton( edit_panel, '\u2191', self.MoveUp )
self._delete_formula = ClientGUICommon.BetterButton( edit_panel, 'X', self.Delete )
self._move_formula_down = ClientGUICommon.BetterButton( edit_panel, '\u2193', self.MoveDown )
self._sub_phrase = QW.QLineEdit( edit_panel )
formulae = formula.GetFormulae()
sub_phrase = formula.GetSubstitutionPhrase()
string_processor = formula.GetStringProcessor()
self._string_processor_button = ClientGUIStringControls.StringProcessorButton( edit_panel, string_processor, self._test_panel.GetTestDataForStringProcessor )
#
for formula in formulae:
pretty_formula = formula.ToPrettyString()
item = QW.QListWidgetItem()
item.setText( pretty_formula )
item.setData( QC.Qt.UserRole, formula )
self._formulae.addItem( item )
self._sub_phrase.setText( sub_phrase )
#
udd_button_vbox = QP.VBoxLayout()
udd_button_vbox.addStretch( 1 )
QP.AddToLayout( udd_button_vbox, self._move_formula_up, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( udd_button_vbox, self._delete_formula, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( udd_button_vbox, self._move_formula_down, CC.FLAGS_CENTER_PERPENDICULAR )
udd_button_vbox.addStretch( 1 )
formulae_hbox = QP.HBoxLayout()
QP.AddToLayout( formulae_hbox, self._formulae, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( formulae_hbox, udd_button_vbox, CC.FLAGS_CENTER_PERPENDICULAR )
ae_button_hbox = QP.HBoxLayout()
QP.AddToLayout( ae_button_hbox, self._add_formula, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( ae_button_hbox, self._edit_formula, CC.FLAGS_CENTER_PERPENDICULAR )
rows = []
rows.append( ( 'substitution phrase:', self._sub_phrase ) )
gridbox = ClientGUICommon.WrapInGrid( edit_panel, rows )
edit_panel.Add( formulae_hbox, CC.FLAGS_EXPAND_BOTH_WAYS )
edit_panel.Add( ae_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, 'Newlines are removed from parsed strings right after parsing, before string processing.', ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
edit_panel.Add( self._string_processor_button, CC.FLAGS_EXPAND_PERPENDICULAR )
#
test_panel.Add( self._test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
#
hbox = QP.HBoxLayout()
QP.AddToLayout( hbox, edit_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_ON_RIGHT )
QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.widget().setLayout( vbox )
def Add( self ):
existing_formula = ClientParsing.ParseFormulaHTML()
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit formula', frame_key = 'deeply_nested_dialog' ) as dlg:
panel = EditFormulaPanel( dlg, existing_formula, self._test_panel.GetTestDataForChild )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
new_formula = panel.GetValue()
pretty_formula = new_formula.ToPrettyString()
item = QW.QListWidgetItem()
item.setText( pretty_formula )
item.setData( QC.Qt.UserRole, new_formula )
self._formulae.addItem( item )
def Delete( self ):
selection = QP.ListWidgetGetSelection( self._formulae )
if selection != -1:
if self._formulae.count() == 1:
QW.QMessageBox.critical( self, 'Error', 'A compound formula needs at least one sub-formula!' )
else:
QP.ListWidgetDelete( self._formulae, selection )
def Edit( self ):
selection = QP.ListWidgetGetSelection( self._formulae )
if selection != -1:
old_formula = QP.GetClientData( self._formulae, selection )
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit formula', frame_key = 'deeply_nested_dialog' ) as dlg:
panel = EditFormulaPanel( dlg, old_formula, self._test_panel.GetTestDataForChild )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
new_formula = panel.GetValue()
pretty_formula = new_formula.ToPrettyString()
self._formulae.item( selection ).setText( pretty_formula )
self._formulae.item( selection ).setData( QC.Qt.UserRole, new_formula )
def GetValue( self ):
formulae = [ QP.GetClientData( self._formulae, i ) for i in range( self._formulae.count() ) ]
sub_phrase = self._sub_phrase.text()
string_processor = self._string_processor_button.GetValue()
formula = ClientParsing.ParseFormulaCompound( formulae, sub_phrase, string_processor )
return formula
def MoveDown( self ):
selection = QP.ListWidgetGetSelection( self._formulae )
if selection != -1 and selection + 1 < self._formulae.count():
pretty_rule = self._formulae.item( selection ).text()
rule = QP.GetClientData( self._formulae, selection )
QP.ListWidgetDelete( self._formulae, selection )
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._formulae.insertItem( selection + 1, item )
def MoveUp( self ):
selection = QP.ListWidgetGetSelection( self._formulae )
if selection != -1 and selection > 0:
pretty_rule = self._formulae.item( selection ).text()
rule = QP.GetClientData( self._formulae, selection )
QP.ListWidgetDelete( self._formulae, selection )
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._formulae.insertItem( selection - 1, item )
class EditContextVariableFormulaPanel( EditSpecificFormulaPanel ):
def __init__( self, parent: QW.QWidget, collapse_newlines: bool, formula: ClientParsing.ParseFormulaContextVariable, test_data: ClientParsing.ParsingTestData ):
EditSpecificFormulaPanel.__init__( self, parent, collapse_newlines )
#
menu_items = []
page_func = HydrusData.Call( ClientPaths.LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'downloader_parsers_formulae.html#context_variable_formula' ) )
menu_items.append( ( 'normal', 'open the context variable formula help', 'Open the help page for context variable formulae in your web browser.', page_func ) )
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
test_panel = ClientGUICommon.StaticBox( self, 'test' )
self._test_panel = ClientGUIParsingTest.TestPanelFormula( test_panel, self.GetValue, test_data = test_data )
self._test_panel.SetCollapseNewlines( collapse_newlines )
#
edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
self._variable_name = QW.QLineEdit( edit_panel )
variable_name = formula.GetVariableName()
string_processor = formula.GetStringProcessor()
self._string_processor_button = ClientGUIStringControls.StringProcessorButton( edit_panel, string_processor, self._test_panel.GetTestDataForStringProcessor )
#
self._variable_name.setText( variable_name )
#
rows = []
rows.append( ( 'variable name:', self._variable_name ) )
gridbox = ClientGUICommon.WrapInGrid( edit_panel, rows )
edit_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, 'Newlines are removed from parsed strings right after parsing, before string processing.', ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
edit_panel.Add( self._string_processor_button, CC.FLAGS_EXPAND_PERPENDICULAR )
#
test_panel.Add( self._test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
#
hbox = QP.HBoxLayout()
QP.AddToLayout( hbox, edit_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_ON_RIGHT )
QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.widget().setLayout( vbox )
def GetValue( self ):
variable_name = self._variable_name.text()
string_processor = self._string_processor_button.GetValue()
formula = ClientParsing.ParseFormulaContextVariable( variable_name, string_processor )
return formula
class EditFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent: QW.QWidget, formula: ClientParsing.ParseFormula, test_data_callable: typing.Callable[ [], ClientParsing.ParsingTestData ] ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._current_formula = formula
self._test_data_callable = test_data_callable
self._collapse_newlines = True
#
my_panel = ClientGUICommon.StaticBox( self, 'formula' )
self._formula_description = QW.QPlainTextEdit( my_panel )
( width, height ) = ClientGUIFunctions.ConvertTextToPixels( self._formula_description, ( 90, 8 ) )
self._formula_description.setMinimumWidth( width )
self._formula_description.setMinimumHeight( height )
self._formula_description.setEnabled( False )
self._edit_formula = ClientGUICommon.BetterButton( my_panel, 'edit formula', self._EditFormula )
self._change_formula_type = ClientGUICommon.BetterButton( my_panel, 'change formula type', self._ChangeFormulaType )
#
self._UpdateControls()
#
button_hbox = QP.HBoxLayout()
QP.AddToLayout( button_hbox, self._edit_formula, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( button_hbox, self._change_formula_type, CC.FLAGS_EXPAND_BOTH_WAYS )
my_panel.Add( self._formula_description, CC.FLAGS_EXPAND_BOTH_WAYS )
my_panel.Add( button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, my_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.widget().setLayout( vbox )
def _ChangeFormulaType( self ):
if self._current_formula.ParsesSeparatedContent():
new_html = ClientParsing.ParseFormulaHTML( content_to_fetch = ClientParsing.HTML_CONTENT_HTML )
new_json = ClientParsing.ParseFormulaJSON( content_to_fetch = ClientParsing.JSON_CONTENT_JSON )
else:
new_html = ClientParsing.ParseFormulaHTML()
new_json = ClientParsing.ParseFormulaJSON()
new_compound = ClientParsing.ParseFormulaCompound()
new_context_variable = ClientParsing.ParseFormulaContextVariable()
if isinstance( self._current_formula, ClientParsing.ParseFormulaHTML ):
order = ( 'json', 'compound', 'context_variable' )
elif isinstance( self._current_formula, ClientParsing.ParseFormulaJSON ):
order = ( 'html', 'compound', 'context_variable' )
elif isinstance( self._current_formula, ClientParsing.ParseFormulaCompound ):
order = ( 'html', 'json', 'context_variable' )
elif isinstance( self._current_formula, ClientParsing.ParseFormulaContextVariable ):
order = ( 'html', 'json', 'compound', 'context_variable' )
choice_tuples = []
for formula_type in order:
if formula_type == 'html':
choice_tuples.append( ( 'change to a new HTML formula', new_html ) )
elif formula_type == 'json':
choice_tuples.append( ( 'change to a new JSON formula', new_json ) )
elif formula_type == 'compound':
choice_tuples.append( ( 'change to a new COMPOUND formula', new_compound ) )
elif formula_type == 'context_variable':
choice_tuples.append( ( 'change to a new CONTEXT VARIABLE formula', new_context_variable ) )
try:
self._current_formula = ClientGUIDialogsQuick.SelectFromList( self, 'select formula type', choice_tuples )
except HydrusExceptions.CancelledException:
return
self._UpdateControls()
def _EditFormula( self ):
if isinstance( self._current_formula, ClientParsing.ParseFormulaHTML ):
panel_class = EditHTMLFormulaPanel
elif isinstance( self._current_formula, ClientParsing.ParseFormulaJSON ):
panel_class = EditJSONFormulaPanel
elif isinstance( self._current_formula, ClientParsing.ParseFormulaCompound ):
panel_class = EditCompoundFormulaPanel
elif isinstance( self._current_formula, ClientParsing.ParseFormulaContextVariable ):
panel_class = EditContextVariableFormulaPanel
else:
raise Exception( 'Formula type not found!' )
test_data = self._test_data_callable()
dlg_title = 'edit formula'
with ClientGUITopLevelWindowsPanels.DialogEdit( self, dlg_title, frame_key = 'deeply_nested_dialog' ) as dlg:
panel = panel_class( dlg, self._collapse_newlines, self._current_formula, test_data )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
self._current_formula = panel.GetValue()
self._UpdateControls()
def _UpdateControls( self ):
if self._current_formula is None:
self._formula_description.clear()
self._edit_formula.setEnabled( False )
self._change_formula_type.setEnabled( False )
else:
self._formula_description.setPlainText( self._current_formula.ToPrettyMultilineString() )
self._edit_formula.setEnabled( True )
self._change_formula_type.setEnabled( True )
def GetValue( self ):
return self._current_formula
def SetCollapseNewlines( self, value: bool ):
self._collapse_newlines = value
class EditHTMLTagRulePanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, tag_rule ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
( rule_type, tag_name, tag_attributes, tag_index, tag_depth, should_test_tag_string, tag_string_string_match ) = tag_rule.ToTuple()
if tag_name is None:
tag_name = ''
if tag_attributes is None:
tag_attributes = {}
if tag_depth is None:
tag_depth = 1
self._current_description = ClientGUICommon.BetterStaticText( self )
self._rule_type = ClientGUICommon.BetterChoice( self )
self._rule_type.addItem( 'search descendants', ClientParsing.HTML_RULE_TYPE_DESCENDING )
self._rule_type.addItem( 'walk back up ancestors', ClientParsing.HTML_RULE_TYPE_ASCENDING )
self._rule_type.addItem( 'search next siblings', ClientParsing.HTML_RULE_TYPE_NEXT_SIBLINGS )
self._rule_type.addItem( 'search previous siblings', ClientParsing.HTML_RULE_TYPE_PREV_SIBLINGS )
self._tag_name = QW.QLineEdit( self )
self._tag_attributes = ClientGUIStringControls.StringToStringDictControl( self, tag_attributes, min_height = 4 )
self._tag_index = ClientGUICommon.NoneableSpinCtrl( self, 'index to fetch', none_phrase = 'get all', min = -65536, max = 65535 )
self._tag_index.setToolTip( 'You can make this negative to do negative indexing, i.e. "Select the second from last item".' )
self._tag_depth = ClientGUICommon.BetterSpinBox( self, min=1, max=255 )
self._should_test_tag_string = QW.QCheckBox( self )
self._tag_string_string_match = ClientGUIStringControls.StringMatchButton( self, tag_string_string_match )
#
self._rule_type.SetValue( rule_type )
self._tag_name.setText( tag_name )
self._tag_index.SetValue( tag_index )
self._tag_depth.setValue( tag_depth )
self._should_test_tag_string.setChecked( should_test_tag_string )
self._UpdateTypeControls()
#
vbox = QP.VBoxLayout()
rows = []
rows.append( ( 'rule type: ', self._rule_type ) )
rows.append( ( 'tag name: ', self._tag_name ) )
gridbox_1 = ClientGUICommon.WrapInGrid( self, rows )
rows = []
rows.append( ( 'index to fetch: ', self._tag_index ) )
rows.append( ( 'depth to climb: ', self._tag_depth ) )
gridbox_2 = ClientGUICommon.WrapInGrid( self, rows )
rows = []
rows.append( ( 'should test tag string: ', self._should_test_tag_string ) )
rows.append( ( 'tag string match: ', self._tag_string_string_match ) )
gridbox_3 = ClientGUICommon.WrapInGrid( self, rows )
QP.AddToLayout( vbox, self._current_description, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, gridbox_1, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, self._tag_attributes, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( vbox, gridbox_2, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, gridbox_3, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.widget().setLayout( vbox )
self._UpdateShouldTest()
#
self._rule_type.currentIndexChanged.connect( self.EventTypeChanged )
self._tag_name.textChanged.connect( self.EventVariableChanged )
self._tag_attributes.columnListContentsChanged.connect( self.EventVariableChanged )
self._tag_index.valueChanged.connect( self.EventVariableChanged )
self._tag_depth.valueChanged.connect( self.EventVariableChanged )
self._should_test_tag_string.clicked.connect( self.EventShouldTestChanged )
ClientGUIFunctions.SetFocusLater( self._tag_name )
def _UpdateShouldTest( self ):
if self._should_test_tag_string.isChecked():
self._tag_string_string_match.setEnabled( True )
else:
self._tag_string_string_match.setEnabled( False )
def _UpdateTypeControls( self ):
rule_type = self._rule_type.GetValue()
if rule_type in [ ClientParsing.HTML_RULE_TYPE_DESCENDING, ClientParsing.HTML_RULE_TYPE_NEXT_SIBLINGS, ClientParsing.HTML_RULE_TYPE_PREV_SIBLINGS ]:
self._tag_attributes.setEnabled( True )
self._tag_index.setEnabled( True )
self._tag_depth.setEnabled( False )
else:
self._tag_attributes.setEnabled( False )
self._tag_index.setEnabled( False )
self._tag_depth.setEnabled( True )
self._UpdateDescription()
def _UpdateDescription( self ):
tag_rule = self.GetValue()
label = tag_rule.ToString()
self._current_description.setText( label )
def EventShouldTestChanged( self ):
self._UpdateShouldTest()
def EventTypeChanged( self, index ):
self._UpdateTypeControls()
def EventVariableChanged( self ):
self._UpdateDescription()
def GetValue( self ):
rule_type = self._rule_type.GetValue()
tag_name = self._tag_name.text()
if tag_name == '':
tag_name = None
should_test_tag_string = self._should_test_tag_string.isChecked()
tag_string_string_match = self._tag_string_string_match.GetValue()
if rule_type in [ ClientParsing.HTML_RULE_TYPE_DESCENDING, ClientParsing.HTML_RULE_TYPE_NEXT_SIBLINGS, ClientParsing.HTML_RULE_TYPE_PREV_SIBLINGS ]:
tag_attributes = self._tag_attributes.GetValue()
tag_index = self._tag_index.GetValue()
tag_rule = ClientParsing.ParseRuleHTML( rule_type = rule_type, tag_name = tag_name, tag_attributes = tag_attributes, tag_index = tag_index, should_test_tag_string = should_test_tag_string, tag_string_string_match = tag_string_string_match )
elif rule_type == ClientParsing.HTML_RULE_TYPE_ASCENDING:
tag_depth = self._tag_depth.value()
tag_rule = ClientParsing.ParseRuleHTML( rule_type = rule_type, tag_name = tag_name, tag_depth = tag_depth, should_test_tag_string = should_test_tag_string, tag_string_string_match = tag_string_string_match )
return tag_rule
class EditHTMLFormulaPanel( EditSpecificFormulaPanel ):
def __init__( self, parent: QW.QWidget, collapse_newlines: bool, formula: ClientParsing.ParseFormulaHTML, test_data: ClientParsing.ParsingTestData ):
EditSpecificFormulaPanel.__init__( self, parent, collapse_newlines )
#
menu_items = []
page_func = HydrusData.Call( ClientPaths.LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'downloader_parsers_formulae.html#html_formula' ) )
menu_items.append( ( 'normal', 'open the html formula help', 'Open the help page for html formulae in your web browser.', page_func ) )
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
test_panel = ClientGUICommon.StaticBox( self, 'test' )
self._test_panel = ClientGUIParsingTest.TestPanelFormula( test_panel, self.GetValue, test_data = test_data )
self._test_panel.SetCollapseNewlines( self._collapse_newlines )
#
edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
self._tag_rules = QW.QListWidget( edit_panel )
self._tag_rules.setSelectionMode( QW.QAbstractItemView.SingleSelection )
self._tag_rules.itemDoubleClicked.connect( self.Edit )
self._add_rule = ClientGUICommon.BetterButton( edit_panel, 'add', self.Add )
self._edit_rule = ClientGUICommon.BetterButton( edit_panel, 'edit', self.Edit )
self._move_rule_up = ClientGUICommon.BetterButton( edit_panel, '\u2191', self.MoveUp )
self._delete_rule = ClientGUICommon.BetterButton( edit_panel, 'X', self.Delete )
self._move_rule_down = ClientGUICommon.BetterButton( edit_panel, '\u2193', self.MoveDown )
self._content_to_fetch = ClientGUICommon.BetterChoice( edit_panel )
self._content_to_fetch.addItem( 'attribute', ClientParsing.HTML_CONTENT_ATTRIBUTE )
self._content_to_fetch.addItem( 'string', ClientParsing.HTML_CONTENT_STRING )
self._content_to_fetch.addItem( 'html', ClientParsing.HTML_CONTENT_HTML )
self._content_to_fetch.currentIndexChanged.connect( self._UpdateControls )
self._attribute_to_fetch = QW.QLineEdit( edit_panel )
tag_rules = formula.GetTagRules()
content_to_fetch = formula.GetContentToFetch()
attribute_to_fetch = formula.GetAttributeToFetch()
string_processor = formula.GetStringProcessor()
self._string_processor_button = ClientGUIStringControls.StringProcessorButton( edit_panel, string_processor, self._test_panel.GetTestDataForStringProcessor )
#
for rule in tag_rules:
pretty_rule = rule.ToString()
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._tag_rules.addItem( item )
self._content_to_fetch.SetValue( content_to_fetch )
self._attribute_to_fetch.setText( attribute_to_fetch )
self._UpdateControls()
#
udd_button_vbox = QP.VBoxLayout()
udd_button_vbox.addStretch( 1 )
QP.AddToLayout( udd_button_vbox, self._move_rule_up, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( udd_button_vbox, self._delete_rule, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( udd_button_vbox, self._move_rule_down, CC.FLAGS_CENTER_PERPENDICULAR )
udd_button_vbox.addStretch( 1 )
tag_rules_hbox = QP.HBoxLayout()
QP.AddToLayout( tag_rules_hbox, self._tag_rules, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( tag_rules_hbox, udd_button_vbox, CC.FLAGS_CENTER_PERPENDICULAR )
ae_button_hbox = QP.HBoxLayout()
QP.AddToLayout( ae_button_hbox, self._add_rule, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( ae_button_hbox, self._edit_rule, CC.FLAGS_CENTER_PERPENDICULAR )
rows = []
rows.append( ( 'content to fetch:', self._content_to_fetch ) )
rows.append( ( 'attribute to fetch: ', self._attribute_to_fetch ) )
gridbox = ClientGUICommon.WrapInGrid( edit_panel, rows )
edit_panel.Add( tag_rules_hbox, CC.FLAGS_EXPAND_BOTH_WAYS )
edit_panel.Add( ae_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, 'Newlines are removed from parsed strings right after parsing, before string processing.', ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
edit_panel.Add( self._string_processor_button, CC.FLAGS_EXPAND_PERPENDICULAR )
#
test_panel.Add( self._test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
#
hbox = QP.HBoxLayout()
QP.AddToLayout( hbox, edit_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_ON_RIGHT )
QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.widget().setLayout( vbox )
def _UpdateControls( self ):
if self._content_to_fetch.GetValue() == ClientParsing.HTML_CONTENT_ATTRIBUTE:
self._attribute_to_fetch.setEnabled( True )
else:
self._attribute_to_fetch.setEnabled( False )
def Add( self ):
dlg_title = 'edit tag rule'
with ClientGUITopLevelWindowsPanels.DialogEdit( self, dlg_title, frame_key = 'deeply_nested_dialog' ) as dlg:
new_rule = ClientParsing.ParseRuleHTML()
panel = EditHTMLTagRulePanel( dlg, new_rule )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
rule = panel.GetValue()
pretty_rule = rule.ToString()
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._tag_rules.addItem( item )
def Delete( self ):
selection = QP.ListWidgetGetSelection( self._tag_rules )
if selection != -1:
QP.ListWidgetDelete( self._tag_rules, selection )
def Edit( self ):
selection = QP.ListWidgetGetSelection( self._tag_rules )
if selection != -1:
rule = QP.GetClientData( self._tag_rules, selection )
dlg_title = 'edit tag rule'
with ClientGUITopLevelWindowsPanels.DialogEdit( self, dlg_title, frame_key = 'deeply_nested_dialog' ) as dlg:
panel = EditHTMLTagRulePanel( dlg, rule )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
rule = panel.GetValue()
pretty_rule = rule.ToString()
self._tag_rules.item( selection ).setText( pretty_rule )
self._tag_rules.item( selection ).setData( QC.Qt.UserRole, rule )
def GetValue( self ):
tags_rules = [ QP.GetClientData( self._tag_rules, i ) for i in range( self._tag_rules.count() ) ]
content_to_fetch = self._content_to_fetch.GetValue()
attribute_to_fetch = self._attribute_to_fetch.text()
if content_to_fetch == ClientParsing.HTML_CONTENT_ATTRIBUTE and attribute_to_fetch == '':
raise HydrusExceptions.VetoException( 'Please enter an attribute to fetch!' )
string_processor = self._string_processor_button.GetValue()
formula = ClientParsing.ParseFormulaHTML( tags_rules, content_to_fetch, attribute_to_fetch, string_processor )
return formula
def MoveDown( self ):
selection = QP.ListWidgetGetSelection( self._tag_rules )
if selection != -1 and selection + 1 < self._tag_rules.count():
pretty_rule = self._tag_rules.item( selection ).text()
rule = QP.GetClientData( self._tag_rules, selection )
QP.ListWidgetDelete( self._tag_rules, selection )
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._tag_rules.insertItem( selection + 1, item )
def MoveUp( self ):
selection = QP.ListWidgetGetSelection( self._tag_rules )
if selection != -1 and selection > 0:
pretty_rule = self._tag_rules.item( selection ).text()
rule = QP.GetClientData( self._tag_rules, selection )
QP.ListWidgetDelete( self._tag_rules, selection )
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._tag_rules.insertItem( selection - 1, item )
class EditJSONParsingRulePanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent: QW.QWidget, rule: ClientParsing.ParseRuleHTML ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._parse_rule_type = ClientGUICommon.BetterChoice( self )
self._parse_rule_type.addItem( 'dictionary entry', ClientParsing.JSON_PARSE_RULE_TYPE_DICT_KEY )
self._parse_rule_type.addItem( 'all dictionary/list items', ClientParsing.JSON_PARSE_RULE_TYPE_ALL_ITEMS )
self._parse_rule_type.addItem( 'indexed item', ClientParsing.JSON_PARSE_RULE_TYPE_INDEXED_ITEM )
string_match = ClientStrings.StringMatch( match_type = ClientStrings.STRING_MATCH_FIXED, match_value = 'posts', example_string = 'posts' )
self._string_match = ClientGUIStringPanels.EditStringMatchPanel( self, string_match )
self._index = ClientGUICommon.BetterSpinBox( self, min=-65536, max=65535 )
self._index.setToolTip( 'You can make this negative to do negative indexing, i.e. "Select the second from last item".' )
#
( parse_rule_type, parse_rule ) = rule
self._parse_rule_type.SetValue( parse_rule_type )
if parse_rule_type == ClientParsing.JSON_PARSE_RULE_TYPE_INDEXED_ITEM:
self._index.setValue( parse_rule )
elif parse_rule_type == ClientParsing.JSON_PARSE_RULE_TYPE_DICT_KEY:
self._string_match.SetValue( parse_rule )
self._UpdateHideShow()
#
vbox = QP.VBoxLayout()
rows = []
rows.append( ( 'list index: ', self._index ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
QP.AddToLayout( vbox, self._parse_rule_type, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._string_match, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.widget().setLayout( vbox )
#
self._parse_rule_type.currentIndexChanged.connect( self._UpdateHideShow )
def _UpdateHideShow( self ):
self._string_match.setEnabled( False )
self._index.setEnabled( False )
parse_rule_type = self._parse_rule_type.GetValue()
if parse_rule_type == ClientParsing.JSON_PARSE_RULE_TYPE_DICT_KEY:
self._string_match.setEnabled( True )
elif parse_rule_type == ClientParsing.JSON_PARSE_RULE_TYPE_INDEXED_ITEM:
self._index.setEnabled( True )
def GetValue( self ):
parse_rule_type = self._parse_rule_type.GetValue()
if parse_rule_type == ClientParsing.JSON_PARSE_RULE_TYPE_DICT_KEY:
parse_rule = self._string_match.GetValue()
elif parse_rule_type == ClientParsing.JSON_PARSE_RULE_TYPE_INDEXED_ITEM:
parse_rule = self._index.value()
elif parse_rule_type == ClientParsing.JSON_PARSE_RULE_TYPE_ALL_ITEMS:
parse_rule = None
return ( parse_rule_type, parse_rule )
class EditJSONFormulaPanel( EditSpecificFormulaPanel ):
def __init__( self, parent: QW.QWidget, collapse_newlines: bool, formula: ClientParsing.ParseFormulaJSON, test_data: ClientParsing.ParsingTestData ):
EditSpecificFormulaPanel.__init__( self, parent, collapse_newlines )
#
menu_items = []
page_func = HydrusData.Call( ClientPaths.LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'downloader_parsers_formulae.html#json_formula' ) )
menu_items.append( ( 'normal', 'open the json formula help', 'Open the help page for json formulae in your web browser.', page_func ) )
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
test_panel = ClientGUICommon.StaticBox( self, 'test' )
self._test_panel = ClientGUIParsingTest.TestPanelFormula( test_panel, self.GetValue, test_data = test_data )
self._test_panel.SetCollapseNewlines( collapse_newlines )
#
edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
self._parse_rules = QW.QListWidget( edit_panel )
self._parse_rules.setSelectionMode( QW.QAbstractItemView.SingleSelection )
self._parse_rules.itemDoubleClicked.connect( self.Edit )
self._add_rule = ClientGUICommon.BetterButton( edit_panel, 'add', self.Add )
self._edit_rule = ClientGUICommon.BetterButton( edit_panel, 'edit', self.Edit )
self._move_rule_up = ClientGUICommon.BetterButton( edit_panel, '\u2191', self.MoveUp )
self._delete_rule = ClientGUICommon.BetterButton( edit_panel, 'X', self.Delete )
self._move_rule_down = ClientGUICommon.BetterButton( edit_panel, '\u2193', self.MoveDown )
self._content_to_fetch = ClientGUICommon.BetterChoice( edit_panel )
self._content_to_fetch.addItem( 'string', ClientParsing.JSON_CONTENT_STRING )
self._content_to_fetch.addItem( 'dictionary keys', ClientParsing.JSON_CONTENT_DICT_KEYS )
self._content_to_fetch.addItem( 'json', ClientParsing.JSON_CONTENT_JSON )
parse_rules = formula.GetParseRules()
content_to_fetch = formula.GetContentToFetch()
string_processor = formula.GetStringProcessor()
self._string_processor_button = ClientGUIStringControls.StringProcessorButton( edit_panel, string_processor, self._test_panel.GetTestDataForStringProcessor )
#
for rule in parse_rules:
pretty_rule = ClientParsing.RenderJSONParseRule( rule )
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._parse_rules.addItem( item )
self._content_to_fetch.SetValue( content_to_fetch )
#
udd_button_vbox = QP.VBoxLayout()
udd_button_vbox.addStretch( 1 )
QP.AddToLayout( udd_button_vbox, self._move_rule_up, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( udd_button_vbox, self._delete_rule, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( udd_button_vbox, self._move_rule_down, CC.FLAGS_CENTER_PERPENDICULAR )
udd_button_vbox.addStretch( 1 )
parse_rules_hbox = QP.HBoxLayout()
QP.AddToLayout( parse_rules_hbox, self._parse_rules, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( parse_rules_hbox, udd_button_vbox, CC.FLAGS_CENTER_PERPENDICULAR )
ae_button_hbox = QP.HBoxLayout()
QP.AddToLayout( ae_button_hbox, self._add_rule, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( ae_button_hbox, self._edit_rule, CC.FLAGS_CENTER_PERPENDICULAR )
rows = []
rows.append( ( 'content to fetch:', self._content_to_fetch ) )
gridbox = ClientGUICommon.WrapInGrid( edit_panel, rows )
edit_panel.Add( parse_rules_hbox, CC.FLAGS_EXPAND_BOTH_WAYS )
edit_panel.Add( ae_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, 'Newlines are removed from parsed strings right after parsing, before string processing.', ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
edit_panel.Add( self._string_processor_button, CC.FLAGS_EXPAND_PERPENDICULAR )
#
test_panel.Add( self._test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
#
hbox = QP.HBoxLayout()
QP.AddToLayout( hbox, edit_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_ON_RIGHT )
QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.widget().setLayout( vbox )
def Add( self ):
dlg_title = 'edit parse rule'
with ClientGUITopLevelWindowsPanels.DialogEdit( self, dlg_title, frame_key = 'deeply_nested_dialog' ) as dlg:
new_rule = ( ClientParsing.JSON_PARSE_RULE_TYPE_DICT_KEY, ClientStrings.StringMatch( match_type = ClientStrings.STRING_MATCH_FIXED, match_value = 'posts', example_string = 'posts' ) )
panel = EditJSONParsingRulePanel( dlg, new_rule )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
rule = panel.GetValue()
pretty_rule = ClientParsing.RenderJSONParseRule( rule )
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._parse_rules.addItem( item )
def Delete( self ):
selection = QP.ListWidgetGetSelection( self._parse_rules )
if selection != -1:
QP.ListWidgetDelete( self._parse_rules, selection )
def Edit( self ):
selection = QP.ListWidgetGetSelection( self._parse_rules )
if selection != -1:
rule = QP.GetClientData( self._parse_rules, selection )
dlg_title = 'edit parse rule'
with ClientGUITopLevelWindowsPanels.DialogEdit( self, dlg_title, frame_key = 'deeply_nested_dialog' ) as dlg:
panel = EditJSONParsingRulePanel( dlg, rule )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
rule = panel.GetValue()
pretty_rule = ClientParsing.RenderJSONParseRule( rule )
self._parse_rules.item( selection ).setText( pretty_rule )
self._parse_rules.item( selection ).setData( QC.Qt.UserRole, rule )
def GetValue( self ):
parse_rules = [ QP.GetClientData( self._parse_rules, i ) for i in range( self._parse_rules.count() ) ]
content_to_fetch = self._content_to_fetch.GetValue()
string_processor = self._string_processor_button.GetValue()
formula = ClientParsing.ParseFormulaJSON( parse_rules, content_to_fetch, string_processor )
return formula
def MoveDown( self ):
selection = QP.ListWidgetGetSelection( self._parse_rules )
if selection != -1 and selection + 1 < self._parse_rules.count():
pretty_rule = self._parse_rules.item( selection ).text()
rule = QP.GetClientData( self._parse_rules, selection )
QP.ListWidgetDelete( self._parse_rules, selection )
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._parse_rules.insertItem( selection + 1, item )
def MoveUp( self ):
selection = QP.ListWidgetGetSelection( self._parse_rules )
if selection != -1 and selection > 0:
pretty_rule = self._parse_rules.item( selection ).text()
rule = QP.GetClientData( self._parse_rules, selection )
QP.ListWidgetDelete( self._parse_rules, selection )
item = QW.QListWidgetItem()
item.setText( pretty_rule )
item.setData( QC.Qt.UserRole, rule )
self._parse_rules.insertItem( selection - 1, item )