mirror of
				https://github.com/chylex/TweetDuck.git
				synced 2025-11-04 01:40:16 +01:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			c5a42e74d9
			...
			random_wip
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d6a14edcdf | 
							
								
								
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,5 @@
 | 
			
		||||
# Auto detect text files and perform LF normalization
 | 
			
		||||
* text=auto eof=lf
 | 
			
		||||
* text=auto
 | 
			
		||||
 | 
			
		||||
*.txt text eof=lf
 | 
			
		||||
*.cs  diff=csharp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +0,0 @@
 | 
			
		||||
github: chylex
 | 
			
		||||
patreon: chylex
 | 
			
		||||
ko_fi: chylex
 | 
			
		||||
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -5,15 +5,8 @@
 | 
			
		||||
bld/*
 | 
			
		||||
!bld/*.iss
 | 
			
		||||
!bld/*.bat
 | 
			
		||||
!bld/*.ps1
 | 
			
		||||
!bld/Redist
 | 
			
		||||
!bld/Resources
 | 
			
		||||
 | 
			
		||||
# Rider
 | 
			
		||||
**/.idea/dictionaries
 | 
			
		||||
**/.idea/misc.xml
 | 
			
		||||
**/.idea/riderMarkupCache.xml
 | 
			
		||||
 | 
			
		||||
# User-specific files
 | 
			
		||||
*.suo
 | 
			
		||||
*.user
 | 
			
		||||
@@ -147,3 +140,4 @@ _UpgradeReport_Files/
 | 
			
		||||
Backup*/
 | 
			
		||||
UpgradeLog*.XML
 | 
			
		||||
UpgradeLog*.htm
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.idea/.idea.TweetDuck/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.idea/.idea.TweetDuck/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,8 +0,0 @@
 | 
			
		||||
# Default ignored files
 | 
			
		||||
/shelf/
 | 
			
		||||
/workspace.xml
 | 
			
		||||
# Rider ignored files
 | 
			
		||||
/projectSettingsUpdater.xml
 | 
			
		||||
/modules.xml
 | 
			
		||||
/contentModel.xml
 | 
			
		||||
/.idea.TweetDuck.iml
 | 
			
		||||
							
								
								
									
										450
									
								
								.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										450
									
								
								.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
								
							@@ -1,450 +0,0 @@
 | 
			
		||||
<component name="ProjectCodeStyleConfiguration">
 | 
			
		||||
  <code_scheme name="Project" version="173">
 | 
			
		||||
    <option name="AUTODETECT_INDENTS" value="false" />
 | 
			
		||||
    <option name="OTHER_INDENT_OPTIONS">
 | 
			
		||||
      <value>
 | 
			
		||||
        <option name="INDENT_SIZE" value="2" />
 | 
			
		||||
        <option name="TAB_SIZE" value="2" />
 | 
			
		||||
      </value>
 | 
			
		||||
    </option>
 | 
			
		||||
    <option name="LINE_SEPARATOR" value="
" />
 | 
			
		||||
    <option name="RIGHT_MARGIN" value="999" />
 | 
			
		||||
    <option name="FORMATTER_TAGS_ENABLED" value="true" />
 | 
			
		||||
    <CssCodeStyleSettings>
 | 
			
		||||
      <option name="HEX_COLOR_LOWER_CASE" value="true" />
 | 
			
		||||
    </CssCodeStyleSettings>
 | 
			
		||||
    <DB2CodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </DB2CodeStyleSettings>
 | 
			
		||||
    <DerbyCodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </DerbyCodeStyleSettings>
 | 
			
		||||
    <GoCodeStyleSettings>
 | 
			
		||||
      <option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
 | 
			
		||||
      <option name="GROUP_STDLIB_IMPORTS" value="true" />
 | 
			
		||||
      <option name="WRAP_COMP_LIT" value="5" />
 | 
			
		||||
      <option name="WRAP_FUNC_PARAMS" value="5" />
 | 
			
		||||
      <option name="WRAP_FUNC_RESULT" value="5" />
 | 
			
		||||
    </GoCodeStyleSettings>
 | 
			
		||||
    <H2CodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </H2CodeStyleSettings>
 | 
			
		||||
    <H2CodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </H2CodeStyleSettings>
 | 
			
		||||
    <HSQLCodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </HSQLCodeStyleSettings>
 | 
			
		||||
    <HTMLCodeStyleSettings>
 | 
			
		||||
      <option name="HTML_ALIGN_TEXT" value="true" />
 | 
			
		||||
      <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
 | 
			
		||||
      <option name="HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE" value="" />
 | 
			
		||||
      <option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
 | 
			
		||||
    </HTMLCodeStyleSettings>
 | 
			
		||||
    <JSCodeStyleSettings version="0">
 | 
			
		||||
      <option name="FORCE_SEMICOLON_STYLE" value="true" />
 | 
			
		||||
      <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACKETS" value="true" />
 | 
			
		||||
      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
 | 
			
		||||
      <option name="FORCE_QUOTE_STYlE" value="true" />
 | 
			
		||||
      <option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" />
 | 
			
		||||
      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
 | 
			
		||||
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
 | 
			
		||||
      <option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
 | 
			
		||||
      <option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
 | 
			
		||||
      <option name="IMPORT_SORT_MODULE_NAME" value="true" />
 | 
			
		||||
    </JSCodeStyleSettings>
 | 
			
		||||
    <JSON>
 | 
			
		||||
      <option name="OBJECT_WRAPPING" value="5" />
 | 
			
		||||
      <option name="ARRAY_WRAPPING" value="5" />
 | 
			
		||||
    </JSON>
 | 
			
		||||
    <JavaCodeStyleSettings>
 | 
			
		||||
      <option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
 | 
			
		||||
      <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
 | 
			
		||||
      <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
 | 
			
		||||
      <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
 | 
			
		||||
        <value />
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="IMPORT_LAYOUT_TABLE">
 | 
			
		||||
        <value>
 | 
			
		||||
          <package name="" withSubpackages="true" static="false" />
 | 
			
		||||
          <package name="javax" withSubpackages="true" static="false" />
 | 
			
		||||
          <package name="java" withSubpackages="true" static="false" />
 | 
			
		||||
          <package name="" withSubpackages="true" static="true" />
 | 
			
		||||
        </value>
 | 
			
		||||
      </option>
 | 
			
		||||
    </JavaCodeStyleSettings>
 | 
			
		||||
    <JetCodeStyleSettings>
 | 
			
		||||
      <option name="PACKAGES_TO_USE_STAR_IMPORTS">
 | 
			
		||||
        <value>
 | 
			
		||||
          <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
 | 
			
		||||
        </value>
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
 | 
			
		||||
      <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
 | 
			
		||||
      <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
 | 
			
		||||
      <option name="IMPORT_NESTED_CLASSES" value="true" />
 | 
			
		||||
      <option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
 | 
			
		||||
      <option name="ALLOW_TRAILING_COMMA" value="true" />
 | 
			
		||||
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
 | 
			
		||||
    </JetCodeStyleSettings>
 | 
			
		||||
    <LessCodeStyleSettings>
 | 
			
		||||
      <option name="HEX_COLOR_LOWER_CASE" value="true" />
 | 
			
		||||
    </LessCodeStyleSettings>
 | 
			
		||||
    <MSSQLCodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </MSSQLCodeStyleSettings>
 | 
			
		||||
    <MySQLCodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </MySQLCodeStyleSettings>
 | 
			
		||||
    <Objective-C>
 | 
			
		||||
      <option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
 | 
			
		||||
      <option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
 | 
			
		||||
      <option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" />
 | 
			
		||||
      <option name="SPACE_BEFORE_INIT_LIST" value="true" />
 | 
			
		||||
      <option name="SPACE_AFTER_DICTIONARY_LITERAL_COLON" value="false" />
 | 
			
		||||
    </Objective-C>
 | 
			
		||||
    <OracleCodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </OracleCodeStyleSettings>
 | 
			
		||||
    <PHPCodeStyleSettings>
 | 
			
		||||
      <option name="ALIGN_KEY_VALUE_PAIRS" value="true" />
 | 
			
		||||
      <option name="CONCAT_SPACES" value="false" />
 | 
			
		||||
      <option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
 | 
			
		||||
      <option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
 | 
			
		||||
      <option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
 | 
			
		||||
      <option name="LOWER_CASE_NULL_CONST" value="true" />
 | 
			
		||||
      <option name="ELSE_IF_STYLE" value="COMBINE" />
 | 
			
		||||
      <option name="VARIABLE_NAMING_STYLE" value="SNAKE_CASE" />
 | 
			
		||||
      <option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="0" />
 | 
			
		||||
      <option name="SPACE_BEFORE_CLOSURE_LEFT_PARENTHESIS" value="false" />
 | 
			
		||||
      <option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
 | 
			
		||||
      <option name="NEW_LINE_AFTER_PHP_OPENING_TAG" value="true" />
 | 
			
		||||
      <option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
 | 
			
		||||
    </PHPCodeStyleSettings>
 | 
			
		||||
    <PostgresCodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </PostgresCodeStyleSettings>
 | 
			
		||||
    <Properties>
 | 
			
		||||
      <option name="KEEP_BLANK_LINES" value="true" />
 | 
			
		||||
    </Properties>
 | 
			
		||||
    <Python>
 | 
			
		||||
      <option name="SPACE_AROUND_EQ_IN_NAMED_PARAMETER" value="true" />
 | 
			
		||||
      <option name="SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT" value="true" />
 | 
			
		||||
      <option name="NEW_LINE_AFTER_COLON" value="true" />
 | 
			
		||||
      <option name="DICT_WRAPPING" value="5" />
 | 
			
		||||
      <option name="DICT_NEW_LINE_AFTER_LEFT_BRACE" value="true" />
 | 
			
		||||
      <option name="DICT_NEW_LINE_BEFORE_RIGHT_BRACE" value="true" />
 | 
			
		||||
    </Python>
 | 
			
		||||
    <RsCodeStyleSettings>
 | 
			
		||||
      <option name="ALIGN_RET_TYPE" value="false" />
 | 
			
		||||
      <option name="ALIGN_TYPE_PARAMS" value="true" />
 | 
			
		||||
      <option name="ALLOW_ONE_LINE_MATCH" value="true" />
 | 
			
		||||
      <option name="SPACE_AROUND_ASSOC_TYPE_BINDING" value="true" />
 | 
			
		||||
    </RsCodeStyleSettings>
 | 
			
		||||
    <Ruby>
 | 
			
		||||
      <option name="INDENT_PRIVATE_METHODS" value="true" />
 | 
			
		||||
      <option name="INDENT_PROTECTED_METHODS" value="true" />
 | 
			
		||||
      <option name="INDENT_PUBLIC_METHODS" value="true" />
 | 
			
		||||
      <option name="INDENT_WHEN_CASES" value="true" />
 | 
			
		||||
      <option name="CHAIN_CALLS_ALIGNMENT" value="2" />
 | 
			
		||||
    </Ruby>
 | 
			
		||||
    <SQLiteCodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </SQLiteCodeStyleSettings>
 | 
			
		||||
    <ScssCodeStyleSettings>
 | 
			
		||||
      <option name="HEX_COLOR_LOWER_CASE" value="true" />
 | 
			
		||||
    </ScssCodeStyleSettings>
 | 
			
		||||
    <SqlCodeStyleSettings version="6">
 | 
			
		||||
      <option name="KEYWORD_CASE" value="2" />
 | 
			
		||||
      <option name="TYPE_CASE" value="2" />
 | 
			
		||||
      <option name="CUSTOM_TYPE_CASE" value="2" />
 | 
			
		||||
      <option name="SUBQUERY_CONTENT" value="1" />
 | 
			
		||||
      <option name="SUBQUERY_CLOSING" value="1" />
 | 
			
		||||
      <option name="INSERT_TABLE_EL_LINE" value="0" />
 | 
			
		||||
      <option name="INSERT_EL_WRAP" value="2" />
 | 
			
		||||
      <option name="SET_EL_WRAP" value="2" />
 | 
			
		||||
      <option name="SET_ALIGN_EQUAL_SIGN" value="false" />
 | 
			
		||||
      <option name="FROM_EL_WRAP" value="2" />
 | 
			
		||||
      <option name="FROM_ALIGN_JOIN_TABLES" value="true" />
 | 
			
		||||
      <option name="FROM_INDENT_JOIN" value="false" />
 | 
			
		||||
      <option name="FROM_ONLY_JOIN_INDENT" value="2" />
 | 
			
		||||
      <option name="WHERE_EL_WRAP" value="2" />
 | 
			
		||||
      <option name="TABLE_OPENING" value="1" />
 | 
			
		||||
      <option name="TABLE_CONTENT" value="2" />
 | 
			
		||||
      <option name="TABLE_CLOSING" value="3" />
 | 
			
		||||
      <option name="TABLE_DEFAULTS_ALIGN" value="false" />
 | 
			
		||||
      <option name="TABLE_NULLABILITIES_ALIGN" value="false" />
 | 
			
		||||
      <option name="CONSTRAINT_WRAP_1" value="false" />
 | 
			
		||||
      <option name="CONSTRAINT_WRAP_3" value="true" />
 | 
			
		||||
      <option name="CONSTRAINT_WRAP_4" value="true" />
 | 
			
		||||
      <option name="VIEW_INDENT_QUERY" value="true" />
 | 
			
		||||
      <option name="EXPR_CASE_WHEN_WRAP" value="false" />
 | 
			
		||||
    </SqlCodeStyleSettings>
 | 
			
		||||
    <SybaseCodeStyleSettings version="6">
 | 
			
		||||
      <option name="USE_GENERIC_STYLE" value="true" />
 | 
			
		||||
    </SybaseCodeStyleSettings>
 | 
			
		||||
    <TypeScriptCodeStyleSettings version="0">
 | 
			
		||||
      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
 | 
			
		||||
      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
 | 
			
		||||
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
 | 
			
		||||
      <option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
 | 
			
		||||
      <option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
 | 
			
		||||
    </TypeScriptCodeStyleSettings>
 | 
			
		||||
    <XML>
 | 
			
		||||
      <option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
 | 
			
		||||
    </XML>
 | 
			
		||||
    <codeStyleSettings language="CMake">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="CSS">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="INDENT_SIZE" value="2" />
 | 
			
		||||
        <option name="TAB_SIZE" value="2" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="Groovy">
 | 
			
		||||
      <option name="ELSE_ON_NEW_LINE" value="true" />
 | 
			
		||||
      <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
 | 
			
		||||
      <option name="IF_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="DOWHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="WHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="FOR_BRACE_FORCE" value="3" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="HTML">
 | 
			
		||||
      <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
 | 
			
		||||
      <option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="INDENT_SIZE" value="2" />
 | 
			
		||||
        <option name="TAB_SIZE" value="2" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="JAVA">
 | 
			
		||||
      <option name="RIGHT_MARGIN" value="999" />
 | 
			
		||||
      <option name="BLANK_LINES_AFTER_PACKAGE" value="0" />
 | 
			
		||||
      <option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
 | 
			
		||||
      <option name="ELSE_ON_NEW_LINE" value="true" />
 | 
			
		||||
      <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
 | 
			
		||||
      <option name="SPACE_AFTER_TYPE_CAST" value="false" />
 | 
			
		||||
      <option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
 | 
			
		||||
      <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
 | 
			
		||||
      <option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
 | 
			
		||||
      <option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
 | 
			
		||||
      <option name="IF_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="DOWHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="WHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="FOR_BRACE_FORCE" value="3" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="JSON">
 | 
			
		||||
      <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
 | 
			
		||||
      <option name="SPACE_WITHIN_BRACKETS" value="true" />
 | 
			
		||||
      <option name="SPACE_WITHIN_BRACES" value="true" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="JavaScript">
 | 
			
		||||
      <option name="ELSE_ON_NEW_LINE" value="true" />
 | 
			
		||||
      <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
 | 
			
		||||
      <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
 | 
			
		||||
      <option name="IF_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="DOWHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="WHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="FOR_BRACE_FORCE" value="3" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="LESS">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="Lua">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="INDENT_SIZE" value="2" />
 | 
			
		||||
        <option name="TAB_SIZE" value="2" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="ObjectiveC">
 | 
			
		||||
      <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
 | 
			
		||||
      <option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
 | 
			
		||||
      <option name="LINE_COMMENT_ADD_SPACE" value="true" />
 | 
			
		||||
      <option name="ELSE_ON_NEW_LINE" value="true" />
 | 
			
		||||
      <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
 | 
			
		||||
      <option name="IF_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="DOWHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="WHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="FOR_BRACE_FORCE" value="3" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="PHP">
 | 
			
		||||
      <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
 | 
			
		||||
      <option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
 | 
			
		||||
      <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
 | 
			
		||||
      <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
 | 
			
		||||
      <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
 | 
			
		||||
      <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
 | 
			
		||||
      <option name="CLASS_BRACE_STYLE" value="1" />
 | 
			
		||||
      <option name="METHOD_BRACE_STYLE" value="1" />
 | 
			
		||||
      <option name="ELSE_ON_NEW_LINE" value="true" />
 | 
			
		||||
      <option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
 | 
			
		||||
      <option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
 | 
			
		||||
      <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
 | 
			
		||||
      <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
 | 
			
		||||
      <option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
 | 
			
		||||
      <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
 | 
			
		||||
      <option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
 | 
			
		||||
      <option name="IF_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="DOWHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="WHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="FOR_BRACE_FORCE" value="3" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="INDENT_SIZE" value="2" />
 | 
			
		||||
        <option name="TAB_SIZE" value="2" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="Puppet">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="Python">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="RHTML">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="Rust">
 | 
			
		||||
      <option name="RIGHT_MARGIN" value="140" />
 | 
			
		||||
      <option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="SASS">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="SCSS">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="SQL">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="Shell Script">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="INDENT_SIZE" value="4" />
 | 
			
		||||
        <option name="TAB_SIZE" value="4" />
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="TOML">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="TypeScript">
 | 
			
		||||
      <option name="ELSE_ON_NEW_LINE" value="true" />
 | 
			
		||||
      <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
 | 
			
		||||
      <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
 | 
			
		||||
      <option name="IF_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="DOWHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="WHILE_BRACE_FORCE" value="3" />
 | 
			
		||||
      <option name="FOR_BRACE_FORCE" value="3" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="XML">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="INDENT_SIZE" value="2" />
 | 
			
		||||
        <option name="TAB_SIZE" value="2" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="go">
 | 
			
		||||
      <option name="CALL_PARAMETERS_WRAP" value="5" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="kotlin">
 | 
			
		||||
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
 | 
			
		||||
      <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
 | 
			
		||||
      <option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
 | 
			
		||||
      <option name="LINE_COMMENT_ADD_SPACE" value="true" />
 | 
			
		||||
      <option name="ELSE_ON_NEW_LINE" value="true" />
 | 
			
		||||
      <option name="METHOD_ANNOTATION_WRAP" value="0" />
 | 
			
		||||
      <option name="CLASS_ANNOTATION_WRAP" value="0" />
 | 
			
		||||
      <option name="FIELD_ANNOTATION_WRAP" value="0" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
			
		||||
        <option name="USE_TAB_CHARACTER" value="true" />
 | 
			
		||||
        <option name="SMART_TABS" value="true" />
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="liquid">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="ruby">
 | 
			
		||||
      <option name="SPACE_WITHIN_BRACES" value="true" />
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
    <codeStyleSettings language="yaml">
 | 
			
		||||
      <indentOptions>
 | 
			
		||||
        <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
 | 
			
		||||
      </indentOptions>
 | 
			
		||||
    </codeStyleSettings>
 | 
			
		||||
  </code_scheme>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
<component name="ProjectCodeStyleConfiguration">
 | 
			
		||||
  <state>
 | 
			
		||||
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
 | 
			
		||||
  </state>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										6
									
								
								.idea/.idea.TweetDuck/.idea/encodings.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								.idea/.idea.TweetDuck/.idea/encodings.xml
									
									
									
										generated
									
									
									
								
							@@ -1,6 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
 | 
			
		||||
    <file url="PROJECT" charset="UTF-8" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
							
								
								
									
										8
									
								
								.idea/.idea.TweetDuck/.idea/indexLayout.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								.idea/.idea.TweetDuck/.idea/indexLayout.xml
									
									
									
										generated
									
									
									
								
							@@ -1,8 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="UserContentModel">
 | 
			
		||||
    <attachedFolders />
 | 
			
		||||
    <explicitIncludes />
 | 
			
		||||
    <explicitExcludes />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@@ -1,770 +0,0 @@
 | 
			
		||||
<component name="InspectionProjectProfileManager">
 | 
			
		||||
  <profile version="1.0">
 | 
			
		||||
    <option name="myName" value="Project" />
 | 
			
		||||
    <inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="nonThreadSafeClasses">
 | 
			
		||||
        <value />
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="nonThreadSafeTypes" value="" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AddOperatorModifier" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AmbiguousFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AmbiguousMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AssertsWithoutMessagesTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AssignmentOrReturnOfFieldWithMutableType" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreTransformationOfOriginalParameter" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="AssignmentToStaticFieldFromInstanceMethod" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AutoBoxing" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreAddedToCollection" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoredTypes" value="java.util.stream.Stream,java.util.stream.IntStream,java.util.stream.LongStream,java.util.stream.DoubleStream,net.minecraft.client.Minecraft,net.minecraft.client.MainWindow" />
 | 
			
		||||
      <option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,net.minecraft.client.MinecraftClient,getInstance|getWindow" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="AutoUnboxing" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AwaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="AwaitWithoutCorrespondingSignal" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="BigDecimalEquals" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="BigDecimalLegacyMethod" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CallableParameterUseCaseInTypeContextInspection" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="CascadeStringReplacementInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="USE_SHORT_ARRAYS_SYNTAX" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="insideTryAllowed" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ClassIndependentOfModule" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ClassLoaderInstantiation" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ClassMayBeInterface" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="reportClassesWithNonAbstractMethods" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ClassMethodNameMatchesFieldNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ClassNewInstance" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ClassOnlyUsedInOneModule" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ClassOnlyUsedInOnePackage" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ClassOverridesFieldOfSuperClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="REPORT_PRIVATE_REDEFINITION" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ClassUnconnectedToPackage" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CloneableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CollectionsFieldAccessReplaceableByMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ComparatorNotSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ComposeMissingKeys" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConditionalExpression" enabled="true" level="INFORMATION" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreSimpleAssignmentsAndReturns" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConfusingElse" enabled="false" level="WEAK WARNING" enabled_by_default="false">
 | 
			
		||||
      <option name="reportWhenNoStatementFollow" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConfusingPlusesOrMinusesJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConstantMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConstantTestNGAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConvertJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConvertLambdaToReference" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ConvertOldAnnotations" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CssConvertColorToHexInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CssConvertColorToRgbInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CssMissingSemicolon" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CssReplaceWithShorthandUnsafely" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CustomClassloader" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CustomSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="CyclicPackageDependency" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DateToString" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DisallowWritingIntoStaticPropertiesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DisconnectedForeachInstructionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="DisjointPackage" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DivideByZeroJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DocumentWriteJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreOnVolatileVariables" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="DriverManagerGetConnection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DuplicateConditionJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="DynamicallyGeneratedCodeJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ES6ConvertIndexedForToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ES6ConvertToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ES6TopLevelAwaitExpression" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="EmptyDirectory" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_reportEmptyBlocks" value="true" />
 | 
			
		||||
      <option name="commentsAreContent" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreSwitchStatementsWithDefault" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ErrorRethrown" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ExceptionNameDoesntEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ExceptionPackage" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ExpectedExceptionNeverThrownTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ExplicitArgumentCanBeLambda" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ExternalizableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="countGettersAndSetters" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="FieldDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="FieldHidesSuperclassField" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreInvisibleFields" value="false" />
 | 
			
		||||
      <option name="ignoreStaticFields" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreTrivialFinalizers" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="FixedTimeStartWithInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="INFO" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="ForLoopThatDoesntUseLoopVariableJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="configuration">
 | 
			
		||||
        <list>
 | 
			
		||||
          <option value="\Codeception\Util\Debug::debug" />
 | 
			
		||||
          <option value="\Codeception\Util\Debug::pause" />
 | 
			
		||||
          <option value="\Doctrine\Common\Util\Debug::dump" />
 | 
			
		||||
          <option value="\Doctrine\Common\Util\Debug::export" />
 | 
			
		||||
          <option value="\Illuminate\Support\Debug\Dumper::dump" />
 | 
			
		||||
          <option value="\Symfony\Component\Debug\Debug::enable" />
 | 
			
		||||
          <option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
 | 
			
		||||
          <option value="\Symfony\Component\Debug\ErrorHandler::register" />
 | 
			
		||||
          <option value="\Symfony\Component\Debug\ExceptionHandler::register" />
 | 
			
		||||
          <option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
 | 
			
		||||
          <option value="\Zend\Debug\Debug::dump" />
 | 
			
		||||
          <option value="\Zend\Di\Display\Console::export" />
 | 
			
		||||
          <option value="dd" />
 | 
			
		||||
          <option value="debug_print_backtrace" />
 | 
			
		||||
          <option value="debug_zval_dump" />
 | 
			
		||||
          <option value="dpm" />
 | 
			
		||||
          <option value="dpq" />
 | 
			
		||||
          <option value="dsm" />
 | 
			
		||||
          <option value="dump" />
 | 
			
		||||
          <option value="dvm" />
 | 
			
		||||
          <option value="error_log" />
 | 
			
		||||
          <option value="kpr" />
 | 
			
		||||
          <option value="phpinfo" />
 | 
			
		||||
          <option value="print_r" />
 | 
			
		||||
          <option value="var_dump" />
 | 
			
		||||
          <option value="var_export" />
 | 
			
		||||
          <option value="wp_die" />
 | 
			
		||||
          <option value="xdebug_break" />
 | 
			
		||||
          <option value="xdebug_call_class" />
 | 
			
		||||
          <option value="xdebug_call_file" />
 | 
			
		||||
          <option value="xdebug_call_function" />
 | 
			
		||||
          <option value="xdebug_call_line" />
 | 
			
		||||
          <option value="xdebug_code_coverage_started" />
 | 
			
		||||
          <option value="xdebug_debug_zval" />
 | 
			
		||||
          <option value="xdebug_debug_zval_stdout" />
 | 
			
		||||
          <option value="xdebug_dump_superglobals" />
 | 
			
		||||
          <option value="xdebug_enable" />
 | 
			
		||||
          <option value="xdebug_get_code_coverage" />
 | 
			
		||||
          <option value="xdebug_get_collected_errors" />
 | 
			
		||||
          <option value="xdebug_get_declared_vars" />
 | 
			
		||||
          <option value="xdebug_get_function_stack" />
 | 
			
		||||
          <option value="xdebug_get_headers" />
 | 
			
		||||
          <option value="xdebug_get_monitored_functions" />
 | 
			
		||||
          <option value="xdebug_get_profiler_filename" />
 | 
			
		||||
          <option value="xdebug_get_stack_depth" />
 | 
			
		||||
          <option value="xdebug_get_tracefile_name" />
 | 
			
		||||
          <option value="xdebug_is_enabled" />
 | 
			
		||||
          <option value="xdebug_memory_usage" />
 | 
			
		||||
          <option value="xdebug_peak_memory_usage" />
 | 
			
		||||
          <option value="xdebug_print_function_stack" />
 | 
			
		||||
          <option value="xdebug_start_code_coverage" />
 | 
			
		||||
          <option value="xdebug_start_error_collection" />
 | 
			
		||||
          <option value="xdebug_start_function_monitor" />
 | 
			
		||||
          <option value="xdebug_start_trace" />
 | 
			
		||||
          <option value="xdebug_stop_code_coverage" />
 | 
			
		||||
          <option value="xdebug_stop_error_collection" />
 | 
			
		||||
          <option value="xdebug_stop_function_monitor" />
 | 
			
		||||
          <option value="xdebug_stop_trace" />
 | 
			
		||||
          <option value="xdebug_time_index" />
 | 
			
		||||
          <option value="xdebug_var_dump" />
 | 
			
		||||
        </list>
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="migratedIntoUserSpace" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="FunctionNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_regex" value="[a-z][A-Za-z]*" />
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="99" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="FunctionWithInconsistentReturnsJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="HibernateResource" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="insideTryAllowed" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="HtmlMissingClosingTag" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="HtmlPresentationalElement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="HtmlRequiredTitleAttribute" enabled="true" level="INFORMATION" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
 | 
			
		||||
      <option name="insideTryAllowed" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="IfStatementWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ImplicitDefaultCharsetUsage" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="InconsistentLanguageLevel" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="IncrementDecrementOperationEquivalentInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="InnerClassOnInterface" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreInnerInterfaces" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreInvisibleFields" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="InsertLiteralUnderscores" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="IsEmptyFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false">
 | 
			
		||||
      <option name="SUGGEST_TO_USE_COUNT_CHECK" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="IsNullFunctionUsageInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JDBCExecuteWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JDBCPrepareStatementWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="insideTryAllowed" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="JNDIResource" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="insideTryAllowed" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JSClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="99" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="JSConstructorReturnsPrimitive" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JSEqualityComparisonWithCoercion.TS" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="mySeverity" value="Always" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="JSJoinVariableDeclarationAndAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JSMissingSwitchBranches" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JSNonASCIINames" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="myAllowOnlyAscii" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="JSNonStrictModeUsed" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JSOctalInteger" enabled="true" level="ERROR" enabled_by_default="true">
 | 
			
		||||
      <option name="myReportNonStrictEs5" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="JSUndeclaredVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="myCheckGlobalDefinitions" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="JSUnusedGlobalSymbols" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="myReportUnusedDefinitions" value="true" />
 | 
			
		||||
      <option name="myReportUnusedProperties" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JUnitTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="JoinDeclarationAndAssignmentJava" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="LambdaCanBeMethodCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="LoadLibraryWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="REPORT_VARIABLES" value="true" />
 | 
			
		||||
      <option name="REPORT_PARAMETERS" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="LocalVariableDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="LocalVariableNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_regex" value="[a-z][A-Za-z]*" />
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="99" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="MalformedSetUpTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MethodCallSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_onlyPrivateOrFinal" value="false" />
 | 
			
		||||
      <option name="m_ignoreEmptyMethods" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="reportIncompatibleParameters" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MethodSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MisorderedAssertEqualsArgumentsTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MisorderedModifiersInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreObjectMethods" value="false" />
 | 
			
		||||
      <option name="ignoreAnonymousClassMethods" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_requireAnnotationsFirst" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NakedNotify" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreNegatedNullComparison" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="NestedAssignment" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NestedClassSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NestedConditionalExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NestedSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NestedTernaryOperatorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="NewExpressionSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonBlockStatementBodyJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonFinalClone" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonReproducibleMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonSerializableFieldInSerializableClass" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignorableAnnotations">
 | 
			
		||||
        <value />
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="ignoreAnonymousInnerClasses" value="false" />
 | 
			
		||||
      <option name="superClassString" value="java.awt.Component" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="NonSerializableObjectBoundToHttpSession" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonSerializableObjectPassedToObjectStream" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonSerializableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NotOptimalIfConditionsInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="REPORT_DUPLICATE_CONDITIONS" value="false" />
 | 
			
		||||
      <option name="SUGGEST_OPTIMIZING_CONDITIONS" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="NotifyCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NotifyWithoutCorrespondingWait" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="NumericToString" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="OCInconsistentNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ObjectAllocationIgnoredJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreRequiredObsoleteCollectionTypes" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="OffsetOperationsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="OneTimeUseVariablesInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ALLOW_LONG_STATEMENTS" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="OverloadedVarargsMethod" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PackageInMultipleModules" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreInvisibleFields" value="true" />
 | 
			
		||||
      <option name="m_ignoreStaticMethodParametersHidingInstanceFields" value="true" />
 | 
			
		||||
      <option name="m_ignoreForConstructors" value="true" />
 | 
			
		||||
      <option name="m_ignoreForPropertySetters" value="true" />
 | 
			
		||||
      <option name="m_ignoreForAbstractMethods" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ParameterNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_regex" value="[a-z][A-Za-z]*" />
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="99" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpAssignmentReplaceableWithOperatorAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpAssignmentReplaceableWithPrefixExpressionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpCSValidationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
 | 
			
		||||
      <option name="EXTENSIONS" value="php,js,css,inc" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpClassNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="0" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpClosureCanBeConvertedToShortArrowFunctionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpCompoundNamespaceDepthInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpConstantNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="0" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpConstantReassignmentInspection" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpDocMissingThrowsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="SKIP_ON_EMPTY_PHPDOC" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpFunctionNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="0" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpInconsistentReturnPointsInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ALLOW_RETURN_NULL_IN_VOID" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpLongTypeFormInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpLoopCanBeConvertedToArrayFillInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpLoopCanBeConvertedToArrayFilterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpLoopCanBeConvertedToArrayMapInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpMethodNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="0" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpMissingParentCallMagicInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpMissingReturnTypeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpMissingStrictTypesDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpMissingVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpNewClassMissingParameterListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpNonCanonicalElementsOrderInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpOverridingMethodVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpPropertyNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_regex" value="[a-z][_a-z\d]*" />
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="0" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="PhpSeparateElseIfInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpSingleStatementWithBracesInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="myCommentsCountAsContent" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpStatementWithoutBracesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpStaticAsDynamicMethodCallInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="SHOW_FOR_MAGIC" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpTraditionalSyntaxArrayLiteralInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpTraitsUseListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpUndefinedCallbackInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpUndefinedClassConstantInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="DOWNGRADE_SEVERITY" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="DOWNGRADE_SEVERITY" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpUnused" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="SHOW_UNUSED_BY_ENTRIES" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="DONT_REPORT_ANONYMOUS" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpVarUsageInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PhpVariableNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_minLength" value="0" />
 | 
			
		||||
      <option name="m_maxLength" value="0" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="PhpVariableVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PointlessBitwiseExpressionJS" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreExpressionsContainingConstants" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ProblematicWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PropertyCanBeStaticInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_onlyWhenTypesAreKnown" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="RawTypeCanBeGeneric" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReadObjectAndWriteObjectPrivate" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReadObjectInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReadResolveAndWriteReplaceProtected" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="RedundantElseClauseInspection" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreSerializable" value="true" />
 | 
			
		||||
      <option name="ignoreCloneable" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="RedundantObjectTypeCheck" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="IGNORE_ALL" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ReferencingObjectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="RegExpOctalEscape" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreLazyOperators" value="true" />
 | 
			
		||||
      <option name="ignoreObscureOperators" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ReplaceAssignmentWithOperatorAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceStringFormatWithLiteral" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceSubstringWithDropLast" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceSubstringWithIndexingOperation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceSubstringWithSubstringAfter" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceSubstringWithSubstringBefore" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReplaceSubstringWithTake" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ResultSetIndexZero" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="RsSimplifyBooleanExpression" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="RuntimeExec" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="RuntimeExecWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="optionConfiguration">
 | 
			
		||||
        <list>
 | 
			
		||||
          <option value="barryvdh/laravel-debugbar" />
 | 
			
		||||
          <option value="behat/behat" />
 | 
			
		||||
          <option value="brianium/paratest" />
 | 
			
		||||
          <option value="codeception/codeception" />
 | 
			
		||||
          <option value="codedungeon/phpunit-result-printer" />
 | 
			
		||||
          <option value="composer/composer" />
 | 
			
		||||
          <option value="doctrine/coding-standard" />
 | 
			
		||||
          <option value="filp/whoops" />
 | 
			
		||||
          <option value="friendsofphp/php-cs-fixer" />
 | 
			
		||||
          <option value="humbug/humbug" />
 | 
			
		||||
          <option value="infection/infection" />
 | 
			
		||||
          <option value="jakub-onderka/php-parallel-lint" />
 | 
			
		||||
          <option value="johnkary/phpunit-speedtrap" />
 | 
			
		||||
          <option value="kalessil/production-dependencies-guard" />
 | 
			
		||||
          <option value="mikey179/vfsStream" />
 | 
			
		||||
          <option value="mockery/mockery" />
 | 
			
		||||
          <option value="mybuilder/phpunit-accelerator" />
 | 
			
		||||
          <option value="orchestra/testbench" />
 | 
			
		||||
          <option value="pdepend/pdepend" />
 | 
			
		||||
          <option value="phan/phan" />
 | 
			
		||||
          <option value="phing/phing" />
 | 
			
		||||
          <option value="phpcompatibility/php-compatibility" />
 | 
			
		||||
          <option value="phpmd/phpmd" />
 | 
			
		||||
          <option value="phpro/grumphp" />
 | 
			
		||||
          <option value="phpspec/phpspec" />
 | 
			
		||||
          <option value="phpspec/prophecy" />
 | 
			
		||||
          <option value="phpstan/phpstan" />
 | 
			
		||||
          <option value="phpunit/phpunit" />
 | 
			
		||||
          <option value="povils/phpmnd" />
 | 
			
		||||
          <option value="roave/security-advisories" />
 | 
			
		||||
          <option value="satooshi/php-coveralls" />
 | 
			
		||||
          <option value="sebastian/phpcpd" />
 | 
			
		||||
          <option value="slevomat/coding-standard" />
 | 
			
		||||
          <option value="spatie/phpunit-watcher" />
 | 
			
		||||
          <option value="squizlabs/php_codesniffer" />
 | 
			
		||||
          <option value="sstalle/php7cc" />
 | 
			
		||||
          <option value="symfony/debug" />
 | 
			
		||||
          <option value="symfony/maker-bundle" />
 | 
			
		||||
          <option value="symfony/phpunit-bridge" />
 | 
			
		||||
          <option value="symfony/var-dumper" />
 | 
			
		||||
          <option value="vimeo/psalm" />
 | 
			
		||||
          <option value="wimg/php-compatibility" />
 | 
			
		||||
          <option value="wp-coding-standards/wpcs" />
 | 
			
		||||
          <option value="yiisoft/yii2-coding-standards" />
 | 
			
		||||
          <option value="yiisoft/yii2-debug" />
 | 
			
		||||
          <option value="yiisoft/yii2-gii" />
 | 
			
		||||
          <option value="zendframework/zend-coding-standard" />
 | 
			
		||||
          <option value="zendframework/zend-debug" />
 | 
			
		||||
          <option value="zendframework/zend-test" />
 | 
			
		||||
        </list>
 | 
			
		||||
      </option>
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="SerialPersistentFieldsWithWrongSignature" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SerialVersionUIDNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreAnonymousInnerClasses" value="false" />
 | 
			
		||||
      <option name="superClassString" value="java.awt.Component" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="SerializableHasSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreAnonymousInnerClasses" value="false" />
 | 
			
		||||
      <option name="superClassString" value="java.awt.Component" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="SerializableInnerClassHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreAnonymousInnerClasses" value="false" />
 | 
			
		||||
      <option name="superClassString" value="java.awt.Component" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="SerializableInnerClassWithNonSerializableOuterClass" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreAnonymousInnerClasses" value="false" />
 | 
			
		||||
      <option name="superClassString" value="java.awt.Component" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="SerializableStoresNonSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SerializableWithUnconstructableAncestor" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SeveralTargetsMessage" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SharedThreadLocalRandom" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ShortEchoTagCanBeUsedInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SignalWithoutCorrespondingAwait" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SimplifiableIfStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SingleStatementInBlock" enabled="false" level="INFORMATION" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="insideTryAllowed" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
 | 
			
		||||
      <option name="processCode" value="true" />
 | 
			
		||||
      <option name="processLiterals" value="true" />
 | 
			
		||||
      <option name="processComments" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="SqlGotoInspection" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="SqlWithoutWhereInspection" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="myDontWarnForLimit" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="StrTrUsageAsStrReplaceInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="StringConcatenationInMessageFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="SUPPRESS_FOR_VALUES_WHICH_COULD_BE_NULL" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="onlyWarnOnLoop" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SubStrUsedAsStrPosInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SuspiciousLiteralUnderscore" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SynchronizationOnStaticField" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_includeNativeMethods" value="true" />
 | 
			
		||||
      <option name="ignoreSynchronizedSuperMethods" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SystemProperties" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SystemRunFinalizersOnExit" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="SystemSetSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreSupers" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="TextLabelInSwitchStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ThreadLocalNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreRethrownExceptions" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ThrowRawExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="TimeToString" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="TooBroadScope" enabled="true" level="WEAK WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_allowConstructorAsInitializer" value="false" />
 | 
			
		||||
      <option name="m_onlyLookAtBlocks" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="TransientFieldInNonSerializableClass" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="TransientFieldNotInitialized" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnNecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnclearBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnconstrainedVariableType" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UndeclaredTests" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnknownInspectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreStaticFieldAccesses" value="false" />
 | 
			
		||||
      <option name="m_ignoreStaticMethodCalls" value="false" />
 | 
			
		||||
      <option name="m_ignoreStaticAccessFromStaticContext" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnnecessaryBoxing" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="onlyReportSuperfluouslyBoxed" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreAnnotations" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnnecessaryLocalVariable" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreImmediatelyReturnedVariables" value="true" />
 | 
			
		||||
      <option name="m_ignoreAnnotatedVariables" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreImmediatelyReturnedVariables" value="true" />
 | 
			
		||||
      <option name="m_ignoreAnnotatedVariables" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnnecessaryParentheses" enabled="true" level="INFORMATION" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreClarifyingParentheses" value="true" />
 | 
			
		||||
      <option name="ignoreParenthesesOnConditionals" value="false" />
 | 
			
		||||
      <option name="ignoreParenthesesOnLambdaParameter" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UnnecessaryUnboxing" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="onlyReportSuperfluouslyUnboxed" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnsetConstructsCanBeMergedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreSemicolonAtEndOfBlock" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnusedCatchParameterJS" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="m_ignoreCatchBlocksWithComments" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="UnusedProperty" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="UseOfAWTPeerClass" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UseOfProcessBuilder" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UseOfSunClasses" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UsingInclusionReturnValueInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignorableAnnotations">
 | 
			
		||||
        <value />
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="ignoreClassesWithOnlyMain" value="true" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="VariableUseSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="VoidExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="WaitNotifyNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="WaitOrAwaitWithoutTimeout" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true">
 | 
			
		||||
      <option name="ignoreNonEmtpyLoops" value="false" />
 | 
			
		||||
    </inspection_tool>
 | 
			
		||||
    <inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
  </profile>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
<component name="InspectionProjectProfileManager">
 | 
			
		||||
  <settings>
 | 
			
		||||
    <option name="PROJECT_PROFILE" value="Project" />
 | 
			
		||||
    <version value="1.0" />
 | 
			
		||||
  </settings>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="JavaScriptLibraryMappings">
 | 
			
		||||
    <file url="PROJECT" libraries="{@types/jquery}" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="TweetDuck" type="DotNetProject" factoryName=".NET Project">
 | 
			
		||||
    <option name="EXE_PATH" value="$PROJECT_DIR$/windows/TweetDuck/bin/x86/Debug/TweetDuck.exe" />
 | 
			
		||||
    <option name="PROGRAM_PARAMETERS" value="-datafolder TweetDuckDebug -nogdpr" />
 | 
			
		||||
    <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/windows/TweetDuck/bin/x86/Debug" />
 | 
			
		||||
    <option name="PASS_PARENT_ENVS" value="1" />
 | 
			
		||||
    <option name="USE_EXTERNAL_CONSOLE" value="0" />
 | 
			
		||||
    <option name="USE_MONO" value="0" />
 | 
			
		||||
    <option name="RUNTIME_ARGUMENTS" value="" />
 | 
			
		||||
    <option name="PROJECT_PATH" value="$PROJECT_DIR$/windows/TweetDuck/TweetDuck.csproj" />
 | 
			
		||||
    <option name="PROJECT_EXE_PATH_TRACKING" value="1" />
 | 
			
		||||
    <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
 | 
			
		||||
    <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
 | 
			
		||||
    <option name="PROJECT_KIND" value="DotNetCore" />
 | 
			
		||||
    <option name="PROJECT_TFM" value="net7.0-windows" />
 | 
			
		||||
    <method v="2">
 | 
			
		||||
      <option name="Build" />
 | 
			
		||||
    </method>
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										11
									
								
								.idea/.idea.TweetDuck/.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								.idea/.idea.TweetDuck/.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							@@ -1,11 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="GitSharedSettings">
 | 
			
		||||
    <option name="FORCE_PUSH_PROHIBITED_PATTERNS">
 | 
			
		||||
      <list />
 | 
			
		||||
    </option>
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="VcsDirectoryMappings">
 | 
			
		||||
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
							
								
								
									
										48
									
								
								Configuration/Arguments.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Configuration/Arguments.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
using System;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Configuration{
 | 
			
		||||
    static class Arguments{
 | 
			
		||||
        // public args
 | 
			
		||||
        public const string ArgDataFolder = "-datafolder";
 | 
			
		||||
        public const string ArgLogging = "-log";
 | 
			
		||||
        public const string ArgIgnoreGDPR = "-nogdpr";
 | 
			
		||||
        public const string ArgFreeze = "-freeze";
 | 
			
		||||
 | 
			
		||||
        // internal args
 | 
			
		||||
        public const string ArgRestart = "-restart";
 | 
			
		||||
        public const string ArgImportCookies = "-importcookies";
 | 
			
		||||
        public const string ArgDeleteCookies = "-deletecookies";
 | 
			
		||||
        public const string ArgUpdated = "-updated";
 | 
			
		||||
 | 
			
		||||
        // class data and methods
 | 
			
		||||
        private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs());
 | 
			
		||||
 | 
			
		||||
        public static bool HasFlag(string flag){
 | 
			
		||||
            return Current.HasFlag(flag);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetValue(string key, string defaultValue){
 | 
			
		||||
            return Current.GetValue(key, defaultValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static CommandLineArgs GetCurrentClean(){
 | 
			
		||||
            CommandLineArgs args = Current.Clone();
 | 
			
		||||
            args.RemoveFlag(ArgRestart);
 | 
			
		||||
            args.RemoveFlag(ArgImportCookies);
 | 
			
		||||
            args.RemoveFlag(ArgDeleteCookies);
 | 
			
		||||
            args.RemoveFlag(ArgUpdated);
 | 
			
		||||
            return args;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static CommandLineArgs GetCurrentForInstaller(){
 | 
			
		||||
            CommandLineArgs args = GetCurrentClean();
 | 
			
		||||
            args.AddFlag(ArgUpdated);
 | 
			
		||||
            return args;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetCurrentForInstallerCmd(){
 | 
			
		||||
            return GetCurrentForInstaller().ToString().Replace("\"", "::");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								Configuration/ConfigManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								Configuration/ConfigManager.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using TweetDuck.Configuration.Instance;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
using TweetDuck.Data.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Configuration{
 | 
			
		||||
    sealed class ConfigManager{
 | 
			
		||||
        public UserConfig User { get; }
 | 
			
		||||
        public SystemConfig System { get; }
 | 
			
		||||
        public PluginConfig Plugins { get; }
 | 
			
		||||
        
 | 
			
		||||
        public event EventHandler ProgramRestartRequested;
 | 
			
		||||
 | 
			
		||||
        private readonly FileConfigInstance<UserConfig> infoUser;
 | 
			
		||||
        private readonly FileConfigInstance<SystemConfig> infoSystem;
 | 
			
		||||
        private readonly PluginConfigInstance infoPlugins;
 | 
			
		||||
 | 
			
		||||
        private readonly IConfigInstance<BaseConfig>[] infoList;
 | 
			
		||||
 | 
			
		||||
        public ConfigManager(){
 | 
			
		||||
            User = new UserConfig(this);
 | 
			
		||||
            System = new SystemConfig(this);
 | 
			
		||||
            Plugins = new PluginConfig(this);
 | 
			
		||||
            
 | 
			
		||||
            infoList = new IConfigInstance<BaseConfig>[]{
 | 
			
		||||
                infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
 | 
			
		||||
                infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
 | 
			
		||||
                infoPlugins = new PluginConfigInstance(Program.PluginConfigFilePath, Plugins)
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // TODO refactor further
 | 
			
		||||
 | 
			
		||||
            infoUser.Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
 | 
			
		||||
 | 
			
		||||
            infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{
 | 
			
		||||
                ConvertToString = value => $"{value.X} {value.Y}",
 | 
			
		||||
                ConvertToObject = value => {
 | 
			
		||||
                    int[] elements = StringUtils.ParseInts(value, ' ');
 | 
			
		||||
                    return new Point(elements[0], elements[1]);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
 | 
			
		||||
                ConvertToString = value => $"{value.Width} {value.Height}",
 | 
			
		||||
                ConvertToObject = value => {
 | 
			
		||||
                    int[] elements = StringUtils.ParseInts(value, ' ');
 | 
			
		||||
                    return new Size(elements[0], elements[1]);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void LoadAll(){
 | 
			
		||||
            infoUser.Load();
 | 
			
		||||
            infoSystem.Load();
 | 
			
		||||
            infoPlugins.Load();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void SaveAll(){
 | 
			
		||||
            infoUser.Save();
 | 
			
		||||
            infoSystem.Save();
 | 
			
		||||
            infoPlugins.Save();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ReloadAll(){
 | 
			
		||||
            infoUser.Reload();
 | 
			
		||||
            infoSystem.Reload();
 | 
			
		||||
            infoPlugins.Reload();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void TriggerProgramRestartRequested(){
 | 
			
		||||
            ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){
 | 
			
		||||
            Type instanceType = instance.GetType();
 | 
			
		||||
            return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public abstract class BaseConfig{
 | 
			
		||||
            private readonly ConfigManager configManager;
 | 
			
		||||
 | 
			
		||||
            protected BaseConfig(ConfigManager configManager){
 | 
			
		||||
                this.configManager = configManager;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Management
 | 
			
		||||
 | 
			
		||||
            public void Save(){
 | 
			
		||||
                configManager.GetInstanceInfo(this).Save();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void Reload(){
 | 
			
		||||
                configManager.GetInstanceInfo(this).Reload();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void Reset(){
 | 
			
		||||
                configManager.GetInstanceInfo(this).Reset();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Construction methods
 | 
			
		||||
 | 
			
		||||
            public T ConstructWithDefaults<T>() where T : BaseConfig{
 | 
			
		||||
                return ConstructWithDefaults(configManager) as T;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            protected abstract BaseConfig ConstructWithDefaults(ConfigManager configManager);
 | 
			
		||||
 | 
			
		||||
            // Utility methods
 | 
			
		||||
 | 
			
		||||
            protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
 | 
			
		||||
                if (!EqualityComparer<T>.Default.Equals(field, value)){
 | 
			
		||||
                    field = value;
 | 
			
		||||
                    eventHandler?.Invoke(this, EventArgs.Empty);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){
 | 
			
		||||
                if (!EqualityComparer<T>.Default.Equals(field, value)){
 | 
			
		||||
                    field = value;
 | 
			
		||||
                    configManager.TriggerProgramRestartRequested();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										104
									
								
								Configuration/Instance/FileConfigInstance.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								Configuration/Instance/FileConfigInstance.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using TweetDuck.Data.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Configuration.Instance{
 | 
			
		||||
    sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{
 | 
			
		||||
        private const string ErrorTitle = "Configuration Error";
 | 
			
		||||
 | 
			
		||||
        public T Instance { get; }
 | 
			
		||||
        public FileSerializer<T> Serializer { get; }
 | 
			
		||||
 | 
			
		||||
        private readonly string filenameMain;
 | 
			
		||||
        private readonly string filenameBackup;
 | 
			
		||||
        private readonly string errorIdentifier;
 | 
			
		||||
 | 
			
		||||
        public FileConfigInstance(string filename, T instance, string errorIdentifier){
 | 
			
		||||
            this.filenameMain = filename;
 | 
			
		||||
            this.filenameBackup = filename+".bak";
 | 
			
		||||
            this.errorIdentifier = errorIdentifier;
 | 
			
		||||
 | 
			
		||||
            this.Instance = instance;
 | 
			
		||||
            this.Serializer = new FileSerializer<T>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void LoadInternal(bool backup){
 | 
			
		||||
            Serializer.Read(backup ? filenameBackup : filenameMain, Instance);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Load(){
 | 
			
		||||
            Exception firstException = null;
 | 
			
		||||
            
 | 
			
		||||
            for(int attempt = 0; attempt < 2; attempt++){
 | 
			
		||||
                try{
 | 
			
		||||
                    LoadInternal(attempt > 0);
 | 
			
		||||
 | 
			
		||||
                    if (firstException != null){ // silently log exception that caused a backup restore
 | 
			
		||||
                        Program.Reporter.LogImportant(firstException.ToString());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }catch(FileNotFoundException){
 | 
			
		||||
                }catch(DirectoryNotFoundException){
 | 
			
		||||
                    break;
 | 
			
		||||
                }catch(Exception e){
 | 
			
		||||
                    if (firstException == null){
 | 
			
		||||
                        firstException = e;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (firstException is FormatException){
 | 
			
		||||
                Program.Reporter.HandleException(ErrorTitle, "The configuration file for "+errorIdentifier+" is outdated or corrupted. If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
 | 
			
		||||
            }
 | 
			
		||||
            else if (firstException is SerializationSoftException sse){
 | 
			
		||||
                Program.Reporter.HandleException(ErrorTitle, $"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for "+errorIdentifier+". If you continue, some of your "+errorIdentifier+" will be reset.", true, firstException);
 | 
			
		||||
            }
 | 
			
		||||
            else if (firstException != null){
 | 
			
		||||
                Program.Reporter.HandleException(ErrorTitle, "Could not open the configuration file for "+errorIdentifier+". If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Save(){
 | 
			
		||||
            try{
 | 
			
		||||
                if (File.Exists(filenameMain)){
 | 
			
		||||
                    File.Delete(filenameBackup);
 | 
			
		||||
                    File.Move(filenameMain, filenameBackup);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Serializer.Write(filenameMain, Instance);
 | 
			
		||||
            }catch(SerializationSoftException e){
 | 
			
		||||
                Program.Reporter.HandleException(ErrorTitle, $"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for "+errorIdentifier+".", true, e);
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException(ErrorTitle, "Could not save the configuration file for "+errorIdentifier+".", true, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Reload(){
 | 
			
		||||
            try{
 | 
			
		||||
                LoadInternal(false);
 | 
			
		||||
            }catch(FileNotFoundException){
 | 
			
		||||
                try{
 | 
			
		||||
                    Serializer.Write(filenameMain, Instance.ConstructWithDefaults<T>());
 | 
			
		||||
                    LoadInternal(false);
 | 
			
		||||
                }catch(Exception e){
 | 
			
		||||
                    Program.Reporter.HandleException(ErrorTitle, "Could not regenerate the configuration file for "+errorIdentifier+".", true, e);
 | 
			
		||||
                }
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException(ErrorTitle, "Could not reload the configuration file for "+errorIdentifier+".", true, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Reset(){
 | 
			
		||||
            try{
 | 
			
		||||
                File.Delete(filenameMain);
 | 
			
		||||
                File.Delete(filenameBackup);
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException(ErrorTitle, "Could not delete configuration files to reset "+errorIdentifier+".", true, e);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            Reload();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								Configuration/Instance/IConfigInstance.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Configuration/Instance/IConfigInstance.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
namespace TweetDuck.Configuration.Instance{
 | 
			
		||||
    interface IConfigInstance<out T>{
 | 
			
		||||
        T Instance { get; }
 | 
			
		||||
 | 
			
		||||
        void Save();
 | 
			
		||||
        void Reload();
 | 
			
		||||
        void Reset();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										69
									
								
								Configuration/Instance/PluginConfigInstance.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								Configuration/Instance/PluginConfigInstance.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Configuration.Instance{
 | 
			
		||||
    class PluginConfigInstance : IConfigInstance<PluginConfig>{
 | 
			
		||||
        public PluginConfig Instance { get; }
 | 
			
		||||
 | 
			
		||||
        private readonly string filename;
 | 
			
		||||
 | 
			
		||||
        public PluginConfigInstance(string filename, PluginConfig instance){
 | 
			
		||||
            this.filename = filename;
 | 
			
		||||
            this.Instance = instance;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Load(){
 | 
			
		||||
            try{
 | 
			
		||||
                using(StreamReader reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
 | 
			
		||||
                    string line = reader.ReadLine();
 | 
			
		||||
 | 
			
		||||
                    if (line == "#Disabled"){
 | 
			
		||||
                        HashSet<string> newDisabled = new HashSet<string>();
 | 
			
		||||
 | 
			
		||||
                        while((line = reader.ReadLine()) != null){
 | 
			
		||||
                            newDisabled.Add(line);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        Instance.ReloadSilently(newDisabled);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }catch(FileNotFoundException){
 | 
			
		||||
            }catch(DirectoryNotFoundException){
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Save(){
 | 
			
		||||
            try{
 | 
			
		||||
                using(StreamWriter writer = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
 | 
			
		||||
                    writer.WriteLine("#Disabled");
 | 
			
		||||
 | 
			
		||||
                    foreach(string identifier in Instance.DisabledPlugins){
 | 
			
		||||
                        writer.WriteLine(identifier);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Reload(){
 | 
			
		||||
            Load();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Reset(){
 | 
			
		||||
            try{
 | 
			
		||||
                File.Delete(filename);
 | 
			
		||||
                Instance.ReloadSilently(Instance.ConstructWithDefaults<PluginConfig>().DisabledPlugins);
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Plugin Configuration Error", "Could not delete the plugin configuration file.", true, e);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            Reload();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										183
									
								
								Configuration/LockManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								Configuration/LockManager.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,183 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Configuration{
 | 
			
		||||
    sealed class LockManager{
 | 
			
		||||
        private const int RetryDelay = 250;
 | 
			
		||||
 | 
			
		||||
        public enum Result{
 | 
			
		||||
            Success, HasProcess, Fail
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly string file;
 | 
			
		||||
        private FileStream lockStream;
 | 
			
		||||
        private Process lockingProcess;
 | 
			
		||||
 | 
			
		||||
        public LockManager(string file){
 | 
			
		||||
            this.file = file;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Lock file
 | 
			
		||||
 | 
			
		||||
        private bool ReleaseLockFileStream(){
 | 
			
		||||
            if (lockStream != null){
 | 
			
		||||
                lockStream.Dispose();
 | 
			
		||||
                lockStream = null;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Result TryCreateLockFile(){
 | 
			
		||||
            void CreateLockFileStream(){
 | 
			
		||||
                lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
 | 
			
		||||
                lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
 | 
			
		||||
                lockStream.Flush(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                CreateLockFileStream();
 | 
			
		||||
                return Result.Success;
 | 
			
		||||
            }catch(DirectoryNotFoundException){
 | 
			
		||||
                try{
 | 
			
		||||
                    CreateLockFileStream();
 | 
			
		||||
                    return Result.Success;
 | 
			
		||||
                }catch{
 | 
			
		||||
                    ReleaseLockFileStream();
 | 
			
		||||
                    return Result.Fail;
 | 
			
		||||
                }
 | 
			
		||||
            }catch(IOException){
 | 
			
		||||
                return Result.HasProcess;
 | 
			
		||||
            }catch{
 | 
			
		||||
                ReleaseLockFileStream();
 | 
			
		||||
                return Result.Fail;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Lock management
 | 
			
		||||
 | 
			
		||||
        public Result Lock(){
 | 
			
		||||
            if (lockStream != null){
 | 
			
		||||
                return Result.Success;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Result initialResult = TryCreateLockFile();
 | 
			
		||||
 | 
			
		||||
            if (initialResult == Result.HasProcess){
 | 
			
		||||
                try{
 | 
			
		||||
                    int pid;
 | 
			
		||||
 | 
			
		||||
                    using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
 | 
			
		||||
                        byte[] bytes = new byte[sizeof(int)];
 | 
			
		||||
                        fileStream.Read(bytes, 0, bytes.Length);
 | 
			
		||||
                        pid = BitConverter.ToInt32(bytes, 0);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    try{
 | 
			
		||||
                        Process foundProcess = Process.GetProcessById(pid);
 | 
			
		||||
 | 
			
		||||
                        using(Process currentProcess = Process.GetCurrentProcess()){
 | 
			
		||||
                            if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
 | 
			
		||||
                                lockingProcess = foundProcess;
 | 
			
		||||
                            }
 | 
			
		||||
                            else{
 | 
			
		||||
                                foundProcess.Close();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }catch{
 | 
			
		||||
                        // GetProcessById throws ArgumentException if the process is missing
 | 
			
		||||
                        // Process.MainModule can throw exceptions in some cases
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return lockingProcess == null ? Result.Fail : Result.HasProcess;
 | 
			
		||||
                }catch{
 | 
			
		||||
                    return Result.Fail;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return initialResult;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Result LockWait(int timeout){
 | 
			
		||||
            for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
 | 
			
		||||
                Result result = Lock();
 | 
			
		||||
 | 
			
		||||
                if (result == Result.HasProcess){
 | 
			
		||||
                    Thread.Sleep(RetryDelay);
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    return result;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Lock();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Unlock(){
 | 
			
		||||
            if (ReleaseLockFileStream()){
 | 
			
		||||
                try{
 | 
			
		||||
                    File.Delete(file);
 | 
			
		||||
                }catch(Exception e){
 | 
			
		||||
                    Program.Reporter.LogImportant(e.ToString());
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Locking process
 | 
			
		||||
 | 
			
		||||
        public bool RestoreLockingProcess(int failTimeout){
 | 
			
		||||
            if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
 | 
			
		||||
                NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
 | 
			
		||||
 | 
			
		||||
                if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool CloseLockingProcess(int closeTimeout, int killTimeout){
 | 
			
		||||
            if (lockingProcess != null){
 | 
			
		||||
                try{
 | 
			
		||||
                    if (lockingProcess.CloseMainWindow()){
 | 
			
		||||
                        WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!lockingProcess.HasExited){
 | 
			
		||||
                        lockingProcess.Kill();
 | 
			
		||||
                        WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (lockingProcess.HasExited){
 | 
			
		||||
                        lockingProcess.Dispose();
 | 
			
		||||
                        lockingProcess = null;
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
 | 
			
		||||
                    if (lockingProcess != null){
 | 
			
		||||
                        bool hasExited = CheckLockingProcessExited();
 | 
			
		||||
                        lockingProcess.Dispose();
 | 
			
		||||
                        return hasExited;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool CheckLockingProcessExited(){
 | 
			
		||||
            lockingProcess.Refresh();
 | 
			
		||||
            return lockingProcess.HasExited;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								Configuration/PluginConfig.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Configuration/PluginConfig.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Plugins.Events;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Configuration{
 | 
			
		||||
    sealed class PluginConfig : ConfigManager.BaseConfig, IPluginConfig{
 | 
			
		||||
        private static readonly string[] DefaultDisabled = {
 | 
			
		||||
            "official/clear-columns",
 | 
			
		||||
            "official/reply-account"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // CONFIGURATION
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<string> DisabledPlugins => disabled;
 | 
			
		||||
        
 | 
			
		||||
        public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
 | 
			
		||||
 | 
			
		||||
        public void SetEnabled(Plugin plugin, bool enabled){
 | 
			
		||||
            if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
 | 
			
		||||
                PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
 | 
			
		||||
                Save();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool IsEnabled(Plugin plugin){
 | 
			
		||||
            return !disabled.Contains(plugin.Identifier);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ReloadSilently(IEnumerable<string> newDisabled){
 | 
			
		||||
            disabled.Clear();
 | 
			
		||||
            disabled.UnionWith(newDisabled);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
 | 
			
		||||
        
 | 
			
		||||
        // END OF CONFIG
 | 
			
		||||
 | 
			
		||||
        public PluginConfig(ConfigManager configManager) : base(configManager){}
 | 
			
		||||
 | 
			
		||||
        protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
 | 
			
		||||
            return new PluginConfig(configManager);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								Configuration/SystemConfig.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Configuration/SystemConfig.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
namespace TweetDuck.Configuration{
 | 
			
		||||
    sealed class SystemConfig : ConfigManager.BaseConfig{
 | 
			
		||||
 | 
			
		||||
        // CONFIGURATION DATA
 | 
			
		||||
        
 | 
			
		||||
        public bool _hardwareAcceleration = true;
 | 
			
		||||
        
 | 
			
		||||
        public bool ClearCacheAutomatically { get; set; } = true;
 | 
			
		||||
        public int ClearCacheThreshold      { get; set; } = 250;
 | 
			
		||||
 | 
			
		||||
        // SPECIAL PROPERTIES
 | 
			
		||||
        
 | 
			
		||||
        public bool HardwareAcceleration{
 | 
			
		||||
            get => _hardwareAcceleration;
 | 
			
		||||
            set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // END OF CONFIG
 | 
			
		||||
 | 
			
		||||
        public SystemConfig(ConfigManager configManager) : base(configManager){}
 | 
			
		||||
 | 
			
		||||
        protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
 | 
			
		||||
            return new SystemConfig(configManager);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										143
									
								
								Configuration/UserConfig.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								Configuration/UserConfig.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Notification;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Configuration{
 | 
			
		||||
    sealed class UserConfig : ConfigManager.BaseConfig{
 | 
			
		||||
        
 | 
			
		||||
        // CONFIGURATION DATA
 | 
			
		||||
 | 
			
		||||
        public bool FirstRun            { get; set; } = true;
 | 
			
		||||
        public bool AllowDataCollection { get; set; } = false;
 | 
			
		||||
 | 
			
		||||
        public WindowState BrowserWindow { get; set; } = new WindowState();
 | 
			
		||||
        public Size PluginsWindowSize    { get; set; } = Size.Empty;
 | 
			
		||||
 | 
			
		||||
        public bool ExpandLinksOnHover        { get; set; } = true;
 | 
			
		||||
        public bool OpenSearchInFirstColumn   { get; set; } = true;
 | 
			
		||||
        public bool KeepLikeFollowDialogsOpen { get; set; } = true;
 | 
			
		||||
        public bool BestImageQuality          { get; set; } = true;
 | 
			
		||||
        public bool EnableAnimatedImages      { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
        private bool _enableSmoothScrolling = true;
 | 
			
		||||
        private bool _enableTouchAdjustment = false;
 | 
			
		||||
        private string _customCefArgs       = null;
 | 
			
		||||
 | 
			
		||||
        public string BrowserPath            { get; set; } = null;
 | 
			
		||||
        public bool IgnoreTrackingUrlWarning { get; set; } = false;
 | 
			
		||||
        public string SearchEngineUrl        { get; set; } = null;
 | 
			
		||||
        private int _zoomLevel                             = 100;
 | 
			
		||||
 | 
			
		||||
        public int VideoPlayerVolume { get; set; } = 50;
 | 
			
		||||
        
 | 
			
		||||
        public bool EnableSpellCheck { get; set; } = false;
 | 
			
		||||
        private string _spellCheckLanguage         = "en-US";
 | 
			
		||||
 | 
			
		||||
        public string TranslationTarget { get; set; } = "en";
 | 
			
		||||
        
 | 
			
		||||
        private TrayIcon.Behavior _trayBehavior       = TrayIcon.Behavior.Disabled;
 | 
			
		||||
        public bool EnableTrayHighlight { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
        public bool EnableUpdateCheck { get; set; } = true;
 | 
			
		||||
        public string DismissedUpdate { get; set; } = null;
 | 
			
		||||
 | 
			
		||||
        public bool DisplayNotificationColumn    { get; set; } = false;
 | 
			
		||||
        public bool NotificationMediaPreviews    { get; set; } = true;
 | 
			
		||||
        public bool NotificationSkipOnLinkClick  { get; set; } = false;
 | 
			
		||||
        public bool NotificationNonIntrusiveMode { get; set; } = true;
 | 
			
		||||
        public int NotificationIdlePauseSeconds  { get; set; } = 0;
 | 
			
		||||
 | 
			
		||||
        public bool DisplayNotificationTimer   { get; set; } = true;
 | 
			
		||||
        public bool NotificationTimerCountDown { get; set; } = false;
 | 
			
		||||
        public int NotificationDurationValue   { get; set; } = 25;
 | 
			
		||||
 | 
			
		||||
        public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
 | 
			
		||||
        public Point CustomNotificationPosition                { get; set; } = ControlExtensions.InvisibleLocation;
 | 
			
		||||
        public int NotificationDisplay                         { get; set; } = 0;
 | 
			
		||||
        public int NotificationEdgeDistance                    { get; set; } = 8;
 | 
			
		||||
 | 
			
		||||
        public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
 | 
			
		||||
        public Size CustomNotificationSize             { get; set; } = Size.Empty;
 | 
			
		||||
        public int NotificationScrollSpeed             { get; set; } = 100;
 | 
			
		||||
        
 | 
			
		||||
        private string _notificationSoundPath;
 | 
			
		||||
        private int _notificationSoundVolume = 100;
 | 
			
		||||
 | 
			
		||||
        private bool _muteNotifications;
 | 
			
		||||
 | 
			
		||||
        public string CustomBrowserCSS      { get; set; } = null;
 | 
			
		||||
        public string CustomNotificationCSS { get; set; } = null;
 | 
			
		||||
        
 | 
			
		||||
        // SPECIAL PROPERTIES
 | 
			
		||||
 | 
			
		||||
        public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
 | 
			
		||||
        public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
 | 
			
		||||
        public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
 | 
			
		||||
 | 
			
		||||
        public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
 | 
			
		||||
        
 | 
			
		||||
        public string NotificationSoundPath{
 | 
			
		||||
            get => _notificationSoundPath ?? string.Empty;
 | 
			
		||||
            set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public int NotificationSoundVolume{
 | 
			
		||||
            get => _notificationSoundVolume;
 | 
			
		||||
            set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool MuteNotifications{
 | 
			
		||||
            get => _muteNotifications;
 | 
			
		||||
            set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int ZoomLevel{
 | 
			
		||||
            get => _zoomLevel;
 | 
			
		||||
            set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public TrayIcon.Behavior TrayBehavior{
 | 
			
		||||
            get => _trayBehavior;
 | 
			
		||||
            set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public bool EnableSmoothScrolling{
 | 
			
		||||
            get => _enableSmoothScrolling;
 | 
			
		||||
            set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool EnableTouchAdjustment{
 | 
			
		||||
            get => _enableTouchAdjustment;
 | 
			
		||||
            set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string CustomCefArgs{
 | 
			
		||||
            get => _customCefArgs;
 | 
			
		||||
            set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string SpellCheckLanguage{
 | 
			
		||||
            get => _spellCheckLanguage;
 | 
			
		||||
            set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // EVENTS
 | 
			
		||||
        
 | 
			
		||||
        public event EventHandler MuteToggled;
 | 
			
		||||
        public event EventHandler ZoomLevelChanged;
 | 
			
		||||
        public event EventHandler TrayBehaviorChanged;
 | 
			
		||||
        public event EventHandler SoundNotificationChanged;
 | 
			
		||||
 | 
			
		||||
        // END OF CONFIG
 | 
			
		||||
        
 | 
			
		||||
        public UserConfig(ConfigManager configManager) : base(configManager){}
 | 
			
		||||
 | 
			
		||||
        protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
 | 
			
		||||
            return new UserConfig(configManager);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								Core/Bridge/PropertyBridge.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Core/Bridge/PropertyBridge.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
using System.Text;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Bridge{
 | 
			
		||||
    static class PropertyBridge{
 | 
			
		||||
        public enum Environment{
 | 
			
		||||
            Browser, Notification
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GenerateScript(Environment environment){
 | 
			
		||||
            string Bool(bool value) => value ? "true;" : "false;";
 | 
			
		||||
            string Str(string value) => '"'+value+"\";";
 | 
			
		||||
 | 
			
		||||
            UserConfig config = Program.Config.User;
 | 
			
		||||
            StringBuilder build = new StringBuilder(128).Append("(function(x){");
 | 
			
		||||
 | 
			
		||||
            build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
 | 
			
		||||
            
 | 
			
		||||
            if (environment == Environment.Browser){
 | 
			
		||||
                build.Append("x.openSearchInFirstColumn=").Append(Bool(config.OpenSearchInFirstColumn));
 | 
			
		||||
                build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(config.KeepLikeFollowDialogsOpen));
 | 
			
		||||
                build.Append("x.muteNotifications=").Append(Bool(config.MuteNotifications));
 | 
			
		||||
                build.Append("x.notificationMediaPreviews=").Append(Bool(config.NotificationMediaPreviews));
 | 
			
		||||
                build.Append("x.translationTarget=").Append(Str(config.TranslationTarget));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (environment == Environment.Notification){
 | 
			
		||||
                build.Append("x.skipOnLinkClick=").Append(Bool(config.NotificationSkipOnLinkClick));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								Core/Bridge/TweetDeckBridge.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								Core/Bridge/TweetDeckBridge.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Management;
 | 
			
		||||
using TweetDuck.Core.Notification;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Bridge{
 | 
			
		||||
    class TweetDeckBridge{
 | 
			
		||||
        public static string FontSize { get; private set; }
 | 
			
		||||
        public static string NotificationHeadLayout { get; private set; }
 | 
			
		||||
        public static readonly ContextInfo ContextInfo = new ContextInfo();
 | 
			
		||||
 | 
			
		||||
        public static void ResetStaticProperties(){
 | 
			
		||||
            FontSize = NotificationHeadLayout = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly FormBrowser form;
 | 
			
		||||
        private readonly FormNotificationMain notification;
 | 
			
		||||
 | 
			
		||||
        protected TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
 | 
			
		||||
            this.form = form;
 | 
			
		||||
            this.notification = notification;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Browser only
 | 
			
		||||
 | 
			
		||||
        public sealed class Browser : TweetDeckBridge{
 | 
			
		||||
            public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
 | 
			
		||||
 | 
			
		||||
            public void OpenContextMenu(){
 | 
			
		||||
                form.InvokeAsyncSafe(form.OpenContextMenu);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void OpenProfileImport(){
 | 
			
		||||
                form.InvokeAsyncSafe(form.OpenProfileImport);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
 | 
			
		||||
                form.InvokeAsyncSafe(() => {
 | 
			
		||||
                    form.OnIntroductionClosed(showGuide, allowDataCollection);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void LoadNotificationLayout(string fontSize, string headLayout){
 | 
			
		||||
                form.InvokeAsyncSafe(() => {
 | 
			
		||||
                    FontSize = fontSize;
 | 
			
		||||
                    NotificationHeadLayout = headLayout;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void SetRightClickedLink(string type, string url){
 | 
			
		||||
                ContextInfo.SetLink(type, url);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
 | 
			
		||||
                ContextInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void DisplayTooltip(string text){
 | 
			
		||||
                form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Notification only
 | 
			
		||||
 | 
			
		||||
        public sealed class Notification : TweetDeckBridge{
 | 
			
		||||
            public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
 | 
			
		||||
 | 
			
		||||
            public void DisplayTooltip(string text){
 | 
			
		||||
                notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void LoadNextNotification(){
 | 
			
		||||
                notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void ShowTweetDetail(){
 | 
			
		||||
                notification.InvokeAsyncSafe(notification.ShowTweetDetail);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Global
 | 
			
		||||
 | 
			
		||||
        public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
 | 
			
		||||
            notification.InvokeAsyncSafe(() => {
 | 
			
		||||
                form.OnTweetNotification();
 | 
			
		||||
                notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnTweetSound(){
 | 
			
		||||
            form.InvokeAsyncSafe(() => {
 | 
			
		||||
                form.OnTweetNotification();
 | 
			
		||||
                form.OnTweetSound();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ScreenshotTweet(string html, int width){
 | 
			
		||||
            form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void PlayVideo(string url, string username){
 | 
			
		||||
            form.InvokeAsyncSafe(() => form.PlayVideo(url, username));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void FixClipboard(){
 | 
			
		||||
            form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OpenBrowser(string url){
 | 
			
		||||
            form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int GetIdleSeconds(){
 | 
			
		||||
            return NativeMethods.GetIdleSeconds();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Alert(string type, string contents){
 | 
			
		||||
            MessageBoxIcon icon;
 | 
			
		||||
 | 
			
		||||
            switch(type){
 | 
			
		||||
                case "error": icon = MessageBoxIcon.Error; break;
 | 
			
		||||
                case "warning": icon = MessageBoxIcon.Warning; break;
 | 
			
		||||
                case "info": icon = MessageBoxIcon.Information; break;
 | 
			
		||||
                default: icon = MessageBoxIcon.None; break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void CrashDebug(string message){
 | 
			
		||||
            #if DEBUG
 | 
			
		||||
            System.Diagnostics.Debug.WriteLine(message);
 | 
			
		||||
            System.Diagnostics.Debugger.Break();
 | 
			
		||||
            #endif
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								Core/Bridge/UpdateBridge.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								Core/Bridge/UpdateBridge.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Updates;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Bridge{
 | 
			
		||||
    class UpdateBridge{
 | 
			
		||||
        private readonly UpdateHandler updates;
 | 
			
		||||
        private readonly Control sync;
 | 
			
		||||
 | 
			
		||||
        private UpdateInfo nextUpdate = null;
 | 
			
		||||
 | 
			
		||||
        public event EventHandler<UpdateInfo> UpdateAccepted;
 | 
			
		||||
        public event EventHandler<UpdateInfo> UpdateDelayed;
 | 
			
		||||
        public event EventHandler<UpdateInfo> UpdateDismissed;
 | 
			
		||||
 | 
			
		||||
        public UpdateBridge(UpdateHandler updates, Control sync){
 | 
			
		||||
            this.sync = sync;
 | 
			
		||||
 | 
			
		||||
            this.updates = updates;
 | 
			
		||||
            this.updates.CheckFinished += updates_CheckFinished;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal void Cleanup(){
 | 
			
		||||
            updates.CheckFinished -= updates_CheckFinished;
 | 
			
		||||
            nextUpdate?.DeleteInstaller();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
 | 
			
		||||
            UpdateInfo foundUpdate = e.Result.HasValue ? e.Result.Value : null;
 | 
			
		||||
 | 
			
		||||
            if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)){
 | 
			
		||||
                nextUpdate.DeleteInstaller();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            nextUpdate = foundUpdate;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void HandleInteractionEvent(EventHandler<UpdateInfo> eventHandler){
 | 
			
		||||
            UpdateInfo tmpInfo = nextUpdate;
 | 
			
		||||
 | 
			
		||||
            if (tmpInfo != null){
 | 
			
		||||
                sync.InvokeAsyncSafe(() => eventHandler?.Invoke(this, tmpInfo));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Bridge methods
 | 
			
		||||
 | 
			
		||||
        public void TriggerUpdateCheck(){
 | 
			
		||||
            updates.Check(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnUpdateAccepted(){
 | 
			
		||||
            HandleInteractionEvent(UpdateAccepted);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnUpdateDelayed(){
 | 
			
		||||
            HandleInteractionEvent(UpdateDelayed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnUpdateDismissed(){
 | 
			
		||||
            HandleInteractionEvent(UpdateDismissed);
 | 
			
		||||
 | 
			
		||||
            nextUpdate?.DeleteInstaller();
 | 
			
		||||
            nextUpdate = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										79
									
								
								Core/Controls/ControlExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Core/Controls/ControlExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Controls{
 | 
			
		||||
    static class ControlExtensions{
 | 
			
		||||
        public static readonly Point InvisibleLocation = new Point(-32000, -32000);
 | 
			
		||||
 | 
			
		||||
        public static void InvokeSafe(this Control control, Action func){
 | 
			
		||||
            if (control.InvokeRequired){
 | 
			
		||||
                control.Invoke(func);
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                func();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void InvokeAsyncSafe(this Control control, Action func){
 | 
			
		||||
            control.BeginInvoke(func);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static float GetDPIScale(this Control control){
 | 
			
		||||
            using(Graphics graphics = control.CreateGraphics()){
 | 
			
		||||
                return graphics.DpiY/96F;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool IsFullyOutsideView(this Form form){
 | 
			
		||||
            return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void MoveToCenter(this Form targetForm, Form parentForm){
 | 
			
		||||
            targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void SetValueInstant(this ProgressBar bar, int value){
 | 
			
		||||
            if (value == bar.Maximum){
 | 
			
		||||
                bar.Value = value;
 | 
			
		||||
                bar.Value = value-1;
 | 
			
		||||
                bar.Value = value;
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                bar.Value = value+1;
 | 
			
		||||
                bar.Value = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void SetValueSafe(this NumericUpDown numUpDown, int value){
 | 
			
		||||
            if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){
 | 
			
		||||
                numUpDown.Value = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void SetValueSafe(this TrackBar trackBar, int value){
 | 
			
		||||
            if (value >= trackBar.Minimum && value <= trackBar.Maximum){
 | 
			
		||||
                trackBar.Value = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool AlignValueToTick(this TrackBar trackBar){
 | 
			
		||||
            if (trackBar.Value % trackBar.SmallChange != 0){
 | 
			
		||||
                trackBar.Value = trackBar.SmallChange*(int)Math.Floor(((double)trackBar.Value/trackBar.SmallChange)+0.5);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            else return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void EnableMultilineShortcuts(this TextBox textBox){
 | 
			
		||||
            textBox.KeyDown += (sender, args) => {
 | 
			
		||||
                if (args.Control && args.KeyCode == Keys.A){
 | 
			
		||||
                    ((TextBox)sender).SelectAll();
 | 
			
		||||
                    args.SuppressKeyPress = true;
 | 
			
		||||
                    args.Handled = true;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								Core/Controls/FlatButton.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Core/Controls/FlatButton.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Controls{
 | 
			
		||||
    sealed class FlatButton : Button{
 | 
			
		||||
        protected override bool ShowFocusCues => false;
 | 
			
		||||
 | 
			
		||||
        public FlatButton(){
 | 
			
		||||
            GotFocus += FlatButton_GotFocus;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FlatButton_GotFocus(object sender, EventArgs e){ // removes extra border when focused
 | 
			
		||||
            NotifyDefault(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								Core/Controls/FlatProgressBar.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Core/Controls/FlatProgressBar.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Controls{
 | 
			
		||||
    sealed class FlatProgressBar : ProgressBar{
 | 
			
		||||
        private readonly SolidBrush brush;
 | 
			
		||||
 | 
			
		||||
        public FlatProgressBar(){
 | 
			
		||||
            brush = new SolidBrush(Color.White);
 | 
			
		||||
 | 
			
		||||
            SetStyle(ControlStyles.UserPaint, true);
 | 
			
		||||
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void SetValueInstant(int value){
 | 
			
		||||
            ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void OnPaint(PaintEventArgs e){
 | 
			
		||||
            if (brush.Color != ForeColor){
 | 
			
		||||
                brush.Color = ForeColor;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Rectangle rect = e.ClipRectangle;
 | 
			
		||||
            rect.Width = (int)(rect.Width*((double)Value/Maximum));
 | 
			
		||||
            e.Graphics.FillRectangle(brush, rect);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void Dispose(bool disposing){
 | 
			
		||||
            base.Dispose(disposing);
 | 
			
		||||
 | 
			
		||||
            if (disposing){
 | 
			
		||||
                brush.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								Core/Controls/LabelVertical.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Core/Controls/LabelVertical.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Controls{
 | 
			
		||||
    sealed class LabelVertical : Label{
 | 
			
		||||
        public int LineHeight { get; set; }
 | 
			
		||||
 | 
			
		||||
        protected override void OnPaint(PaintEventArgs e){
 | 
			
		||||
            int y = (int)Math.Floor((ClientRectangle.Height-Text.Length*LineHeight)/2F)-1;
 | 
			
		||||
 | 
			
		||||
            using(Brush brush = new SolidBrush(ForeColor)){
 | 
			
		||||
                foreach(char chr in Text){
 | 
			
		||||
                    string str = chr.ToString();
 | 
			
		||||
                    float x = (ClientRectangle.Width-e.Graphics.MeasureString(str, Font).Width)/2F;
 | 
			
		||||
 | 
			
		||||
                    e.Graphics.DrawString(str, Font, brush, x, y);
 | 
			
		||||
                    y += LineHeight;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								Core/Controls/NumericUpDownEx.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Core/Controls/NumericUpDownEx.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Controls{
 | 
			
		||||
    sealed class NumericUpDownEx : NumericUpDown{
 | 
			
		||||
        public string TextSuffix { get; set ; }
 | 
			
		||||
 | 
			
		||||
        protected override void UpdateEditText(){
 | 
			
		||||
            base.UpdateEditText();
 | 
			
		||||
 | 
			
		||||
            if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){
 | 
			
		||||
                ChangingText = true;
 | 
			
		||||
                Text += TextSuffix;
 | 
			
		||||
                ChangingText = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,21 @@
 | 
			
		||||
namespace TweetDuck.Browser {
 | 
			
		||||
namespace TweetDuck.Core {
 | 
			
		||||
    sealed partial class FormBrowser {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private System.ComponentModel.IContainer components = null;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clean up any resources being used.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
 | 
			
		||||
        protected override void Dispose(bool disposing) {
 | 
			
		||||
            if (disposing && (components != null)) {
 | 
			
		||||
                components.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            base.Dispose(disposing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region Windows Form Designer generated code
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -13,7 +24,7 @@
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private void InitializeComponent() {
 | 
			
		||||
            this.components = new System.ComponentModel.Container();
 | 
			
		||||
            this.trayIcon = new TrayIcon(this.components);
 | 
			
		||||
            this.trayIcon = new TweetDuck.Core.Other.TrayIcon(this.components);
 | 
			
		||||
            this.toolTip = new System.Windows.Forms.ToolTip(this.components);
 | 
			
		||||
            this.timerResize = new System.Windows.Forms.Timer(this.components);
 | 
			
		||||
            this.SuspendLayout();
 | 
			
		||||
@@ -27,11 +38,10 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
 | 
			
		||||
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 | 
			
		||||
            this.BackColor = TweetLib.Core.Features.TweetDeck.TweetDeckBrowser.BackgroundColor;
 | 
			
		||||
            this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
 | 
			
		||||
            this.ClientSize = new System.Drawing.Size(1008, 730);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.Icon = Properties.Resources.icon;
 | 
			
		||||
            this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
 | 
			
		||||
            this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
 | 
			
		||||
            this.MinimumSize = new System.Drawing.Size(348, 424);
 | 
			
		||||
            this.Name = "FormBrowser";
 | 
			
		||||
            this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
 | 
			
		||||
@@ -47,7 +57,7 @@
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        private TrayIcon trayIcon;
 | 
			
		||||
        private TweetDuck.Core.Other.TrayIcon trayIcon;
 | 
			
		||||
        private System.Windows.Forms.ToolTip toolTip;
 | 
			
		||||
        private System.Windows.Forms.Timer timerResize;
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										575
									
								
								Core/FormBrowser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										575
									
								
								Core/FormBrowser.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,575 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using TweetDuck.Core.Bridge;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Handling;
 | 
			
		||||
using TweetDuck.Core.Handling.General;
 | 
			
		||||
using TweetDuck.Core.Management;
 | 
			
		||||
using TweetDuck.Core.Notification;
 | 
			
		||||
using TweetDuck.Core.Notification.Screenshot;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Other.Analytics;
 | 
			
		||||
using TweetDuck.Core.Other.Settings.Dialogs;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Plugins.Events;
 | 
			
		||||
using TweetDuck.Resources;
 | 
			
		||||
using TweetDuck.Updates;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core{
 | 
			
		||||
    sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
 | 
			
		||||
        private static UserConfig Config => Program.Config.User;
 | 
			
		||||
 | 
			
		||||
        public bool IsWaiting{
 | 
			
		||||
            set{
 | 
			
		||||
                if (value){
 | 
			
		||||
                    browser.Enabled = false;
 | 
			
		||||
                    Cursor = Cursors.WaitCursor;
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    browser.Enabled = true;
 | 
			
		||||
                    Cursor = Cursors.Default;
 | 
			
		||||
 | 
			
		||||
                    if (Focused){ // re-focus browser only if the window or a child is activated
 | 
			
		||||
                        browser.Focus();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string UpdateInstallerPath { get; private set; }
 | 
			
		||||
        private bool ignoreUpdateCheckError;
 | 
			
		||||
        
 | 
			
		||||
        public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
 | 
			
		||||
 | 
			
		||||
        private readonly TweetDeckBrowser browser;
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
        private readonly UpdateHandler updates;
 | 
			
		||||
        private readonly FormNotificationTweet notification;
 | 
			
		||||
        private readonly ContextMenu contextMenu;
 | 
			
		||||
        private readonly UpdateBridge updateBridge;
 | 
			
		||||
 | 
			
		||||
        private bool isLoaded;
 | 
			
		||||
        private FormWindowState prevState;
 | 
			
		||||
        
 | 
			
		||||
        private TweetScreenshotManager notificationScreenshotManager;
 | 
			
		||||
        private VideoPlayer videoPlayer;
 | 
			
		||||
        private AnalyticsManager analytics;
 | 
			
		||||
 | 
			
		||||
        public FormBrowser(){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            Text = Program.BrandName;
 | 
			
		||||
            
 | 
			
		||||
            this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath);
 | 
			
		||||
            this.plugins.Reloaded += plugins_Reloaded;
 | 
			
		||||
            this.plugins.Executed += plugins_Executed;
 | 
			
		||||
            this.plugins.Reload();
 | 
			
		||||
 | 
			
		||||
            this.notification = new FormNotificationTweet(this, plugins);
 | 
			
		||||
            this.notification.Show();
 | 
			
		||||
            
 | 
			
		||||
            this.updates = new UpdateHandler(Program.InstallerPath);
 | 
			
		||||
            this.updates.CheckFinished += updates_CheckFinished;
 | 
			
		||||
 | 
			
		||||
            this.updateBridge = new UpdateBridge(updates, this);
 | 
			
		||||
            this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
 | 
			
		||||
            this.updateBridge.UpdateDelayed += updateBridge_UpdateDelayed;
 | 
			
		||||
            this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
 | 
			
		||||
 | 
			
		||||
            this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
 | 
			
		||||
            this.contextMenu = ContextMenuBrowser.CreateMenu(this);
 | 
			
		||||
 | 
			
		||||
            Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
 | 
			
		||||
 | 
			
		||||
            Disposed += (sender, args) => {
 | 
			
		||||
                Config.MuteToggled -= Config_MuteToggled;
 | 
			
		||||
                Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
 | 
			
		||||
                
 | 
			
		||||
                browser.Dispose();
 | 
			
		||||
                updates.Dispose();
 | 
			
		||||
                contextMenu.Dispose();
 | 
			
		||||
 | 
			
		||||
                notificationScreenshotManager?.Dispose();
 | 
			
		||||
                videoPlayer?.Dispose();
 | 
			
		||||
                analytics?.Dispose();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Config.MuteToggled += Config_MuteToggled;
 | 
			
		||||
 | 
			
		||||
            this.trayIcon.ClickRestore += trayIcon_ClickRestore;
 | 
			
		||||
            this.trayIcon.ClickClose += trayIcon_ClickClose;
 | 
			
		||||
            Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
 | 
			
		||||
 | 
			
		||||
            UpdateTray();
 | 
			
		||||
 | 
			
		||||
            if (Config.MuteNotifications){
 | 
			
		||||
                UpdateFormIcon();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (Config.AllowDataCollection){
 | 
			
		||||
                analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RestoreWindow();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ShowChildForm(Form form){
 | 
			
		||||
            form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
 | 
			
		||||
            form.Show(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ForceClose(){
 | 
			
		||||
            trayIcon.Visible = false; // checked in FormClosing event
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // window setup
 | 
			
		||||
 | 
			
		||||
        private void RestoreWindow(){
 | 
			
		||||
            Config.BrowserWindow.Restore(this, true);
 | 
			
		||||
            prevState = WindowState;
 | 
			
		||||
            isLoaded = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void UpdateFormIcon(){ // TODO fix to show icon in taskbar too
 | 
			
		||||
            Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void UpdateTray(){
 | 
			
		||||
            trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // event handlers
 | 
			
		||||
 | 
			
		||||
        private void timerResize_Tick(object sender, EventArgs e){
 | 
			
		||||
            FormBrowser_ResizeEnd(this, e); // also stops timer
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormBrowser_Activated(object sender, EventArgs e){
 | 
			
		||||
            if (!isLoaded)return;
 | 
			
		||||
 | 
			
		||||
            trayIcon.HasNotifications = false;
 | 
			
		||||
 | 
			
		||||
            if (!browser.Enabled){      // when taking a screenshot, the window is unfocused and
 | 
			
		||||
                browser.Enabled = true; // the browser is disabled; if the user clicks back into
 | 
			
		||||
            }                           // the window, enable the browser again
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormBrowser_LocationChanged(object sender, EventArgs e){
 | 
			
		||||
            if (!isLoaded)return;
 | 
			
		||||
            
 | 
			
		||||
            timerResize.Stop();
 | 
			
		||||
            timerResize.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormBrowser_Resize(object sender, EventArgs e){
 | 
			
		||||
            if (!isLoaded)return;
 | 
			
		||||
 | 
			
		||||
            if (WindowState != prevState){
 | 
			
		||||
                prevState = WindowState;
 | 
			
		||||
 | 
			
		||||
                if (WindowState == FormWindowState.Minimized){
 | 
			
		||||
                    if (Config.TrayBehavior.ShouldHideOnMinimize()){
 | 
			
		||||
                        Hide(); // hides taskbar too?! welp that works I guess
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    FormBrowser_ResizeEnd(sender, e);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                timerResize.Stop();
 | 
			
		||||
                timerResize.Start();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
 | 
			
		||||
            if (!isLoaded)return;
 | 
			
		||||
 | 
			
		||||
            timerResize.Stop();
 | 
			
		||||
            
 | 
			
		||||
            if (Location != ControlExtensions.InvisibleLocation){
 | 
			
		||||
                Config.BrowserWindow.Save(this);
 | 
			
		||||
                Config.Save();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){
 | 
			
		||||
            if (!isLoaded)return;
 | 
			
		||||
 | 
			
		||||
            if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){
 | 
			
		||||
                Hide(); // hides taskbar too?! welp that works I guess
 | 
			
		||||
                e.Cancel = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){
 | 
			
		||||
            if (isLoaded && UpdateInstallerPath == null){
 | 
			
		||||
                updateBridge.Cleanup();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Config_MuteToggled(object sender, EventArgs e){
 | 
			
		||||
            UpdateFormIcon();
 | 
			
		||||
            AnalyticsFile.NotificationMutes.Trigger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Config_TrayBehaviorChanged(object sender, EventArgs e){
 | 
			
		||||
            UpdateTray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void trayIcon_ClickRestore(object sender, EventArgs e){
 | 
			
		||||
            Show();
 | 
			
		||||
            RestoreWindow();
 | 
			
		||||
            Activate();
 | 
			
		||||
            UpdateTray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void trayIcon_ClickClose(object sender, EventArgs e){
 | 
			
		||||
            ForceClose();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
 | 
			
		||||
            if (e.HasErrors){
 | 
			
		||||
                FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (isLoaded){
 | 
			
		||||
                browser.ReloadToTweetDeck();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private static void plugins_Executed(object sender, PluginErrorEventArgs e){
 | 
			
		||||
            if (e.HasErrors){
 | 
			
		||||
                FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
 | 
			
		||||
            e.Result.Handle(update => {
 | 
			
		||||
                string tag = update.VersionTag;
 | 
			
		||||
 | 
			
		||||
                if (tag != Program.VersionTag && tag != Config.DismissedUpdate){
 | 
			
		||||
                    update.BeginSilentDownload();
 | 
			
		||||
                    browser.ShowUpdateNotification(tag, update.ReleaseNotes);
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    updates.StartTimer();
 | 
			
		||||
                }
 | 
			
		||||
            }, ex => {
 | 
			
		||||
                if (!ignoreUpdateCheckError){
 | 
			
		||||
                    Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
 | 
			
		||||
                    updates.StartTimer();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            ignoreUpdateCheckError = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void updateBridge_UpdateAccepted(object sender, UpdateInfo update){
 | 
			
		||||
            FormManager.CloseAllDialogs();
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrEmpty(Config.DismissedUpdate)){
 | 
			
		||||
                Config.DismissedUpdate = null;
 | 
			
		||||
                Config.Save();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void OnFinished(){
 | 
			
		||||
                UpdateDownloadStatus status = update.DownloadStatus;
 | 
			
		||||
 | 
			
		||||
                if (status == UpdateDownloadStatus.Done){
 | 
			
		||||
                    UpdateInstallerPath = update.InstallerPath;
 | 
			
		||||
                    ForceClose();
 | 
			
		||||
                }
 | 
			
		||||
                else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: "+(update.DownloadError?.Message ?? "unknown error")+"\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
 | 
			
		||||
                    BrowserUtils.OpenExternalBrowser(Program.Website);
 | 
			
		||||
                    ForceClose();
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    Show();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (update.DownloadStatus.IsFinished(true)){
 | 
			
		||||
                OnFinished();
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                FormUpdateDownload downloadForm = new FormUpdateDownload(update);
 | 
			
		||||
 | 
			
		||||
                downloadForm.VisibleChanged += (sender2, args2) => {
 | 
			
		||||
                    downloadForm.MoveToCenter(this);
 | 
			
		||||
                    Hide();
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                downloadForm.FormClosed += (sender2, args2) => {
 | 
			
		||||
                    if (downloadForm.DialogResult != DialogResult.OK){
 | 
			
		||||
                        update.CancelDownload();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    downloadForm.Dispose();
 | 
			
		||||
                    OnFinished();
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                downloadForm.Show();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void updateBridge_UpdateDelayed(object sender, UpdateInfo update){
 | 
			
		||||
            // stops the timer
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){
 | 
			
		||||
            Config.DismissedUpdate = update.VersionTag;
 | 
			
		||||
            Config.Save();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void WndProc(ref Message m){
 | 
			
		||||
            if (isLoaded && m.Msg == Program.WindowRestoreMessage){
 | 
			
		||||
                if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
 | 
			
		||||
                    trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
 | 
			
		||||
                if (videoPlayer != null && videoPlayer.Running){
 | 
			
		||||
                    videoPlayer.Close();
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    browser.OnMouseClickExtra(m.WParam);
 | 
			
		||||
                    AnalyticsFile.BrowserExtraMouseButtons.Trigger();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            base.WndProc(ref m);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // bridge methods
 | 
			
		||||
 | 
			
		||||
        public void PauseNotification(){
 | 
			
		||||
            notification.PauseNotification();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ResumeNotification(){
 | 
			
		||||
            notification.ResumeNotification();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public void ReinjectCustomCSS(string css){
 | 
			
		||||
            browser.ReinjectCustomCSS(css);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ReloadToTweetDeck(){
 | 
			
		||||
            #if DEBUG
 | 
			
		||||
            ScriptLoader.HotSwap();
 | 
			
		||||
            #else
 | 
			
		||||
            if (ModifierKeys.HasFlag(Keys.Shift)){
 | 
			
		||||
                ScriptLoader.ClearCache();
 | 
			
		||||
            }
 | 
			
		||||
            #endif
 | 
			
		||||
 | 
			
		||||
            ignoreUpdateCheckError = false;
 | 
			
		||||
            browser.ReloadToTweetDeck();
 | 
			
		||||
            AnalyticsFile.BrowserReloads.Trigger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddSearchColumn(string query){
 | 
			
		||||
            browser.AddSearchColumn(query);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void TriggerTweetScreenshot(){
 | 
			
		||||
            browser.TriggerTweetScreenshot();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ReloadColumns(){
 | 
			
		||||
            browser.ReloadColumns();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void PlaySoundNotification(){
 | 
			
		||||
            browser.PlaySoundNotification();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ApplyROT13(){
 | 
			
		||||
            browser.ApplyROT13();
 | 
			
		||||
            AnalyticsFile.UsedROT13.Trigger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OpenDevTools(){
 | 
			
		||||
            browser.OpenDevTools();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // callback handlers
 | 
			
		||||
        
 | 
			
		||||
        public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
 | 
			
		||||
            if (Config.FirstRun){
 | 
			
		||||
                Config.FirstRun = false;
 | 
			
		||||
                Config.AllowDataCollection = allowDataCollection;
 | 
			
		||||
                Config.Save();
 | 
			
		||||
 | 
			
		||||
                if (allowDataCollection && analytics == null){
 | 
			
		||||
                    analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (showGuide){
 | 
			
		||||
                FormGuide.Show();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OpenContextMenu(){
 | 
			
		||||
            contextMenu.Show(this, PointToClient(Cursor.Position));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OpenSettings(){
 | 
			
		||||
            OpenSettings(null);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public void OpenSettings(Type startTab){
 | 
			
		||||
            if (!FormManager.TryBringToFront<FormSettings>()){
 | 
			
		||||
                bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
 | 
			
		||||
 | 
			
		||||
                FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
 | 
			
		||||
                
 | 
			
		||||
                form.FormClosed += (sender, args) => {
 | 
			
		||||
                    if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
 | 
			
		||||
                        Config.DismissedUpdate = null;
 | 
			
		||||
                        Config.Save();
 | 
			
		||||
                        
 | 
			
		||||
                        updates.Check(true);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!Config.EnableTrayHighlight){
 | 
			
		||||
                        trayIcon.HasNotifications = false;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (Config.AllowDataCollection){
 | 
			
		||||
                        if (analytics == null){
 | 
			
		||||
                            analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (analytics != null){
 | 
			
		||||
                        analytics.Dispose();
 | 
			
		||||
                        analytics = null;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    BrowserCache.RefreshTimer();
 | 
			
		||||
                    
 | 
			
		||||
                    if (form.ShouldReloadBrowser){
 | 
			
		||||
                        FormManager.TryFind<FormPlugins>()?.Close();
 | 
			
		||||
                        plugins.Reload(); // also reloads the browser
 | 
			
		||||
                    }
 | 
			
		||||
                    else{
 | 
			
		||||
                        browser.UpdateProperties();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    notification.RequiresResize = true;
 | 
			
		||||
                    form.Dispose();
 | 
			
		||||
                };
 | 
			
		||||
                
 | 
			
		||||
                AnalyticsFile.OpenOptions.Trigger();
 | 
			
		||||
                ShowChildForm(form);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OpenAbout(){
 | 
			
		||||
            if (!FormManager.TryBringToFront<FormAbout>()){
 | 
			
		||||
                AnalyticsFile.OpenAbout.Trigger();
 | 
			
		||||
                ShowChildForm(new FormAbout());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OpenPlugins(){
 | 
			
		||||
            if (!FormManager.TryBringToFront<FormPlugins>()){
 | 
			
		||||
                AnalyticsFile.OpenPlugins.Trigger();
 | 
			
		||||
                ShowChildForm(new FormPlugins(plugins));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OpenProfileImport(){
 | 
			
		||||
            FormManager.TryFind<FormSettings>()?.Close();
 | 
			
		||||
 | 
			
		||||
            using(DialogSettingsManage dialog = new DialogSettingsManage(plugins, true)){
 | 
			
		||||
                if (!dialog.IsDisposed && dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting){ // needs disposal check because the dialog may be closed in constructor
 | 
			
		||||
                    BrowserProcessHandler.UpdatePrefs();
 | 
			
		||||
                    FormManager.TryFind<FormPlugins>()?.Close();
 | 
			
		||||
                    plugins.Reload(); // also reloads the browser
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
 | 
			
		||||
            if (Config.EnableTrayHighlight && !ContainsFocus){
 | 
			
		||||
                trayIcon.HasNotifications = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnTweetSound(){
 | 
			
		||||
            AnalyticsFile.SoundNotifications.Trigger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void PlayVideo(string url, string username){
 | 
			
		||||
            if (string.IsNullOrEmpty(url)){
 | 
			
		||||
                videoPlayer?.Close();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (videoPlayer == null){
 | 
			
		||||
                videoPlayer = new VideoPlayer(this);
 | 
			
		||||
 | 
			
		||||
                videoPlayer.ProcessExited += (sender, args) => {
 | 
			
		||||
                    browser.HideVideoOverlay(true);
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            videoPlayer.Launch(url, username);
 | 
			
		||||
            AnalyticsFile.VideoPlays.Trigger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool ProcessBrowserKey(Keys key){
 | 
			
		||||
            if (videoPlayer != null && videoPlayer.Running){
 | 
			
		||||
                videoPlayer.SendKeyEvent(key);
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
 | 
			
		||||
            Activate();
 | 
			
		||||
 | 
			
		||||
            if (!browser.IsTweetDeckWebsite){
 | 
			
		||||
                FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            notification.FinishCurrentNotification();
 | 
			
		||||
            browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
 | 
			
		||||
            AnalyticsFile.TweetDetails.Trigger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnTweetScreenshotReady(string html, int width){
 | 
			
		||||
            if (notificationScreenshotManager == null){
 | 
			
		||||
                notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            notificationScreenshotManager.Trigger(html, width);
 | 
			
		||||
            AnalyticsFile.TweetScreenshots.Trigger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void DisplayTooltip(string text){
 | 
			
		||||
            if (string.IsNullOrEmpty(text)){
 | 
			
		||||
                toolTip.Hide(this);
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                Point position = PointToClient(Cursor.Position);
 | 
			
		||||
                position.Offset(20, 10);
 | 
			
		||||
                toolTip.Show(text, this, position);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								Core/FormManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Core/FormManager.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core{
 | 
			
		||||
    static class FormManager{
 | 
			
		||||
        public static T TryFind<T>() where T : Form{
 | 
			
		||||
            return Application.OpenForms.OfType<T>().FirstOrDefault();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool TryBringToFront<T>() where T : Form{
 | 
			
		||||
            T form = TryFind<T>();
 | 
			
		||||
 | 
			
		||||
            if (form != null){
 | 
			
		||||
                form.BringToFront();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            else return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool HasAnyDialogs => Application.OpenForms.OfType<IAppDialog>().Any();
 | 
			
		||||
        
 | 
			
		||||
        public static void CloseAllDialogs(){
 | 
			
		||||
            foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
 | 
			
		||||
                ((Form)dialog).Close();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public interface IAppDialog{}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										229
									
								
								Core/Handling/ContextMenuBase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								Core/Handling/ContextMenuBase.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,229 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using TweetDuck.Core.Bridge;
 | 
			
		||||
using TweetDuck.Core.Management;
 | 
			
		||||
using TweetDuck.Core.Notification;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Other.Analytics;
 | 
			
		||||
using TweetDuck.Resources;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    abstract class ContextMenuBase : IContextMenuHandler{
 | 
			
		||||
        protected static UserConfig Config => Program.Config.User;
 | 
			
		||||
 | 
			
		||||
        private static TwitterUtils.ImageQuality ImageQuality => Config.TwitterImageQuality;
 | 
			
		||||
        
 | 
			
		||||
        private const CefMenuCommand MenuOpenLinkUrl     = (CefMenuCommand)26500;
 | 
			
		||||
        private const CefMenuCommand MenuCopyLinkUrl     = (CefMenuCommand)26501;
 | 
			
		||||
        private const CefMenuCommand MenuCopyUsername    = (CefMenuCommand)26502;
 | 
			
		||||
        private const CefMenuCommand MenuViewImage       = (CefMenuCommand)26503;
 | 
			
		||||
        private const CefMenuCommand MenuOpenMediaUrl    = (CefMenuCommand)26504;
 | 
			
		||||
        private const CefMenuCommand MenuCopyMediaUrl    = (CefMenuCommand)26505;
 | 
			
		||||
        private const CefMenuCommand MenuSaveMedia       = (CefMenuCommand)26506;
 | 
			
		||||
        private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
 | 
			
		||||
        private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26508;
 | 
			
		||||
        private const CefMenuCommand MenuReadApplyROT13  = (CefMenuCommand)26509;
 | 
			
		||||
        private const CefMenuCommand MenuOpenDevTools    = (CefMenuCommand)26599;
 | 
			
		||||
        
 | 
			
		||||
        protected ContextInfo.ContextData Context { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private readonly AnalyticsFile.IProvider analytics;
 | 
			
		||||
 | 
			
		||||
        protected ContextMenuBase(AnalyticsFile.IProvider analytics){
 | 
			
		||||
            this.analytics = analytics;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
 | 
			
		||||
            if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
 | 
			
		||||
                Context = TweetDeckBridge.ContextInfo.Reset();
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                Context = TweetDeckBridge.ContextInfo.Create(parameters);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
 | 
			
		||||
                model.AddItem(MenuSearchInBrowser, "Search in browser");
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
                model.AddItem(MenuReadApplyROT13, "Apply ROT13");
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            string TextOpen(string name) => "Open "+name+" in browser";
 | 
			
		||||
            string TextCopy(string name) => "Copy "+name+" address";
 | 
			
		||||
            string TextSave(string name) => "Save "+name+" as...";
 | 
			
		||||
            
 | 
			
		||||
            if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
 | 
			
		||||
                if (TwitterUtils.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){
 | 
			
		||||
                    model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
 | 
			
		||||
                    model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
 | 
			
		||||
                    model.AddItem(MenuCopyUsername, "Copy account username");
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
 | 
			
		||||
                    model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (Context.Types.HasFlag(ContextInfo.ContextType.Video)){
 | 
			
		||||
                model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
 | 
			
		||||
                model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
 | 
			
		||||
                model.AddItem(MenuSaveMedia, TextSave("video"));
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
            }
 | 
			
		||||
            else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != TweetNotification.AppLogo.Url){
 | 
			
		||||
                model.AddItem(MenuViewImage, "View image in photo viewer");
 | 
			
		||||
                model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
 | 
			
		||||
                model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
 | 
			
		||||
                model.AddItem(MenuSaveMedia, TextSave("image"));
 | 
			
		||||
 | 
			
		||||
                if (Context.Chirp.Images.Length > 1){
 | 
			
		||||
                    model.AddItem(MenuSaveTweetImages, TextSave("all images"));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
 | 
			
		||||
            Control control = browserControl.AsControl();
 | 
			
		||||
 | 
			
		||||
            switch(commandId){
 | 
			
		||||
                case MenuOpenLinkUrl:
 | 
			
		||||
                    OpenBrowser(control, Context.LinkUrl);
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case MenuCopyLinkUrl:
 | 
			
		||||
                    SetClipboardText(control, Context.UnsafeLinkUrl);
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case MenuCopyUsername: {
 | 
			
		||||
                    string url = Context.UnsafeLinkUrl;
 | 
			
		||||
                    Match match = TwitterUtils.RegexAccount.Match(url);
 | 
			
		||||
 | 
			
		||||
                    SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
 | 
			
		||||
                    control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                case MenuOpenMediaUrl:
 | 
			
		||||
                    OpenBrowser(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case MenuCopyMediaUrl:
 | 
			
		||||
                    SetClipboardText(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case MenuViewImage: {
 | 
			
		||||
                    string url = Context.MediaUrl;
 | 
			
		||||
 | 
			
		||||
                    control.InvokeAsyncSafe(() => {
 | 
			
		||||
                        TwitterUtils.ViewImage(url, ImageQuality);
 | 
			
		||||
                        analytics.AnalyticsFile.ViewedImages.Trigger();
 | 
			
		||||
                    });
 | 
			
		||||
                    
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                case MenuSaveMedia: {
 | 
			
		||||
                    bool isVideo = Context.Types.HasFlag(ContextInfo.ContextType.Video);
 | 
			
		||||
                    string url = Context.MediaUrl;
 | 
			
		||||
                    string username = Context.Chirp.Authors.LastOrDefault();
 | 
			
		||||
                    
 | 
			
		||||
                    control.InvokeAsyncSafe(() => {
 | 
			
		||||
                        if (isVideo){
 | 
			
		||||
                            TwitterUtils.DownloadVideo(url, username);
 | 
			
		||||
                            analytics.AnalyticsFile.DownloadedVideos.Trigger();
 | 
			
		||||
                        }
 | 
			
		||||
                        else{
 | 
			
		||||
                            TwitterUtils.DownloadImage(url, username, ImageQuality);
 | 
			
		||||
                            analytics.AnalyticsFile.DownloadedImages.Trigger();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                case MenuSaveTweetImages: {
 | 
			
		||||
                    string[] urls = Context.Chirp.Images;
 | 
			
		||||
                    string username = Context.Chirp.Authors.LastOrDefault();
 | 
			
		||||
                    
 | 
			
		||||
                    control.InvokeAsyncSafe(() => {
 | 
			
		||||
                        TwitterUtils.DownloadImages(urls, username, ImageQuality);
 | 
			
		||||
                        analytics.AnalyticsFile.DownloadedImages.Trigger();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                case MenuReadApplyROT13:
 | 
			
		||||
                    string selection = parameters.SelectionText;
 | 
			
		||||
                    control.InvokeAsyncSafe(() => FormMessage.Information("ROT13", StringUtils.ConvertRot13(selection), FormMessage.OK));
 | 
			
		||||
                    control.InvokeAsyncSafe(analytics.AnalyticsFile.UsedROT13.Trigger);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuSearchInBrowser:
 | 
			
		||||
                    string query = parameters.SelectionText;
 | 
			
		||||
                    control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
 | 
			
		||||
                    DeselectAll(frame);
 | 
			
		||||
                    break;
 | 
			
		||||
                    
 | 
			
		||||
                case MenuOpenDevTools:
 | 
			
		||||
                    browserControl.ShowDevTools();
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
 | 
			
		||||
            Context = TweetDeckBridge.ContextInfo.Reset();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected static void DeselectAll(IFrame frame){
 | 
			
		||||
            ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected static void OpenBrowser(Control control, string url){
 | 
			
		||||
            control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected static void SetClipboardText(Control control, string text){
 | 
			
		||||
            control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
 | 
			
		||||
            model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser)+1, insertCommand, insertLabel);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        protected static void AddDebugMenuItems(IMenuModel model){
 | 
			
		||||
            if (BrowserUtils.HasDevTools){
 | 
			
		||||
                AddSeparator(model);
 | 
			
		||||
                model.AddItem(MenuOpenDevTools, "Open dev tools");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected static void RemoveSeparatorIfLast(IMenuModel model){
 | 
			
		||||
            if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
 | 
			
		||||
                model.RemoveAt(model.Count-1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected static void AddSeparator(IMenuModel model){
 | 
			
		||||
            if (model.Count > 0 && model.GetTypeAt(model.Count-1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										179
									
								
								Core/Handling/ContextMenuBrowser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								Core/Handling/ContextMenuBrowser.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Management;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    sealed class ContextMenuBrowser : ContextMenuBase{
 | 
			
		||||
        private const CefMenuCommand MenuGlobal   = (CefMenuCommand)26600;
 | 
			
		||||
        private const CefMenuCommand MenuMute     = (CefMenuCommand)26601;
 | 
			
		||||
        private const CefMenuCommand MenuSettings = (CefMenuCommand)26602;
 | 
			
		||||
        private const CefMenuCommand MenuPlugins  = (CefMenuCommand)26003;
 | 
			
		||||
        private const CefMenuCommand MenuAbout    = (CefMenuCommand)26604;
 | 
			
		||||
        
 | 
			
		||||
        private const CefMenuCommand MenuOpenTweetUrl       = (CefMenuCommand)26610;
 | 
			
		||||
        private const CefMenuCommand MenuCopyTweetUrl       = (CefMenuCommand)26611;
 | 
			
		||||
        private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
 | 
			
		||||
        private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
 | 
			
		||||
        private const CefMenuCommand MenuScreenshotTweet    = (CefMenuCommand)26614;
 | 
			
		||||
        private const CefMenuCommand MenuWriteApplyROT13    = (CefMenuCommand)26615;
 | 
			
		||||
        private const CefMenuCommand MenuSearchInColumn     = (CefMenuCommand)26616;
 | 
			
		||||
 | 
			
		||||
        private const string TitleReloadBrowser = "Reload browser";
 | 
			
		||||
        private const string TitleMuteNotifications = "Mute notifications";
 | 
			
		||||
        private const string TitleSettings = "Options";
 | 
			
		||||
        private const string TitlePlugins = "Plugins";
 | 
			
		||||
        private const string TitleAboutProgram = "About "+Program.BrandName;
 | 
			
		||||
 | 
			
		||||
        private readonly FormBrowser form;
 | 
			
		||||
        
 | 
			
		||||
        public ContextMenuBrowser(FormBrowser form) : base(form){
 | 
			
		||||
            this.form = form;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
 | 
			
		||||
            bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
 | 
			
		||||
            bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
 | 
			
		||||
 | 
			
		||||
            model.Remove(CefMenuCommand.Back);
 | 
			
		||||
            model.Remove(CefMenuCommand.Forward);
 | 
			
		||||
            model.Remove(CefMenuCommand.Print);
 | 
			
		||||
            model.Remove(CefMenuCommand.ViewSource);
 | 
			
		||||
            RemoveSeparatorIfLast(model);
 | 
			
		||||
 | 
			
		||||
            if (isSelecting){
 | 
			
		||||
                if (isEditing){
 | 
			
		||||
                    model.AddSeparator();
 | 
			
		||||
                    model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
 | 
			
		||||
 | 
			
		||||
            if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
 | 
			
		||||
                InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (Context.Types.HasFlag(ContextInfo.ContextType.Chirp) && !isSelecting && !isEditing){
 | 
			
		||||
                model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
 | 
			
		||||
                model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
 | 
			
		||||
                model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrEmpty(Context.Chirp.QuoteUrl)){
 | 
			
		||||
                    model.AddSeparator();
 | 
			
		||||
                    model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
 | 
			
		||||
                    model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!isSelecting && !isEditing){
 | 
			
		||||
                AddSeparator(model);
 | 
			
		||||
 | 
			
		||||
                IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
 | 
			
		||||
            
 | 
			
		||||
                globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
 | 
			
		||||
                globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
 | 
			
		||||
                globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
 | 
			
		||||
                globalMenu.AddSeparator();
 | 
			
		||||
 | 
			
		||||
                globalMenu.AddItem(MenuSettings, TitleSettings);
 | 
			
		||||
                globalMenu.AddItem(MenuPlugins, TitlePlugins);
 | 
			
		||||
                globalMenu.AddItem(MenuAbout, TitleAboutProgram);
 | 
			
		||||
                
 | 
			
		||||
                AddDebugMenuItems(globalMenu);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RemoveSeparatorIfLast(model);
 | 
			
		||||
            
 | 
			
		||||
            form.InvokeAsyncSafe(form.AnalyticsFile.BrowserContextMenus.Trigger);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
 | 
			
		||||
            if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            switch(commandId){
 | 
			
		||||
                case CefMenuCommand.Reload:
 | 
			
		||||
                    form.InvokeAsyncSafe(form.ReloadToTweetDeck);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuSettings:
 | 
			
		||||
                    form.InvokeAsyncSafe(form.OpenSettings);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuAbout:
 | 
			
		||||
                    form.InvokeAsyncSafe(form.OpenAbout);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuPlugins:
 | 
			
		||||
                    form.InvokeAsyncSafe(form.OpenPlugins);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuMute:
 | 
			
		||||
                    form.InvokeAsyncSafe(ToggleMuteNotifications);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuOpenTweetUrl:
 | 
			
		||||
                    OpenBrowser(form, Context.Chirp.TweetUrl);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuCopyTweetUrl:
 | 
			
		||||
                    SetClipboardText(form, Context.Chirp.TweetUrl);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuScreenshotTweet:
 | 
			
		||||
                    form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuOpenQuotedTweetUrl:
 | 
			
		||||
                    OpenBrowser(form, Context.Chirp.QuoteUrl);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuCopyQuotedTweetUrl:
 | 
			
		||||
                    SetClipboardText(form, Context.Chirp.QuoteUrl);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuWriteApplyROT13:
 | 
			
		||||
                    form.InvokeAsyncSafe(form.ApplyROT13);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuSearchInColumn:
 | 
			
		||||
                    string query = parameters.SelectionText;
 | 
			
		||||
                    form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
 | 
			
		||||
                    DeselectAll(frame);
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static ContextMenu CreateMenu(FormBrowser form){
 | 
			
		||||
            ContextMenu menu = new ContextMenu();
 | 
			
		||||
 | 
			
		||||
            menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
 | 
			
		||||
            menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications());
 | 
			
		||||
            menu.MenuItems.Add("-");
 | 
			
		||||
            menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings());
 | 
			
		||||
            menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins());
 | 
			
		||||
            menu.MenuItems.Add(TitleAboutProgram,  (sender, args) => form.OpenAbout());
 | 
			
		||||
 | 
			
		||||
            menu.Popup += (sender, args) => {
 | 
			
		||||
                menu.MenuItems[1].Checked = Config.MuteNotifications;
 | 
			
		||||
                form.AnalyticsFile.BrowserContextMenus.Trigger();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            return menu;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void ToggleMuteNotifications(){
 | 
			
		||||
            Config.MuteNotifications = !Config.MuteNotifications;
 | 
			
		||||
            Config.Save();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								Core/Handling/ContextMenuGuide.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Core/Handling/ContextMenuGuide.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Other.Analytics;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    sealed class ContextMenuGuide : ContextMenuBase{
 | 
			
		||||
        public ContextMenuGuide(AnalyticsFile.IProvider analytics) : base(analytics){}
 | 
			
		||||
 | 
			
		||||
        public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
 | 
			
		||||
            model.Clear();
 | 
			
		||||
            base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
 | 
			
		||||
            AddDebugMenuItems(model);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								Core/Handling/ContextMenuNotification.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								Core/Handling/ContextMenuNotification.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Notification;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    sealed class ContextMenuNotification : ContextMenuBase{
 | 
			
		||||
        private const CefMenuCommand MenuViewDetail         = (CefMenuCommand)26600;
 | 
			
		||||
        private const CefMenuCommand MenuSkipTweet          = (CefMenuCommand)26601;
 | 
			
		||||
        private const CefMenuCommand MenuFreeze             = (CefMenuCommand)26602;
 | 
			
		||||
        private const CefMenuCommand MenuCopyTweetUrl       = (CefMenuCommand)26603;
 | 
			
		||||
        private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604;
 | 
			
		||||
 | 
			
		||||
        private readonly FormNotificationBase form;
 | 
			
		||||
        private readonly bool enableCustomMenu;
 | 
			
		||||
 | 
			
		||||
        public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
 | 
			
		||||
            this.form = form;
 | 
			
		||||
            this.enableCustomMenu = enableCustomMenu;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
 | 
			
		||||
            model.Clear();
 | 
			
		||||
 | 
			
		||||
            if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
 | 
			
		||||
                model.AddItem(CefMenuCommand.Copy, "Copy");
 | 
			
		||||
                model.AddSeparator();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
 | 
			
		||||
 | 
			
		||||
            if (enableCustomMenu){
 | 
			
		||||
                if (form.CanViewDetail){
 | 
			
		||||
                    model.AddItem(MenuViewDetail, "View detail");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                model.AddItem(MenuSkipTweet, "Skip tweet");
 | 
			
		||||
                model.AddCheckItem(MenuFreeze, "Freeze");
 | 
			
		||||
                model.SetChecked(MenuFreeze, form.FreezeTimer);
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
 | 
			
		||||
                    model.AddSeparator();
 | 
			
		||||
                    model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
 | 
			
		||||
 | 
			
		||||
                    if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
 | 
			
		||||
                        model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            AddDebugMenuItems(model);
 | 
			
		||||
            RemoveSeparatorIfLast(model);
 | 
			
		||||
 | 
			
		||||
            form.InvokeAsyncSafe(() => {
 | 
			
		||||
                form.ContextMenuOpen = true;
 | 
			
		||||
                form.AnalyticsFile.NotificationContextMenus.Trigger();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
 | 
			
		||||
            if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            switch(commandId){
 | 
			
		||||
                case MenuSkipTweet:
 | 
			
		||||
                    form.InvokeAsyncSafe(form.FinishCurrentNotification);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuFreeze:
 | 
			
		||||
                    form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuViewDetail:
 | 
			
		||||
                    form.InvokeSafe(form.ShowTweetDetail);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuCopyTweetUrl:
 | 
			
		||||
                    SetClipboardText(form, form.CurrentTweetUrl);
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case MenuCopyQuotedTweetUrl:
 | 
			
		||||
                    SetClipboardText(form, form.CurrentQuoteUrl);
 | 
			
		||||
                    return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
 | 
			
		||||
            base.OnContextMenuDismissed(browserControl, browser, frame);
 | 
			
		||||
            form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								Core/Handling/DragHandlerBrowser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Core/Handling/DragHandlerBrowser.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using CefSharp.Enums;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    sealed class DragHandlerBrowser : IDragHandler{
 | 
			
		||||
        private readonly RequestHandlerBrowser requestHandler;
 | 
			
		||||
 | 
			
		||||
        public DragHandlerBrowser(RequestHandlerBrowser requestHandler){
 | 
			
		||||
            this.requestHandler = requestHandler;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){
 | 
			
		||||
            void TriggerDragStart(string type, string data = null){
 | 
			
		||||
                browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
 | 
			
		||||
 | 
			
		||||
            if (dragData.IsLink){
 | 
			
		||||
                TriggerDragStart("link", dragData.LinkUrl);
 | 
			
		||||
            }
 | 
			
		||||
            else if (dragData.IsFragment){
 | 
			
		||||
                TriggerDragStart("text", dragData.FragmentText.Trim());
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                TriggerDragStart("unknown");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IList<DraggableRegion> regions){}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								Core/Handling/Filters/ResponseFilterBase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								Core/Handling/Filters/ResponseFilterBase.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling.Filters{
 | 
			
		||||
    abstract class ResponseFilterBase : IResponseFilter{
 | 
			
		||||
        private enum State{
 | 
			
		||||
            Reading, Writing, Done
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly Encoding encoding;
 | 
			
		||||
        private byte[] responseData;
 | 
			
		||||
 | 
			
		||||
        private State state;
 | 
			
		||||
        private int offset;
 | 
			
		||||
 | 
			
		||||
        protected ResponseFilterBase(int totalBytes, Encoding encoding){
 | 
			
		||||
            this.responseData = new byte[totalBytes];
 | 
			
		||||
            this.encoding = encoding;
 | 
			
		||||
            this.state = State.Reading;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten){
 | 
			
		||||
            int responseLength = responseData.Length;
 | 
			
		||||
 | 
			
		||||
            if (state == State.Reading){
 | 
			
		||||
                int bytesToRead = Math.Min(responseLength-offset, (int)Math.Min(dataIn?.Length ?? 0, int.MaxValue));
 | 
			
		||||
                
 | 
			
		||||
                dataIn?.Read(responseData, offset, bytesToRead);
 | 
			
		||||
                offset += bytesToRead;
 | 
			
		||||
                
 | 
			
		||||
                dataInRead = bytesToRead;
 | 
			
		||||
                dataOutWritten = 0;
 | 
			
		||||
                
 | 
			
		||||
                if (offset >= responseLength){
 | 
			
		||||
                    responseData = encoding.GetBytes(ProcessResponse(encoding.GetString(responseData)));
 | 
			
		||||
                    state = State.Writing;
 | 
			
		||||
                    offset = 0;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return FilterStatus.NeedMoreData;
 | 
			
		||||
            }
 | 
			
		||||
            else if (state == State.Writing){
 | 
			
		||||
                int bytesToWrite = Math.Min(responseLength-offset, (int)Math.Min(dataOut.Length, int.MaxValue));
 | 
			
		||||
 | 
			
		||||
                if (bytesToWrite > 0){
 | 
			
		||||
                    dataOut.Write(responseData, offset, bytesToWrite);
 | 
			
		||||
                    offset += bytesToWrite;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                dataOutWritten = bytesToWrite;
 | 
			
		||||
                dataInRead = 0;
 | 
			
		||||
 | 
			
		||||
                if (offset < responseLength){
 | 
			
		||||
                    return FilterStatus.NeedMoreData;
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    state = State.Done;
 | 
			
		||||
                    return FilterStatus.Done;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                throw new InvalidOperationException("This resource filter cannot be reused.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public abstract bool InitFilter();
 | 
			
		||||
        protected abstract string ProcessResponse(string text);
 | 
			
		||||
        public abstract void Dispose();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								Core/Handling/Filters/ResponseFilterVendor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Core/Handling/Filters/ResponseFilterVendor.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling.Filters{
 | 
			
		||||
    sealed class ResponseFilterVendor : ResponseFilterBase{
 | 
			
		||||
        private static readonly Regex RegexRestoreJQuery = new Regex(@"(\w+)\.fn=\1\.prototype", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
        public ResponseFilterVendor(int totalBytes) : base(totalBytes, Encoding.UTF8){}
 | 
			
		||||
 | 
			
		||||
        public override bool InitFilter(){
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        protected override string ProcessResponse(string text){
 | 
			
		||||
            return RegexRestoreJQuery.Replace(text, "window.$$=$1;$&", 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void Dispose(){}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								Core/Handling/General/BrowserProcessHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Core/Handling/General/BrowserProcessHandler.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling.General{
 | 
			
		||||
    sealed class BrowserProcessHandler : IBrowserProcessHandler{
 | 
			
		||||
        public static Task UpdatePrefs(){
 | 
			
		||||
            return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void UpdatePrefsInternal(){
 | 
			
		||||
            UserConfig config = Program.Config.User;
 | 
			
		||||
 | 
			
		||||
            using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
 | 
			
		||||
                ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _);
 | 
			
		||||
                ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _);
 | 
			
		||||
                ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void IBrowserProcessHandler.OnContextInitialized(){
 | 
			
		||||
            UpdatePrefsInternal();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
 | 
			
		||||
        void IDisposable.Dispose(){}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								Core/Handling/General/FileDialogHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Core/Handling/General/FileDialogHandler.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling.General{
 | 
			
		||||
    sealed class FileDialogHandler : IDialogHandler{
 | 
			
		||||
        public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
 | 
			
		||||
            if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple){
 | 
			
		||||
                string allFilters = string.Join(";", acceptFilters.Select(filter => "*"+filter));
 | 
			
		||||
 | 
			
		||||
                using(OpenFileDialog dialog = new OpenFileDialog{
 | 
			
		||||
                    AutoUpgradeEnabled = true,
 | 
			
		||||
                    DereferenceLinks = true,
 | 
			
		||||
                    Multiselect = mode == CefFileDialogMode.OpenMultiple,
 | 
			
		||||
                    Title = "Open Files",
 | 
			
		||||
                    Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
 | 
			
		||||
                }){
 | 
			
		||||
                    if (dialog.ShowDialog() == DialogResult.OK){
 | 
			
		||||
                        string ext = Path.GetExtension(dialog.FileName);
 | 
			
		||||
                        callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList());
 | 
			
		||||
                    }
 | 
			
		||||
                    else{
 | 
			
		||||
                        callback.Cancel();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    callback.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                callback.Dispose();
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								Core/Handling/General/JavaScriptDialogHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								Core/Handling/General/JavaScriptDialogHandler.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling.General{
 | 
			
		||||
    sealed class JavaScriptDialogHandler : IJsDialogHandler{
 | 
			
		||||
        private static FormMessage CreateMessageForm(string caption, string text){
 | 
			
		||||
            MessageBoxIcon icon = MessageBoxIcon.None;
 | 
			
		||||
            int pipe = text.IndexOf('|');
 | 
			
		||||
 | 
			
		||||
            if (pipe != -1){
 | 
			
		||||
                switch(text.Substring(0, pipe)){
 | 
			
		||||
                    case "error": icon = MessageBoxIcon.Error; break;
 | 
			
		||||
                    case "warning": icon = MessageBoxIcon.Warning; break;
 | 
			
		||||
                    case "info": icon = MessageBoxIcon.Information; break;
 | 
			
		||||
                    case "question": icon = MessageBoxIcon.Question; break;
 | 
			
		||||
                    default: return new FormMessage(caption, text, icon);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                text = text.Substring(pipe+1);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new FormMessage(caption, text, icon);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
 | 
			
		||||
            browserControl.AsControl().InvokeSafe(() => {
 | 
			
		||||
                FormMessage form;
 | 
			
		||||
                TextBox input = null;
 | 
			
		||||
 | 
			
		||||
                if (dialogType == CefJsDialogType.Alert){
 | 
			
		||||
                    form = CreateMessageForm("Browser Message", messageText);
 | 
			
		||||
                    form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
 | 
			
		||||
                }
 | 
			
		||||
                else if (dialogType == CefJsDialogType.Confirm){
 | 
			
		||||
                    form = CreateMessageForm("Browser Confirmation", messageText);
 | 
			
		||||
                    form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
 | 
			
		||||
                    form.AddButton(FormMessage.Yes, ControlType.Focused);
 | 
			
		||||
                }
 | 
			
		||||
                else if (dialogType == CefJsDialogType.Prompt){
 | 
			
		||||
                    form = CreateMessageForm("Browser Prompt", messageText);
 | 
			
		||||
                    form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
 | 
			
		||||
                    form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
 | 
			
		||||
 | 
			
		||||
                    float dpiScale = form.GetDPIScale();
 | 
			
		||||
                    int inputPad = form.HasIcon ? 43 : 0;
 | 
			
		||||
 | 
			
		||||
                    input = new TextBox{
 | 
			
		||||
                        Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
 | 
			
		||||
                        Font = SystemFonts.MessageBoxFont,
 | 
			
		||||
                        Location = new Point(BrowserUtils.Scale(22+inputPad, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
 | 
			
		||||
                        Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44+inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    form.Controls.Add(input);
 | 
			
		||||
                    form.ActiveControl = input;
 | 
			
		||||
                    form.Height += input.Size.Height+input.Margin.Vertical;
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    callback.Continue(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                bool success = form.ShowDialog() == DialogResult.OK;
 | 
			
		||||
 | 
			
		||||
                if (input == null){
 | 
			
		||||
                    callback.Continue(success);
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    callback.Continue(success, input.Text);
 | 
			
		||||
                    input.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                form.Dispose();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IJsDialogHandler.OnJSBeforeUnload(IWebBrowser browserControl, IBrowser browser, string message, bool isReload, IJsDialogCallback callback){
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){}
 | 
			
		||||
        void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								Core/Handling/General/LifeSpanHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Core/Handling/General/LifeSpanHandler.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling.General{
 | 
			
		||||
    sealed class LifeSpanHandler : ILifeSpanHandler{
 | 
			
		||||
        public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
 | 
			
		||||
            switch(targetDisposition){
 | 
			
		||||
                case WindowOpenDisposition.NewBackgroundTab:
 | 
			
		||||
                case WindowOpenDisposition.NewForegroundTab:
 | 
			
		||||
                case WindowOpenDisposition.NewPopup:
 | 
			
		||||
                case WindowOpenDisposition.NewWindow:
 | 
			
		||||
                    browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){
 | 
			
		||||
            newBrowser = null;
 | 
			
		||||
            return HandleLinkClick(browserControl, targetDisposition, targetUrl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){}
 | 
			
		||||
 | 
			
		||||
        public bool DoClose(IWebBrowser browserControl, IBrowser browser){
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnBeforeClose(IWebBrowser browserControl, IBrowser browser){}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								Core/Handling/KeyboardHandlerBase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Core/Handling/KeyboardHandlerBase.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    class KeyboardHandlerBase : IKeyboardHandler{
 | 
			
		||||
        protected virtual bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
 | 
			
		||||
            if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I){
 | 
			
		||||
                if (BrowserUtils.HasDevTools){
 | 
			
		||||
                    browser.ShowDevTools();
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    browserControl.AsControl().InvokeSafe(() => {
 | 
			
		||||
                        string extraMessage;
 | 
			
		||||
                        
 | 
			
		||||
                        if (Program.IsPortable){
 | 
			
		||||
                            extraMessage = "Please download the portable installer, select the folder with your current installation of TweetDuck Portable, and tick 'Install dev tools' during the installation process.";
 | 
			
		||||
                        }
 | 
			
		||||
                        else{
 | 
			
		||||
                            extraMessage = "Please download the installer, and tick 'Install dev tools' during the installation process. The installer will automatically find and update your current installation of TweetDuck.";
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        FormMessage.Information("Dev Tools", "You do not have dev tools installed. "+extraMessage, FormMessage.OK);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
 | 
			
		||||
            if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("chrome-devtools://")){
 | 
			
		||||
                return HandleRawKey(browserControl, browser, (Keys)windowsKeyCode, modifiers);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								Core/Handling/KeyboardHandlerBrowser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Core/Handling/KeyboardHandlerBrowser.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    sealed class KeyboardHandlerBrowser : KeyboardHandlerBase{
 | 
			
		||||
        private readonly FormBrowser form;
 | 
			
		||||
 | 
			
		||||
        public KeyboardHandlerBrowser(FormBrowser form){
 | 
			
		||||
            this.form = form;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
 | 
			
		||||
            if (base.HandleRawKey(browserControl, browser, key, modifiers)){
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return form.ProcessBrowserKey(key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								Core/Handling/KeyboardHandlerNotification.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Core/Handling/KeyboardHandlerNotification.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Notification;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling {
 | 
			
		||||
    sealed class KeyboardHandlerNotification : KeyboardHandlerBase{
 | 
			
		||||
        private readonly FormNotificationBase notification;
 | 
			
		||||
 | 
			
		||||
        public KeyboardHandlerNotification(FormNotificationBase notification){
 | 
			
		||||
            this.notification = notification;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void TriggerKeyboardShortcutAnalytics(){
 | 
			
		||||
            notification.InvokeAsyncSafe(notification.AnalyticsFile.NotificationKeyboardShortcuts.Trigger);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
 | 
			
		||||
            if (base.HandleRawKey(browserControl, browser, key, modifiers)){
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            switch(key){
 | 
			
		||||
                case Keys.Enter:
 | 
			
		||||
                    notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
 | 
			
		||||
                    TriggerKeyboardShortcutAnalytics();
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case Keys.Escape:
 | 
			
		||||
                    notification.InvokeAsyncSafe(notification.HideNotification);
 | 
			
		||||
                    TriggerKeyboardShortcutAnalytics();
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                case Keys.Space:
 | 
			
		||||
                    notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
 | 
			
		||||
                    TriggerKeyboardShortcutAnalytics();
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								Core/Handling/RequestHandlerBase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								Core/Handling/RequestHandlerBase.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Specialized;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using CefSharp.Handler;
 | 
			
		||||
using TweetDuck.Core.Handling.General;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    class RequestHandlerBase : DefaultRequestHandler{
 | 
			
		||||
        private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$", RegexOptions.Compiled);
 | 
			
		||||
        private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
 | 
			
		||||
 | 
			
		||||
        public static void LoadResourceRewriteRules(string rules){
 | 
			
		||||
            if (string.IsNullOrEmpty(rules)){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            TweetDeckHashes.Clear();
 | 
			
		||||
 | 
			
		||||
            foreach(string rule in rules.Replace(" ", "").ToLower().Split(',')){
 | 
			
		||||
                string[] split = rule.Split('=');
 | 
			
		||||
 | 
			
		||||
                if (split.Length == 2){
 | 
			
		||||
                    string key = split[0];
 | 
			
		||||
                    string hash = split[1];
 | 
			
		||||
 | 
			
		||||
                    if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))){
 | 
			
		||||
                        TweetDeckHashes.Add(key, hash);
 | 
			
		||||
                    }
 | 
			
		||||
                    else{
 | 
			
		||||
                        throw new ArgumentException("Invalid hash characters: "+rule);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    throw new ArgumentException("A rule must have exactly one '=' character: "+rule);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly bool autoReload;
 | 
			
		||||
 | 
			
		||||
        public RequestHandlerBase(bool autoReload){
 | 
			
		||||
            this.autoReload = autoReload;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
 | 
			
		||||
            return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
 | 
			
		||||
            if (BrowserUtils.HasDevTools){
 | 
			
		||||
                NameValueCollection headers = request.Headers;
 | 
			
		||||
                headers.Remove("x-devtools-emulate-network-conditions-client-id");
 | 
			
		||||
                request.Headers = headers;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
 | 
			
		||||
            if ((request.ResourceType == ResourceType.Script || request.ResourceType == ResourceType.Stylesheet) && TweetDeckHashes.Count > 0){
 | 
			
		||||
                string url = request.Url;
 | 
			
		||||
                Match match = TweetDeckResourceUrl.Match(url);
 | 
			
		||||
 | 
			
		||||
                if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)){
 | 
			
		||||
                    if (match.Groups[2].Value == hash){
 | 
			
		||||
                        Program.Reporter.LogVerbose("[RequestHandlerBase] Accepting " + url);
 | 
			
		||||
                    }
 | 
			
		||||
                    else{
 | 
			
		||||
                        Program.Reporter.LogVerbose("[RequestHandlerBase] Replacing " + url + " hash with " + hash);
 | 
			
		||||
                        request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3");
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return base.OnResourceResponse(browserControl, browser, frame, request, response);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
 | 
			
		||||
            if (autoReload){
 | 
			
		||||
                browser.Reload();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								Core/Handling/RequestHandlerBrowser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								Core/Handling/RequestHandlerBrowser.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
using System.Collections.Specialized;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Handling.Filters;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    sealed class RequestHandlerBrowser : RequestHandlerBase{
 | 
			
		||||
        private const string UrlVendorResource = "/dist/vendor";
 | 
			
		||||
        private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
 | 
			
		||||
 | 
			
		||||
        public string BlockNextUserNavUrl { get; set; }
 | 
			
		||||
 | 
			
		||||
        public RequestHandlerBrowser() : base(true){}
 | 
			
		||||
 | 
			
		||||
        public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
 | 
			
		||||
            if (request.ResourceType == ResourceType.MainFrame){
 | 
			
		||||
                if (request.Url.EndsWith("//twitter.com/")){
 | 
			
		||||
                    request.Url = TwitterUtils.TweetDeckURL; // redirect plain twitter.com requests, fixes bugs with login 2FA
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if (request.ResourceType == ResourceType.Script){
 | 
			
		||||
                string url = request.Url;
 | 
			
		||||
 | 
			
		||||
                if (url.Contains("analytics.")){
 | 
			
		||||
                    callback.Dispose();
 | 
			
		||||
                    return CefReturnValue.Cancel;
 | 
			
		||||
                }
 | 
			
		||||
                else if (url.Contains(UrlVendorResource)){
 | 
			
		||||
                    NameValueCollection headers = request.Headers;
 | 
			
		||||
                    headers["Accept-Encoding"] = "identity";
 | 
			
		||||
                    request.Headers = headers;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect){
 | 
			
		||||
            if (userGesture && request.TransitionType == TransitionType.LinkClicked){
 | 
			
		||||
                bool block = request.Url == BlockNextUserNavUrl;
 | 
			
		||||
                BlockNextUserNavUrl = string.Empty;
 | 
			
		||||
                return block;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
 | 
			
		||||
            if (request.ResourceType == ResourceType.Image && request.Url.Contains(UrlLoadingSpinner)){
 | 
			
		||||
                request.Url = TwitterUtils.LoadingSpinner.Url;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return base.OnResourceResponse(browserControl, browser, frame, request, response);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
 | 
			
		||||
            if (request.ResourceType == ResourceType.Script && request.Url.Contains(UrlVendorResource) && int.TryParse(response.ResponseHeaders["Content-Length"], out int totalBytes)){
 | 
			
		||||
                return new ResponseFilterVendor(totalBytes);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								Core/Handling/ResourceHandlerFactory.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Core/Handling/ResourceHandlerFactory.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    sealed class ResourceHandlerFactory : IResourceHandlerFactory{
 | 
			
		||||
        public bool HasHandlers => !handlers.IsEmpty;
 | 
			
		||||
 | 
			
		||||
        private readonly ConcurrentDictionary<string, IResourceHandler> handlers = new ConcurrentDictionary<string, IResourceHandler>(StringComparer.OrdinalIgnoreCase);
 | 
			
		||||
 | 
			
		||||
        public IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request){
 | 
			
		||||
            try{
 | 
			
		||||
                return handlers.TryGetValue(request.Url, out IResourceHandler handler) ? handler : null;
 | 
			
		||||
            }finally{
 | 
			
		||||
                request.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // registration
 | 
			
		||||
 | 
			
		||||
        public bool RegisterHandler(string url, IResourceHandler handler){
 | 
			
		||||
            if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
 | 
			
		||||
                handlers.AddOrUpdate(uri.AbsoluteUri, handler, (key, prev) => handler);
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool RegisterHandler(ResourceLink link){
 | 
			
		||||
            return RegisterHandler(link.Url, link.Handler);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool UnregisterHandler(string url){
 | 
			
		||||
            return handlers.TryRemove(url, out IResourceHandler _);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool UnregisterHandler(ResourceLink link){
 | 
			
		||||
            return UnregisterHandler(link.Url);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								Core/Handling/ResourceHandlerNotification.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								Core/Handling/ResourceHandlerNotification.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using System.Collections.Specialized;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Handling{
 | 
			
		||||
    sealed class ResourceHandlerNotification : IResourceHandler{
 | 
			
		||||
        private readonly NameValueCollection headers = new NameValueCollection(0);
 | 
			
		||||
        private MemoryStream dataIn;
 | 
			
		||||
 | 
			
		||||
        public void SetHTML(string html){
 | 
			
		||||
            dataIn?.Dispose();
 | 
			
		||||
            dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose(){
 | 
			
		||||
            if (dataIn != null){
 | 
			
		||||
                dataIn.Dispose();
 | 
			
		||||
                dataIn = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback){
 | 
			
		||||
            callback.Continue();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl){
 | 
			
		||||
            redirectUrl = null;
 | 
			
		||||
 | 
			
		||||
            response.MimeType = "text/html";
 | 
			
		||||
            response.StatusCode = 200;
 | 
			
		||||
            response.StatusText = "OK";
 | 
			
		||||
            response.ResponseHeaders = headers;
 | 
			
		||||
            responseLength = dataIn?.Length ?? -1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback){
 | 
			
		||||
            callback.Dispose();
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                int length = (int)dataIn.Length;
 | 
			
		||||
 | 
			
		||||
                dataIn.CopyTo(dataOut, length);
 | 
			
		||||
                bytesRead = length;
 | 
			
		||||
                return true;
 | 
			
		||||
            }catch{ // catch IOException, possibly NullReferenceException if dataIn is null
 | 
			
		||||
                bytesRead = 0;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IResourceHandler.CanGetCookie(Cookie cookie){
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IResourceHandler.CanSetCookie(Cookie cookie){
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void IResourceHandler.Cancel(){}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								Core/Management/BrowserCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								Core/Management/BrowserCache.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Management{
 | 
			
		||||
    static class BrowserCache{
 | 
			
		||||
        public static string CacheFolder => Path.Combine(Program.StoragePath, "Cache");
 | 
			
		||||
        
 | 
			
		||||
        private static bool ClearOnExit;
 | 
			
		||||
        private static Timer AutoClearTimer;
 | 
			
		||||
 | 
			
		||||
        private static long CalculateCacheSize(){
 | 
			
		||||
            return new DirectoryInfo(CacheFolder).EnumerateFiles().Select(file => {
 | 
			
		||||
                try{
 | 
			
		||||
                    return file.Length;
 | 
			
		||||
                }catch{
 | 
			
		||||
                    return 0L;
 | 
			
		||||
                }
 | 
			
		||||
            }).Sum();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void GetCacheSize(Action<Task<long>> callbackBytes){
 | 
			
		||||
            Task<long> task = new Task<long>(CalculateCacheSize);
 | 
			
		||||
            task.ContinueWith(callbackBytes);
 | 
			
		||||
            task.Start();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static void RefreshTimer(){
 | 
			
		||||
            bool shouldRun = Program.Config.System.ClearCacheAutomatically && !ClearOnExit;
 | 
			
		||||
 | 
			
		||||
            if (!shouldRun && AutoClearTimer != null){
 | 
			
		||||
                AutoClearTimer.Dispose();
 | 
			
		||||
                AutoClearTimer = null;
 | 
			
		||||
            }
 | 
			
		||||
            else if (shouldRun && AutoClearTimer == null){
 | 
			
		||||
                AutoClearTimer = new Timer(state => {
 | 
			
		||||
                    if (AutoClearTimer != null){
 | 
			
		||||
                        try{
 | 
			
		||||
                            if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold*1024L*1024L){
 | 
			
		||||
                                SetClearOnExit();
 | 
			
		||||
                            }
 | 
			
		||||
                        }catch(Exception){
 | 
			
		||||
                            // TODO should probably log errors and report them at some point
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }, null, TimeSpan.FromSeconds(30), TimeSpan.FromHours(4));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void SetClearOnExit(){
 | 
			
		||||
            ClearOnExit = true;
 | 
			
		||||
            RefreshTimer();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void TryClearNow(){
 | 
			
		||||
            try{
 | 
			
		||||
                Directory.Delete(CacheFolder, true);
 | 
			
		||||
            }catch{
 | 
			
		||||
                // welp, too bad
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void Exit(){
 | 
			
		||||
            if (AutoClearTimer != null){
 | 
			
		||||
                AutoClearTimer.Dispose();
 | 
			
		||||
                AutoClearTimer = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (ClearOnExit){
 | 
			
		||||
                TryClearNow();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										161
									
								
								Core/Management/ContextInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								Core/Management/ContextInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
using System;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Management{
 | 
			
		||||
    sealed class ContextInfo{
 | 
			
		||||
        private LinkInfo link;
 | 
			
		||||
        private ChirpInfo? chirp;
 | 
			
		||||
        
 | 
			
		||||
        public ContextInfo(){
 | 
			
		||||
            Reset();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public void SetLink(string type, string url){
 | 
			
		||||
            link = string.IsNullOrEmpty(url) ? null : new LinkInfo(type, url);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
 | 
			
		||||
            chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?)null : new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ContextData Reset(){
 | 
			
		||||
            link = null;
 | 
			
		||||
            chirp = null;
 | 
			
		||||
            return ContextData.Empty;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ContextData Create(IContextMenuParams parameters){
 | 
			
		||||
            ContextData.Builder builder = new ContextData.Builder();
 | 
			
		||||
            builder.AddContext(parameters);
 | 
			
		||||
 | 
			
		||||
            if (link != null){
 | 
			
		||||
                builder.AddOverride(link.Type, link.Url);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (chirp.HasValue){
 | 
			
		||||
                builder.AddChirp(chirp.Value);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return builder.Build();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Data structures
 | 
			
		||||
 | 
			
		||||
        private sealed class LinkInfo{
 | 
			
		||||
            public string Type { get; }
 | 
			
		||||
            public string Url { get; }
 | 
			
		||||
 | 
			
		||||
            public LinkInfo(string type, string url){
 | 
			
		||||
                this.Type = type;
 | 
			
		||||
                this.Url = url;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public struct ChirpInfo{
 | 
			
		||||
            public string TweetUrl { get; }
 | 
			
		||||
            public string QuoteUrl { get; }
 | 
			
		||||
 | 
			
		||||
            public string[] Authors => chirpAuthors?.Split(';') ?? StringUtils.EmptyArray;
 | 
			
		||||
            public string[] Images => chirpImages?.Split(';') ?? StringUtils.EmptyArray;
 | 
			
		||||
            
 | 
			
		||||
            private readonly string chirpAuthors;
 | 
			
		||||
            private readonly string chirpImages;
 | 
			
		||||
 | 
			
		||||
            public ChirpInfo(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
 | 
			
		||||
                this.TweetUrl = tweetUrl;
 | 
			
		||||
                this.QuoteUrl = quoteUrl;
 | 
			
		||||
                this.chirpAuthors = chirpAuthors;
 | 
			
		||||
                this.chirpImages = chirpImages;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Constructed context
 | 
			
		||||
 | 
			
		||||
        [Flags]
 | 
			
		||||
        public enum ContextType{
 | 
			
		||||
            Unknown = 0,
 | 
			
		||||
            Link    = 0b0001,
 | 
			
		||||
            Image   = 0b0010,
 | 
			
		||||
            Video   = 0b0100,
 | 
			
		||||
            Chirp   = 0b1000
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public sealed class ContextData{
 | 
			
		||||
            public static readonly ContextData Empty = new Builder().Build();
 | 
			
		||||
 | 
			
		||||
            public ContextType Types { get; }
 | 
			
		||||
 | 
			
		||||
            public string LinkUrl { get; }
 | 
			
		||||
            public string UnsafeLinkUrl { get; }
 | 
			
		||||
            public string MediaUrl { get; }
 | 
			
		||||
 | 
			
		||||
            public ChirpInfo Chirp { get; }
 | 
			
		||||
 | 
			
		||||
            public ContextData(ContextType types, string linkUrl, string unsafeLinkUrl, string mediaUrl, ChirpInfo chirp){
 | 
			
		||||
                Types = types;
 | 
			
		||||
                LinkUrl = linkUrl;
 | 
			
		||||
                UnsafeLinkUrl = unsafeLinkUrl;
 | 
			
		||||
                MediaUrl = mediaUrl;
 | 
			
		||||
                Chirp = chirp;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public sealed class Builder{
 | 
			
		||||
                private ContextType types = ContextType.Unknown;
 | 
			
		||||
 | 
			
		||||
                private string linkUrl = string.Empty;
 | 
			
		||||
                private string unsafeLinkUrl = string.Empty;
 | 
			
		||||
                private string mediaUrl = string.Empty;
 | 
			
		||||
 | 
			
		||||
                private ChirpInfo chirp = default(ChirpInfo);
 | 
			
		||||
 | 
			
		||||
                public void AddContext(IContextMenuParams parameters){
 | 
			
		||||
                    ContextMenuType flags = parameters.TypeFlags;
 | 
			
		||||
 | 
			
		||||
                    if (flags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
 | 
			
		||||
                        types |= ContextType.Image;
 | 
			
		||||
                        types &= ~ContextType.Video;
 | 
			
		||||
                        mediaUrl = parameters.SourceUrl;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (flags.HasFlag(ContextMenuType.Link)){
 | 
			
		||||
                        types |= ContextType.Link;
 | 
			
		||||
                        linkUrl = parameters.LinkUrl;
 | 
			
		||||
                        unsafeLinkUrl = parameters.UnfilteredLinkUrl;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                public void AddOverride(string type, string url){
 | 
			
		||||
                    switch(type){
 | 
			
		||||
                        case "link":
 | 
			
		||||
                            types |= ContextType.Link;
 | 
			
		||||
                            linkUrl = url;
 | 
			
		||||
                            unsafeLinkUrl = url;
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "image":
 | 
			
		||||
                            types |= ContextType.Image;
 | 
			
		||||
                            types &= ~(ContextType.Video | ContextType.Link);
 | 
			
		||||
                            mediaUrl = url;
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "video":
 | 
			
		||||
                            types |= ContextType.Video;
 | 
			
		||||
                            types &= ~(ContextType.Image | ContextType.Link);
 | 
			
		||||
                            mediaUrl = url;
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                public void AddChirp(ChirpInfo chirp){
 | 
			
		||||
                    this.types |= ContextType.Chirp;
 | 
			
		||||
                    this.chirp = chirp;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                public ContextData Build(){
 | 
			
		||||
                    return new ContextData(types, linkUrl, unsafeLinkUrl, mediaUrl, chirp);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										215
									
								
								Core/Management/ProfileManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								Core/Management/ProfileManager.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Plugins.Enums;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Management{
 | 
			
		||||
    sealed class ProfileManager{
 | 
			
		||||
        private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
 | 
			
		||||
        private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
 | 
			
		||||
 | 
			
		||||
        [Flags]
 | 
			
		||||
        public enum Items{
 | 
			
		||||
            None = 0,
 | 
			
		||||
            UserConfig = 1,
 | 
			
		||||
            SystemConfig = 2,
 | 
			
		||||
            Session = 4,
 | 
			
		||||
            PluginData = 8,
 | 
			
		||||
            All = UserConfig|SystemConfig|Session|PluginData
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private readonly string file;
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
 | 
			
		||||
        public ProfileManager(string file, PluginManager plugins){
 | 
			
		||||
            this.file = file;
 | 
			
		||||
            this.plugins = plugins;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Export(Items items){
 | 
			
		||||
            try{
 | 
			
		||||
                using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
 | 
			
		||||
                    if (items.HasFlag(Items.UserConfig)){
 | 
			
		||||
                        stream.WriteFile("config", Program.UserConfigFilePath);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (items.HasFlag(Items.SystemConfig)){
 | 
			
		||||
                        stream.WriteFile("system", Program.SystemConfigFilePath);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (items.HasFlag(Items.PluginData)){
 | 
			
		||||
                        stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
 | 
			
		||||
 | 
			
		||||
                        foreach(Plugin plugin in plugins.Plugins){
 | 
			
		||||
                            foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
 | 
			
		||||
                                try{
 | 
			
		||||
                                    stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
 | 
			
		||||
                                }catch(ArgumentOutOfRangeException e){
 | 
			
		||||
                                    FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (items.HasFlag(Items.Session)){
 | 
			
		||||
                        stream.WriteFile("cookies", CookiesPath);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    stream.Flush();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Items FindImportItems(){
 | 
			
		||||
            Items items = Items.None;
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
 | 
			
		||||
                    string key;
 | 
			
		||||
 | 
			
		||||
                    while((key = stream.SkipFile()) != null){
 | 
			
		||||
                        switch(key){
 | 
			
		||||
                            case "config":
 | 
			
		||||
                                items |= Items.UserConfig;
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "system":
 | 
			
		||||
                                items |= Items.SystemConfig;
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "plugin.config":
 | 
			
		||||
                            case "plugin.data":
 | 
			
		||||
                                items |= Items.PluginData;
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "cookies":
 | 
			
		||||
                                items |= Items.Session;
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }catch(Exception){
 | 
			
		||||
                items = Items.None;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return items;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Import(Items items){
 | 
			
		||||
            try{
 | 
			
		||||
                HashSet<string> missingPlugins = new HashSet<string>();
 | 
			
		||||
 | 
			
		||||
                using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
 | 
			
		||||
                    CombinedFileStream.Entry entry;
 | 
			
		||||
 | 
			
		||||
                    while((entry = stream.ReadFile()) != null){
 | 
			
		||||
                        switch(entry.KeyName){
 | 
			
		||||
                            case "config":
 | 
			
		||||
                                if (items.HasFlag(Items.UserConfig)){
 | 
			
		||||
                                    entry.WriteToFile(Program.UserConfigFilePath);
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "system":
 | 
			
		||||
                                if (items.HasFlag(Items.SystemConfig)){
 | 
			
		||||
                                    entry.WriteToFile(Program.SystemConfigFilePath);
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "plugin.config":
 | 
			
		||||
                                if (items.HasFlag(Items.PluginData)){
 | 
			
		||||
                                    entry.WriteToFile(Program.PluginConfigFilePath);
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "plugin.data":
 | 
			
		||||
                                if (items.HasFlag(Items.PluginData)){
 | 
			
		||||
                                    string[] value = entry.KeyValue;
 | 
			
		||||
 | 
			
		||||
                                    entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
 | 
			
		||||
 | 
			
		||||
                                    if (!plugins.IsPluginInstalled(value[0])){
 | 
			
		||||
                                        missingPlugins.Add(value[0]);
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "cookies":
 | 
			
		||||
                                if (items.HasFlag(Items.Session)){
 | 
			
		||||
                                    entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (missingPlugins.Count > 0){
 | 
			
		||||
                    FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Profile Import", "An exception happened while importing TweetDuck profile.", true, e);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void ImportCookies(){
 | 
			
		||||
            if (File.Exists(TempCookiesPath)){
 | 
			
		||||
                try{
 | 
			
		||||
                    if (File.Exists(CookiesPath)){
 | 
			
		||||
                        File.Delete(CookiesPath);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    File.Move(TempCookiesPath, CookiesPath);
 | 
			
		||||
                }catch(Exception e){
 | 
			
		||||
                    Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void DeleteCookies(){
 | 
			
		||||
            try{
 | 
			
		||||
                if (File.Exists(CookiesPath)){
 | 
			
		||||
                    File.Delete(CookiesPath);
 | 
			
		||||
                }
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
 | 
			
		||||
            if (Directory.Exists(root)){
 | 
			
		||||
                int rootLength = root.Length;
 | 
			
		||||
                return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength));
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                return Enumerable.Empty<PathInfo>();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private sealed class PathInfo{
 | 
			
		||||
            public string Full { get; }
 | 
			
		||||
            public string Relative { get; }
 | 
			
		||||
 | 
			
		||||
            public PathInfo(string fullPath, int rootLength){
 | 
			
		||||
                this.Full = fullPath;
 | 
			
		||||
                this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										213
									
								
								Core/Management/VideoPlayer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								Core/Management/VideoPlayer.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,213 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetLib.Communication;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Management{
 | 
			
		||||
    sealed class VideoPlayer : IDisposable{
 | 
			
		||||
        private static UserConfig Config => Program.Config.User;
 | 
			
		||||
 | 
			
		||||
        public bool Running => currentInstance != null && currentInstance.Running;
 | 
			
		||||
 | 
			
		||||
        public event EventHandler ProcessExited;
 | 
			
		||||
 | 
			
		||||
        private readonly FormBrowser owner;
 | 
			
		||||
 | 
			
		||||
        private Instance currentInstance;
 | 
			
		||||
        private bool isClosing;
 | 
			
		||||
 | 
			
		||||
        public VideoPlayer(FormBrowser owner){
 | 
			
		||||
            this.owner = owner;
 | 
			
		||||
            this.owner.FormClosing += owner_FormClosing;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Launch(string url, string username){
 | 
			
		||||
            if (Running){
 | 
			
		||||
                Destroy();
 | 
			
		||||
                isClosing = false;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            try{
 | 
			
		||||
                DuplexPipe.Server pipe = DuplexPipe.CreateServer();
 | 
			
		||||
                pipe.DataIn += pipe_DataIn;
 | 
			
		||||
 | 
			
		||||
                Process process;
 | 
			
		||||
 | 
			
		||||
                if ((process = Process.Start(new ProcessStartInfo{
 | 
			
		||||
                    FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
 | 
			
		||||
                    Arguments = $"{owner.Handle} {(int)Math.Floor(100F*owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
 | 
			
		||||
                    UseShellExecute = false,
 | 
			
		||||
                    RedirectStandardOutput = true
 | 
			
		||||
                })) != null){
 | 
			
		||||
                    currentInstance = new Instance(process, pipe, url, username);
 | 
			
		||||
 | 
			
		||||
                    process.EnableRaisingEvents = true;
 | 
			
		||||
                    process.Exited += process_Exited;
 | 
			
		||||
                    
 | 
			
		||||
                    process.BeginOutputReadLine();
 | 
			
		||||
                    process.OutputDataReceived += process_OutputDataReceived;
 | 
			
		||||
 | 
			
		||||
                    pipe.DisposeToken();
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    pipe.DataIn -= pipe_DataIn;
 | 
			
		||||
                    pipe.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void SendKeyEvent(Keys key){
 | 
			
		||||
            currentInstance?.Pipe.Write("key", ((int)key).ToString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
 | 
			
		||||
            owner.InvokeSafe(() => {
 | 
			
		||||
                switch(e.Key){
 | 
			
		||||
                    case "vol":
 | 
			
		||||
                        if (int.TryParse(e.Data, out int volume) && volume != Config.VideoPlayerVolume){
 | 
			
		||||
                            Config.VideoPlayerVolume = volume;
 | 
			
		||||
                            Config.Save();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case "download":
 | 
			
		||||
                        if (currentInstance != null){
 | 
			
		||||
                            owner.AnalyticsFile.DownloadedVideos.Trigger();
 | 
			
		||||
                            TwitterUtils.DownloadVideo(currentInstance.Url, currentInstance.Username);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case "rip":
 | 
			
		||||
                        currentInstance?.Dispose();
 | 
			
		||||
                        currentInstance = null;
 | 
			
		||||
 | 
			
		||||
                        isClosing = false;
 | 
			
		||||
                        TriggerProcessExitEventUnsafe();
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Close(){
 | 
			
		||||
            if (currentInstance != null){
 | 
			
		||||
                if (isClosing){
 | 
			
		||||
                    Destroy();
 | 
			
		||||
                    isClosing = false;
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    isClosing = true;
 | 
			
		||||
                    currentInstance.Process.Exited -= process_Exited;
 | 
			
		||||
                    currentInstance.Pipe.Write("die");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose(){
 | 
			
		||||
            ProcessExited = null;
 | 
			
		||||
 | 
			
		||||
            isClosing = true;
 | 
			
		||||
            Destroy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Destroy(){
 | 
			
		||||
            if (currentInstance != null){
 | 
			
		||||
                currentInstance.KillAndDispose();
 | 
			
		||||
                currentInstance = null;
 | 
			
		||||
 | 
			
		||||
                TriggerProcessExitEventUnsafe();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void owner_FormClosing(object sender, FormClosingEventArgs e){
 | 
			
		||||
            if (currentInstance != null){
 | 
			
		||||
                currentInstance.Process.Exited -= process_Exited;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void process_OutputDataReceived(object sender, DataReceivedEventArgs e){
 | 
			
		||||
            if (!string.IsNullOrEmpty(e.Data)){
 | 
			
		||||
                Program.Reporter.LogVerbose("[VideoPlayer] "+e.Data);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void process_Exited(object sender, EventArgs e){
 | 
			
		||||
            if (currentInstance == null){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            int exitCode = currentInstance.Process.ExitCode;
 | 
			
		||||
            string url = currentInstance.Url;
 | 
			
		||||
 | 
			
		||||
            currentInstance.Dispose();
 | 
			
		||||
            currentInstance = null;
 | 
			
		||||
 | 
			
		||||
            switch(exitCode){
 | 
			
		||||
                case 3: // CODE_LAUNCH_FAIL
 | 
			
		||||
                    if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
 | 
			
		||||
                        BrowserUtils.OpenExternalBrowser(url);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case 4: // CODE_MEDIA_ERROR
 | 
			
		||||
                    if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
 | 
			
		||||
                        BrowserUtils.OpenExternalBrowser(url);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void TriggerProcessExitEventUnsafe(){
 | 
			
		||||
            ProcessExited?.Invoke(this, EventArgs.Empty);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private sealed class Instance : IDisposable{
 | 
			
		||||
            public bool Running{
 | 
			
		||||
                get{
 | 
			
		||||
                    Process.Refresh();
 | 
			
		||||
                    return !Process.HasExited;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public Process Process { get; }
 | 
			
		||||
            public DuplexPipe.Server Pipe { get; }
 | 
			
		||||
 | 
			
		||||
            public string Url { get; }
 | 
			
		||||
            public string Username { get; }
 | 
			
		||||
 | 
			
		||||
            public Instance(Process process, DuplexPipe.Server pipe, string url, string username){
 | 
			
		||||
                this.Process = process;
 | 
			
		||||
                this.Pipe = pipe;
 | 
			
		||||
                this.Url = url;
 | 
			
		||||
                this.Username = username;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void KillAndDispose(){
 | 
			
		||||
                try{
 | 
			
		||||
                    Process.Kill();
 | 
			
		||||
                }catch{
 | 
			
		||||
                    // kill me instead then
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Dispose();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void Dispose(){
 | 
			
		||||
                Process.Dispose();
 | 
			
		||||
                Pipe.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								Core/Notification/Example/FormNotificationExample.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								Core/Notification/Example/FormNotificationExample.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Resources;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification.Example{
 | 
			
		||||
    sealed class FormNotificationExample : FormNotificationMain{
 | 
			
		||||
        public override bool RequiresResize => true;
 | 
			
		||||
        protected override bool CanDragWindow => Config.NotificationPosition == TweetNotification.Position.Custom;
 | 
			
		||||
 | 
			
		||||
        protected override FormBorderStyle NotificationBorderStyle{
 | 
			
		||||
            get{
 | 
			
		||||
                if (Config.NotificationSize == TweetNotification.Size.Custom){
 | 
			
		||||
                    switch(base.NotificationBorderStyle){
 | 
			
		||||
                        case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
 | 
			
		||||
                        case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return base.NotificationBorderStyle;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override string BodyClasses => base.BodyClasses+" td-example";
 | 
			
		||||
 | 
			
		||||
        public event EventHandler Ready;
 | 
			
		||||
 | 
			
		||||
        private readonly TweetNotification exampleNotification;
 | 
			
		||||
 | 
			
		||||
        public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
 | 
			
		||||
            browser.LoadingStateChanged += browser_LoadingStateChanged;
 | 
			
		||||
 | 
			
		||||
            string exampleTweetHTML = ScriptLoader.LoadResourceSilent("pages/example.html")?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
 | 
			
		||||
 | 
			
		||||
            #if DEBUG
 | 
			
		||||
            exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
 | 
			
		||||
            #endif
 | 
			
		||||
 | 
			
		||||
            exampleNotification = new TweetNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
 | 
			
		||||
            if (!e.IsLoading){
 | 
			
		||||
                Ready?.Invoke(this, EventArgs.Empty);
 | 
			
		||||
                browser.LoadingStateChanged -= browser_LoadingStateChanged;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void HideNotification(){
 | 
			
		||||
            Location = ControlExtensions.InvisibleLocation;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void FinishCurrentNotification(){}
 | 
			
		||||
 | 
			
		||||
        public void ShowExampleNotification(bool reset){
 | 
			
		||||
            if (reset){
 | 
			
		||||
                LoadTweet(exampleNotification);
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                PrepareAndDisplayWindow();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            UpdateTitle();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,21 @@
 | 
			
		||||
namespace TweetDuck.Browser.Notification {
 | 
			
		||||
namespace TweetDuck.Core.Notification {
 | 
			
		||||
    partial class FormNotificationBase {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private System.ComponentModel.IContainer components = null;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clean up any resources being used.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
 | 
			
		||||
        protected override void Dispose(bool disposing) {
 | 
			
		||||
            if (disposing && (components != null)) {
 | 
			
		||||
                components.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            base.Dispose(disposing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region Windows Form Designer generated code
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -22,9 +33,8 @@
 | 
			
		||||
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 | 
			
		||||
            this.BackColor = System.Drawing.SystemColors.Control;
 | 
			
		||||
            this.ClientSize = new System.Drawing.Size(284, 122);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
 | 
			
		||||
            this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
 | 
			
		||||
            this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
            this.MinimizeBox = false;
 | 
			
		||||
            this.Name = "FormNotification";
 | 
			
		||||
							
								
								
									
										236
									
								
								Core/Notification/FormNotificationBase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								Core/Notification/FormNotificationBase.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,236 @@
 | 
			
		||||
using CefSharp.WinForms;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using TweetDuck.Core.Bridge;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Handling;
 | 
			
		||||
using TweetDuck.Core.Handling.General;
 | 
			
		||||
using TweetDuck.Core.Other.Analytics;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification{
 | 
			
		||||
    abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
 | 
			
		||||
        protected static UserConfig Config => Program.Config.User;
 | 
			
		||||
 | 
			
		||||
        protected static int FontSizeLevel{
 | 
			
		||||
            get{
 | 
			
		||||
                switch(TweetDeckBridge.FontSize){
 | 
			
		||||
                    case "largest": return 4;
 | 
			
		||||
                    case "large": return 3;
 | 
			
		||||
                    case "small": return 1;
 | 
			
		||||
                    case "smallest": return 0;
 | 
			
		||||
                    default: return 2;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected virtual Point PrimaryLocation{
 | 
			
		||||
            get{
 | 
			
		||||
                Screen screen;
 | 
			
		||||
 | 
			
		||||
                if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length){
 | 
			
		||||
                    screen = Screen.AllScreens[Config.NotificationDisplay-1];
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    screen = Screen.FromControl(owner); // TODO may be disposed?
 | 
			
		||||
                }
 | 
			
		||||
            
 | 
			
		||||
                int edgeDist = Config.NotificationEdgeDistance;
 | 
			
		||||
 | 
			
		||||
                switch(Config.NotificationPosition){
 | 
			
		||||
                    case TweetNotification.Position.TopLeft:
 | 
			
		||||
                        return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
 | 
			
		||||
 | 
			
		||||
                    case TweetNotification.Position.TopRight:
 | 
			
		||||
                        return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
 | 
			
		||||
 | 
			
		||||
                    case TweetNotification.Position.BottomLeft:
 | 
			
		||||
                        return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
 | 
			
		||||
 | 
			
		||||
                    case TweetNotification.Position.BottomRight:
 | 
			
		||||
                        return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
 | 
			
		||||
 | 
			
		||||
                    case TweetNotification.Position.Custom:
 | 
			
		||||
                        if (!Config.IsCustomNotificationPositionSet){
 | 
			
		||||
                            Config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
 | 
			
		||||
                            Config.Save();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        return Config.CustomNotificationPosition;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Location;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
 | 
			
		||||
        protected virtual bool CanDragWindow => true;
 | 
			
		||||
 | 
			
		||||
        public new Point Location{
 | 
			
		||||
            get{
 | 
			
		||||
                return base.Location;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            set{
 | 
			
		||||
                Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
 | 
			
		||||
                FormBorderStyle = NotificationBorderStyle;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected virtual FormBorderStyle NotificationBorderStyle{
 | 
			
		||||
            get{
 | 
			
		||||
                if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
 | 
			
		||||
                    return FormBorderStyle.FixedSingle;
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    return FormBorderStyle.FixedToolWindow;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public AnalyticsFile AnalyticsFile => owner.AnalyticsFile;
 | 
			
		||||
        
 | 
			
		||||
        protected override bool ShowWithoutActivation => true;
 | 
			
		||||
        
 | 
			
		||||
        protected float DpiScale { get; }
 | 
			
		||||
        protected double SizeScale => DpiScale*Config.ZoomLevel/100.0;
 | 
			
		||||
 | 
			
		||||
        protected readonly FormBrowser owner;
 | 
			
		||||
        protected readonly ChromiumWebBrowser browser;
 | 
			
		||||
        
 | 
			
		||||
        private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
 | 
			
		||||
 | 
			
		||||
        private TweetNotification currentNotification;
 | 
			
		||||
        private int pauseCounter;
 | 
			
		||||
        
 | 
			
		||||
        public string CurrentTweetUrl => currentNotification?.TweetUrl;
 | 
			
		||||
        public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
 | 
			
		||||
 | 
			
		||||
        public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
 | 
			
		||||
 | 
			
		||||
        protected bool IsPaused => pauseCounter > 0;
 | 
			
		||||
        protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
 | 
			
		||||
        
 | 
			
		||||
        public bool FreezeTimer { get; set; }
 | 
			
		||||
        public bool ContextMenuOpen { get; set; }
 | 
			
		||||
 | 
			
		||||
        protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            this.owner = owner;
 | 
			
		||||
            this.owner.FormClosed += owner_FormClosed;
 | 
			
		||||
 | 
			
		||||
            ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
 | 
			
		||||
            resourceHandlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
 | 
			
		||||
            resourceHandlerFactory.RegisterHandler(TweetNotification.AppLogo);
 | 
			
		||||
 | 
			
		||||
            this.browser = new ChromiumWebBrowser("about:blank"){
 | 
			
		||||
                MenuHandler = new ContextMenuNotification(this, enableContextMenu),
 | 
			
		||||
                JsDialogHandler = new JavaScriptDialogHandler(),
 | 
			
		||||
                LifeSpanHandler = new LifeSpanHandler(),
 | 
			
		||||
                RequestHandler = new RequestHandlerBase(false),
 | 
			
		||||
                ResourceHandlerFactory = resourceHandlerFactory
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.browser.Dock = DockStyle.None;
 | 
			
		||||
            this.browser.ClientSize = ClientSize;
 | 
			
		||||
            this.browser.SetupZoomEvents();
 | 
			
		||||
 | 
			
		||||
            Controls.Add(browser);
 | 
			
		||||
 | 
			
		||||
            Disposed += (sender, args) => {
 | 
			
		||||
                this.browser.Dispose();
 | 
			
		||||
                this.owner.FormClosed -= owner_FormClosed;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            DpiScale = this.GetDPIScale();
 | 
			
		||||
 | 
			
		||||
            // ReSharper disable once VirtualMemberCallInContructor
 | 
			
		||||
            UpdateTitle();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void WndProc(ref Message m){
 | 
			
		||||
            if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow){ // WM_SYSCOMMAND, SC_MOVE
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            base.WndProc(ref m);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // event handlers
 | 
			
		||||
 | 
			
		||||
        private void owner_FormClosed(object sender, FormClosedEventArgs e){
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // notification methods
 | 
			
		||||
 | 
			
		||||
        public virtual void HideNotification(){
 | 
			
		||||
            browser.Load("about:blank");
 | 
			
		||||
            DisplayTooltip(null);
 | 
			
		||||
 | 
			
		||||
            Location = ControlExtensions.InvisibleLocation;
 | 
			
		||||
            currentNotification = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void FinishCurrentNotification(){}
 | 
			
		||||
 | 
			
		||||
        public virtual void PauseNotification(){
 | 
			
		||||
            if (pauseCounter++ == 0 && IsNotificationVisible){
 | 
			
		||||
                Location = ControlExtensions.InvisibleLocation;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void ResumeNotification(){
 | 
			
		||||
            if (pauseCounter > 0){
 | 
			
		||||
                --pauseCounter;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected abstract string GetTweetHTML(TweetNotification tweet);
 | 
			
		||||
 | 
			
		||||
        protected virtual void LoadTweet(TweetNotification tweet){
 | 
			
		||||
            currentNotification = tweet;
 | 
			
		||||
            resourceHandler.SetHTML(GetTweetHTML(tweet));
 | 
			
		||||
 | 
			
		||||
            browser.Load(TwitterUtils.TweetDeckURL);
 | 
			
		||||
            DisplayTooltip(null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected virtual void SetNotificationSize(int width, int height){
 | 
			
		||||
            browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected virtual void UpdateTitle(){
 | 
			
		||||
            string title = currentNotification?.ColumnTitle;
 | 
			
		||||
            Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ShowTweetDetail(){
 | 
			
		||||
            if (currentNotification != null){
 | 
			
		||||
                owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void MoveToVisibleLocation(){
 | 
			
		||||
            bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
 | 
			
		||||
            Location = PrimaryLocation;
 | 
			
		||||
 | 
			
		||||
            if (needsReactivating){
 | 
			
		||||
                NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void DisplayTooltip(string text){
 | 
			
		||||
            if (string.IsNullOrEmpty(text)){
 | 
			
		||||
                toolTip.Hide(this);
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                Point position = PointToClient(Cursor.Position);
 | 
			
		||||
                position.Offset(20, 5);
 | 
			
		||||
                toolTip.Show(text, this, position);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Browser.Notification {
 | 
			
		||||
namespace TweetDuck.Core.Notification {
 | 
			
		||||
    partial class FormNotificationMain {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -26,7 +26,7 @@
 | 
			
		||||
            this.components = new System.ComponentModel.Container();
 | 
			
		||||
            this.timerDisplayDelay = new System.Windows.Forms.Timer(this.components);
 | 
			
		||||
            this.timerProgress = new System.Windows.Forms.Timer(this.components);
 | 
			
		||||
            this.progressBarTimer = new TweetDuck.Controls.FlatProgressBar();
 | 
			
		||||
            this.progressBarTimer = new TweetDuck.Core.Controls.FlatProgressBar();
 | 
			
		||||
            this.SuspendLayout();
 | 
			
		||||
            // 
 | 
			
		||||
            // timerDisplayDelay
 | 
			
		||||
@@ -59,7 +59,6 @@
 | 
			
		||||
            this.BackColor = System.Drawing.SystemColors.Control;
 | 
			
		||||
            this.ClientSize = new System.Drawing.Size(284, 122);
 | 
			
		||||
            this.Controls.Add(this.progressBarTimer);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotification_FormClosing);
 | 
			
		||||
            this.ResumeLayout(false);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										276
									
								
								Core/Notification/FormNotificationMain.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								Core/Notification/FormNotificationMain.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,276 @@
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Bridge;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Handling;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Plugins.Enums;
 | 
			
		||||
using TweetDuck.Resources;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification{
 | 
			
		||||
    abstract partial class FormNotificationMain : FormNotificationBase{
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
        private readonly int timerBarHeight;
 | 
			
		||||
 | 
			
		||||
        protected int timeLeft, totalTime;
 | 
			
		||||
        protected bool pausedDuringNotification;
 | 
			
		||||
        
 | 
			
		||||
        private readonly NativeMethods.HookProc mouseHookDelegate;
 | 
			
		||||
        private IntPtr mouseHook;
 | 
			
		||||
        private bool blockXButtonUp;
 | 
			
		||||
 | 
			
		||||
        private bool? prevDisplayTimer;
 | 
			
		||||
        private int? prevFontSize;
 | 
			
		||||
 | 
			
		||||
        public virtual bool RequiresResize{
 | 
			
		||||
            get{
 | 
			
		||||
                return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            set{
 | 
			
		||||
                if (value){
 | 
			
		||||
                    prevDisplayTimer = null;
 | 
			
		||||
                    prevFontSize = null;
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    prevDisplayTimer = Config.DisplayNotificationTimer;
 | 
			
		||||
                    prevFontSize = FontSizeLevel;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private int BaseClientWidth{
 | 
			
		||||
            get{
 | 
			
		||||
                switch(Config.NotificationSize){
 | 
			
		||||
                    default:
 | 
			
		||||
                        return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
 | 
			
		||||
 | 
			
		||||
                    case TweetNotification.Size.Custom:
 | 
			
		||||
                        return Config.CustomNotificationSize.Width;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private int BaseClientHeight{
 | 
			
		||||
            get{
 | 
			
		||||
                switch(Config.NotificationSize){
 | 
			
		||||
                    default:
 | 
			
		||||
                        return BrowserUtils.Scale(122, SizeScale*(1.0+0.08*FontSizeLevel));
 | 
			
		||||
 | 
			
		||||
                    case TweetNotification.Size.Custom:
 | 
			
		||||
                        return Config.CustomNotificationSize.Height;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
 | 
			
		||||
        
 | 
			
		||||
        public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-timerBarHeight) : ClientSize;
 | 
			
		||||
 | 
			
		||||
        protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            this.plugins = pluginManager;
 | 
			
		||||
            this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
 | 
			
		||||
            
 | 
			
		||||
            browser.KeyboardHandler = new KeyboardHandlerNotification(this);
 | 
			
		||||
            browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge.Notification(owner, this));
 | 
			
		||||
 | 
			
		||||
            browser.LoadingStateChanged += Browser_LoadingStateChanged;
 | 
			
		||||
            browser.FrameLoadEnd += Browser_FrameLoadEnd;
 | 
			
		||||
 | 
			
		||||
            plugins.Register(browser, PluginEnvironment.Notification, this);
 | 
			
		||||
 | 
			
		||||
            mouseHookDelegate = MouseHookProc;
 | 
			
		||||
            Disposed += (sender, args) => StopMouseHook(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // mouse wheel hook
 | 
			
		||||
 | 
			
		||||
        private void StartMouseHook(){
 | 
			
		||||
            if (mouseHook == IntPtr.Zero){
 | 
			
		||||
                mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void StopMouseHook(bool force){
 | 
			
		||||
            if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)){
 | 
			
		||||
                NativeMethods.UnhookWindowsHookEx(mouseHook);
 | 
			
		||||
                mouseHook = IntPtr.Zero;
 | 
			
		||||
                blockXButtonUp = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){
 | 
			
		||||
            if (nCode == 0){
 | 
			
		||||
                int eventType = wParam.ToInt32();
 | 
			
		||||
 | 
			
		||||
                if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
 | 
			
		||||
                    browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed*0.01), CefEventFlags.None);
 | 
			
		||||
                    return NativeMethods.HOOK_HANDLED;
 | 
			
		||||
                }
 | 
			
		||||
                else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
 | 
			
		||||
                    int extraButton = NativeMethods.GetMouseHookData(lParam);
 | 
			
		||||
 | 
			
		||||
                    if (extraButton == 2){ // forward button
 | 
			
		||||
                        this.InvokeAsyncSafe(FinishCurrentNotification);
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (extraButton == 1){ // back button
 | 
			
		||||
                        this.InvokeAsyncSafe(Close);
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    blockXButtonUp = true;
 | 
			
		||||
                    this.InvokeAsyncSafe(AnalyticsFile.NotificationExtraMouseButtons.Trigger);
 | 
			
		||||
                    return NativeMethods.HOOK_HANDLED;
 | 
			
		||||
                }
 | 
			
		||||
                else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
 | 
			
		||||
                    blockXButtonUp = false;
 | 
			
		||||
 | 
			
		||||
                    if (!Visible){
 | 
			
		||||
                        StopMouseHook(false);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return NativeMethods.HOOK_HANDLED;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // event handlers
 | 
			
		||||
 | 
			
		||||
        private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
 | 
			
		||||
            if (e.CloseReason == CloseReason.UserClosing){
 | 
			
		||||
                HideNotification();
 | 
			
		||||
                e.Cancel = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
 | 
			
		||||
            if (!e.IsLoading && browser.Address != "about:blank"){
 | 
			
		||||
                this.InvokeSafe(() => {
 | 
			
		||||
                    Visible = true; // ensures repaint before moving the window to a visible location
 | 
			
		||||
                    timerDisplayDelay.Start();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
 | 
			
		||||
            IFrame frame = e.Frame;
 | 
			
		||||
 | 
			
		||||
            if (frame.IsMain && browser.Address != "about:blank"){
 | 
			
		||||
                frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
 | 
			
		||||
                ScriptLoader.ExecuteFile(frame, "notification.js", this);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void timerDisplayDelay_Tick(object sender, EventArgs e){
 | 
			
		||||
            OnNotificationReady();
 | 
			
		||||
            timerDisplayDelay.Stop();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void timerHideProgress_Tick(object sender, EventArgs e){
 | 
			
		||||
            if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            timeLeft -= timerProgress.Interval;
 | 
			
		||||
 | 
			
		||||
            int value = BrowserUtils.Scale(progressBarTimer.Maximum+25, (totalTime-timeLeft)/(double)totalTime);
 | 
			
		||||
            progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum-value : value);
 | 
			
		||||
 | 
			
		||||
            if (timeLeft <= 0){
 | 
			
		||||
                FinishCurrentNotification();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // notification methods
 | 
			
		||||
 | 
			
		||||
        public virtual void ShowNotification(TweetNotification notification){
 | 
			
		||||
            LoadTweet(notification);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void HideNotification(){
 | 
			
		||||
            base.HideNotification();
 | 
			
		||||
            
 | 
			
		||||
            progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
 | 
			
		||||
            timerProgress.Stop();
 | 
			
		||||
            totalTime = 0;
 | 
			
		||||
 | 
			
		||||
            StopMouseHook(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void FinishCurrentNotification(){
 | 
			
		||||
            timerProgress.Stop();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void PauseNotification(){
 | 
			
		||||
            if (!IsPaused){
 | 
			
		||||
                pausedDuringNotification = IsNotificationVisible;
 | 
			
		||||
                timerProgress.Stop();
 | 
			
		||||
                StopMouseHook(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            base.PauseNotification();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void ResumeNotification(){
 | 
			
		||||
            bool wasPaused = IsPaused;
 | 
			
		||||
            base.ResumeNotification();
 | 
			
		||||
 | 
			
		||||
            if (wasPaused && !IsPaused && pausedDuringNotification){
 | 
			
		||||
                OnNotificationReady();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override string GetTweetHTML(TweetNotification tweet){
 | 
			
		||||
            string html = tweet.GenerateHtml(BodyClasses, this);
 | 
			
		||||
 | 
			
		||||
            foreach(InjectedHTML injection in plugins.NotificationInjections){
 | 
			
		||||
                html = injection.InjectInto(html);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return html;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void LoadTweet(TweetNotification tweet){
 | 
			
		||||
            timerProgress.Stop();
 | 
			
		||||
            totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
 | 
			
		||||
            progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
 | 
			
		||||
 | 
			
		||||
            base.LoadTweet(tweet);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void SetNotificationSize(int width, int height){
 | 
			
		||||
            if (Config.DisplayNotificationTimer){
 | 
			
		||||
                ClientSize = new Size(width, height+timerBarHeight);
 | 
			
		||||
                progressBarTimer.Visible = true;
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                ClientSize = new Size(width, height);
 | 
			
		||||
                progressBarTimer.Visible = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            browser.ClientSize = new Size(width, height);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        protected void PrepareAndDisplayWindow(){
 | 
			
		||||
            if (RequiresResize){
 | 
			
		||||
                RequiresResize = false;
 | 
			
		||||
                SetNotificationSize(BaseClientWidth, BaseClientHeight);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            MoveToVisibleLocation();
 | 
			
		||||
            StartMouseHook();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected virtual void OnNotificationReady(){
 | 
			
		||||
            PrepareAndDisplayWindow();
 | 
			
		||||
            timerProgress.Start();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Browser.Notification {
 | 
			
		||||
namespace TweetDuck.Core.Notification {
 | 
			
		||||
    partial class FormNotificationTweet {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
							
								
								
									
										165
									
								
								Core/Notification/FormNotificationTweet.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								Core/Notification/FormNotificationTweet.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification{
 | 
			
		||||
    sealed partial class FormNotificationTweet : FormNotificationMain{
 | 
			
		||||
        private const int NonIntrusiveIdleLimit = 30;
 | 
			
		||||
        private const int TrimMinimum = 32;
 | 
			
		||||
 | 
			
		||||
        protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation;
 | 
			
		||||
        private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
 | 
			
		||||
 | 
			
		||||
        protected override bool CanDragWindow{
 | 
			
		||||
            get{
 | 
			
		||||
                if (ModifierKeys.HasFlag(Keys.Alt)){
 | 
			
		||||
                    hasTemporarilyMoved = true;
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
 | 
			
		||||
        private bool needsTrim;
 | 
			
		||||
        private bool hasTemporarilyMoved;
 | 
			
		||||
 | 
			
		||||
        public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            Config.MuteToggled += Config_MuteToggled;
 | 
			
		||||
            Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
 | 
			
		||||
 | 
			
		||||
            if (Config.MuteNotifications){
 | 
			
		||||
                PauseNotification();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void WndProc(ref Message m){
 | 
			
		||||
            if (m.Msg == 0x00A7){ // WM_NCMBUTTONDOWN
 | 
			
		||||
                int hitTest = m.WParam.ToInt32();
 | 
			
		||||
 | 
			
		||||
                if (hitTest == 2 || hitTest == 20){ // HTCAPTION, HTCLOSE
 | 
			
		||||
                    hasTemporarilyMoved = false;
 | 
			
		||||
                    MoveToVisibleLocation();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            base.WndProc(ref m);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // event handlers
 | 
			
		||||
 | 
			
		||||
        private void Config_MuteToggled(object sender, EventArgs e){
 | 
			
		||||
            if (Config.MuteNotifications){
 | 
			
		||||
                PauseNotification();
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                ResumeNotification();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void timerCursorCheck_Tick(object sender, EventArgs e){
 | 
			
		||||
            if (!IsCursorOverNotificationArea){
 | 
			
		||||
                ResumeNotification();
 | 
			
		||||
                timerCursorCheck.Stop();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void timerIdlePauseCheck_Tick(object sender, EventArgs e){
 | 
			
		||||
            if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds){
 | 
			
		||||
                ResumeNotification();
 | 
			
		||||
                timerIdlePauseCheck.Stop();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // notification methods
 | 
			
		||||
 | 
			
		||||
        public override void ShowNotification(TweetNotification notification){
 | 
			
		||||
            tweetQueue.Enqueue(notification);
 | 
			
		||||
            
 | 
			
		||||
            if (!IsPaused){
 | 
			
		||||
                UpdateTitle();
 | 
			
		||||
 | 
			
		||||
                if (totalTime == 0){
 | 
			
		||||
                    LoadNextNotification();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            needsTrim |= tweetQueue.Count >= TrimMinimum;
 | 
			
		||||
            AnalyticsFile.DesktopNotifications.Trigger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void HideNotification(){
 | 
			
		||||
            base.HideNotification();
 | 
			
		||||
            tweetQueue.Clear();
 | 
			
		||||
 | 
			
		||||
            if (needsTrim){
 | 
			
		||||
                tweetQueue.TrimExcess();
 | 
			
		||||
                needsTrim = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            hasTemporarilyMoved = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void FinishCurrentNotification(){
 | 
			
		||||
            if (tweetQueue.Count > 0){
 | 
			
		||||
                LoadNextNotification();
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                HideNotification();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void ResumeNotification(){
 | 
			
		||||
            bool wasPaused = IsPaused;
 | 
			
		||||
            base.ResumeNotification();
 | 
			
		||||
 | 
			
		||||
            if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0){
 | 
			
		||||
                LoadNextNotification();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void LoadNextNotification(){
 | 
			
		||||
            if (!IsNotificationVisible){
 | 
			
		||||
                if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){
 | 
			
		||||
                    if (!timerCursorCheck.Enabled){
 | 
			
		||||
                        PauseNotification();
 | 
			
		||||
                        timerCursorCheck.Start();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds){
 | 
			
		||||
                    if (!timerIdlePauseCheck.Enabled){
 | 
			
		||||
                        PauseNotification();
 | 
			
		||||
                        timerIdlePauseCheck.Start();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            LoadTweet(tweetQueue.Dequeue());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void UpdateTitle(){
 | 
			
		||||
            base.UpdateTitle();
 | 
			
		||||
 | 
			
		||||
            if (tweetQueue.Count > 0){
 | 
			
		||||
                Text = Text+" ("+tweetQueue.Count+" more left)";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void OnNotificationReady(){
 | 
			
		||||
            UpdateTitle();
 | 
			
		||||
            base.OnNotificationReady();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,98 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Drawing.Imaging;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Resources;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification.Screenshot{
 | 
			
		||||
    sealed class FormNotificationScreenshotable : FormNotificationBase{
 | 
			
		||||
        protected override bool CanDragWindow => false;
 | 
			
		||||
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
        private int height;
 | 
			
		||||
 | 
			
		||||
        public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, false){
 | 
			
		||||
            this.plugins = pluginManager;
 | 
			
		||||
 | 
			
		||||
            int realWidth = BrowserUtils.Scale(width, DpiScale);
 | 
			
		||||
 | 
			
		||||
            browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
 | 
			
		||||
 | 
			
		||||
            browser.LoadingStateChanged += (sender, args) => {
 | 
			
		||||
                if (args.IsLoading){
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                string script = ScriptLoader.LoadResourceSilent("screenshot.js");
 | 
			
		||||
                        
 | 
			
		||||
                if (script == null){
 | 
			
		||||
                    this.InvokeAsyncSafe(callback);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                using(IFrame frame = args.Browser.MainFrame){
 | 
			
		||||
                    ScriptLoader.ExecuteScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            SetNotificationSize(realWidth, 1024);
 | 
			
		||||
            LoadTweet(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override string GetTweetHTML(TweetNotification tweet){
 | 
			
		||||
            string html = tweet.GenerateHtml("td-screenshot", this);
 | 
			
		||||
 | 
			
		||||
            foreach(InjectedHTML injection in plugins.NotificationInjections){
 | 
			
		||||
                html = injection.InjectInto(html);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return html;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SetScreenshotHeight(int browserHeight){
 | 
			
		||||
            this.height = BrowserUtils.Scale(browserHeight, SizeScale);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool TakeScreenshot(bool ignoreHeightError = false){
 | 
			
		||||
            if (!ignoreHeightError){
 | 
			
		||||
                if (height == 0){
 | 
			
		||||
                    FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                else if (height > ClientSize.Height){
 | 
			
		||||
                    FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK);
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!WindowsUtils.IsAeroEnabled){
 | 
			
		||||
                MoveToVisibleLocation(); // TODO make this look nicer I guess
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            IntPtr context = NativeMethods.GetDC(this.Handle);
 | 
			
		||||
 | 
			
		||||
            if (context == IntPtr.Zero){
 | 
			
		||||
                FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                using(Bitmap bmp = new Bitmap(ClientSize.Width, Math.Max(1, height), PixelFormat.Format32bppRgb)){
 | 
			
		||||
                    try{
 | 
			
		||||
                        NativeMethods.RenderSourceIntoBitmap(context, bmp);
 | 
			
		||||
                    }finally{
 | 
			
		||||
                        NativeMethods.ReleaseDC(this.Handle, context);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Clipboard.SetImage(bmp);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								Core/Notification/Screenshot/ScreenshotBridge.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Core/Notification/Screenshot/ScreenshotBridge.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification.Screenshot{
 | 
			
		||||
    sealed class ScreenshotBridge{
 | 
			
		||||
        private readonly Control owner;
 | 
			
		||||
 | 
			
		||||
        private readonly Action<int> safeSetHeight;
 | 
			
		||||
        private readonly Action safeTriggerScreenshot;
 | 
			
		||||
 | 
			
		||||
        public ScreenshotBridge(Control owner, Action<int> safeSetHeight, Action safeTriggerScreenshot){
 | 
			
		||||
            this.owner = owner;
 | 
			
		||||
            this.safeSetHeight = safeSetHeight;
 | 
			
		||||
            this.safeTriggerScreenshot = safeTriggerScreenshot;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void SetHeight(int tweetHeight){
 | 
			
		||||
            owner.InvokeSafe(() => safeSetHeight(tweetHeight));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void TriggerScreenshot(){
 | 
			
		||||
            owner.InvokeSafe(safeTriggerScreenshot);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										148
									
								
								Core/Notification/Screenshot/TweetScreenshotManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								Core/Notification/Screenshot/TweetScreenshotManager.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,148 @@
 | 
			
		||||
#if DEBUG
 | 
			
		||||
// Uncomment to keep screenshot windows visible for debugging
 | 
			
		||||
// #define NO_HIDE_SCREENSHOTS
 | 
			
		||||
 | 
			
		||||
// Uncomment to generate screenshots of individual frames for at most 1 second
 | 
			
		||||
// #define GEN_SCREENSHOT_FRAMES
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
 | 
			
		||||
#if GEN_SCREENSHOT_FRAMES
 | 
			
		||||
using System.Drawing.Imaging;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification.Screenshot{
 | 
			
		||||
    sealed class TweetScreenshotManager : IDisposable{
 | 
			
		||||
        private readonly FormBrowser owner;
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
        private readonly Timer timeout;
 | 
			
		||||
        private readonly Timer disposer;
 | 
			
		||||
 | 
			
		||||
        #if GEN_SCREENSHOT_FRAMES
 | 
			
		||||
        private readonly Timer debugger;
 | 
			
		||||
        private int frameCounter;
 | 
			
		||||
 | 
			
		||||
        public const int WaitFrames = 60;
 | 
			
		||||
        #else
 | 
			
		||||
        public const int WaitFrames = 5;
 | 
			
		||||
        #endif
 | 
			
		||||
        
 | 
			
		||||
        private FormNotificationScreenshotable screenshot;
 | 
			
		||||
 | 
			
		||||
        public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){
 | 
			
		||||
            this.owner = owner;
 | 
			
		||||
            this.plugins = pluginManager;
 | 
			
		||||
 | 
			
		||||
            this.timeout = new Timer{ Interval = 8000 };
 | 
			
		||||
            this.timeout.Tick += timeout_Tick;
 | 
			
		||||
 | 
			
		||||
            this.disposer = new Timer{ Interval = 1 };
 | 
			
		||||
            this.disposer.Tick += disposer_Tick;
 | 
			
		||||
 | 
			
		||||
            #if GEN_SCREENSHOT_FRAMES
 | 
			
		||||
            this.debugger = new Timer{ Interval = 16 };
 | 
			
		||||
            this.debugger.Tick += debugger_Tick;
 | 
			
		||||
            #endif
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void timeout_Tick(object sender, EventArgs e){
 | 
			
		||||
            timeout.Stop();
 | 
			
		||||
            OnFinished();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void disposer_Tick(object sender, EventArgs e){
 | 
			
		||||
            disposer.Stop();
 | 
			
		||||
            screenshot.Dispose();
 | 
			
		||||
            screenshot = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Trigger(string html, int width){
 | 
			
		||||
            if (screenshot != null){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            screenshot = new FormNotificationScreenshotable(Callback, owner, plugins, html, width);
 | 
			
		||||
            screenshot.Show();
 | 
			
		||||
            timeout.Start();
 | 
			
		||||
 | 
			
		||||
            #if GEN_SCREENSHOT_FRAMES
 | 
			
		||||
            StartDebugger();
 | 
			
		||||
            #endif
 | 
			
		||||
 | 
			
		||||
            #if !NO_HIDE_SCREENSHOTS
 | 
			
		||||
            owner.IsWaiting = true;
 | 
			
		||||
            #endif
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Callback(){
 | 
			
		||||
            if (!timeout.Enabled){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            timeout.Stop();
 | 
			
		||||
            screenshot.TakeScreenshot();
 | 
			
		||||
            
 | 
			
		||||
            #if !NO_HIDE_SCREENSHOTS
 | 
			
		||||
            OnFinished();
 | 
			
		||||
            #else
 | 
			
		||||
            screenshot.MoveToVisibleLocation();
 | 
			
		||||
            screenshot.FormClosed += (sender, args) => disposer.Start();
 | 
			
		||||
            #endif
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void OnFinished(){
 | 
			
		||||
            #if GEN_SCREENSHOT_FRAMES
 | 
			
		||||
            debugger.Stop();
 | 
			
		||||
            #endif
 | 
			
		||||
 | 
			
		||||
            screenshot.Location = ControlExtensions.InvisibleLocation;
 | 
			
		||||
            owner.IsWaiting = false;
 | 
			
		||||
            disposer.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose(){
 | 
			
		||||
            #if GEN_SCREENSHOT_FRAMES
 | 
			
		||||
            debugger.Dispose();
 | 
			
		||||
            #endif
 | 
			
		||||
 | 
			
		||||
            timeout.Dispose();
 | 
			
		||||
            disposer.Dispose();
 | 
			
		||||
            screenshot?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #if GEN_SCREENSHOT_FRAMES
 | 
			
		||||
        private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots");
 | 
			
		||||
 | 
			
		||||
        private void StartDebugger(){
 | 
			
		||||
            frameCounter = 0;
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                Directory.Delete(DebugScreenshotPath, true);
 | 
			
		||||
                WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10);
 | 
			
		||||
            }catch(DirectoryNotFoundException){}
 | 
			
		||||
            
 | 
			
		||||
            Directory.CreateDirectory(DebugScreenshotPath);
 | 
			
		||||
            debugger.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void debugger_Tick(object sender, EventArgs e){
 | 
			
		||||
            if (frameCounter < 63 && screenshot.TakeScreenshot(true)){
 | 
			
		||||
                try{
 | 
			
		||||
                    Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_"+(++frameCounter)+".png"), ImageFormat.Png);
 | 
			
		||||
                }catch{
 | 
			
		||||
                    System.Diagnostics.Debug.WriteLine("Failed generating frame "+frameCounter);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                debugger.Stop();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        #endif
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								Core/Notification/SoundNotification.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								Core/Notification/SoundNotification.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Other;
 | 
			
		||||
using TweetDuck.Core.Other.Settings;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification{
 | 
			
		||||
    static class SoundNotification{
 | 
			
		||||
        public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
 | 
			
		||||
        
 | 
			
		||||
        public static IResourceHandler CreateFileHandler(string path){
 | 
			
		||||
            string mimeType;
 | 
			
		||||
 | 
			
		||||
            switch(Path.GetExtension(path)){
 | 
			
		||||
                case ".weba":
 | 
			
		||||
                case ".webm": mimeType = "audio/webm"; break;
 | 
			
		||||
                case ".wav": mimeType = "audio/wav"; break;
 | 
			
		||||
                case ".ogg": mimeType = "audio/ogg"; break;
 | 
			
		||||
                case ".mp3": mimeType = "audio/mp3"; break;
 | 
			
		||||
                case ".flac": mimeType = "audio/flac"; break;
 | 
			
		||||
                case ".opus": mimeType = "audio/ogg; codecs=opus"; break;
 | 
			
		||||
                default: mimeType = null; break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                return ResourceHandler.FromFilePath(path, mimeType);
 | 
			
		||||
            }catch{
 | 
			
		||||
                FormBrowser browser = FormManager.TryFind<FormBrowser>();
 | 
			
		||||
 | 
			
		||||
                browser?.InvokeAsyncSafe(() => {
 | 
			
		||||
                    using(FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n"+path, MessageBoxIcon.Error)){
 | 
			
		||||
                        form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
 | 
			
		||||
                        
 | 
			
		||||
                        Button btnViewOptions = form.AddButton("View Options");
 | 
			
		||||
                        btnViewOptions.Width += 16;
 | 
			
		||||
                        btnViewOptions.Location = new Point(btnViewOptions.Location.X-16, btnViewOptions.Location.Y);
 | 
			
		||||
 | 
			
		||||
                        if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){
 | 
			
		||||
                            browser.OpenSettings(typeof(TabSettingsSounds));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										74
									
								
								Core/Notification/TweetNotification.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								Core/Notification/TweetNotification.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using TweetDuck.Core.Bridge;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
using TweetDuck.Resources;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Notification{
 | 
			
		||||
    sealed class TweetNotification{
 | 
			
		||||
        private const string DefaultHeadLayout = @"<html class=""scroll-v os-windows dark txt-size--14"" lang=""en-US"" id=""tduck"" data-td-font=""medium"" data-td-theme=""dark""><head><meta charset=""utf-8""><link href=""https://ton.twimg.com/tweetdeck-web/web/dist/bundle.4b1f87e09d.css"" rel=""stylesheet""><style type='text/css'>body { background: rgb(34, 36, 38) !important }</style>";
 | 
			
		||||
        public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
 | 
			
		||||
        
 | 
			
		||||
        public enum Position{
 | 
			
		||||
            TopLeft, TopRight, BottomLeft, BottomRight, Custom
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public enum Size{
 | 
			
		||||
            Auto, Custom
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string ColumnId { get; }
 | 
			
		||||
        public string ChirpId { get; }
 | 
			
		||||
 | 
			
		||||
        public string ColumnTitle { get; }
 | 
			
		||||
        public string TweetUrl { get; }
 | 
			
		||||
        public string QuoteUrl { get; }
 | 
			
		||||
        
 | 
			
		||||
        private readonly string html;
 | 
			
		||||
        private readonly int characters;
 | 
			
		||||
        
 | 
			
		||||
        public TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl){
 | 
			
		||||
            this.ColumnId = columnId;
 | 
			
		||||
            this.ChirpId = chirpId;
 | 
			
		||||
 | 
			
		||||
            this.ColumnTitle = title;
 | 
			
		||||
            this.TweetUrl = tweetUrl;
 | 
			
		||||
            this.QuoteUrl = quoteUrl;
 | 
			
		||||
 | 
			
		||||
            this.html = html;
 | 
			
		||||
            this.characters = characters;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int GetDisplayDuration(int value){
 | 
			
		||||
            return 2000+Math.Max(1000, value*characters);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GenerateHtml(string bodyClasses, Control sync){
 | 
			
		||||
            string headLayout = TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout;
 | 
			
		||||
            string mainCSS = ScriptLoader.LoadResource("styles/notification.css", sync) ?? string.Empty;
 | 
			
		||||
            string customCSS = Program.Config.User.CustomNotificationCSS ?? string.Empty;
 | 
			
		||||
            
 | 
			
		||||
            StringBuilder build = new StringBuilder(320 + headLayout.Length + mainCSS.Length + customCSS.Length + html.Length);
 | 
			
		||||
            build.Append("<!DOCTYPE html>");
 | 
			
		||||
            build.Append(headLayout);
 | 
			
		||||
            build.Append("<style type='text/css'>").Append(mainCSS).Append("</style>");
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(customCSS)){
 | 
			
		||||
                build.Append("<style type='text/css'>").Append(customCSS).Append("</style>");
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            build.Append("</head><body class='scroll-styled-v");
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrEmpty(bodyClasses)){
 | 
			
		||||
                build.Append(' ').Append(bodyClasses);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            build.Append("'><div class='column' style='width:100%!important;min-height:100vh!important;height:auto!important;overflow:initial!important;'>");
 | 
			
		||||
            build.Append(html);
 | 
			
		||||
            build.Append("</div></body></html>");
 | 
			
		||||
            return build.ToString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										135
									
								
								Core/Other/Analytics/AnalyticsFile.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								Core/Other/Analytics/AnalyticsFile.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using TweetDuck.Data.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Analytics{
 | 
			
		||||
    [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
 | 
			
		||||
    sealed class AnalyticsFile{
 | 
			
		||||
        private static readonly FileSerializer<AnalyticsFile> Serializer = new FileSerializer<AnalyticsFile>();
 | 
			
		||||
 | 
			
		||||
        static AnalyticsFile(){
 | 
			
		||||
            Serializer.RegisterTypeConverter(typeof(DateTime), new SingleTypeConverter<DateTime>{
 | 
			
		||||
                ConvertToString = value => value.ToBinary().ToString(),
 | 
			
		||||
                ConvertToObject = value => DateTime.FromBinary(long.Parse(value))
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            Serializer.RegisterTypeConverter(typeof(Counter), new SingleTypeConverter<Counter>{
 | 
			
		||||
                ConvertToString = value => value.Value.ToString(),
 | 
			
		||||
                ConvertToObject = value => int.Parse(value)
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static readonly AnalyticsFile Dummy = new AnalyticsFile();
 | 
			
		||||
 | 
			
		||||
        // STATE PROPERTIES
 | 
			
		||||
        
 | 
			
		||||
        public DateTime LastDataCollection  { get; set; } = DateTime.MinValue;
 | 
			
		||||
        public string LastCollectionVersion { get; set; } = null;
 | 
			
		||||
        public string LastCollectionMessage { get; set; } = null;
 | 
			
		||||
 | 
			
		||||
        // USAGE DATA
 | 
			
		||||
 | 
			
		||||
        public Counter OpenOptions { get; private set; } = 0;
 | 
			
		||||
        public Counter OpenPlugins { get; private set; } = 0;
 | 
			
		||||
        public Counter OpenAbout   { get; private set; } = 0;
 | 
			
		||||
        public Counter OpenGuide   { get; private set; } = 0;
 | 
			
		||||
 | 
			
		||||
        public Counter DesktopNotifications { get; private set; } = 0;
 | 
			
		||||
        public Counter SoundNotifications   { get; private set; } = 0;
 | 
			
		||||
        public Counter NotificationMutes    { get; private set; } = 0;
 | 
			
		||||
        
 | 
			
		||||
        public Counter BrowserContextMenus           { get; private set; } = 0;
 | 
			
		||||
        public Counter BrowserExtraMouseButtons      { get; private set; } = 0;
 | 
			
		||||
        public Counter NotificationContextMenus      { get; private set; } = 0;
 | 
			
		||||
        public Counter NotificationExtraMouseButtons { get; private set; } = 0;
 | 
			
		||||
        public Counter NotificationKeyboardShortcuts { get; private set; } = 0;
 | 
			
		||||
 | 
			
		||||
        public Counter BrowserReloads   { get; private set; } = 0;
 | 
			
		||||
        public Counter CopiedUsernames  { get; private set; } = 0;
 | 
			
		||||
        public Counter ViewedImages     { get; private set; } = 0;
 | 
			
		||||
        public Counter DownloadedImages { get; private set; } = 0;
 | 
			
		||||
        public Counter DownloadedVideos { get; private set; } = 0;
 | 
			
		||||
        public Counter UsedROT13        { get; private set; } = 0;
 | 
			
		||||
 | 
			
		||||
        public Counter TweetScreenshots { get; private set; } = 0;
 | 
			
		||||
        public Counter TweetDetails     { get; private set; } = 0;
 | 
			
		||||
        public Counter VideoPlays       { get; private set; } = 0;
 | 
			
		||||
 | 
			
		||||
        // END OF DATA
 | 
			
		||||
 | 
			
		||||
        public event EventHandler PropertyChanged;
 | 
			
		||||
        
 | 
			
		||||
        private readonly string file;
 | 
			
		||||
        
 | 
			
		||||
        private AnalyticsFile(string file){
 | 
			
		||||
            this.file = file;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private AnalyticsFile(){
 | 
			
		||||
            this.file = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SetupProperties(){
 | 
			
		||||
            foreach(Counter counter in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.PropertyType == typeof(Counter)).Select(prop => (Counter)prop.GetValue(this))){
 | 
			
		||||
                counter.SetOwner(this);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnPropertyChanged(){
 | 
			
		||||
            PropertyChanged?.Invoke(this, EventArgs.Empty);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Save(){
 | 
			
		||||
            if (file == null){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                Serializer.Write(file, this);
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Analytics File Error", "Could not save the analytics file.", true, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static AnalyticsFile Load(string file){
 | 
			
		||||
            AnalyticsFile config = new AnalyticsFile(file);
 | 
			
		||||
            
 | 
			
		||||
            try{
 | 
			
		||||
                Serializer.ReadIfExists(file, config);
 | 
			
		||||
            }catch(Exception e){
 | 
			
		||||
                Program.Reporter.HandleException("Analytics File Error", "Could not open the analytics file.", true, e);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            config.SetupProperties();
 | 
			
		||||
            return config;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public interface IProvider{
 | 
			
		||||
            AnalyticsFile AnalyticsFile { get; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public sealed class Counter{
 | 
			
		||||
            public int Value { get; private set; }
 | 
			
		||||
 | 
			
		||||
            private AnalyticsFile owner;
 | 
			
		||||
 | 
			
		||||
            public Counter(int value){
 | 
			
		||||
                this.Value = value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void SetOwner(AnalyticsFile owner){
 | 
			
		||||
                this.owner = owner;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void Trigger(){
 | 
			
		||||
                ++Value;
 | 
			
		||||
                owner?.OnPropertyChanged();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static implicit operator int(Counter counter) => counter.Value;
 | 
			
		||||
            public static implicit operator Counter(int value) => new Counter(value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										158
									
								
								Core/Other/Analytics/AnalyticsManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								Core/Other/Analytics/AnalyticsManager.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
			
		||||
// Uncomment to debug reports locally
 | 
			
		||||
// #define ANALYTICS_LOCALHOST
 | 
			
		||||
// #define ANALYTICS_INSTANT
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Timers;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Analytics{
 | 
			
		||||
    sealed class AnalyticsManager : IDisposable{
 | 
			
		||||
        private static readonly TimeSpan CollectionInterval = TimeSpan.FromDays(14);
 | 
			
		||||
 | 
			
		||||
        private static readonly Uri CollectionUrl = new Uri(
 | 
			
		||||
            #if (DEBUG && ANALYTICS_LOCALHOST)
 | 
			
		||||
            "http://localhost/newhome/tweetduck/~breadcrumb/request.php?type=report"
 | 
			
		||||
            #else
 | 
			
		||||
            "https://tweetduck.chylex.com/breadcrumb/report"
 | 
			
		||||
            #endif
 | 
			
		||||
            );
 | 
			
		||||
        
 | 
			
		||||
        public AnalyticsFile File { get; }
 | 
			
		||||
 | 
			
		||||
        private readonly FormBrowser browser;
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
        private readonly Timer currentTimer, saveTimer;
 | 
			
		||||
 | 
			
		||||
        public AnalyticsManager(FormBrowser browser, PluginManager plugins, string file){
 | 
			
		||||
            this.browser = browser;
 | 
			
		||||
            this.plugins = plugins;
 | 
			
		||||
 | 
			
		||||
            this.File = AnalyticsFile.Load(file);
 | 
			
		||||
            this.File.PropertyChanged += File_PropertyChanged;
 | 
			
		||||
 | 
			
		||||
            this.currentTimer = new Timer{ SynchronizingObject = browser };
 | 
			
		||||
            this.currentTimer.Elapsed += currentTimer_Elapsed;
 | 
			
		||||
 | 
			
		||||
            this.saveTimer = new Timer{ SynchronizingObject = browser, Interval = 60000 };
 | 
			
		||||
            this.saveTimer.Elapsed += saveTimer_Elapsed;
 | 
			
		||||
 | 
			
		||||
            if (this.File.LastCollectionVersion != Program.VersionTag){
 | 
			
		||||
                ScheduleReportIn(TimeSpan.FromHours(8), string.Empty);
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                RestartTimer();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            #if (DEBUG && ANALYTICS_INSTANT)
 | 
			
		||||
            SendReport();
 | 
			
		||||
            #endif
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose(){
 | 
			
		||||
            File.PropertyChanged -= File_PropertyChanged;
 | 
			
		||||
 | 
			
		||||
            if (saveTimer.Enabled){
 | 
			
		||||
                File.Save();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            currentTimer.Dispose();
 | 
			
		||||
            saveTimer.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void File_PropertyChanged(object sender, EventArgs e){
 | 
			
		||||
            saveTimer.Enabled = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void saveTimer_Elapsed(object sender, ElapsedEventArgs e){
 | 
			
		||||
            saveTimer.Stop();
 | 
			
		||||
            File.Save();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ScheduleReportIn(TimeSpan delay, string message = null){
 | 
			
		||||
            SetLastDataCollectionTime(DateTime.Now.Subtract(CollectionInterval).Add(delay), message);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SetLastDataCollectionTime(DateTime dt, string message = null){
 | 
			
		||||
            File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
 | 
			
		||||
            File.LastCollectionVersion = Program.VersionTag;
 | 
			
		||||
            File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture);
 | 
			
		||||
 | 
			
		||||
            File.Save();
 | 
			
		||||
            RestartTimer();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void RestartTimer(){
 | 
			
		||||
            TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
 | 
			
		||||
            int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes));
 | 
			
		||||
            
 | 
			
		||||
            currentTimer.Interval = Math.Max(minutesTillNext, 2)*60000;
 | 
			
		||||
            currentTimer.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void currentTimer_Elapsed(object sender, ElapsedEventArgs e){
 | 
			
		||||
            currentTimer.Stop();
 | 
			
		||||
 | 
			
		||||
            TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
 | 
			
		||||
            
 | 
			
		||||
            if (Math.Floor(diff.TotalMinutes) >= CollectionInterval.TotalMinutes){
 | 
			
		||||
                SendReport();
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                RestartTimer();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private void SendReport(){
 | 
			
		||||
            AnalyticsReportGenerator.ExternalInfo info = AnalyticsReportGenerator.ExternalInfo.From(browser);
 | 
			
		||||
 | 
			
		||||
            Task.Factory.StartNew(() => {
 | 
			
		||||
                AnalyticsReport report = AnalyticsReportGenerator.Create(File, info, plugins);
 | 
			
		||||
 | 
			
		||||
                #if (DEBUG && !ANALYTICS_INSTANT)
 | 
			
		||||
                System.Diagnostics.Debugger.Break();
 | 
			
		||||
                #endif
 | 
			
		||||
 | 
			
		||||
                BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
 | 
			
		||||
            }).ContinueWith(task => browser.InvokeAsyncSafe(() => {
 | 
			
		||||
                if (task.Status == TaskStatus.RanToCompletion){
 | 
			
		||||
                    SetLastDataCollectionTime(DateTime.Now);
 | 
			
		||||
                }
 | 
			
		||||
                else if (task.Exception != null){
 | 
			
		||||
                    string message = null;
 | 
			
		||||
 | 
			
		||||
                    if (task.Exception.InnerException is WebException e){
 | 
			
		||||
                        switch(e.Status){
 | 
			
		||||
                            case WebExceptionStatus.ConnectFailure:
 | 
			
		||||
                                message = "Connection Error";
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case WebExceptionStatus.NameResolutionFailure:
 | 
			
		||||
                                message = "DNS Error";
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case WebExceptionStatus.ProtocolError:
 | 
			
		||||
                                HttpWebResponse response = e.Response as HttpWebResponse;
 | 
			
		||||
                                message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        #if DEBUG
 | 
			
		||||
                        System.IO.Stream responseStream = e.Response.GetResponseStream();
 | 
			
		||||
 | 
			
		||||
                        if (responseStream != null){
 | 
			
		||||
                            System.Diagnostics.Debug.WriteLine(new System.IO.StreamReader(responseStream).ReadToEnd());
 | 
			
		||||
                        }
 | 
			
		||||
                        #endif
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message));
 | 
			
		||||
                }
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								Core/Other/Analytics/AnalyticsReport.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								Core/Other/Analytics/AnalyticsReport.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Specialized;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Analytics{
 | 
			
		||||
    sealed class AnalyticsReport : IEnumerable{
 | 
			
		||||
        private OrderedDictionary data = new OrderedDictionary(32);
 | 
			
		||||
        private int separators;
 | 
			
		||||
 | 
			
		||||
        public void Add(int ignored){ // adding separators to pretty print
 | 
			
		||||
            data.Add((++separators).ToString(), null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Add(string key, string value){
 | 
			
		||||
            data.Add(key, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public AnalyticsReport FinalizeReport(){
 | 
			
		||||
            if (!data.IsReadOnly){
 | 
			
		||||
                data = data.AsReadOnly();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerator GetEnumerator(){
 | 
			
		||||
            return data.GetEnumerator();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public NameValueCollection ToNameValueCollection(){
 | 
			
		||||
            NameValueCollection collection = new NameValueCollection();
 | 
			
		||||
 | 
			
		||||
            foreach(DictionaryEntry entry in data){
 | 
			
		||||
                if (entry.Value != null){
 | 
			
		||||
                    collection.Add(((string)entry.Key).ToLower().Replace(' ', '_'), (string)entry.Value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return collection;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ToString(){
 | 
			
		||||
            StringBuilder build = new StringBuilder(625);
 | 
			
		||||
 | 
			
		||||
            foreach(DictionaryEntry entry in data){
 | 
			
		||||
                if (entry.Value == null){
 | 
			
		||||
                    build.AppendLine();
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    build.AppendLine(entry.Key+": "+entry.Value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return build.ToString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										325
									
								
								Core/Other/Analytics/AnalyticsReportGenerator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								Core/Other/Analytics/AnalyticsReportGenerator.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,325 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using Microsoft.Win32;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Management;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using TweetDuck.Core.Notification;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Plugins.Enums;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Analytics{
 | 
			
		||||
    static class AnalyticsReportGenerator{
 | 
			
		||||
        public static AnalyticsReport Create(AnalyticsFile file, ExternalInfo info, PluginManager plugins){
 | 
			
		||||
            Dictionary<string, string> editLayoutDesign = EditLayoutDesignPluginData;
 | 
			
		||||
 | 
			
		||||
            return new AnalyticsReport{
 | 
			
		||||
                { "App Version"   , Program.VersionTag },
 | 
			
		||||
                { "App Type"      , Program.IsPortable ? "portable" : "installed" },
 | 
			
		||||
                { "App Dev Tools" , Bool(BrowserUtils.HasDevTools) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "System Name"        , SystemName },
 | 
			
		||||
                { "System Edition"     , SystemEdition },
 | 
			
		||||
                { "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
 | 
			
		||||
                { "System Build"       , SystemBuild },
 | 
			
		||||
                { "System Locale"      , Program.Culture.Name.ToLower() },
 | 
			
		||||
                0,
 | 
			
		||||
                { "RAM" , Exact(RamSize) },
 | 
			
		||||
                { "GPU" , GpuVendor },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Screen Count"      , Exact(Screen.AllScreens.Length) },
 | 
			
		||||
                { "Screen Resolution" , info.Resolution ?? "(unknown)" },
 | 
			
		||||
                { "Screen DPI"        , info.DPI != null ? Exact(info.DPI.Value) : "(unknown)" },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Hardware Acceleration"     , Bool(SysConfig.HardwareAcceleration) },
 | 
			
		||||
                { "Clear Cache Automatically" , Bool(SysConfig.ClearCacheAutomatically) },
 | 
			
		||||
                { "Clear Cache Threshold"     , Exact(SysConfig.ClearCacheThreshold) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Expand Links"                  , Bool(UserConfig.ExpandLinksOnHover) },
 | 
			
		||||
                { "Search In First Column"        , Bool(UserConfig.OpenSearchInFirstColumn) },
 | 
			
		||||
                { "Keep Like Follow Dialogs Open" , Bool(UserConfig.KeepLikeFollowDialogsOpen) },
 | 
			
		||||
                { "Best Image Quality"            , Bool(UserConfig.BestImageQuality) },
 | 
			
		||||
                { "Animated Images"               , Bool(UserConfig.EnableAnimatedImages) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Smooth Scrolling" , Bool(UserConfig.EnableSmoothScrolling) },
 | 
			
		||||
                { "Custom Browser"   , CustomBrowser },
 | 
			
		||||
                { "Zoom"             , Exact(UserConfig.ZoomLevel) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Spell Check"                 , Bool(UserConfig.EnableSpellCheck) },
 | 
			
		||||
                { "Spell Check Language"        , UserConfig.SpellCheckLanguage.ToLower() },
 | 
			
		||||
                { "Translation Target Language" , UserConfig.TranslationTarget },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Updates"          , Bool(UserConfig.EnableUpdateCheck) },
 | 
			
		||||
                { "Update Dismissed" , Bool(!string.IsNullOrEmpty(UserConfig.DismissedUpdate)) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Tray"           , TrayMode },
 | 
			
		||||
                { "Tray Highlight" , Bool(UserConfig.EnableTrayHighlight) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Notification Position"            , NotificationPosition },
 | 
			
		||||
                { "Notification Size"                , NotificationSize },
 | 
			
		||||
                { "Notification Timer"               , NotificationTimer },
 | 
			
		||||
                { "Notification Timer Speed"         , RoundUp(UserConfig.NotificationDurationValue, 5) },
 | 
			
		||||
                { "Notification Scroll Speed"        , Exact(UserConfig.NotificationScrollSpeed) },
 | 
			
		||||
                { "Notification Column Title"        , Bool(UserConfig.DisplayNotificationColumn) },
 | 
			
		||||
                { "Notification Media Previews"      , Bool(UserConfig.NotificationMediaPreviews) },
 | 
			
		||||
                { "Notification Link Skip"           , Bool(UserConfig.NotificationSkipOnLinkClick) },
 | 
			
		||||
                { "Notification Non-Intrusive"       , Bool(UserConfig.NotificationNonIntrusiveMode) },
 | 
			
		||||
                { "Notification Idle Pause"          , Exact(UserConfig.NotificationIdlePauseSeconds) },
 | 
			
		||||
                { "Custom Sound Notification"        , string.IsNullOrEmpty(UserConfig.NotificationSoundPath) ? "off" : Path.GetExtension(UserConfig.NotificationSoundPath) },
 | 
			
		||||
                { "Custom Sound Notification Volume" , RoundUp(UserConfig.NotificationSoundVolume, 5) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Program Arguments"       , List(ProgramArguments) },
 | 
			
		||||
                { "Custom CEF Arguments"    , RoundUp((UserConfig.CustomCefArgs ?? string.Empty).Length, 10) },
 | 
			
		||||
                { "Custom Browser CSS"      , RoundUp((UserConfig.CustomBrowserCSS ?? string.Empty).Length, 50) },
 | 
			
		||||
                { "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Plugins All"     , List(plugins.Plugins.Select(Plugin)) },
 | 
			
		||||
                { "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(Plugin)) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Theme"               , Dict(editLayoutDesign, "_theme",                  "light/def") },
 | 
			
		||||
                { "Column Width"        , Dict(editLayoutDesign, "columnWidth",             "310px/def") },
 | 
			
		||||
                { "Font Size"           , Dict(editLayoutDesign, "fontSize",                "12px/def") },
 | 
			
		||||
                { "Large Quote Font"    , Dict(editLayoutDesign, "increaseQuoteTextSize",   "false/def") },
 | 
			
		||||
                { "Small Compose Font"  , Dict(editLayoutDesign, "smallComposeTextSize",    "false/def") },
 | 
			
		||||
                { "Avatar Radius"       , Dict(editLayoutDesign, "avatarRadius",            "2/def") },
 | 
			
		||||
                { "Hide Tweet Actions"  , Dict(editLayoutDesign, "hideTweetActions",        "true/def") },
 | 
			
		||||
                { "Move Tweet Actions"  , Dict(editLayoutDesign, "moveTweetActionsToRight", "true/def") },
 | 
			
		||||
                { "Theme Color Tweaks"  , Dict(editLayoutDesign, "themeColorTweaks",        "true/def") },
 | 
			
		||||
                { "Revert Icons"        , Dict(editLayoutDesign, "revertIcons",             "true/def") },
 | 
			
		||||
                { "Optimize Animations" , Dict(editLayoutDesign, "optimizeAnimations",      "true/def") },
 | 
			
		||||
                { "Reply Account Mode"  , ReplyAccountConfigFromPlugin },
 | 
			
		||||
                { "Template Count"      , Exact(TemplateCountFromPlugin) },
 | 
			
		||||
                0,
 | 
			
		||||
                { "Opened Options"                   , LogRound(file.OpenOptions, 4) },
 | 
			
		||||
                { "Opened Plugins"                   , LogRound(file.OpenPlugins, 4) },
 | 
			
		||||
                { "Opened About"                     , LogRound(file.OpenAbout, 4) },
 | 
			
		||||
                { "Opened Guide"                     , LogRound(file.OpenGuide, 4) },
 | 
			
		||||
                { "Desktop Notifications"            , LogRound(file.DesktopNotifications, 5) },
 | 
			
		||||
                { "Sound Notifications"              , LogRound(file.SoundNotifications, 5) },
 | 
			
		||||
                { "Notification Mutes"               , LogRound(file.NotificationMutes, 2) },
 | 
			
		||||
                { "Browser Context Menus"            , LogRound(file.BrowserContextMenus, 2) },
 | 
			
		||||
                { "Browser Extra Mouse Buttons"      , LogRound(file.BrowserExtraMouseButtons, 2) },
 | 
			
		||||
                { "Notification Context Menus"       , LogRound(file.NotificationContextMenus, 2) },
 | 
			
		||||
                { "Notification Extra Mouse Buttons" , LogRound(file.NotificationExtraMouseButtons, 2) },
 | 
			
		||||
                { "Notification Keyboard Shortcuts"  , LogRound(file.NotificationKeyboardShortcuts, 2) },
 | 
			
		||||
                { "Browser Reloads"                  , LogRound(file.BrowserReloads, 2) },
 | 
			
		||||
                { "Copied Usernames"                 , LogRound(file.CopiedUsernames, 2) },
 | 
			
		||||
                { "Viewed Images"                    , LogRound(file.ViewedImages, 2) },
 | 
			
		||||
                { "Downloaded Images"                , LogRound(file.DownloadedImages, 2) },
 | 
			
		||||
                { "Downloaded Videos"                , LogRound(file.DownloadedVideos, 2) },
 | 
			
		||||
                { "Used ROT13"                       , LogRound(file.UsedROT13, 2) },
 | 
			
		||||
                { "Tweet Screenshots"                , LogRound(file.TweetScreenshots, 2) },
 | 
			
		||||
                { "Tweet Details"                    , LogRound(file.TweetDetails, 2) },
 | 
			
		||||
                { "Video Plays"                      , LogRound(file.VideoPlays, 4) }
 | 
			
		||||
            }.FinalizeReport();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static UserConfig UserConfig => Program.Config.User;
 | 
			
		||||
        private static SystemConfig SysConfig => Program.Config.System;
 | 
			
		||||
 | 
			
		||||
        private static string Bool(bool value) => value ? "on" : "off";
 | 
			
		||||
        private static string Exact(int value) => value.ToString();
 | 
			
		||||
        private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString();
 | 
			
		||||
        private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
 | 
			
		||||
        private static string Plugin(Plugin plugin) => plugin.Group.GetIdentifierPrefixShort()+plugin.Identifier.Substring(plugin.Group.GetIdentifierPrefix().Length);
 | 
			
		||||
        private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
 | 
			
		||||
        private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
 | 
			
		||||
 | 
			
		||||
        private static string SystemName { get; }
 | 
			
		||||
        private static string SystemEdition { get; }
 | 
			
		||||
        private static string SystemBuild { get; }
 | 
			
		||||
        private static int RamSize { get; }
 | 
			
		||||
        private static string GpuVendor { get; }
 | 
			
		||||
        private static string[] ProgramArguments { get; }
 | 
			
		||||
 | 
			
		||||
        static AnalyticsReportGenerator(){
 | 
			
		||||
            string osName, osEdition, osBuild;
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                using(RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false)){
 | 
			
		||||
                    // ReSharper disable once PossibleNullReferenceException
 | 
			
		||||
                    osName = key.GetValue("ProductName") as string;
 | 
			
		||||
                    osBuild = key.GetValue("CurrentBuild") as string;
 | 
			
		||||
                    osEdition = null;
 | 
			
		||||
                    
 | 
			
		||||
                    if (osName != null){
 | 
			
		||||
                        Match match = Regex.Match(osName, @"^(.*?\d+(?:\.\d+)?) (.*)$");
 | 
			
		||||
 | 
			
		||||
                        if (match.Success){
 | 
			
		||||
                            osName = match.Groups[1].Value;
 | 
			
		||||
                            osEdition = match.Groups[2].Value;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }catch{
 | 
			
		||||
                osName = osEdition = osBuild = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SystemName = osName ?? "Windows (unknown)";
 | 
			
		||||
            SystemEdition = osEdition ?? "(unknown)";
 | 
			
		||||
            SystemBuild = osBuild ?? "(unknown)";
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory")){
 | 
			
		||||
                    foreach(ManagementBaseObject obj in searcher.Get()){
 | 
			
		||||
                        RamSize += (int)((ulong)obj["Capacity"]/(1024L*1024L));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }catch{
 | 
			
		||||
                RamSize = 0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            string gpu = null;
 | 
			
		||||
 | 
			
		||||
            try{
 | 
			
		||||
                using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController")){
 | 
			
		||||
                    foreach(ManagementBaseObject obj in searcher.Get()){
 | 
			
		||||
                        string vendor = obj["Caption"] as string;
 | 
			
		||||
 | 
			
		||||
                        if (!string.IsNullOrEmpty(vendor)){
 | 
			
		||||
                            gpu = vendor;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }catch{
 | 
			
		||||
                // rip
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            GpuVendor = gpu ?? "(unknown)";
 | 
			
		||||
 | 
			
		||||
            Dictionary<string, string> args = new Dictionary<string, string>();
 | 
			
		||||
            Arguments.GetCurrentClean().ToDictionary(args);
 | 
			
		||||
            ProgramArguments = args.Keys.Select(key => key.TrimStart('-')).ToArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string CustomBrowser{
 | 
			
		||||
            get{
 | 
			
		||||
                return Path.GetFileName(UserConfig.BrowserPath) ?? string.Empty;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string TrayMode{
 | 
			
		||||
            get{
 | 
			
		||||
                switch(UserConfig.TrayBehavior){
 | 
			
		||||
                    case TrayIcon.Behavior.DisplayOnly: return "icon";
 | 
			
		||||
                    case TrayIcon.Behavior.MinimizeToTray: return "minimize";
 | 
			
		||||
                    case TrayIcon.Behavior.CloseToTray: return "close";
 | 
			
		||||
                    case TrayIcon.Behavior.Combined: return "combined";
 | 
			
		||||
                    default: return "off";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string NotificationPosition{
 | 
			
		||||
            get{
 | 
			
		||||
                switch(UserConfig.NotificationPosition){
 | 
			
		||||
                    case TweetNotification.Position.TopLeft: return "top left";
 | 
			
		||||
                    case TweetNotification.Position.TopRight: return "top right";
 | 
			
		||||
                    case TweetNotification.Position.BottomLeft: return "bottom left";
 | 
			
		||||
                    case TweetNotification.Position.BottomRight: return "bottom right";
 | 
			
		||||
                    default: return "custom";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string NotificationSize{
 | 
			
		||||
            get{
 | 
			
		||||
                switch(UserConfig.NotificationSize){
 | 
			
		||||
                    case TweetNotification.Size.Auto: return "auto";
 | 
			
		||||
                    default: return RoundUp(UserConfig.CustomNotificationSize.Width, 20)+"x"+RoundUp(UserConfig.CustomNotificationSize.Height, 20);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string NotificationTimer{
 | 
			
		||||
            get{
 | 
			
		||||
                if (!UserConfig.DisplayNotificationTimer){
 | 
			
		||||
                    return "off";
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    return UserConfig.NotificationTimerCountDown ? "count down" : "count up";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Dictionary<string, string> EditLayoutDesignPluginData{
 | 
			
		||||
            get{
 | 
			
		||||
                Dictionary<string, string> dict = new Dictionary<string, string>();
 | 
			
		||||
                
 | 
			
		||||
                try{
 | 
			
		||||
                    string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "edit-design", "config.json"));
 | 
			
		||||
 | 
			
		||||
                    foreach(Match match in Regex.Matches(data, "\"(\\w+?)\":(.*?)[,}]")){
 | 
			
		||||
                        dict[match.Groups[1].Value] = match.Groups[2].Value.Trim('"');
 | 
			
		||||
                    }
 | 
			
		||||
                }catch{
 | 
			
		||||
                    // rip
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return dict;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static int TemplateCountFromPlugin{
 | 
			
		||||
            get{
 | 
			
		||||
                try{
 | 
			
		||||
                    string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "templates", "config.json"));
 | 
			
		||||
                    return Math.Min(StringUtils.CountOccurrences(data, "{\"name\":"), StringUtils.CountOccurrences(data, ",\"contents\":"));
 | 
			
		||||
                }catch{
 | 
			
		||||
                    return 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string ReplyAccountConfigFromPlugin{
 | 
			
		||||
            get{
 | 
			
		||||
                try{
 | 
			
		||||
                    string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "reply-account", "configuration.js")).Replace(" ", "");
 | 
			
		||||
 | 
			
		||||
                    Match matchType = Regex.Match(data, "defaultAccount:\"([#@])(.*?)\"(?:,|$)");
 | 
			
		||||
                    Match matchAdvanced = Regex.Match(data, "useAdvancedSelector:(.*?)(?:,|$)", RegexOptions.Multiline);
 | 
			
		||||
 | 
			
		||||
                    if (!matchType.Success){
 | 
			
		||||
                        return data.Contains("defaultAccount:\"\"") ? "(legacy)" : "(unknown)";
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
 | 
			
		||||
                    return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/"+accType : accType;
 | 
			
		||||
                }catch{
 | 
			
		||||
                    return "(unknown)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class ExternalInfo{
 | 
			
		||||
            public static ExternalInfo From(Form form){
 | 
			
		||||
                if (form == null){
 | 
			
		||||
                    return new ExternalInfo();
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    Screen screen = Screen.FromControl(form); // works on multi-monitor setups even in tray
 | 
			
		||||
                    int dpi;
 | 
			
		||||
 | 
			
		||||
                    using(Graphics graphics = form.CreateGraphics()){
 | 
			
		||||
                        dpi = (int)graphics.DpiY;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return new ExternalInfo{
 | 
			
		||||
                        Resolution = screen.Bounds.Width+"x"+screen.Bounds.Height,
 | 
			
		||||
                        DPI = dpi
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public string Resolution { get; private set; }
 | 
			
		||||
            public int? DPI { get; private set; }
 | 
			
		||||
 | 
			
		||||
            private ExternalInfo(){}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs {
 | 
			
		||||
namespace TweetDuck.Core.Other {
 | 
			
		||||
    sealed partial class FormAbout {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -126,7 +126,6 @@ namespace TweetDuck.Dialogs {
 | 
			
		||||
            this.Controls.Add(this.tablePanelLinks);
 | 
			
		||||
            this.Controls.Add(this.labelDescription);
 | 
			
		||||
            this.Controls.Add(this.pictureLogo);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
 | 
			
		||||
            this.HelpButton = true;
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
							
								
								
									
										46
									
								
								Core/Other/FormAbout.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Core/Other/FormAbout.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other{
 | 
			
		||||
    sealed partial class FormAbout : Form, FormManager.IAppDialog{
 | 
			
		||||
        private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
 | 
			
		||||
        private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
 | 
			
		||||
 | 
			
		||||
        public FormAbout(){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            Text = "About "+Program.BrandName+" "+Program.VersionTag;
 | 
			
		||||
 | 
			
		||||
            labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
 | 
			
		||||
            
 | 
			
		||||
            labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
 | 
			
		||||
            labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
 | 
			
		||||
            labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
 | 
			
		||||
 | 
			
		||||
            MemoryStream logoStream = new MemoryStream(Properties.Resources.avatar);
 | 
			
		||||
            pictureLogo.Image = Image.FromStream(logoStream);
 | 
			
		||||
            Disposed += (sender, args) => logoStream.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
 | 
			
		||||
            BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
 | 
			
		||||
            ShowGuide();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){
 | 
			
		||||
            e.Cancel = true;
 | 
			
		||||
            ShowGuide();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ShowGuide(){
 | 
			
		||||
            FormGuide.Show();
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,21 @@
 | 
			
		||||
namespace TweetDuck.Dialogs {
 | 
			
		||||
namespace TweetDuck.Core.Other {
 | 
			
		||||
    partial class FormGuide {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private System.ComponentModel.IContainer components = null;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clean up any resources being used.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
 | 
			
		||||
        protected override void Dispose(bool disposing) {
 | 
			
		||||
            if (disposing && (components != null)) {
 | 
			
		||||
                components.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            base.Dispose(disposing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region Windows Form Designer generated code
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -20,7 +31,6 @@
 | 
			
		||||
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 | 
			
		||||
            this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
 | 
			
		||||
            this.ClientSize = new System.Drawing.Size(424, 282);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.Icon = global::TweetDuck.Properties.Resources.icon;
 | 
			
		||||
            this.MinimumSize = new System.Drawing.Size(440, 320);
 | 
			
		||||
            this.Name = "FormGuide";
 | 
			
		||||
							
								
								
									
										122
									
								
								Core/Other/FormGuide.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								Core/Other/FormGuide.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using CefSharp;
 | 
			
		||||
using CefSharp.WinForms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Handling;
 | 
			
		||||
using TweetDuck.Core.Handling.General;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
using TweetDuck.Resources;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other{
 | 
			
		||||
    sealed partial class FormGuide : Form, FormManager.IAppDialog{
 | 
			
		||||
        private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
 | 
			
		||||
        private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
 | 
			
		||||
        
 | 
			
		||||
        private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandler.FromString(""));
 | 
			
		||||
 | 
			
		||||
        public static bool CheckGuideUrl(string url, out string hash){
 | 
			
		||||
            if (!url.Contains("//tweetduck.chylex.com/guide")){
 | 
			
		||||
                hash = null;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            string path = url.Substring(url.IndexOf("/guide")+1);
 | 
			
		||||
            Match match = Regex.Match(path, GuidePathRegex);
 | 
			
		||||
 | 
			
		||||
            if (match.Success){
 | 
			
		||||
                hash = match.Groups[1].Value;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                hash = null;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void Show(string hash = null){
 | 
			
		||||
            string url = GuideUrl+(hash ?? string.Empty);
 | 
			
		||||
            FormGuide guide = FormManager.TryFind<FormGuide>();
 | 
			
		||||
            
 | 
			
		||||
            if (guide == null){
 | 
			
		||||
                FormBrowser owner = FormManager.TryFind<FormBrowser>();
 | 
			
		||||
 | 
			
		||||
                if (owner != null){
 | 
			
		||||
                    owner.AnalyticsFile.OpenGuide.Trigger();
 | 
			
		||||
                    new FormGuide(url, owner).Show(owner);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                guide.Reload(url);
 | 
			
		||||
                guide.Activate();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly ChromiumWebBrowser browser;
 | 
			
		||||
        private string nextUrl;
 | 
			
		||||
 | 
			
		||||
        private FormGuide(string url, FormBrowser owner){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            Text = Program.BrandName+" Guide";
 | 
			
		||||
            Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
 | 
			
		||||
            VisibleChanged += (sender, args) => this.MoveToCenter(owner);
 | 
			
		||||
 | 
			
		||||
            ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
 | 
			
		||||
            resourceHandlerFactory.RegisterHandler(DummyPage);
 | 
			
		||||
            
 | 
			
		||||
            this.browser = new ChromiumWebBrowser(url){
 | 
			
		||||
                MenuHandler = new ContextMenuGuide(owner),
 | 
			
		||||
                JsDialogHandler = new JavaScriptDialogHandler(),
 | 
			
		||||
                KeyboardHandler = new KeyboardHandlerBase(),
 | 
			
		||||
                LifeSpanHandler = new LifeSpanHandler(),
 | 
			
		||||
                RequestHandler = new RequestHandlerBase(true),
 | 
			
		||||
                ResourceHandlerFactory = resourceHandlerFactory
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            browser.LoadingStateChanged += browser_LoadingStateChanged;
 | 
			
		||||
            browser.FrameLoadEnd += browser_FrameLoadEnd;
 | 
			
		||||
            
 | 
			
		||||
            browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
 | 
			
		||||
            browser.Dock = DockStyle.None;
 | 
			
		||||
            browser.Location = ControlExtensions.InvisibleLocation;
 | 
			
		||||
            browser.SetupZoomEvents();
 | 
			
		||||
 | 
			
		||||
            Controls.Add(browser);
 | 
			
		||||
 | 
			
		||||
            Disposed += (sender, args) => {
 | 
			
		||||
                browser.Dispose();
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Reload(string url){
 | 
			
		||||
            nextUrl = url;
 | 
			
		||||
            browser.LoadingStateChanged += browser_LoadingStateChanged;
 | 
			
		||||
            browser.Dock = DockStyle.None;
 | 
			
		||||
            browser.Location = ControlExtensions.InvisibleLocation;
 | 
			
		||||
            browser.Load(DummyPage.Url);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
 | 
			
		||||
            if (!e.IsLoading){
 | 
			
		||||
                if (browser.Address == DummyPage.Url){
 | 
			
		||||
                    browser.Load(nextUrl);
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    this.InvokeAsyncSafe(() => {
 | 
			
		||||
                        browser.Location = Point.Empty;
 | 
			
		||||
                        browser.Dock = DockStyle.Fill;
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    browser.LoadingStateChanged -= browser_LoadingStateChanged;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
 | 
			
		||||
            ScriptLoader.ExecuteScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs {
 | 
			
		||||
namespace TweetDuck.Core.Other {
 | 
			
		||||
    partial class FormMessage {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -60,7 +60,6 @@
 | 
			
		||||
            this.ClientSize = new System.Drawing.Size(98, 133);
 | 
			
		||||
            this.Controls.Add(this.labelMessage);
 | 
			
		||||
            this.Controls.Add(this.panelActions);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
            this.MinimizeBox = false;
 | 
			
		||||
							
								
								
									
										222
									
								
								Core/Other/FormMessage.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								Core/Other/FormMessage.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other{
 | 
			
		||||
    [Flags]
 | 
			
		||||
    enum ControlType{
 | 
			
		||||
        None = 0,
 | 
			
		||||
        Accept = 1, // triggered by pressing enter when a non-button is focused
 | 
			
		||||
        Cancel = 2, // triggered by closing the dialog without pressing a button
 | 
			
		||||
        Focused = 4 // active control after the dialog is showed
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sealed partial class FormMessage : Form{
 | 
			
		||||
        public const string OK = "OK";
 | 
			
		||||
        public const string Yes = "Yes";
 | 
			
		||||
        public const string No = "No";
 | 
			
		||||
        public const string Cancel = "Cancel";
 | 
			
		||||
        public const string Retry = "Retry";
 | 
			
		||||
        public const string Ignore = "Ignore";
 | 
			
		||||
        public const string Exit = "Exit";
 | 
			
		||||
 | 
			
		||||
        public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null){
 | 
			
		||||
            return Show(caption, text, MessageBoxIcon.Information, buttonAccept, buttonCancel);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool Warning(string caption, string text, string buttonAccept, string buttonCancel = null){
 | 
			
		||||
            return Show(caption, text, MessageBoxIcon.Warning, buttonAccept, buttonCancel);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool Error(string caption, string text, string buttonAccept, string buttonCancel = null){
 | 
			
		||||
            return Show(caption, text, MessageBoxIcon.Error, buttonAccept, buttonCancel);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool Question(string caption, string text, string buttonAccept, string buttonCancel = null){
 | 
			
		||||
            return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool Show(string caption, string text, MessageBoxIcon icon, string button){
 | 
			
		||||
            return Show(caption, text, icon, button, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel){
 | 
			
		||||
            using(FormMessage message = new FormMessage(caption, text, icon)){
 | 
			
		||||
                if (buttonCancel == null){
 | 
			
		||||
                    message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused);
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel);
 | 
			
		||||
                    message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return message.ShowDialog() == DialogResult.OK;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Instance
 | 
			
		||||
 | 
			
		||||
        public Button ClickedButton { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public bool HasIcon => icon != null;
 | 
			
		||||
        public int ActionPanelY => panelActions.Location.Y;
 | 
			
		||||
 | 
			
		||||
        private int ClientWidth{
 | 
			
		||||
            get => ClientSize.Width;
 | 
			
		||||
            set => ClientSize = new Size(value, ClientSize.Height);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private int ButtonDistance{
 | 
			
		||||
            get => BrowserUtils.Scale(96, dpiScale);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly Icon icon;
 | 
			
		||||
        private readonly bool isReady;
 | 
			
		||||
        private readonly float dpiScale;
 | 
			
		||||
 | 
			
		||||
        private int realFormWidth, minFormWidth;
 | 
			
		||||
        private int buttonCount;
 | 
			
		||||
        private int prevLabelWidth, prevLabelHeight;
 | 
			
		||||
        private bool wasLabelMultiline;
 | 
			
		||||
 | 
			
		||||
        public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            this.dpiScale = this.GetDPIScale();
 | 
			
		||||
 | 
			
		||||
            this.prevLabelWidth = labelMessage.Width;
 | 
			
		||||
            this.prevLabelHeight = labelMessage.Height;
 | 
			
		||||
            this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
 | 
			
		||||
 | 
			
		||||
            switch(messageIcon){
 | 
			
		||||
                case MessageBoxIcon.Information:
 | 
			
		||||
                    icon = SystemIcons.Information;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case MessageBoxIcon.Warning:
 | 
			
		||||
                    icon = SystemIcons.Warning;
 | 
			
		||||
                    break;
 | 
			
		||||
                    
 | 
			
		||||
                case MessageBoxIcon.Error:
 | 
			
		||||
                    icon = SystemIcons.Error;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case MessageBoxIcon.Question:
 | 
			
		||||
                    icon = SystemIcons.Question;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    icon = null;
 | 
			
		||||
                    labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.isReady = true;
 | 
			
		||||
 | 
			
		||||
            this.Text = caption;
 | 
			
		||||
            this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormMessage_SizeChanged(object sender, EventArgs e){
 | 
			
		||||
            RecalculateButtonLocation();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Button AddButton(string title, ControlType type){
 | 
			
		||||
            return AddButton(title, DialogResult.OK, type);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None){
 | 
			
		||||
            Button button = new Button{
 | 
			
		||||
                Anchor = AnchorStyles.Bottom,
 | 
			
		||||
                Font = SystemFonts.MessageBoxFont,
 | 
			
		||||
                Location = new Point(0, 12),
 | 
			
		||||
                Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
 | 
			
		||||
                TabIndex = 256-buttonCount,
 | 
			
		||||
                Text = title,
 | 
			
		||||
                UseVisualStyleBackColor = true
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            button.Click += (sender, args) => {
 | 
			
		||||
                ClickedButton = (Button)sender;
 | 
			
		||||
                DialogResult = result;
 | 
			
		||||
                Close();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            panelActions.Controls.Add(button);
 | 
			
		||||
            ++buttonCount;
 | 
			
		||||
 | 
			
		||||
            minFormWidth += ButtonDistance;
 | 
			
		||||
            ClientWidth = Math.Max(realFormWidth, minFormWidth);
 | 
			
		||||
            RecalculateButtonLocation();
 | 
			
		||||
 | 
			
		||||
            if (type.HasFlag(ControlType.Accept)){
 | 
			
		||||
                AcceptButton = button;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (type.HasFlag(ControlType.Cancel)){
 | 
			
		||||
                CancelButton = button;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (type.HasFlag(ControlType.Focused)){
 | 
			
		||||
                ActiveControl = button;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return button;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddActionControl(Control control){
 | 
			
		||||
            panelActions.Controls.Add(control);
 | 
			
		||||
            
 | 
			
		||||
            control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
 | 
			
		||||
 | 
			
		||||
            minFormWidth += control.Width+control.Margin.Horizontal;
 | 
			
		||||
            ClientWidth = Math.Max(realFormWidth, minFormWidth);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private void RecalculateButtonLocation(){
 | 
			
		||||
            int dist = ButtonDistance;
 | 
			
		||||
            int start = ClientWidth-dist;
 | 
			
		||||
 | 
			
		||||
            for(int index = 0; index < buttonCount; index++){
 | 
			
		||||
                Control control = panelActions.Controls[index];
 | 
			
		||||
                control.Location = new Point(start-index*dist, control.Location.Y);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void labelMessage_SizeChanged(object sender, EventArgs e){
 | 
			
		||||
            if (!isReady){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
 | 
			
		||||
            int labelOffset = BrowserUtils.Scale(8, dpiScale);
 | 
			
		||||
 | 
			
		||||
            if (isMultiline && !wasLabelMultiline){
 | 
			
		||||
                labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-labelOffset);
 | 
			
		||||
                prevLabelHeight += labelOffset;
 | 
			
		||||
            }
 | 
			
		||||
            else if (!isMultiline && wasLabelMultiline){
 | 
			
		||||
                labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+labelOffset);
 | 
			
		||||
                prevLabelHeight -= labelOffset;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
 | 
			
		||||
            ClientWidth = Math.Max(realFormWidth, minFormWidth);
 | 
			
		||||
            Height += labelMessage.Height-prevLabelHeight;
 | 
			
		||||
 | 
			
		||||
            prevLabelWidth = labelMessage.Width;
 | 
			
		||||
            prevLabelHeight = labelMessage.Height;
 | 
			
		||||
            wasLabelMultiline = isMultiline;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void OnPaint(PaintEventArgs e){
 | 
			
		||||
            if (icon != null){
 | 
			
		||||
                e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            base.OnPaint(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
using TweetDuck.Controls;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Dialogs {
 | 
			
		||||
namespace TweetDuck.Core.Other {
 | 
			
		||||
    partial class FormPlugins {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -29,7 +27,7 @@ namespace TweetDuck.Dialogs {
 | 
			
		||||
            this.btnClose = new System.Windows.Forms.Button();
 | 
			
		||||
            this.btnReload = new System.Windows.Forms.Button();
 | 
			
		||||
            this.btnOpenFolder = new System.Windows.Forms.Button();
 | 
			
		||||
            this.flowLayoutPlugins = new FlowLayoutPanelNoHScroll();
 | 
			
		||||
            this.flowLayoutPlugins = new TweetDuck.Plugins.Controls.PluginListFlowLayout();
 | 
			
		||||
            this.timerLayout = new System.Windows.Forms.Timer(this.components);
 | 
			
		||||
            this.SuspendLayout();
 | 
			
		||||
            // 
 | 
			
		||||
@@ -103,7 +101,6 @@ namespace TweetDuck.Dialogs {
 | 
			
		||||
            this.Controls.Add(this.btnOpenFolder);
 | 
			
		||||
            this.Controls.Add(this.btnReload);
 | 
			
		||||
            this.Controls.Add(this.btnClose);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.Icon = global::TweetDuck.Properties.Resources.icon;
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
            this.MinimizeBox = false;
 | 
			
		||||
@@ -120,7 +117,7 @@ namespace TweetDuck.Dialogs {
 | 
			
		||||
        private System.Windows.Forms.Button btnClose;
 | 
			
		||||
        private System.Windows.Forms.Button btnReload;
 | 
			
		||||
        private System.Windows.Forms.Button btnOpenFolder;
 | 
			
		||||
        private FlowLayoutPanelNoHScroll flowLayoutPlugins;
 | 
			
		||||
        private Plugins.Controls.PluginListFlowLayout flowLayoutPlugins;
 | 
			
		||||
        private System.Windows.Forms.Timer timerLayout;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										112
									
								
								Core/Other/FormPlugins.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Core/Other/FormPlugins.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Plugins.Controls;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other{
 | 
			
		||||
    sealed partial class FormPlugins : Form, FormManager.IAppDialog{
 | 
			
		||||
        private static UserConfig Config => Program.Config.User;
 | 
			
		||||
 | 
			
		||||
        private readonly PluginManager pluginManager;
 | 
			
		||||
        
 | 
			
		||||
        public FormPlugins(){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            Text = Program.BrandName+" Plugins";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public FormPlugins(PluginManager pluginManager) : this(){
 | 
			
		||||
            this.pluginManager = pluginManager;
 | 
			
		||||
 | 
			
		||||
            if (!Config.PluginsWindowSize.IsEmpty){
 | 
			
		||||
                Size targetSize = Config.PluginsWindowSize;
 | 
			
		||||
                Size = new Size(Math.Max(MinimumSize.Width, targetSize.Width), Math.Max(MinimumSize.Height, targetSize.Height));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            Shown += (sender, args) => {
 | 
			
		||||
                ReloadPluginList();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            FormClosed += (sender, args) => {
 | 
			
		||||
                Config.PluginsWindowSize = Size;
 | 
			
		||||
                Config.Save();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            ResizeEnd += (sender, args) => {
 | 
			
		||||
                timerLayout.Start();
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private int GetPluginOrderIndex(Plugin plugin){
 | 
			
		||||
            return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ReloadPluginList(){
 | 
			
		||||
            flowLayoutPlugins.Controls.Clear();
 | 
			
		||||
            flowLayoutPlugins.SuspendLayout();
 | 
			
		||||
 | 
			
		||||
            foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){
 | 
			
		||||
                flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));
 | 
			
		||||
 | 
			
		||||
                flowLayoutPlugins.Controls.Add(new Panel{
 | 
			
		||||
                    BackColor = Color.DimGray,
 | 
			
		||||
                    Margin = new Padding(0),
 | 
			
		||||
                    Size = new Size(1, 1)
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            flowLayoutPlugins.ResumeLayout(true);
 | 
			
		||||
            
 | 
			
		||||
            timerLayout_Tick(null, EventArgs.Empty);
 | 
			
		||||
            timerLayout.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void timerLayout_Tick(object sender, EventArgs e){
 | 
			
		||||
            timerLayout.Stop();
 | 
			
		||||
            
 | 
			
		||||
            // stupid WinForms scrollbars and panels
 | 
			
		||||
            Padding = new Padding(Padding.Left, Padding.Top, Padding.Right+1, Padding.Bottom+1);
 | 
			
		||||
            Padding = new Padding(Padding.Left, Padding.Top, Padding.Right-1, Padding.Bottom-1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void flowLayoutPlugins_Resize(object sender, EventArgs e){
 | 
			
		||||
            Control lastPlugin = flowLayoutPlugins.Controls.OfType<PluginControl>().LastOrDefault();
 | 
			
		||||
            
 | 
			
		||||
            if (lastPlugin == null){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool showScrollBar = lastPlugin.Location.Y+lastPlugin.Height+1 >= flowLayoutPlugins.Height;
 | 
			
		||||
            int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
 | 
			
		||||
            
 | 
			
		||||
            flowLayoutPlugins.AutoScroll = showScrollBar;
 | 
			
		||||
            flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
 | 
			
		||||
 | 
			
		||||
            foreach(Control control in flowLayoutPlugins.Controls){
 | 
			
		||||
                control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1].Visible = !showScrollBar;
 | 
			
		||||
            flowLayoutPlugins.Focus();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnOpenFolder_Click(object sender, EventArgs e){
 | 
			
		||||
            using(Process.Start("explorer.exe", '"'+pluginManager.PathCustomPlugins+'"')){}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnReload_Click(object sender, EventArgs e){
 | 
			
		||||
            if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
 | 
			
		||||
                pluginManager.Reload();
 | 
			
		||||
                ReloadPluginList();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnClose_Click(object sender, EventArgs e){
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs {
 | 
			
		||||
namespace TweetDuck.Core.Other {
 | 
			
		||||
    sealed partial class FormSettings {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -90,7 +90,6 @@
 | 
			
		||||
            this.Controls.Add(this.panelContents);
 | 
			
		||||
            this.Controls.Add(this.panelButtons);
 | 
			
		||||
            this.Controls.Add(this.btnClose);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
 | 
			
		||||
            this.Icon = global::TweetDuck.Properties.Resources.icon;
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
							
								
								
									
										210
									
								
								Core/Other/FormSettings.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								Core/Other/FormSettings.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,210 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Handling.General;
 | 
			
		||||
using TweetDuck.Core.Notification.Example;
 | 
			
		||||
using TweetDuck.Core.Other.Analytics;
 | 
			
		||||
using TweetDuck.Core.Other.Settings;
 | 
			
		||||
using TweetDuck.Core.Other.Settings.Dialogs;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
using TweetDuck.Updates;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other{
 | 
			
		||||
    sealed partial class FormSettings : Form, FormManager.IAppDialog{
 | 
			
		||||
        public bool ShouldReloadBrowser { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private readonly FormBrowser browser;
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
 | 
			
		||||
        private readonly int buttonHeight;
 | 
			
		||||
 | 
			
		||||
        private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(8);
 | 
			
		||||
        private SettingsTab currentTab;
 | 
			
		||||
 | 
			
		||||
        public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            Text = Program.BrandName+" Options";
 | 
			
		||||
 | 
			
		||||
            this.browser = browser;
 | 
			
		||||
            this.browser.PauseNotification();
 | 
			
		||||
 | 
			
		||||
            this.plugins = plugins;
 | 
			
		||||
            
 | 
			
		||||
            this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
 | 
			
		||||
 | 
			
		||||
            PrepareLoad();
 | 
			
		||||
 | 
			
		||||
            AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates));
 | 
			
		||||
            AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
 | 
			
		||||
            AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification));
 | 
			
		||||
            AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins));
 | 
			
		||||
            AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS, this.browser.OpenDevTools));
 | 
			
		||||
 | 
			
		||||
            SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void PrepareLoad(){
 | 
			
		||||
            Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void PrepareUnload(){ // TODO refactor this further later
 | 
			
		||||
            currentTab.Control.OnClosing();
 | 
			
		||||
            
 | 
			
		||||
            Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
 | 
			
		||||
            Program.Config.SaveAll();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Config_ProgramRestartRequested(object sender, EventArgs e){
 | 
			
		||||
            if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
 | 
			
		||||
                Program.Restart();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
 | 
			
		||||
            PrepareUnload();
 | 
			
		||||
 | 
			
		||||
            foreach(SettingsTab tab in tabs.Values){
 | 
			
		||||
                if (tab.IsInitialized){
 | 
			
		||||
                    tab.Control.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            browser.ResumeNotification();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnManageOptions_Click(object sender, EventArgs e){
 | 
			
		||||
            PrepareUnload();
 | 
			
		||||
 | 
			
		||||
            using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
 | 
			
		||||
                FormClosing -= FormSettings_FormClosing;
 | 
			
		||||
                
 | 
			
		||||
                if (dialog.ShowDialog() == DialogResult.OK){
 | 
			
		||||
                    if (!dialog.IsRestarting){
 | 
			
		||||
                        browser.ResumeNotification();
 | 
			
		||||
 | 
			
		||||
                        if (dialog.ShouldReloadBrowser){
 | 
			
		||||
                            BrowserProcessHandler.UpdatePrefs();
 | 
			
		||||
                            ShouldReloadBrowser = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Close();
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    FormClosing += FormSettings_FormClosing;
 | 
			
		||||
                    PrepareLoad();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnClose_Click(object sender, EventArgs e){
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void AddButton<T>(string title, Func<T> constructor) where T : BaseTabSettings{
 | 
			
		||||
            FlatButton btn = new FlatButton{
 | 
			
		||||
                BackColor = SystemColors.Control,
 | 
			
		||||
                FlatStyle = FlatStyle.Flat,
 | 
			
		||||
                Font = SystemFonts.MessageBoxFont,
 | 
			
		||||
                Location = new Point(0, (buttonHeight+1)*(panelButtons.Controls.Count/2)),
 | 
			
		||||
                Margin = new Padding(0),
 | 
			
		||||
                Size = new Size(panelButtons.Width, buttonHeight),
 | 
			
		||||
                Text = title,
 | 
			
		||||
                UseVisualStyleBackColor = true
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            btn.FlatAppearance.BorderSize = 0;
 | 
			
		||||
            btn.FlatAppearance.MouseDownBackColor = Color.FromArgb(179, 213, 232);
 | 
			
		||||
            btn.FlatAppearance.MouseOverBackColor = Color.FromArgb(216, 230, 237);
 | 
			
		||||
            
 | 
			
		||||
            panelButtons.Controls.Add(btn);
 | 
			
		||||
 | 
			
		||||
            panelButtons.Controls.Add(new Panel{
 | 
			
		||||
                BackColor = Color.DimGray,
 | 
			
		||||
                Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+buttonHeight),
 | 
			
		||||
                Margin = new Padding(0),
 | 
			
		||||
                Size = new Size(panelButtons.Width, 1)
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            tabs.Add(typeof(T), new SettingsTab(btn, constructor));
 | 
			
		||||
 | 
			
		||||
            btn.Click += (sender, args) => SelectTab<T>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SelectTab<T>() where T : BaseTabSettings{
 | 
			
		||||
            SelectTab(tabs[typeof(T)]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SelectTab(SettingsTab tab){
 | 
			
		||||
            if (currentTab != null){
 | 
			
		||||
                currentTab.Button.BackColor = SystemColors.Control;
 | 
			
		||||
                currentTab.Control.OnClosing();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
 | 
			
		||||
 | 
			
		||||
            if (!tab.IsInitialized){
 | 
			
		||||
                foreach(Control control in tab.Control.InteractiveControls){
 | 
			
		||||
                    if (control is ComboBox){
 | 
			
		||||
                        control.MouseLeave += control_MouseLeave;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (control is TrackBar){
 | 
			
		||||
                        control.MouseWheel += control_MouseWheel;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (tab.Control.Height < panelContents.Height-2){
 | 
			
		||||
                    tab.Control.Height = panelContents.Height-2; // fixes off-by-pixel error on high DPI
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                tab.Control.OnReady();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything
 | 
			
		||||
            panelContents.PerformLayout();
 | 
			
		||||
 | 
			
		||||
            panelContents.SuspendLayout();
 | 
			
		||||
            panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
 | 
			
		||||
            panelContents.Controls.Clear();
 | 
			
		||||
            panelContents.Controls.Add(tab.Control);
 | 
			
		||||
            panelContents.ResumeLayout(true);
 | 
			
		||||
 | 
			
		||||
            panelContents.VerticalScroll.Enabled = true;
 | 
			
		||||
            panelContents.Focus();
 | 
			
		||||
 | 
			
		||||
            currentTab = tab;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void control_MouseLeave(object sender, EventArgs e){
 | 
			
		||||
            if (sender is ComboBox cb && cb.DroppedDown){
 | 
			
		||||
                return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            panelContents.Focus();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void control_MouseWheel(object sender, MouseEventArgs e){
 | 
			
		||||
            ((HandledMouseEventArgs)e).Handled = true;
 | 
			
		||||
            panelContents.Focus();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private sealed class SettingsTab{
 | 
			
		||||
            public Button Button { get; }
 | 
			
		||||
 | 
			
		||||
            public BaseTabSettings Control => control ?? (control = constructor());
 | 
			
		||||
            public bool IsInitialized => control != null;
 | 
			
		||||
 | 
			
		||||
            private readonly Func<BaseTabSettings> constructor;
 | 
			
		||||
            private BaseTabSettings control;
 | 
			
		||||
 | 
			
		||||
            public SettingsTab(Button button, Func<BaseTabSettings> constructor){
 | 
			
		||||
                this.Button = button;
 | 
			
		||||
                this.constructor = constructor;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								Core/Other/Settings/BaseTabSettings.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Core/Other/Settings/BaseTabSettings.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings{
 | 
			
		||||
    class BaseTabSettings : UserControl{
 | 
			
		||||
        protected static UserConfig Config => Program.Config.User;
 | 
			
		||||
        protected static SystemConfig SysConfig => Program.Config.System;
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<Control> InteractiveControls{
 | 
			
		||||
            get{
 | 
			
		||||
                IEnumerable<Control> FindInteractiveControls(Control parent){
 | 
			
		||||
                    foreach(Control control in parent.Controls){
 | 
			
		||||
                        if (control is Panel subPanel){
 | 
			
		||||
                            foreach(Control subControl in FindInteractiveControls(subPanel)){
 | 
			
		||||
                                yield return subControl;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else{
 | 
			
		||||
                            yield return control;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return FindInteractiveControls(this);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected BaseTabSettings(){
 | 
			
		||||
            Padding = new Padding(6);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void OnReady(){}
 | 
			
		||||
        public virtual void OnClosing(){}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								Core/Other/Settings/Dialogs/DialogSettingsAnalytics.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								Core/Other/Settings/Dialogs/DialogSettingsAnalytics.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
 | 
			
		||||
    partial class DialogSettingsAnalytics {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private System.ComponentModel.IContainer components = null;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clean up any resources being used.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
 | 
			
		||||
        protected override void Dispose(bool disposing) {
 | 
			
		||||
            if (disposing && (components != null)) {
 | 
			
		||||
                components.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            base.Dispose(disposing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region Windows Form Designer generated code
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required method for Designer support - do not modify
 | 
			
		||||
        /// the contents of this method with the code editor.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private void InitializeComponent() {
 | 
			
		||||
            this.textBoxReport = new System.Windows.Forms.TextBox();
 | 
			
		||||
            this.btnClose = new System.Windows.Forms.Button();
 | 
			
		||||
            this.labelInfo = new System.Windows.Forms.Label();
 | 
			
		||||
            this.SuspendLayout();
 | 
			
		||||
            // 
 | 
			
		||||
            // textBoxReport
 | 
			
		||||
            // 
 | 
			
		||||
            this.textBoxReport.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Left) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.textBoxReport.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
 | 
			
		||||
            this.textBoxReport.Location = new System.Drawing.Point(12, 45);
 | 
			
		||||
            this.textBoxReport.Multiline = true;
 | 
			
		||||
            this.textBoxReport.Name = "textBoxReport";
 | 
			
		||||
            this.textBoxReport.ReadOnly = true;
 | 
			
		||||
            this.textBoxReport.ScrollBars = System.Windows.Forms.ScrollBars.Both;
 | 
			
		||||
            this.textBoxReport.Size = new System.Drawing.Size(435, 474);
 | 
			
		||||
            this.textBoxReport.TabIndex = 1;
 | 
			
		||||
            // 
 | 
			
		||||
            // btnClose
 | 
			
		||||
            // 
 | 
			
		||||
            this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.btnClose.AutoSize = true;
 | 
			
		||||
            this.btnClose.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
 | 
			
		||||
            this.btnClose.Location = new System.Drawing.Point(397, 525);
 | 
			
		||||
            this.btnClose.Name = "btnClose";
 | 
			
		||||
            this.btnClose.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
 | 
			
		||||
            this.btnClose.Size = new System.Drawing.Size(50, 25);
 | 
			
		||||
            this.btnClose.TabIndex = 2;
 | 
			
		||||
            this.btnClose.Text = "Close";
 | 
			
		||||
            this.btnClose.UseVisualStyleBackColor = true;
 | 
			
		||||
            this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
 | 
			
		||||
            // 
 | 
			
		||||
            // labelInfo
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelInfo.AutoSize = true;
 | 
			
		||||
            this.labelInfo.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
 | 
			
		||||
            this.labelInfo.Location = new System.Drawing.Point(12, 9);
 | 
			
		||||
            this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
 | 
			
		||||
            this.labelInfo.Name = "labelInfo";
 | 
			
		||||
            this.labelInfo.Size = new System.Drawing.Size(434, 30);
 | 
			
		||||
            this.labelInfo.TabIndex = 0;
 | 
			
		||||
            this.labelInfo.Text = "When enabled, this data will be sent over a secure network roughly every 14 days." +
 | 
			
		||||
    "\r\nSome numbers in the report were made imprecise on purpose.";
 | 
			
		||||
            // 
 | 
			
		||||
            // DialogSettingsAnalytics
 | 
			
		||||
            // 
 | 
			
		||||
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
 | 
			
		||||
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 | 
			
		||||
            this.ClientSize = new System.Drawing.Size(459, 562);
 | 
			
		||||
            this.Controls.Add(this.labelInfo);
 | 
			
		||||
            this.Controls.Add(this.btnClose);
 | 
			
		||||
            this.Controls.Add(this.textBoxReport);
 | 
			
		||||
            this.MinimumSize = new System.Drawing.Size(475, 340);
 | 
			
		||||
            this.Name = "DialogSettingsAnalytics";
 | 
			
		||||
            this.ShowIcon = false;
 | 
			
		||||
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
 | 
			
		||||
            this.ResumeLayout(false);
 | 
			
		||||
            this.PerformLayout();
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        private System.Windows.Forms.TextBox textBoxReport;
 | 
			
		||||
        private System.Windows.Forms.Button btnClose;
 | 
			
		||||
        private System.Windows.Forms.Label labelInfo;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Other.Analytics;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
 | 
			
		||||
    sealed partial class DialogSettingsAnalytics : Form{
 | 
			
		||||
        public string CefArgs => textBoxReport.Text;
 | 
			
		||||
 | 
			
		||||
        public DialogSettingsAnalytics(AnalyticsReport report){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
            
 | 
			
		||||
            Text = Program.BrandName+" Options - Analytics Report";
 | 
			
		||||
            
 | 
			
		||||
            textBoxReport.EnableMultilineShortcuts();
 | 
			
		||||
            textBoxReport.Text = report.ToString().TrimEnd();
 | 
			
		||||
            textBoxReport.Select(0, 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnClose_Click(object sender, EventArgs e){
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs.Settings {
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
 | 
			
		||||
    partial class DialogSettingsCSS {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -160,7 +160,6 @@
 | 
			
		||||
            this.Controls.Add(this.btnOpenDevTools);
 | 
			
		||||
            this.Controls.Add(this.btnApply);
 | 
			
		||||
            this.Controls.Add(this.btnCancel);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.MinimumSize = new System.Drawing.Size(320, 240);
 | 
			
		||||
            this.Name = "DialogSettingsCSS";
 | 
			
		||||
            this.ShowIcon = false;
 | 
			
		||||
							
								
								
									
										142
									
								
								Core/Other/Settings/Dialogs/DialogSettingsCSS.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								Core/Other/Settings/Dialogs/DialogSettingsCSS.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
 | 
			
		||||
    sealed partial class DialogSettingsCSS : Form{
 | 
			
		||||
        public string BrowserCSS => textBoxBrowserCSS.Text;
 | 
			
		||||
        public string NotificationCSS => textBoxNotificationCSS.Text;
 | 
			
		||||
 | 
			
		||||
        private readonly Action<string> reinjectBrowserCSS;
 | 
			
		||||
        private readonly Action openDevTools;
 | 
			
		||||
 | 
			
		||||
        public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
            
 | 
			
		||||
            Text = Program.BrandName+" Options - CSS";
 | 
			
		||||
 | 
			
		||||
            this.reinjectBrowserCSS = reinjectBrowserCSS;
 | 
			
		||||
            this.openDevTools = openDevTools;
 | 
			
		||||
            
 | 
			
		||||
            textBoxBrowserCSS.EnableMultilineShortcuts();
 | 
			
		||||
            textBoxBrowserCSS.Text = browserCSS ?? "";
 | 
			
		||||
 | 
			
		||||
            textBoxNotificationCSS.EnableMultilineShortcuts();
 | 
			
		||||
            textBoxNotificationCSS.Text = notificationCSS ?? "";
 | 
			
		||||
 | 
			
		||||
            if (!BrowserUtils.HasDevTools){
 | 
			
		||||
                btnOpenDevTools.Enabled = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ActiveControl = textBoxBrowserCSS;
 | 
			
		||||
            textBoxBrowserCSS.Select(textBoxBrowserCSS.TextLength, 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void tabPanel_SelectedIndexChanged(object sender, EventArgs e){
 | 
			
		||||
            TextBox tb = tabPanel.SelectedTab.Controls.OfType<TextBox>().FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
            if (tb != null){
 | 
			
		||||
                tb.Focus();
 | 
			
		||||
                tb.Select(tb.TextLength, 0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void textBoxCSS_KeyDown(object sender, KeyEventArgs e){
 | 
			
		||||
            TextBox tb = (TextBox)sender;
 | 
			
		||||
            string text = tb.Text;
 | 
			
		||||
 | 
			
		||||
            if (e.KeyCode == Keys.Back && e.Modifiers == Keys.Control){
 | 
			
		||||
                e.SuppressKeyPress = true;
 | 
			
		||||
 | 
			
		||||
                int deleteTo = tb.SelectionStart;
 | 
			
		||||
 | 
			
		||||
                if (deleteTo > 0){
 | 
			
		||||
                    char initialChar = text[--deleteTo];
 | 
			
		||||
                    bool shouldDeleteAlphanumeric = char.IsLetterOrDigit(initialChar);
 | 
			
		||||
                
 | 
			
		||||
                    while(--deleteTo >= 0){
 | 
			
		||||
                        if ((shouldDeleteAlphanumeric && !char.IsLetterOrDigit(text[deleteTo])) ||
 | 
			
		||||
                            (!shouldDeleteAlphanumeric && text[deleteTo] != initialChar)){
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    if (!(deleteTo < text.Length-1 && text[deleteTo] == '\r' && text[deleteTo+1] == '\n')){
 | 
			
		||||
                        ++deleteTo;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    tb.Select(deleteTo, tb.SelectionLength+tb.SelectionStart-deleteTo);
 | 
			
		||||
                    tb.SelectedText = string.Empty;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){
 | 
			
		||||
                int deleteTo = tb.SelectionStart;
 | 
			
		||||
 | 
			
		||||
                if (deleteTo > 1 && text[deleteTo-1] == ' ' && text[deleteTo-2] == ' '){
 | 
			
		||||
                    e.SuppressKeyPress = true;
 | 
			
		||||
 | 
			
		||||
                    tb.Select(deleteTo-2, 2);
 | 
			
		||||
                    tb.SelectedText = string.Empty;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if (e.KeyCode == Keys.Enter && e.Modifiers == Keys.None && tb.SelectionLength == 0){
 | 
			
		||||
                int insertAt = tb.SelectionStart, cursorOffset = 0;
 | 
			
		||||
                string insertText;
 | 
			
		||||
 | 
			
		||||
                if (insertAt == 0){
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                else if (text[insertAt-1] == '{'){
 | 
			
		||||
                    insertText = Environment.NewLine+"  ";
 | 
			
		||||
 | 
			
		||||
                    int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt+1) : -1;
 | 
			
		||||
 | 
			
		||||
                    if (nextBracket == -1 || text[nextBracket] == '{'){
 | 
			
		||||
                        string insertExtra = Environment.NewLine+"}";
 | 
			
		||||
                        insertText += insertExtra;
 | 
			
		||||
                        cursorOffset -= insertExtra.Length;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else{
 | 
			
		||||
                    int lineStart = text.LastIndexOf('\n', tb.SelectionStart-1);
 | 
			
		||||
 | 
			
		||||
                    Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart+1), "^([ \t]+)");
 | 
			
		||||
                    insertText = match.Success ? Environment.NewLine+match.Groups[1].Value : null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrEmpty(insertText)){
 | 
			
		||||
                    e.SuppressKeyPress = true;
 | 
			
		||||
                    tb.Text = text.Insert(insertAt, insertText);
 | 
			
		||||
                    tb.SelectionStart = insertAt+cursorOffset+insertText.Length;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){
 | 
			
		||||
            timerTestBrowser.Stop();
 | 
			
		||||
            timerTestBrowser.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void timerTestBrowser_Tick(object sender, EventArgs e){
 | 
			
		||||
            reinjectBrowserCSS(textBoxBrowserCSS.Text);
 | 
			
		||||
            timerTestBrowser.Stop();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnOpenDevTools_Click(object sender, EventArgs e){
 | 
			
		||||
            openDevTools();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnApply_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogResult = DialogResult.OK;
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnCancel_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogResult = DialogResult.Cancel;
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs.Settings {
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
 | 
			
		||||
    partial class DialogSettingsCefArgs {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -106,7 +106,6 @@
 | 
			
		||||
            this.Controls.Add(this.btnApply);
 | 
			
		||||
            this.Controls.Add(this.btnCancel);
 | 
			
		||||
            this.Controls.Add(this.textBoxArgs);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.MinimumSize = new System.Drawing.Size(520, 160);
 | 
			
		||||
            this.Name = "DialogSettingsCefArgs";
 | 
			
		||||
            this.ShowIcon = false;
 | 
			
		||||
							
								
								
									
										48
									
								
								Core/Other/Settings/Dialogs/DialogSettingsCefArgs.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Core/Other/Settings/Dialogs/DialogSettingsCefArgs.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
 | 
			
		||||
    sealed partial class DialogSettingsCefArgs : Form{
 | 
			
		||||
        public string CefArgs => textBoxArgs.Text;
 | 
			
		||||
 | 
			
		||||
        private readonly string initialArgs;
 | 
			
		||||
 | 
			
		||||
        public DialogSettingsCefArgs(string args){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
            
 | 
			
		||||
            Text = Program.BrandName+" Options - CEF Arguments";
 | 
			
		||||
            
 | 
			
		||||
            textBoxArgs.EnableMultilineShortcuts();
 | 
			
		||||
            textBoxArgs.Text = initialArgs = args ?? "";
 | 
			
		||||
            textBoxArgs.Select(textBoxArgs.Text.Length, 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnHelp_Click(object sender, EventArgs e){
 | 
			
		||||
            BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnApply_Click(object sender, EventArgs e){
 | 
			
		||||
            if (CefArgs == initialArgs){
 | 
			
		||||
                DialogResult = DialogResult.Cancel;
 | 
			
		||||
                Close();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
 | 
			
		||||
            string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
 | 
			
		||||
 | 
			
		||||
            if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
 | 
			
		||||
                DialogResult = DialogResult.OK;
 | 
			
		||||
                Close();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnCancel_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogResult = DialogResult.Cancel;
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs.Settings {
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
 | 
			
		||||
    partial class DialogSettingsManage {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -121,7 +121,7 @@
 | 
			
		||||
            this.cbSystemConfig.Size = new System.Drawing.Size(109, 19);
 | 
			
		||||
            this.cbSystemConfig.TabIndex = 1;
 | 
			
		||||
            this.cbSystemConfig.Text = "System Options";
 | 
			
		||||
            this.toolTip.SetToolTip(this.cbSystemConfig, "Options related to the current system and hardware,\r\nsuch as hardware acceleration, proxy, and cache settings.");
 | 
			
		||||
            this.toolTip.SetToolTip(this.cbSystemConfig, "Hardware acceleration and cache options.");
 | 
			
		||||
            this.cbSystemConfig.UseVisualStyleBackColor = true;
 | 
			
		||||
            this.cbSystemConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
 | 
			
		||||
            // 
 | 
			
		||||
@@ -206,7 +206,6 @@
 | 
			
		||||
            this.Controls.Add(this.btnCancel);
 | 
			
		||||
            this.Controls.Add(this.panelDecision);
 | 
			
		||||
            this.Controls.Add(this.panelSelection);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
            this.MinimizeBox = false;
 | 
			
		||||
							
								
								
									
										244
									
								
								Core/Other/Settings/Dialogs/DialogSettingsManage.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								Core/Other/Settings/Dialogs/DialogSettingsManage.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,244 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using TweetDuck.Core.Management;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
 | 
			
		||||
    sealed partial class DialogSettingsManage : Form{
 | 
			
		||||
        private enum State{
 | 
			
		||||
            Deciding, Reset, Import, Export
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private ProfileManager.Items SelectedItems{
 | 
			
		||||
            get => _selectedItems;
 | 
			
		||||
 | 
			
		||||
            set{
 | 
			
		||||
                // this will call events and SetFlag, which also updates the UI
 | 
			
		||||
                foreach(KeyValuePair<CheckBox, ProfileManager.Items> kvp in checkBoxMap){
 | 
			
		||||
                    kvp.Key.Checked = value.HasFlag(kvp.Value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool SelectedItemsForceRestart{
 | 
			
		||||
            get => _selectedItems.HasFlag(ProfileManager.Items.Session);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public bool IsRestarting { get; private set; }
 | 
			
		||||
        public bool ShouldReloadBrowser { get; private set; }
 | 
			
		||||
        
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
        private readonly Dictionary<CheckBox, ProfileManager.Items> checkBoxMap = new Dictionary<CheckBox, ProfileManager.Items>(4);
 | 
			
		||||
        private readonly bool openImportImmediately;
 | 
			
		||||
 | 
			
		||||
        private State currentState;
 | 
			
		||||
        private ProfileManager importManager;
 | 
			
		||||
        private bool requestedRestartFromConfig;
 | 
			
		||||
 | 
			
		||||
        private ProfileManager.Items _selectedItems = ProfileManager.Items.None;
 | 
			
		||||
 | 
			
		||||
        public DialogSettingsManage(PluginManager plugins, bool openImportImmediately = false){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            this.plugins = plugins;
 | 
			
		||||
            this.currentState = State.Deciding;
 | 
			
		||||
 | 
			
		||||
            this.checkBoxMap[cbProgramConfig] = ProfileManager.Items.UserConfig;
 | 
			
		||||
            this.checkBoxMap[cbSystemConfig] = ProfileManager.Items.SystemConfig;
 | 
			
		||||
            this.checkBoxMap[cbSession] = ProfileManager.Items.Session;
 | 
			
		||||
            this.checkBoxMap[cbPluginData] = ProfileManager.Items.PluginData;
 | 
			
		||||
 | 
			
		||||
            this.openImportImmediately = openImportImmediately;
 | 
			
		||||
 | 
			
		||||
            if (openImportImmediately){
 | 
			
		||||
                radioImport.Checked = true;
 | 
			
		||||
                btnContinue_Click(null, EventArgs.Empty);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void radioDecision_CheckedChanged(object sender, EventArgs e){
 | 
			
		||||
            btnContinue.Enabled = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void checkBoxSelection_CheckedChanged(object sender, EventArgs e){
 | 
			
		||||
            CheckBox cb = (CheckBox)sender;
 | 
			
		||||
            SetFlag(checkBoxMap[cb], cb.Checked);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnContinue_Click(object sender, EventArgs e){
 | 
			
		||||
            string file;
 | 
			
		||||
 | 
			
		||||
            switch(currentState){
 | 
			
		||||
                case State.Deciding:
 | 
			
		||||
                    // Reset
 | 
			
		||||
                    if (radioReset.Checked){
 | 
			
		||||
                        currentState = State.Reset;
 | 
			
		||||
 | 
			
		||||
                        Text = "Restore Defaults";
 | 
			
		||||
                        SelectedItems = ProfileManager.Items.UserConfig;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Import
 | 
			
		||||
                    else if (radioImport.Checked){
 | 
			
		||||
                        using(OpenFileDialog dialog = new OpenFileDialog{
 | 
			
		||||
                            AutoUpgradeEnabled = true,
 | 
			
		||||
                            DereferenceLinks = true,
 | 
			
		||||
                            Title = "Import TweetDuck Profile",
 | 
			
		||||
                            Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
 | 
			
		||||
                        }){
 | 
			
		||||
                            if (dialog.ShowDialog() != DialogResult.OK){
 | 
			
		||||
                                if (openImportImmediately){
 | 
			
		||||
                                    Close();
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                return;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            file = dialog.FileName;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        importManager = new ProfileManager(file, plugins);
 | 
			
		||||
                        currentState = State.Import;
 | 
			
		||||
 | 
			
		||||
                        Text = "Import Profile";
 | 
			
		||||
                        SelectedItems = importManager.FindImportItems();
 | 
			
		||||
 | 
			
		||||
                        foreach(CheckBox cb in checkBoxMap.Keys){
 | 
			
		||||
                            cb.Enabled = cb.Checked;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Export
 | 
			
		||||
                    else if (radioExport.Checked){
 | 
			
		||||
                        currentState = State.Export;
 | 
			
		||||
 | 
			
		||||
                        Text = "Export Profile";
 | 
			
		||||
                        btnContinue.Text = "Export Profile";
 | 
			
		||||
                        SelectedItems = ProfileManager.Items.UserConfig | ProfileManager.Items.PluginData;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Continue...
 | 
			
		||||
                    panelDecision.Visible = false;
 | 
			
		||||
                    panelSelection.Visible = true;
 | 
			
		||||
                    Height += panelSelection.Height-panelDecision.Height;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case State.Reset:
 | 
			
		||||
                    if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)){
 | 
			
		||||
                        Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
 | 
			
		||||
 | 
			
		||||
                        if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)){
 | 
			
		||||
                            Program.Config.User.Reset();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){
 | 
			
		||||
                            Program.Config.System.Reset();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
 | 
			
		||||
 | 
			
		||||
                        if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)){
 | 
			
		||||
                            Program.Config.Plugins.Reset();
 | 
			
		||||
 | 
			
		||||
                            try{
 | 
			
		||||
                                Directory.Delete(Program.PluginDataPath, true);
 | 
			
		||||
                            }catch(Exception ex){
 | 
			
		||||
                                Program.Reporter.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (SelectedItemsForceRestart){
 | 
			
		||||
                            RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[]{ Arguments.ArgDeleteCookies } : StringUtils.EmptyArray);
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (requestedRestartFromConfig){
 | 
			
		||||
                            if (FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
 | 
			
		||||
                                RestartProgram();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        ShouldReloadBrowser = true;
 | 
			
		||||
 | 
			
		||||
                        DialogResult = DialogResult.OK;
 | 
			
		||||
                        Close();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case State.Import:
 | 
			
		||||
                    if (importManager.Import(SelectedItems)){
 | 
			
		||||
                        Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
 | 
			
		||||
                        Program.Config.ReloadAll();
 | 
			
		||||
                        Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
 | 
			
		||||
                        
 | 
			
		||||
                        if (SelectedItemsForceRestart){
 | 
			
		||||
                            RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[]{ Arguments.ArgImportCookies } : StringUtils.EmptyArray);
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (requestedRestartFromConfig){
 | 
			
		||||
                            if (FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
 | 
			
		||||
                                RestartProgram();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    ShouldReloadBrowser = true;
 | 
			
		||||
 | 
			
		||||
                    DialogResult = DialogResult.OK;
 | 
			
		||||
                    Close();
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case State.Export:
 | 
			
		||||
                    using(SaveFileDialog dialog = new SaveFileDialog{
 | 
			
		||||
                        AddExtension = true,
 | 
			
		||||
                        AutoUpgradeEnabled = true,
 | 
			
		||||
                        OverwritePrompt = true,
 | 
			
		||||
                        DefaultExt = "tdsettings",
 | 
			
		||||
                        FileName = "TweetDuck.tdsettings",
 | 
			
		||||
                        Title = "Export TweetDuck Profile",
 | 
			
		||||
                        Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
 | 
			
		||||
                    }){
 | 
			
		||||
                        if (dialog.ShowDialog() != DialogResult.OK){
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        file = dialog.FileName;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    new ProfileManager(file, plugins).Export(SelectedItems);
 | 
			
		||||
 | 
			
		||||
                    DialogResult = DialogResult.OK;
 | 
			
		||||
                    Close();
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnCancel_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogResult = DialogResult.Cancel;
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Config_ProgramRestartRequested(object sender, EventArgs e){
 | 
			
		||||
            requestedRestartFromConfig = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SetFlag(ProfileManager.Items flag, bool enable){
 | 
			
		||||
            _selectedItems = enable ? _selectedItems | flag : _selectedItems & ~flag;
 | 
			
		||||
            btnContinue.Enabled = _selectedItems != ProfileManager.Items.None;
 | 
			
		||||
            
 | 
			
		||||
            if (currentState == State.Import){
 | 
			
		||||
                btnContinue.Text = SelectedItemsForceRestart ? "Import && Restart" : "Import Profile";
 | 
			
		||||
            }
 | 
			
		||||
            else if (currentState == State.Reset){
 | 
			
		||||
                btnContinue.Text = SelectedItemsForceRestart ? "Restore && Restart" : "Restore Defaults";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void RestartProgram(params string[] extraArgs){
 | 
			
		||||
            IsRestarting = true;
 | 
			
		||||
            Program.Restart(extraArgs);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs.Settings {
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
 | 
			
		||||
    partial class DialogSettingsRestart {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -148,7 +148,6 @@
 | 
			
		||||
            this.Controls.Add(this.flowPanel);
 | 
			
		||||
            this.Controls.Add(this.btnRestart);
 | 
			
		||||
            this.Controls.Add(this.btnCancel);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
            this.MinimizeBox = false;
 | 
			
		||||
							
								
								
									
										61
									
								
								Core/Other/Settings/Dialogs/DialogSettingsRestart.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								Core/Other/Settings/Dialogs/DialogSettingsRestart.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using TweetDuck.Data;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
 | 
			
		||||
    sealed partial class DialogSettingsRestart : Form{
 | 
			
		||||
        public CommandLineArgs Args { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public DialogSettingsRestart(CommandLineArgs currentArgs){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
 | 
			
		||||
            cbLogging.CheckedChanged += control_Change;
 | 
			
		||||
            
 | 
			
		||||
            if (Program.IsPortable){
 | 
			
		||||
                tbDataFolder.Text = "Not available in portable version";
 | 
			
		||||
                tbDataFolder.Enabled = false;
 | 
			
		||||
            }
 | 
			
		||||
            else{
 | 
			
		||||
                tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
 | 
			
		||||
                tbDataFolder.TextChanged += control_Change;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            control_Change(this, EventArgs.Empty);
 | 
			
		||||
 | 
			
		||||
            Text = Program.BrandName+" Arguments";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void control_Change(object sender, EventArgs e){
 | 
			
		||||
            Args = new CommandLineArgs();
 | 
			
		||||
            
 | 
			
		||||
            if (cbLogging.Checked){
 | 
			
		||||
                Args.AddFlag(Arguments.ArgLogging);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
 | 
			
		||||
                Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            tbShortcutTarget.Text = $@"""{Application.ExecutablePath}""{(Args.Count > 0 ? " " : "")}{Args}";
 | 
			
		||||
            tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void tbShortcutTarget_Click(object sender, EventArgs e){
 | 
			
		||||
            if (tbShortcutTarget.SelectionLength == 0){
 | 
			
		||||
                tbShortcutTarget.SelectAll();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnRestart_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogResult = DialogResult.OK;
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnCancel_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogResult = DialogResult.Cancel;
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs.Settings {
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
 | 
			
		||||
    partial class DialogSettingsSearchEngine {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -88,7 +88,6 @@
 | 
			
		||||
            this.Controls.Add(this.btnApply);
 | 
			
		||||
            this.Controls.Add(this.btnCancel);
 | 
			
		||||
            this.Controls.Add(this.textBoxUrl);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
 | 
			
		||||
            this.MaximizeBox = false;
 | 
			
		||||
            this.MinimizeBox = false;
 | 
			
		||||
							
								
								
									
										27
									
								
								Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
 | 
			
		||||
    sealed partial class DialogSettingsSearchEngine : Form{
 | 
			
		||||
        public string Url => textBoxUrl.Text;
 | 
			
		||||
 | 
			
		||||
        public DialogSettingsSearchEngine(){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
            
 | 
			
		||||
            Text = Program.BrandName+" Options - Custom Search Engine";
 | 
			
		||||
            
 | 
			
		||||
            textBoxUrl.Text = Program.Config.User.SearchEngineUrl ?? "";
 | 
			
		||||
            textBoxUrl.Select(textBoxUrl.Text.Length, 0);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private void btnApply_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogResult = DialogResult.OK;
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnCancel_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogResult = DialogResult.Cancel;
 | 
			
		||||
            Close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs.Settings {
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings {
 | 
			
		||||
    partial class TabSettingsAdvanced {
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -32,7 +32,7 @@
 | 
			
		||||
            this.btnRestart = new System.Windows.Forms.Button();
 | 
			
		||||
            this.btnOpenAppFolder = new System.Windows.Forms.Button();
 | 
			
		||||
            this.btnOpenDataFolder = new System.Windows.Forms.Button();
 | 
			
		||||
            this.numClearCacheThreshold = new TweetDuck.Controls.NumericUpDownEx();
 | 
			
		||||
            this.numClearCacheThreshold = new TweetDuck.Core.Controls.NumericUpDownEx();
 | 
			
		||||
            this.checkClearCacheAuto = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelApp = new System.Windows.Forms.Label();
 | 
			
		||||
            this.panelAppButtons = new System.Windows.Forms.Panel();
 | 
			
		||||
@@ -40,34 +40,22 @@
 | 
			
		||||
            this.panelClearCacheAuto = new System.Windows.Forms.Panel();
 | 
			
		||||
            this.panelConfiguration = new System.Windows.Forms.Panel();
 | 
			
		||||
            this.labelConfiguration = new System.Windows.Forms.Label();
 | 
			
		||||
            this.flowPanelLeft = new System.Windows.Forms.FlowLayoutPanel();
 | 
			
		||||
            this.labelBrowserSettings = new System.Windows.Forms.Label();
 | 
			
		||||
            this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.checkTouchAdjustment = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelProxy = new System.Windows.Forms.Label();
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelDevTools = new System.Windows.Forms.Label();
 | 
			
		||||
            this.checkDevToolsInContextMenu = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.checkDevToolsWindowOnTop = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.flowPanelRight = new System.Windows.Forms.FlowLayoutPanel();
 | 
			
		||||
            this.panelSeparator = new System.Windows.Forms.Panel();
 | 
			
		||||
            this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
 | 
			
		||||
            ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit();
 | 
			
		||||
            this.panelAppButtons.SuspendLayout();
 | 
			
		||||
            this.panelClearCacheAuto.SuspendLayout();
 | 
			
		||||
            this.panelConfiguration.SuspendLayout();
 | 
			
		||||
            this.flowPanelLeft.SuspendLayout();
 | 
			
		||||
            this.flowPanelRight.SuspendLayout();
 | 
			
		||||
            this.flowPanel.SuspendLayout();
 | 
			
		||||
            this.SuspendLayout();
 | 
			
		||||
            // 
 | 
			
		||||
            // btnClearCache
 | 
			
		||||
            // 
 | 
			
		||||
            this.btnClearCache.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.btnClearCache.Location = new System.Drawing.Point(5, 145);
 | 
			
		||||
            this.btnClearCache.Location = new System.Drawing.Point(5, 135);
 | 
			
		||||
            this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
 | 
			
		||||
            this.btnClearCache.Name = "btnClearCache";
 | 
			
		||||
            this.btnClearCache.Size = new System.Drawing.Size(143, 25);
 | 
			
		||||
            this.btnClearCache.TabIndex = 5;
 | 
			
		||||
            this.btnClearCache.TabIndex = 3;
 | 
			
		||||
            this.btnClearCache.Text = "Clear Cache (...)";
 | 
			
		||||
            this.btnClearCache.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
@@ -154,7 +142,7 @@
 | 
			
		||||
            0,
 | 
			
		||||
            0});
 | 
			
		||||
            this.numClearCacheThreshold.Name = "numClearCacheThreshold";
 | 
			
		||||
            this.numClearCacheThreshold.Size = new System.Drawing.Size(80, 23);
 | 
			
		||||
            this.numClearCacheThreshold.Size = new System.Drawing.Size(89, 23);
 | 
			
		||||
            this.numClearCacheThreshold.TabIndex = 1;
 | 
			
		||||
            this.numClearCacheThreshold.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
 | 
			
		||||
            this.numClearCacheThreshold.TextSuffix = " MB";
 | 
			
		||||
@@ -203,206 +191,67 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelCache.AutoSize = true;
 | 
			
		||||
            this.labelCache.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelCache.Location = new System.Drawing.Point(0, 122);
 | 
			
		||||
            this.labelCache.Location = new System.Drawing.Point(0, 112);
 | 
			
		||||
            this.labelCache.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
 | 
			
		||||
            this.labelCache.Name = "labelCache";
 | 
			
		||||
            this.labelCache.Size = new System.Drawing.Size(123, 19);
 | 
			
		||||
            this.labelCache.TabIndex = 4;
 | 
			
		||||
            this.labelCache.TabIndex = 2;
 | 
			
		||||
            this.labelCache.Text = "BROWSER CACHE";
 | 
			
		||||
            // 
 | 
			
		||||
            // panelClearCacheAuto
 | 
			
		||||
            // 
 | 
			
		||||
            this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto);
 | 
			
		||||
            this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold);
 | 
			
		||||
            this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 173);
 | 
			
		||||
            this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 163);
 | 
			
		||||
            this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0);
 | 
			
		||||
            this.panelClearCacheAuto.Name = "panelClearCacheAuto";
 | 
			
		||||
            this.panelClearCacheAuto.Size = new System.Drawing.Size(300, 28);
 | 
			
		||||
            this.panelClearCacheAuto.TabIndex = 6;
 | 
			
		||||
            this.panelClearCacheAuto.TabIndex = 4;
 | 
			
		||||
            // 
 | 
			
		||||
            // panelConfiguration
 | 
			
		||||
            // 
 | 
			
		||||
            this.panelConfiguration.Controls.Add(this.btnEditCSS);
 | 
			
		||||
            this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
 | 
			
		||||
            this.panelConfiguration.Location = new System.Drawing.Point(0, 132);
 | 
			
		||||
            this.panelConfiguration.Location = new System.Drawing.Point(0, 241);
 | 
			
		||||
            this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0);
 | 
			
		||||
            this.panelConfiguration.Name = "panelConfiguration";
 | 
			
		||||
            this.panelConfiguration.Size = new System.Drawing.Size(300, 31);
 | 
			
		||||
            this.panelConfiguration.TabIndex = 3;
 | 
			
		||||
            this.panelConfiguration.TabIndex = 6;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelConfiguration
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelConfiguration.AutoSize = true;
 | 
			
		||||
            this.labelConfiguration.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelConfiguration.Location = new System.Drawing.Point(0, 112);
 | 
			
		||||
            this.labelConfiguration.Location = new System.Drawing.Point(0, 221);
 | 
			
		||||
            this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
 | 
			
		||||
            this.labelConfiguration.Name = "labelConfiguration";
 | 
			
		||||
            this.labelConfiguration.Size = new System.Drawing.Size(123, 19);
 | 
			
		||||
            this.labelConfiguration.TabIndex = 2;
 | 
			
		||||
            this.labelConfiguration.TabIndex = 5;
 | 
			
		||||
            this.labelConfiguration.Text = "CONFIGURATION";
 | 
			
		||||
            // 
 | 
			
		||||
            // flowPanelLeft
 | 
			
		||||
            // flowPanel
 | 
			
		||||
            // 
 | 
			
		||||
            this.flowPanelLeft.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
 | 
			
		||||
            this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Left)));
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelBrowserSettings);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkHardwareAcceleration);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkAutomaticallyDetectColorProfile);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkTouchAdjustment);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelCache);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.btnClearCache);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.panelClearCacheAuto);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelProxy);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkUseSystemProxyForAllConnections);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelDevTools);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkDevToolsInContextMenu);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkDevToolsWindowOnTop);
 | 
			
		||||
            this.flowPanelLeft.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
 | 
			
		||||
            this.flowPanelLeft.Location = new System.Drawing.Point(9, 9);
 | 
			
		||||
            this.flowPanelLeft.Name = "flowPanelLeft";
 | 
			
		||||
            this.flowPanelLeft.Size = new System.Drawing.Size(300, 462);
 | 
			
		||||
            this.flowPanelLeft.TabIndex = 0;
 | 
			
		||||
            this.flowPanelLeft.WrapContents = false;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelBrowserSettings
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelBrowserSettings.AutoSize = true;
 | 
			
		||||
            this.labelBrowserSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelBrowserSettings.Location = new System.Drawing.Point(0, 0);
 | 
			
		||||
            this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
 | 
			
		||||
            this.labelBrowserSettings.Name = "labelBrowserSettings";
 | 
			
		||||
            this.labelBrowserSettings.Size = new System.Drawing.Size(143, 19);
 | 
			
		||||
            this.labelBrowserSettings.TabIndex = 0;
 | 
			
		||||
            this.labelBrowserSettings.Text = "BROWSER SETTINGS";
 | 
			
		||||
            // 
 | 
			
		||||
            // checkHardwareAcceleration
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkHardwareAcceleration.AutoSize = true;
 | 
			
		||||
            this.checkHardwareAcceleration.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 23);
 | 
			
		||||
            this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
 | 
			
		||||
            this.checkHardwareAcceleration.Size = new System.Drawing.Size(146, 19);
 | 
			
		||||
            this.checkHardwareAcceleration.TabIndex = 1;
 | 
			
		||||
            this.checkHardwareAcceleration.Text = "Hardware Acceleration";
 | 
			
		||||
            this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // checkAutomaticallyDetectColorProfile
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.AutoSize = true;
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.Location = new System.Drawing.Point(6, 47);
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.Name = "checkAutomaticallyDetectColorProfile";
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.Size = new System.Drawing.Size(206, 19);
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.TabIndex = 2;
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.Text = "Automatically Detect Color Profile";
 | 
			
		||||
            this.checkAutomaticallyDetectColorProfile.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // checkTouchAdjustment
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkTouchAdjustment.AutoSize = true;
 | 
			
		||||
            this.checkTouchAdjustment.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkTouchAdjustment.Location = new System.Drawing.Point(6, 71);
 | 
			
		||||
            this.checkTouchAdjustment.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkTouchAdjustment.Name = "checkTouchAdjustment";
 | 
			
		||||
            this.checkTouchAdjustment.Size = new System.Drawing.Size(163, 19);
 | 
			
		||||
            this.checkTouchAdjustment.TabIndex = 3;
 | 
			
		||||
            this.checkTouchAdjustment.Text = "Touch Screen Adjustment";
 | 
			
		||||
            this.checkTouchAdjustment.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelProxy
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelProxy.AutoSize = true;
 | 
			
		||||
            this.labelProxy.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelProxy.Location = new System.Drawing.Point(0, 231);
 | 
			
		||||
            this.labelProxy.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
 | 
			
		||||
            this.labelProxy.Name = "labelProxy";
 | 
			
		||||
            this.labelProxy.Size = new System.Drawing.Size(54, 19);
 | 
			
		||||
            this.labelProxy.TabIndex = 7;
 | 
			
		||||
            this.labelProxy.Text = "PROXY";
 | 
			
		||||
            // 
 | 
			
		||||
            // checkUseSystemProxyForAllConnections
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.AutoSize = true;
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.Location = new System.Drawing.Point(6, 257);
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.Name = "checkUseSystemProxyForAllConnections";
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.Size = new System.Drawing.Size(223, 19);
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.TabIndex = 8;
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.Text = "Use System Proxy for All Connections";
 | 
			
		||||
            this.checkUseSystemProxyForAllConnections.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelDevTools
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelDevTools.AutoSize = true;
 | 
			
		||||
            this.labelDevTools.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelDevTools.Location = new System.Drawing.Point(0, 308);
 | 
			
		||||
            this.labelDevTools.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
 | 
			
		||||
            this.labelDevTools.Name = "labelDevTools";
 | 
			
		||||
            this.labelDevTools.Size = new System.Drawing.Size(156, 19);
 | 
			
		||||
            this.labelDevTools.TabIndex = 9;
 | 
			
		||||
            this.labelDevTools.Text = "DEVELOPMENT TOOLS";
 | 
			
		||||
            // 
 | 
			
		||||
            // checkDevToolsInContextMenu
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkDevToolsInContextMenu.AutoSize = true;
 | 
			
		||||
            this.checkDevToolsInContextMenu.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkDevToolsInContextMenu.Location = new System.Drawing.Point(6, 334);
 | 
			
		||||
            this.checkDevToolsInContextMenu.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
 | 
			
		||||
            this.checkDevToolsInContextMenu.Name = "checkDevToolsInContextMenu";
 | 
			
		||||
            this.checkDevToolsInContextMenu.Size = new System.Drawing.Size(201, 19);
 | 
			
		||||
            this.checkDevToolsInContextMenu.TabIndex = 10;
 | 
			
		||||
            this.checkDevToolsInContextMenu.Text = "Show Dev Tools in Context Menu";
 | 
			
		||||
            this.checkDevToolsInContextMenu.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // checkDevToolsWindowOnTop
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkDevToolsWindowOnTop.AutoSize = true;
 | 
			
		||||
            this.checkDevToolsWindowOnTop.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkDevToolsWindowOnTop.Location = new System.Drawing.Point(6, 358);
 | 
			
		||||
            this.checkDevToolsWindowOnTop.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkDevToolsWindowOnTop.Name = "checkDevToolsWindowOnTop";
 | 
			
		||||
            this.checkDevToolsWindowOnTop.Size = new System.Drawing.Size(168, 19);
 | 
			
		||||
            this.checkDevToolsWindowOnTop.TabIndex = 11;
 | 
			
		||||
            this.checkDevToolsWindowOnTop.Text = "Dev Tools Window On Top";
 | 
			
		||||
            this.checkDevToolsWindowOnTop.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // flowPanelRight
 | 
			
		||||
            // 
 | 
			
		||||
            this.flowPanelRight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Left)));
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelApp);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.panelAppButtons);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelConfiguration);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.panelConfiguration);
 | 
			
		||||
            this.flowPanelRight.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
 | 
			
		||||
            this.flowPanelRight.Location = new System.Drawing.Point(322, 9);
 | 
			
		||||
            this.flowPanelRight.Name = "flowPanelRight";
 | 
			
		||||
            this.flowPanelRight.Size = new System.Drawing.Size(300, 462);
 | 
			
		||||
            this.flowPanelRight.TabIndex = 1;
 | 
			
		||||
            this.flowPanelRight.WrapContents = false;
 | 
			
		||||
            // 
 | 
			
		||||
            // panelSeparator
 | 
			
		||||
            // 
 | 
			
		||||
            this.panelSeparator.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Left)));
 | 
			
		||||
            this.panelSeparator.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(200)))), ((int)(((byte)(200)))));
 | 
			
		||||
            this.panelSeparator.Location = new System.Drawing.Point(312, 0);
 | 
			
		||||
            this.panelSeparator.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0);
 | 
			
		||||
            this.panelSeparator.Name = "panelSeparator";
 | 
			
		||||
            this.panelSeparator.Size = new System.Drawing.Size(1, 480);
 | 
			
		||||
            this.panelSeparator.TabIndex = 3;
 | 
			
		||||
            this.flowPanel.Controls.Add(this.labelApp);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.panelAppButtons);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.labelCache);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.btnClearCache);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.panelClearCacheAuto);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.labelConfiguration);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.panelConfiguration);
 | 
			
		||||
            this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
 | 
			
		||||
            this.flowPanel.Location = new System.Drawing.Point(9, 9);
 | 
			
		||||
            this.flowPanel.Name = "flowPanel";
 | 
			
		||||
            this.flowPanel.Size = new System.Drawing.Size(300, 462);
 | 
			
		||||
            this.flowPanel.TabIndex = 0;
 | 
			
		||||
            this.flowPanel.WrapContents = false;
 | 
			
		||||
            // 
 | 
			
		||||
            // TabSettingsAdvanced
 | 
			
		||||
            // 
 | 
			
		||||
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
 | 
			
		||||
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 | 
			
		||||
            this.Controls.Add(this.panelSeparator);
 | 
			
		||||
            this.Controls.Add(this.flowPanelRight);
 | 
			
		||||
            this.Controls.Add(this.flowPanelLeft);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.Controls.Add(this.flowPanel);
 | 
			
		||||
            this.Name = "TabSettingsAdvanced";
 | 
			
		||||
            this.Size = new System.Drawing.Size(631, 480);
 | 
			
		||||
            ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit();
 | 
			
		||||
@@ -410,10 +259,8 @@
 | 
			
		||||
            this.panelClearCacheAuto.ResumeLayout(false);
 | 
			
		||||
            this.panelClearCacheAuto.PerformLayout();
 | 
			
		||||
            this.panelConfiguration.ResumeLayout(false);
 | 
			
		||||
            this.flowPanelLeft.ResumeLayout(false);
 | 
			
		||||
            this.flowPanelLeft.PerformLayout();
 | 
			
		||||
            this.flowPanelRight.ResumeLayout(false);
 | 
			
		||||
            this.flowPanelRight.PerformLayout();
 | 
			
		||||
            this.flowPanel.ResumeLayout(false);
 | 
			
		||||
            this.flowPanel.PerformLayout();
 | 
			
		||||
            this.ResumeLayout(false);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
@@ -436,17 +283,6 @@
 | 
			
		||||
        private System.Windows.Forms.Label labelConfiguration;
 | 
			
		||||
        private Controls.NumericUpDownEx numClearCacheThreshold;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkClearCacheAuto;
 | 
			
		||||
        private System.Windows.Forms.FlowLayoutPanel flowPanelLeft;
 | 
			
		||||
        private System.Windows.Forms.Label labelDevTools;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkDevToolsInContextMenu;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkDevToolsWindowOnTop;
 | 
			
		||||
        private System.Windows.Forms.Label labelProxy;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkUseSystemProxyForAllConnections;
 | 
			
		||||
        private System.Windows.Forms.FlowLayoutPanel flowPanelRight;
 | 
			
		||||
        private System.Windows.Forms.Label labelBrowserSettings;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkTouchAdjustment;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkAutomaticallyDetectColorProfile;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkHardwareAcceleration;
 | 
			
		||||
        private System.Windows.Forms.Panel panelSeparator;
 | 
			
		||||
        private System.Windows.Forms.FlowLayoutPanel flowPanel;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										157
									
								
								Core/Other/Settings/TabSettingsAdvanced.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								Core/Other/Settings/TabSettingsAdvanced.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Configuration;
 | 
			
		||||
using TweetDuck.Core.Controls;
 | 
			
		||||
using TweetDuck.Core.Management;
 | 
			
		||||
using TweetDuck.Core.Other.Settings.Dialogs;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings{
 | 
			
		||||
    sealed partial class TabSettingsAdvanced : BaseTabSettings{
 | 
			
		||||
        private readonly Action<string> reinjectBrowserCSS;
 | 
			
		||||
        private readonly Action openDevTools;
 | 
			
		||||
 | 
			
		||||
        public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
 | 
			
		||||
            this.reinjectBrowserCSS = reinjectBrowserCSS;
 | 
			
		||||
            this.openDevTools = openDevTools;
 | 
			
		||||
 | 
			
		||||
            // application
 | 
			
		||||
            
 | 
			
		||||
            toolTip.SetToolTip(btnOpenAppFolder, "Opens the folder where the app is located.");
 | 
			
		||||
            toolTip.SetToolTip(btnOpenDataFolder, "Opens the folder where your profile data is located.");
 | 
			
		||||
            toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
 | 
			
		||||
            toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
 | 
			
		||||
 | 
			
		||||
            // browser cache
 | 
			
		||||
 | 
			
		||||
            toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
 | 
			
		||||
            toolTip.SetToolTip(checkClearCacheAuto, "Automatically clears cache when its size exceeds the set threshold. Note that cache can only be cleared when closing TweetDuck.");
 | 
			
		||||
 | 
			
		||||
            checkClearCacheAuto.Checked = SysConfig.ClearCacheAutomatically;
 | 
			
		||||
            numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
 | 
			
		||||
            numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
 | 
			
		||||
            
 | 
			
		||||
            BrowserCache.GetCacheSize(task => {
 | 
			
		||||
                string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result/(1024.0*1024.0))+" MB" : "unknown";
 | 
			
		||||
                this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // configuration
 | 
			
		||||
 | 
			
		||||
            toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
 | 
			
		||||
            toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void OnReady(){
 | 
			
		||||
            btnOpenAppFolder.Click += btnOpenAppFolder_Click;
 | 
			
		||||
            btnOpenDataFolder.Click += btnOpenDataFolder_Click;
 | 
			
		||||
            btnRestart.Click += btnRestart_Click;
 | 
			
		||||
            btnRestartArgs.Click += btnRestartArgs_Click;
 | 
			
		||||
 | 
			
		||||
            btnClearCache.Click += btnClearCache_Click;
 | 
			
		||||
            checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
 | 
			
		||||
            
 | 
			
		||||
            btnEditCefArgs.Click += btnEditCefArgs_Click;
 | 
			
		||||
            btnEditCSS.Click += btnEditCSS_Click;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void OnClosing(){
 | 
			
		||||
            SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked;
 | 
			
		||||
            SysConfig.ClearCacheThreshold = (int)numClearCacheThreshold.Value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region Application
 | 
			
		||||
        
 | 
			
		||||
        private void btnOpenAppFolder_Click(object sender, EventArgs e){
 | 
			
		||||
            using(Process.Start("explorer.exe", "\""+Program.ProgramPath+"\"")){}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnOpenDataFolder_Click(object sender, EventArgs e){
 | 
			
		||||
            using(Process.Start("explorer.exe", "\""+Program.StoragePath+"\"")){}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnRestart_Click(object sender, EventArgs e){
 | 
			
		||||
            Program.Restart();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnRestartArgs_Click(object sender, EventArgs e){
 | 
			
		||||
            using(DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean())){
 | 
			
		||||
                if (dialog.ShowDialog() == DialogResult.OK){
 | 
			
		||||
                    Program.RestartWithArgs(dialog.Args);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
        #region Browser Cache
 | 
			
		||||
 | 
			
		||||
        private void btnClearCache_Click(object sender, EventArgs e){
 | 
			
		||||
            btnClearCache.Enabled = false;
 | 
			
		||||
            BrowserCache.SetClearOnExit();
 | 
			
		||||
            FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void checkClearCacheAuto_CheckedChanged(object sender, EventArgs e){
 | 
			
		||||
            numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
        #region Configuration
 | 
			
		||||
 | 
			
		||||
        private void btnEditCefArgs_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs);
 | 
			
		||||
 | 
			
		||||
            form.VisibleChanged += (sender2, args2) => {
 | 
			
		||||
                form.MoveToCenter(ParentForm);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            form.FormClosed += (sender2, args2) => {
 | 
			
		||||
                RestoreParentForm();
 | 
			
		||||
 | 
			
		||||
                if (form.DialogResult == DialogResult.OK){
 | 
			
		||||
                    Config.CustomCefArgs = form.CefArgs;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                form.Dispose();
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            form.Show(ParentForm);
 | 
			
		||||
            NativeMethods.SetFormDisabled(ParentForm, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnEditCSS_Click(object sender, EventArgs e){
 | 
			
		||||
            DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools);
 | 
			
		||||
 | 
			
		||||
            form.VisibleChanged += (sender2, args2) => {
 | 
			
		||||
                form.MoveToCenter(ParentForm);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            form.FormClosed += (sender2, args2) => {
 | 
			
		||||
                RestoreParentForm();
 | 
			
		||||
 | 
			
		||||
                if (form.DialogResult == DialogResult.OK){
 | 
			
		||||
                    Config.CustomBrowserCSS = form.BrowserCSS;
 | 
			
		||||
                    Config.CustomNotificationCSS = form.NotificationCSS;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing
 | 
			
		||||
                form.Dispose();
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            form.Show(ParentForm);
 | 
			
		||||
            NativeMethods.SetFormDisabled(ParentForm, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void RestoreParentForm(){
 | 
			
		||||
            if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event
 | 
			
		||||
                NativeMethods.SetFormDisabled(ParentForm, false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										182
									
								
								Core/Other/Settings/TabSettingsFeedback.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								Core/Other/Settings/TabSettingsFeedback.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings {
 | 
			
		||||
    partial class TabSettingsFeedback {
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private System.ComponentModel.IContainer components = null;
 | 
			
		||||
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// Clean up any resources being used.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
 | 
			
		||||
        protected override void Dispose(bool disposing) {
 | 
			
		||||
            if (disposing && (components != null)) {
 | 
			
		||||
                components.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            base.Dispose(disposing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region Component Designer generated code
 | 
			
		||||
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// Required method for Designer support - do not modify 
 | 
			
		||||
        /// the contents of this method with the code editor.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private void InitializeComponent() {
 | 
			
		||||
            this.components = new System.ComponentModel.Container();
 | 
			
		||||
            this.panelDataCollection = new System.Windows.Forms.Panel();
 | 
			
		||||
            this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
 | 
			
		||||
            this.checkDataCollection = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelDataCollectionMessage = new System.Windows.Forms.Label();
 | 
			
		||||
            this.btnViewReport = new System.Windows.Forms.Button();
 | 
			
		||||
            this.btnSendFeedback = new System.Windows.Forms.Button();
 | 
			
		||||
            this.labelDataCollection = new System.Windows.Forms.Label();
 | 
			
		||||
            this.labelFeedback = new System.Windows.Forms.Label();
 | 
			
		||||
            this.toolTip = new System.Windows.Forms.ToolTip(this.components);
 | 
			
		||||
            this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
 | 
			
		||||
            this.panelDataCollection.SuspendLayout();
 | 
			
		||||
            this.flowPanel.SuspendLayout();
 | 
			
		||||
            this.SuspendLayout();
 | 
			
		||||
            // 
 | 
			
		||||
            // panelDataCollection
 | 
			
		||||
            // 
 | 
			
		||||
            this.panelDataCollection.Controls.Add(this.labelDataCollectionLink);
 | 
			
		||||
            this.panelDataCollection.Controls.Add(this.checkDataCollection);
 | 
			
		||||
            this.panelDataCollection.Location = new System.Drawing.Point(0, 78);
 | 
			
		||||
            this.panelDataCollection.Margin = new System.Windows.Forms.Padding(0);
 | 
			
		||||
            this.panelDataCollection.Name = "panelDataCollection";
 | 
			
		||||
            this.panelDataCollection.Size = new System.Drawing.Size(300, 28);
 | 
			
		||||
            this.panelDataCollection.TabIndex = 3;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelDataCollectionLink
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelDataCollectionLink.AutoSize = true;
 | 
			
		||||
            this.labelDataCollectionLink.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10);
 | 
			
		||||
            this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
 | 
			
		||||
            this.labelDataCollectionLink.Location = new System.Drawing.Point(153, 4);
 | 
			
		||||
            this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0);
 | 
			
		||||
            this.labelDataCollectionLink.Name = "labelDataCollectionLink";
 | 
			
		||||
            this.labelDataCollectionLink.Size = new System.Drawing.Size(71, 21);
 | 
			
		||||
            this.labelDataCollectionLink.TabIndex = 1;
 | 
			
		||||
            this.labelDataCollectionLink.TabStop = true;
 | 
			
		||||
            this.labelDataCollectionLink.Text = "(learn more)";
 | 
			
		||||
            this.labelDataCollectionLink.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
 | 
			
		||||
            this.labelDataCollectionLink.UseCompatibleTextRendering = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // checkDataCollection
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkDataCollection.AutoSize = true;
 | 
			
		||||
            this.checkDataCollection.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkDataCollection.Location = new System.Drawing.Point(6, 6);
 | 
			
		||||
            this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 6, 0, 2);
 | 
			
		||||
            this.checkDataCollection.Name = "checkDataCollection";
 | 
			
		||||
            this.checkDataCollection.Size = new System.Drawing.Size(147, 19);
 | 
			
		||||
            this.checkDataCollection.TabIndex = 0;
 | 
			
		||||
            this.checkDataCollection.Text = "Send Anonymous Data";
 | 
			
		||||
            this.checkDataCollection.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelDataCollectionMessage
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelDataCollectionMessage.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.labelDataCollectionMessage.Location = new System.Drawing.Point(6, 143);
 | 
			
		||||
            this.labelDataCollectionMessage.Margin = new System.Windows.Forms.Padding(6);
 | 
			
		||||
            this.labelDataCollectionMessage.Name = "labelDataCollectionMessage";
 | 
			
		||||
            this.labelDataCollectionMessage.Size = new System.Drawing.Size(288, 67);
 | 
			
		||||
            this.labelDataCollectionMessage.TabIndex = 5;
 | 
			
		||||
            // 
 | 
			
		||||
            // btnViewReport
 | 
			
		||||
            // 
 | 
			
		||||
            this.btnViewReport.AutoSize = true;
 | 
			
		||||
            this.btnViewReport.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.btnViewReport.Location = new System.Drawing.Point(5, 109);
 | 
			
		||||
            this.btnViewReport.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
 | 
			
		||||
            this.btnViewReport.Name = "btnViewReport";
 | 
			
		||||
            this.btnViewReport.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
 | 
			
		||||
            this.btnViewReport.Size = new System.Drawing.Size(155, 25);
 | 
			
		||||
            this.btnViewReport.TabIndex = 4;
 | 
			
		||||
            this.btnViewReport.Text = "View My Analytics Report";
 | 
			
		||||
            this.btnViewReport.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // btnSendFeedback
 | 
			
		||||
            // 
 | 
			
		||||
            this.btnSendFeedback.AutoSize = true;
 | 
			
		||||
            this.btnSendFeedback.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.btnSendFeedback.Location = new System.Drawing.Point(5, 23);
 | 
			
		||||
            this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
 | 
			
		||||
            this.btnSendFeedback.Name = "btnSendFeedback";
 | 
			
		||||
            this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
 | 
			
		||||
            this.btnSendFeedback.Size = new System.Drawing.Size(170, 25);
 | 
			
		||||
            this.btnSendFeedback.TabIndex = 1;
 | 
			
		||||
            this.btnSendFeedback.Text = "Send Feedback / Bug Report";
 | 
			
		||||
            this.btnSendFeedback.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelDataCollection
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelDataCollection.AutoSize = true;
 | 
			
		||||
            this.labelDataCollection.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelDataCollection.Location = new System.Drawing.Point(3, 63);
 | 
			
		||||
            this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
 | 
			
		||||
            this.labelDataCollection.Name = "labelDataCollection";
 | 
			
		||||
            this.labelDataCollection.Size = new System.Drawing.Size(88, 15);
 | 
			
		||||
            this.labelDataCollection.TabIndex = 2;
 | 
			
		||||
            this.labelDataCollection.Text = "Data Collection";
 | 
			
		||||
            // 
 | 
			
		||||
            // labelFeedback
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelFeedback.AutoSize = true;
 | 
			
		||||
            this.labelFeedback.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelFeedback.Location = new System.Drawing.Point(0, 0);
 | 
			
		||||
            this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
 | 
			
		||||
            this.labelFeedback.Name = "labelFeedback";
 | 
			
		||||
            this.labelFeedback.Size = new System.Drawing.Size(75, 19);
 | 
			
		||||
            this.labelFeedback.TabIndex = 0;
 | 
			
		||||
            this.labelFeedback.Text = "FEEDBACK";
 | 
			
		||||
            // 
 | 
			
		||||
            // flowPanel
 | 
			
		||||
            // 
 | 
			
		||||
            this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Left) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Right)));
 | 
			
		||||
            this.flowPanel.Controls.Add(this.labelFeedback);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.btnSendFeedback);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.labelDataCollection);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.panelDataCollection);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.btnViewReport);
 | 
			
		||||
            this.flowPanel.Controls.Add(this.labelDataCollectionMessage);
 | 
			
		||||
            this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
 | 
			
		||||
            this.flowPanel.Location = new System.Drawing.Point(9, 9);
 | 
			
		||||
            this.flowPanel.Name = "flowPanel";
 | 
			
		||||
            this.flowPanel.Size = new System.Drawing.Size(300, 462);
 | 
			
		||||
            this.flowPanel.TabIndex = 0;
 | 
			
		||||
            this.flowPanel.WrapContents = false;
 | 
			
		||||
            // 
 | 
			
		||||
            // TabSettingsFeedback
 | 
			
		||||
            // 
 | 
			
		||||
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
 | 
			
		||||
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 | 
			
		||||
            this.Controls.Add(this.flowPanel);
 | 
			
		||||
            this.Name = "TabSettingsFeedback";
 | 
			
		||||
            this.Size = new System.Drawing.Size(631, 480);
 | 
			
		||||
            this.panelDataCollection.ResumeLayout(false);
 | 
			
		||||
            this.panelDataCollection.PerformLayout();
 | 
			
		||||
            this.flowPanel.ResumeLayout(false);
 | 
			
		||||
            this.flowPanel.PerformLayout();
 | 
			
		||||
            this.ResumeLayout(false);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        private System.Windows.Forms.Panel panelDataCollection;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkDataCollection;
 | 
			
		||||
        private System.Windows.Forms.Label labelDataCollection;
 | 
			
		||||
        private System.Windows.Forms.Label labelFeedback;
 | 
			
		||||
        private System.Windows.Forms.ToolTip toolTip;
 | 
			
		||||
        private System.Windows.Forms.LinkLabel labelDataCollectionLink;
 | 
			
		||||
        private System.Windows.Forms.Button btnSendFeedback;
 | 
			
		||||
        private System.Windows.Forms.Button btnViewReport;
 | 
			
		||||
        private System.Windows.Forms.Label labelDataCollectionMessage;
 | 
			
		||||
        private System.Windows.Forms.FlowLayoutPanel flowPanel;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								Core/Other/Settings/TabSettingsFeedback.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								Core/Other/Settings/TabSettingsFeedback.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Windows.Forms;
 | 
			
		||||
using TweetDuck.Core.Other.Analytics;
 | 
			
		||||
using TweetDuck.Core.Other.Settings.Dialogs;
 | 
			
		||||
using TweetDuck.Core.Utils;
 | 
			
		||||
using TweetDuck.Plugins;
 | 
			
		||||
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings{
 | 
			
		||||
    sealed partial class TabSettingsFeedback : BaseTabSettings{
 | 
			
		||||
        private readonly AnalyticsFile analyticsFile;
 | 
			
		||||
        private readonly AnalyticsReportGenerator.ExternalInfo analyticsInfo;
 | 
			
		||||
        private readonly PluginManager plugins;
 | 
			
		||||
 | 
			
		||||
        public TabSettingsFeedback(AnalyticsManager analytics, AnalyticsReportGenerator.ExternalInfo analyticsInfo, PluginManager plugins){
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
            
 | 
			
		||||
            this.analyticsFile = analytics?.File ?? AnalyticsFile.Load(Program.AnalyticsFilePath);
 | 
			
		||||
            this.analyticsInfo = analyticsInfo;
 | 
			
		||||
            this.plugins = plugins;
 | 
			
		||||
 | 
			
		||||
            // feedback
 | 
			
		||||
 | 
			
		||||
            checkDataCollection.Checked = Config.AllowDataCollection;
 | 
			
		||||
 | 
			
		||||
            if (analytics != null){
 | 
			
		||||
                string collectionTime = analyticsFile.LastCollectionMessage;
 | 
			
		||||
                labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: "+collectionTime;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void OnReady(){
 | 
			
		||||
            btnSendFeedback.Click += btnSendFeedback_Click;
 | 
			
		||||
            checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged;
 | 
			
		||||
            labelDataCollectionLink.LinkClicked += labelDataCollectionLink_LinkClicked;
 | 
			
		||||
            btnViewReport.Click += btnViewReport_Click;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region Feedback
 | 
			
		||||
 | 
			
		||||
        private void btnSendFeedback_Click(object sender, EventArgs e){
 | 
			
		||||
            BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/issues/new");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void checkDataCollection_CheckedChanged(object sender, EventArgs e){
 | 
			
		||||
            Config.AllowDataCollection = checkDataCollection.Checked;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
 | 
			
		||||
            BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void btnViewReport_Click(object sender, EventArgs e){
 | 
			
		||||
            using(DialogSettingsAnalytics dialog = new DialogSettingsAnalytics(AnalyticsReportGenerator.Create(analyticsFile, analyticsInfo, plugins))){
 | 
			
		||||
                dialog.ShowDialog();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace TweetDuck.Dialogs.Settings {
 | 
			
		||||
namespace TweetDuck.Core.Other.Settings {
 | 
			
		||||
    partial class TabSettingsGeneral {
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// Required designer variable.
 | 
			
		||||
@@ -34,43 +34,36 @@
 | 
			
		||||
            this.trackBarZoom = new System.Windows.Forms.TrackBar();
 | 
			
		||||
            this.labelZoom = new System.Windows.Forms.Label();
 | 
			
		||||
            this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
 | 
			
		||||
            this.labelUI = new System.Windows.Forms.Label();
 | 
			
		||||
            this.panelZoom = new System.Windows.Forms.Panel();
 | 
			
		||||
            this.checkAnimatedAvatars = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelUpdates = new System.Windows.Forms.Label();
 | 
			
		||||
            this.flowPanelLeft = new System.Windows.Forms.FlowLayoutPanel();
 | 
			
		||||
            this.labelUI = new System.Windows.Forms.Label();
 | 
			
		||||
            this.checkFocusDmInput = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelTray = new System.Windows.Forms.Label();
 | 
			
		||||
            this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.labelTrayIcon = new System.Windows.Forms.Label();
 | 
			
		||||
            this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelBrowserSettings = new System.Windows.Forms.Label();
 | 
			
		||||
            this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelTweetDeckSettings = new System.Windows.Forms.Label();
 | 
			
		||||
            this.checkHideTweetsByNftUsers = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.checkTouchAdjustment = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelBrowserPath = new System.Windows.Forms.Label();
 | 
			
		||||
            this.comboBoxCustomBrowser = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.comboBoxBrowserPath = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.labelSearchEngine = new System.Windows.Forms.Label();
 | 
			
		||||
            this.comboBoxSearchEngine = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.flowPanelRight = new System.Windows.Forms.FlowLayoutPanel();
 | 
			
		||||
            this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelLocales = new System.Windows.Forms.Label();
 | 
			
		||||
            this.checkSpellCheck = new System.Windows.Forms.CheckBox();
 | 
			
		||||
            this.labelSpellCheckLanguage = new System.Windows.Forms.Label();
 | 
			
		||||
            this.comboBoxSpellCheckLanguage = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.labelTranslationTarget = new System.Windows.Forms.Label();
 | 
			
		||||
            this.comboBoxTranslationTarget = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.labelFirstDayOfWeek = new System.Windows.Forms.Label();
 | 
			
		||||
            this.comboBoxFirstDayOfWeek = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.labelExternalApplications = new System.Windows.Forms.Label();
 | 
			
		||||
            this.panelCustomBrowser = new System.Windows.Forms.Panel();
 | 
			
		||||
            this.btnCustomBrowserChange = new System.Windows.Forms.Button();
 | 
			
		||||
            this.labelVideoPlayerPath = new System.Windows.Forms.Label();
 | 
			
		||||
            this.panelCustomVideoPlayer = new System.Windows.Forms.Panel();
 | 
			
		||||
            this.comboBoxCustomVideoPlayer = new System.Windows.Forms.ComboBox();
 | 
			
		||||
            this.btnCustomVideoPlayerChange = new System.Windows.Forms.Button();
 | 
			
		||||
            this.panelSeparator = new System.Windows.Forms.Panel();
 | 
			
		||||
            ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
 | 
			
		||||
            this.panelZoom.SuspendLayout();
 | 
			
		||||
            this.flowPanelLeft.SuspendLayout();
 | 
			
		||||
            this.flowPanelRight.SuspendLayout();
 | 
			
		||||
            this.panelCustomBrowser.SuspendLayout();
 | 
			
		||||
            this.panelCustomVideoPlayer.SuspendLayout();
 | 
			
		||||
            this.SuspendLayout();
 | 
			
		||||
            // 
 | 
			
		||||
            // checkExpandLinks
 | 
			
		||||
@@ -89,7 +82,7 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkUpdateNotifications.AutoSize = true;
 | 
			
		||||
            this.checkUpdateNotifications.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 381);
 | 
			
		||||
            this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 393);
 | 
			
		||||
            this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
 | 
			
		||||
            this.checkUpdateNotifications.Name = "checkUpdateNotifications";
 | 
			
		||||
            this.checkUpdateNotifications.Size = new System.Drawing.Size(182, 19);
 | 
			
		||||
@@ -101,7 +94,7 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.btnCheckUpdates.AutoSize = true;
 | 
			
		||||
            this.btnCheckUpdates.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.btnCheckUpdates.Location = new System.Drawing.Point(5, 405);
 | 
			
		||||
            this.btnCheckUpdates.Location = new System.Drawing.Point(5, 417);
 | 
			
		||||
            this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
 | 
			
		||||
            this.btnCheckUpdates.Name = "btnCheckUpdates";
 | 
			
		||||
            this.btnCheckUpdates.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
 | 
			
		||||
@@ -114,7 +107,7 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
 | 
			
		||||
            this.labelZoomValue.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.labelZoomValue.Location = new System.Drawing.Point(176, 4);
 | 
			
		||||
            this.labelZoomValue.Location = new System.Drawing.Point(147, 4);
 | 
			
		||||
            this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
 | 
			
		||||
            this.labelZoomValue.Name = "labelZoomValue";
 | 
			
		||||
            this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
 | 
			
		||||
@@ -126,23 +119,23 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkBestImageQuality.AutoSize = true;
 | 
			
		||||
            this.checkBestImageQuality.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkBestImageQuality.Location = new System.Drawing.Point(6, 259);
 | 
			
		||||
            this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
 | 
			
		||||
            this.checkBestImageQuality.Location = new System.Drawing.Point(6, 98);
 | 
			
		||||
            this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkBestImageQuality.Name = "checkBestImageQuality";
 | 
			
		||||
            this.checkBestImageQuality.Size = new System.Drawing.Size(182, 19);
 | 
			
		||||
            this.checkBestImageQuality.TabIndex = 9;
 | 
			
		||||
            this.checkBestImageQuality.Text = "Download Best Image Quality";
 | 
			
		||||
            this.checkBestImageQuality.Size = new System.Drawing.Size(125, 19);
 | 
			
		||||
            this.checkBestImageQuality.TabIndex = 4;
 | 
			
		||||
            this.checkBestImageQuality.Text = "Best Image Quality";
 | 
			
		||||
            this.checkBestImageQuality.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // checkOpenSearchInFirstColumn
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.AutoSize = true;
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 74);
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 50);
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(245, 19);
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.TabIndex = 3;
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.TabIndex = 2;
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
 | 
			
		||||
            this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
@@ -155,7 +148,7 @@
 | 
			
		||||
            this.trackBarZoom.Maximum = 200;
 | 
			
		||||
            this.trackBarZoom.Minimum = 50;
 | 
			
		||||
            this.trackBarZoom.Name = "trackBarZoom";
 | 
			
		||||
            this.trackBarZoom.Size = new System.Drawing.Size(177, 30);
 | 
			
		||||
            this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
 | 
			
		||||
            this.trackBarZoom.SmallChange = 5;
 | 
			
		||||
            this.trackBarZoom.TabIndex = 0;
 | 
			
		||||
            this.trackBarZoom.TickFrequency = 25;
 | 
			
		||||
@@ -177,6 +170,17 @@
 | 
			
		||||
            this.zoomUpdateTimer.Interval = 250;
 | 
			
		||||
            this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick);
 | 
			
		||||
            // 
 | 
			
		||||
            // labelUI
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelUI.AutoSize = true;
 | 
			
		||||
            this.labelUI.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelUI.Location = new System.Drawing.Point(0, 0);
 | 
			
		||||
            this.labelUI.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
 | 
			
		||||
            this.labelUI.Name = "labelUI";
 | 
			
		||||
            this.labelUI.Size = new System.Drawing.Size(118, 19);
 | 
			
		||||
            this.labelUI.TabIndex = 0;
 | 
			
		||||
            this.labelUI.Text = "USER INTERFACE";
 | 
			
		||||
            // 
 | 
			
		||||
            // panelZoom
 | 
			
		||||
            // 
 | 
			
		||||
            this.panelZoom.Controls.Add(this.trackBarZoom);
 | 
			
		||||
@@ -191,11 +195,11 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkAnimatedAvatars.AutoSize = true;
 | 
			
		||||
            this.checkAnimatedAvatars.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 307);
 | 
			
		||||
            this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 122);
 | 
			
		||||
            this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
 | 
			
		||||
            this.checkAnimatedAvatars.Size = new System.Drawing.Size(158, 19);
 | 
			
		||||
            this.checkAnimatedAvatars.TabIndex = 11;
 | 
			
		||||
            this.checkAnimatedAvatars.TabIndex = 5;
 | 
			
		||||
            this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
 | 
			
		||||
            this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
@@ -203,8 +207,8 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelUpdates.AutoSize = true;
 | 
			
		||||
            this.labelUpdates.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelUpdates.Location = new System.Drawing.Point(0, 355);
 | 
			
		||||
            this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
 | 
			
		||||
            this.labelUpdates.Location = new System.Drawing.Point(0, 367);
 | 
			
		||||
            this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
 | 
			
		||||
            this.labelUpdates.Name = "labelUpdates";
 | 
			
		||||
            this.labelUpdates.Size = new System.Drawing.Size(69, 19);
 | 
			
		||||
            this.labelUpdates.TabIndex = 12;
 | 
			
		||||
@@ -216,16 +220,16 @@
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Left)));
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelUI);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkExpandLinks);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkFocusDmInput);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkOpenSearchInFirstColumn);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkSmoothScrolling);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkBestImageQuality);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkAnimatedAvatars);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelZoom);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.panelZoom);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelTweetDeckSettings);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkBestImageQuality);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkHideTweetsByNftUsers);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkAnimatedAvatars);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelTray);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.comboBoxTrayType);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelTrayIcon);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkTrayHighlight);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.labelUpdates);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.checkUpdateNotifications);
 | 
			
		||||
            this.flowPanelLeft.Controls.Add(this.btnCheckUpdates);
 | 
			
		||||
@@ -236,107 +240,129 @@
 | 
			
		||||
            this.flowPanelLeft.TabIndex = 0;
 | 
			
		||||
            this.flowPanelLeft.WrapContents = false;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelUI
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelUI.AutoSize = true;
 | 
			
		||||
            this.labelUI.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelUI.Location = new System.Drawing.Point(0, 0);
 | 
			
		||||
            this.labelUI.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
 | 
			
		||||
            this.labelUI.Name = "labelUI";
 | 
			
		||||
            this.labelUI.Size = new System.Drawing.Size(118, 19);
 | 
			
		||||
            this.labelUI.TabIndex = 0;
 | 
			
		||||
            this.labelUI.Text = "USER INTERFACE";
 | 
			
		||||
            // 
 | 
			
		||||
            // checkFocusDmInput
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkFocusDmInput.AutoSize = true;
 | 
			
		||||
            this.checkFocusDmInput.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkFocusDmInput.Location = new System.Drawing.Point(6, 50);
 | 
			
		||||
            this.checkFocusDmInput.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkFocusDmInput.Name = "checkFocusDmInput";
 | 
			
		||||
            this.checkFocusDmInput.Size = new System.Drawing.Size(282, 19);
 | 
			
		||||
            this.checkFocusDmInput.TabIndex = 2;
 | 
			
		||||
            this.checkFocusDmInput.Text = "Focus Input Field When Opening Direct Message";
 | 
			
		||||
            this.checkFocusDmInput.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // checkKeepLikeFollowDialogsOpen
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 98);
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 74);
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(190, 19);
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.TabIndex = 4;
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.TabIndex = 3;
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
 | 
			
		||||
            this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelTray
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelTray.AutoSize = true;
 | 
			
		||||
            this.labelTray.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelTray.Location = new System.Drawing.Point(0, 236);
 | 
			
		||||
            this.labelTray.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
 | 
			
		||||
            this.labelTray.Name = "labelTray";
 | 
			
		||||
            this.labelTray.Size = new System.Drawing.Size(99, 19);
 | 
			
		||||
            this.labelTray.TabIndex = 8;
 | 
			
		||||
            this.labelTray.Text = "SYSTEM TRAY";
 | 
			
		||||
            // 
 | 
			
		||||
            // comboBoxTrayType
 | 
			
		||||
            // 
 | 
			
		||||
            this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.comboBoxTrayType.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.comboBoxTrayType.FormattingEnabled = true;
 | 
			
		||||
            this.comboBoxTrayType.Location = new System.Drawing.Point(5, 260);
 | 
			
		||||
            this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
 | 
			
		||||
            this.comboBoxTrayType.Name = "comboBoxTrayType";
 | 
			
		||||
            this.comboBoxTrayType.Size = new System.Drawing.Size(144, 23);
 | 
			
		||||
            this.comboBoxTrayType.TabIndex = 9;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelTrayIcon
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelTrayIcon.AutoSize = true;
 | 
			
		||||
            this.labelTrayIcon.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelTrayIcon.Location = new System.Drawing.Point(3, 295);
 | 
			
		||||
            this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
 | 
			
		||||
            this.labelTrayIcon.Name = "labelTrayIcon";
 | 
			
		||||
            this.labelTrayIcon.Size = new System.Drawing.Size(56, 15);
 | 
			
		||||
            this.labelTrayIcon.TabIndex = 10;
 | 
			
		||||
            this.labelTrayIcon.Text = "Tray Icon";
 | 
			
		||||
            // 
 | 
			
		||||
            // checkTrayHighlight
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkTrayHighlight.AutoSize = true;
 | 
			
		||||
            this.checkTrayHighlight.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkTrayHighlight.Location = new System.Drawing.Point(6, 316);
 | 
			
		||||
            this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
 | 
			
		||||
            this.checkTrayHighlight.Name = "checkTrayHighlight";
 | 
			
		||||
            this.checkTrayHighlight.Size = new System.Drawing.Size(114, 19);
 | 
			
		||||
            this.checkTrayHighlight.TabIndex = 11;
 | 
			
		||||
            this.checkTrayHighlight.Text = "Enable Highlight";
 | 
			
		||||
            this.checkTrayHighlight.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelBrowserSettings
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelBrowserSettings.AutoSize = true;
 | 
			
		||||
            this.labelBrowserSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelBrowserSettings.Location = new System.Drawing.Point(0, 0);
 | 
			
		||||
            this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
 | 
			
		||||
            this.labelBrowserSettings.Name = "labelBrowserSettings";
 | 
			
		||||
            this.labelBrowserSettings.Size = new System.Drawing.Size(143, 19);
 | 
			
		||||
            this.labelBrowserSettings.TabIndex = 0;
 | 
			
		||||
            this.labelBrowserSettings.Text = "BROWSER SETTINGS";
 | 
			
		||||
            // 
 | 
			
		||||
            // checkSmoothScrolling
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkSmoothScrolling.AutoSize = true;
 | 
			
		||||
            this.checkSmoothScrolling.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 122);
 | 
			
		||||
            this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 26);
 | 
			
		||||
            this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
 | 
			
		||||
            this.checkSmoothScrolling.Name = "checkSmoothScrolling";
 | 
			
		||||
            this.checkSmoothScrolling.Size = new System.Drawing.Size(117, 19);
 | 
			
		||||
            this.checkSmoothScrolling.TabIndex = 5;
 | 
			
		||||
            this.checkSmoothScrolling.TabIndex = 1;
 | 
			
		||||
            this.checkSmoothScrolling.Text = "Smooth Scrolling";
 | 
			
		||||
            this.checkSmoothScrolling.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelTweetDeckSettings
 | 
			
		||||
            // checkTouchAdjustment
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelTweetDeckSettings.AutoSize = true;
 | 
			
		||||
            this.labelTweetDeckSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelTweetDeckSettings.Location = new System.Drawing.Point(0, 233);
 | 
			
		||||
            this.labelTweetDeckSettings.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
 | 
			
		||||
            this.labelTweetDeckSettings.Name = "labelTweetDeckSettings";
 | 
			
		||||
            this.labelTweetDeckSettings.Size = new System.Drawing.Size(67, 19);
 | 
			
		||||
            this.labelTweetDeckSettings.TabIndex = 8;
 | 
			
		||||
            this.labelTweetDeckSettings.Text = "TWITTER";
 | 
			
		||||
            // 
 | 
			
		||||
            // checkHideTweetsByNftUsers
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkHideTweetsByNftUsers.AutoSize = true;
 | 
			
		||||
            this.checkHideTweetsByNftUsers.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkHideTweetsByNftUsers.Location = new System.Drawing.Point(6, 283);
 | 
			
		||||
            this.checkHideTweetsByNftUsers.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkHideTweetsByNftUsers.Name = "checkHideTweetsByNftUsers";
 | 
			
		||||
            this.checkHideTweetsByNftUsers.Size = new System.Drawing.Size(231, 19);
 | 
			
		||||
            this.checkHideTweetsByNftUsers.TabIndex = 10;
 | 
			
		||||
            this.checkHideTweetsByNftUsers.Text = "Hide Tweets by Users with NFT Avatars";
 | 
			
		||||
            this.checkHideTweetsByNftUsers.UseVisualStyleBackColor = true;
 | 
			
		||||
            this.checkTouchAdjustment.AutoSize = true;
 | 
			
		||||
            this.checkTouchAdjustment.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkTouchAdjustment.Location = new System.Drawing.Point(6, 50);
 | 
			
		||||
            this.checkTouchAdjustment.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkTouchAdjustment.Name = "checkTouchAdjustment";
 | 
			
		||||
            this.checkTouchAdjustment.Size = new System.Drawing.Size(163, 19);
 | 
			
		||||
            this.checkTouchAdjustment.TabIndex = 2;
 | 
			
		||||
            this.checkTouchAdjustment.Text = "Touch Screen Adjustment";
 | 
			
		||||
            this.checkTouchAdjustment.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelBrowserPath
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelBrowserPath.AutoSize = true;
 | 
			
		||||
            this.labelBrowserPath.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelBrowserPath.Location = new System.Drawing.Point(3, 275);
 | 
			
		||||
            this.labelBrowserPath.Location = new System.Drawing.Point(3, 107);
 | 
			
		||||
            this.labelBrowserPath.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
 | 
			
		||||
            this.labelBrowserPath.Name = "labelBrowserPath";
 | 
			
		||||
            this.labelBrowserPath.Size = new System.Drawing.Size(104, 15);
 | 
			
		||||
            this.labelBrowserPath.TabIndex = 9;
 | 
			
		||||
            this.labelBrowserPath.TabIndex = 4;
 | 
			
		||||
            this.labelBrowserPath.Text = "Open Links With...";
 | 
			
		||||
            // 
 | 
			
		||||
            // comboBoxCustomBrowser
 | 
			
		||||
            // comboBoxBrowserPath
 | 
			
		||||
            // 
 | 
			
		||||
            this.comboBoxCustomBrowser.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.comboBoxCustomBrowser.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.comboBoxCustomBrowser.FormattingEnabled = true;
 | 
			
		||||
            this.comboBoxCustomBrowser.Location = new System.Drawing.Point(5, 1);
 | 
			
		||||
            this.comboBoxCustomBrowser.Margin = new System.Windows.Forms.Padding(5, 1, 3, 0);
 | 
			
		||||
            this.comboBoxCustomBrowser.Name = "comboBoxCustomBrowser";
 | 
			
		||||
            this.comboBoxCustomBrowser.Size = new System.Drawing.Size(173, 23);
 | 
			
		||||
            this.comboBoxCustomBrowser.TabIndex = 0;
 | 
			
		||||
            this.comboBoxBrowserPath.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.comboBoxBrowserPath.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.comboBoxBrowserPath.FormattingEnabled = true;
 | 
			
		||||
            this.comboBoxBrowserPath.Location = new System.Drawing.Point(5, 126);
 | 
			
		||||
            this.comboBoxBrowserPath.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
 | 
			
		||||
            this.comboBoxBrowserPath.Name = "comboBoxBrowserPath";
 | 
			
		||||
            this.comboBoxBrowserPath.Size = new System.Drawing.Size(173, 23);
 | 
			
		||||
            this.comboBoxBrowserPath.TabIndex = 5;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelSearchEngine
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelSearchEngine.AutoSize = true;
 | 
			
		||||
            this.labelSearchEngine.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelSearchEngine.Location = new System.Drawing.Point(3, 389);
 | 
			
		||||
            this.labelSearchEngine.Location = new System.Drawing.Point(3, 164);
 | 
			
		||||
            this.labelSearchEngine.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
 | 
			
		||||
            this.labelSearchEngine.Name = "labelSearchEngine";
 | 
			
		||||
            this.labelSearchEngine.Size = new System.Drawing.Size(82, 15);
 | 
			
		||||
            this.labelSearchEngine.TabIndex = 13;
 | 
			
		||||
            this.labelSearchEngine.TabIndex = 6;
 | 
			
		||||
            this.labelSearchEngine.Text = "Search Engine";
 | 
			
		||||
            // 
 | 
			
		||||
            // comboBoxSearchEngine
 | 
			
		||||
@@ -344,58 +370,69 @@
 | 
			
		||||
            this.comboBoxSearchEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.comboBoxSearchEngine.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.comboBoxSearchEngine.FormattingEnabled = true;
 | 
			
		||||
            this.comboBoxSearchEngine.Location = new System.Drawing.Point(5, 408);
 | 
			
		||||
            this.comboBoxSearchEngine.Location = new System.Drawing.Point(5, 183);
 | 
			
		||||
            this.comboBoxSearchEngine.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
 | 
			
		||||
            this.comboBoxSearchEngine.Name = "comboBoxSearchEngine";
 | 
			
		||||
            this.comboBoxSearchEngine.Size = new System.Drawing.Size(173, 23);
 | 
			
		||||
            this.comboBoxSearchEngine.TabIndex = 14;
 | 
			
		||||
            this.comboBoxSearchEngine.TabIndex = 7;
 | 
			
		||||
            // 
 | 
			
		||||
            // flowPanelRight
 | 
			
		||||
            // 
 | 
			
		||||
            this.flowPanelRight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
 | 
			
		||||
            | System.Windows.Forms.AnchorStyles.Left)));
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelBrowserSettings);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.checkSmoothScrolling);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.checkTouchAdjustment);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.checkHardwareAcceleration);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelBrowserPath);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.comboBoxBrowserPath);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelSearchEngine);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.comboBoxSearchEngine);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelLocales);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.checkSpellCheck);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelSpellCheckLanguage);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.comboBoxSpellCheckLanguage);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelTranslationTarget);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.comboBoxTranslationTarget);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelFirstDayOfWeek);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.comboBoxFirstDayOfWeek);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelExternalApplications);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelBrowserPath);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.panelCustomBrowser);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelVideoPlayerPath);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.panelCustomVideoPlayer);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.labelSearchEngine);
 | 
			
		||||
            this.flowPanelRight.Controls.Add(this.comboBoxSearchEngine);
 | 
			
		||||
            this.flowPanelRight.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
 | 
			
		||||
            this.flowPanelRight.Location = new System.Drawing.Point(322, 9);
 | 
			
		||||
            this.flowPanelRight.Name = "flowPanelRight";
 | 
			
		||||
            this.flowPanelRight.Size = new System.Drawing.Size(300, 462);
 | 
			
		||||
            this.flowPanelRight.TabIndex = 2;
 | 
			
		||||
            this.flowPanelRight.TabIndex = 1;
 | 
			
		||||
            this.flowPanelRight.WrapContents = false;
 | 
			
		||||
            // 
 | 
			
		||||
            // checkHardwareAcceleration
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkHardwareAcceleration.AutoSize = true;
 | 
			
		||||
            this.checkHardwareAcceleration.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 74);
 | 
			
		||||
            this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
 | 
			
		||||
            this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
 | 
			
		||||
            this.checkHardwareAcceleration.Size = new System.Drawing.Size(146, 19);
 | 
			
		||||
            this.checkHardwareAcceleration.TabIndex = 3;
 | 
			
		||||
            this.checkHardwareAcceleration.Text = "Hardware Acceleration";
 | 
			
		||||
            this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelLocales
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelLocales.AutoSize = true;
 | 
			
		||||
            this.labelLocales.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelLocales.Location = new System.Drawing.Point(0, 0);
 | 
			
		||||
            this.labelLocales.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
 | 
			
		||||
            this.labelLocales.Location = new System.Drawing.Point(0, 236);
 | 
			
		||||
            this.labelLocales.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
 | 
			
		||||
            this.labelLocales.Name = "labelLocales";
 | 
			
		||||
            this.labelLocales.Size = new System.Drawing.Size(67, 19);
 | 
			
		||||
            this.labelLocales.TabIndex = 0;
 | 
			
		||||
            this.labelLocales.TabIndex = 8;
 | 
			
		||||
            this.labelLocales.Text = "LOCALES";
 | 
			
		||||
            // 
 | 
			
		||||
            // checkSpellCheck
 | 
			
		||||
            // 
 | 
			
		||||
            this.checkSpellCheck.AutoSize = true;
 | 
			
		||||
            this.checkSpellCheck.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.checkSpellCheck.Location = new System.Drawing.Point(6, 26);
 | 
			
		||||
            this.checkSpellCheck.Location = new System.Drawing.Point(6, 262);
 | 
			
		||||
            this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
 | 
			
		||||
            this.checkSpellCheck.Name = "checkSpellCheck";
 | 
			
		||||
            this.checkSpellCheck.Size = new System.Drawing.Size(125, 19);
 | 
			
		||||
            this.checkSpellCheck.TabIndex = 1;
 | 
			
		||||
            this.checkSpellCheck.TabIndex = 9;
 | 
			
		||||
            this.checkSpellCheck.Text = "Enable Spell Check";
 | 
			
		||||
            this.checkSpellCheck.UseVisualStyleBackColor = true;
 | 
			
		||||
            // 
 | 
			
		||||
@@ -403,11 +440,11 @@
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelSpellCheckLanguage.AutoSize = true;
 | 
			
		||||
            this.labelSpellCheckLanguage.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelSpellCheckLanguage.Location = new System.Drawing.Point(3, 59);
 | 
			
		||||
            this.labelSpellCheckLanguage.Location = new System.Drawing.Point(3, 295);
 | 
			
		||||
            this.labelSpellCheckLanguage.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
 | 
			
		||||
            this.labelSpellCheckLanguage.Name = "labelSpellCheckLanguage";
 | 
			
		||||
            this.labelSpellCheckLanguage.Size = new System.Drawing.Size(123, 15);
 | 
			
		||||
            this.labelSpellCheckLanguage.TabIndex = 2;
 | 
			
		||||
            this.labelSpellCheckLanguage.TabIndex = 10;
 | 
			
		||||
            this.labelSpellCheckLanguage.Text = "Spell Check Language";
 | 
			
		||||
            // 
 | 
			
		||||
            // comboBoxSpellCheckLanguage
 | 
			
		||||
@@ -415,21 +452,21 @@
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.FormattingEnabled = true;
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.Location = new System.Drawing.Point(5, 78);
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.Location = new System.Drawing.Point(5, 314);
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.Name = "comboBoxSpellCheckLanguage";
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.Size = new System.Drawing.Size(290, 23);
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.TabIndex = 3;
 | 
			
		||||
            this.comboBoxSpellCheckLanguage.TabIndex = 11;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelTranslationTarget
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelTranslationTarget.AutoSize = true;
 | 
			
		||||
            this.labelTranslationTarget.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelTranslationTarget.Location = new System.Drawing.Point(3, 116);
 | 
			
		||||
            this.labelTranslationTarget.Location = new System.Drawing.Point(3, 352);
 | 
			
		||||
            this.labelTranslationTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
 | 
			
		||||
            this.labelTranslationTarget.Name = "labelTranslationTarget";
 | 
			
		||||
            this.labelTranslationTarget.Size = new System.Drawing.Size(142, 15);
 | 
			
		||||
            this.labelTranslationTarget.TabIndex = 4;
 | 
			
		||||
            this.labelTranslationTarget.TabIndex = 12;
 | 
			
		||||
            this.labelTranslationTarget.Text = "Bing Translator Language";
 | 
			
		||||
            // 
 | 
			
		||||
            // comboBoxTranslationTarget
 | 
			
		||||
@@ -437,114 +474,11 @@
 | 
			
		||||
            this.comboBoxTranslationTarget.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.comboBoxTranslationTarget.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.comboBoxTranslationTarget.FormattingEnabled = true;
 | 
			
		||||
            this.comboBoxTranslationTarget.Location = new System.Drawing.Point(5, 135);
 | 
			
		||||
            this.comboBoxTranslationTarget.Location = new System.Drawing.Point(5, 371);
 | 
			
		||||
            this.comboBoxTranslationTarget.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
 | 
			
		||||
            this.comboBoxTranslationTarget.Name = "comboBoxTranslationTarget";
 | 
			
		||||
            this.comboBoxTranslationTarget.Size = new System.Drawing.Size(290, 23);
 | 
			
		||||
            this.comboBoxTranslationTarget.TabIndex = 5;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelFirstDayOfWeek
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelFirstDayOfWeek.AutoSize = true;
 | 
			
		||||
            this.labelFirstDayOfWeek.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelFirstDayOfWeek.Location = new System.Drawing.Point(3, 173);
 | 
			
		||||
            this.labelFirstDayOfWeek.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
 | 
			
		||||
            this.labelFirstDayOfWeek.Name = "labelFirstDayOfWeek";
 | 
			
		||||
            this.labelFirstDayOfWeek.Size = new System.Drawing.Size(125, 15);
 | 
			
		||||
            this.labelFirstDayOfWeek.TabIndex = 6;
 | 
			
		||||
            this.labelFirstDayOfWeek.Text = "First Day Of The Week";
 | 
			
		||||
            // 
 | 
			
		||||
            // comboBoxFirstDayOfWeek
 | 
			
		||||
            // 
 | 
			
		||||
            this.comboBoxFirstDayOfWeek.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.comboBoxFirstDayOfWeek.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.comboBoxFirstDayOfWeek.FormattingEnabled = true;
 | 
			
		||||
            this.comboBoxFirstDayOfWeek.Location = new System.Drawing.Point(5, 192);
 | 
			
		||||
            this.comboBoxFirstDayOfWeek.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
 | 
			
		||||
            this.comboBoxFirstDayOfWeek.Name = "comboBoxFirstDayOfWeek";
 | 
			
		||||
            this.comboBoxFirstDayOfWeek.Size = new System.Drawing.Size(173, 23);
 | 
			
		||||
            this.comboBoxFirstDayOfWeek.TabIndex = 7;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelExternalApplications
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelExternalApplications.AutoSize = true;
 | 
			
		||||
            this.labelExternalApplications.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelExternalApplications.Location = new System.Drawing.Point(0, 243);
 | 
			
		||||
            this.labelExternalApplications.Margin = new System.Windows.Forms.Padding(0, 25, 0, 1);
 | 
			
		||||
            this.labelExternalApplications.Name = "labelExternalApplications";
 | 
			
		||||
            this.labelExternalApplications.Size = new System.Drawing.Size(176, 19);
 | 
			
		||||
            this.labelExternalApplications.TabIndex = 8;
 | 
			
		||||
            this.labelExternalApplications.Text = "EXTERNAL APPLICATIONS";
 | 
			
		||||
            // 
 | 
			
		||||
            // panelCustomBrowser
 | 
			
		||||
            // 
 | 
			
		||||
            this.panelCustomBrowser.Controls.Add(this.comboBoxCustomBrowser);
 | 
			
		||||
            this.panelCustomBrowser.Controls.Add(this.btnCustomBrowserChange);
 | 
			
		||||
            this.panelCustomBrowser.Location = new System.Drawing.Point(0, 293);
 | 
			
		||||
            this.panelCustomBrowser.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
 | 
			
		||||
            this.panelCustomBrowser.Name = "panelCustomBrowser";
 | 
			
		||||
            this.panelCustomBrowser.Size = new System.Drawing.Size(300, 24);
 | 
			
		||||
            this.panelCustomBrowser.TabIndex = 10;
 | 
			
		||||
            // 
 | 
			
		||||
            // btnCustomBrowserChange
 | 
			
		||||
            // 
 | 
			
		||||
            this.btnCustomBrowserChange.AutoSize = true;
 | 
			
		||||
            this.btnCustomBrowserChange.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.btnCustomBrowserChange.Location = new System.Drawing.Point(186, 0);
 | 
			
		||||
            this.btnCustomBrowserChange.Margin = new System.Windows.Forms.Padding(5, 0, 3, 0);
 | 
			
		||||
            this.btnCustomBrowserChange.Name = "btnCustomBrowserChange";
 | 
			
		||||
            this.btnCustomBrowserChange.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
 | 
			
		||||
            this.btnCustomBrowserChange.Size = new System.Drawing.Size(71, 25);
 | 
			
		||||
            this.btnCustomBrowserChange.TabIndex = 1;
 | 
			
		||||
            this.btnCustomBrowserChange.Text = "Change...";
 | 
			
		||||
            this.btnCustomBrowserChange.UseVisualStyleBackColor = true;
 | 
			
		||||
            this.btnCustomBrowserChange.Visible = false;
 | 
			
		||||
            // 
 | 
			
		||||
            // labelVideoPlayerPath
 | 
			
		||||
            // 
 | 
			
		||||
            this.labelVideoPlayerPath.AutoSize = true;
 | 
			
		||||
            this.labelVideoPlayerPath.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
 | 
			
		||||
            this.labelVideoPlayerPath.Location = new System.Drawing.Point(3, 332);
 | 
			
		||||
            this.labelVideoPlayerPath.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
 | 
			
		||||
            this.labelVideoPlayerPath.Name = "labelVideoPlayerPath";
 | 
			
		||||
            this.labelVideoPlayerPath.Size = new System.Drawing.Size(106, 15);
 | 
			
		||||
            this.labelVideoPlayerPath.TabIndex = 11;
 | 
			
		||||
            this.labelVideoPlayerPath.Text = "Play Videos With...";
 | 
			
		||||
            // 
 | 
			
		||||
            // panelCustomVideoPlayer
 | 
			
		||||
            // 
 | 
			
		||||
            this.panelCustomVideoPlayer.Controls.Add(this.comboBoxCustomVideoPlayer);
 | 
			
		||||
            this.panelCustomVideoPlayer.Controls.Add(this.btnCustomVideoPlayerChange);
 | 
			
		||||
            this.panelCustomVideoPlayer.Location = new System.Drawing.Point(0, 350);
 | 
			
		||||
            this.panelCustomVideoPlayer.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
 | 
			
		||||
            this.panelCustomVideoPlayer.Name = "panelCustomVideoPlayer";
 | 
			
		||||
            this.panelCustomVideoPlayer.Size = new System.Drawing.Size(300, 24);
 | 
			
		||||
            this.panelCustomVideoPlayer.TabIndex = 12;
 | 
			
		||||
            // 
 | 
			
		||||
            // comboBoxCustomVideoPlayer
 | 
			
		||||
            // 
 | 
			
		||||
            this.comboBoxCustomVideoPlayer.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 | 
			
		||||
            this.comboBoxCustomVideoPlayer.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.comboBoxCustomVideoPlayer.FormattingEnabled = true;
 | 
			
		||||
            this.comboBoxCustomVideoPlayer.Location = new System.Drawing.Point(5, 1);
 | 
			
		||||
            this.comboBoxCustomVideoPlayer.Margin = new System.Windows.Forms.Padding(5, 1, 3, 0);
 | 
			
		||||
            this.comboBoxCustomVideoPlayer.Name = "comboBoxCustomVideoPlayer";
 | 
			
		||||
            this.comboBoxCustomVideoPlayer.Size = new System.Drawing.Size(173, 23);
 | 
			
		||||
            this.comboBoxCustomVideoPlayer.TabIndex = 0;
 | 
			
		||||
            // 
 | 
			
		||||
            // btnCustomVideoPlayerChange
 | 
			
		||||
            // 
 | 
			
		||||
            this.btnCustomVideoPlayerChange.AutoSize = true;
 | 
			
		||||
            this.btnCustomVideoPlayerChange.Font = new System.Drawing.Font("Segoe UI", 9F);
 | 
			
		||||
            this.btnCustomVideoPlayerChange.Location = new System.Drawing.Point(186, 0);
 | 
			
		||||
            this.btnCustomVideoPlayerChange.Margin = new System.Windows.Forms.Padding(5, 0, 3, 0);
 | 
			
		||||
            this.btnCustomVideoPlayerChange.Name = "btnCustomVideoPlayerChange";
 | 
			
		||||
            this.btnCustomVideoPlayerChange.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
 | 
			
		||||
            this.btnCustomVideoPlayerChange.Size = new System.Drawing.Size(71, 25);
 | 
			
		||||
            this.btnCustomVideoPlayerChange.TabIndex = 1;
 | 
			
		||||
            this.btnCustomVideoPlayerChange.Text = "Change...";
 | 
			
		||||
            this.btnCustomVideoPlayerChange.UseVisualStyleBackColor = true;
 | 
			
		||||
            this.btnCustomVideoPlayerChange.Visible = false;
 | 
			
		||||
            this.comboBoxTranslationTarget.TabIndex = 13;
 | 
			
		||||
            // 
 | 
			
		||||
            // panelSeparator
 | 
			
		||||
            // 
 | 
			
		||||
@@ -555,7 +489,7 @@
 | 
			
		||||
            this.panelSeparator.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0);
 | 
			
		||||
            this.panelSeparator.Name = "panelSeparator";
 | 
			
		||||
            this.panelSeparator.Size = new System.Drawing.Size(1, 480);
 | 
			
		||||
            this.panelSeparator.TabIndex = 1;
 | 
			
		||||
            this.panelSeparator.TabIndex = 2;
 | 
			
		||||
            // 
 | 
			
		||||
            // TabSettingsGeneral
 | 
			
		||||
            // 
 | 
			
		||||
@@ -564,7 +498,6 @@
 | 
			
		||||
            this.Controls.Add(this.panelSeparator);
 | 
			
		||||
            this.Controls.Add(this.flowPanelRight);
 | 
			
		||||
            this.Controls.Add(this.flowPanelLeft);
 | 
			
		||||
            this.Font = TweetDuck.Controls.ControlExtensions.DefaultFont;
 | 
			
		||||
            this.Name = "TabSettingsGeneral";
 | 
			
		||||
            this.Size = new System.Drawing.Size(631, 480);
 | 
			
		||||
            ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
 | 
			
		||||
@@ -573,10 +506,6 @@
 | 
			
		||||
            this.flowPanelLeft.PerformLayout();
 | 
			
		||||
            this.flowPanelRight.ResumeLayout(false);
 | 
			
		||||
            this.flowPanelRight.PerformLayout();
 | 
			
		||||
            this.panelCustomBrowser.ResumeLayout(false);
 | 
			
		||||
            this.panelCustomBrowser.PerformLayout();
 | 
			
		||||
            this.panelCustomVideoPlayer.ResumeLayout(false);
 | 
			
		||||
            this.panelCustomVideoPlayer.PerformLayout();
 | 
			
		||||
            this.ResumeLayout(false);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
@@ -591,6 +520,7 @@
 | 
			
		||||
        private System.Windows.Forms.Label labelZoomValue;
 | 
			
		||||
        private System.Windows.Forms.TrackBar trackBarZoom;
 | 
			
		||||
        private System.Windows.Forms.Timer zoomUpdateTimer;
 | 
			
		||||
        private System.Windows.Forms.Label labelUI;
 | 
			
		||||
        private System.Windows.Forms.Panel panelZoom;
 | 
			
		||||
        private System.Windows.Forms.Label labelUpdates;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkBestImageQuality;
 | 
			
		||||
@@ -599,30 +529,24 @@
 | 
			
		||||
        private System.Windows.Forms.FlowLayoutPanel flowPanelLeft;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkKeepLikeFollowDialogsOpen;
 | 
			
		||||
        private System.Windows.Forms.Label labelBrowserPath;
 | 
			
		||||
        private System.Windows.Forms.ComboBox comboBoxCustomBrowser;
 | 
			
		||||
        private System.Windows.Forms.ComboBox comboBoxBrowserPath;
 | 
			
		||||
        private System.Windows.Forms.Label labelBrowserSettings;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkSmoothScrolling;
 | 
			
		||||
        private System.Windows.Forms.Label labelSearchEngine;
 | 
			
		||||
        private System.Windows.Forms.ComboBox comboBoxSearchEngine;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkTouchAdjustment;
 | 
			
		||||
        private System.Windows.Forms.FlowLayoutPanel flowPanelRight;
 | 
			
		||||
        private System.Windows.Forms.Panel panelSeparator;
 | 
			
		||||
        private System.Windows.Forms.Label labelTray;
 | 
			
		||||
        private System.Windows.Forms.ComboBox comboBoxTrayType;
 | 
			
		||||
        private System.Windows.Forms.Label labelTrayIcon;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkTrayHighlight;
 | 
			
		||||
        private System.Windows.Forms.Label labelLocales;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkSpellCheck;
 | 
			
		||||
        private System.Windows.Forms.Label labelSpellCheckLanguage;
 | 
			
		||||
        private System.Windows.Forms.ComboBox comboBoxSpellCheckLanguage;
 | 
			
		||||
        private System.Windows.Forms.Label labelTranslationTarget;
 | 
			
		||||
        private System.Windows.Forms.ComboBox comboBoxTranslationTarget;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkFocusDmInput;
 | 
			
		||||
        private System.Windows.Forms.Panel panelCustomBrowser;
 | 
			
		||||
        private System.Windows.Forms.Button btnCustomBrowserChange;
 | 
			
		||||
        private System.Windows.Forms.Label labelVideoPlayerPath;
 | 
			
		||||
        private System.Windows.Forms.Panel panelCustomVideoPlayer;
 | 
			
		||||
        private System.Windows.Forms.ComboBox comboBoxCustomVideoPlayer;
 | 
			
		||||
        private System.Windows.Forms.Button btnCustomVideoPlayerChange;
 | 
			
		||||
        private System.Windows.Forms.Label labelExternalApplications;
 | 
			
		||||
        private System.Windows.Forms.Label labelFirstDayOfWeek;
 | 
			
		||||
        private System.Windows.Forms.ComboBox comboBoxFirstDayOfWeek;
 | 
			
		||||
        private System.Windows.Forms.Label labelUI;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkHideTweetsByNftUsers;
 | 
			
		||||
        private System.Windows.Forms.Label labelTweetDeckSettings;
 | 
			
		||||
        private System.Windows.Forms.CheckBox checkHardwareAcceleration;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user