mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
21e49505d1
|
|||
e0025e02d1
|
|||
4a9590c1d9
|
|||
7de0c50387
|
|||
ccb87351c1
|
|||
273e7266eb
|
|||
427975e5ce
|
|||
adb304b6a2
|
|||
3a89a28f8b
|
|||
cff93dcc97
|
|||
dfde38ea3b
|
|||
a8e7f065cf
|
|||
5ebfc67e48
|
|||
57b03baad9
|
|||
c91f1d0e5e
|
|||
6a421292b3
|
|||
ceae748503
|
|||
008de87e55
|
|||
e7479ef9e3
|
|||
13f8f12ac8
|
|||
fd634379d7
|
|||
e854315a81
|
|||
86136d7692
|
|||
db251bfdfd
|
|||
9d39f26d54
|
|||
9cd813c02c
|
|||
8e1f87e062
|
|||
dda954285c
|
|||
ed4f7b6b72
|
|||
7239dcf4d2
|
|||
901cca268e
|
|||
b909341988
|
|||
3e607ae0fe
|
|||
587060f73c
|
|||
3ea6f6ac18
|
|||
c162761464
|
|||
994f9635ef
|
|||
ab8752845d
|
|||
a94ee2fe4b
|
|||
763c999b09
|
|||
b18cd2658c
|
|||
d9782f554f
|
|||
f1d9663709
|
|||
bcf77052a5
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,6 +5,8 @@
|
||||
bld/*
|
||||
!bld/*.iss
|
||||
!bld/*.bat
|
||||
!bld/*.ps1
|
||||
!bld/Redist
|
||||
!bld/Resources
|
||||
|
||||
# Rider
|
||||
@@ -144,4 +146,3 @@ _UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
|
357
.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
generated
357
.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
generated
@@ -1,12 +1,40 @@
|
||||
<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" />
|
||||
@@ -17,23 +45,146 @@
|
||||
<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="SPACE_BEFORE_CLASS_LBRACE" 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="SPACE_BEFORE_CLASS_LBRACE" 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" />
|
||||
@@ -42,6 +193,14 @@
|
||||
<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" />
|
||||
@@ -49,6 +208,20 @@
|
||||
<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" />
|
||||
@@ -58,6 +231,28 @@
|
||||
<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" />
|
||||
@@ -70,29 +265,13 @@
|
||||
<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="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
|
||||
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
|
||||
<option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
|
||||
<option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
|
||||
<option name="SPACE_BEFORE_METHOD_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_IF_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_ELSE_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_WHILE_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_FOR_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_DO_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_SWITCH_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_TRY_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_CATCH_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_FINALLY_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_ELSE_KEYWORD" value="false" />
|
||||
<option name="SPACE_BEFORE_WHILE_KEYWORD" value="false" />
|
||||
<option name="SPACE_BEFORE_CATCH_KEYWORD" value="false" />
|
||||
<option name="SPACE_BEFORE_FINALLY_KEYWORD" value="false" />
|
||||
<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>
|
||||
@@ -101,6 +280,81 @@
|
||||
<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" />
|
||||
@@ -115,10 +369,19 @@
|
||||
<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>
|
||||
@@ -126,29 +389,13 @@
|
||||
<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="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
|
||||
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
|
||||
<option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
|
||||
<option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
|
||||
<option name="SPACE_BEFORE_METHOD_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_IF_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_ELSE_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_WHILE_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_FOR_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_DO_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_SWITCH_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_TRY_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_CATCH_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_FINALLY_LBRACE" value="false" />
|
||||
<option name="SPACE_BEFORE_ELSE_KEYWORD" value="false" />
|
||||
<option name="SPACE_BEFORE_WHILE_KEYWORD" value="false" />
|
||||
<option name="SPACE_BEFORE_CATCH_KEYWORD" value="false" />
|
||||
<option name="SPACE_BEFORE_FINALLY_KEYWORD" value="false" />
|
||||
<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>
|
||||
@@ -160,6 +407,40 @@
|
||||
<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" />
|
||||
|
2
.idea/.idea.TweetDuck/.idea/indexLayout.xml
generated
2
.idea/.idea.TweetDuck/.idea/indexLayout.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ContentModelUserStore">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
|
@@ -532,7 +532,6 @@
|
||||
<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="ReuseOfLocalVariableJS" 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" />
|
||||
|
6
.idea/.idea.TweetDuck/.idea/jsLibraryMappings.xml
generated
Normal file
6
.idea/.idea.TweetDuck/.idea/jsLibraryMappings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="PROJECT" libraries="{@types/jquery}" />
|
||||
</component>
|
||||
</project>
|
7
.idea/.idea.TweetDuck/.idea/riderModule.iml
generated
7
.idea/.idea.TweetDuck/.idea/riderModule.iml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RIDER_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$/../.." />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@@ -3,25 +3,25 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using TweetLib.Core.Application;
|
||||
|
||||
namespace TweetDuck.Application{
|
||||
class SystemHandler : IAppSystemHandler{
|
||||
void IAppSystemHandler.OpenAssociatedProgram(string path){
|
||||
try{
|
||||
using(Process.Start(new ProcessStartInfo{
|
||||
namespace TweetDuck.Application {
|
||||
class SystemHandler : IAppSystemHandler {
|
||||
void IAppSystemHandler.OpenAssociatedProgram(string path) {
|
||||
try {
|
||||
using (Process.Start(new ProcessStartInfo {
|
||||
FileName = path,
|
||||
ErrorDialog = true
|
||||
})){}
|
||||
}catch(Exception e){
|
||||
})) {}
|
||||
} catch (Exception e) {
|
||||
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e);
|
||||
}
|
||||
}
|
||||
|
||||
void IAppSystemHandler.OpenFileExplorer(string path){
|
||||
if (File.Exists(path)){
|
||||
using(Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")){}
|
||||
void IAppSystemHandler.OpenFileExplorer(string path) {
|
||||
if (File.Exists(path)) {
|
||||
using (Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")) {}
|
||||
}
|
||||
else if (Directory.Exists(path)){
|
||||
using(Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')){}
|
||||
else if (Directory.Exists(path)) {
|
||||
using (Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,41 +1,78 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using CefSharp;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Browser;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters{
|
||||
sealed class CefScriptExecutor : IScriptExecutor{
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
sealed class CefScriptExecutor : IScriptExecutor {
|
||||
private readonly IWebBrowser browser;
|
||||
|
||||
public CefScriptExecutor(IWebBrowser browser){
|
||||
public CefScriptExecutor(IWebBrowser browser) {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
public void RunFunction(string name, params object[] args){
|
||||
browser.ExecuteScriptAsync(name, args);
|
||||
public void RunFunction(string name, params object[] args) {
|
||||
browser.ExecuteJsAsync(name, args);
|
||||
}
|
||||
|
||||
public void RunScript(string identifier, string script){
|
||||
public void RunScript(string identifier, string script) {
|
||||
using IFrame frame = browser.GetMainFrame();
|
||||
RunScript(frame, script, identifier);
|
||||
}
|
||||
|
||||
public bool RunFile(string file){
|
||||
public void RunBootstrap(string moduleNamespace) {
|
||||
using IFrame frame = browser.GetMainFrame();
|
||||
return RunFile(frame, file);
|
||||
RunBootstrap(frame, moduleNamespace);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
public static void RunScript(IFrame frame, string script, string identifier){
|
||||
if (script != null){
|
||||
public static void RunScript(IFrame frame, string script, string identifier) {
|
||||
if (script != null) {
|
||||
frame.ExecuteJavaScriptAsync(script, identifier, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool RunFile(IFrame frame, string file){
|
||||
string script = Program.Resources.Load(file);
|
||||
RunScript(frame, script, "root:" + Path.GetFileNameWithoutExtension(file));
|
||||
return script != null;
|
||||
public static void RunBootstrap(IFrame frame, string moduleNamespace) {
|
||||
string script = GetBootstrapScript(moduleNamespace, includeStylesheets: true);
|
||||
|
||||
if (script != null) {
|
||||
RunScript(frame, script, "bootstrap");
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetBootstrapScript(string moduleNamespace, bool includeStylesheets) {
|
||||
string script = FileUtils.ReadFileOrNull(Path.Combine(Program.ResourcesPath, "bootstrap.js"));
|
||||
|
||||
if (script == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string path = Path.Combine(Program.ResourcesPath, moduleNamespace);
|
||||
var files = new DirectoryInfo(path).GetFiles();
|
||||
|
||||
var moduleNames = new List<string>();
|
||||
var stylesheetNames = new List<string>();
|
||||
|
||||
foreach (var file in files) {
|
||||
var ext = Path.GetExtension(file.Name);
|
||||
|
||||
var targetList = ext switch {
|
||||
".js" => moduleNames,
|
||||
".css" => includeStylesheets ? stylesheetNames : null,
|
||||
_ => null
|
||||
};
|
||||
|
||||
targetList?.Add(Path.GetFileNameWithoutExtension(file.Name));
|
||||
}
|
||||
|
||||
script = script.Replace("{{namespace}}", moduleNamespace);
|
||||
script = script.Replace("{{modules}}", string.Join("|", moduleNames));
|
||||
script = script.Replace("{{stylesheets}}", string.Join("|", stylesheetNames));
|
||||
|
||||
return script;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,13 +3,14 @@ using TweetDuck.Configuration;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Bridge{
|
||||
static class PropertyBridge{
|
||||
public enum Environment{
|
||||
Browser, Notification
|
||||
namespace TweetDuck.Browser.Bridge {
|
||||
static class PropertyBridge {
|
||||
public enum Environment {
|
||||
Browser,
|
||||
Notification
|
||||
}
|
||||
|
||||
public static string GenerateScript(Environment environment){
|
||||
public static string GenerateScript(Environment environment) {
|
||||
static string Bool(bool value) => value ? "true;" : "false;";
|
||||
static string Str(string value) => $"\"{value}\";";
|
||||
|
||||
@@ -18,7 +19,7 @@ namespace TweetDuck.Browser.Bridge{
|
||||
|
||||
build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
|
||||
|
||||
if (environment == Environment.Browser){
|
||||
if (environment == Environment.Browser) {
|
||||
build.Append("x.focusDmInput=").Append(Bool(config.FocusDmInput));
|
||||
build.Append("x.openSearchInFirstColumn=").Append(Bool(config.OpenSearchInFirstColumn));
|
||||
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(config.KeepLikeFollowDialogsOpen));
|
||||
@@ -28,7 +29,7 @@ namespace TweetDuck.Browser.Bridge{
|
||||
build.Append("x.firstDayOfWeek=").Append(config.CalendarFirstDay == -1 ? LocaleUtils.GetJQueryDayOfWeek(Lib.Culture.DateTimeFormat.FirstDayOfWeek) : config.CalendarFirstDay);
|
||||
}
|
||||
|
||||
if (environment == Environment.Notification){
|
||||
if (environment == Environment.Notification) {
|
||||
build.Append("x.skipOnLinkClick=").Append(Bool(config.NotificationSkipOnLinkClick));
|
||||
}
|
||||
|
||||
|
@@ -12,10 +12,10 @@ using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Bridge{
|
||||
namespace TweetDuck.Browser.Bridge {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
class TweetDeckBridge{
|
||||
public static void ResetStaticProperties(){
|
||||
class TweetDeckBridge {
|
||||
public static void ResetStaticProperties() {
|
||||
FormNotificationBase.FontSize = null;
|
||||
FormNotificationBase.HeadLayout = null;
|
||||
}
|
||||
@@ -23,112 +23,116 @@ namespace TweetDuck.Browser.Bridge{
|
||||
private readonly FormBrowser form;
|
||||
private readonly FormNotificationMain notification;
|
||||
|
||||
protected TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
|
||||
private 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 sealed class Browser : TweetDeckBridge {
|
||||
public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification) {}
|
||||
|
||||
public void OpenContextMenu(){
|
||||
public void OnModulesLoaded(string moduleNamespace) {
|
||||
form.InvokeAsyncSafe(() => form.OnModulesLoaded(moduleNamespace));
|
||||
}
|
||||
|
||||
public void OpenContextMenu() {
|
||||
form.InvokeAsyncSafe(form.OpenContextMenu);
|
||||
}
|
||||
|
||||
public void OpenProfileImport(){
|
||||
public void OpenProfileImport() {
|
||||
form.InvokeAsyncSafe(form.OpenProfileImport);
|
||||
}
|
||||
|
||||
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide, allowDataCollection));
|
||||
public void OnIntroductionClosed(bool showGuide) {
|
||||
form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide));
|
||||
}
|
||||
|
||||
public void LoadNotificationLayout(string fontSize, string headLayout){
|
||||
public void LoadNotificationLayout(string fontSize, string headLayout) {
|
||||
form.InvokeAsyncSafe(() => {
|
||||
FormNotificationBase.FontSize = fontSize;
|
||||
FormNotificationBase.HeadLayout = headLayout;
|
||||
});
|
||||
}
|
||||
|
||||
public void SetRightClickedLink(string type, string url){
|
||||
public void SetRightClickedLink(string type, string url) {
|
||||
ContextMenuBase.CurrentInfo.SetLink(type, url);
|
||||
}
|
||||
|
||||
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||
ContextMenuBase.CurrentInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||
public void SetRightClickedChirp(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
|
||||
ContextMenuBase.CurrentInfo.SetChirp(columnId, chirpId, tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text){
|
||||
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 sealed class Notification : TweetDeckBridge {
|
||||
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification) {}
|
||||
|
||||
public void DisplayTooltip(string text){
|
||||
public void DisplayTooltip(string text) {
|
||||
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
||||
}
|
||||
|
||||
public void LoadNextNotification(){
|
||||
public void LoadNextNotification() {
|
||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||
}
|
||||
|
||||
public void ShowTweetDetail(){
|
||||
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){
|
||||
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl) {
|
||||
notification.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
notification.ShowNotification(new DesktopNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
||||
});
|
||||
}
|
||||
|
||||
public void OnTweetSound(){
|
||||
public void OnTweetSound() {
|
||||
form.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
form.OnTweetSound();
|
||||
});
|
||||
}
|
||||
|
||||
public void ScreenshotTweet(string html, int width){
|
||||
public void ScreenshotTweet(string html, int width) {
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
|
||||
}
|
||||
|
||||
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay){
|
||||
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
|
||||
form.InvokeAsyncSafe(() => form.PlayVideo(videoUrl, tweetUrl, username, callShowOverlay));
|
||||
}
|
||||
|
||||
public void StopVideo(){
|
||||
public void StopVideo() {
|
||||
form.InvokeAsyncSafe(form.StopVideo);
|
||||
}
|
||||
|
||||
public void FixClipboard(){
|
||||
public void FixClipboard() {
|
||||
form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles);
|
||||
}
|
||||
|
||||
public void OpenBrowser(string url){
|
||||
public void OpenBrowser(string url) {
|
||||
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||
}
|
||||
|
||||
public void MakeGetRequest(string url, IJavascriptCallback onSuccess, IJavascriptCallback onError){
|
||||
public void MakeGetRequest(string url, IJavascriptCallback onSuccess, IJavascriptCallback onError) {
|
||||
Task.Run(async () => {
|
||||
var client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
|
||||
|
||||
try{
|
||||
try {
|
||||
var result = await client.DownloadStringTaskAsync(url);
|
||||
await onSuccess.ExecuteAsync(result);
|
||||
}catch(Exception e){
|
||||
} catch (Exception e) {
|
||||
await onError.ExecuteAsync(e.Message);
|
||||
}finally{
|
||||
} finally {
|
||||
onSuccess.Dispose();
|
||||
onError.Dispose();
|
||||
client.Dispose();
|
||||
@@ -136,12 +140,12 @@ namespace TweetDuck.Browser.Bridge{
|
||||
});
|
||||
}
|
||||
|
||||
public int GetIdleSeconds(){
|
||||
public int GetIdleSeconds() {
|
||||
return NativeMethods.GetIdleSeconds();
|
||||
}
|
||||
|
||||
public void Alert(string type, string contents){
|
||||
MessageBoxIcon icon = type switch{
|
||||
public void Alert(string type, string contents) {
|
||||
MessageBoxIcon icon = type switch {
|
||||
"error" => MessageBoxIcon.Error,
|
||||
"warning" => MessageBoxIcon.Warning,
|
||||
"info" => MessageBoxIcon.Information,
|
||||
@@ -151,7 +155,7 @@ namespace TweetDuck.Browser.Bridge{
|
||||
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
||||
}
|
||||
|
||||
public void CrashDebug(string message){
|
||||
public void CrashDebug(string message) {
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.WriteLine(message);
|
||||
System.Diagnostics.Debugger.Break();
|
||||
|
@@ -4,9 +4,9 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Core.Systems.Updates;
|
||||
|
||||
namespace TweetDuck.Browser.Bridge{
|
||||
namespace TweetDuck.Browser.Bridge {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
class UpdateBridge{
|
||||
class UpdateBridge {
|
||||
private readonly UpdateHandler updates;
|
||||
private readonly Control sync;
|
||||
|
||||
@@ -15,47 +15,47 @@ namespace TweetDuck.Browser.Bridge{
|
||||
public event EventHandler<UpdateInfo> UpdateAccepted;
|
||||
public event EventHandler<UpdateInfo> UpdateDismissed;
|
||||
|
||||
public UpdateBridge(UpdateHandler updates, Control sync){
|
||||
public UpdateBridge(UpdateHandler updates, Control sync) {
|
||||
this.sync = sync;
|
||||
|
||||
this.updates = updates;
|
||||
this.updates.CheckFinished += updates_CheckFinished;
|
||||
}
|
||||
|
||||
internal void Cleanup(){
|
||||
internal void Cleanup() {
|
||||
updates.CheckFinished -= updates_CheckFinished;
|
||||
nextUpdate?.DeleteInstaller();
|
||||
}
|
||||
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e) {
|
||||
UpdateInfo foundUpdate = e.Result.HasValue ? e.Result.Value : null;
|
||||
|
||||
if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)){
|
||||
if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)) {
|
||||
nextUpdate.DeleteInstaller();
|
||||
}
|
||||
|
||||
nextUpdate = foundUpdate;
|
||||
}
|
||||
|
||||
private void HandleInteractionEvent(EventHandler<UpdateInfo> eventHandler){
|
||||
private void HandleInteractionEvent(EventHandler<UpdateInfo> eventHandler) {
|
||||
UpdateInfo tmpInfo = nextUpdate;
|
||||
|
||||
if (tmpInfo != null){
|
||||
if (tmpInfo != null) {
|
||||
sync.InvokeAsyncSafe(() => eventHandler?.Invoke(this, tmpInfo));
|
||||
}
|
||||
}
|
||||
|
||||
// Bridge methods
|
||||
|
||||
public void TriggerUpdateCheck(){
|
||||
public void TriggerUpdateCheck() {
|
||||
updates.Check(false);
|
||||
}
|
||||
|
||||
public void OnUpdateAccepted(){
|
||||
public void OnUpdateAccepted() {
|
||||
HandleInteractionEvent(UpdateAccepted);
|
||||
}
|
||||
|
||||
public void OnUpdateDismissed(){
|
||||
public void OnUpdateDismissed() {
|
||||
HandleInteractionEvent(UpdateDismissed);
|
||||
|
||||
nextUpdate?.DeleteInstaller();
|
||||
|
@@ -2,38 +2,38 @@
|
||||
using CefSharp;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Data{
|
||||
sealed class ContextInfo{
|
||||
namespace TweetDuck.Browser.Data {
|
||||
sealed class ContextInfo {
|
||||
private LinkInfo link;
|
||||
private ChirpInfo? chirp;
|
||||
|
||||
public ContextInfo(){
|
||||
public ContextInfo() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void SetLink(string type, string url){
|
||||
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 void SetChirp(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
|
||||
chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?) null : new ChirpInfo(columnId, chirpId, tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||
}
|
||||
|
||||
public ContextData Reset(){
|
||||
public ContextData Reset() {
|
||||
link = null;
|
||||
chirp = null;
|
||||
return ContextData.Empty;
|
||||
}
|
||||
|
||||
public ContextData Create(IContextMenuParams parameters){
|
||||
public ContextData Create(IContextMenuParams parameters) {
|
||||
ContextData.Builder builder = new ContextData.Builder();
|
||||
builder.AddContext(parameters);
|
||||
|
||||
if (link != null){
|
||||
if (link != null) {
|
||||
builder.AddOverride(link.Type, link.Url);
|
||||
}
|
||||
|
||||
if (chirp.HasValue){
|
||||
if (chirp.HasValue) {
|
||||
builder.AddChirp(chirp.Value);
|
||||
}
|
||||
|
||||
@@ -42,17 +42,20 @@ namespace TweetDuck.Browser.Data{
|
||||
|
||||
// Data structures
|
||||
|
||||
private sealed class LinkInfo{
|
||||
private sealed class LinkInfo {
|
||||
public string Type { get; }
|
||||
public string Url { get; }
|
||||
|
||||
public LinkInfo(string type, string url){
|
||||
public LinkInfo(string type, string url) {
|
||||
this.Type = type;
|
||||
this.Url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChirpInfo{
|
||||
public readonly struct ChirpInfo {
|
||||
public string ColumnId { get; }
|
||||
public string ChirpId { get; }
|
||||
|
||||
public string TweetUrl { get; }
|
||||
public string QuoteUrl { get; }
|
||||
|
||||
@@ -62,7 +65,9 @@ namespace TweetDuck.Browser.Data{
|
||||
private readonly string chirpAuthors;
|
||||
private readonly string chirpImages;
|
||||
|
||||
public ChirpInfo(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||
public ChirpInfo(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
|
||||
this.ColumnId = columnId;
|
||||
this.ChirpId = chirpId;
|
||||
this.TweetUrl = tweetUrl;
|
||||
this.QuoteUrl = quoteUrl;
|
||||
this.chirpAuthors = chirpAuthors;
|
||||
@@ -73,7 +78,7 @@ namespace TweetDuck.Browser.Data{
|
||||
// Constructed context
|
||||
|
||||
[Flags]
|
||||
public enum ContextType{
|
||||
public enum ContextType {
|
||||
Unknown = 0,
|
||||
Link = 0b0001,
|
||||
Image = 0b0010,
|
||||
@@ -81,7 +86,7 @@ namespace TweetDuck.Browser.Data{
|
||||
Chirp = 0b1000
|
||||
}
|
||||
|
||||
public sealed class ContextData{
|
||||
public sealed class ContextData {
|
||||
public static readonly ContextData Empty = new Builder().Build();
|
||||
|
||||
public ContextType Types { get; }
|
||||
@@ -92,7 +97,7 @@ namespace TweetDuck.Browser.Data{
|
||||
|
||||
public ChirpInfo Chirp { get; }
|
||||
|
||||
public ContextData(ContextType types, string linkUrl, string unsafeLinkUrl, string mediaUrl, ChirpInfo chirp){
|
||||
private ContextData(ContextType types, string linkUrl, string unsafeLinkUrl, string mediaUrl, ChirpInfo chirp) {
|
||||
Types = types;
|
||||
LinkUrl = linkUrl;
|
||||
UnsafeLinkUrl = unsafeLinkUrl;
|
||||
@@ -100,7 +105,7 @@ namespace TweetDuck.Browser.Data{
|
||||
Chirp = chirp;
|
||||
}
|
||||
|
||||
public sealed class Builder{
|
||||
public sealed class Builder {
|
||||
private ContextType types = ContextType.Unknown;
|
||||
|
||||
private string linkUrl = string.Empty;
|
||||
@@ -109,24 +114,24 @@ namespace TweetDuck.Browser.Data{
|
||||
|
||||
private ChirpInfo chirp = default;
|
||||
|
||||
public void AddContext(IContextMenuParams parameters){
|
||||
public void AddContext(IContextMenuParams parameters) {
|
||||
ContextMenuType flags = parameters.TypeFlags;
|
||||
|
||||
if (flags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
|
||||
if (flags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) {
|
||||
types |= ContextType.Image;
|
||||
types &= ~ContextType.Video;
|
||||
mediaUrl = parameters.SourceUrl;
|
||||
}
|
||||
|
||||
if (flags.HasFlag(ContextMenuType.Link)){
|
||||
if (flags.HasFlag(ContextMenuType.Link)) {
|
||||
types |= ContextType.Link;
|
||||
linkUrl = parameters.LinkUrl;
|
||||
unsafeLinkUrl = parameters.UnfilteredLinkUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddOverride(string type, string url){
|
||||
switch(type){
|
||||
public void AddOverride(string type, string url) {
|
||||
switch (type) {
|
||||
case "link":
|
||||
types |= ContextType.Link;
|
||||
linkUrl = url;
|
||||
@@ -147,12 +152,12 @@ namespace TweetDuck.Browser.Data{
|
||||
}
|
||||
}
|
||||
|
||||
public void AddChirp(ChirpInfo chirp){
|
||||
public void AddChirp(ChirpInfo chirp) {
|
||||
this.types |= ContextType.Chirp;
|
||||
this.chirp = chirp;
|
||||
}
|
||||
|
||||
public ContextData Build(){
|
||||
public ContextData Build() {
|
||||
return new ContextData(types, linkUrl, unsafeLinkUrl, mediaUrl, chirp);
|
||||
}
|
||||
}
|
||||
|
@@ -2,20 +2,20 @@
|
||||
using System.Collections.Concurrent;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Browser.Data{
|
||||
sealed class ResourceHandlers{
|
||||
namespace TweetDuck.Browser.Data {
|
||||
sealed class ResourceHandlers {
|
||||
private readonly ConcurrentDictionary<string, Func<IResourceHandler>> handlers = new ConcurrentDictionary<string, Func<IResourceHandler>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public bool HasHandler(IRequest request){
|
||||
public bool HasHandler(IRequest request) {
|
||||
return handlers.ContainsKey(request.Url);
|
||||
}
|
||||
|
||||
public IResourceHandler GetHandler(IRequest request){
|
||||
public IResourceHandler GetHandler(IRequest request) {
|
||||
return handlers.TryGetValue(request.Url, out var factory) ? factory() : null;
|
||||
}
|
||||
|
||||
public bool Register(string url, Func<IResourceHandler> factory){
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||
public bool Register(string url, Func<IResourceHandler> factory) {
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
|
||||
handlers.AddOrUpdate(uri.AbsoluteUri, factory, (key, prev) => factory);
|
||||
return true;
|
||||
}
|
||||
@@ -23,27 +23,27 @@ namespace TweetDuck.Browser.Data{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Register(ResourceLink link){
|
||||
public bool Register(ResourceLink link) {
|
||||
return Register(link.Url, link.Factory);
|
||||
}
|
||||
|
||||
public bool Unregister(string url){
|
||||
public bool Unregister(string url) {
|
||||
return handlers.TryRemove(url, out _);
|
||||
}
|
||||
|
||||
public bool Unregister(ResourceLink link){
|
||||
public bool Unregister(ResourceLink link) {
|
||||
return Unregister(link.Url);
|
||||
}
|
||||
|
||||
public static Func<IResourceHandler> ForString(string str){
|
||||
public static Func<IResourceHandler> ForString(string str) {
|
||||
return () => ResourceHandler.FromString(str);
|
||||
}
|
||||
|
||||
public static Func<IResourceHandler> ForString(string str, string mimeType){
|
||||
public static Func<IResourceHandler> ForString(string str, string mimeType) {
|
||||
return () => ResourceHandler.FromString(str, mimeType: mimeType);
|
||||
}
|
||||
|
||||
public static Func<IResourceHandler> ForBytes(byte[] bytes, string mimeType){
|
||||
public static Func<IResourceHandler> ForBytes(byte[] bytes, string mimeType) {
|
||||
return () => ResourceHandler.FromByteArray(bytes, mimeType);
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Browser.Data{
|
||||
sealed class ResourceLink{
|
||||
namespace TweetDuck.Browser.Data {
|
||||
sealed class ResourceLink {
|
||||
public string Url { get; }
|
||||
public Func<IResourceHandler> Factory { get; }
|
||||
|
||||
public ResourceLink(string url, Func<IResourceHandler> factory){
|
||||
public ResourceLink(string url, Func<IResourceHandler> factory) {
|
||||
this.Url = url;
|
||||
this.Factory = factory;
|
||||
}
|
||||
|
@@ -4,35 +4,35 @@ using TweetDuck.Controls;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Data{
|
||||
sealed class WindowState{
|
||||
namespace TweetDuck.Browser.Data {
|
||||
sealed class WindowState {
|
||||
private Rectangle rect;
|
||||
private bool isMaximized;
|
||||
|
||||
public void Save(Form form){
|
||||
public void Save(Form form) {
|
||||
rect = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds;
|
||||
isMaximized = form.WindowState == FormWindowState.Maximized;
|
||||
}
|
||||
|
||||
public void Restore(Form form, bool firstTimeFullscreen){
|
||||
if (rect != Rectangle.Empty){
|
||||
public void Restore(Form form, bool firstTimeFullscreen) {
|
||||
if (rect != Rectangle.Empty) {
|
||||
form.DesktopBounds = rect;
|
||||
form.WindowState = isMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
|
||||
}
|
||||
|
||||
if ((rect == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()){
|
||||
if ((rect == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()) {
|
||||
form.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
|
||||
form.WindowState = FormWindowState.Maximized;
|
||||
Save(form);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly SingleTypeConverter<WindowState> Converter = new SingleTypeConverter<WindowState>{
|
||||
public static readonly SingleTypeConverter<WindowState> Converter = new SingleTypeConverter<WindowState> {
|
||||
ConvertToString = value => $"{(value.isMaximized ? 'M' : '_')}{value.rect.X} {value.rect.Y} {value.rect.Width} {value.rect.Height}",
|
||||
ConvertToObject = value => {
|
||||
int[] elements = StringUtils.ParseInts(value.Substring(1), ' ');
|
||||
|
||||
return new WindowState{
|
||||
return new WindowState {
|
||||
rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]),
|
||||
isMaximized = value[0] == 'M'
|
||||
};
|
||||
|
@@ -15,29 +15,29 @@ using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Dialogs.Settings;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Management.Analytics;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Events;
|
||||
using TweetLib.Core.Systems.Updates;
|
||||
|
||||
namespace TweetDuck.Browser{
|
||||
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
||||
namespace TweetDuck.Browser {
|
||||
sealed partial class FormBrowser : Form {
|
||||
private static UserConfig Config => Program.Config.User;
|
||||
|
||||
public bool IsWaiting{
|
||||
set{
|
||||
if (value){
|
||||
public bool IsWaiting {
|
||||
set {
|
||||
if (value) {
|
||||
browser.Enabled = false;
|
||||
Cursor = Cursors.WaitCursor;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
browser.Enabled = true;
|
||||
Cursor = Cursors.Default;
|
||||
|
||||
if (Focused){ // re-focus browser only if the window or a child is activated
|
||||
if (Focused) { // re-focus browser only if the window or a child is activated
|
||||
browser.Focus();
|
||||
}
|
||||
}
|
||||
@@ -47,13 +47,12 @@ namespace TweetDuck.Browser{
|
||||
public UpdateInstaller UpdateInstaller { get; private set; }
|
||||
private bool ignoreUpdateCheckError;
|
||||
|
||||
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
|
||||
|
||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
||||
private readonly TweetDeckBrowser browser;
|
||||
private readonly FormNotificationTweet notification;
|
||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
||||
|
||||
private readonly ResourceProvider resourceProvider;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly UpdateHandler updates;
|
||||
private readonly ContextMenu contextMenu;
|
||||
@@ -64,13 +63,14 @@ namespace TweetDuck.Browser{
|
||||
|
||||
private TweetScreenshotManager notificationScreenshotManager;
|
||||
private VideoPlayer videoPlayer;
|
||||
private AnalyticsManager analytics;
|
||||
|
||||
public FormBrowser(PluginSchemeFactory pluginScheme){
|
||||
public FormBrowser(ResourceProvider resourceProvider, PluginSchemeFactory pluginScheme) {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName;
|
||||
|
||||
this.resourceProvider = resourceProvider;
|
||||
|
||||
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
|
||||
this.plugins.Reloaded += plugins_Reloaded;
|
||||
this.plugins.Executed += plugins_Executed;
|
||||
@@ -90,7 +90,7 @@ namespace TweetDuck.Browser{
|
||||
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
|
||||
Controls.Add(new MenuStrip { Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
Config.MuteToggled -= Config_MuteToggled;
|
||||
@@ -106,19 +106,15 @@ namespace TweetDuck.Browser{
|
||||
|
||||
UpdateTray();
|
||||
|
||||
if (Config.MuteNotifications){
|
||||
if (Config.MuteNotifications) {
|
||||
UpdateFormIcon();
|
||||
}
|
||||
|
||||
if (Config.AllowDataCollection){
|
||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||
}
|
||||
|
||||
RestoreWindow();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
if (disposing){
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
|
||||
updates.Dispose();
|
||||
@@ -126,25 +122,24 @@ namespace TweetDuck.Browser{
|
||||
|
||||
notificationScreenshotManager?.Dispose();
|
||||
videoPlayer?.Dispose();
|
||||
analytics?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void ShowChildForm(Form form){
|
||||
private void ShowChildForm(Form form) {
|
||||
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||
form.Show(this);
|
||||
}
|
||||
|
||||
public void ForceClose(){
|
||||
public void ForceClose() {
|
||||
trayIcon.Visible = false; // checked in FormClosing event
|
||||
Close();
|
||||
}
|
||||
|
||||
// window setup
|
||||
|
||||
private void RestoreWindow(){
|
||||
private void RestoreWindow() {
|
||||
Config.BrowserWindow.Restore(this, true);
|
||||
browser.PrepareSize(ClientSize);
|
||||
|
||||
@@ -152,34 +147,34 @@ namespace TweetDuck.Browser{
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
private void UpdateFormIcon(){ // TODO fix to show icon in taskbar too
|
||||
private void UpdateFormIcon() { // TODO fix to show icon in taskbar too
|
||||
Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
|
||||
}
|
||||
|
||||
private void UpdateTray(){
|
||||
private void UpdateTray() {
|
||||
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void timerResize_Tick(object sender, EventArgs e){
|
||||
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){
|
||||
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
|
||||
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){
|
||||
private void FormBrowser_LocationChanged(object sender, EventArgs e) {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,111 +182,108 @@ namespace TweetDuck.Browser{
|
||||
timerResize.Start();
|
||||
}
|
||||
|
||||
private void FormBrowser_Resize(object sender, EventArgs e){
|
||||
if (!isLoaded){
|
||||
private void FormBrowser_Resize(object sender, EventArgs e) {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (WindowState != prevState){
|
||||
if (WindowState != prevState) {
|
||||
prevState = WindowState;
|
||||
|
||||
if (WindowState == FormWindowState.Minimized){
|
||||
if (Config.TrayBehavior.ShouldHideOnMinimize()){
|
||||
if (WindowState == FormWindowState.Minimized) {
|
||||
if (Config.TrayBehavior.ShouldHideOnMinimize()) {
|
||||
Hide(); // hides taskbar too?! welp that works I guess
|
||||
}
|
||||
}
|
||||
else{
|
||||
else {
|
||||
FormBrowser_ResizeEnd(sender, e);
|
||||
}
|
||||
}
|
||||
else{
|
||||
else {
|
||||
timerResize.Stop();
|
||||
timerResize.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
|
||||
if (!isLoaded){
|
||||
private void FormBrowser_ResizeEnd(object sender, EventArgs e) { // also triggers when the window moves
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
timerResize.Stop();
|
||||
browser.PrepareSize(ClientSize); // needed to pre-size browser control when launched in maximized state
|
||||
|
||||
if (Location != ControlExtensions.InvisibleLocation){
|
||||
if (Location != ControlExtensions.InvisibleLocation) {
|
||||
Config.BrowserWindow.Save(this);
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (!isLoaded){
|
||||
private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e) {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){
|
||||
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 && UpdateInstaller == null){
|
||||
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e) {
|
||||
if (isLoaded && UpdateInstaller == null) {
|
||||
updateBridge.Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e){
|
||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
||||
UpdateFormIcon();
|
||||
AnalyticsFile.NotificationMutes.Trigger();
|
||||
}
|
||||
|
||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e) {
|
||||
UpdateTray();
|
||||
}
|
||||
|
||||
private void trayIcon_ClickRestore(object sender, EventArgs e){
|
||||
private void trayIcon_ClickRestore(object sender, EventArgs e) {
|
||||
Show();
|
||||
RestoreWindow();
|
||||
Activate();
|
||||
UpdateTray();
|
||||
}
|
||||
|
||||
private void trayIcon_ClickClose(object sender, EventArgs e){
|
||||
private void trayIcon_ClickClose(object sender, EventArgs e) {
|
||||
ForceClose();
|
||||
}
|
||||
|
||||
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||
if (e.HasErrors){
|
||||
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){
|
||||
if (isLoaded) {
|
||||
browser.ReloadToTweetDeck();
|
||||
}
|
||||
}
|
||||
|
||||
private void plugins_Executed(object sender, PluginErrorEventArgs e){
|
||||
if (e.HasErrors){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||
});
|
||||
private void plugins_Executed(object sender, PluginErrorEventArgs e) {
|
||||
if (e.HasErrors) {
|
||||
this.InvokeAsyncSafe(() => { 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){
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e) {
|
||||
e.Result.Handle(update => {
|
||||
string tag = update.VersionTag;
|
||||
|
||||
if (tag != Program.VersionTag && tag != Config.DismissedUpdate){
|
||||
if (tag != Program.VersionTag && tag != Config.DismissedUpdate) {
|
||||
update.BeginSilentDownload();
|
||||
browser.ShowUpdateNotification(tag, update.ReleaseNotes);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
updates.StartTimer();
|
||||
}
|
||||
}, ex => {
|
||||
if (!ignoreUpdateCheckError){
|
||||
if (!ignoreUpdateCheckError) {
|
||||
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
|
||||
updates.StartTimer();
|
||||
}
|
||||
@@ -300,34 +292,34 @@ namespace TweetDuck.Browser{
|
||||
ignoreUpdateCheckError = true;
|
||||
}
|
||||
|
||||
private void updateBridge_UpdateAccepted(object sender, UpdateInfo update){
|
||||
private void updateBridge_UpdateAccepted(object sender, UpdateInfo update) {
|
||||
FormManager.CloseAllDialogs();
|
||||
|
||||
if (!string.IsNullOrEmpty(Config.DismissedUpdate)){
|
||||
if (!string.IsNullOrEmpty(Config.DismissedUpdate)) {
|
||||
Config.DismissedUpdate = null;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
void OnFinished(){
|
||||
void OnFinished() {
|
||||
UpdateDownloadStatus status = update.DownloadStatus;
|
||||
|
||||
if (status == UpdateDownloadStatus.Done){
|
||||
if (status == UpdateDownloadStatus.Done) {
|
||||
UpdateInstaller = new UpdateInstaller(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)){
|
||||
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{
|
||||
else {
|
||||
Show();
|
||||
}
|
||||
}
|
||||
|
||||
if (update.DownloadStatus.IsFinished(true)){
|
||||
if (update.DownloadStatus.IsFinished(true)) {
|
||||
OnFinished();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
FormUpdateDownload downloadForm = new FormUpdateDownload(update);
|
||||
|
||||
downloadForm.VisibleChanged += (sender2, args2) => {
|
||||
@@ -336,7 +328,7 @@ namespace TweetDuck.Browser{
|
||||
};
|
||||
|
||||
downloadForm.FormClosed += (sender2, args2) => {
|
||||
if (downloadForm.DialogResult != DialogResult.OK){
|
||||
if (downloadForm.DialogResult != DialogResult.OK) {
|
||||
update.CancelDownload();
|
||||
}
|
||||
|
||||
@@ -348,29 +340,28 @@ namespace TweetDuck.Browser{
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){
|
||||
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){
|
||||
protected override void WndProc(ref Message m) {
|
||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage) {
|
||||
using Process me = Process.GetCurrentProcess();
|
||||
|
||||
if (me.Id == m.WParam.ToInt32()){
|
||||
if (me.Id == 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){
|
||||
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN) {
|
||||
if (videoPlayer != null && videoPlayer.Running) {
|
||||
videoPlayer.Close();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
browser.OnMouseClickExtra(m.WParam);
|
||||
AnalyticsFile.BrowserExtraMouseButtons.Trigger();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -381,111 +372,106 @@ namespace TweetDuck.Browser{
|
||||
|
||||
// bridge methods
|
||||
|
||||
public void PauseNotification(){
|
||||
public void OnModulesLoaded(string moduleNamespace) {
|
||||
browser.OnModulesLoaded(moduleNamespace);
|
||||
}
|
||||
|
||||
public void PauseNotification() {
|
||||
notification.PauseNotification();
|
||||
}
|
||||
|
||||
public void ResumeNotification(){
|
||||
public void ResumeNotification() {
|
||||
notification.ResumeNotification();
|
||||
}
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
public void ReinjectCustomCSS(string css) {
|
||||
browser.ReinjectCustomCSS(css);
|
||||
}
|
||||
|
||||
public void ReloadToTweetDeck(){
|
||||
Program.Resources.OnReloadTriggered();
|
||||
public void ReloadToTweetDeck() {
|
||||
#if DEBUG
|
||||
ResourceHotSwap.Run();
|
||||
resourceProvider.ClearCache();
|
||||
#else
|
||||
if (ModifierKeys.HasFlag(Keys.Shift)) {
|
||||
resourceProvider.ClearCache();
|
||||
}
|
||||
#endif
|
||||
|
||||
ignoreUpdateCheckError = false;
|
||||
browser.ReloadToTweetDeck();
|
||||
AnalyticsFile.BrowserReloads.Trigger();
|
||||
}
|
||||
|
||||
public void AddSearchColumn(string query){
|
||||
public void AddSearchColumn(string query) {
|
||||
browser.AddSearchColumn(query);
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.TriggerTweetScreenshot();
|
||||
public void TriggerTweetScreenshot(string columnId, string chirpId) {
|
||||
browser.TriggerTweetScreenshot(columnId, chirpId);
|
||||
}
|
||||
|
||||
public void ReloadColumns(){
|
||||
public void ReloadColumns() {
|
||||
browser.ReloadColumns();
|
||||
}
|
||||
|
||||
public void PlaySoundNotification(){
|
||||
public void PlaySoundNotification() {
|
||||
browser.PlaySoundNotification();
|
||||
}
|
||||
|
||||
public void ApplyROT13(){
|
||||
public void ApplyROT13() {
|
||||
browser.ApplyROT13();
|
||||
AnalyticsFile.UsedROT13.Trigger();
|
||||
}
|
||||
|
||||
public void OpenDevTools(){
|
||||
public void OpenDevTools() {
|
||||
browser.OpenDevTools();
|
||||
}
|
||||
|
||||
// callback handlers
|
||||
|
||||
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
if (Config.FirstRun){
|
||||
public void OnIntroductionClosed(bool showGuide) {
|
||||
if (Config.FirstRun) {
|
||||
Config.FirstRun = false;
|
||||
Config.AllowDataCollection = allowDataCollection;
|
||||
Config.Save();
|
||||
|
||||
if (allowDataCollection && analytics == null){
|
||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (showGuide){
|
||||
if (showGuide) {
|
||||
FormGuide.Show();
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenContextMenu(){
|
||||
public void OpenContextMenu() {
|
||||
contextMenu.Show(this, PointToClient(Cursor.Position));
|
||||
}
|
||||
|
||||
public void OpenSettings(){
|
||||
public void OpenSettings() {
|
||||
OpenSettings(null);
|
||||
}
|
||||
|
||||
public void OpenSettings(Type startTab){
|
||||
if (!FormManager.TryBringToFront<FormSettings>()){
|
||||
public void OpenSettings(Type startTab) {
|
||||
if (!FormManager.TryBringToFront<FormSettings>()) {
|
||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||
|
||||
FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
|
||||
FormSettings form = new FormSettings(this, plugins, updates, startTab);
|
||||
|
||||
form.FormClosed += (sender, args) => {
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck) {
|
||||
Config.DismissedUpdate = null;
|
||||
Config.Save();
|
||||
|
||||
updates.Check(true);
|
||||
}
|
||||
|
||||
if (!Config.EnableTrayHighlight){
|
||||
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){
|
||||
if (form.ShouldReloadBrowser) {
|
||||
FormManager.TryFind<FormPlugins>()?.Close();
|
||||
plugins.Reload(); // also reloads the browser
|
||||
}
|
||||
else{
|
||||
else {
|
||||
browser.UpdateProperties();
|
||||
}
|
||||
|
||||
@@ -493,52 +479,47 @@ namespace TweetDuck.Browser{
|
||||
form.Dispose();
|
||||
};
|
||||
|
||||
AnalyticsFile.OpenOptions.Trigger();
|
||||
ShowChildForm(form);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenAbout(){
|
||||
if (!FormManager.TryBringToFront<FormAbout>()){
|
||||
AnalyticsFile.OpenAbout.Trigger();
|
||||
public void OpenAbout() {
|
||||
if (!FormManager.TryBringToFront<FormAbout>()) {
|
||||
ShowChildForm(new FormAbout());
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenPlugins(){
|
||||
if (!FormManager.TryBringToFront<FormPlugins>()){
|
||||
AnalyticsFile.OpenPlugins.Trigger();
|
||||
public void OpenPlugins() {
|
||||
if (!FormManager.TryBringToFront<FormPlugins>()) {
|
||||
ShowChildForm(new FormPlugins(plugins));
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenProfileImport(){
|
||||
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
|
||||
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){
|
||||
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 OnTweetSound() {}
|
||||
|
||||
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay){
|
||||
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
|
||||
string playerPath = Config.VideoPlayerPath;
|
||||
|
||||
if (playerPath == null || !File.Exists(playerPath)){
|
||||
if (videoPlayer == null){
|
||||
if (playerPath == null || !File.Exists(playerPath)) {
|
||||
if (videoPlayer == null) {
|
||||
videoPlayer = new VideoPlayer(this);
|
||||
videoPlayer.ProcessExited += (sender, args) => browser.HideVideoOverlay(true);
|
||||
}
|
||||
@@ -548,28 +529,26 @@ namespace TweetDuck.Browser{
|
||||
|
||||
videoPlayer.Launch(videoUrl, tweetUrl, username);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
callShowOverlay.Dispose();
|
||||
|
||||
string quotedUrl = '"' + videoUrl + '"';
|
||||
string playerArgs = Config.VideoPlayerPathArgs == null ? quotedUrl : Config.VideoPlayerPathArgs + ' ' + quotedUrl;
|
||||
|
||||
try{
|
||||
using(Process.Start(playerPath, playerArgs)){}
|
||||
}catch(Exception e){
|
||||
try {
|
||||
using (Process.Start(playerPath, playerArgs)) {}
|
||||
} catch (Exception e) {
|
||||
Program.Reporter.HandleException("Error Opening Video Player", "Could not open the video player.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
AnalyticsFile.VideoPlays.Trigger();
|
||||
}
|
||||
|
||||
public void StopVideo(){
|
||||
public void StopVideo() {
|
||||
videoPlayer?.Close();
|
||||
}
|
||||
|
||||
public bool ProcessBrowserKey(Keys key){
|
||||
if (videoPlayer != null && videoPlayer.Running){
|
||||
public bool ProcessBrowserKey(Keys key) {
|
||||
if (videoPlayer != null && videoPlayer.Running) {
|
||||
videoPlayer.SendKeyEvent(key);
|
||||
return true;
|
||||
}
|
||||
@@ -577,33 +556,28 @@ namespace TweetDuck.Browser{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
|
||||
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
|
||||
Activate();
|
||||
|
||||
if (!browser.IsTweetDeckWebsite){
|
||||
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);
|
||||
}
|
||||
|
||||
public void OnTweetScreenshotReady(string html, int width) {
|
||||
notificationScreenshotManager ??= new TweetScreenshotManager(this, plugins);
|
||||
notificationScreenshotManager.Trigger(html, width);
|
||||
AnalyticsFile.TweetScreenshots.Trigger();
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text){
|
||||
if (string.IsNullOrEmpty(text)){
|
||||
public void DisplayTooltip(string text) {
|
||||
if (string.IsNullOrEmpty(text)) {
|
||||
toolTip.Hide(this);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
Point position = PointToClient(Cursor.Position);
|
||||
position.Offset(20, 10);
|
||||
toolTip.Show(text, this, position);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
@@ -10,48 +11,41 @@ using TweetDuck.Configuration;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Management.Analytics;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
abstract class ContextMenuBase : IContextMenuHandler {
|
||||
public static ContextInfo CurrentInfo { get; } = new ContextInfo();
|
||||
|
||||
protected static UserConfig Config => Program.Config.User;
|
||||
private static 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 MenuCopyImage = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26507;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26508;
|
||||
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26509;
|
||||
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand)26510;
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||
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 MenuCopyImage = (CefMenuCommand) 26506;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand) 26507;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand) 26508;
|
||||
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand) 26509;
|
||||
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand) 26510;
|
||||
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 (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading){
|
||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading) {
|
||||
Context = CurrentInfo.Reset();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
Context = CurrentInfo.Create(parameters);
|
||||
}
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)) {
|
||||
model.AddItem(MenuSearchInBrowser, "Search in browser");
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
|
||||
@@ -62,13 +56,13 @@ namespace TweetDuck.Browser.Handling{
|
||||
static string TextCopy(string name) => "Copy " + name + " address";
|
||||
static string TextSave(string name) => "Save " + name + " as...";
|
||||
|
||||
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
||||
if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){
|
||||
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)) {
|
||||
if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)) {
|
||||
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
|
||||
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
|
||||
model.AddItem(MenuCopyUsername, "Copy account username");
|
||||
}
|
||||
else{
|
||||
else {
|
||||
model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
|
||||
model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
|
||||
}
|
||||
@@ -76,20 +70,20 @@ namespace TweetDuck.Browser.Handling{
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)){
|
||||
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 != FormNotificationBase.AppLogo.Url){
|
||||
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != FormNotificationBase.AppLogo.Url) {
|
||||
model.AddItem(MenuViewImage, "View image in photo viewer");
|
||||
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
||||
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
||||
model.AddItem(MenuCopyImage, "Copy image");
|
||||
model.AddItem(MenuSaveMedia, TextSave("image"));
|
||||
|
||||
if (Context.Chirp.Images.Length > 1){
|
||||
if (Context.Chirp.Images.Length > 1) {
|
||||
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
|
||||
}
|
||||
|
||||
@@ -97,10 +91,10 @@ namespace TweetDuck.Browser.Handling{
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||
Control control = browserControl.AsControl();
|
||||
|
||||
switch(commandId){
|
||||
switch (commandId) {
|
||||
case MenuOpenLinkUrl:
|
||||
OpenBrowser(control, Context.LinkUrl);
|
||||
break;
|
||||
@@ -114,7 +108,6 @@ namespace TweetDuck.Browser.Handling{
|
||||
Match match = TwitterUrls.RegexAccount.Match(url);
|
||||
|
||||
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -129,9 +122,7 @@ namespace TweetDuck.Browser.Handling{
|
||||
case MenuCopyImage: {
|
||||
string url = Context.MediaUrl;
|
||||
|
||||
control.InvokeAsyncSafe(() => {
|
||||
TwitterUtils.CopyImage(url, ImageQuality);
|
||||
});
|
||||
control.InvokeAsyncSafe(() => { TwitterUtils.CopyImage(url, ImageQuality); });
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -141,7 +132,6 @@ namespace TweetDuck.Browser.Handling{
|
||||
|
||||
control.InvokeAsyncSafe(() => {
|
||||
TwitterUtils.ViewImage(url, ImageQuality);
|
||||
analytics.AnalyticsFile.ViewedImages.Trigger();
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -153,13 +143,11 @@ namespace TweetDuck.Browser.Handling{
|
||||
string username = Context.Chirp.Authors.LastOrDefault();
|
||||
|
||||
control.InvokeAsyncSafe(() => {
|
||||
if (isVideo){
|
||||
if (isVideo) {
|
||||
TwitterUtils.DownloadVideo(url, username);
|
||||
analytics.AnalyticsFile.DownloadedVideos.Trigger();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
TwitterUtils.DownloadImage(url, username, ImageQuality);
|
||||
analytics.AnalyticsFile.DownloadedImages.Trigger();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -172,7 +160,6 @@ namespace TweetDuck.Browser.Handling{
|
||||
|
||||
control.InvokeAsyncSafe(() => {
|
||||
TwitterUtils.DownloadImages(urls, username, ImageQuality);
|
||||
analytics.AnalyticsFile.DownloadedImages.Trigger();
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -181,7 +168,6 @@ namespace TweetDuck.Browser.Handling{
|
||||
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:
|
||||
@@ -191,52 +177,52 @@ namespace TweetDuck.Browser.Handling{
|
||||
break;
|
||||
|
||||
case MenuOpenDevTools:
|
||||
browserControl.OpenDevToolsCustom();
|
||||
browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
||||
Context = CurrentInfo.Reset();
|
||||
}
|
||||
|
||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static void DeselectAll(IFrame frame){
|
||||
protected static void DeselectAll(IFrame frame) {
|
||||
CefScriptExecutor.RunScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
||||
}
|
||||
|
||||
protected static void OpenBrowser(Control control, string url){
|
||||
protected static void OpenBrowser(Control control, string url) {
|
||||
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||
}
|
||||
|
||||
protected static void SetClipboardText(Control control, string text){
|
||||
protected static void SetClipboardText(Control control, string text) {
|
||||
control.InvokeAsyncSafe(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
|
||||
}
|
||||
|
||||
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
|
||||
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){
|
||||
protected static void AddDebugMenuItems(IMenuModel model) {
|
||||
if (Config.DevToolsInContextMenu) {
|
||||
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){
|
||||
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
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@@ -4,21 +4,21 @@ using TweetDuck.Browser.Data;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Browser.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;
|
||||
namespace TweetDuck.Browser.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 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";
|
||||
@@ -28,11 +28,11 @@ namespace TweetDuck.Browser.Handling{
|
||||
|
||||
private readonly FormBrowser form;
|
||||
|
||||
public ContextMenuBrowser(FormBrowser form) : base(form){
|
||||
public ContextMenuBrowser(FormBrowser form) {
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
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);
|
||||
|
||||
@@ -42,8 +42,8 @@ namespace TweetDuck.Browser.Handling{
|
||||
model.Remove(CefMenuCommand.ViewSource);
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
if (isSelecting){
|
||||
if (isEditing){
|
||||
if (isSelecting) {
|
||||
if (isEditing) {
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
|
||||
}
|
||||
@@ -53,16 +53,16 @@ namespace TweetDuck.Browser.Handling{
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
if (isSelecting && !isEditing && TwitterUrls.IsTweetDeck(frame.Url)){
|
||||
if (isSelecting && !isEditing && TwitterUrls.IsTweetDeck(frame.Url)) {
|
||||
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
||||
}
|
||||
|
||||
if (Context.Types.HasFlag(ContextInfo.ContextType.Chirp) && !isSelecting && !isEditing){
|
||||
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)){
|
||||
if (!string.IsNullOrEmpty(Context.Chirp.QuoteUrl)) {
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
@@ -71,7 +71,7 @@ namespace TweetDuck.Browser.Handling{
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
if (!isSelecting && !isEditing){
|
||||
if (!isSelecting && !isEditing) {
|
||||
AddSeparator(model);
|
||||
|
||||
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
|
||||
@@ -89,16 +89,14 @@ namespace TweetDuck.Browser.Handling{
|
||||
}
|
||||
|
||||
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)){
|
||||
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){
|
||||
switch (commandId) {
|
||||
case CefMenuCommand.Reload:
|
||||
form.InvokeAsyncSafe(form.ReloadToTweetDeck);
|
||||
return true;
|
||||
@@ -128,7 +126,10 @@ namespace TweetDuck.Browser.Handling{
|
||||
return true;
|
||||
|
||||
case MenuScreenshotTweet:
|
||||
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
|
||||
var chirp = Context.Chirp;
|
||||
|
||||
form.InvokeAsyncSafe(() => form.TriggerTweetScreenshot(chirp.ColumnId, chirp.ChirpId));
|
||||
|
||||
return true;
|
||||
|
||||
case MenuOpenQuotedTweetUrl:
|
||||
@@ -153,7 +154,7 @@ namespace TweetDuck.Browser.Handling{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ContextMenu CreateMenu(FormBrowser form){
|
||||
public static ContextMenu CreateMenu(FormBrowser form) {
|
||||
ContextMenu menu = new ContextMenu();
|
||||
|
||||
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
|
||||
@@ -165,13 +166,12 @@ namespace TweetDuck.Browser.Handling{
|
||||
|
||||
menu.Popup += (sender, args) => {
|
||||
menu.MenuItems[1].Checked = Config.MuteNotifications;
|
||||
form.AnalyticsFile.BrowserContextMenus.Trigger();
|
||||
};
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private static void ToggleMuteNotifications(){
|
||||
private static void ToggleMuteNotifications() {
|
||||
Config.MuteNotifications = !Config.MuteNotifications;
|
||||
Config.Save();
|
||||
}
|
||||
|
@@ -1,11 +1,8 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Management.Analytics;
|
||||
|
||||
namespace TweetDuck.Browser.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){
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class ContextMenuGuide : ContextMenuBase {
|
||||
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);
|
||||
|
@@ -2,34 +2,34 @@
|
||||
using TweetDuck.Browser.Notification;
|
||||
using TweetDuck.Controls;
|
||||
|
||||
namespace TweetDuck.Browser.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;
|
||||
namespace TweetDuck.Browser.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){
|
||||
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) {
|
||||
this.form = form;
|
||||
this.enableCustomMenu = enableCustomMenu;
|
||||
}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||
model.Clear();
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||
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){
|
||||
if (enableCustomMenu) {
|
||||
if (form.CanViewDetail) {
|
||||
model.AddItem(MenuViewDetail, "View detail");
|
||||
}
|
||||
|
||||
@@ -37,11 +37,11 @@ namespace TweetDuck.Browser.Handling{
|
||||
model.AddCheckItem(MenuFreeze, "Freeze");
|
||||
model.SetChecked(MenuFreeze, form.FreezeTimer);
|
||||
|
||||
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
|
||||
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)) {
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
||||
|
||||
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
|
||||
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)) {
|
||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
}
|
||||
}
|
||||
@@ -52,16 +52,15 @@ namespace TweetDuck.Browser.Handling{
|
||||
|
||||
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)){
|
||||
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){
|
||||
switch (commandId) {
|
||||
case MenuSkipTweet:
|
||||
form.InvokeAsyncSafe(form.FinishCurrentNotification);
|
||||
return true;
|
||||
@@ -86,7 +85,7 @@ namespace TweetDuck.Browser.Handling{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
||||
}
|
||||
|
@@ -1,35 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
sealed class DragHandlerBrowser : IDragHandler{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class DragHandlerBrowser : IDragHandler {
|
||||
private readonly RequestHandlerBrowser requestHandler;
|
||||
|
||||
public DragHandlerBrowser(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);
|
||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
|
||||
void TriggerDragStart(string type, string data = null) {
|
||||
browserControl.ExecuteJsAsync("window.TDGF_onGlobalDragStart", type, data);
|
||||
}
|
||||
|
||||
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
||||
|
||||
if (dragData.IsLink){
|
||||
if (dragData.IsLink) {
|
||||
TriggerDragStart("link", dragData.LinkUrl);
|
||||
}
|
||||
else if (dragData.IsFragment){
|
||||
else if (dragData.IsFragment) {
|
||||
TriggerDragStart("text", dragData.FragmentText.Trim());
|
||||
}
|
||||
else{
|
||||
else {
|
||||
TriggerDragStart("unknown");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions){}
|
||||
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions) {}
|
||||
}
|
||||
}
|
||||
|
@@ -3,10 +3,12 @@ using System.IO;
|
||||
using System.Text;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Browser.Handling.Filters{
|
||||
abstract class ResponseFilterBase : IResponseFilter{
|
||||
private enum State{
|
||||
Reading, Writing, Done
|
||||
namespace TweetDuck.Browser.Handling.Filters {
|
||||
abstract class ResponseFilterBase : IResponseFilter {
|
||||
private enum State {
|
||||
Reading,
|
||||
Writing,
|
||||
Done
|
||||
}
|
||||
|
||||
private readonly Encoding encoding;
|
||||
@@ -15,17 +17,17 @@ namespace TweetDuck.Browser.Handling.Filters{
|
||||
private State state;
|
||||
private int offset;
|
||||
|
||||
protected ResponseFilterBase(int totalBytes, Encoding encoding){
|
||||
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){
|
||||
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));
|
||||
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;
|
||||
@@ -33,7 +35,7 @@ namespace TweetDuck.Browser.Handling.Filters{
|
||||
dataInRead = bytesToRead;
|
||||
dataOutWritten = 0;
|
||||
|
||||
if (offset >= responseLength){
|
||||
if (offset >= responseLength) {
|
||||
responseData = encoding.GetBytes(ProcessResponse(encoding.GetString(responseData)));
|
||||
state = State.Writing;
|
||||
offset = 0;
|
||||
@@ -41,10 +43,10 @@ namespace TweetDuck.Browser.Handling.Filters{
|
||||
|
||||
return FilterStatus.NeedMoreData;
|
||||
}
|
||||
else if (state == State.Writing){
|
||||
int bytesToWrite = Math.Min(responseLength - offset, (int)Math.Min(dataOut.Length, int.MaxValue));
|
||||
else if (state == State.Writing) {
|
||||
int bytesToWrite = Math.Min(responseLength - offset, (int) Math.Min(dataOut.Length, int.MaxValue));
|
||||
|
||||
if (bytesToWrite > 0){
|
||||
if (bytesToWrite > 0) {
|
||||
dataOut.Write(responseData, offset, bytesToWrite);
|
||||
offset += bytesToWrite;
|
||||
}
|
||||
@@ -52,15 +54,15 @@ namespace TweetDuck.Browser.Handling.Filters{
|
||||
dataOutWritten = bytesToWrite;
|
||||
dataInRead = 0;
|
||||
|
||||
if (offset < responseLength){
|
||||
if (offset < responseLength) {
|
||||
return FilterStatus.NeedMoreData;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
state = State.Done;
|
||||
return FilterStatus.Done;
|
||||
}
|
||||
}
|
||||
else{
|
||||
else {
|
||||
throw new InvalidOperationException("This resource filter cannot be reused.");
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,20 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace TweetDuck.Browser.Handling.Filters{
|
||||
sealed class ResponseFilterVendor : ResponseFilterBase{
|
||||
namespace TweetDuck.Browser.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 ResponseFilterVendor(int totalBytes) : base(totalBytes, Encoding.UTF8) {}
|
||||
|
||||
public override bool InitFilter(){
|
||||
public override bool InitFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override string ProcessResponse(string text){
|
||||
protected override string ProcessResponse(string text) {
|
||||
return RegexRestoreJQuery.Replace(text, "window.$$=$1;$&", 1);
|
||||
}
|
||||
|
||||
public override void Dispose(){}
|
||||
public override void Dispose() {}
|
||||
}
|
||||
}
|
||||
|
@@ -3,13 +3,13 @@ using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using TweetDuck.Configuration;
|
||||
|
||||
namespace TweetDuck.Browser.Handling.General{
|
||||
sealed class BrowserProcessHandler : IBrowserProcessHandler{
|
||||
public static Task UpdatePrefs(){
|
||||
namespace TweetDuck.Browser.Handling.General {
|
||||
sealed class BrowserProcessHandler : IBrowserProcessHandler {
|
||||
public static Task UpdatePrefs() {
|
||||
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
|
||||
}
|
||||
|
||||
private static void UpdatePrefsInternal(){
|
||||
private static void UpdatePrefsInternal() {
|
||||
UserConfig config = Program.Config.User;
|
||||
using IRequestContext ctx = Cef.GetGlobalRequestContext();
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace TweetDuck.Browser.Handling.General{
|
||||
ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
|
||||
}
|
||||
|
||||
void IBrowserProcessHandler.OnContextInitialized(){
|
||||
void IBrowserProcessHandler.OnContextInitialized() {
|
||||
UpdatePrefsInternal();
|
||||
}
|
||||
|
||||
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
|
||||
void IDisposable.Dispose(){}
|
||||
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay) {}
|
||||
void IDisposable.Dispose() {}
|
||||
}
|
||||
}
|
||||
|
35
Browser/Handling/General/CustomLifeSpanHandler.cs
Normal file
35
Browser/Handling/General/CustomLifeSpanHandler.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Handling.General {
|
||||
sealed class CustomLifeSpanHandler : LifeSpanHandler {
|
||||
private static bool IsPopupAllowed(string url) {
|
||||
return url.StartsWith("https://twitter.com/teams/authorize?");
|
||||
}
|
||||
|
||||
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl) {
|
||||
switch (targetDisposition) {
|
||||
case WindowOpenDisposition.NewBackgroundTab:
|
||||
case WindowOpenDisposition.NewForegroundTab:
|
||||
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override 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);
|
||||
}
|
||||
|
||||
protected override bool DoClose(IWebBrowser browserControl, IBrowser browser) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,59 +3,59 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.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){
|
||||
namespace TweetDuck.Browser.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.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter));
|
||||
|
||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||
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){
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
string ext = Path.GetExtension(dialog.FileName)?.ToLower();
|
||||
callback.Continue(acceptFilters.FindIndex(filter => ParseFileType(filter).Contains(ext)), dialog.FileNames.ToList());
|
||||
}
|
||||
else{
|
||||
else {
|
||||
callback.Cancel();
|
||||
}
|
||||
|
||||
callback.Dispose();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ParseFileType(string type){
|
||||
if (string.IsNullOrEmpty(type)){
|
||||
return new string[0];
|
||||
private static IEnumerable<string> ParseFileType(string type) {
|
||||
if (string.IsNullOrEmpty(type)) {
|
||||
return StringUtils.EmptyArray;
|
||||
}
|
||||
|
||||
if (type[0] == '.'){
|
||||
return new string[]{ type };
|
||||
if (type[0] == '.') {
|
||||
return new string[] { type };
|
||||
}
|
||||
|
||||
switch(type){
|
||||
case "image/jpeg": return new string[]{ ".jpg", ".jpeg" };
|
||||
case "image/png": return new string[]{ ".png" };
|
||||
case "image/gif": return new string[]{ ".gif" };
|
||||
case "image/webp": return new string[]{ ".webp" };
|
||||
case "video/mp4": return new string[]{ ".mp4" };
|
||||
case "video/quicktime": return new string[]{ ".mov", ".qt" };
|
||||
switch (type) {
|
||||
case "image/jpeg": return new string[] { ".jpg", ".jpeg" };
|
||||
case "image/png": return new string[] { ".png" };
|
||||
case "image/gif": return new string[] { ".gif" };
|
||||
case "image/webp": return new string[] { ".webp" };
|
||||
case "video/mp4": return new string[] { ".mp4" };
|
||||
case "video/quicktime": return new string[] { ".mov", ".qt" };
|
||||
}
|
||||
|
||||
System.Diagnostics.Debugger.Break();
|
||||
return new string[0];
|
||||
return StringUtils.EmptyArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,14 +5,14 @@ using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Handling.General{
|
||||
sealed class JavaScriptDialogHandler : IJsDialogHandler{
|
||||
private static FormMessage CreateMessageForm(string caption, string text){
|
||||
namespace TweetDuck.Browser.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){
|
||||
icon = text.Substring(0, pipe) switch{
|
||||
if (pipe != -1) {
|
||||
icon = text.Substring(0, pipe) switch {
|
||||
"error" => MessageBoxIcon.Error,
|
||||
"warning" => MessageBoxIcon.Warning,
|
||||
"info" => MessageBoxIcon.Information,
|
||||
@@ -20,7 +20,7 @@ namespace TweetDuck.Browser.Handling.General{
|
||||
_ => MessageBoxIcon.None
|
||||
};
|
||||
|
||||
if (icon != MessageBoxIcon.None){
|
||||
if (icon != MessageBoxIcon.None) {
|
||||
text = text.Substring(pipe + 1);
|
||||
}
|
||||
}
|
||||
@@ -28,21 +28,21 @@ namespace TweetDuck.Browser.Handling.General{
|
||||
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){
|
||||
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){
|
||||
if (dialogType == CefJsDialogType.Alert) {
|
||||
form = CreateMessageForm("Browser Message", messageText);
|
||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Confirm){
|
||||
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){
|
||||
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);
|
||||
@@ -50,7 +50,7 @@ namespace TweetDuck.Browser.Handling.General{
|
||||
float dpiScale = form.GetDPIScale();
|
||||
int inputPad = form.HasIcon ? 43 : 0;
|
||||
|
||||
input = new TextBox{
|
||||
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)),
|
||||
@@ -61,17 +61,17 @@ namespace TweetDuck.Browser.Handling.General{
|
||||
form.ActiveControl = input;
|
||||
form.Height += input.Size.Height + input.Margin.Vertical;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
callback.Continue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = form.ShowDialog() == DialogResult.OK;
|
||||
|
||||
if (input == null){
|
||||
if (input == null) {
|
||||
callback.Continue(success);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
callback.Continue(success, input.Text);
|
||||
input.Dispose();
|
||||
}
|
||||
@@ -82,12 +82,12 @@ namespace TweetDuck.Browser.Handling.General{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IJsDialogHandler.OnBeforeUnloadDialog(IWebBrowser browserControl, IBrowser browser, string messageText, bool isReload, IJsDialogCallback callback){
|
||||
bool IJsDialogHandler.OnBeforeUnloadDialog(IWebBrowser browserControl, IBrowser browser, string messageText, bool isReload, IJsDialogCallback callback) {
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){}
|
||||
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){}
|
||||
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser) {}
|
||||
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser) {}
|
||||
}
|
||||
}
|
||||
|
@@ -1,38 +0,0 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Handling.General{
|
||||
sealed class LifeSpanHandler : ILifeSpanHandler{
|
||||
private static bool IsPopupAllowed(string url){
|
||||
return url.StartsWith("https://twitter.com/teams/authorize?");
|
||||
}
|
||||
|
||||
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
|
||||
switch(targetDisposition){
|
||||
case WindowOpenDisposition.NewBackgroundTab:
|
||||
case WindowOpenDisposition.NewForegroundTab:
|
||||
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
||||
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){}
|
||||
}
|
||||
}
|
@@ -1,46 +1,27 @@
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.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){
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
class KeyboardHandlerBase : IKeyboardHandler {
|
||||
protected virtual bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
|
||||
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I) {
|
||||
browserControl.OpenDevToolsCustom();
|
||||
}
|
||||
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("devtools://")){
|
||||
return HandleRawKey(browserControl, browser, (Keys)windowsKeyCode, modifiers);
|
||||
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("devtools://")) {
|
||||
return HandleRawKey(browserControl, (Keys) windowsKeyCode, modifiers);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,16 @@
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
sealed class KeyboardHandlerBrowser : KeyboardHandlerBase{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class KeyboardHandlerBrowser : KeyboardHandlerBase {
|
||||
private readonly FormBrowser form;
|
||||
|
||||
public KeyboardHandlerBrowser(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);
|
||||
protected override bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
|
||||
return base.HandleRawKey(browserControl, key, modifiers) || form.ProcessBrowserKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,37 +3,30 @@ using CefSharp;
|
||||
using TweetDuck.Browser.Notification;
|
||||
using TweetDuck.Controls;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
sealed class KeyboardHandlerNotification : KeyboardHandlerBase{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class KeyboardHandlerNotification : KeyboardHandlerBase {
|
||||
private readonly FormNotificationBase notification;
|
||||
|
||||
public KeyboardHandlerNotification(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)){
|
||||
protected override bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
|
||||
if (base.HandleRawKey(browserControl, key, modifiers)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch(key){
|
||||
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:
|
||||
|
@@ -2,20 +2,20 @@
|
||||
using CefSharp.Handler;
|
||||
using TweetDuck.Browser.Handling.General;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
class RequestHandlerBase : RequestHandler{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
class RequestHandlerBase : RequestHandler {
|
||||
private readonly bool autoReload;
|
||||
|
||||
public RequestHandlerBase(bool autoReload){
|
||||
public RequestHandlerBase(bool autoReload) {
|
||||
this.autoReload = autoReload;
|
||||
}
|
||||
|
||||
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
|
||||
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
||||
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
|
||||
return CustomLifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
||||
}
|
||||
|
||||
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
if (autoReload){
|
||||
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
|
||||
if (autoReload) {
|
||||
browser.Reload();
|
||||
}
|
||||
}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
using CefSharp;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class RequestHandlerBrowser : RequestHandlerBase {
|
||||
public string BlockNextUserNavUrl { get; set; }
|
||||
|
||||
public RequestHandlerBrowser() : base(true){}
|
||||
public RequestHandlerBrowser() : base(true) {}
|
||||
|
||||
protected override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect){
|
||||
if (userGesture && request.TransitionType == TransitionType.LinkClicked){
|
||||
protected 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;
|
||||
}
|
||||
else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)){
|
||||
else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -5,35 +5,35 @@ using System.Text;
|
||||
using CefSharp;
|
||||
using CefSharp.Callback;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
sealed class ResourceHandlerNotification : IResourceHandler{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class ResourceHandlerNotification : IResourceHandler {
|
||||
private readonly NameValueCollection headers = new NameValueCollection(0);
|
||||
private MemoryStream dataIn;
|
||||
|
||||
public void SetHTML(string html){
|
||||
public void SetHTML(string html) {
|
||||
dataIn?.Dispose();
|
||||
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
if (dataIn != null){
|
||||
public void Dispose() {
|
||||
if (dataIn != null) {
|
||||
dataIn.Dispose();
|
||||
dataIn = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback){
|
||||
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback) {
|
||||
callback.Dispose();
|
||||
handleRequest = true;
|
||||
|
||||
if (dataIn != null){
|
||||
if (dataIn != null) {
|
||||
dataIn.Position = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl){
|
||||
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl) {
|
||||
redirectUrl = null;
|
||||
|
||||
response.MimeType = "text/html";
|
||||
@@ -43,37 +43,37 @@ namespace TweetDuck.Browser.Handling{
|
||||
responseLength = dataIn?.Length ?? 0;
|
||||
}
|
||||
|
||||
bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback){
|
||||
bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback) {
|
||||
callback?.Dispose(); // TODO unnecessary null check once ReadResponse is removed
|
||||
|
||||
try{
|
||||
try {
|
||||
byte[] buffer = new byte[Math.Min(dataIn.Length - dataIn.Position, dataOut.Length)];
|
||||
int length = buffer.Length;
|
||||
|
||||
dataIn.Read(buffer, 0, length);
|
||||
dataOut.Write(buffer, 0, length);
|
||||
bytesRead = length;
|
||||
}catch{ // catch IOException, possibly NullReferenceException if dataIn is null
|
||||
} catch { // catch IOException, possibly NullReferenceException if dataIn is null
|
||||
bytesRead = 0;
|
||||
}
|
||||
|
||||
return bytesRead > 0;
|
||||
}
|
||||
|
||||
bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback){
|
||||
bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback) {
|
||||
bytesSkipped = -2; // ERR_FAILED
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback){
|
||||
return ((IResourceHandler)this).Open(request, out bool _, callback);
|
||||
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback) {
|
||||
return ((IResourceHandler) this).Open(request, out bool _, callback);
|
||||
}
|
||||
|
||||
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback){
|
||||
return ((IResourceHandler)this).Read(dataOut, out bytesRead, null);
|
||||
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback) {
|
||||
return ((IResourceHandler) this).Read(dataOut, out bytesRead, null);
|
||||
}
|
||||
|
||||
void IResourceHandler.Cancel(){}
|
||||
void IResourceHandler.Cancel() {}
|
||||
}
|
||||
}
|
||||
|
101
Browser/Handling/ResourceProvider.cs
Normal file
101
Browser/Handling/ResourceProvider.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using CefSharp;
|
||||
using TweetLib.Core.Browser;
|
||||
using IOFile = System.IO.File;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
internal sealed class ResourceProvider : IResourceProvider<IResourceHandler> {
|
||||
private readonly Dictionary<string, ICachedResource> cache = new Dictionary<string, ICachedResource>();
|
||||
|
||||
public IResourceHandler Status(HttpStatusCode code, string message) {
|
||||
return CreateStatusHandler(code, message);
|
||||
}
|
||||
|
||||
public IResourceHandler File(string path) {
|
||||
string key = new Uri(path).LocalPath;
|
||||
|
||||
if (cache.TryGetValue(key, out var cachedResource)) {
|
||||
return cachedResource.GetResource();
|
||||
}
|
||||
|
||||
cachedResource = FileWithCaching(path);
|
||||
cache[key] = cachedResource;
|
||||
return cachedResource.GetResource();
|
||||
}
|
||||
|
||||
private ICachedResource FileWithCaching(string path) {
|
||||
try {
|
||||
return new CachedFile(System.IO.File.ReadAllBytes(path), Path.GetExtension(path));
|
||||
} catch (FileNotFoundException) {
|
||||
return new CachedStatus(HttpStatusCode.NotFound, "File not found.");
|
||||
} catch (DirectoryNotFoundException) {
|
||||
return new CachedStatus(HttpStatusCode.NotFound, "Directory not found.");
|
||||
} catch (Exception e) {
|
||||
return new CachedStatus(HttpStatusCode.InternalServerError, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCache() {
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
private static ResourceHandler CreateHandler(byte[] bytes) {
|
||||
var handler = ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
|
||||
handler.Headers.Set("Access-Control-Allow-Origin", "*");
|
||||
return handler;
|
||||
}
|
||||
|
||||
private static IResourceHandler CreateFileContentsHandler(byte[] bytes, string extension) {
|
||||
if (bytes.Length == 0) {
|
||||
return CreateStatusHandler(HttpStatusCode.NoContent, "File is empty."); // FromByteArray crashes CEF internals with no contents
|
||||
}
|
||||
else {
|
||||
var handler = CreateHandler(bytes);
|
||||
handler.MimeType = Cef.GetMimeType(extension);
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
private static IResourceHandler CreateStatusHandler(HttpStatusCode code, string message) {
|
||||
var handler = CreateHandler(Encoding.UTF8.GetBytes(message));
|
||||
handler.StatusCode = (int) code;
|
||||
return handler;
|
||||
}
|
||||
|
||||
private interface ICachedResource {
|
||||
IResourceHandler GetResource();
|
||||
}
|
||||
|
||||
private sealed class CachedFile : ICachedResource {
|
||||
private readonly byte[] bytes;
|
||||
private readonly string extension;
|
||||
|
||||
public CachedFile(byte[] bytes, string extension) {
|
||||
this.bytes = bytes;
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
public IResourceHandler GetResource() {
|
||||
return CreateFileContentsHandler(bytes, extension);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CachedStatus : ICachedResource {
|
||||
private readonly HttpStatusCode code;
|
||||
private readonly string message;
|
||||
|
||||
public CachedStatus(HttpStatusCode code, string message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public IResourceHandler GetResource() {
|
||||
return CreateStatusHandler(code, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,19 +2,19 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Browser.Data;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
abstract class ResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler{
|
||||
private class SelfFactoryImpl : IResourceRequestHandlerFactory{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
abstract class ResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler {
|
||||
private class SelfFactoryImpl : IResourceRequestHandlerFactory {
|
||||
private readonly ResourceRequestHandler me;
|
||||
|
||||
public SelfFactoryImpl(ResourceRequestHandler me){
|
||||
public SelfFactoryImpl(ResourceRequestHandler me) {
|
||||
this.me = me;
|
||||
}
|
||||
|
||||
bool IResourceRequestHandlerFactory.HasHandlers { get; } = true;
|
||||
bool IResourceRequestHandlerFactory.HasHandlers => true;
|
||||
|
||||
[SuppressMessage("ReSharper", "RedundantAssignment")]
|
||||
IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling){
|
||||
IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) {
|
||||
disableDefaultHandling = me.ResourceHandlers.HasHandler(request);
|
||||
return me;
|
||||
}
|
||||
@@ -23,12 +23,12 @@ namespace TweetDuck.Browser.Handling{
|
||||
public IResourceRequestHandlerFactory SelfFactory { get; }
|
||||
public ResourceHandlers ResourceHandlers { get; }
|
||||
|
||||
protected ResourceRequestHandler(){
|
||||
protected ResourceRequestHandler() {
|
||||
this.SelfFactory = new SelfFactoryImpl(this);
|
||||
this.ResourceHandlers = new ResourceHandlers();
|
||||
}
|
||||
|
||||
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request){
|
||||
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request) {
|
||||
return ResourceHandlers.GetHandler(request);
|
||||
}
|
||||
}
|
||||
|
@@ -4,58 +4,55 @@ using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using CefSharp;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
class ResourceRequestHandlerBase : ResourceRequestHandler{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
class ResourceRequestHandlerBase : ResourceRequestHandler {
|
||||
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$");
|
||||
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
|
||||
|
||||
public static void LoadResourceRewriteRules(string rules){
|
||||
if (string.IsNullOrEmpty(rules)){
|
||||
public static void LoadResourceRewriteRules(string rules) {
|
||||
if (string.IsNullOrEmpty(rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TweetDeckHashes.Clear();
|
||||
|
||||
foreach(string rule in rules.Replace(" ", "").ToLower().Split(',')){
|
||||
foreach (string rule in rules.Replace(" ", "").ToLower().Split(',')) {
|
||||
var (key, hash) = StringUtils.SplitInTwo(rule, '=') ?? throw new ArgumentException("A rule must have one '=' character: " + rule);
|
||||
|
||||
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))){
|
||||
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))) {
|
||||
TweetDeckHashes.Add(key, hash);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
throw new ArgumentException("Invalid hash characters: " + rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||
if (request.ResourceType == ResourceType.CspReport){
|
||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
|
||||
if (request.ResourceType == ResourceType.CspReport) {
|
||||
callback.Dispose();
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
protected 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){
|
||||
protected 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){
|
||||
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{
|
||||
else {
|
||||
Program.Reporter.LogVerbose("[RequestHandlerBase] Replacing " + url + " hash with " + hash);
|
||||
request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3");
|
||||
return true;
|
||||
|
@@ -3,36 +3,36 @@ using TweetDuck.Browser.Handling.Filters;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Browser.Handling{
|
||||
class ResourceRequestHandlerBrowser : ResourceRequestHandlerBase{
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
class ResourceRequestHandlerBrowser : ResourceRequestHandlerBase {
|
||||
private const string UrlVendorResource = "/dist/vendor";
|
||||
private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
|
||||
private const string UrlVersionCheck = "/web/dist/version.json";
|
||||
|
||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||
if (request.ResourceType == ResourceType.MainFrame){
|
||||
if (request.Url.EndsWith("//twitter.com/")){
|
||||
protected 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 = TwitterUrls.TweetDeck; // redirect plain twitter.com requests, fixes bugs with login 2FA
|
||||
}
|
||||
}
|
||||
else if (request.ResourceType == ResourceType.Image){
|
||||
if (request.Url.Contains(UrlLoadingSpinner)){
|
||||
else if (request.ResourceType == ResourceType.Image) {
|
||||
if (request.Url.Contains(UrlLoadingSpinner)) {
|
||||
request.Url = TwitterUtils.LoadingSpinner.Url;
|
||||
}
|
||||
}
|
||||
else if (request.ResourceType == ResourceType.Script){
|
||||
else if (request.ResourceType == ResourceType.Script) {
|
||||
string url = request.Url;
|
||||
|
||||
if (url.Contains("analytics.")){
|
||||
if (url.Contains("analytics.")) {
|
||||
callback.Dispose();
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
else if (url.Contains(UrlVendorResource)){
|
||||
else if (url.Contains(UrlVendorResource)) {
|
||||
request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true);
|
||||
}
|
||||
}
|
||||
else if (request.ResourceType == ResourceType.Xhr){
|
||||
if (request.Url.Contains(UrlVersionCheck)){
|
||||
else if (request.ResourceType == ResourceType.Xhr) {
|
||||
if (request.Url.Contains(UrlVersionCheck)) {
|
||||
callback.Dispose();
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
@@ -41,8 +41,8 @@ namespace TweetDuck.Browser.Handling{
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
protected 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.Headers["Content-Length"], out int totalBytes)){
|
||||
protected 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.Headers["Content-Length"], out int totalBytes)) {
|
||||
return new ResponseFilterVendor(totalBytes);
|
||||
}
|
||||
|
||||
|
@@ -1,19 +1,21 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Notification.Example{
|
||||
sealed class FormNotificationExample : FormNotificationMain{
|
||||
namespace TweetDuck.Browser.Notification.Example {
|
||||
sealed class FormNotificationExample : FormNotificationMain {
|
||||
public override bool RequiresResize => true;
|
||||
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
|
||||
|
||||
protected override FormBorderStyle NotificationBorderStyle{
|
||||
get{
|
||||
if (Config.NotificationSize == DesktopNotification.Size.Custom){
|
||||
switch(base.NotificationBorderStyle){
|
||||
protected override FormBorderStyle NotificationBorderStyle {
|
||||
get {
|
||||
if (Config.NotificationSize == DesktopNotification.Size.Custom) {
|
||||
switch (base.NotificationBorderStyle) {
|
||||
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
|
||||
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
|
||||
}
|
||||
@@ -29,10 +31,10 @@ namespace TweetDuck.Browser.Notification.Example{
|
||||
|
||||
private readonly DesktopNotification exampleNotification;
|
||||
|
||||
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
||||
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false) {
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
|
||||
string exampleTweetHTML = Program.Resources.LoadSilent("pages/example.html")?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
|
||||
string exampleTweetHTML = FileUtils.ReadFileOrNull(Path.Combine(Program.ResourcesPath, "notification/example/example.html"))?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
|
||||
|
||||
#if DEBUG
|
||||
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||
@@ -41,24 +43,24 @@ namespace TweetDuck.Browser.Notification.Example{
|
||||
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||
if (!e.IsLoading) {
|
||||
Ready?.Invoke(this, EventArgs.Empty);
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public override void HideNotification(){
|
||||
public override void HideNotification() {
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification(){}
|
||||
public override void FinishCurrentNotification() {}
|
||||
|
||||
public void ShowExampleNotification(bool reset){
|
||||
if (reset){
|
||||
public void ShowExampleNotification(bool reset) {
|
||||
if (reset) {
|
||||
LoadTweet(exampleNotification);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
PrepareAndDisplayWindow();
|
||||
}
|
||||
|
||||
|
@@ -6,13 +6,12 @@ using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Browser.Handling.General;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Management.Analytics;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Browser.Notification{
|
||||
abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
abstract partial class FormNotificationBase : Form {
|
||||
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandlers.ForBytes(Properties.Resources.avatar, "image/png"));
|
||||
|
||||
protected const string BlankURL = TwitterUrls.TweetDeck + "/?blank";
|
||||
@@ -22,8 +21,8 @@ namespace TweetDuck.Browser.Notification{
|
||||
|
||||
protected static UserConfig Config => Program.Config.User;
|
||||
|
||||
protected static int FontSizeLevel{
|
||||
get => FontSize switch{
|
||||
protected static int FontSizeLevel {
|
||||
get => FontSize switch {
|
||||
"largest" => 4,
|
||||
"large" => 3,
|
||||
"small" => 1,
|
||||
@@ -32,20 +31,20 @@ namespace TweetDuck.Browser.Notification{
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual Point PrimaryLocation{
|
||||
get{
|
||||
protected virtual Point PrimaryLocation {
|
||||
get {
|
||||
Screen screen;
|
||||
|
||||
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length){
|
||||
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length) {
|
||||
screen = Screen.AllScreens[Config.NotificationDisplay - 1];
|
||||
}
|
||||
else{
|
||||
else {
|
||||
screen = Screen.FromControl(owner);
|
||||
}
|
||||
|
||||
int edgeDist = Config.NotificationEdgeDistance;
|
||||
|
||||
switch(Config.NotificationPosition){
|
||||
switch (Config.NotificationPosition) {
|
||||
case DesktopNotification.Position.TopLeft:
|
||||
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist);
|
||||
|
||||
@@ -59,7 +58,7 @@ namespace TweetDuck.Browser.Notification{
|
||||
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
||||
|
||||
case DesktopNotification.Position.Custom:
|
||||
if (!Config.IsCustomNotificationPositionSet){
|
||||
if (!Config.IsCustomNotificationPositionSet) {
|
||||
Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
||||
Config.Save();
|
||||
}
|
||||
@@ -74,36 +73,32 @@ namespace TweetDuck.Browser.Notification{
|
||||
protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
|
||||
protected virtual bool CanDragWindow => true;
|
||||
|
||||
public new Point Location{
|
||||
get{
|
||||
return base.Location;
|
||||
}
|
||||
public new Point Location {
|
||||
get { return base.Location; }
|
||||
|
||||
set{
|
||||
set {
|
||||
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
|
||||
FormBorderStyle = NotificationBorderStyle;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual FormBorderStyle NotificationBorderStyle{
|
||||
get{
|
||||
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
|
||||
protected virtual FormBorderStyle NotificationBorderStyle {
|
||||
get {
|
||||
if (WindowsUtils.ShouldAvoidToolWindow && Visible) { // Visible = workaround for alt+tab
|
||||
return FormBorderStyle.FixedSingle;
|
||||
}
|
||||
else{
|
||||
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;
|
||||
private readonly FormBrowser owner;
|
||||
|
||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
||||
protected readonly ChromiumWebBrowser browser;
|
||||
@@ -125,7 +120,7 @@ namespace TweetDuck.Browser.Notification{
|
||||
public bool FreezeTimer { get; set; }
|
||||
public bool ContextMenuOpen { get; set; }
|
||||
|
||||
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){
|
||||
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu) {
|
||||
InitializeComponent();
|
||||
|
||||
this.owner = owner;
|
||||
@@ -138,10 +133,10 @@ namespace TweetDuck.Browser.Notification{
|
||||
resourceHandlers.Register(TwitterUrls.TweetDeck, () => this.resourceHandler);
|
||||
resourceHandlers.Register(AppLogo);
|
||||
|
||||
this.browser = new ChromiumWebBrowser(BlankURL){
|
||||
this.browser = new ChromiumWebBrowser(BlankURL) {
|
||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
LifeSpanHandler = new CustomLifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBase(false),
|
||||
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
|
||||
};
|
||||
@@ -163,8 +158,8 @@ namespace TweetDuck.Browser.Notification{
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
if (disposing){
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
resourceHandler.Dispose();
|
||||
}
|
||||
@@ -172,8 +167,8 @@ namespace TweetDuck.Browser.Notification{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m){
|
||||
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow){ // WM_SYSCOMMAND, SC_MOVE
|
||||
protected override void WndProc(ref Message m) {
|
||||
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow) { // WM_SYSCOMMAND, SC_MOVE
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -182,13 +177,13 @@ namespace TweetDuck.Browser.Notification{
|
||||
|
||||
// event handlers
|
||||
|
||||
private void owner_FormClosed(object sender, FormClosedEventArgs e){
|
||||
private void owner_FormClosed(object sender, FormClosedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public virtual void HideNotification(){
|
||||
public virtual void HideNotification() {
|
||||
browser.Load(BlankURL);
|
||||
DisplayTooltip(null);
|
||||
|
||||
@@ -196,23 +191,23 @@ namespace TweetDuck.Browser.Notification{
|
||||
currentNotification = null;
|
||||
}
|
||||
|
||||
public virtual void FinishCurrentNotification(){}
|
||||
public virtual void FinishCurrentNotification() {}
|
||||
|
||||
public virtual void PauseNotification(){
|
||||
if (pauseCounter++ == 0 && IsNotificationVisible){
|
||||
public virtual void PauseNotification() {
|
||||
if (pauseCounter++ == 0 && IsNotificationVisible) {
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ResumeNotification(){
|
||||
if (pauseCounter > 0){
|
||||
public virtual void ResumeNotification() {
|
||||
if (pauseCounter > 0) {
|
||||
--pauseCounter;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract string GetTweetHTML(DesktopNotification tweet);
|
||||
|
||||
protected virtual void LoadTweet(DesktopNotification tweet){
|
||||
protected virtual void LoadTweet(DesktopNotification tweet) {
|
||||
currentNotification = tweet;
|
||||
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
||||
|
||||
@@ -220,35 +215,35 @@ namespace TweetDuck.Browser.Notification{
|
||||
DisplayTooltip(null);
|
||||
}
|
||||
|
||||
protected virtual void SetNotificationSize(int width, int height){
|
||||
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(){
|
||||
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){
|
||||
public void ShowTweetDetail() {
|
||||
if (currentNotification != null) {
|
||||
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveToVisibleLocation(){
|
||||
public void MoveToVisibleLocation() {
|
||||
bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
|
||||
Location = PrimaryLocation;
|
||||
|
||||
if (needsReactivating){
|
||||
if (needsReactivating) {
|
||||
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text){
|
||||
if (string.IsNullOrEmpty(text)){
|
||||
public void DisplayTooltip(string text) {
|
||||
if (string.IsNullOrEmpty(text)) {
|
||||
toolTip.Hide(this);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
Point position = PointToClient(Cursor.Position);
|
||||
position.Offset(20, 5);
|
||||
toolTip.Show(text, this, position);
|
||||
|
@@ -8,14 +8,13 @@ using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Browser.Notification{
|
||||
abstract partial class FormNotificationMain : FormNotificationBase{
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
abstract partial class FormNotificationMain : FormNotificationBase {
|
||||
private readonly PluginManager plugins;
|
||||
private readonly int timerBarHeight;
|
||||
|
||||
@@ -31,32 +30,30 @@ namespace TweetDuck.Browser.Notification{
|
||||
private bool? prevDisplayTimer;
|
||||
private int? prevFontSize;
|
||||
|
||||
public virtual bool RequiresResize{
|
||||
get{
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
|
||||
}
|
||||
public virtual bool RequiresResize {
|
||||
get { return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel; }
|
||||
|
||||
set{
|
||||
if (value){
|
||||
set {
|
||||
if (value) {
|
||||
prevDisplayTimer = null;
|
||||
prevFontSize = null;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
prevDisplayTimer = Config.DisplayNotificationTimer;
|
||||
prevFontSize = FontSizeLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int BaseClientWidth{
|
||||
get => Config.NotificationSize switch{
|
||||
private int BaseClientWidth {
|
||||
get => Config.NotificationSize switch {
|
||||
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width,
|
||||
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
|
||||
};
|
||||
}
|
||||
|
||||
private int BaseClientHeight{
|
||||
get => Config.NotificationSize switch{
|
||||
private int BaseClientHeight {
|
||||
get => Config.NotificationSize switch {
|
||||
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height,
|
||||
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
|
||||
};
|
||||
@@ -66,7 +63,7 @@ namespace TweetDuck.Browser.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){
|
||||
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu) {
|
||||
InitializeComponent();
|
||||
|
||||
this.plugins = pluginManager;
|
||||
@@ -76,7 +73,6 @@ namespace TweetDuck.Browser.Notification{
|
||||
browser.RegisterJsBridge("$TD", new TweetDeckBridge.Notification(owner, this));
|
||||
|
||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||
|
||||
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser, url => TwitterUrls.IsTweetDeck(url) && url != BlankURL));
|
||||
|
||||
@@ -86,8 +82,8 @@ namespace TweetDuck.Browser.Notification{
|
||||
|
||||
// helpers
|
||||
|
||||
private void SetOpacity(int opacity){
|
||||
if (currentOpacity != opacity){
|
||||
private void SetOpacity(int opacity) {
|
||||
if (currentOpacity != opacity) {
|
||||
currentOpacity = opacity;
|
||||
Opacity = opacity / 100.0;
|
||||
}
|
||||
@@ -95,54 +91,53 @@ namespace TweetDuck.Browser.Notification{
|
||||
|
||||
// mouse wheel hook
|
||||
|
||||
private void StartMouseHook(){
|
||||
if (mouseHook == IntPtr.Zero){
|
||||
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)){
|
||||
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){
|
||||
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
|
||||
if (nCode == 0) {
|
||||
int eventType = wParam.ToInt32();
|
||||
|
||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
|
||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser) {
|
||||
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01);
|
||||
|
||||
if (Config.EnableSmoothScrolling){
|
||||
browser.ExecuteScriptAsync("window.TDGF_scrollSmoothly", (int)Math.Round(-delta / 0.6));
|
||||
if (Config.EnableSmoothScrolling) {
|
||||
browser.ExecuteJsAsync("window.TDGF_scrollSmoothly", (int) Math.Round(-delta / 0.6));
|
||||
}
|
||||
else{
|
||||
else {
|
||||
browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None);
|
||||
}
|
||||
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)) {
|
||||
int extraButton = NativeMethods.GetMouseHookData(lParam);
|
||||
|
||||
if (extraButton == 2){ // forward button
|
||||
if (extraButton == 2) { // forward button
|
||||
this.InvokeAsyncSafe(FinishCurrentNotification);
|
||||
}
|
||||
else if (extraButton == 1){ // back button
|
||||
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){
|
||||
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp) {
|
||||
blockXButtonUp = false;
|
||||
|
||||
if (!Visible){
|
||||
if (!Visible) {
|
||||
StopMouseHook(false);
|
||||
}
|
||||
|
||||
@@ -155,15 +150,15 @@ namespace TweetDuck.Browser.Notification{
|
||||
|
||||
// event handlers
|
||||
|
||||
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (e.CloseReason == CloseReason.UserClosing){
|
||||
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 != BlankURL){
|
||||
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||
if (!e.IsLoading && browser.Address != BlankURL) {
|
||||
this.InvokeSafe(() => {
|
||||
Visible = true; // ensures repaint before moving the window to a visible location
|
||||
timerDisplayDelay.Start();
|
||||
@@ -171,53 +166,44 @@ namespace TweetDuck.Browser.Notification{
|
||||
}
|
||||
}
|
||||
|
||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
IFrame frame = e.Frame;
|
||||
|
||||
if (frame.IsMain && browser.Address != BlankURL){
|
||||
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
||||
CefScriptExecutor.RunFile(frame, "notification.js");
|
||||
}
|
||||
}
|
||||
|
||||
private void timerDisplayDelay_Tick(object sender, EventArgs e){
|
||||
private void timerDisplayDelay_Tick(object sender, EventArgs e) {
|
||||
OnNotificationReady();
|
||||
timerDisplayDelay.Stop();
|
||||
}
|
||||
|
||||
private void timerHideProgress_Tick(object sender, EventArgs e){
|
||||
private void timerHideProgress_Tick(object sender, EventArgs e) {
|
||||
bool isCursorInside = Bounds.Contains(Cursor.Position);
|
||||
|
||||
if (isCursorInside){
|
||||
if (isCursorInside) {
|
||||
StartMouseHook();
|
||||
SetOpacity(100);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
StopMouseHook(false);
|
||||
SetOpacity(Config.NotificationWindowOpacity);
|
||||
}
|
||||
|
||||
if (isCursorInside || FreezeTimer || ContextMenuOpen){
|
||||
if (isCursorInside || FreezeTimer || ContextMenuOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeLeft -= timerProgress.Interval;
|
||||
|
||||
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double)totalTime);
|
||||
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double) totalTime);
|
||||
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value);
|
||||
|
||||
if (timeLeft <= 0){
|
||||
if (timeLeft <= 0) {
|
||||
FinishCurrentNotification();
|
||||
}
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public virtual void ShowNotification(DesktopNotification notification){
|
||||
public virtual void ShowNotification(DesktopNotification notification) {
|
||||
LoadTweet(notification);
|
||||
}
|
||||
|
||||
public override void HideNotification(){
|
||||
public override void HideNotification() {
|
||||
base.HideNotification();
|
||||
|
||||
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||
@@ -227,12 +213,12 @@ namespace TweetDuck.Browser.Notification{
|
||||
StopMouseHook(false);
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification(){
|
||||
public override void FinishCurrentNotification() {
|
||||
timerProgress.Stop();
|
||||
}
|
||||
|
||||
public override void PauseNotification(){
|
||||
if (!IsPaused){
|
||||
public override void PauseNotification() {
|
||||
if (!IsPaused) {
|
||||
pausedDuringNotification = IsNotificationVisible;
|
||||
timerProgress.Stop();
|
||||
StopMouseHook(true);
|
||||
@@ -241,26 +227,23 @@ namespace TweetDuck.Browser.Notification{
|
||||
base.PauseNotification();
|
||||
}
|
||||
|
||||
public override void ResumeNotification(){
|
||||
public override void ResumeNotification() {
|
||||
bool wasPaused = IsPaused;
|
||||
base.ResumeNotification();
|
||||
|
||||
if (wasPaused && !IsPaused && pausedDuringNotification){
|
||||
if (wasPaused && !IsPaused && pausedDuringNotification) {
|
||||
OnNotificationReady();
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetTweetHTML(DesktopNotification tweet){
|
||||
string html = tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS);
|
||||
|
||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||
html = injection.InjectInto(html);
|
||||
protected override string GetTweetHTML(DesktopNotification tweet) {
|
||||
return tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, new string[] {
|
||||
PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification),
|
||||
CefScriptExecutor.GetBootstrapScript("notification", includeStylesheets: false)
|
||||
});
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
protected override void LoadTweet(DesktopNotification tweet){
|
||||
protected override void LoadTweet(DesktopNotification tweet) {
|
||||
timerProgress.Stop();
|
||||
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
|
||||
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||
@@ -268,12 +251,12 @@ namespace TweetDuck.Browser.Notification{
|
||||
base.LoadTweet(tweet);
|
||||
}
|
||||
|
||||
protected override void SetNotificationSize(int width, int height){
|
||||
if (Config.DisplayNotificationTimer){
|
||||
protected override void SetNotificationSize(int width, int height) {
|
||||
if (Config.DisplayNotificationTimer) {
|
||||
ClientSize = new Size(width, height + timerBarHeight);
|
||||
progressBarTimer.Visible = true;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
ClientSize = new Size(width, height);
|
||||
progressBarTimer.Visible = false;
|
||||
}
|
||||
@@ -281,8 +264,8 @@ namespace TweetDuck.Browser.Notification{
|
||||
browser.ClientSize = new Size(width, height);
|
||||
}
|
||||
|
||||
protected void PrepareAndDisplayWindow(){
|
||||
if (RequiresResize){
|
||||
protected void PrepareAndDisplayWindow() {
|
||||
if (RequiresResize) {
|
||||
RequiresResize = false;
|
||||
SetNotificationSize(BaseClientWidth, BaseClientHeight);
|
||||
}
|
||||
@@ -291,7 +274,7 @@ namespace TweetDuck.Browser.Notification{
|
||||
MoveToVisibleLocation();
|
||||
}
|
||||
|
||||
protected virtual void OnNotificationReady(){
|
||||
protected virtual void OnNotificationReady() {
|
||||
PrepareAndDisplayWindow();
|
||||
timerProgress.Start();
|
||||
}
|
||||
|
@@ -6,21 +6,21 @@ using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
|
||||
namespace TweetDuck.Browser.Notification{
|
||||
sealed partial class FormNotificationTweet : FormNotificationMain{
|
||||
namespace TweetDuck.Browser.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)){
|
||||
protected override bool CanDragWindow {
|
||||
get {
|
||||
if (ModifierKeys.HasFlag(Keys.Alt)) {
|
||||
hasTemporarilyMoved = true;
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -30,22 +30,22 @@ namespace TweetDuck.Browser.Notification{
|
||||
private bool needsTrim;
|
||||
private bool hasTemporarilyMoved;
|
||||
|
||||
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){
|
||||
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){
|
||||
if (Config.MuteNotifications) {
|
||||
PauseNotification();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m){
|
||||
if (m.Msg == 0x00A7){ // WM_NCMBUTTONDOWN
|
||||
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
|
||||
if (hitTest == 2 || hitTest == 20) { // HTCAPTION, HTCLOSE
|
||||
hasTemporarilyMoved = false;
|
||||
MoveToVisibleLocation();
|
||||
return;
|
||||
@@ -57,24 +57,24 @@ namespace TweetDuck.Browser.Notification{
|
||||
|
||||
// event handlers
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e){
|
||||
if (Config.MuteNotifications){
|
||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
||||
if (Config.MuteNotifications) {
|
||||
PauseNotification();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
ResumeNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void timerCursorCheck_Tick(object sender, EventArgs e){
|
||||
if (!IsCursorOverNotificationArea){
|
||||
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){
|
||||
private void timerIdlePauseCheck_Tick(object sender, EventArgs e) {
|
||||
if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds) {
|
||||
ResumeNotification();
|
||||
timerIdlePauseCheck.Stop();
|
||||
}
|
||||
@@ -82,26 +82,25 @@ namespace TweetDuck.Browser.Notification{
|
||||
|
||||
// notification methods
|
||||
|
||||
public override void ShowNotification(DesktopNotification notification){
|
||||
public override void ShowNotification(DesktopNotification notification) {
|
||||
tweetQueue.Enqueue(notification);
|
||||
|
||||
if (!IsPaused){
|
||||
if (!IsPaused) {
|
||||
UpdateTitle();
|
||||
|
||||
if (totalTime == 0){
|
||||
if (totalTime == 0) {
|
||||
LoadNextNotification();
|
||||
}
|
||||
}
|
||||
|
||||
needsTrim |= tweetQueue.Count >= TrimMinimum;
|
||||
AnalyticsFile.DesktopNotifications.Trigger();
|
||||
}
|
||||
|
||||
public override void HideNotification(){
|
||||
public override void HideNotification() {
|
||||
base.HideNotification();
|
||||
tweetQueue.Clear();
|
||||
|
||||
if (needsTrim){
|
||||
if (needsTrim) {
|
||||
tweetQueue.TrimExcess();
|
||||
needsTrim = false;
|
||||
}
|
||||
@@ -109,36 +108,36 @@ namespace TweetDuck.Browser.Notification{
|
||||
hasTemporarilyMoved = false;
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification(){
|
||||
if (tweetQueue.Count > 0){
|
||||
public override void FinishCurrentNotification() {
|
||||
if (tweetQueue.Count > 0) {
|
||||
LoadNextNotification();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
HideNotification();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResumeNotification(){
|
||||
public override void ResumeNotification() {
|
||||
bool wasPaused = IsPaused;
|
||||
base.ResumeNotification();
|
||||
|
||||
if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0){
|
||||
if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0) {
|
||||
LoadNextNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadNextNotification(){
|
||||
if (!IsNotificationVisible){
|
||||
if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){
|
||||
if (!timerCursorCheck.Enabled){
|
||||
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){
|
||||
else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds) {
|
||||
if (!timerIdlePauseCheck.Enabled) {
|
||||
PauseNotification();
|
||||
timerIdlePauseCheck.Start();
|
||||
}
|
||||
@@ -150,15 +149,15 @@ namespace TweetDuck.Browser.Notification{
|
||||
LoadTweet(tweetQueue.Dequeue());
|
||||
}
|
||||
|
||||
protected override void UpdateTitle(){
|
||||
protected override void UpdateTitle() {
|
||||
base.UpdateTitle();
|
||||
|
||||
if (tweetQueue.Count > 0){
|
||||
if (tweetQueue.Count > 0) {
|
||||
Text = Text + " (" + tweetQueue.Count + " more left)";
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNotificationReady(){
|
||||
protected override void OnNotificationReady() {
|
||||
UpdateTitle();
|
||||
base.OnNotificationReady();
|
||||
}
|
||||
|
@@ -1,24 +1,25 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Windows.Forms;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using CefSharp.DevTools.Page;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||
namespace TweetDuck.Browser.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){
|
||||
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, false) {
|
||||
this.plugins = pluginManager;
|
||||
|
||||
int realWidth = BrowserUtils.Scale(width, DpiScale);
|
||||
@@ -26,73 +27,65 @@ namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
browser.RegisterJsBridge("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
|
||||
|
||||
browser.LoadingStateChanged += (sender, args) => {
|
||||
if (args.IsLoading){
|
||||
if (args.IsLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
string script = Program.Resources.LoadSilent("screenshot.js");
|
||||
string script = FileUtils.ReadFileOrNull(Path.Combine(Program.ResourcesPath, "notification/screenshot/screenshot.js"));
|
||||
|
||||
if (script == null){
|
||||
if (script == null) {
|
||||
this.InvokeAsyncSafe(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
using IFrame frame = args.Browser.MainFrame;
|
||||
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
||||
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("1/*FRAMES*/", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
||||
};
|
||||
|
||||
SetNotificationSize(realWidth, 1024);
|
||||
LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
||||
}
|
||||
|
||||
protected override string GetTweetHTML(DesktopNotification tweet){
|
||||
string html = tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS);
|
||||
|
||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||
html = injection.InjectInto(html);
|
||||
protected override string GetTweetHTML(DesktopNotification tweet) {
|
||||
return tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, Array.Empty<string>());
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
private void SetScreenshotHeight(int browserHeight){
|
||||
private void SetScreenshotHeight(int browserHeight) {
|
||||
this.height = BrowserUtils.Scale(browserHeight, SizeScale);
|
||||
}
|
||||
|
||||
public bool TakeScreenshot(bool ignoreHeightError = false){
|
||||
if (!ignoreHeightError){
|
||||
if (height == 0){
|
||||
public Task<Image> TakeScreenshot(bool ignoreHeightError = false) {
|
||||
if (!ignoreHeightError) {
|
||||
if (height == 0) {
|
||||
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
else if (height > ClientSize.Height){
|
||||
else if (height > ClientSize.Height) {
|
||||
FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!WindowsUtils.IsAeroEnabled){
|
||||
MoveToVisibleLocation(); // TODO make this look nicer I guess
|
||||
return Task.Run(TakeScreenshotImpl);
|
||||
}
|
||||
|
||||
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);
|
||||
private async Task<Image> TakeScreenshotImpl() {
|
||||
if (this.height == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Clipboard.SetImage(bmp);
|
||||
return true;
|
||||
Viewport viewport = new Viewport {
|
||||
Width = this.ClientSize.Width,
|
||||
Height = this.height,
|
||||
Scale = 1
|
||||
};
|
||||
|
||||
byte[] data;
|
||||
using (var devToolsClient = browser.GetDevToolsClient()) {
|
||||
data = (await devToolsClient.Page.CaptureScreenshotAsync(CaptureScreenshotFormat.Png, clip: viewport)).Data;
|
||||
}
|
||||
|
||||
return Image.FromStream(new MemoryStream(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,25 +3,25 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
|
||||
namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
sealed class ScreenshotBridge{
|
||||
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){
|
||||
public ScreenshotBridge(Control owner, Action<int> safeSetHeight, Action safeTriggerScreenshot) {
|
||||
this.owner = owner;
|
||||
this.safeSetHeight = safeSetHeight;
|
||||
this.safeTriggerScreenshot = safeTriggerScreenshot;
|
||||
}
|
||||
|
||||
public void SetHeight(int tweetHeight){
|
||||
public void SetHeight(int tweetHeight) {
|
||||
owner.InvokeSafe(() => safeSetHeight(tweetHeight));
|
||||
}
|
||||
|
||||
public void TriggerScreenshot(){
|
||||
public void TriggerScreenshot() {
|
||||
owner.InvokeSafe(safeTriggerScreenshot);
|
||||
}
|
||||
}
|
||||
|
@@ -7,18 +7,19 @@
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Utils;
|
||||
#endif
|
||||
|
||||
namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
sealed class TweetScreenshotManager : IDisposable{
|
||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
||||
sealed class TweetScreenshotManager : IDisposable {
|
||||
private readonly FormBrowser owner;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly Timer timeout;
|
||||
@@ -35,35 +36,35 @@ namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
|
||||
private FormNotificationScreenshotable screenshot;
|
||||
|
||||
public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){
|
||||
public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager) {
|
||||
this.owner = owner;
|
||||
this.plugins = pluginManager;
|
||||
|
||||
this.timeout = new Timer{ Interval = 8000 };
|
||||
this.timeout = new Timer { Interval = 8000 };
|
||||
this.timeout.Tick += timeout_Tick;
|
||||
|
||||
this.disposer = new Timer{ Interval = 1 };
|
||||
this.disposer = new Timer { Interval = 1 };
|
||||
this.disposer.Tick += disposer_Tick;
|
||||
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
this.debugger = new Timer{ Interval = 16 };
|
||||
this.debugger = new Timer { Interval = 16 };
|
||||
this.debugger.Tick += debugger_Tick;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void timeout_Tick(object sender, EventArgs e){
|
||||
private void timeout_Tick(object sender, EventArgs e) {
|
||||
timeout.Stop();
|
||||
OnFinished();
|
||||
}
|
||||
|
||||
private void disposer_Tick(object sender, EventArgs e){
|
||||
private void disposer_Tick(object sender, EventArgs e) {
|
||||
disposer.Stop();
|
||||
screenshot.Dispose();
|
||||
screenshot = null;
|
||||
}
|
||||
|
||||
public void Trigger(string html, int width){
|
||||
if (screenshot != null){
|
||||
public void Trigger(string html, int width) {
|
||||
if (screenshot != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,14 +81,21 @@ namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Callback(){
|
||||
if (!timeout.Enabled){
|
||||
private void Callback() {
|
||||
if (!timeout.Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeout.Stop();
|
||||
screenshot.TakeScreenshot();
|
||||
screenshot.TakeScreenshot().ContinueWith(HandleResult, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
private void HandleResult(Task<Image> task) {
|
||||
if (task.IsFaulted) {
|
||||
Program.Reporter.HandleException("Screenshot Failed", "An error occurred while taking a screenshot.", true, task.Exception!.InnerException);
|
||||
}
|
||||
else if (task.IsCompleted) {
|
||||
Clipboard.SetImage(task.Result);
|
||||
#if !NO_HIDE_SCREENSHOTS
|
||||
OnFinished();
|
||||
#else
|
||||
@@ -95,8 +103,9 @@ namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
screenshot.FormClosed += (sender, args) => disposer.Start();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFinished(){
|
||||
private void OnFinished() {
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
debugger.Stop();
|
||||
#endif
|
||||
@@ -106,7 +115,7 @@ namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
disposer.Start();
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
public void Dispose() {
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
debugger.Dispose();
|
||||
#endif
|
||||
@@ -119,30 +128,36 @@ namespace TweetDuck.Browser.Notification.Screenshot{
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots");
|
||||
|
||||
private void StartDebugger(){
|
||||
private void StartDebugger() {
|
||||
frameCounter = 0;
|
||||
|
||||
try{
|
||||
try {
|
||||
Directory.Delete(DebugScreenshotPath, true);
|
||||
WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10);
|
||||
}catch(DirectoryNotFoundException){}
|
||||
} 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);
|
||||
private void debugger_Tick(object sender, EventArgs e) {
|
||||
if (frameCounter < 63) {
|
||||
int frame = ++frameCounter;
|
||||
screenshot.TakeScreenshot(true).ContinueWith(task => SaveDebugFrame(task, frame), TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
}
|
||||
else{
|
||||
else {
|
||||
debugger.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveDebugFrame(Task<Image> task, int frame) {
|
||||
if (task.IsFaulted) {
|
||||
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frame + ": " + task.Exception!.InnerException);
|
||||
}
|
||||
else if (task.IsCompleted) {
|
||||
task.Result?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frame) + ".png"), ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -9,12 +9,12 @@ using TweetDuck.Dialogs;
|
||||
using TweetDuck.Dialogs.Settings;
|
||||
using TweetDuck.Management;
|
||||
|
||||
namespace TweetDuck.Browser.Notification{
|
||||
static class SoundNotification{
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
static class SoundNotification {
|
||||
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
||||
|
||||
public static Func<IResourceHandler> CreateFileHandler(string path){
|
||||
string mimeType = Path.GetExtension(path) switch{
|
||||
public static Func<IResourceHandler> CreateFileHandler(string path) {
|
||||
string mimeType = Path.GetExtension(path) switch {
|
||||
".weba" => "audio/webm",
|
||||
".webm" => "audio/webm",
|
||||
".wav" => "audio/wav",
|
||||
@@ -25,9 +25,9 @@ namespace TweetDuck.Browser.Notification{
|
||||
_ => null
|
||||
};
|
||||
|
||||
try{
|
||||
try {
|
||||
return ResourceHandlers.ForBytes(File.ReadAllBytes(path), mimeType);
|
||||
}catch{
|
||||
} catch {
|
||||
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
||||
|
||||
browser?.InvokeAsyncSafe(() => {
|
||||
@@ -38,7 +38,7 @@ namespace TweetDuck.Browser.Notification{
|
||||
btnViewOptions.Width += 16;
|
||||
btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y);
|
||||
|
||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){
|
||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions) {
|
||||
browser.OpenSettings(typeof(TabSettingsSounds));
|
||||
}
|
||||
});
|
||||
|
@@ -4,10 +4,14 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using Res = TweetDuck.Properties.Resources;
|
||||
|
||||
namespace TweetDuck.Browser{
|
||||
sealed partial class TrayIcon : Component{
|
||||
public enum Behavior{ // keep order
|
||||
Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined
|
||||
namespace TweetDuck.Browser {
|
||||
sealed partial class TrayIcon : Component {
|
||||
public enum Behavior { // keep order
|
||||
Disabled,
|
||||
DisplayOnly,
|
||||
MinimizeToTray,
|
||||
CloseToTray,
|
||||
Combined
|
||||
}
|
||||
|
||||
private static UserConfig Config => Program.Config.User;
|
||||
@@ -15,25 +19,21 @@ namespace TweetDuck.Browser{
|
||||
public event EventHandler ClickRestore;
|
||||
public event EventHandler ClickClose;
|
||||
|
||||
public bool Visible{
|
||||
get{
|
||||
return notifyIcon.Visible;
|
||||
}
|
||||
public bool Visible {
|
||||
get { return notifyIcon.Visible; }
|
||||
|
||||
set{
|
||||
set {
|
||||
notifyIcon.Visible = value;
|
||||
hasNotifications = false;
|
||||
UpdateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasNotifications{
|
||||
get{
|
||||
return hasNotifications;
|
||||
}
|
||||
public bool HasNotifications {
|
||||
get { return hasNotifications; }
|
||||
|
||||
set{
|
||||
if (hasNotifications != value){
|
||||
set {
|
||||
if (hasNotifications != value) {
|
||||
hasNotifications = value;
|
||||
UpdateIcon();
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace TweetDuck.Browser{
|
||||
private readonly ContextMenu contextMenu;
|
||||
private bool hasNotifications;
|
||||
|
||||
public TrayIcon(){
|
||||
public TrayIcon() {
|
||||
InitializeComponent();
|
||||
|
||||
this.contextMenu = new ContextMenu();
|
||||
@@ -59,12 +59,12 @@ namespace TweetDuck.Browser{
|
||||
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
|
||||
}
|
||||
|
||||
public TrayIcon(IContainer container) : this(){
|
||||
public TrayIcon(IContainer container) : this() {
|
||||
container.Add(this);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
if (disposing){
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
contextMenu.Dispose();
|
||||
}
|
||||
@@ -72,52 +72,52 @@ namespace TweetDuck.Browser{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void UpdateIcon(){
|
||||
if (Visible){
|
||||
private void UpdateIcon() {
|
||||
if (Visible) {
|
||||
notifyIcon.Icon = hasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray;
|
||||
}
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e){
|
||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
private void trayIcon_MouseClick(object sender, MouseEventArgs e){
|
||||
if (e.Button == MouseButtons.Left){
|
||||
private void trayIcon_MouseClick(object sender, MouseEventArgs e) {
|
||||
if (e.Button == MouseButtons.Left) {
|
||||
menuItemRestore_Click(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void contextMenu_Popup(object sender, EventArgs e){
|
||||
private void contextMenu_Popup(object sender, EventArgs e) {
|
||||
contextMenu.MenuItems[1].Checked = Config.MuteNotifications;
|
||||
}
|
||||
|
||||
private void menuItemRestore_Click(object sender, EventArgs e){
|
||||
private void menuItemRestore_Click(object sender, EventArgs e) {
|
||||
ClickRestore?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void menuItemMuteNotifications_Click(object sender, EventArgs e){
|
||||
private void menuItemMuteNotifications_Click(object sender, EventArgs e) {
|
||||
Config.MuteNotifications = !contextMenu.MenuItems[1].Checked;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
private void menuItemClose_Click(object sender, EventArgs e){
|
||||
private void menuItemClose_Click(object sender, EventArgs e) {
|
||||
ClickClose?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
static class BehaviorExtensions{
|
||||
public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior){
|
||||
static class BehaviorExtensions {
|
||||
public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior) {
|
||||
return behavior != TrayIcon.Behavior.Disabled;
|
||||
}
|
||||
|
||||
public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior){
|
||||
public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior) {
|
||||
return behavior == TrayIcon.Behavior.MinimizeToTray || behavior == TrayIcon.Behavior.Combined;
|
||||
}
|
||||
|
||||
public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior){
|
||||
public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior) {
|
||||
return behavior == TrayIcon.Behavior.CloseToTray || behavior == TrayIcon.Behavior.Combined;
|
||||
}
|
||||
}
|
||||
|
@@ -19,23 +19,22 @@ using TweetLib.Core.Features.Plugins.Enums;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Browser{
|
||||
sealed class TweetDeckBrowser : IDisposable{
|
||||
namespace TweetDuck.Browser {
|
||||
sealed class TweetDeckBrowser : IDisposable {
|
||||
private static UserConfig Config => Program.Config.User;
|
||||
|
||||
private const string ErrorUrl = "http://td/error";
|
||||
private const string TwitterStyleUrl = "https://abs.twimg.com/tduck/css";
|
||||
private const string NamespaceTweetDeck = "tweetdeck";
|
||||
|
||||
public bool Ready { get; private set; }
|
||||
|
||||
public bool Enabled{
|
||||
public bool Enabled {
|
||||
get => browser.Enabled;
|
||||
set => browser.Enabled = value;
|
||||
}
|
||||
|
||||
public bool IsTweetDeckWebsite{
|
||||
get{
|
||||
if (!Ready){
|
||||
public bool IsTweetDeckWebsite {
|
||||
get {
|
||||
if (!Ready) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -49,7 +48,7 @@ namespace TweetDuck.Browser{
|
||||
|
||||
private string prevSoundNotificationPath = null;
|
||||
|
||||
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge){
|
||||
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge) {
|
||||
var resourceRequestHandler = new ResourceRequestHandlerBrowser();
|
||||
resourceHandlers = resourceRequestHandler.ResourceHandlers;
|
||||
|
||||
@@ -58,13 +57,13 @@ namespace TweetDuck.Browser{
|
||||
|
||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||
|
||||
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck){
|
||||
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
|
||||
DialogHandler = new FileDialogHandler(),
|
||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
||||
MenuHandler = new ContextMenuBrowser(owner),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBrowser(owner),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
LifeSpanHandler = new CustomLifeSpanHandler(),
|
||||
RequestHandler = requestHandler,
|
||||
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
|
||||
};
|
||||
@@ -77,7 +76,6 @@ namespace TweetDuck.Browser{
|
||||
this.browser.RegisterJsBridge("$TD", tdBridge);
|
||||
this.browser.RegisterJsBridge("$TDU", updateBridge);
|
||||
|
||||
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.Location = ControlExtensions.InvisibleLocation;
|
||||
this.browser.SetupZoomEvents();
|
||||
@@ -91,25 +89,25 @@ namespace TweetDuck.Browser{
|
||||
|
||||
// setup and management
|
||||
|
||||
public void PrepareSize(Size size){
|
||||
if (!Ready){
|
||||
public void PrepareSize(Size size) {
|
||||
if (!Ready) {
|
||||
browser.Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBrowserReady(){
|
||||
if (!Ready){
|
||||
private void OnBrowserReady() {
|
||||
if (!Ready) {
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
Ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Focus(){
|
||||
public void Focus() {
|
||||
browser.Focus();
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
public void Dispose() {
|
||||
Config.MuteToggled -= Config_MuteToggled;
|
||||
Config.SoundNotificationChanged -= Config_SoundNotificationInfoChanged;
|
||||
|
||||
@@ -118,9 +116,9 @@ namespace TweetDuck.Browser{
|
||||
|
||||
// event handlers
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
foreach(string word in TwitterUtils.DictionaryWords){
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||
if (!e.IsLoading) {
|
||||
foreach (string word in TwitterUtils.DictionaryWords) {
|
||||
browser.AddWordToDictionary(word);
|
||||
}
|
||||
|
||||
@@ -129,160 +127,145 @@ namespace TweetDuck.Browser{
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e) {
|
||||
IFrame frame = e.Frame;
|
||||
|
||||
if (frame.IsMain){
|
||||
if (frame.IsMain) {
|
||||
string url = frame.Url;
|
||||
|
||||
if (TwitterUrls.IsTwitter(url)){
|
||||
string css = Program.Resources.Load("styles/twitter.css");
|
||||
resourceHandlers.Register(TwitterStyleUrl, ResourceHandlers.ForString(css, "text/css"));
|
||||
|
||||
CefScriptExecutor.RunFile(frame, "twitter.js");
|
||||
}
|
||||
|
||||
if (!TwitterUrls.IsTwitterLogin2Factor(url)){
|
||||
if (TwitterUrls.IsTweetDeck(url) || (TwitterUrls.IsTwitter(url) && !TwitterUrls.IsTwitterLogin2Factor(url))) {
|
||||
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
||||
IFrame frame = e.Frame;
|
||||
string url = frame.Url;
|
||||
|
||||
if (frame.IsMain){
|
||||
if (TwitterUrls.IsTweetDeck(url)){
|
||||
if (frame.IsMain) {
|
||||
if (TwitterUrls.IsTweetDeck(url)) {
|
||||
UpdateProperties();
|
||||
CefScriptExecutor.RunFile(frame, "code.js");
|
||||
|
||||
InjectBrowserCSS();
|
||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||
Config_SoundNotificationInfoChanged(null, EventArgs.Empty);
|
||||
CefScriptExecutor.RunBootstrap(frame, NamespaceTweetDeck);
|
||||
|
||||
TweetDeckBridge.ResetStaticProperties();
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){
|
||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
|
||||
CefScriptExecutor.RunScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
|
||||
}
|
||||
|
||||
if (Config.FirstRun){
|
||||
CefScriptExecutor.RunFile(frame, "introduction.js");
|
||||
if (Config.FirstRun) {
|
||||
CefScriptExecutor.RunBootstrap(frame, "introduction");
|
||||
}
|
||||
}
|
||||
else if (TwitterUrls.IsTwitter(url)) {
|
||||
CefScriptExecutor.RunBootstrap(frame, "login");
|
||||
}
|
||||
|
||||
CefScriptExecutor.RunBootstrap(frame, "update");
|
||||
}
|
||||
}
|
||||
|
||||
CefScriptExecutor.RunFile(frame, "update.js");
|
||||
}
|
||||
|
||||
if (url == ErrorUrl){
|
||||
resourceHandlers.Unregister(ErrorUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_LoadError(object sender, LoadErrorEventArgs e){
|
||||
if (e.ErrorCode == CefErrorCode.Aborted){
|
||||
private void browser_LoadError(object sender, LoadErrorEventArgs e) {
|
||||
if (e.ErrorCode == CefErrorCode.Aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
|
||||
string errorPage = Program.Resources.LoadSilent("pages/error.html");
|
||||
|
||||
if (errorPage != null){
|
||||
if (!e.FailedUrl.StartsWith("td://", StringComparison.Ordinal)) {
|
||||
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
|
||||
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
|
||||
|
||||
resourceHandlers.Register(ErrorUrl, ResourceHandlers.ForString(errorPage.Replace("{err}", errorTitle)));
|
||||
browser.Load(ErrorUrl);
|
||||
}
|
||||
browser.Load("td://resources/error/error.html#" + Uri.EscapeDataString(errorTitle));
|
||||
}
|
||||
}
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e){
|
||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
||||
UpdateProperties();
|
||||
}
|
||||
|
||||
private void Config_SoundNotificationInfoChanged(object sender, EventArgs e){
|
||||
private void Config_SoundNotificationInfoChanged(object sender, EventArgs e) {
|
||||
const string soundUrl = "https://ton.twimg.com/tduck/updatesnd";
|
||||
|
||||
bool hasCustomSound = Config.IsCustomSoundNotificationSet;
|
||||
string newNotificationPath = Config.NotificationSoundPath;
|
||||
|
||||
if (prevSoundNotificationPath != newNotificationPath){
|
||||
if (prevSoundNotificationPath != newNotificationPath) {
|
||||
prevSoundNotificationPath = newNotificationPath;
|
||||
|
||||
if (hasCustomSound){
|
||||
if (hasCustomSound) {
|
||||
resourceHandlers.Register(soundUrl, SoundNotification.CreateFileHandler(newNotificationPath));
|
||||
}
|
||||
else{
|
||||
else {
|
||||
resourceHandlers.Unregister(soundUrl);
|
||||
}
|
||||
}
|
||||
|
||||
browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Config.NotificationSoundVolume);
|
||||
browser.ExecuteJsAsync("TDGF_setSoundNotificationData", hasCustomSound, Config.NotificationSoundVolume);
|
||||
}
|
||||
|
||||
// external handling
|
||||
|
||||
public void HideVideoOverlay(bool focus){
|
||||
if (focus){
|
||||
public void HideVideoOverlay(bool focus) {
|
||||
if (focus) {
|
||||
browser.GetBrowser().GetHost().SendFocusEvent(true);
|
||||
}
|
||||
|
||||
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
|
||||
browser.ExecuteJsAsync("$('#td-video-player-overlay').remove()");
|
||||
}
|
||||
|
||||
// javascript calls
|
||||
|
||||
public void ReloadToTweetDeck(){
|
||||
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUrls.TweetDeck}'");
|
||||
public void ReloadToTweetDeck() {
|
||||
browser.ExecuteJsAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUrls.TweetDeck}'");
|
||||
}
|
||||
|
||||
public void UpdateProperties(){
|
||||
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser));
|
||||
public void OnModulesLoaded(string moduleNamespace) {
|
||||
if (moduleNamespace == NamespaceTweetDeck) {
|
||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||
Config_SoundNotificationInfoChanged(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void InjectBrowserCSS(){
|
||||
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", Program.Resources.Load("styles/browser.css")?.TrimEnd() ?? string.Empty);
|
||||
public void UpdateProperties() {
|
||||
browser.ExecuteJsAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser));
|
||||
}
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
||||
public void ReinjectCustomCSS(string css) {
|
||||
browser.ExecuteJsAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
||||
}
|
||||
|
||||
public void OnMouseClickExtra(IntPtr param){
|
||||
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF);
|
||||
public void OnMouseClickExtra(IntPtr param) {
|
||||
browser.ExecuteJsAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF);
|
||||
}
|
||||
|
||||
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
|
||||
browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
|
||||
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
|
||||
browser.ExecuteJsAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
|
||||
}
|
||||
|
||||
public void AddSearchColumn(string query){
|
||||
browser.ExecuteScriptAsync("TDGF_performSearch", query);
|
||||
public void AddSearchColumn(string query) {
|
||||
browser.ExecuteJsAsync("TDGF_performSearch", query);
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
|
||||
public void TriggerTweetScreenshot(string columnId, string chirpId) {
|
||||
browser.ExecuteJsAsync("TDGF_triggerScreenshot", columnId, chirpId);
|
||||
}
|
||||
|
||||
public void ReloadColumns(){
|
||||
browser.ExecuteScriptAsync("TDGF_reloadColumns()");
|
||||
public void ReloadColumns() {
|
||||
browser.ExecuteJsAsync("TDGF_reloadColumns()");
|
||||
}
|
||||
|
||||
public void PlaySoundNotification(){
|
||||
browser.ExecuteScriptAsync("TDGF_playSoundNotification()");
|
||||
public void PlaySoundNotification() {
|
||||
browser.ExecuteJsAsync("TDGF_playSoundNotification()");
|
||||
}
|
||||
|
||||
public void ApplyROT13(){
|
||||
browser.ExecuteScriptAsync("TDGF_applyROT13()");
|
||||
public void ApplyROT13() {
|
||||
browser.ExecuteJsAsync("TDGF_applyROT13()");
|
||||
}
|
||||
|
||||
public void ShowUpdateNotification(string versionTag, string releaseNotes){
|
||||
browser.ExecuteScriptAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes)));
|
||||
public void ShowUpdateNotification(string versionTag, string releaseNotes) {
|
||||
browser.ExecuteJsAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes)));
|
||||
}
|
||||
|
||||
public void OpenDevTools(){
|
||||
public void OpenDevTools() {
|
||||
browser.OpenDevToolsCustom();
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
static class Arguments{
|
||||
namespace TweetDuck.Configuration {
|
||||
static class Arguments {
|
||||
// public args
|
||||
public const string ArgDataFolder = "-datafolder";
|
||||
public const string ArgLogging = "-log";
|
||||
@@ -18,15 +18,15 @@ namespace TweetDuck.Configuration{
|
||||
// class data and methods
|
||||
private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs());
|
||||
|
||||
public static bool HasFlag(string flag){
|
||||
public static bool HasFlag(string flag) {
|
||||
return Current.HasFlag(flag);
|
||||
}
|
||||
|
||||
public static string GetValue(string key){
|
||||
public static string GetValue(string key) {
|
||||
return Current.GetValue(key);
|
||||
}
|
||||
|
||||
public static CommandLineArgs GetCurrentClean(){
|
||||
public static CommandLineArgs GetCurrentClean() {
|
||||
CommandLineArgs args = Current.Clone();
|
||||
args.RemoveFlag(ArgRestart);
|
||||
args.RemoveFlag(ArgImportCookies);
|
||||
@@ -35,13 +35,13 @@ namespace TweetDuck.Configuration{
|
||||
return args;
|
||||
}
|
||||
|
||||
public static CommandLineArgs GetCurrentForInstaller(){
|
||||
public static CommandLineArgs GetCurrentForInstaller() {
|
||||
CommandLineArgs args = GetCurrentClean();
|
||||
args.AddFlag(ArgUpdated);
|
||||
return args;
|
||||
}
|
||||
|
||||
public static string GetCurrentForInstallerCmd(){
|
||||
public static string GetCurrentForInstallerCmd() {
|
||||
return GetCurrentForInstaller().ToString().Replace("\"", "::");
|
||||
}
|
||||
}
|
||||
|
@@ -6,8 +6,8 @@ using TweetLib.Core.Serialization.Converters;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class ConfigManager : IConfigManager{
|
||||
namespace TweetDuck.Configuration {
|
||||
sealed class ConfigManager : IConfigManager {
|
||||
public UserConfig User { get; }
|
||||
public SystemConfig System { get; }
|
||||
public PluginConfig Plugins { get; }
|
||||
@@ -20,12 +20,12 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
private readonly IConfigInstance<BaseConfig>[] infoList;
|
||||
|
||||
public ConfigManager(){
|
||||
public ConfigManager() {
|
||||
User = new UserConfig(this);
|
||||
System = new SystemConfig(this);
|
||||
Plugins = new PluginConfig(this);
|
||||
|
||||
infoList = new IConfigInstance<BaseConfig>[]{
|
||||
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<PluginConfig>(Program.PluginConfigFilePath, Plugins)
|
||||
@@ -35,7 +35,7 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
infoUser.Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
|
||||
|
||||
infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{
|
||||
infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point> {
|
||||
ConvertToString = value => $"{value.X} {value.Y}",
|
||||
ConvertToObject = value => {
|
||||
int[] elements = StringUtils.ParseInts(value, ' ');
|
||||
@@ -43,7 +43,7 @@ namespace TweetDuck.Configuration{
|
||||
}
|
||||
});
|
||||
|
||||
infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
|
||||
infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size> {
|
||||
ConvertToString = value => $"{value.Width} {value.Height}",
|
||||
ConvertToObject = value => {
|
||||
int[] elements = StringUtils.ParseInts(value, ' ');
|
||||
@@ -52,29 +52,29 @@ namespace TweetDuck.Configuration{
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadAll(){
|
||||
public void LoadAll() {
|
||||
infoUser.Load();
|
||||
infoSystem.Load();
|
||||
infoPlugins.Load();
|
||||
}
|
||||
|
||||
public void SaveAll(){
|
||||
public void SaveAll() {
|
||||
infoUser.Save();
|
||||
infoSystem.Save();
|
||||
infoPlugins.Save();
|
||||
}
|
||||
|
||||
public void ReloadAll(){
|
||||
public void ReloadAll() {
|
||||
infoUser.Reload();
|
||||
infoSystem.Reload();
|
||||
infoPlugins.Reload();
|
||||
}
|
||||
|
||||
void IConfigManager.TriggerProgramRestartRequested(){
|
||||
void IConfigManager.TriggerProgramRestartRequested() {
|
||||
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){
|
||||
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance) {
|
||||
Type instanceType = instance.GetType();
|
||||
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
||||
}
|
||||
|
@@ -5,8 +5,8 @@ using TweetLib.Core.Features.Plugins.Config;
|
||||
using TweetLib.Core.Features.Plugins.Events;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class PluginConfig : BaseConfig, IPluginConfig{
|
||||
namespace TweetDuck.Configuration {
|
||||
sealed class PluginConfig : BaseConfig, IPluginConfig {
|
||||
private static readonly string[] DefaultDisabled = {
|
||||
"official/clear-columns",
|
||||
"official/reply-account"
|
||||
@@ -22,9 +22,9 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
// END OF CONFIG
|
||||
|
||||
public PluginConfig(IConfigManager configManager) : base(configManager){}
|
||||
public PluginConfig(IConfigManager configManager) : base(configManager) {}
|
||||
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
|
||||
return new PluginConfig(configManager);
|
||||
}
|
||||
|
||||
@@ -32,19 +32,19 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
IEnumerable<string> IPluginConfig.DisabledPlugins => disabled;
|
||||
|
||||
void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins){
|
||||
void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins) {
|
||||
disabled.Clear();
|
||||
disabled.UnionWith(newDisabledPlugins);
|
||||
}
|
||||
|
||||
public void SetEnabled(Plugin plugin, bool enabled){
|
||||
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
|
||||
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){
|
||||
public bool IsEnabled(Plugin plugin) {
|
||||
return !disabled.Contains(plugin.Identifier);
|
||||
}
|
||||
}
|
||||
|
@@ -1,27 +1,26 @@
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class SystemConfig : BaseConfig{
|
||||
|
||||
namespace TweetDuck.Configuration {
|
||||
sealed class SystemConfig : BaseConfig {
|
||||
// CONFIGURATION DATA
|
||||
|
||||
public bool _hardwareAcceleration = true;
|
||||
private bool _hardwareAcceleration = true;
|
||||
|
||||
public bool ClearCacheAutomatically { get; set; } = true;
|
||||
public int ClearCacheThreshold { get; set; } = 250;
|
||||
|
||||
// SPECIAL PROPERTIES
|
||||
|
||||
public bool HardwareAcceleration{
|
||||
public bool HardwareAcceleration {
|
||||
get => _hardwareAcceleration;
|
||||
set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value);
|
||||
}
|
||||
|
||||
// END OF CONFIG
|
||||
|
||||
public SystemConfig(IConfigManager configManager) : base(configManager){}
|
||||
public SystemConfig(IConfigManager configManager) : base(configManager) {}
|
||||
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
|
||||
return new SystemConfig(configManager);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using TweetDuck.Browser;
|
||||
using TweetDuck.Browser.Data;
|
||||
@@ -7,12 +8,13 @@ using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class UserConfig : BaseConfig{
|
||||
|
||||
namespace TweetDuck.Configuration {
|
||||
sealed class UserConfig : BaseConfig {
|
||||
// CONFIGURATION DATA
|
||||
|
||||
public bool FirstRun { get; set; } = true;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
public bool AllowDataCollection { get; set; } = false;
|
||||
|
||||
public WindowState BrowserWindow { get; set; } = new WindowState();
|
||||
@@ -27,6 +29,7 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
private bool _enableSmoothScrolling = true;
|
||||
private bool _enableTouchAdjustment = false;
|
||||
private bool _enableColorProfileDetection = false;
|
||||
private string _customCefArgs = null;
|
||||
|
||||
public string BrowserPath { get; set; } = null;
|
||||
@@ -79,6 +82,9 @@ namespace TweetDuck.Configuration{
|
||||
public string CustomBrowserCSS { get; set; } = null;
|
||||
public string CustomNotificationCSS { get; set; } = null;
|
||||
|
||||
private bool _useSystemProxyForAllConnections;
|
||||
|
||||
public bool DevToolsInContextMenu { get; set; } = false;
|
||||
public bool DevToolsWindowOnTop { get; set; } = true;
|
||||
|
||||
// SPECIAL PROPERTIES
|
||||
@@ -89,47 +95,57 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
|
||||
|
||||
public string NotificationSoundPath{
|
||||
public string NotificationSoundPath {
|
||||
get => _notificationSoundPath ?? string.Empty;
|
||||
set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
|
||||
}
|
||||
|
||||
public int NotificationSoundVolume{
|
||||
public int NotificationSoundVolume {
|
||||
get => _notificationSoundVolume;
|
||||
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
|
||||
}
|
||||
|
||||
public bool MuteNotifications{
|
||||
public bool MuteNotifications {
|
||||
get => _muteNotifications;
|
||||
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
|
||||
}
|
||||
|
||||
public int ZoomLevel{
|
||||
public int ZoomLevel {
|
||||
get => _zoomLevel;
|
||||
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
|
||||
}
|
||||
|
||||
public TrayIcon.Behavior TrayBehavior{
|
||||
public TrayIcon.Behavior TrayBehavior {
|
||||
get => _trayBehavior;
|
||||
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
|
||||
}
|
||||
|
||||
public bool EnableSmoothScrolling{
|
||||
public bool EnableSmoothScrolling {
|
||||
get => _enableSmoothScrolling;
|
||||
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value);
|
||||
}
|
||||
|
||||
public bool EnableTouchAdjustment{
|
||||
public bool EnableTouchAdjustment {
|
||||
get => _enableTouchAdjustment;
|
||||
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
|
||||
}
|
||||
|
||||
public string CustomCefArgs{
|
||||
public bool EnableColorProfileDetection {
|
||||
get => _enableColorProfileDetection;
|
||||
set => UpdatePropertyWithRestartRequest(ref _enableColorProfileDetection, value);
|
||||
}
|
||||
|
||||
public bool UseSystemProxyForAllConnections {
|
||||
get => _useSystemProxyForAllConnections;
|
||||
set => UpdatePropertyWithRestartRequest(ref _useSystemProxyForAllConnections, value);
|
||||
}
|
||||
|
||||
public string CustomCefArgs {
|
||||
get => _customCefArgs;
|
||||
set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value);
|
||||
}
|
||||
|
||||
public string SpellCheckLanguage{
|
||||
public string SpellCheckLanguage {
|
||||
get => _spellCheckLanguage;
|
||||
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value);
|
||||
}
|
||||
@@ -143,9 +159,9 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
// END OF CONFIG
|
||||
|
||||
public UserConfig(IConfigManager configManager) : base(configManager){}
|
||||
public UserConfig(IConfigManager configManager) : base(configManager) {}
|
||||
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
|
||||
return new UserConfig(configManager);
|
||||
}
|
||||
}
|
||||
|
@@ -3,73 +3,73 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls{
|
||||
static class ControlExtensions{
|
||||
namespace TweetDuck.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){
|
||||
public static void InvokeSafe(this Control control, Action func) {
|
||||
if (control.InvokeRequired) {
|
||||
control.Invoke(func);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeAsyncSafe(this Control control, Action func){
|
||||
public static void InvokeAsyncSafe(this Control control, Action func) {
|
||||
control.BeginInvoke(func);
|
||||
}
|
||||
|
||||
public static float GetDPIScale(this Control control){
|
||||
public static float GetDPIScale(this Control control) {
|
||||
using Graphics graphics = control.CreateGraphics();
|
||||
return graphics.DpiY / 96F;
|
||||
}
|
||||
|
||||
public static bool IsFullyOutsideView(this Form form){
|
||||
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){
|
||||
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){
|
||||
public static void SetValueInstant(this ProgressBar bar, int value) {
|
||||
if (value == bar.Maximum) {
|
||||
bar.Value = value;
|
||||
bar.Value = value - 1;
|
||||
bar.Value = value;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
bar.Value = value + 1;
|
||||
bar.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValueSafe(this NumericUpDown numUpDown, int value){
|
||||
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){
|
||||
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){
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||
public static void EnableMultilineShortcuts(this TextBox textBox) {
|
||||
textBox.KeyDown += (sender, args) => {
|
||||
if (args.Control && args.KeyCode == Keys.A){
|
||||
((TextBox)sender).SelectAll();
|
||||
if (args.Control && args.KeyCode == Keys.A) {
|
||||
((TextBox) sender).SelectAll();
|
||||
args.SuppressKeyPress = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
@@ -1,15 +1,15 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls{
|
||||
sealed class FlatButton : Button{
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class FlatButton : Button {
|
||||
protected override bool ShowFocusCues => false;
|
||||
|
||||
public FlatButton(){
|
||||
public FlatButton() {
|
||||
GotFocus += FlatButton_GotFocus;
|
||||
}
|
||||
|
||||
private void FlatButton_GotFocus(object sender, EventArgs e){ // removes extra border when focused
|
||||
private void FlatButton_GotFocus(object sender, EventArgs e) { // removes extra border when focused
|
||||
NotifyDefault(false);
|
||||
}
|
||||
}
|
||||
|
@@ -2,35 +2,35 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls{
|
||||
sealed class FlatProgressBar : ProgressBar{
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class FlatProgressBar : ProgressBar {
|
||||
private readonly SolidBrush brush;
|
||||
|
||||
public FlatProgressBar(){
|
||||
public FlatProgressBar() {
|
||||
brush = new SolidBrush(Color.White);
|
||||
|
||||
SetStyle(ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||
}
|
||||
|
||||
public void SetValueInstant(int value){
|
||||
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){
|
||||
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));
|
||||
rect.Width = (int) (rect.Width * ((double) Value / Maximum));
|
||||
e.Graphics.FillRectangle(brush, rect);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
protected override void Dispose(bool disposing) {
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing){
|
||||
if (disposing) {
|
||||
brush.Dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Controls{
|
||||
sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel{
|
||||
protected override void WndProc(ref Message m){
|
||||
if (m.Msg == 0x85){ // WM_NCPAINT
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel {
|
||||
protected override void WndProc(ref Message m) {
|
||||
if (m.Msg == 0x85) { // WM_NCPAINT
|
||||
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
|
||||
}
|
||||
|
||||
|
@@ -2,15 +2,15 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls{
|
||||
sealed class LabelVertical : Label{
|
||||
namespace TweetDuck.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;
|
||||
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){
|
||||
foreach (char chr in Text) {
|
||||
string str = chr.ToString();
|
||||
float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F;
|
||||
|
||||
|
@@ -1,14 +1,14 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls{
|
||||
sealed class NumericUpDownEx : NumericUpDown{
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class NumericUpDownEx : NumericUpDown {
|
||||
public string TextSuffix { get; set ; }
|
||||
|
||||
protected override void UpdateEditText(){
|
||||
protected override void UpdateEditText() {
|
||||
base.UpdateEditText();
|
||||
|
||||
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){
|
||||
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime) {
|
||||
ChangingText = true;
|
||||
Text += TextSuffix;
|
||||
ChangingText = false;
|
||||
|
@@ -5,12 +5,12 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Dialogs{
|
||||
sealed partial class FormAbout : Form, FormManager.IAppDialog{
|
||||
namespace TweetDuck.Dialogs {
|
||||
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(){
|
||||
public FormAbout() {
|
||||
InitializeComponent();
|
||||
|
||||
Text = "About " + Program.BrandName + " " + Program.VersionTag;
|
||||
@@ -26,20 +26,20 @@ namespace TweetDuck.Dialogs{
|
||||
Disposed += (sender, args) => logoStream.Dispose();
|
||||
}
|
||||
|
||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
|
||||
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
|
||||
}
|
||||
|
||||
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
|
||||
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent) {
|
||||
ShowGuide();
|
||||
}
|
||||
|
||||
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){
|
||||
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e) {
|
||||
e.Cancel = true;
|
||||
ShowGuide();
|
||||
}
|
||||
|
||||
private void ShowGuide(){
|
||||
private void ShowGuide() {
|
||||
FormGuide.Show();
|
||||
Close();
|
||||
}
|
||||
|
@@ -1,10 +1,8 @@
|
||||
using System.Drawing;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Browser;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Data;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Browser.Handling.General;
|
||||
@@ -12,45 +10,24 @@ using TweetDuck.Controls;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Dialogs{
|
||||
sealed partial class FormGuide : Form, FormManager.IAppDialog{
|
||||
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
|
||||
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
|
||||
namespace TweetDuck.Dialogs {
|
||||
sealed partial class FormGuide : Form, FormManager.IAppDialog {
|
||||
private const string GuideUrl = @"td://guide/index.html";
|
||||
|
||||
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandlers.ForString(string.Empty));
|
||||
|
||||
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);
|
||||
public static void Show(string hash = null) {
|
||||
string url = GuideUrl + (string.IsNullOrEmpty(hash) ? string.Empty : "#" + hash);
|
||||
FormGuide guide = FormManager.TryFind<FormGuide>();
|
||||
|
||||
if (guide == null){
|
||||
if (guide == null) {
|
||||
FormBrowser owner = FormManager.TryFind<FormBrowser>();
|
||||
|
||||
if (owner != null){
|
||||
owner.AnalyticsFile.OpenGuide.Trigger();
|
||||
if (owner != null) {
|
||||
new FormGuide(url, owner).Show(owner);
|
||||
}
|
||||
}
|
||||
else{
|
||||
else {
|
||||
guide.Reload(url);
|
||||
guide.Activate();
|
||||
}
|
||||
@@ -62,7 +39,7 @@ namespace TweetDuck.Dialogs{
|
||||
|
||||
private string nextUrl;
|
||||
|
||||
private FormGuide(string url, FormBrowser owner){
|
||||
private FormGuide(string url, FormBrowser owner) {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName + " Guide";
|
||||
@@ -72,19 +49,18 @@ namespace TweetDuck.Dialogs{
|
||||
var resourceRequestHandler = new ResourceRequestHandlerBase();
|
||||
resourceRequestHandler.ResourceHandlers.Register(DummyPage);
|
||||
|
||||
this.browser = new ChromiumWebBrowser(url){
|
||||
MenuHandler = new ContextMenuGuide(owner),
|
||||
this.browser = new ChromiumWebBrowser(url) {
|
||||
MenuHandler = new ContextMenuGuide(),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBase(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
LifeSpanHandler = new CustomLifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBase(true),
|
||||
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
|
||||
};
|
||||
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||
|
||||
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
||||
browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
browser.SetupZoomEvents();
|
||||
@@ -93,15 +69,15 @@ namespace TweetDuck.Dialogs{
|
||||
Disposed += (sender, args) => browser.Dispose();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
if (disposing){
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void Reload(string url){
|
||||
private void Reload(string url) {
|
||||
nextUrl = url;
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
browser.Dock = DockStyle.None;
|
||||
@@ -109,12 +85,12 @@ namespace TweetDuck.Dialogs{
|
||||
browser.Load(DummyPage.Url);
|
||||
}
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
if (browser.Address == DummyPage.Url){
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||
if (!e.IsLoading) {
|
||||
if (browser.Address == DummyPage.Url) {
|
||||
browser.Load(nextUrl);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
this.InvokeAsyncSafe(() => {
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
@@ -124,9 +100,5 @@ namespace TweetDuck.Dialogs{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
CefScriptExecutor.RunScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,16 +4,16 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Dialogs{
|
||||
namespace TweetDuck.Dialogs {
|
||||
[Flags]
|
||||
enum ControlType{
|
||||
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{
|
||||
sealed partial class FormMessage : Form {
|
||||
public const string OK = "OK";
|
||||
public const string Yes = "Yes";
|
||||
public const string No = "No";
|
||||
@@ -22,33 +22,33 @@ namespace TweetDuck.Dialogs{
|
||||
public const string Ignore = "Ignore";
|
||||
public const string Exit = "Exit";
|
||||
|
||||
public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
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){
|
||||
if (buttonCancel == null) {
|
||||
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel);
|
||||
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused);
|
||||
}
|
||||
@@ -63,12 +63,12 @@ namespace TweetDuck.Dialogs{
|
||||
public bool HasIcon => icon != null;
|
||||
public int ActionPanelY => panelActions.Location.Y;
|
||||
|
||||
private int ClientWidth{
|
||||
private int ClientWidth {
|
||||
get => ClientSize.Width;
|
||||
set => ClientSize = new Size(value, ClientSize.Height);
|
||||
}
|
||||
|
||||
private int ButtonDistance{
|
||||
private int ButtonDistance {
|
||||
get => BrowserUtils.Scale(96, dpiScale);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace TweetDuck.Dialogs{
|
||||
private int prevLabelWidth, prevLabelHeight;
|
||||
private bool wasLabelMultiline;
|
||||
|
||||
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
|
||||
public FormMessage(string caption, string text, MessageBoxIcon messageIcon) {
|
||||
InitializeComponent();
|
||||
|
||||
this.dpiScale = this.GetDPIScale();
|
||||
@@ -90,7 +90,7 @@ namespace TweetDuck.Dialogs{
|
||||
this.prevLabelHeight = labelMessage.Height;
|
||||
this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
|
||||
|
||||
switch(messageIcon){
|
||||
switch (messageIcon) {
|
||||
case MessageBoxIcon.Information:
|
||||
icon = SystemIcons.Information;
|
||||
break;
|
||||
@@ -119,16 +119,16 @@ namespace TweetDuck.Dialogs{
|
||||
this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
|
||||
}
|
||||
|
||||
private void FormMessage_SizeChanged(object sender, EventArgs e){
|
||||
private void FormMessage_SizeChanged(object sender, EventArgs e) {
|
||||
RecalculateButtonLocation();
|
||||
}
|
||||
|
||||
public Button AddButton(string title, ControlType type){
|
||||
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{
|
||||
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),
|
||||
@@ -139,7 +139,7 @@ namespace TweetDuck.Dialogs{
|
||||
};
|
||||
|
||||
button.Click += (sender, args) => {
|
||||
ClickedButton = (Button)sender;
|
||||
ClickedButton = (Button) sender;
|
||||
DialogResult = result;
|
||||
Close();
|
||||
};
|
||||
@@ -151,22 +151,22 @@ namespace TweetDuck.Dialogs{
|
||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||
RecalculateButtonLocation();
|
||||
|
||||
if (type.HasFlag(ControlType.Accept)){
|
||||
if (type.HasFlag(ControlType.Accept)) {
|
||||
AcceptButton = button;
|
||||
}
|
||||
|
||||
if (type.HasFlag(ControlType.Cancel)){
|
||||
if (type.HasFlag(ControlType.Cancel)) {
|
||||
CancelButton = button;
|
||||
}
|
||||
|
||||
if (type.HasFlag(ControlType.Focused)){
|
||||
if (type.HasFlag(ControlType.Focused)) {
|
||||
ActiveControl = button;
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
public void AddActionControl(Control control){
|
||||
public void AddActionControl(Control control) {
|
||||
panelActions.Controls.Add(control);
|
||||
|
||||
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
|
||||
@@ -175,29 +175,29 @@ namespace TweetDuck.Dialogs{
|
||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||
}
|
||||
|
||||
private void RecalculateButtonLocation(){
|
||||
private void RecalculateButtonLocation() {
|
||||
int dist = ButtonDistance;
|
||||
int start = ClientWidth - dist;
|
||||
|
||||
for(int index = 0; index < buttonCount; index++){
|
||||
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){
|
||||
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){
|
||||
if (isMultiline && !wasLabelMultiline) {
|
||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y - labelOffset);
|
||||
prevLabelHeight += labelOffset;
|
||||
}
|
||||
else if (!isMultiline && wasLabelMultiline){
|
||||
else if (!isMultiline && wasLabelMultiline) {
|
||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y + labelOffset);
|
||||
prevLabelHeight -= labelOffset;
|
||||
}
|
||||
@@ -211,8 +211,8 @@ namespace TweetDuck.Dialogs{
|
||||
wasLabelMultiline = isMultiline;
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e){
|
||||
if (icon != null){
|
||||
protected override void OnPaint(PaintEventArgs e) {
|
||||
if (icon != null) {
|
||||
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1 + BrowserUtils.Scale(25, dpiScale));
|
||||
}
|
||||
|
||||
|
@@ -8,52 +8,48 @@ using TweetDuck.Plugins;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
|
||||
namespace TweetDuck.Dialogs{
|
||||
sealed partial class FormPlugins : Form, FormManager.IAppDialog{
|
||||
namespace TweetDuck.Dialogs {
|
||||
sealed partial class FormPlugins : Form, FormManager.IAppDialog {
|
||||
private static UserConfig Config => Program.Config.User;
|
||||
|
||||
private readonly PluginManager pluginManager;
|
||||
|
||||
public FormPlugins(){
|
||||
public FormPlugins() {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName + " Plugins";
|
||||
}
|
||||
|
||||
public FormPlugins(PluginManager pluginManager) : this(){
|
||||
public FormPlugins(PluginManager pluginManager) : this() {
|
||||
this.pluginManager = pluginManager;
|
||||
|
||||
if (!Config.PluginsWindowSize.IsEmpty){
|
||||
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();
|
||||
};
|
||||
Shown += (sender, args) => { ReloadPluginList(); };
|
||||
|
||||
FormClosed += (sender, args) => {
|
||||
Config.PluginsWindowSize = Size;
|
||||
Config.Save();
|
||||
};
|
||||
|
||||
ResizeEnd += (sender, args) => {
|
||||
timerLayout.Start();
|
||||
};
|
||||
ResizeEnd += (sender, args) => { timerLayout.Start(); };
|
||||
}
|
||||
|
||||
private int GetPluginOrderIndex(Plugin plugin){
|
||||
private int GetPluginOrderIndex(Plugin plugin) {
|
||||
return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2;
|
||||
}
|
||||
|
||||
private void ReloadPluginList(){
|
||||
private void ReloadPluginList() {
|
||||
flowLayoutPlugins.Controls.Clear();
|
||||
flowLayoutPlugins.SuspendLayout();
|
||||
|
||||
foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){
|
||||
foreach (Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)) {
|
||||
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));
|
||||
|
||||
flowLayoutPlugins.Controls.Add(new Panel{
|
||||
flowLayoutPlugins.Controls.Add(new Panel {
|
||||
BackColor = Color.DimGray,
|
||||
Margin = new Padding(0),
|
||||
Size = new Size(1, 1)
|
||||
@@ -66,7 +62,7 @@ namespace TweetDuck.Dialogs{
|
||||
timerLayout.Start();
|
||||
}
|
||||
|
||||
private void timerLayout_Tick(object sender, EventArgs e){
|
||||
private void timerLayout_Tick(object sender, EventArgs e) {
|
||||
timerLayout.Stop();
|
||||
|
||||
// stupid WinForms scrollbars and panels
|
||||
@@ -74,10 +70,10 @@ namespace TweetDuck.Dialogs{
|
||||
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right - 1, Padding.Bottom - 1);
|
||||
}
|
||||
|
||||
public void flowLayoutPlugins_Resize(object sender, EventArgs e){
|
||||
private void flowLayoutPlugins_Resize(object sender, EventArgs e) {
|
||||
Control lastPlugin = flowLayoutPlugins.Controls.OfType<PluginControl>().LastOrDefault();
|
||||
|
||||
if (lastPlugin == null){
|
||||
if (lastPlugin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,7 +83,7 @@ namespace TweetDuck.Dialogs{
|
||||
flowLayoutPlugins.AutoScroll = showScrollBar;
|
||||
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
|
||||
|
||||
foreach(Control control in flowLayoutPlugins.Controls){
|
||||
foreach (Control control in flowLayoutPlugins.Controls) {
|
||||
control.Width = flowLayoutPlugins.Width - control.Margin.Horizontal - horizontalOffset;
|
||||
}
|
||||
|
||||
@@ -95,18 +91,18 @@ namespace TweetDuck.Dialogs{
|
||||
flowLayoutPlugins.Focus();
|
||||
}
|
||||
|
||||
private void btnOpenFolder_Click(object sender, EventArgs e){
|
||||
private void btnOpenFolder_Click(object sender, EventArgs e) {
|
||||
App.SystemHandler.OpenFileExplorer(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)){
|
||||
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){
|
||||
private void btnClose_Click(object sender, EventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
@@ -9,13 +9,12 @@ using TweetDuck.Configuration;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs.Settings;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Management.Analytics;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Systems.Updates;
|
||||
|
||||
namespace TweetDuck.Dialogs{
|
||||
sealed partial class FormSettings : Form, FormManager.IAppDialog{
|
||||
namespace TweetDuck.Dialogs {
|
||||
sealed partial class FormSettings : Form, FormManager.IAppDialog {
|
||||
public bool ShouldReloadBrowser { get; private set; }
|
||||
|
||||
private readonly FormBrowser browser;
|
||||
@@ -26,7 +25,7 @@ namespace TweetDuck.Dialogs{
|
||||
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){
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab) {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName + " Options";
|
||||
@@ -44,34 +43,34 @@ namespace TweetDuck.Dialogs{
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
|
||||
AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification));
|
||||
AddButton("Tray", () => new TabSettingsTray());
|
||||
AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins));
|
||||
AddButton("Feedback", () => new TabSettingsFeedback());
|
||||
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS, this.browser.OpenDevTools));
|
||||
|
||||
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
|
||||
}
|
||||
|
||||
private void PrepareLoad(){
|
||||
private void PrepareLoad() {
|
||||
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
|
||||
}
|
||||
|
||||
private void PrepareUnload(){ // TODO refactor this further later
|
||||
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)){
|
||||
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){
|
||||
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e) {
|
||||
PrepareUnload();
|
||||
|
||||
foreach(SettingsTab tab in tabs.Values){
|
||||
if (tab.IsInitialized){
|
||||
foreach (SettingsTab tab in tabs.Values) {
|
||||
if (tab.IsInitialized) {
|
||||
tab.Control.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -79,17 +78,17 @@ namespace TweetDuck.Dialogs{
|
||||
browser.ResumeNotification();
|
||||
}
|
||||
|
||||
private void btnManageOptions_Click(object sender, EventArgs e){
|
||||
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){
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
if (!dialog.IsRestarting) {
|
||||
browser.ResumeNotification();
|
||||
|
||||
if (dialog.ShouldReloadBrowser){
|
||||
if (dialog.ShouldReloadBrowser) {
|
||||
BrowserProcessHandler.UpdatePrefs();
|
||||
ShouldReloadBrowser = true;
|
||||
}
|
||||
@@ -97,18 +96,18 @@ namespace TweetDuck.Dialogs{
|
||||
|
||||
Close();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
FormClosing += FormSettings_FormClosing;
|
||||
PrepareLoad();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnClose_Click(object sender, EventArgs e){
|
||||
private void btnClose_Click(object sender, EventArgs e) {
|
||||
Close();
|
||||
}
|
||||
|
||||
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTab{
|
||||
FlatButton btn = new FlatButton{
|
||||
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTab {
|
||||
FlatButton btn = new FlatButton {
|
||||
BackColor = SystemColors.Control,
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
Font = SystemFonts.MessageBoxFont,
|
||||
@@ -125,7 +124,7 @@ namespace TweetDuck.Dialogs{
|
||||
|
||||
panelButtons.Controls.Add(btn);
|
||||
|
||||
panelButtons.Controls.Add(new Panel{
|
||||
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),
|
||||
@@ -137,29 +136,29 @@ namespace TweetDuck.Dialogs{
|
||||
btn.Click += (sender, args) => SelectTab<T>();
|
||||
}
|
||||
|
||||
private void SelectTab<T>() where T : BaseTab{
|
||||
private void SelectTab<T>() where T : BaseTab {
|
||||
SelectTab(tabs[typeof(T)]);
|
||||
}
|
||||
|
||||
private void SelectTab(SettingsTab tab){
|
||||
if (currentTab != null){
|
||||
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){
|
||||
if (!tab.IsInitialized) {
|
||||
foreach (Control control in tab.Control.InteractiveControls) {
|
||||
if (control is ComboBox) {
|
||||
control.MouseLeave += control_MouseLeave;
|
||||
}
|
||||
else if (control is TrackBar){
|
||||
else if (control is TrackBar) {
|
||||
control.MouseWheel += control_MouseWheel;
|
||||
}
|
||||
}
|
||||
|
||||
if (tab.Control.Height < panelContents.Height - 2){
|
||||
if (tab.Control.Height < panelContents.Height - 2) {
|
||||
tab.Control.Height = panelContents.Height - 2; // fixes off-by-pixel error on high DPI
|
||||
}
|
||||
|
||||
@@ -181,20 +180,20 @@ namespace TweetDuck.Dialogs{
|
||||
currentTab = tab;
|
||||
}
|
||||
|
||||
private void control_MouseLeave(object sender, EventArgs e){
|
||||
if (sender is ComboBox cb && cb.DroppedDown){
|
||||
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;
|
||||
private void control_MouseWheel(object sender, MouseEventArgs e) {
|
||||
((HandledMouseEventArgs) e).Handled = true;
|
||||
panelContents.Focus();
|
||||
}
|
||||
|
||||
private sealed class SettingsTab{
|
||||
private sealed class SettingsTab {
|
||||
public Button Button { get; }
|
||||
|
||||
public BaseTab Control => control ??= constructor();
|
||||
@@ -203,26 +202,26 @@ namespace TweetDuck.Dialogs{
|
||||
private readonly Func<BaseTab> constructor;
|
||||
private BaseTab control;
|
||||
|
||||
public SettingsTab(Button button, Func<BaseTab> constructor){
|
||||
public SettingsTab(Button button, Func<BaseTab> constructor) {
|
||||
this.Button = button;
|
||||
this.constructor = constructor;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class BaseTab : UserControl{
|
||||
internal class BaseTab : UserControl {
|
||||
protected static UserConfig Config => Program.Config.User;
|
||||
protected static SystemConfig SysConfig => Program.Config.System;
|
||||
|
||||
public IEnumerable<Control> InteractiveControls{
|
||||
get{
|
||||
static IEnumerable<Control> FindInteractiveControls(Control parent){
|
||||
foreach(Control control in parent.Controls){
|
||||
if (control is Panel subPanel){
|
||||
foreach(Control subControl in FindInteractiveControls(subPanel)){
|
||||
public IEnumerable<Control> InteractiveControls {
|
||||
get {
|
||||
static 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{
|
||||
else {
|
||||
yield return control;
|
||||
}
|
||||
}
|
||||
@@ -232,12 +231,12 @@ namespace TweetDuck.Dialogs{
|
||||
}
|
||||
}
|
||||
|
||||
protected BaseTab(){
|
||||
protected BaseTab() {
|
||||
Padding = new Padding(6);
|
||||
}
|
||||
|
||||
public virtual void OnReady(){}
|
||||
public virtual void OnClosing(){}
|
||||
public virtual void OnReady() {}
|
||||
public virtual void OnClosing() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
94
Dialogs/Settings/DialogSettingsAnalytics.Designer.cs
generated
94
Dialogs/Settings/DialogSettingsAnalytics.Designer.cs
generated
@@ -1,94 +0,0 @@
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Management.Analytics;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class DialogSettingsAnalytics : Form{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,17 +3,16 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class DialogSettingsCSS : Form{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
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){
|
||||
public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools) {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName + " Options - CSS";
|
||||
@@ -27,44 +26,40 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
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){
|
||||
private void tabPanel_SelectedIndexChanged(object sender, EventArgs e) {
|
||||
TextBox tb = tabPanel.SelectedTab.Controls.OfType<TextBox>().FirstOrDefault();
|
||||
|
||||
if (tb != null){
|
||||
if (tb != null) {
|
||||
tb.Focus();
|
||||
tb.Select(tb.TextLength, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void textBoxCSS_KeyDown(object sender, KeyEventArgs e){
|
||||
TextBox tb = (TextBox)sender;
|
||||
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){
|
||||
if (e.KeyCode == Keys.Back && e.Modifiers == Keys.Control) {
|
||||
e.SuppressKeyPress = true;
|
||||
|
||||
int deleteTo = tb.SelectionStart;
|
||||
|
||||
if (deleteTo > 0){
|
||||
if (deleteTo > 0) {
|
||||
char initialChar = text[--deleteTo];
|
||||
bool shouldDeleteAlphanumeric = char.IsLetterOrDigit(initialChar);
|
||||
|
||||
while(--deleteTo >= 0){
|
||||
while (--deleteTo >= 0) {
|
||||
if ((shouldDeleteAlphanumeric && !char.IsLetterOrDigit(text[deleteTo])) ||
|
||||
(!shouldDeleteAlphanumeric && text[deleteTo] != initialChar)){
|
||||
(!shouldDeleteAlphanumeric && text[deleteTo] != initialChar)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(deleteTo < text.Length - 1 && text[deleteTo] == '\r' && text[deleteTo + 1] == '\n')){
|
||||
if (!(deleteTo < text.Length - 1 && text[deleteTo] == '\r' && text[deleteTo + 1] == '\n')) {
|
||||
++deleteTo;
|
||||
}
|
||||
|
||||
@@ -72,42 +67,42 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
tb.SelectedText = string.Empty;
|
||||
}
|
||||
}
|
||||
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){
|
||||
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None) {
|
||||
int deleteTo = tb.SelectionStart;
|
||||
|
||||
if (deleteTo > 1 && text[deleteTo - 1] == ' ' && text[deleteTo - 2] == ' '){
|
||||
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){
|
||||
else if (e.KeyCode == Keys.Enter && e.Modifiers == Keys.None && tb.SelectionLength == 0) {
|
||||
int insertAt = tb.SelectionStart, cursorOffset = 0;
|
||||
string insertText;
|
||||
|
||||
if (insertAt == 0){
|
||||
if (insertAt == 0) {
|
||||
return;
|
||||
}
|
||||
else if (text[insertAt - 1] == '{'){
|
||||
else if (text[insertAt - 1] == '{') {
|
||||
insertText = Environment.NewLine + " ";
|
||||
|
||||
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt + 1) : -1;
|
||||
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[] { '{', '}' }, insertAt + 1) : -1;
|
||||
|
||||
if (nextBracket == -1 || text[nextBracket] == '{'){
|
||||
if (nextBracket == -1 || text[nextBracket] == '{') {
|
||||
string insertExtra = Environment.NewLine + "}";
|
||||
insertText += insertExtra;
|
||||
cursorOffset -= insertExtra.Length;
|
||||
}
|
||||
}
|
||||
else{
|
||||
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)){
|
||||
if (!string.IsNullOrEmpty(insertText)) {
|
||||
e.SuppressKeyPress = true;
|
||||
tb.Text = text.Insert(insertAt, insertText);
|
||||
tb.SelectionStart = insertAt + cursorOffset + insertText.Length;
|
||||
@@ -115,26 +110,26 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){
|
||||
private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e) {
|
||||
timerTestBrowser.Stop();
|
||||
timerTestBrowser.Start();
|
||||
}
|
||||
|
||||
private void timerTestBrowser_Tick(object sender, EventArgs e){
|
||||
private void timerTestBrowser_Tick(object sender, EventArgs e) {
|
||||
reinjectBrowserCSS(textBoxBrowserCSS.Text);
|
||||
timerTestBrowser.Stop();
|
||||
}
|
||||
|
||||
private void btnOpenDevTools_Click(object sender, EventArgs e){
|
||||
private void btnOpenDevTools_Click(object sender, EventArgs e) {
|
||||
openDevTools();
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
private void btnApply_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e){
|
||||
private void btnCancel_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
|
@@ -4,13 +4,13 @@ using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class DialogSettingsCefArgs : Form{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class DialogSettingsCefArgs : Form {
|
||||
public string CefArgs => textBoxArgs.Text;
|
||||
|
||||
private readonly string initialArgs;
|
||||
|
||||
public DialogSettingsCefArgs(string args){
|
||||
public DialogSettingsCefArgs(string args) {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName + " Options - CEF Arguments";
|
||||
@@ -20,12 +20,12 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
|
||||
}
|
||||
|
||||
private void btnHelp_Click(object sender, EventArgs e){
|
||||
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){
|
||||
private void btnApply_Click(object sender, EventArgs e) {
|
||||
if (CefArgs == initialArgs) {
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
return;
|
||||
@@ -34,13 +34,13 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
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)){
|
||||
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)) {
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e){
|
||||
private void btnCancel_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
|
@@ -3,21 +3,21 @@ using System.Windows.Forms;
|
||||
using TweetLib.Core.Utils;
|
||||
using IOPath = System.IO.Path;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class DialogSettingsExternalProgram : Form{
|
||||
public string Path{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class DialogSettingsExternalProgram : Form {
|
||||
public string Path {
|
||||
get => StringUtils.NullIfEmpty(textBoxPath.Text);
|
||||
set => textBoxPath.Text = value ?? string.Empty;
|
||||
}
|
||||
|
||||
public string Args{
|
||||
public string Args {
|
||||
get => StringUtils.NullIfEmpty(textBoxArgs.Text);
|
||||
set => textBoxArgs.Text = value ?? string.Empty;
|
||||
}
|
||||
|
||||
private readonly string fileDialogTitle;
|
||||
|
||||
public DialogSettingsExternalProgram(string windowTitle, string fileDialogTitle){
|
||||
public DialogSettingsExternalProgram(string windowTitle, string fileDialogTitle) {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName + " Options - " + windowTitle;
|
||||
@@ -27,8 +27,8 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
this.fileDialogTitle = fileDialogTitle;
|
||||
}
|
||||
|
||||
private void btnBrowse_Click(object sender, EventArgs e){
|
||||
using OpenFileDialog dialog = new OpenFileDialog{
|
||||
private void btnBrowse_Click(object sender, EventArgs e) {
|
||||
using OpenFileDialog dialog = new OpenFileDialog {
|
||||
AutoUpgradeEnabled = true,
|
||||
DereferenceLinks = true,
|
||||
InitialDirectory = IOPath.GetDirectoryName(Path), // returns null if argument is null
|
||||
@@ -36,18 +36,18 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
Filter = "Executables (*.exe;*.bat;*.cmd)|*.exe;*.bat;*.cmd|All Files (*.*)|*.*"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK && Path != dialog.FileName){
|
||||
if (dialog.ShowDialog() == DialogResult.OK && Path != dialog.FileName) {
|
||||
Path = dialog.FileName;
|
||||
Args = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
private void btnApply_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e){
|
||||
private void btnCancel_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
|
@@ -7,24 +7,27 @@ using TweetDuck.Management;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class DialogSettingsManage : Form{
|
||||
private enum State{
|
||||
Deciding, Reset, Import, Export
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class DialogSettingsManage : Form {
|
||||
private enum State {
|
||||
Deciding,
|
||||
Reset,
|
||||
Import,
|
||||
Export
|
||||
}
|
||||
|
||||
private ProfileManager.Items SelectedItems{
|
||||
private ProfileManager.Items SelectedItems {
|
||||
get => _selectedItems;
|
||||
|
||||
set{
|
||||
set {
|
||||
// this will call events and SetFlag, which also updates the UI
|
||||
foreach(KeyValuePair<CheckBox, ProfileManager.Items> kvp in checkBoxMap){
|
||||
foreach (KeyValuePair<CheckBox, ProfileManager.Items> kvp in checkBoxMap) {
|
||||
kvp.Key.Checked = value.HasFlag(kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool SelectedItemsForceRestart{
|
||||
private bool SelectedItemsForceRestart {
|
||||
get => _selectedItems.HasFlag(ProfileManager.Items.Session);
|
||||
}
|
||||
|
||||
@@ -41,7 +44,7 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
|
||||
private ProfileManager.Items _selectedItems = ProfileManager.Items.None;
|
||||
|
||||
public DialogSettingsManage(PluginManager plugins, bool openImportImmediately = false){
|
||||
public DialogSettingsManage(PluginManager plugins, bool openImportImmediately = false) {
|
||||
InitializeComponent();
|
||||
|
||||
this.plugins = plugins;
|
||||
@@ -54,28 +57,28 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
|
||||
this.openImportImmediately = openImportImmediately;
|
||||
|
||||
if (openImportImmediately){
|
||||
if (openImportImmediately) {
|
||||
radioImport.Checked = true;
|
||||
btnContinue_Click(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void radioDecision_CheckedChanged(object sender, EventArgs e){
|
||||
private void radioDecision_CheckedChanged(object sender, EventArgs e) {
|
||||
btnContinue.Enabled = true;
|
||||
}
|
||||
|
||||
private void checkBoxSelection_CheckedChanged(object sender, EventArgs e){
|
||||
CheckBox cb = (CheckBox)sender;
|
||||
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){
|
||||
private void btnContinue_Click(object sender, EventArgs e) {
|
||||
string file;
|
||||
|
||||
switch(currentState){
|
||||
switch (currentState) {
|
||||
case State.Deciding:
|
||||
// Reset
|
||||
if (radioReset.Checked){
|
||||
if (radioReset.Checked) {
|
||||
currentState = State.Reset;
|
||||
|
||||
Text = "Restore Defaults";
|
||||
@@ -83,15 +86,15 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
}
|
||||
|
||||
// Import
|
||||
else if (radioImport.Checked){
|
||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||
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){
|
||||
}) {
|
||||
if (dialog.ShowDialog() != DialogResult.OK) {
|
||||
if (openImportImmediately) {
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -107,13 +110,13 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
Text = "Import Profile";
|
||||
SelectedItems = importManager.FindImportItems();
|
||||
|
||||
foreach(CheckBox cb in checkBoxMap.Keys){
|
||||
foreach (CheckBox cb in checkBoxMap.Keys) {
|
||||
cb.Enabled = cb.Checked;
|
||||
}
|
||||
}
|
||||
|
||||
// Export
|
||||
else if (radioExport.Checked){
|
||||
else if (radioExport.Checked) {
|
||||
currentState = State.Export;
|
||||
|
||||
Text = "Export Profile";
|
||||
@@ -128,34 +131,34 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
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)){
|
||||
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)){
|
||||
if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)) {
|
||||
Program.Config.User.Reset();
|
||||
}
|
||||
|
||||
if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){
|
||||
if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)) {
|
||||
Program.Config.System.Reset();
|
||||
}
|
||||
|
||||
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
|
||||
|
||||
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)){
|
||||
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)) {
|
||||
Program.Config.Plugins.Reset();
|
||||
|
||||
try{
|
||||
try {
|
||||
Directory.Delete(Program.PluginDataPath, true);
|
||||
}catch(Exception ex){
|
||||
} 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);
|
||||
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)){
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -169,16 +172,16 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
break;
|
||||
|
||||
case State.Import:
|
||||
if (importManager.Import(SelectedItems)){
|
||||
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);
|
||||
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)){
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -191,7 +194,7 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
break;
|
||||
|
||||
case State.Export:
|
||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||
using (SaveFileDialog dialog = new SaveFileDialog {
|
||||
AddExtension = true,
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = true,
|
||||
@@ -199,8 +202,8 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
FileName = "TweetDuck.tdsettings",
|
||||
Title = "Export TweetDuck Profile",
|
||||
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
|
||||
}){
|
||||
if (dialog.ShowDialog() != DialogResult.OK){
|
||||
}) {
|
||||
if (dialog.ShowDialog() != DialogResult.OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -215,28 +218,28 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e){
|
||||
private void btnCancel_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Config_ProgramRestartRequested(object sender, EventArgs e){
|
||||
private void Config_ProgramRestartRequested(object sender, EventArgs e) {
|
||||
requestedRestartFromConfig = true;
|
||||
}
|
||||
|
||||
private void SetFlag(ProfileManager.Items flag, bool enable){
|
||||
private void SetFlag(ProfileManager.Items flag, bool enable) {
|
||||
_selectedItems = enable ? _selectedItems | flag : _selectedItems & ~flag;
|
||||
btnContinue.Enabled = _selectedItems != ProfileManager.Items.None;
|
||||
|
||||
if (currentState == State.Import){
|
||||
if (currentState == State.Import) {
|
||||
btnContinue.Text = SelectedItemsForceRestart ? "Import && Restart" : "Import Profile";
|
||||
}
|
||||
else if (currentState == State.Reset){
|
||||
else if (currentState == State.Reset) {
|
||||
btnContinue.Text = SelectedItemsForceRestart ? "Restore && Restart" : "Restore Defaults";
|
||||
}
|
||||
}
|
||||
|
||||
private void RestartProgram(params string[] extraArgs){
|
||||
private void RestartProgram(params string[] extraArgs) {
|
||||
IsRestarting = true;
|
||||
Program.Restart(extraArgs);
|
||||
}
|
||||
|
@@ -3,21 +3,21 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class DialogSettingsRestart : Form{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class DialogSettingsRestart : Form {
|
||||
public CommandLineArgs Args { get; private set; }
|
||||
|
||||
public DialogSettingsRestart(CommandLineArgs currentArgs){
|
||||
public DialogSettingsRestart(CommandLineArgs currentArgs) {
|
||||
InitializeComponent();
|
||||
|
||||
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
|
||||
cbLogging.CheckedChanged += control_Change;
|
||||
|
||||
if (Program.IsPortable){
|
||||
if (Program.IsPortable) {
|
||||
tbDataFolder.Text = "Not available in portable version";
|
||||
tbDataFolder.Enabled = false;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
|
||||
tbDataFolder.TextChanged += control_Change;
|
||||
}
|
||||
@@ -27,14 +27,14 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
Text = Program.BrandName + " Arguments";
|
||||
}
|
||||
|
||||
private void control_Change(object sender, EventArgs e){
|
||||
private void control_Change(object sender, EventArgs e) {
|
||||
Args = new CommandLineArgs();
|
||||
|
||||
if (cbLogging.Checked){
|
||||
if (cbLogging.Checked) {
|
||||
Args.AddFlag(Arguments.ArgLogging);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
|
||||
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled) {
|
||||
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
|
||||
}
|
||||
|
||||
@@ -42,18 +42,18 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0);
|
||||
}
|
||||
|
||||
private void tbShortcutTarget_Click(object sender, EventArgs e){
|
||||
if (tbShortcutTarget.SelectionLength == 0){
|
||||
private void tbShortcutTarget_Click(object sender, EventArgs e) {
|
||||
if (tbShortcutTarget.SelectionLength == 0) {
|
||||
tbShortcutTarget.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnRestart_Click(object sender, EventArgs e){
|
||||
private void btnRestart_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e){
|
||||
private void btnCancel_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class DialogSettingsSearchEngine : Form{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class DialogSettingsSearchEngine : Form {
|
||||
public string Url => textBoxUrl.Text;
|
||||
|
||||
public DialogSettingsSearchEngine(){
|
||||
public DialogSettingsSearchEngine() {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName + " Options - Custom Search Engine";
|
||||
@@ -14,12 +14,12 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
private void btnApply_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e){
|
||||
private void btnCancel_Click(object sender, EventArgs e) {
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
|
54
Dialogs/Settings/TabSettingsAdvanced.Designer.cs
generated
54
Dialogs/Settings/TabSettingsAdvanced.Designer.cs
generated
@@ -41,7 +41,10 @@
|
||||
this.panelConfiguration = new System.Windows.Forms.Panel();
|
||||
this.labelConfiguration = new System.Windows.Forms.Label();
|
||||
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
|
||||
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();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit();
|
||||
this.panelAppButtons.SuspendLayout();
|
||||
@@ -242,7 +245,10 @@
|
||||
this.flowPanel.Controls.Add(this.panelClearCacheAuto);
|
||||
this.flowPanel.Controls.Add(this.labelConfiguration);
|
||||
this.flowPanel.Controls.Add(this.panelConfiguration);
|
||||
this.flowPanel.Controls.Add(this.labelProxy);
|
||||
this.flowPanel.Controls.Add(this.checkUseSystemProxyForAllConnections);
|
||||
this.flowPanel.Controls.Add(this.labelDevTools);
|
||||
this.flowPanel.Controls.Add(this.checkDevToolsInContextMenu);
|
||||
this.flowPanel.Controls.Add(this.checkDevToolsWindowOnTop);
|
||||
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||
this.flowPanel.Location = new System.Drawing.Point(9, 9);
|
||||
@@ -251,26 +257,61 @@
|
||||
this.flowPanel.TabIndex = 0;
|
||||
this.flowPanel.WrapContents = false;
|
||||
//
|
||||
// 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, 302);
|
||||
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, 328);
|
||||
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, 302);
|
||||
this.labelDevTools.Location = new System.Drawing.Point(0, 379);
|
||||
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 = 7;
|
||||
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, 405);
|
||||
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, 328);
|
||||
this.checkDevToolsWindowOnTop.Margin = new System.Windows.Forms.Padding(6, 6, 0, 2);
|
||||
this.checkDevToolsWindowOnTop.Location = new System.Drawing.Point(6, 429);
|
||||
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 = 8;
|
||||
this.checkDevToolsWindowOnTop.TabIndex = 11;
|
||||
this.checkDevToolsWindowOnTop.Text = "Dev Tools Window On Top";
|
||||
this.checkDevToolsWindowOnTop.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -312,6 +353,9 @@
|
||||
private System.Windows.Forms.CheckBox checkClearCacheAuto;
|
||||
private System.Windows.Forms.FlowLayoutPanel flowPanel;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -7,12 +7,12 @@ using TweetDuck.Management;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class TabSettingsAdvanced : FormSettings.BaseTab{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class TabSettingsAdvanced : FormSettings.BaseTab {
|
||||
private readonly Action<string> reinjectBrowserCSS;
|
||||
private readonly Action openDevTools;
|
||||
|
||||
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools){
|
||||
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools) {
|
||||
InitializeComponent();
|
||||
|
||||
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||
@@ -35,7 +35,7 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
|
||||
|
||||
BrowserCache.GetCacheSize(task => {
|
||||
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result / (1024.0 * 1024.0)) + " MB" : "unknown";
|
||||
string text = task.Status == TaskStatus.RanToCompletion ? (int) Math.Ceiling(task.Result / (1024.0 * 1024.0)) + " MB" : "unknown";
|
||||
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
|
||||
});
|
||||
|
||||
@@ -44,18 +44,22 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
|
||||
toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows.");
|
||||
|
||||
// proxy
|
||||
|
||||
toolTip.SetToolTip(checkUseSystemProxyForAllConnections, "Sets whether all connections should automatically detect and use the system proxy.\r\nBy default, only the browser component uses the system proxy, while other parts (such as update checks) ignore it.\r\nDisabled by default because Windows' proxy detection can be really slow.");
|
||||
|
||||
checkUseSystemProxyForAllConnections.Checked = Config.UseSystemProxyForAllConnections;
|
||||
|
||||
// development tools
|
||||
|
||||
toolTip.SetToolTip(checkDevToolsInContextMenu, "Sets whether all context menus include an option to open dev tools.");
|
||||
toolTip.SetToolTip(checkDevToolsWindowOnTop, "Sets whether dev tool windows appears on top of other windows.");
|
||||
|
||||
checkDevToolsInContextMenu.Checked = Config.DevToolsInContextMenu;
|
||||
checkDevToolsWindowOnTop.Checked = Config.DevToolsWindowOnTop;
|
||||
|
||||
if (!BrowserUtils.HasDevTools){
|
||||
checkDevToolsWindowOnTop.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
public override void OnReady() {
|
||||
btnOpenAppFolder.Click += btnOpenAppFolder_Click;
|
||||
btnOpenDataFolder.Click += btnOpenDataFolder_Click;
|
||||
btnRestart.Click += btnRestart_Click;
|
||||
@@ -67,63 +71,66 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
btnEditCefArgs.Click += btnEditCefArgs_Click;
|
||||
btnEditCSS.Click += btnEditCSS_Click;
|
||||
|
||||
checkUseSystemProxyForAllConnections.CheckedChanged += checkUseSystemProxyForAllConnections_CheckedChanged;
|
||||
|
||||
checkDevToolsWindowOnTop.CheckedChanged += checkDevToolsWindowOnTop_CheckedChanged;
|
||||
checkDevToolsInContextMenu.CheckedChanged += checkDevToolsInContextMenuOnCheckedChanged;
|
||||
}
|
||||
|
||||
public override void OnClosing(){
|
||||
public override void OnClosing() {
|
||||
SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked;
|
||||
SysConfig.ClearCacheThreshold = (int)numClearCacheThreshold.Value;
|
||||
SysConfig.ClearCacheThreshold = (int) numClearCacheThreshold.Value;
|
||||
}
|
||||
|
||||
#region Application
|
||||
|
||||
private void btnOpenAppFolder_Click(object sender, EventArgs e){
|
||||
private void btnOpenAppFolder_Click(object sender, EventArgs e) {
|
||||
App.SystemHandler.OpenFileExplorer(Program.ProgramPath);
|
||||
}
|
||||
|
||||
private void btnOpenDataFolder_Click(object sender, EventArgs e){
|
||||
private void btnOpenDataFolder_Click(object sender, EventArgs e) {
|
||||
App.SystemHandler.OpenFileExplorer(Program.StoragePath);
|
||||
}
|
||||
|
||||
private void btnRestart_Click(object sender, EventArgs e){
|
||||
private void btnRestart_Click(object sender, EventArgs e) {
|
||||
Program.Restart();
|
||||
}
|
||||
|
||||
private void btnRestartArgs_Click(object sender, EventArgs e){
|
||||
private void btnRestartArgs_Click(object sender, EventArgs e) {
|
||||
using DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean());
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
Program.RestartWithArgs(dialog.Args);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Browser Cache
|
||||
|
||||
private void btnClearCache_Click(object sender, EventArgs e){
|
||||
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){
|
||||
private void checkClearCacheAuto_CheckedChanged(object sender, EventArgs e) {
|
||||
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Configuration
|
||||
|
||||
private void btnEditCefArgs_Click(object sender, EventArgs e){
|
||||
private void btnEditCefArgs_Click(object sender, EventArgs e) {
|
||||
DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs);
|
||||
|
||||
form.VisibleChanged += (sender2, args2) => {
|
||||
form.MoveToCenter(ParentForm);
|
||||
};
|
||||
form.VisibleChanged += (sender2, args2) => { form.MoveToCenter(ParentForm); };
|
||||
|
||||
form.FormClosed += (sender2, args2) => {
|
||||
RestoreParentForm();
|
||||
|
||||
if (form.DialogResult == DialogResult.OK){
|
||||
if (form.DialogResult == DialogResult.OK) {
|
||||
Config.CustomCefArgs = form.CefArgs;
|
||||
}
|
||||
|
||||
@@ -134,17 +141,15 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
NativeMethods.SetFormDisabled(ParentForm, true);
|
||||
}
|
||||
|
||||
private void btnEditCSS_Click(object sender, EventArgs e){
|
||||
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.VisibleChanged += (sender2, args2) => { form.MoveToCenter(ParentForm); };
|
||||
|
||||
form.FormClosed += (sender2, args2) => {
|
||||
RestoreParentForm();
|
||||
|
||||
if (form.DialogResult == DialogResult.OK){
|
||||
if (form.DialogResult == DialogResult.OK) {
|
||||
Config.CustomBrowserCSS = form.BrowserCSS;
|
||||
Config.CustomNotificationCSS = form.NotificationCSS;
|
||||
}
|
||||
@@ -157,16 +162,29 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
NativeMethods.SetFormDisabled(ParentForm, true);
|
||||
}
|
||||
|
||||
private void RestoreParentForm(){
|
||||
if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event
|
||||
private void RestoreParentForm() {
|
||||
if (ParentForm != null) { // when the parent is closed first, ParentForm is null in FormClosed event
|
||||
NativeMethods.SetFormDisabled(ParentForm, false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Proxy
|
||||
|
||||
private void checkUseSystemProxyForAllConnections_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.UseSystemProxyForAllConnections = checkUseSystemProxyForAllConnections.Checked;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Development Tools
|
||||
|
||||
private void checkDevToolsWindowOnTop_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkDevToolsInContextMenuOnCheckedChanged(object sender, EventArgs e) {
|
||||
Config.DevToolsInContextMenu = checkDevToolsInContextMenu.Checked;
|
||||
}
|
||||
|
||||
private void checkDevToolsWindowOnTop_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.DevToolsWindowOnTop = checkDevToolsWindowOnTop.Checked;
|
||||
}
|
||||
|
||||
|
125
Dialogs/Settings/TabSettingsFeedback.Designer.cs
generated
125
Dialogs/Settings/TabSettingsFeedback.Designer.cs
generated
@@ -24,80 +24,13 @@
|
||||
/// </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.labelFeedback = new System.Windows.Forms.Label();
|
||||
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;
|
||||
@@ -111,16 +44,17 @@
|
||||
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
|
||||
this.btnSendFeedback.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelDataCollection
|
||||
// flowPanel
|
||||
//
|
||||
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";
|
||||
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.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;
|
||||
//
|
||||
// labelFeedback
|
||||
//
|
||||
@@ -133,24 +67,6 @@
|
||||
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);
|
||||
@@ -158,25 +74,16 @@
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.Button btnSendFeedback;
|
||||
private System.Windows.Forms.FlowLayoutPanel flowPanel;
|
||||
private System.Windows.Forms.Label labelFeedback;
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
@@ -1,58 +1,22 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Management.Analytics;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class TabSettingsFeedback : FormSettings.BaseTab{
|
||||
private readonly AnalyticsFile analyticsFile;
|
||||
private readonly AnalyticsReportGenerator.ExternalInfo analyticsInfo;
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
public TabSettingsFeedback(AnalyticsManager analytics, AnalyticsReportGenerator.ExternalInfo analyticsInfo, PluginManager plugins){
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class TabSettingsFeedback : FormSettings.BaseTab {
|
||||
public TabSettingsFeedback() {
|
||||
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(){
|
||||
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){
|
||||
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
|
||||
}
|
||||
}
|
||||
|
41
Dialogs/Settings/TabSettingsGeneral.Designer.cs
generated
41
Dialogs/Settings/TabSettingsGeneral.Designer.cs
generated
@@ -44,6 +44,7 @@
|
||||
this.labelBrowserSettings = new System.Windows.Forms.Label();
|
||||
this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
|
||||
this.checkTouchAdjustment = new System.Windows.Forms.CheckBox();
|
||||
this.checkAutomaticallyDetectColorProfile = new System.Windows.Forms.CheckBox();
|
||||
this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
|
||||
this.labelBrowserPath = new System.Windows.Forms.Label();
|
||||
this.comboBoxCustomBrowser = new System.Windows.Forms.ComboBox();
|
||||
@@ -90,11 +91,11 @@
|
||||
//
|
||||
this.checkUpdateNotifications.AutoSize = true;
|
||||
this.checkUpdateNotifications.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 403);
|
||||
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 409);
|
||||
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);
|
||||
this.checkUpdateNotifications.TabIndex = 14;
|
||||
this.checkUpdateNotifications.TabIndex = 15;
|
||||
this.checkUpdateNotifications.Text = "Check Updates Automatically";
|
||||
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -102,12 +103,12 @@
|
||||
//
|
||||
this.btnCheckUpdates.AutoSize = true;
|
||||
this.btnCheckUpdates.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 427);
|
||||
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 433);
|
||||
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);
|
||||
this.btnCheckUpdates.Size = new System.Drawing.Size(128, 25);
|
||||
this.btnCheckUpdates.TabIndex = 15;
|
||||
this.btnCheckUpdates.TabIndex = 16;
|
||||
this.btnCheckUpdates.Text = "Check Updates Now";
|
||||
this.btnCheckUpdates.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -166,11 +167,11 @@
|
||||
//
|
||||
this.labelZoom.AutoSize = true;
|
||||
this.labelZoom.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 299);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 323);
|
||||
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelZoom.Name = "labelZoom";
|
||||
this.labelZoom.Size = new System.Drawing.Size(39, 15);
|
||||
this.labelZoom.TabIndex = 11;
|
||||
this.labelZoom.TabIndex = 12;
|
||||
this.labelZoom.Text = "Zoom";
|
||||
//
|
||||
// zoomUpdateTimer
|
||||
@@ -193,11 +194,11 @@
|
||||
//
|
||||
this.panelZoom.Controls.Add(this.trackBarZoom);
|
||||
this.panelZoom.Controls.Add(this.labelZoomValue);
|
||||
this.panelZoom.Location = new System.Drawing.Point(0, 315);
|
||||
this.panelZoom.Location = new System.Drawing.Point(0, 339);
|
||||
this.panelZoom.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0);
|
||||
this.panelZoom.Name = "panelZoom";
|
||||
this.panelZoom.Size = new System.Drawing.Size(300, 35);
|
||||
this.panelZoom.TabIndex = 12;
|
||||
this.panelZoom.TabIndex = 13;
|
||||
//
|
||||
// checkAnimatedAvatars
|
||||
//
|
||||
@@ -215,11 +216,11 @@
|
||||
//
|
||||
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, 377);
|
||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
|
||||
this.labelUpdates.Location = new System.Drawing.Point(0, 383);
|
||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 7, 0, 1);
|
||||
this.labelUpdates.Name = "labelUpdates";
|
||||
this.labelUpdates.Size = new System.Drawing.Size(69, 19);
|
||||
this.labelUpdates.TabIndex = 13;
|
||||
this.labelUpdates.TabIndex = 14;
|
||||
this.labelUpdates.Text = "UPDATES";
|
||||
//
|
||||
// flowPanelLeft
|
||||
@@ -236,6 +237,7 @@
|
||||
this.flowPanelLeft.Controls.Add(this.labelBrowserSettings);
|
||||
this.flowPanelLeft.Controls.Add(this.checkSmoothScrolling);
|
||||
this.flowPanelLeft.Controls.Add(this.checkTouchAdjustment);
|
||||
this.flowPanelLeft.Controls.Add(this.checkAutomaticallyDetectColorProfile);
|
||||
this.flowPanelLeft.Controls.Add(this.checkHardwareAcceleration);
|
||||
this.flowPanelLeft.Controls.Add(this.labelZoom);
|
||||
this.flowPanelLeft.Controls.Add(this.panelZoom);
|
||||
@@ -308,15 +310,27 @@
|
||||
this.checkTouchAdjustment.Text = "Touch Screen Adjustment";
|
||||
this.checkTouchAdjustment.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, 266);
|
||||
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 = 10;
|
||||
this.checkAutomaticallyDetectColorProfile.Text = "Automatically Detect Color Profile";
|
||||
this.checkAutomaticallyDetectColorProfile.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkHardwareAcceleration
|
||||
//
|
||||
this.checkHardwareAcceleration.AutoSize = true;
|
||||
this.checkHardwareAcceleration.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 266);
|
||||
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 290);
|
||||
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 = 10;
|
||||
this.checkHardwareAcceleration.TabIndex = 11;
|
||||
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
|
||||
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -619,6 +633,7 @@
|
||||
private System.Windows.Forms.Label labelSearchEngine;
|
||||
private System.Windows.Forms.ComboBox comboBoxSearchEngine;
|
||||
private System.Windows.Forms.CheckBox checkTouchAdjustment;
|
||||
private System.Windows.Forms.CheckBox checkAutomaticallyDetectColorProfile;
|
||||
private System.Windows.Forms.FlowLayoutPanel flowPanelRight;
|
||||
private System.Windows.Forms.Panel panelSeparator;
|
||||
private System.Windows.Forms.Label labelLocales;
|
||||
|
@@ -8,8 +8,8 @@ using TweetDuck.Utils;
|
||||
using TweetLib.Core.Systems.Updates;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class TabSettingsGeneral : FormSettings.BaseTab{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class TabSettingsGeneral : FormSettings.BaseTab {
|
||||
private readonly Action reloadColumns;
|
||||
|
||||
private readonly UpdateHandler updates;
|
||||
@@ -24,7 +24,7 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
private readonly int searchEngineIndexDefault;
|
||||
private readonly int searchEngineIndexCustom;
|
||||
|
||||
public TabSettingsGeneral(Action reloadColumns, UpdateHandler updates){
|
||||
public TabSettingsGeneral(Action reloadColumns, UpdateHandler updates) {
|
||||
InitializeComponent();
|
||||
|
||||
this.reloadColumns = reloadColumns;
|
||||
@@ -66,6 +66,7 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
|
||||
toolTip.SetToolTip(checkSmoothScrolling, "Toggles smooth mouse wheel scrolling.");
|
||||
toolTip.SetToolTip(checkTouchAdjustment, "Toggles Chromium touch screen adjustment.\r\nDisabled by default, because it is very imprecise with TweetDeck.");
|
||||
toolTip.SetToolTip(checkAutomaticallyDetectColorProfile, "Automatically detects the color profile of your system.\r\nUses the sRGB profile if disabled.");
|
||||
toolTip.SetToolTip(checkHardwareAcceleration, "Uses graphics card to improve performance.\r\nDisable if you experience visual glitches, or to save a small amount of RAM.");
|
||||
toolTip.SetToolTip(comboBoxCustomBrowser, "Sets the default browser for opening links.");
|
||||
toolTip.SetToolTip(comboBoxCustomVideoPlayer, "Sets the default application for playing videos.");
|
||||
@@ -73,9 +74,10 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
|
||||
checkSmoothScrolling.Checked = Config.EnableSmoothScrolling;
|
||||
checkTouchAdjustment.Checked = Config.EnableTouchAdjustment;
|
||||
checkAutomaticallyDetectColorProfile.Checked = Config.EnableColorProfileDetection;
|
||||
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
|
||||
|
||||
foreach(WindowsUtils.Browser browserInfo in WindowsUtils.FindInstalledBrowsers()){
|
||||
foreach (WindowsUtils.Browser browserInfo in WindowsUtils.FindInstalledBrowsers()) {
|
||||
comboBoxCustomBrowser.Items.Add(browserInfo);
|
||||
}
|
||||
|
||||
@@ -104,17 +106,17 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
|
||||
checkSpellCheck.Checked = Config.EnableSpellCheck;
|
||||
|
||||
try{
|
||||
foreach(LocaleUtils.Item item in LocaleUtils.SpellCheckLanguages){
|
||||
try {
|
||||
foreach (LocaleUtils.Item item in LocaleUtils.SpellCheckLanguages) {
|
||||
comboBoxSpellCheckLanguage.Items.Add(item);
|
||||
}
|
||||
}catch{
|
||||
} catch {
|
||||
comboBoxSpellCheckLanguage.Items.Add(new LocaleUtils.Item("en-US"));
|
||||
}
|
||||
|
||||
comboBoxSpellCheckLanguage.SelectedItem = new LocaleUtils.Item(Config.SpellCheckLanguage);
|
||||
|
||||
foreach(LocaleUtils.Item item in LocaleUtils.TweetDeckTranslationLocales){
|
||||
foreach (LocaleUtils.Item item in LocaleUtils.TweetDeckTranslationLocales) {
|
||||
comboBoxTranslationTarget.Items.Add(item);
|
||||
}
|
||||
|
||||
@@ -132,7 +134,7 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
comboBoxFirstDayOfWeek.SelectedItem = daysOfWeek.OfType<DayOfWeekItem>().FirstOrDefault(dow => dow.Id == Config.CalendarFirstDay) ?? daysOfWeek[0];
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
public override void OnReady() {
|
||||
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
|
||||
checkFocusDmInput.CheckedChanged += checkFocusDmInput_CheckedChanged;
|
||||
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
|
||||
@@ -146,6 +148,7 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
|
||||
checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged;
|
||||
checkTouchAdjustment.CheckedChanged += checkTouchAdjustment_CheckedChanged;
|
||||
checkAutomaticallyDetectColorProfile.CheckedChanged += checkAutomaticallyDetectColorProfile_CheckedChanged;
|
||||
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
|
||||
comboBoxCustomBrowser.SelectedIndexChanged += comboBoxCustomBrowser_SelectedIndexChanged;
|
||||
btnCustomBrowserChange.Click += btnCustomBrowserChange_Click;
|
||||
@@ -159,70 +162,71 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
comboBoxFirstDayOfWeek.SelectedValueChanged += comboBoxFirstDayOfWeek_SelectedValueChanged;
|
||||
}
|
||||
|
||||
public override void OnClosing(){
|
||||
public override void OnClosing() {
|
||||
Config.ZoomLevel = trackBarZoom.Value;
|
||||
}
|
||||
|
||||
#region User Interface
|
||||
|
||||
private void checkExpandLinks_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkExpandLinks_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
|
||||
}
|
||||
|
||||
private void checkFocusDmInput_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkFocusDmInput_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.FocusDmInput = checkFocusDmInput.Checked;
|
||||
}
|
||||
|
||||
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
|
||||
}
|
||||
|
||||
private void checkKeepLikeFollowDialogsOpen_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkKeepLikeFollowDialogsOpen_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.KeepLikeFollowDialogsOpen = checkKeepLikeFollowDialogsOpen.Checked;
|
||||
}
|
||||
|
||||
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.BestImageQuality = checkBestImageQuality.Checked;
|
||||
}
|
||||
|
||||
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
|
||||
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
|
||||
}
|
||||
|
||||
private void trackBarZoom_ValueChanged(object sender, EventArgs e){
|
||||
if (trackBarZoom.AlignValueToTick()){
|
||||
private void trackBarZoom_ValueChanged(object sender, EventArgs e) {
|
||||
if (trackBarZoom.AlignValueToTick()) {
|
||||
zoomUpdateTimer.Stop();
|
||||
zoomUpdateTimer.Start();
|
||||
labelZoomValue.Text = trackBarZoom.Value + "%";
|
||||
}
|
||||
}
|
||||
|
||||
private void zoomUpdateTimer_Tick(object sender, EventArgs e){
|
||||
private void zoomUpdateTimer_Tick(object sender, EventArgs e) {
|
||||
Config.ZoomLevel = trackBarZoom.Value;
|
||||
zoomUpdateTimer.Stop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Updates
|
||||
|
||||
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
|
||||
}
|
||||
|
||||
private void btnCheckUpdates_Click(object sender, EventArgs e){
|
||||
private void btnCheckUpdates_Click(object sender, EventArgs e) {
|
||||
Config.DismissedUpdate = null;
|
||||
|
||||
btnCheckUpdates.Enabled = false;
|
||||
updateCheckEventId = updates.Check(true);
|
||||
}
|
||||
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||
if (e.EventId == updateCheckEventId){
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e) {
|
||||
if (e.EventId == updateCheckEventId) {
|
||||
btnCheckUpdates.Enabled = true;
|
||||
|
||||
e.Result.Handle(update => {
|
||||
if (update.VersionTag == Program.VersionTag){
|
||||
if (update.VersionTag == Program.VersionTag) {
|
||||
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
|
||||
}
|
||||
}, ex => {
|
||||
@@ -232,35 +236,40 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Browser Settings
|
||||
|
||||
private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableSmoothScrolling = checkSmoothScrolling.Checked;
|
||||
}
|
||||
|
||||
private void checkTouchAdjustment_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkTouchAdjustment_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableTouchAdjustment = checkTouchAdjustment.Checked;
|
||||
}
|
||||
|
||||
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkAutomaticallyDetectColorProfile_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableColorProfileDetection = checkAutomaticallyDetectColorProfile.Checked;
|
||||
}
|
||||
|
||||
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e) {
|
||||
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
|
||||
}
|
||||
|
||||
private void UpdateBrowserChangeButton(){
|
||||
private void UpdateBrowserChangeButton() {
|
||||
btnCustomBrowserChange.Visible = comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom;
|
||||
}
|
||||
|
||||
private void UpdateBrowserPathSelection(){
|
||||
if (string.IsNullOrEmpty(Config.BrowserPath) || !File.Exists(Config.BrowserPath)){
|
||||
private void UpdateBrowserPathSelection() {
|
||||
if (string.IsNullOrEmpty(Config.BrowserPath) || !File.Exists(Config.BrowserPath)) {
|
||||
comboBoxCustomBrowser.SelectedIndex = browserListIndexDefault;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
WindowsUtils.Browser browserInfo = comboBoxCustomBrowser.Items.OfType<WindowsUtils.Browser>().FirstOrDefault(browser => browser.Path == Config.BrowserPath);
|
||||
|
||||
if (browserInfo == null || Config.BrowserPathArgs != null){
|
||||
if (browserInfo == null || Config.BrowserPathArgs != null) {
|
||||
comboBoxCustomBrowser.SelectedIndex = browserListIndexCustom;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
comboBoxCustomBrowser.SelectedItem = browserInfo;
|
||||
}
|
||||
}
|
||||
@@ -268,23 +277,23 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
UpdateBrowserChangeButton();
|
||||
}
|
||||
|
||||
private void comboBoxCustomBrowser_SelectedIndexChanged(object sender, EventArgs e){
|
||||
if (comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom){
|
||||
private void comboBoxCustomBrowser_SelectedIndexChanged(object sender, EventArgs e) {
|
||||
if (comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom) {
|
||||
btnCustomBrowserChange_Click(sender, e);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
Config.BrowserPath = (comboBoxCustomBrowser.SelectedItem as WindowsUtils.Browser)?.Path; // default browser item is a string and casts to null
|
||||
Config.BrowserPathArgs = null;
|
||||
UpdateBrowserChangeButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnCustomBrowserChange_Click(object sender, EventArgs e){
|
||||
using(DialogSettingsExternalProgram dialog = new DialogSettingsExternalProgram("External Browser", "Open Links With..."){
|
||||
private void btnCustomBrowserChange_Click(object sender, EventArgs e) {
|
||||
using (DialogSettingsExternalProgram dialog = new DialogSettingsExternalProgram("External Browser", "Open Links With...") {
|
||||
Path = Config.BrowserPath,
|
||||
Args = Config.BrowserPathArgs
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
}) {
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
Config.BrowserPath = dialog.Path;
|
||||
Config.BrowserPathArgs = dialog.Args;
|
||||
}
|
||||
@@ -295,38 +304,38 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
comboBoxCustomBrowser.SelectedIndexChanged += comboBoxCustomBrowser_SelectedIndexChanged;
|
||||
}
|
||||
|
||||
private void UpdateVideoPlayerChangeButton(){
|
||||
private void UpdateVideoPlayerChangeButton() {
|
||||
btnCustomVideoPlayerChange.Visible = comboBoxCustomVideoPlayer.SelectedIndex == videoPlayerListIndexCustom;
|
||||
}
|
||||
|
||||
private void UpdateVideoPlayerPathSelection(){
|
||||
if (string.IsNullOrEmpty(Config.VideoPlayerPath) || !File.Exists(Config.VideoPlayerPath)){
|
||||
private void UpdateVideoPlayerPathSelection() {
|
||||
if (string.IsNullOrEmpty(Config.VideoPlayerPath) || !File.Exists(Config.VideoPlayerPath)) {
|
||||
comboBoxCustomVideoPlayer.SelectedIndex = videoPlayerListIndexDefault;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
comboBoxCustomVideoPlayer.SelectedIndex = videoPlayerListIndexCustom;
|
||||
}
|
||||
|
||||
UpdateVideoPlayerChangeButton();
|
||||
}
|
||||
|
||||
private void comboBoxCustomVideoPlayer_SelectedIndexChanged(object sender, EventArgs e){
|
||||
if (comboBoxCustomVideoPlayer.SelectedIndex == videoPlayerListIndexCustom){
|
||||
private void comboBoxCustomVideoPlayer_SelectedIndexChanged(object sender, EventArgs e) {
|
||||
if (comboBoxCustomVideoPlayer.SelectedIndex == videoPlayerListIndexCustom) {
|
||||
btnCustomVideoPlayerChange_Click(sender, e);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
Config.VideoPlayerPath = null;
|
||||
Config.VideoPlayerPathArgs = null;
|
||||
UpdateVideoPlayerChangeButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnCustomVideoPlayerChange_Click(object sender, EventArgs e){
|
||||
using(DialogSettingsExternalProgram dialog = new DialogSettingsExternalProgram("External Video Player", "Play Videos With..."){
|
||||
private void btnCustomVideoPlayerChange_Click(object sender, EventArgs e) {
|
||||
using (DialogSettingsExternalProgram dialog = new DialogSettingsExternalProgram("External Video Player", "Play Videos With...") {
|
||||
Path = Config.VideoPlayerPath,
|
||||
Args = Config.VideoPlayerPathArgs
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
}) {
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
Config.VideoPlayerPath = dialog.Path;
|
||||
Config.VideoPlayerPathArgs = dialog.Args;
|
||||
}
|
||||
@@ -337,10 +346,10 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
comboBoxCustomVideoPlayer.SelectedIndexChanged += comboBoxCustomVideoPlayer_SelectedIndexChanged;
|
||||
}
|
||||
|
||||
private void comboBoxSearchEngine_SelectedIndexChanged(object sender, EventArgs e){
|
||||
if (comboBoxSearchEngine.SelectedIndex == searchEngineIndexCustom){
|
||||
using(DialogSettingsSearchEngine dialog = new DialogSettingsSearchEngine()){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
private void comboBoxSearchEngine_SelectedIndexChanged(object sender, EventArgs e) {
|
||||
if (comboBoxSearchEngine.SelectedIndex == searchEngineIndexCustom) {
|
||||
using (DialogSettingsSearchEngine dialog = new DialogSettingsSearchEngine()) {
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
Config.SearchEngineUrl = dialog.Url.Trim();
|
||||
}
|
||||
}
|
||||
@@ -349,32 +358,32 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
UpdateSearchEngineSelection();
|
||||
comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
Config.SearchEngineUrl = (comboBoxSearchEngine.SelectedItem as SearchEngine)?.Url; // default search engine item is a string and casts to null
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSearchEngineSelection(){
|
||||
if (string.IsNullOrEmpty(Config.SearchEngineUrl)){
|
||||
private void UpdateSearchEngineSelection() {
|
||||
if (string.IsNullOrEmpty(Config.SearchEngineUrl)) {
|
||||
comboBoxSearchEngine.SelectedIndex = searchEngineIndexDefault;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
SearchEngine engineInfo = comboBoxSearchEngine.Items.OfType<SearchEngine>().FirstOrDefault(engine => engine.Url == Config.SearchEngineUrl);
|
||||
|
||||
if (engineInfo == null){
|
||||
if (engineInfo == null) {
|
||||
comboBoxSearchEngine.SelectedIndex = searchEngineIndexCustom;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
comboBoxSearchEngine.SelectedItem = engineInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SearchEngine{
|
||||
private sealed class SearchEngine {
|
||||
private string Name { get; }
|
||||
public string Url { get; }
|
||||
|
||||
public SearchEngine(string name, string url){
|
||||
public SearchEngine(string name, string url) {
|
||||
Name = name;
|
||||
Url = url;
|
||||
}
|
||||
@@ -385,30 +394,31 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Locales
|
||||
|
||||
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableSpellCheck = checkSpellCheck.Checked;
|
||||
BrowserProcessHandler.UpdatePrefs();
|
||||
}
|
||||
|
||||
private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e){
|
||||
private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e) {
|
||||
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code ?? "en-US";
|
||||
}
|
||||
|
||||
private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e){
|
||||
private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e) {
|
||||
Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code ?? "en";
|
||||
}
|
||||
|
||||
private void comboBoxFirstDayOfWeek_SelectedValueChanged(object sender, EventArgs e){
|
||||
private void comboBoxFirstDayOfWeek_SelectedValueChanged(object sender, EventArgs e) {
|
||||
Config.CalendarFirstDay = (comboBoxFirstDayOfWeek.SelectedItem as DayOfWeekItem)?.Id ?? -1;
|
||||
}
|
||||
|
||||
private sealed class DayOfWeekItem{
|
||||
private sealed class DayOfWeekItem {
|
||||
private string Name { get; }
|
||||
public int Id { get; }
|
||||
|
||||
public DayOfWeekItem(string name, DayOfWeek dow){
|
||||
public DayOfWeekItem(string name, DayOfWeek dow) {
|
||||
Name = name;
|
||||
Id = LocaleUtils.GetJQueryDayOfWeek(dow);
|
||||
}
|
||||
|
@@ -4,13 +4,13 @@ using TweetDuck.Browser.Notification.Example;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class TabSettingsNotifications : FormSettings.BaseTab{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class TabSettingsNotifications : FormSettings.BaseTab {
|
||||
private static readonly int[] IdlePauseSeconds = { 0, 30, 60, 120, 300 };
|
||||
|
||||
private readonly FormNotificationExample notification;
|
||||
|
||||
public TabSettingsNotifications(FormNotificationExample notification){
|
||||
public TabSettingsNotifications(FormNotificationExample notification) {
|
||||
InitializeComponent();
|
||||
|
||||
this.notification = notification;
|
||||
@@ -67,18 +67,28 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
|
||||
toolTip.SetToolTip(radioLocCustom, "Drag the example notification window to the desired location.");
|
||||
|
||||
switch(Config.NotificationPosition){
|
||||
case DesktopNotification.Position.TopLeft: radioLocTL.Checked = true; break;
|
||||
case DesktopNotification.Position.TopRight: radioLocTR.Checked = true; break;
|
||||
case DesktopNotification.Position.BottomLeft: radioLocBL.Checked = true; break;
|
||||
case DesktopNotification.Position.BottomRight: radioLocBR.Checked = true; break;
|
||||
case DesktopNotification.Position.Custom: radioLocCustom.Checked = true; break;
|
||||
switch (Config.NotificationPosition) {
|
||||
case DesktopNotification.Position.TopLeft:
|
||||
radioLocTL.Checked = true;
|
||||
break;
|
||||
case DesktopNotification.Position.TopRight:
|
||||
radioLocTR.Checked = true;
|
||||
break;
|
||||
case DesktopNotification.Position.BottomLeft:
|
||||
radioLocBL.Checked = true;
|
||||
break;
|
||||
case DesktopNotification.Position.BottomRight:
|
||||
radioLocBR.Checked = true;
|
||||
break;
|
||||
case DesktopNotification.Position.Custom:
|
||||
radioLocCustom.Checked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
|
||||
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
|
||||
|
||||
foreach(Screen screen in Screen.AllScreens){
|
||||
foreach (Screen screen in Screen.AllScreens) {
|
||||
comboBoxDisplay.Items.Add($"{screen.DeviceName.TrimStart('\\', '.')} ({screen.Bounds.Width}x{screen.Bounds.Height})");
|
||||
}
|
||||
|
||||
@@ -92,16 +102,20 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
toolTip.SetToolTip(radioSizeAuto, "Notification size is based on the font size and browser zoom level.");
|
||||
toolTip.SetToolTip(radioSizeCustom, "Resize the example notification window to the desired size.");
|
||||
|
||||
switch(Config.NotificationSize){
|
||||
case DesktopNotification.Size.Auto: radioSizeAuto.Checked = true; break;
|
||||
case DesktopNotification.Size.Custom: radioSizeCustom.Checked = true; break;
|
||||
switch (Config.NotificationSize) {
|
||||
case DesktopNotification.Size.Auto:
|
||||
radioSizeAuto.Checked = true;
|
||||
break;
|
||||
case DesktopNotification.Size.Custom:
|
||||
radioSizeCustom.Checked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
|
||||
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
public override void OnReady() {
|
||||
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged;
|
||||
checkMediaPreviews.CheckedChanged += checkMediaPreviews_CheckedChanged;
|
||||
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
|
||||
@@ -129,23 +143,23 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
trackBarScrollSpeed.ValueChanged += trackBarScrollSpeed_ValueChanged;
|
||||
}
|
||||
|
||||
private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){
|
||||
if (Parent == null){
|
||||
private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e) {
|
||||
if (Parent == null) {
|
||||
notification.HideNotification();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
notification.ShowExampleNotification(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void notification_Move(object sender, EventArgs e){
|
||||
if (radioLocCustom.Checked && notification.Location != ControlExtensions.InvisibleLocation){
|
||||
private void notification_Move(object sender, EventArgs e) {
|
||||
if (radioLocCustom.Checked && notification.Location != ControlExtensions.InvisibleLocation) {
|
||||
Config.CustomNotificationPosition = notification.Location;
|
||||
}
|
||||
}
|
||||
|
||||
private void notification_ResizeEnd(object sender, EventArgs e){
|
||||
if (radioSizeCustom.Checked){
|
||||
private void notification_ResizeEnd(object sender, EventArgs e) {
|
||||
if (radioSizeCustom.Checked) {
|
||||
Config.CustomNotificationSize = notification.BrowserSize;
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
@@ -153,49 +167,50 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
|
||||
#region General
|
||||
|
||||
private void checkColumnName_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkColumnName_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.DisplayNotificationColumn = checkColumnName.Checked;
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.NotificationMediaPreviews = checkMediaPreviews.Checked;
|
||||
}
|
||||
|
||||
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked;
|
||||
}
|
||||
|
||||
private void checkNonIntrusive_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkNonIntrusive_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.NotificationNonIntrusiveMode = checkNonIntrusive.Checked;
|
||||
}
|
||||
|
||||
private void comboBoxIdlePause_SelectedValueChanged(object sender, EventArgs e){
|
||||
private void comboBoxIdlePause_SelectedValueChanged(object sender, EventArgs e) {
|
||||
Config.NotificationIdlePauseSeconds = IdlePauseSeconds[comboBoxIdlePause.SelectedIndex];
|
||||
}
|
||||
|
||||
private void trackBarOpacity_ValueChanged(object sender, EventArgs e){
|
||||
if (trackBarOpacity.AlignValueToTick()){
|
||||
private void trackBarOpacity_ValueChanged(object sender, EventArgs e) {
|
||||
if (trackBarOpacity.AlignValueToTick()) {
|
||||
Config.NotificationWindowOpacity = trackBarOpacity.Value;
|
||||
labelOpacityValue.Text = Config.NotificationWindowOpacity + "%";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Timer
|
||||
|
||||
private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.DisplayNotificationTimer = checkNotificationTimer.Checked;
|
||||
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
|
||||
notification.ShowExampleNotification(true);
|
||||
}
|
||||
|
||||
private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.NotificationTimerCountDown = checkTimerCountDown.Checked;
|
||||
notification.ShowExampleNotification(true);
|
||||
}
|
||||
|
||||
private void trackBarDuration_ValueChanged(object sender, EventArgs e){
|
||||
private void trackBarDuration_ValueChanged(object sender, EventArgs e) {
|
||||
durationUpdateTimer.Stop();
|
||||
durationUpdateTimer.Start();
|
||||
|
||||
@@ -203,37 +218,38 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
labelDurationValue.Text = Config.NotificationDurationValue + " ms/c";
|
||||
}
|
||||
|
||||
private void btnDurationShort_Click(object sender, EventArgs e){
|
||||
private void btnDurationShort_Click(object sender, EventArgs e) {
|
||||
trackBarDuration.Value = 15;
|
||||
}
|
||||
|
||||
private void btnDurationMedium_Click(object sender, EventArgs e){
|
||||
private void btnDurationMedium_Click(object sender, EventArgs e) {
|
||||
trackBarDuration.Value = 25;
|
||||
}
|
||||
|
||||
private void btnDurationLong_Click(object sender, EventArgs e){
|
||||
private void btnDurationLong_Click(object sender, EventArgs e) {
|
||||
trackBarDuration.Value = 35;
|
||||
}
|
||||
|
||||
private void durationUpdateTimer_Tick(object sender, EventArgs e){
|
||||
private void durationUpdateTimer_Tick(object sender, EventArgs e) {
|
||||
notification.ShowExampleNotification(true);
|
||||
durationUpdateTimer.Stop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Location
|
||||
|
||||
private void radioLoc_CheckedChanged(object sender, EventArgs e){
|
||||
if (radioLocTL.Checked){
|
||||
private void radioLoc_CheckedChanged(object sender, EventArgs e) {
|
||||
if (radioLocTL.Checked) {
|
||||
Config.NotificationPosition = DesktopNotification.Position.TopLeft;
|
||||
}
|
||||
else if (radioLocTR.Checked){
|
||||
else if (radioLocTR.Checked) {
|
||||
Config.NotificationPosition = DesktopNotification.Position.TopRight;
|
||||
}
|
||||
else if (radioLocBL.Checked){
|
||||
else if (radioLocBL.Checked) {
|
||||
Config.NotificationPosition = DesktopNotification.Position.BottomLeft;
|
||||
}
|
||||
else if (radioLocBR.Checked){
|
||||
else if (radioLocBR.Checked) {
|
||||
Config.NotificationPosition = DesktopNotification.Position.BottomRight;
|
||||
}
|
||||
|
||||
@@ -241,8 +257,8 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void radioLocCustom_Click(object sender, EventArgs e){
|
||||
if (!Config.IsCustomNotificationPositionSet){
|
||||
private void radioLocCustom_Click(object sender, EventArgs e) {
|
||||
if (!Config.IsCustomNotificationPositionSet) {
|
||||
Config.CustomNotificationPosition = notification.Location;
|
||||
}
|
||||
|
||||
@@ -251,7 +267,7 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
|
||||
notification.ShowExampleNotification(false);
|
||||
|
||||
if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is Outside View", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){
|
||||
if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is Outside View", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)) {
|
||||
Config.NotificationPosition = DesktopNotification.Position.TopRight;
|
||||
notification.MoveToVisibleLocation();
|
||||
|
||||
@@ -262,30 +278,31 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){
|
||||
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e) {
|
||||
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
|
||||
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e) {
|
||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
|
||||
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Size
|
||||
|
||||
private void radioSize_CheckedChanged(object sender, EventArgs e){
|
||||
if (radioSizeAuto.Checked){
|
||||
private void radioSize_CheckedChanged(object sender, EventArgs e) {
|
||||
if (radioSizeAuto.Checked) {
|
||||
Config.NotificationSize = DesktopNotification.Size.Auto;
|
||||
}
|
||||
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void radioSizeCustom_Click(object sender, EventArgs e){
|
||||
if (!Config.IsCustomNotificationSizeSet){
|
||||
private void radioSizeCustom_Click(object sender, EventArgs e) {
|
||||
if (!Config.IsCustomNotificationSizeSet) {
|
||||
Config.CustomNotificationSize = notification.BrowserSize;
|
||||
}
|
||||
|
||||
@@ -293,8 +310,8 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){
|
||||
if (trackBarScrollSpeed.AlignValueToTick()){
|
||||
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e) {
|
||||
if (trackBarScrollSpeed.AlignValueToTick()) {
|
||||
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
|
||||
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
|
||||
}
|
||||
|
@@ -6,11 +6,11 @@ using TweetDuck.Browser.Notification;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class TabSettingsSounds : FormSettings.BaseTab{
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class TabSettingsSounds : FormSettings.BaseTab {
|
||||
private readonly Action playSoundNotification;
|
||||
|
||||
public TabSettingsSounds(Action playSoundNotification){
|
||||
public TabSettingsSounds(Action playSoundNotification) {
|
||||
InitializeComponent();
|
||||
|
||||
this.playSoundNotification = playSoundNotification;
|
||||
@@ -27,21 +27,21 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
NativeMethods.SendMessage(tbCustomSound.Handle, NativeMethods.EM_SETCUEBANNER, 0, "(default TweetDeck sound)");
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
public override void OnReady() {
|
||||
tbCustomSound.TextChanged += tbCustomSound_TextChanged;
|
||||
btnPlaySound.Click += btnPlaySound_Click;
|
||||
btnBrowseSound.Click += btnBrowseSound_Click;
|
||||
btnResetSound.Click += btnResetSound_Click;
|
||||
}
|
||||
|
||||
public override void OnClosing(){
|
||||
public override void OnClosing() {
|
||||
Config.NotificationSoundPath = tbCustomSound.Text;
|
||||
Config.NotificationSoundVolume = trackBarVolume.Value;
|
||||
}
|
||||
|
||||
#region Sound Notification
|
||||
|
||||
private bool RefreshCanPlay(){
|
||||
private bool RefreshCanPlay() {
|
||||
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
|
||||
bool canPlay = isEmpty || File.Exists(tbCustomSound.Text);
|
||||
|
||||
@@ -51,32 +51,32 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
return canPlay;
|
||||
}
|
||||
|
||||
private void tbCustomSound_TextChanged(object sender, EventArgs e){
|
||||
private void tbCustomSound_TextChanged(object sender, EventArgs e) {
|
||||
RefreshCanPlay();
|
||||
}
|
||||
|
||||
private void btnPlaySound_Click(object sender, EventArgs e){
|
||||
if (RefreshCanPlay()){
|
||||
private void btnPlaySound_Click(object sender, EventArgs e) {
|
||||
if (RefreshCanPlay()) {
|
||||
Config.NotificationSoundPath = tbCustomSound.Text;
|
||||
Config.NotificationSoundVolume = trackBarVolume.Value;
|
||||
playSoundNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnBrowseSound_Click(object sender, EventArgs e){
|
||||
using OpenFileDialog dialog = new OpenFileDialog{
|
||||
private void btnBrowseSound_Click(object sender, EventArgs e) {
|
||||
using OpenFileDialog dialog = new OpenFileDialog {
|
||||
AutoUpgradeEnabled = true,
|
||||
DereferenceLinks = true,
|
||||
Title = "Custom Notification Sound",
|
||||
Filter = $"Sound file ({SoundNotification.SupportedFormats})|{SoundNotification.SupportedFormats}|All files (*.*)|*.*"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
try{
|
||||
if (new FileInfo(dialog.FileName).Length > (1024 * 1024) && !FormMessage.Warning("Sound Notification", "The sound file is larger than 1 MB, this will cause increased memory usage. Use this file anyway?", FormMessage.Yes, FormMessage.No)){
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
try {
|
||||
if (new FileInfo(dialog.FileName).Length > (1024 * 1024) && !FormMessage.Warning("Sound Notification", "The sound file is larger than 1 MB, this will cause increased memory usage. Use this file anyway?", FormMessage.Yes, FormMessage.No)) {
|
||||
return;
|
||||
}
|
||||
}catch{
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@@ -84,17 +84,17 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
private void btnResetSound_Click(object sender, EventArgs e){
|
||||
private void btnResetSound_Click(object sender, EventArgs e) {
|
||||
tbCustomSound.Text = string.Empty;
|
||||
}
|
||||
|
||||
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
|
||||
private void trackBarVolume_ValueChanged(object sender, EventArgs e) {
|
||||
volumeUpdateTimer.Stop();
|
||||
volumeUpdateTimer.Start();
|
||||
labelVolumeValue.Text = trackBarVolume.Value + "%";
|
||||
}
|
||||
|
||||
private void volumeUpdateTimer_Tick(object sender, EventArgs e){
|
||||
private void volumeUpdateTimer_Tick(object sender, EventArgs e) {
|
||||
Config.NotificationSoundVolume = trackBarVolume.Value;
|
||||
volumeUpdateTimer.Stop();
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using TweetDuck.Browser;
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings{
|
||||
sealed partial class TabSettingsTray : FormSettings.BaseTab{
|
||||
public TabSettingsTray(){
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class TabSettingsTray : FormSettings.BaseTab {
|
||||
public TabSettingsTray() {
|
||||
InitializeComponent();
|
||||
|
||||
// system tray
|
||||
@@ -16,25 +16,25 @@ namespace TweetDuck.Dialogs.Settings{
|
||||
comboBoxTrayType.Items.Add("Minimize to Tray");
|
||||
comboBoxTrayType.Items.Add("Close to Tray");
|
||||
comboBoxTrayType.Items.Add("Combined");
|
||||
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count - 1);
|
||||
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int) Config.TrayBehavior, 0), comboBoxTrayType.Items.Count - 1);
|
||||
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
public override void OnReady() {
|
||||
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
|
||||
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
|
||||
}
|
||||
|
||||
#region System Tray
|
||||
|
||||
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
|
||||
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
|
||||
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e) {
|
||||
Config.TrayBehavior = (TrayIcon.Behavior) comboBoxTrayType.SelectedIndex;
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
}
|
||||
|
||||
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
|
||||
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
|
||||
}
|
||||
|
||||
|
@@ -1,136 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using TweetLib.Core.Serialization;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
|
||||
namespace TweetDuck.Management.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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,160 +0,0 @@
|
||||
// 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.Browser;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Management.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", Lib.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
|
||||
|
||||
WebUtils.NewClient(BrowserUtils.UserAgentVanilla).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:
|
||||
message = "HTTP Error " + (e.Response is HttpWebResponse response ? $"{(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));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDuck.Management.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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,322 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
using TweetDuck.Browser;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Management.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" , Lib.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(plugins.Config.IsEnabled).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 => UserConfig.TrayBehavior switch{
|
||||
TrayIcon.Behavior.DisplayOnly => "icon",
|
||||
TrayIcon.Behavior.MinimizeToTray => "minimize",
|
||||
TrayIcon.Behavior.CloseToTray => "close",
|
||||
TrayIcon.Behavior.Combined => "combined",
|
||||
_ => "off"
|
||||
};
|
||||
}
|
||||
|
||||
private static string NotificationPosition{
|
||||
get => UserConfig.NotificationPosition switch{
|
||||
DesktopNotification.Position.TopLeft => "top left",
|
||||
DesktopNotification.Position.TopRight => "top right",
|
||||
DesktopNotification.Position.BottomLeft => "bottom left",
|
||||
DesktopNotification.Position.BottomRight => "bottom right",
|
||||
_ => "custom"
|
||||
};
|
||||
}
|
||||
|
||||
private static string NotificationSize{
|
||||
get => UserConfig.NotificationSize switch{
|
||||
DesktopNotification.Size.Auto => "auto",
|
||||
_ => 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(){}
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,44 +4,44 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TweetDuck.Management{
|
||||
static class BrowserCache{
|
||||
namespace TweetDuck.Management {
|
||||
static class BrowserCache {
|
||||
public static string CacheFolder => Path.Combine(Program.StoragePath, "Cache");
|
||||
|
||||
private static bool ClearOnExit;
|
||||
private static Timer AutoClearTimer;
|
||||
private static bool clearOnExit;
|
||||
private static Timer autoClearTimer;
|
||||
|
||||
private static long CalculateCacheSize(){
|
||||
private static long CalculateCacheSize() {
|
||||
return new DirectoryInfo(CacheFolder).EnumerateFiles().Select(file => {
|
||||
try{
|
||||
try {
|
||||
return file.Length;
|
||||
}catch{
|
||||
} catch {
|
||||
return 0L;
|
||||
}
|
||||
}).Sum();
|
||||
}
|
||||
|
||||
public static void GetCacheSize(Action<Task<long>> callbackBytes){
|
||||
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;
|
||||
public static void RefreshTimer() {
|
||||
bool shouldRun = Program.Config.System.ClearCacheAutomatically && !clearOnExit;
|
||||
|
||||
if (!shouldRun && AutoClearTimer != null){
|
||||
AutoClearTimer.Dispose();
|
||||
AutoClearTimer = null;
|
||||
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){
|
||||
else if (shouldRun && autoClearTimer == null) {
|
||||
autoClearTimer = new Timer(state => {
|
||||
if (autoClearTimer != null) {
|
||||
try {
|
||||
if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold * 1024L * 1024L) {
|
||||
SetClearOnExit();
|
||||
}
|
||||
}catch(Exception){
|
||||
} catch (Exception) {
|
||||
// TODO should probably log errors and report them at some point
|
||||
}
|
||||
}
|
||||
@@ -49,26 +49,26 @@ namespace TweetDuck.Management{
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetClearOnExit(){
|
||||
ClearOnExit = true;
|
||||
public static void SetClearOnExit() {
|
||||
clearOnExit = true;
|
||||
RefreshTimer();
|
||||
}
|
||||
|
||||
public static void TryClearNow(){
|
||||
try{
|
||||
public static void TryClearNow() {
|
||||
try {
|
||||
Directory.Delete(CacheFolder, true);
|
||||
}catch{
|
||||
} catch {
|
||||
// welp, too bad
|
||||
}
|
||||
}
|
||||
|
||||
public static void Exit(){
|
||||
if (AutoClearTimer != null){
|
||||
AutoClearTimer.Dispose();
|
||||
AutoClearTimer = null;
|
||||
public static void Exit() {
|
||||
if (autoClearTimer != null) {
|
||||
autoClearTimer.Dispose();
|
||||
autoClearTimer = null;
|
||||
}
|
||||
|
||||
if (ClearOnExit){
|
||||
if (clearOnExit) {
|
||||
TryClearNow();
|
||||
}
|
||||
}
|
||||
|
@@ -4,13 +4,13 @@ using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Management{
|
||||
static class ClipboardManager{
|
||||
namespace TweetDuck.Management {
|
||||
static class ClipboardManager {
|
||||
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
|
||||
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
||||
|
||||
public static void SetText(string text, TextDataFormat format){
|
||||
if (string.IsNullOrEmpty(text)){
|
||||
public static void SetText(string text, TextDataFormat format) {
|
||||
if (string.IsNullOrEmpty(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,22 +19,22 @@ namespace TweetDuck.Management{
|
||||
SetClipboardData(obj);
|
||||
}
|
||||
|
||||
public static void SetImage(Image image){
|
||||
public static void SetImage(Image image) {
|
||||
DataObject obj = new DataObject();
|
||||
obj.SetImage(image);
|
||||
SetClipboardData(obj);
|
||||
}
|
||||
|
||||
private static void SetClipboardData(DataObject obj){
|
||||
try{
|
||||
private static void SetClipboardData(DataObject obj) {
|
||||
try {
|
||||
Clipboard.SetDataObject(obj);
|
||||
}catch(ExternalException e){
|
||||
} catch (ExternalException e) {
|
||||
Program.Reporter.HandleException("Clipboard Error", "TweetDuck could not access the clipboard as it is currently used by another process.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StripHtmlStyles(){
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)){
|
||||
public static void StripHtmlStyles() {
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -1,32 +1,32 @@
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Management{
|
||||
static class FormManager{
|
||||
namespace TweetDuck.Management {
|
||||
static class FormManager {
|
||||
private static FormCollection OpenForms => System.Windows.Forms.Application.OpenForms;
|
||||
|
||||
public static T TryFind<T>() where T : Form{
|
||||
public static T TryFind<T>() where T : Form {
|
||||
return OpenForms.OfType<T>().FirstOrDefault();
|
||||
}
|
||||
|
||||
public static bool TryBringToFront<T>() where T : Form{
|
||||
public static bool TryBringToFront<T>() where T : Form {
|
||||
T form = TryFind<T>();
|
||||
|
||||
if (form != null){
|
||||
if (form != null) {
|
||||
form.BringToFront();
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CloseAllDialogs(){
|
||||
foreach(IAppDialog dialog in OpenForms.OfType<IAppDialog>().Reverse()){
|
||||
((Form)dialog).Close();
|
||||
public static void CloseAllDialogs() {
|
||||
foreach (IAppDialog dialog in OpenForms.OfType<IAppDialog>().Reverse()) {
|
||||
((Form) dialog).Close();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAppDialog{}
|
||||
public interface IAppDialog {}
|
||||
}
|
||||
}
|
||||
|
@@ -5,8 +5,8 @@ using TweetDuck.Dialogs;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Systems.Startup;
|
||||
|
||||
namespace TweetDuck.Management{
|
||||
sealed class LockManager{
|
||||
namespace TweetDuck.Management {
|
||||
sealed class LockManager {
|
||||
private const int WaitRetryDelay = 250;
|
||||
private const int RestoreFailTimeout = 2000;
|
||||
private const int CloseNaturallyTimeout = 10000;
|
||||
@@ -14,26 +14,26 @@ namespace TweetDuck.Management{
|
||||
|
||||
private readonly LockFile lockFile;
|
||||
|
||||
public LockManager(string path){
|
||||
public LockManager(string path) {
|
||||
this.lockFile = new LockFile(path);
|
||||
}
|
||||
|
||||
public bool Lock(bool wasRestarted){
|
||||
public bool Lock(bool wasRestarted) {
|
||||
return wasRestarted ? LaunchAfterRestart() : LaunchNormally();
|
||||
}
|
||||
|
||||
public bool Unlock(){
|
||||
public bool Unlock() {
|
||||
return lockFile.Unlock();
|
||||
}
|
||||
|
||||
// Locking
|
||||
|
||||
private bool LaunchNormally(){
|
||||
private bool LaunchNormally() {
|
||||
LockResult lockResult = lockFile.Lock();
|
||||
|
||||
if (lockResult is LockResult.HasProcess info){
|
||||
if (!RestoreProcess(info.Process) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
|
||||
if (!CloseProcess(info.Process)){
|
||||
if (lockResult is LockResult.HasProcess info) {
|
||||
if (!RestoreProcess(info.Process) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)) {
|
||||
if (!CloseProcess(info.Process)) {
|
||||
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
|
||||
return false;
|
||||
}
|
||||
@@ -41,16 +41,16 @@ namespace TweetDuck.Management{
|
||||
info.Dispose();
|
||||
lockResult = lockFile.Lock();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (lockResult is LockResult.Fail fail){
|
||||
if (lockResult is LockResult.Fail fail) {
|
||||
ShowGenericException(fail);
|
||||
return false;
|
||||
}
|
||||
else if (lockResult != LockResult.Success){
|
||||
else if (lockResult != LockResult.Success) {
|
||||
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
||||
return false;
|
||||
}
|
||||
@@ -58,15 +58,15 @@ namespace TweetDuck.Management{
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool LaunchAfterRestart(){
|
||||
private bool LaunchAfterRestart() {
|
||||
LockResult lockResult = lockFile.LockWait(10000, WaitRetryDelay);
|
||||
|
||||
while(lockResult != LockResult.Success){
|
||||
if (lockResult is LockResult.Fail fail){
|
||||
while (lockResult != LockResult.Success) {
|
||||
if (lockResult is LockResult.Fail fail) {
|
||||
ShowGenericException(fail);
|
||||
return false;
|
||||
}
|
||||
else if (!FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
|
||||
else if (!FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -78,15 +78,15 @@ namespace TweetDuck.Management{
|
||||
|
||||
// Helpers
|
||||
|
||||
private static void ShowGenericException(LockResult.Fail fail){
|
||||
private static void ShowGenericException(LockResult.Fail fail) {
|
||||
Program.Reporter.HandleException("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", false, fail.Exception);
|
||||
}
|
||||
|
||||
private static bool RestoreProcess(Process process){
|
||||
if (process.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)process.Id, 0);
|
||||
private static bool RestoreProcess(Process process) {
|
||||
if (process.MainWindowHandle == IntPtr.Zero) { // restore if the original process is in tray
|
||||
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint) process.Id, 0);
|
||||
|
||||
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)){
|
||||
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -94,34 +94,34 @@ namespace TweetDuck.Management{
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool CloseProcess(Process process){
|
||||
try{
|
||||
if (process.CloseMainWindow()){
|
||||
private static bool CloseProcess(Process process) {
|
||||
try {
|
||||
if (process.CloseMainWindow()) {
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseNaturallyTimeout, WaitRetryDelay);
|
||||
}
|
||||
|
||||
if (!process.HasExited){
|
||||
if (!process.HasExited) {
|
||||
process.Kill();
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseKillTimeout, WaitRetryDelay);
|
||||
}
|
||||
|
||||
if (process.HasExited){
|
||||
if (process.HasExited) {
|
||||
process.Dispose();
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
||||
} catch (Exception ex) when (ex is InvalidOperationException || ex is Win32Exception) {
|
||||
bool hasExited = CheckProcessExited(process);
|
||||
process.Dispose();
|
||||
return hasExited;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CheckProcessExited(Process process){
|
||||
private static bool CheckProcessExited(Process process) {
|
||||
process.Refresh();
|
||||
return process.HasExited;
|
||||
}
|
||||
|
@@ -7,77 +7,82 @@ using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Management{
|
||||
sealed class ProfileManager{
|
||||
namespace TweetDuck.Management {
|
||||
sealed class ProfileManager {
|
||||
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
|
||||
private static readonly string LocalPrefsPath = Path.Combine(Program.StoragePath, "LocalPrefs.json");
|
||||
|
||||
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
|
||||
private static readonly string TempLocalPrefsPath = Path.Combine(Program.StoragePath, "LocalPrefsTmp.json");
|
||||
|
||||
private static readonly int SessionFileCount = 2;
|
||||
|
||||
[Flags]
|
||||
public enum Items{
|
||||
public enum Items {
|
||||
None = 0,
|
||||
UserConfig = 1,
|
||||
SystemConfig = 2,
|
||||
Session = 4,
|
||||
PluginData = 8,
|
||||
All = UserConfig|SystemConfig|Session|PluginData
|
||||
All = UserConfig | SystemConfig | Session | PluginData
|
||||
}
|
||||
|
||||
private readonly string file;
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
public ProfileManager(string file, 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)){
|
||||
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)){
|
||||
if (items.HasFlag(Items.SystemConfig)) {
|
||||
stream.WriteFile("system", Program.SystemConfigFilePath);
|
||||
}
|
||||
|
||||
if (items.HasFlag(Items.PluginData)){
|
||||
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){
|
||||
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)){
|
||||
if (items.HasFlag(Items.Session)) {
|
||||
stream.WriteFile("cookies", CookiesPath);
|
||||
stream.WriteFile("localprefs", LocalPrefsPath);
|
||||
}
|
||||
|
||||
stream.Flush();
|
||||
}
|
||||
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
} catch (Exception e) {
|
||||
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Items FindImportItems(){
|
||||
public Items FindImportItems() {
|
||||
Items items = Items.None;
|
||||
|
||||
try{
|
||||
try {
|
||||
using CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None));
|
||||
string key;
|
||||
|
||||
while((key = stream.SkipFile()) != null){
|
||||
switch(key){
|
||||
while ((key = stream.SkipFile()) != null) {
|
||||
switch (key) {
|
||||
case "config":
|
||||
items |= Items.UserConfig;
|
||||
break;
|
||||
@@ -92,54 +97,56 @@ namespace TweetDuck.Management{
|
||||
break;
|
||||
|
||||
case "cookies":
|
||||
case "localprefs":
|
||||
items |= Items.Session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}catch(Exception){
|
||||
} catch (Exception) {
|
||||
items = Items.None;
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public bool Import(Items items){
|
||||
try{
|
||||
HashSet<string> missingPlugins = new HashSet<string>();
|
||||
public bool Import(Items items) {
|
||||
try {
|
||||
var missingPlugins = new HashSet<string>();
|
||||
var sessionFiles = new HashSet<string>();
|
||||
|
||||
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
||||
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){
|
||||
while ((entry = stream.ReadFile()) != null) {
|
||||
switch (entry.KeyName) {
|
||||
case "config":
|
||||
if (items.HasFlag(Items.UserConfig)){
|
||||
if (items.HasFlag(Items.UserConfig)) {
|
||||
entry.WriteToFile(Program.UserConfigFilePath);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "system":
|
||||
if (items.HasFlag(Items.SystemConfig)){
|
||||
if (items.HasFlag(Items.SystemConfig)) {
|
||||
entry.WriteToFile(Program.SystemConfigFilePath);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "plugin.config":
|
||||
if (items.HasFlag(Items.PluginData)){
|
||||
if (items.HasFlag(Items.PluginData)) {
|
||||
entry.WriteToFile(Program.PluginConfigFilePath);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "plugin.data":
|
||||
if (items.HasFlag(Items.PluginData)){
|
||||
if (items.HasFlag(Items.PluginData)) {
|
||||
string[] value = entry.KeyValue;
|
||||
|
||||
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
||||
|
||||
if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))){
|
||||
if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))) {
|
||||
missingPlugins.Add(value[0]);
|
||||
}
|
||||
}
|
||||
@@ -147,8 +154,17 @@ namespace TweetDuck.Management{
|
||||
break;
|
||||
|
||||
case "cookies":
|
||||
if (items.HasFlag(Items.Session)){
|
||||
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
|
||||
if (items.HasFlag(Items.Session)) {
|
||||
entry.WriteToFile(TempCookiesPath);
|
||||
sessionFiles.Add(entry.KeyName);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "localprefs":
|
||||
if (items.HasFlag(Items.Session)) {
|
||||
entry.WriteToFile(TempLocalPrefsPath);
|
||||
sessionFiles.Add(entry.KeyName);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -156,56 +172,68 @@ namespace TweetDuck.Management{
|
||||
}
|
||||
}
|
||||
|
||||
if (missingPlugins.Count > 0){
|
||||
if (items.HasFlag(Items.Session) && sessionFiles.Count != SessionFileCount) {
|
||||
FormMessage.Error("Profile Import Error", "Cannot import login session from an older version of TweetDuck.", FormMessage.OK);
|
||||
File.Delete(TempCookiesPath);
|
||||
File.Delete(TempLocalPrefsPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
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){
|
||||
} 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)){
|
||||
public static void ImportCookies() {
|
||||
if (File.Exists(TempCookiesPath) && File.Exists(TempLocalPrefsPath)) {
|
||||
try {
|
||||
if (File.Exists(CookiesPath)) {
|
||||
File.Delete(CookiesPath);
|
||||
}
|
||||
|
||||
if (File.Exists(LocalPrefsPath)) {
|
||||
File.Delete(LocalPrefsPath);
|
||||
}
|
||||
|
||||
File.Move(TempCookiesPath, CookiesPath);
|
||||
}catch(Exception e){
|
||||
File.Move(TempLocalPrefsPath, LocalPrefsPath);
|
||||
} 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)){
|
||||
public static void DeleteCookies() {
|
||||
try {
|
||||
if (File.Exists(CookiesPath)) {
|
||||
File.Delete(CookiesPath);
|
||||
}
|
||||
}catch(Exception e){
|
||||
} 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)){
|
||||
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{
|
||||
else {
|
||||
return Enumerable.Empty<PathInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PathInfo{
|
||||
private sealed class PathInfo {
|
||||
public string Full { get; }
|
||||
public string Relative { get; }
|
||||
|
||||
public PathInfo(string fullPath, int rootLength){
|
||||
public PathInfo(string fullPath, int rootLength) {
|
||||
this.Full = fullPath;
|
||||
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
|
||||
}
|
||||
|
@@ -7,10 +7,10 @@ using TweetDuck.Configuration;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Communication;
|
||||
using TweetLib.Communication.Pipe;
|
||||
|
||||
namespace TweetDuck.Management{
|
||||
sealed class VideoPlayer : IDisposable{
|
||||
namespace TweetDuck.Management {
|
||||
sealed class VideoPlayer : IDisposable {
|
||||
private static UserConfig Config => Program.Config.User;
|
||||
|
||||
public bool Running => currentInstance != null && currentInstance.Running;
|
||||
@@ -22,29 +22,30 @@ namespace TweetDuck.Management{
|
||||
private Instance currentInstance;
|
||||
private bool isClosing;
|
||||
|
||||
public VideoPlayer(FormBrowser owner){
|
||||
public VideoPlayer(FormBrowser owner) {
|
||||
this.owner = owner;
|
||||
this.owner.FormClosing += owner_FormClosing;
|
||||
}
|
||||
|
||||
public void Launch(string videoUrl, string tweetUrl, string username){
|
||||
if (Running){
|
||||
public void Launch(string videoUrl, string tweetUrl, string username) {
|
||||
if (Running) {
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
|
||||
try{
|
||||
try {
|
||||
DuplexPipe.Server pipe = DuplexPipe.CreateServer();
|
||||
pipe.DataIn += pipe_DataIn;
|
||||
|
||||
Process process;
|
||||
|
||||
if ((process = Process.Start(new ProcessStartInfo{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo {
|
||||
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
||||
Arguments = $"{owner.Handle} {(int)Math.Floor(100F * owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{videoUrl}\" \"{pipe.GenerateToken()}\"",
|
||||
Arguments = $"{owner.Handle} {(int) Math.Floor(100F * owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{videoUrl}\" \"{pipe.GenerateToken()}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
})) != null){
|
||||
};
|
||||
|
||||
Process process;
|
||||
if ((process = Process.Start(startInfo)) != null) {
|
||||
currentInstance = new Instance(process, pipe, videoUrl, tweetUrl, username);
|
||||
|
||||
process.EnableRaisingEvents = true;
|
||||
@@ -55,24 +56,24 @@ namespace TweetDuck.Management{
|
||||
|
||||
pipe.DisposeToken();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
pipe.DataIn -= pipe_DataIn;
|
||||
pipe.Dispose();
|
||||
}
|
||||
}catch(Exception e){
|
||||
} 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());
|
||||
public void SendKeyEvent(Keys key) {
|
||||
currentInstance?.Pipe.Write("key", ((int) key).ToString());
|
||||
}
|
||||
|
||||
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
|
||||
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e) {
|
||||
owner.InvokeSafe(() => {
|
||||
switch(e.Key){
|
||||
switch (e.Key) {
|
||||
case "vol":
|
||||
if (int.TryParse(e.Data, out int volume) && volume != Config.VideoPlayerVolume){
|
||||
if (int.TryParse(e.Data, out int volume) && volume != Config.VideoPlayerVolume) {
|
||||
Config.VideoPlayerVolume = volume;
|
||||
Config.Save();
|
||||
}
|
||||
@@ -80,8 +81,7 @@ namespace TweetDuck.Management{
|
||||
break;
|
||||
|
||||
case "download":
|
||||
if (currentInstance != null){
|
||||
owner.AnalyticsFile.DownloadedVideos.Trigger();
|
||||
if (currentInstance != null) {
|
||||
TwitterUtils.DownloadVideo(currentInstance.VideoUrl, currentInstance.Username);
|
||||
}
|
||||
|
||||
@@ -98,13 +98,13 @@ namespace TweetDuck.Management{
|
||||
});
|
||||
}
|
||||
|
||||
public void Close(){
|
||||
if (currentInstance != null){
|
||||
if (isClosing){
|
||||
public void Close() {
|
||||
if (currentInstance != null) {
|
||||
if (isClosing) {
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
isClosing = true;
|
||||
currentInstance.Process.Exited -= process_Exited;
|
||||
currentInstance.Pipe.Write("die");
|
||||
@@ -112,15 +112,15 @@ namespace TweetDuck.Management{
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
public void Dispose() {
|
||||
ProcessExited = null;
|
||||
|
||||
isClosing = true;
|
||||
Destroy();
|
||||
}
|
||||
|
||||
private void Destroy(){
|
||||
if (currentInstance != null){
|
||||
private void Destroy() {
|
||||
if (currentInstance != null) {
|
||||
currentInstance.KillAndDispose();
|
||||
currentInstance = null;
|
||||
|
||||
@@ -128,20 +128,20 @@ namespace TweetDuck.Management{
|
||||
}
|
||||
}
|
||||
|
||||
private void owner_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (currentInstance != null){
|
||||
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)){
|
||||
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){
|
||||
private void process_Exited(object sender, EventArgs e) {
|
||||
if (currentInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,16 +151,16 @@ namespace TweetDuck.Management{
|
||||
currentInstance.Dispose();
|
||||
currentInstance = null;
|
||||
|
||||
switch(exitCode){
|
||||
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)){
|
||||
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(tweetUrl);
|
||||
}
|
||||
|
||||
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)){
|
||||
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(tweetUrl);
|
||||
}
|
||||
|
||||
@@ -170,13 +170,13 @@ namespace TweetDuck.Management{
|
||||
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
|
||||
}
|
||||
|
||||
private void TriggerProcessExitEventUnsafe(){
|
||||
private void TriggerProcessExitEventUnsafe() {
|
||||
ProcessExited?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private sealed class Instance : IDisposable{
|
||||
public bool Running{
|
||||
get{
|
||||
private sealed class Instance : IDisposable {
|
||||
public bool Running {
|
||||
get {
|
||||
Process.Refresh();
|
||||
return !Process.HasExited;
|
||||
}
|
||||
@@ -189,7 +189,7 @@ namespace TweetDuck.Management{
|
||||
public string TweetUrl { get; }
|
||||
public string Username { get; }
|
||||
|
||||
public Instance(Process process, DuplexPipe.Server pipe, string videoUrl, string tweetUrl, string username){
|
||||
public Instance(Process process, DuplexPipe.Server pipe, string videoUrl, string tweetUrl, string username) {
|
||||
this.Process = process;
|
||||
this.Pipe = pipe;
|
||||
this.VideoUrl = videoUrl;
|
||||
@@ -197,17 +197,17 @@ namespace TweetDuck.Management{
|
||||
this.Username = username;
|
||||
}
|
||||
|
||||
public void KillAndDispose(){
|
||||
try{
|
||||
public void KillAndDispose() {
|
||||
try {
|
||||
Process.Kill();
|
||||
}catch{
|
||||
} catch {
|
||||
// kill me instead then
|
||||
}
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
public void Dispose() {
|
||||
Process.Dispose();
|
||||
Pipe.Dispose();
|
||||
}
|
||||
|
@@ -6,19 +6,19 @@ using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed partial class PluginControl : UserControl{
|
||||
namespace TweetDuck.Plugins {
|
||||
sealed partial class PluginControl : UserControl {
|
||||
private readonly PluginManager pluginManager;
|
||||
private readonly Plugin plugin;
|
||||
private readonly bool isConfigurable;
|
||||
|
||||
private int nextHeight;
|
||||
|
||||
public PluginControl(){
|
||||
public PluginControl() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public PluginControl(PluginManager pluginManager, Plugin plugin) : this(){
|
||||
public PluginControl(PluginManager pluginManager, Plugin plugin) : this() {
|
||||
this.pluginManager = pluginManager;
|
||||
this.plugin = plugin;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
float dpiScale = this.GetDPIScale();
|
||||
|
||||
if (dpiScale > 1F){
|
||||
if (dpiScale > 1F) {
|
||||
Size = MaximumSize = new Size(MaximumSize.Width, MaximumSize.Height + 3);
|
||||
}
|
||||
|
||||
@@ -40,20 +40,20 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
UpdatePluginState();
|
||||
|
||||
if (labelDescription.Text.Length == 0){
|
||||
if (labelDescription.Text.Length == 0) {
|
||||
labelDescription.Visible = false;
|
||||
}
|
||||
|
||||
panelDescription_Resize(panelDescription, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void timerLayout_Tick(object sender, EventArgs e){
|
||||
private void timerLayout_Tick(object sender, EventArgs e) {
|
||||
timerLayout.Stop();
|
||||
Height = nextHeight;
|
||||
ResumeLayout();
|
||||
}
|
||||
|
||||
private void panelDescription_Resize(object sender, EventArgs e){
|
||||
private void panelDescription_Resize(object sender, EventArgs e) {
|
||||
SuspendLayout();
|
||||
|
||||
int maxWidth = panelDescription.Width - (panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
|
||||
@@ -64,37 +64,37 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
int requiredLines = Math.Max(descriptionLines, 1 + (string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1) + (isConfigurable ? 1 : 0));
|
||||
|
||||
nextHeight = requiredLines switch{
|
||||
nextHeight = requiredLines switch {
|
||||
1 => MaximumSize.Height - 2 * (font.Height - 1),
|
||||
2 => MaximumSize.Height - 1 * (font.Height - 1),
|
||||
_ => MaximumSize.Height
|
||||
};
|
||||
|
||||
if (nextHeight != Height){
|
||||
if (nextHeight != Height) {
|
||||
timerLayout.Start();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
ResumeLayout();
|
||||
}
|
||||
}
|
||||
|
||||
private void labelWebsite_Click(object sender, EventArgs e){
|
||||
if (labelWebsite.Text.Length > 0){
|
||||
private void labelWebsite_Click(object sender, EventArgs e) {
|
||||
if (labelWebsite.Text.Length > 0) {
|
||||
BrowserUtils.OpenExternalBrowser(labelWebsite.Text);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnConfigure_Click(object sender, EventArgs e){
|
||||
private void btnConfigure_Click(object sender, EventArgs e) {
|
||||
pluginManager.ConfigurePlugin(plugin);
|
||||
ParentForm?.Close();
|
||||
}
|
||||
|
||||
private void btnToggleState_Click(object sender, EventArgs e){
|
||||
private void btnToggleState_Click(object sender, EventArgs e) {
|
||||
pluginManager.Config.SetEnabled(plugin, !pluginManager.Config.IsEnabled(plugin));
|
||||
UpdatePluginState();
|
||||
}
|
||||
|
||||
private void UpdatePluginState(){
|
||||
private void UpdatePluginState() {
|
||||
bool isEnabled = pluginManager.Config.IsEnabled(plugin) && plugin.CanRun;
|
||||
Color textColor = isEnabled ? Color.Black : Color.FromArgb(90, 90, 90);
|
||||
|
||||
@@ -102,23 +102,23 @@ namespace TweetDuck.Plugins{
|
||||
labelAuthor.ForeColor = textColor;
|
||||
labelWebsite.ForeColor = isEnabled ? Color.Blue : Color.FromArgb(90, 90, 249);
|
||||
|
||||
if (plugin.Group == PluginGroup.Official){
|
||||
if (plugin.Group == PluginGroup.Official) {
|
||||
labelType.Text = "CORE";
|
||||
labelType.BackColor = isEnabled ? Color.FromArgb(154, 195, 217) : Color.FromArgb(185, 185, 185);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
labelType.Text = "USER";
|
||||
labelType.BackColor = isEnabled ? Color.FromArgb(208, 154, 217) : Color.FromArgb(185, 185, 185);
|
||||
}
|
||||
|
||||
if (plugin.CanRun){
|
||||
if (plugin.CanRun) {
|
||||
labelName.ForeColor = textColor;
|
||||
labelDescription.ForeColor = textColor;
|
||||
btnToggleState.Text = isEnabled ? "Disable" : "Enable";
|
||||
btnConfigure.Visible = isConfigurable;
|
||||
btnConfigure.Enabled = isEnabled;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
labelName.ForeColor = Color.DarkRed;
|
||||
labelDescription.ForeColor = Color.DarkRed;
|
||||
btnToggleState.Visible = false;
|
||||
|
@@ -5,31 +5,30 @@ using TweetDuck.Utils;
|
||||
using TweetLib.Core.Browser;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Events;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginDispatcher : IPluginDispatcher{
|
||||
namespace TweetDuck.Plugins {
|
||||
sealed class PluginDispatcher : IPluginDispatcher {
|
||||
public event EventHandler<PluginDispatchEventArgs> Ready;
|
||||
|
||||
private readonly IWebBrowser browser;
|
||||
private readonly IScriptExecutor executor;
|
||||
private readonly Func<string, bool> executeOnUrl;
|
||||
|
||||
public PluginDispatcher(IWebBrowser browser, Func<string, bool> executeOnUrl){
|
||||
public PluginDispatcher(IWebBrowser browser, Func<string, bool> executeOnUrl) {
|
||||
this.executeOnUrl = executeOnUrl;
|
||||
this.browser = browser;
|
||||
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||
this.executor = new CefScriptExecutor(browser);
|
||||
}
|
||||
|
||||
void IPluginDispatcher.AttachBridge(string name, object bridge){
|
||||
void IPluginDispatcher.AttachBridge(string name, object bridge) {
|
||||
browser.RegisterJsBridge(name, bridge);
|
||||
}
|
||||
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
||||
IFrame frame = e.Frame;
|
||||
|
||||
if (frame.IsMain && executeOnUrl(frame.Url)){
|
||||
if (frame.IsMain && executeOnUrl(frame.Url)) {
|
||||
Ready?.Invoke(this, new PluginDispatchEventArgs(executor));
|
||||
}
|
||||
}
|
||||
|
@@ -1,47 +1,23 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using CefSharp;
|
||||
using TweetLib.Core.Browser;
|
||||
using CefSharp;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginSchemeFactory : ISchemeHandlerFactory{
|
||||
namespace TweetDuck.Plugins {
|
||||
sealed class PluginSchemeFactory : ISchemeHandlerFactory {
|
||||
public const string Name = PluginSchemeHandler<IResourceHandler>.Name;
|
||||
|
||||
private readonly PluginSchemeHandler<IResourceHandler> handler = new PluginSchemeHandler<IResourceHandler>(new ResourceProvider());
|
||||
private readonly PluginSchemeHandler<IResourceHandler> handler;
|
||||
|
||||
internal void Setup(PluginManager plugins){
|
||||
public PluginSchemeFactory(ResourceProvider resourceProvider) {
|
||||
handler = new PluginSchemeHandler<IResourceHandler>(resourceProvider);
|
||||
}
|
||||
|
||||
internal void Setup(PluginManager plugins) {
|
||||
handler.Setup(plugins);
|
||||
}
|
||||
|
||||
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request){
|
||||
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
|
||||
return handler.Process(request.Url);
|
||||
}
|
||||
|
||||
private sealed class ResourceProvider : IResourceProvider<IResourceHandler>{
|
||||
private static ResourceHandler CreateHandler(byte[] bytes){
|
||||
var handler = ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
|
||||
handler.Headers.Set("Access-Control-Allow-Origin", "*");
|
||||
return handler;
|
||||
}
|
||||
|
||||
public IResourceHandler Status(HttpStatusCode code, string message){
|
||||
var handler = CreateHandler(Encoding.UTF8.GetBytes(message));
|
||||
handler.StatusCode = (int)code;
|
||||
return handler;
|
||||
}
|
||||
|
||||
public IResourceHandler File(byte[] bytes, string extension){
|
||||
if (bytes.Length == 0){
|
||||
return Status(HttpStatusCode.NoContent, "File is empty."); // FromByteArray crashes CEF internals with no contents
|
||||
}
|
||||
else{
|
||||
var handler = CreateHandler(bytes);
|
||||
handler.MimeType = Cef.GetMimeType(extension);
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
98
Program.cs
98
Program.cs
@@ -1,9 +1,9 @@
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Application;
|
||||
using TweetDuck.Browser;
|
||||
using TweetDuck.Browser.Handling;
|
||||
@@ -19,8 +19,8 @@ using TweetLib.Core.Collections;
|
||||
using TweetLib.Core.Utils;
|
||||
using Win = System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck{
|
||||
static class Program{
|
||||
namespace TweetDuck {
|
||||
static class Program {
|
||||
public const string BrandName = Lib.BrandName;
|
||||
public const string VersionTag = Version.Tag;
|
||||
|
||||
@@ -31,8 +31,9 @@ namespace TweetDuck{
|
||||
|
||||
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable"));
|
||||
|
||||
public static readonly string ScriptPath = Path.Combine(ProgramPath, "scripts");
|
||||
public static readonly string ResourcesPath = Path.Combine(ProgramPath, "resources");
|
||||
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
|
||||
public static readonly string GuidePath = Path.Combine(ProgramPath, "guide");
|
||||
|
||||
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
|
||||
|
||||
@@ -43,7 +44,6 @@ namespace TweetDuck{
|
||||
public static string UserConfigFilePath => Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
||||
public static string SystemConfigFilePath => Path.Combine(StoragePath, "TD_SystemConfig.cfg");
|
||||
public static string PluginConfigFilePath => Path.Combine(StoragePath, "TD_PluginConfig.cfg");
|
||||
public static string AnalyticsFilePath => Path.Combine(StoragePath, "TD_Analytics.cfg");
|
||||
|
||||
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
|
||||
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
|
||||
@@ -51,80 +51,75 @@ namespace TweetDuck{
|
||||
public static uint WindowRestoreMessage;
|
||||
|
||||
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
|
||||
private static bool HasCleanedUp;
|
||||
private static bool hasCleanedUp;
|
||||
|
||||
public static Reporter Reporter { get; }
|
||||
public static ConfigManager Config { get; }
|
||||
public static ScriptLoader Resources { get; }
|
||||
|
||||
static Program(){
|
||||
static Program() {
|
||||
Reporter = new Reporter(ErrorLogFilePath);
|
||||
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
|
||||
|
||||
Config = new ConfigManager();
|
||||
|
||||
#if DEBUG
|
||||
Resources = new ScriptLoaderDebug();
|
||||
#else
|
||||
Resources = new ScriptLoader();
|
||||
#endif
|
||||
|
||||
Lib.Initialize(new App.Builder{
|
||||
Lib.Initialize(new App.Builder {
|
||||
ErrorHandler = Reporter,
|
||||
SystemHandler = new SystemHandler(),
|
||||
ResourceHandler = Resources
|
||||
});
|
||||
}
|
||||
|
||||
internal static void SetupWinForms(){
|
||||
internal static void SetupWinForms() {
|
||||
Win.Application.EnableVisualStyles();
|
||||
Win.Application.SetCompatibleTextRenderingDefault(false);
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
private static void Main(){
|
||||
private static void Main() {
|
||||
SetupWinForms();
|
||||
Cef.EnableHighDPISupport();
|
||||
|
||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||
|
||||
if (!FileUtils.CheckFolderWritePermission(StoragePath)){
|
||||
if (!FileUtils.CheckFolderWritePermission(StoragePath)) {
|
||||
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: " + StoragePath, FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LockManager.Lock(Arguments.HasFlag(Arguments.ArgRestart))){
|
||||
if (!LockManager.Lock(Arguments.HasFlag(Arguments.ArgRestart))) {
|
||||
return;
|
||||
}
|
||||
|
||||
Config.LoadAll();
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgImportCookies)){
|
||||
if (Arguments.HasFlag(Arguments.ArgImportCookies)) {
|
||||
ProfileManager.ImportCookies();
|
||||
}
|
||||
else if (Arguments.HasFlag(Arguments.ArgDeleteCookies)){
|
||||
else if (Arguments.HasFlag(Arguments.ArgDeleteCookies)) {
|
||||
ProfileManager.DeleteCookies();
|
||||
}
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgUpdated)){
|
||||
if (Arguments.HasFlag(Arguments.ArgUpdated)) {
|
||||
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
|
||||
WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(StoragePath, "Service Worker"), 4000);
|
||||
BrowserCache.TryClearNow();
|
||||
}
|
||||
|
||||
try{
|
||||
try {
|
||||
ResourceRequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze));
|
||||
}catch(Exception e){
|
||||
} catch (Exception e) {
|
||||
FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: " + e.Message, FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config.User.UseSystemProxyForAllConnections) {
|
||||
WebUtils.EnableSystemProxy();
|
||||
}
|
||||
|
||||
BrowserCache.RefreshTimer();
|
||||
|
||||
CefSharpSettings.WcfEnabled = false;
|
||||
CefSharpSettings.LegacyJavascriptBindingEnabled = true;
|
||||
|
||||
CefSettings settings = new CefSettings{
|
||||
CefSettings settings = new CefSettings {
|
||||
UserAgent = BrowserUtils.UserAgentChrome,
|
||||
BrowserSubprocessPath = Path.Combine(ProgramPath, BrandName + ".Browser.exe"),
|
||||
CachePath = StoragePath,
|
||||
@@ -135,16 +130,12 @@ namespace TweetDuck{
|
||||
#endif
|
||||
};
|
||||
|
||||
var pluginScheme = new PluginSchemeFactory();
|
||||
var resourceProvider = new ResourceProvider();
|
||||
var resourceScheme = new ResourceSchemeFactory(resourceProvider);
|
||||
var pluginScheme = new PluginSchemeFactory(resourceProvider);
|
||||
|
||||
settings.RegisterScheme(new CefCustomScheme{
|
||||
SchemeName = PluginSchemeFactory.Name,
|
||||
IsStandard = false,
|
||||
IsSecure = true,
|
||||
IsCorsEnabled = true,
|
||||
IsCSPBypassing = true,
|
||||
SchemeHandlerFactory = pluginScheme
|
||||
});
|
||||
settings.SetupCustomScheme(ResourceSchemeFactory.Name, resourceScheme);
|
||||
settings.SetupCustomScheme(PluginSchemeFactory.Name, pluginScheme);
|
||||
|
||||
CommandLineArgs.ReadCefArguments(Config.User.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
||||
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
||||
@@ -153,50 +144,49 @@ namespace TweetDuck{
|
||||
|
||||
Win.Application.ApplicationExit += (sender, args) => ExitCleanup();
|
||||
|
||||
FormBrowser mainForm = new FormBrowser(pluginScheme);
|
||||
Resources.Initialize(mainForm);
|
||||
FormBrowser mainForm = new FormBrowser(resourceProvider, pluginScheme);
|
||||
Win.Application.Run(mainForm);
|
||||
|
||||
if (mainForm.UpdateInstaller != null){
|
||||
if (mainForm.UpdateInstaller != null) {
|
||||
ExitCleanup();
|
||||
|
||||
if (mainForm.UpdateInstaller.Launch()){
|
||||
if (mainForm.UpdateInstaller.Launch()) {
|
||||
Win.Application.Exit();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
RestartWithArgsInternal(Arguments.GetCurrentClean());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDataStoragePath(){
|
||||
private static string GetDataStoragePath() {
|
||||
string custom = Arguments.GetValue(Arguments.ArgDataFolder);
|
||||
|
||||
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){
|
||||
if (Path.GetInvalidPathChars().Any(custom.Contains)){
|
||||
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))) {
|
||||
if (Path.GetInvalidPathChars().Any(custom.Contains)) {
|
||||
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder contains invalid characters:\n" + custom);
|
||||
}
|
||||
else if (!Path.IsPathRooted(custom)){
|
||||
else if (!Path.IsPathRooted(custom)) {
|
||||
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder has to be either a simple folder name, or a full path:\n" + custom);
|
||||
}
|
||||
|
||||
return Environment.ExpandEnvironmentVariables(custom);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), custom ?? BrandName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Restart(params string[] extraArgs){
|
||||
public static void Restart(params string[] extraArgs) {
|
||||
CommandLineArgs args = Arguments.GetCurrentClean();
|
||||
CommandLineArgs.ReadStringArray('-', extraArgs, args);
|
||||
RestartWithArgs(args);
|
||||
}
|
||||
|
||||
public static void RestartWithArgs(CommandLineArgs args){
|
||||
public static void RestartWithArgs(CommandLineArgs args) {
|
||||
FormBrowser browserForm = FormManager.TryFind<FormBrowser>();
|
||||
|
||||
if (browserForm != null){
|
||||
if (browserForm != null) {
|
||||
browserForm.ForceClose();
|
||||
|
||||
ExitCleanup();
|
||||
@@ -204,14 +194,14 @@ namespace TweetDuck{
|
||||
}
|
||||
}
|
||||
|
||||
private static void RestartWithArgsInternal(CommandLineArgs args){
|
||||
private static void RestartWithArgsInternal(CommandLineArgs args) {
|
||||
args.AddFlag(Arguments.ArgRestart);
|
||||
Process.Start(ExecutablePath, args.ToString());
|
||||
Win.Application.Exit();
|
||||
}
|
||||
|
||||
private static void ExitCleanup(){
|
||||
if (HasCleanedUp){
|
||||
private static void ExitCleanup() {
|
||||
if (hasCleanedUp) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,7 +211,7 @@ namespace TweetDuck{
|
||||
BrowserCache.Exit();
|
||||
|
||||
LockManager.Unlock();
|
||||
HasCleanedUp = true;
|
||||
hasCleanedUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
46
Reporter.cs
46
Reporter.cs
@@ -9,53 +9,53 @@ using TweetDuck.Dialogs;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Application;
|
||||
|
||||
namespace TweetDuck{
|
||||
sealed class Reporter : IAppErrorHandler{
|
||||
namespace TweetDuck {
|
||||
sealed class Reporter : IAppErrorHandler {
|
||||
private readonly string logFile;
|
||||
|
||||
public Reporter(string logFile){
|
||||
public Reporter(string logFile) {
|
||||
this.logFile = logFile;
|
||||
}
|
||||
|
||||
public void SetupUnhandledExceptionHandler(string caption){
|
||||
public void SetupUnhandledExceptionHandler(string caption) {
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
|
||||
if (args.ExceptionObject is Exception ex){
|
||||
if (args.ExceptionObject is Exception ex) {
|
||||
HandleException(caption, "An unhandled exception has occurred.", false, ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public bool LogVerbose(string data){
|
||||
public bool LogVerbose(string data) {
|
||||
return Arguments.HasFlag(Arguments.ArgLogging) && LogImportant(data);
|
||||
}
|
||||
|
||||
public bool LogImportant(string data){
|
||||
return ((IAppErrorHandler)this).Log(data);
|
||||
public bool LogImportant(string data) {
|
||||
return ((IAppErrorHandler) this).Log(data);
|
||||
}
|
||||
|
||||
bool IAppErrorHandler.Log(string text){
|
||||
bool IAppErrorHandler.Log(string text) {
|
||||
#if DEBUG
|
||||
Debug.WriteLine(text);
|
||||
#endif
|
||||
|
||||
StringBuilder build = new StringBuilder();
|
||||
|
||||
if (!File.Exists(logFile)){
|
||||
if (!File.Exists(logFile)) {
|
||||
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
|
||||
}
|
||||
|
||||
build.Append("[").Append(DateTime.Now.ToString("G", Lib.Culture)).Append("]\r\n");
|
||||
build.Append(text).Append("\r\n\r\n");
|
||||
|
||||
try{
|
||||
try {
|
||||
File.AppendAllText(logFile, build.ToString(), Encoding.UTF8);
|
||||
return true;
|
||||
}catch{
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
||||
public void HandleException(string caption, string message, bool canIgnore, Exception e) {
|
||||
bool loggedSuccessfully = LogImportant(e.ToString());
|
||||
|
||||
string exceptionText = e is ExpandedLogException ? e.Message + "\n\nDetails with potentially sensitive information are in the Error Log." : e.Message;
|
||||
@@ -67,7 +67,7 @@ namespace TweetDuck{
|
||||
btnIgnore.Enabled = canIgnore;
|
||||
form.ActiveControl = canIgnore ? btnIgnore : btnExit;
|
||||
|
||||
Button btnOpenLog = new Button{
|
||||
Button btnOpenLog = new Button {
|
||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
||||
Enabled = loggedSuccessfully,
|
||||
Font = SystemFonts.MessageBoxFont,
|
||||
@@ -79,37 +79,37 @@ namespace TweetDuck{
|
||||
};
|
||||
|
||||
btnOpenLog.Click += (sender, args) => {
|
||||
using(Process.Start(logFile)){}
|
||||
using (Process.Start(logFile)) {}
|
||||
};
|
||||
|
||||
form.AddActionControl(btnOpenLog);
|
||||
|
||||
if (form.ShowDialog() == DialogResult.Ignore){
|
||||
if (form.ShowDialog() == DialogResult.Ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
try {
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}catch{
|
||||
} catch {
|
||||
Environment.FailFast(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void HandleEarlyFailure(string caption, string message){
|
||||
public static void HandleEarlyFailure(string caption, string message) {
|
||||
Program.SetupWinForms();
|
||||
FormMessage.Error(caption, message, "Exit");
|
||||
|
||||
try{
|
||||
try {
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}catch{
|
||||
} catch {
|
||||
Environment.FailFast(message, new Exception(message));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ExpandedLogException : Exception{
|
||||
public sealed class ExpandedLogException : Exception {
|
||||
private readonly string details;
|
||||
|
||||
public ExpandedLogException(Exception source, string details) : base(source.Message, source){
|
||||
public ExpandedLogException(Exception source, string details) : base(source.Message, source) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
|
99
Resources/Content/.all.js
Normal file
99
Resources/Content/.all.js
Normal file
@@ -0,0 +1,99 @@
|
||||
// noinspection ES6UnusedImports,JSUnusedLocalSymbols
|
||||
|
||||
/*
|
||||
* Imports all root modules and global functions to mark them as used.
|
||||
*/
|
||||
|
||||
import introduction from "./introduction/introduction.js";
|
||||
import hide_cookie_bar from "./login/hide_cookie_bar.js";
|
||||
import setup_document_attributes from "./login/setup_document_attributes.js";
|
||||
import add_skip_button from "./notification/add_skip_button.js";
|
||||
import disable_clipboard_formatting_notification from "./notification/disable_clipboard_formatting.js";
|
||||
import expand_links_or_show_tooltip_notification from "./notification/expand_links_or_show_tooltip.js";
|
||||
import handle_links from "./notification/handle_links.js";
|
||||
import handle_show_this_thread_link from "./notification/handle_show_this_thread_link.js";
|
||||
import recalculate_tweet_sent_time from "./notification/recalculate_tweet_sent_time.js";
|
||||
import reset_scroll_position_on_load from "./notification/reset_scroll_position_on_load.js";
|
||||
import scroll_smoothly from "./notification/scroll_smoothly.js";
|
||||
import setup_body_hover_class from "./notification/setup_body_hover_class.js";
|
||||
import add_tweetduck_to_settings_menu from "./tweetdeck/add_tweetduck_to_settings_menu.js";
|
||||
import bypass_tco_links from "./tweetdeck/bypass_t.co_links.js";
|
||||
import clear_search_input from "./tweetdeck/clear_search_input.js";
|
||||
import configure_first_day_of_week from "./tweetdeck/configure_first_day_of_week.js";
|
||||
import configure_language_for_translations from "./tweetdeck/configure_language_for_translations.js";
|
||||
import disable_clipboard_formatting from "./tweetdeck/disable_clipboard_formatting.js";
|
||||
import disable_td_metrics from "./tweetdeck/disable_td_metrics.js";
|
||||
import drag_links_onto_columns from "./tweetdeck/drag_links_onto_columns.js";
|
||||
import expand_links_or_show_tooltip from "./tweetdeck/expand_links_or_show_tooltip.js";
|
||||
import fix_dm_input_box_focus from "./tweetdeck/fix_dm_input_box_focus.js";
|
||||
import fix_horizontal_scrolling_of_column_container from "./tweetdeck/fix_horizontal_scrolling_of_column_container.js";
|
||||
import fix_marking_dm_as_read_when_replying from "./tweetdeck/fix_marking_dm_as_read_when_replying.js";
|
||||
import fix_media_preview_urls from "./tweetdeck/fix_media_preview_urls.js";
|
||||
import fix_missing_bing_translator_languages from "./tweetdeck/fix_missing_bing_translator_languages.js";
|
||||
import fix_os_name from "./tweetdeck/fix_os_name.js";
|
||||
import fix_scheduled_tweets_not_appearing from "./tweetdeck/fix_scheduled_tweets_not_appearing.js";
|
||||
import fix_youtube_previews from "./tweetdeck/fix_youtube_previews.js";
|
||||
import focus_composer_after_alt_tab from "./tweetdeck/focus_composer_after_alt_tab.js";
|
||||
import focus_composer_after_image_upload from "./tweetdeck/focus_composer_after_image_upload.js";
|
||||
import focus_composer_after_switching_account from "./tweetdeck/focus_composer_after_switching_account.js";
|
||||
import handle_extra_mouse_buttons from "./tweetdeck/handle_extra_mouse_buttons.js";
|
||||
import hook_theme_settings from "./tweetdeck/hook_theme_settings.js";
|
||||
import inject_css from "./tweetdeck/inject_css.js";
|
||||
import keep_like_follow_dialogs_open from "./tweetdeck/keep_like_follow_dialogs_open.js";
|
||||
import limit_loaded_dm_count from "./tweetdeck/limit_loaded_dm_count.js";
|
||||
import make_retweets_lowercase from "./tweetdeck/make_retweets_lowercase.js";
|
||||
import middle_click_tweet_icon_actions from "./tweetdeck/middle_click_tweet_icon_actions.js";
|
||||
import move_accounts_above_hashtags_in_search from "./tweetdeck/move_accounts_above_hashtags_in_search.js";
|
||||
import offline_notification from "./tweetdeck/offline_notification.js";
|
||||
import open_search_externally from "./tweetdeck/open_search_externally.js";
|
||||
import open_search_in_first_column from "./tweetdeck/open_search_in_first_column.js";
|
||||
import paste_images_from_clipboard from "./tweetdeck/paste_images_from_clipboard.js";
|
||||
import perform_search from "./tweetdeck/perform_search.js";
|
||||
import pin_composer_icon from "./tweetdeck/pin_composer_icon.js";
|
||||
import ready_plugins from "./tweetdeck/ready_plugins.js";
|
||||
import register_composer_active_event from "./tweetdeck/register_composer_active_event.js";
|
||||
import register_global_functions from "./tweetdeck/register_global_functions.js";
|
||||
import register_global_functions_jquery from "./tweetdeck/register_global_functions_jquery.js";
|
||||
import restore_cleared_column from "./tweetdeck/restore_cleared_column.js";
|
||||
import screenshot_tweet from "./tweetdeck/screenshot_tweet.js";
|
||||
import setup_column_type_attributes from "./tweetdeck/setup_column_type_attributes.js";
|
||||
import setup_desktop_notifications from "./tweetdeck/setup_desktop_notifications.js";
|
||||
import setup_link_context_menu from "./tweetdeck/setup_link_context_menu.js";
|
||||
import setup_sound_notifications from "./tweetdeck/setup_sound_notifications.js";
|
||||
import setup_tweet_context_menu from "./tweetdeck/setup_tweet_context_menu.js";
|
||||
import setup_tweetduck_account_bamboozle from "./tweetdeck/setup_tweetduck_account_bamboozle.js";
|
||||
import setup_video_player from "./tweetdeck/setup_video_player.js";
|
||||
import skip_pre_login_page from "./tweetdeck/skip_pre_login_page.js";
|
||||
import update from "./update/update.js";
|
||||
|
||||
const globalFunctions = [
|
||||
window.PluginBase,
|
||||
window.TDGF_applyROT13,
|
||||
window.TDGF_getColumnName,
|
||||
window.TDGF_injectMustache,
|
||||
window.TDGF_onGlobalDragStart,
|
||||
window.TDGF_onMouseClickExtra,
|
||||
window.TDGF_onPropertiesUpdated,
|
||||
window.TDGF_performSearch,
|
||||
window.TDGF_playSoundNotification,
|
||||
window.TDGF_playVideo,
|
||||
window.TDGF_registerPropertyUpdateCallback,
|
||||
window.TDGF_reinjectCustomCSS,
|
||||
window.TDGF_reload,
|
||||
window.TDGF_reloadColumns,
|
||||
window.TDGF_scrollSmoothly,
|
||||
window.TDGF_setSoundNotificationData,
|
||||
window.TDGF_showTweetDetail,
|
||||
window.TDGF_triggerScreenshot,
|
||||
window.TDPF_configurePlugin,
|
||||
window.TDPF_createCustomStyle,
|
||||
window.TDPF_createStorage,
|
||||
window.TDPF_loadConfigurationFile,
|
||||
window.TDPF_playVideo,
|
||||
window.TDPF_registerPropertyUpdateCallback,
|
||||
window.TDPF_requestReload,
|
||||
window.TDPF_setPluginState,
|
||||
window.TDUF_displayNotification,
|
||||
window.TD_PLUGINS_INSTALL,
|
||||
window.jQuery,
|
||||
];
|
76
Resources/Content/api/bridge.js
Normal file
76
Resources/Content/api/bridge.js
Normal file
@@ -0,0 +1,76 @@
|
||||
if (!("$TD" in window)) {
|
||||
throw "Missing $TD in global scope";
|
||||
}
|
||||
|
||||
if (!("$TDX" in window)) {
|
||||
throw "Missing $TDX in global scope";
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef TD_Bridge
|
||||
* @type {Object}
|
||||
*
|
||||
* @property {function(type: "error"|"warning"|"info"|"", contents: string)} alert
|
||||
* @property {function(message: string)} crashDebug
|
||||
* @property {function(tooltip: string|null)} displayTooltip
|
||||
* @property {function} fixClipboard
|
||||
* @property {function} loadNextNotification
|
||||
* @property {function(fontSize: string, headLayout: string)} loadNotificationLayout
|
||||
* @property {function(namespace: string)} onModulesLoaded
|
||||
* @property {function(columnId: string, chirpId: string, columnName: string, tweetHtml: string, tweetCharacters: int, tweetUrl: string, quoteUrl: string)} onTweetPopup
|
||||
* @property {function} onTweetSound
|
||||
* @property {function(url: string)} openBrowser
|
||||
* @property {function} openContextMenu
|
||||
* @property {function(showGuide: boolean)} onIntroductionClosed
|
||||
* @property {function(videoUrl: string, tweetUrl: string, username: string, callback: function)} playVideo
|
||||
* @property {function(columnId: string, chirpId: string, tweetUrl: string, quoteUrl: string, chirpAuthors: string, chirpImages: string)} setRightClickedChirp
|
||||
* @property {function(type: string, url: string)} setRightClickedLink
|
||||
* @property {function} showTweetDetail
|
||||
* @property {function(html: string, width: number)} screenshotTweet
|
||||
* @property {function} stopVideo
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef TD_Properties
|
||||
* @type {Object}
|
||||
*
|
||||
* @property {boolean} [expandLinksOnHover]
|
||||
* @property {number} [firstDayOfWeek]
|
||||
* @property {boolean} [focusDmInput]
|
||||
* @property {boolean} [keepLikeFollowDialogsOpen]
|
||||
* @property {boolean} [muteNotifications]
|
||||
* @property {boolean} [notificationMediaPreviews]
|
||||
* @property {boolean} [openSearchInFirstColumn]
|
||||
* @property {boolean} [skipOnLinkClick]
|
||||
* @property {string} [translationTarget]
|
||||
*/
|
||||
|
||||
/** @type {TD_Bridge} */
|
||||
export const $TD = window.$TD;
|
||||
|
||||
/** @type {TD_Properties} */
|
||||
export const $TDX = window.$TDX;
|
||||
|
||||
/**
|
||||
* @type {function(TD_Properties)[]}
|
||||
*/
|
||||
const propertyUpdateCallbacks = [];
|
||||
|
||||
/**
|
||||
* Registers a callback that responds to `$TDX` property value changes.
|
||||
* @param {function(TD_Properties)} callback
|
||||
* @param {boolean} [callAfterRegistering] whether to call the callback immediately after registering
|
||||
*/
|
||||
export function registerPropertyUpdateCallback(callback, callAfterRegistering) {
|
||||
propertyUpdateCallbacks.push(callback);
|
||||
if (callAfterRegistering) {
|
||||
callback($TDX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers all registered callbacks.
|
||||
*/
|
||||
export function triggerPropertiesUpdated() {
|
||||
propertyUpdateCallbacks.forEach(func => func($TDX));
|
||||
}
|
67
Resources/Content/api/jquery.js
vendored
Normal file
67
Resources/Content/api/jquery.js
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
if (!("$" in window)) {
|
||||
throw "Missing jQuery in global scope";
|
||||
}
|
||||
|
||||
/** @type {JQuery} */
|
||||
export const $ = window.$;
|
||||
|
||||
/**
|
||||
* Returns a jQuery object, or throws if no elements are found.
|
||||
* @param {JQuery.Selector} selector
|
||||
* @param {Element|string|null} [context]
|
||||
* @returns {JQuery}
|
||||
*/
|
||||
export const $$ = function(selector, context) {
|
||||
// noinspection JSValidateTypes
|
||||
const result = $(selector, context);
|
||||
|
||||
if (!result.length) {
|
||||
throw "No elements were found for selector: " + selector;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef InternalJQueryData
|
||||
* @type {Object}
|
||||
*
|
||||
* @property {InternalJQueryEvents} events
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef InternalJQueryEvents
|
||||
* @type {Object<string, function[]>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {EventTarget} target
|
||||
* @returns {InternalJQueryEvents}
|
||||
*/
|
||||
export function getEvents(target) {
|
||||
// noinspection JSUnresolvedFunction
|
||||
/** @type {InternalJQueryData} */
|
||||
const jqData = $._data(target);
|
||||
return jqData.events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws if an element does not have a registered jQuery event.
|
||||
* @param {EventTarget|HTMLElement} element
|
||||
* @param {string} eventName
|
||||
*/
|
||||
export function ensureEventExists(element, eventName) {
|
||||
if (!(eventName in getEvents(element))) {
|
||||
const tagName = element?.tagName.toLowerCase() ?? element?.nodeName;
|
||||
const classList = "classList" in element ? Array.from(element.classList).map(cls => `.${cls}`).join("") : "";
|
||||
throw `Missing jQuery event '${eventName}' in: ${tagName}${classList}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param jq {JQuery}
|
||||
* @returns {HTMLElement|null}
|
||||
*/
|
||||
export function single(jq) {
|
||||
return jq.length === 1 ? jq[0] : null;
|
||||
}
|
41
Resources/Content/api/ready.js
Normal file
41
Resources/Content/api/ready.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { $ } from "./jquery.js";
|
||||
import { TD } from "./td.js";
|
||||
import { crashDebug } from "./utils.js";
|
||||
|
||||
const callbacks = [];
|
||||
|
||||
/**
|
||||
* @param {function} callback
|
||||
*/
|
||||
function executeCallback(callback) {
|
||||
try {
|
||||
callback();
|
||||
} catch (e) {
|
||||
crashDebug("Caught error in function " + callback.name);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isAppReady() {
|
||||
return TD.ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function} callback
|
||||
*/
|
||||
export function onAppReady(callback) {
|
||||
if (isAppReady()) {
|
||||
executeCallback(callback);
|
||||
}
|
||||
else {
|
||||
callbacks.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).one("TD.ready", function() {
|
||||
callbacks.forEach(executeCallback);
|
||||
callbacks.length = 0;
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user