1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-09-14 19:32:10 +02:00

Compare commits

...

55 Commits
1.19 ... 1.21

Author SHA1 Message Date
21e49505d1 Release 1.21 2021-12-25 08:06:21 +01:00
e0025e02d1 Add option to use system proxy for all connections
Closes #319
2021-12-25 08:06:21 +01:00
4a9590c1d9 Disable color profile detection by default & add option to re-enable it 2021-12-25 08:06:20 +01:00
7de0c50387 Apply default background color only to Twitter and TweetDeck websites 2021-12-25 05:52:59 +01:00
ccb87351c1 Fix notifications having outdated theme-related attributes on the <html> element 2021-12-24 15:50:01 +01:00
273e7266eb Fix bootstrap script issues (wrong ID and stylesheets appearing in notifications) 2021-12-24 15:31:10 +01:00
427975e5ce Automatically update relative time shown in notifications 2021-12-24 14:54:58 +01:00
adb304b6a2 Remove section on Dev Tools in the guide 2021-12-24 12:38:15 +01:00
3a89a28f8b Move the official guide directly into the app 2021-12-24 11:30:42 +01:00
cff93dcc97 Remove scripts folder from pre-build script 2021-12-24 11:00:12 +01:00
dfde38ea3b Fix screenshotting wrong tweet if cursor moves away from right-clicked tweet too quickly 2021-12-24 10:14:12 +01:00
a8e7f065cf Rewrite screenshot functionality using new DevTools API 2021-12-24 09:12:46 +01:00
5ebfc67e48 Replace ScriptLoader & reimplement resource hot swap 2021-12-24 06:41:34 +01:00
57b03baad9 Add in-memory caching to td:// and tdp:// schemes 2021-12-23 18:26:42 +01:00
c91f1d0e5e Remove "scripts" folder and JS/CSS minification 2021-12-23 18:22:21 +01:00
6a421292b3 Refactor notification bootstrapping 2021-12-23 16:00:35 +01:00
ceae748503 Fix the possibility of plugin functions running in wrong order if main module loading gets delayed 2021-12-23 05:23:02 +01:00
008de87e55 Refactor plugin bootstrapping 2021-12-23 05:02:53 +01:00
e7479ef9e3 Fix missing module import 2021-12-22 13:25:03 +01:00
13f8f12ac8 Refactor error page 2021-12-22 10:05:01 +01:00
fd634379d7 Improve resistance to errors in bootstrapping script and modules 2021-12-22 08:49:00 +01:00
e854315a81 Reorganize resources folder 2021-12-22 08:37:45 +01:00
86136d7692 Refactor update checker as a bootstrapped module 2021-12-22 07:53:52 +01:00
db251bfdfd Add new resources folder to installers & remove old CEF files on update 2021-12-22 07:25:39 +01:00
9d39f26d54 Refactor introduction dialog as a bootstrapped module 2021-12-22 06:40:14 +01:00
9cd813c02c Refactor login page scripts & styles into modules and remove stuff broken by Twitter updates 2021-12-22 06:35:47 +01:00
8e1f87e062 Expand module bootstrapping mechanism 2021-12-22 06:35:47 +01:00
dda954285c Refactor main browser CSS injections 2021-12-22 05:08:04 +01:00
ed4f7b6b72 Refactor main browser code into JS modules 2021-12-22 03:00:14 +01:00
7239dcf4d2 Add td:// scheme for modularized resources 2021-12-21 12:48:45 +01:00
901cca268e Minor refactoring of custom URL schemes 2021-12-21 12:48:45 +01:00
b909341988 Delete devtools_resources.pak when updating 2021-12-21 12:48:45 +01:00
3e607ae0fe Fix JS script execution after updating CefSharp 2021-12-21 12:27:29 +01:00
587060f73c Update Rider project settings 2021-12-21 12:27:28 +01:00
3ea6f6ac18 Make opening dev tools via context menu immediately focus right-clicked element 2021-12-18 03:13:18 +01:00
c162761464 Reduce minification of resource files 2021-12-18 01:49:01 +01:00
994f9635ef Fix post build scripts to support moved F# executables 2021-12-18 01:29:31 +01:00
ab8752845d Fix importing/exporting login session after CEF updates 2021-12-17 23:48:02 +01:00
a94ee2fe4b Update CefSharp to 96 & add VC++ 2019 and UCRT DLLs to the repository 2021-12-17 23:48:02 +01:00
763c999b09 Remove analytics 2021-12-17 20:27:48 +01:00
b18cd2658c Fix source code indentation problems 2021-12-17 01:46:08 +01:00
d9782f554f Move TweetLib.Communication library to .NET Standard 2.0 2021-12-17 00:38:40 +01:00
f1d9663709 Update CefSharp to 92 2021-12-16 23:31:40 +01:00
bcf77052a5 Reformat solution & setup Rider project 2021-08-07 09:41:00 +02:00
85d15b32e9 Release 1.20 2021-02-03 04:27:46 +01:00
3d3b695c85 Fix broken login page after Twitter changed it
Closes #313
2021-02-03 03:52:00 +01:00
88b7cac298 Add Rider module 2021-02-03 03:51:02 +01:00
c7efa631e1 Enforce LF line endings 2021-02-01 00:39:45 +01:00
aeb90cbb1a Update CefSharp to 86 2021-02-01 00:00:34 +01:00
ae1c59847f Refactor locking mechanism & improve error reporting for failed locks 2020-06-16 00:28:35 +02:00
651d9be57c Release 1.19.0.2 2020-06-14 15:54:19 +02:00
eeb32db6fb Work around CEF tooltip showing for links 2020-06-14 15:22:58 +02:00
daa0780644 Release 1.19.0.1 2020-06-09 20:27:29 +02:00
8502f9105f Add sound notification file size warning as it's now loaded into memory 2020-06-08 23:33:43 +02:00
16ced3d827 Fix resource handlers reuse & broken notification sound 2020-06-08 23:33:43 +02:00
361 changed files with 16213 additions and 14544 deletions

4
.gitattributes vendored
View File

@@ -1,5 +1,3 @@
# Auto detect text files and perform LF normalization # Auto detect text files and perform LF normalization
* text=auto * text=auto eof=lf
*.txt text eof=lf
*.cs diff=csharp *.cs diff=csharp

7
.gitignore vendored
View File

@@ -5,8 +5,14 @@
bld/* bld/*
!bld/*.iss !bld/*.iss
!bld/*.bat !bld/*.bat
!bld/*.ps1
!bld/Redist
!bld/Resources !bld/Resources
# Rider
.idea/.idea.TweetDuck/.idea/dictionaries
.idea/.idea.TweetDuck/.idea/misc.xml
# User-specific files # User-specific files
*.suo *.suo
*.user *.user
@@ -140,4 +146,3 @@ _UpgradeReport_Files/
Backup*/ Backup*/
UpgradeLog*.XML UpgradeLog*.XML
UpgradeLog*.htm UpgradeLog*.htm

8
.idea/.idea.TweetDuck/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/projectSettingsUpdater.xml
/modules.xml
/contentModel.xml
/.idea.TweetDuck.iml

View File

@@ -0,0 +1,450 @@
<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="&#10;" />
<option name="RIGHT_MARGIN" value="999" />
<option name="FORMATTER_TAGS_ENABLED" value="true" />
<CssCodeStyleSettings>
<option name="HEX_COLOR_LOWER_CASE" value="true" />
</CssCodeStyleSettings>
<DB2CodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</DB2CodeStyleSettings>
<DerbyCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</DerbyCodeStyleSettings>
<GoCodeStyleSettings>
<option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
<option name="GROUP_STDLIB_IMPORTS" value="true" />
<option name="WRAP_COMP_LIT" value="5" />
<option name="WRAP_FUNC_PARAMS" value="5" />
<option name="WRAP_FUNC_RESULT" value="5" />
</GoCodeStyleSettings>
<H2CodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</H2CodeStyleSettings>
<H2CodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</H2CodeStyleSettings>
<HSQLCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</HSQLCodeStyleSettings>
<HTMLCodeStyleSettings>
<option name="HTML_ALIGN_TEXT" value="true" />
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE" value="" />
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACKETS" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
<option name="IMPORT_SORT_MODULE_NAME" value="true" />
</JSCodeStyleSettings>
<JSON>
<option name="OBJECT_WRAPPING" value="5" />
<option name="ARRAY_WRAPPING" value="5" />
</JSON>
<JavaCodeStyleSettings>
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" />
<package name="javax" withSubpackages="true" static="false" />
<package name="java" withSubpackages="true" static="false" />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="IMPORT_NESTED_CLASSES" value="true" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<LessCodeStyleSettings>
<option name="HEX_COLOR_LOWER_CASE" value="true" />
</LessCodeStyleSettings>
<MSSQLCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</MSSQLCodeStyleSettings>
<MySQLCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</MySQLCodeStyleSettings>
<Objective-C>
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
<option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" />
<option name="SPACE_BEFORE_INIT_LIST" value="true" />
<option name="SPACE_AFTER_DICTIONARY_LITERAL_COLON" value="false" />
</Objective-C>
<OracleCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</OracleCodeStyleSettings>
<PHPCodeStyleSettings>
<option name="ALIGN_KEY_VALUE_PAIRS" value="true" />
<option name="CONCAT_SPACES" value="false" />
<option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
<option name="LOWER_CASE_NULL_CONST" value="true" />
<option name="ELSE_IF_STYLE" value="COMBINE" />
<option name="VARIABLE_NAMING_STYLE" value="SNAKE_CASE" />
<option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="0" />
<option name="SPACE_BEFORE_CLOSURE_LEFT_PARENTHESIS" value="false" />
<option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
<option name="NEW_LINE_AFTER_PHP_OPENING_TAG" value="true" />
<option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
</PHPCodeStyleSettings>
<PostgresCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</PostgresCodeStyleSettings>
<Properties>
<option name="KEEP_BLANK_LINES" value="true" />
</Properties>
<Python>
<option name="SPACE_AROUND_EQ_IN_NAMED_PARAMETER" value="true" />
<option name="SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT" value="true" />
<option name="NEW_LINE_AFTER_COLON" value="true" />
<option name="DICT_WRAPPING" value="5" />
<option name="DICT_NEW_LINE_AFTER_LEFT_BRACE" value="true" />
<option name="DICT_NEW_LINE_BEFORE_RIGHT_BRACE" value="true" />
</Python>
<RsCodeStyleSettings>
<option name="ALIGN_RET_TYPE" value="false" />
<option name="ALIGN_TYPE_PARAMS" value="true" />
<option name="ALLOW_ONE_LINE_MATCH" value="true" />
<option name="SPACE_AROUND_ASSOC_TYPE_BINDING" value="true" />
</RsCodeStyleSettings>
<Ruby>
<option name="INDENT_PRIVATE_METHODS" value="true" />
<option name="INDENT_PROTECTED_METHODS" value="true" />
<option name="INDENT_PUBLIC_METHODS" value="true" />
<option name="INDENT_WHEN_CASES" value="true" />
<option name="CHAIN_CALLS_ALIGNMENT" value="2" />
</Ruby>
<SQLiteCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</SQLiteCodeStyleSettings>
<ScssCodeStyleSettings>
<option name="HEX_COLOR_LOWER_CASE" value="true" />
</ScssCodeStyleSettings>
<SqlCodeStyleSettings version="6">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
<option name="SUBQUERY_CONTENT" value="1" />
<option name="SUBQUERY_CLOSING" value="1" />
<option name="INSERT_TABLE_EL_LINE" value="0" />
<option name="INSERT_EL_WRAP" value="2" />
<option name="SET_EL_WRAP" value="2" />
<option name="SET_ALIGN_EQUAL_SIGN" value="false" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_ALIGN_JOIN_TABLES" value="true" />
<option name="FROM_INDENT_JOIN" value="false" />
<option name="FROM_ONLY_JOIN_INDENT" value="2" />
<option name="WHERE_EL_WRAP" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_DEFAULTS_ALIGN" value="false" />
<option name="TABLE_NULLABILITIES_ALIGN" value="false" />
<option name="CONSTRAINT_WRAP_1" value="false" />
<option name="CONSTRAINT_WRAP_3" value="true" />
<option name="CONSTRAINT_WRAP_4" value="true" />
<option name="VIEW_INDENT_QUERY" value="true" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
</SqlCodeStyleSettings>
<SybaseCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</SybaseCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
</TypeScriptCodeStyleSettings>
<XML>
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</XML>
<codeStyleSettings language="CMake">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="CSS">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Groovy">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="HTML">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="RIGHT_MARGIN" value="999" />
<option name="BLANK_LINES_AFTER_PACKAGE" value="0" />
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="SPACE_AFTER_TYPE_CAST" value="false" />
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="SPACE_WITHIN_BRACKETS" value="true" />
<option name="SPACE_WITHIN_BRACES" value="true" />
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="LESS">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Lua">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="PHP">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="CLASS_BRACE_STYLE" value="1" />
<option name="METHOD_BRACE_STYLE" value="1" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Puppet">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Python">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="RHTML">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Rust">
<option name="RIGHT_MARGIN" value="140" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SASS">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SCSS">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SQL">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Shell Script">
<indentOptions>
<option name="INDENT_SIZE" value="4" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TOML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="go">
<option name="CALL_PARAMETERS_WRAP" value="5" />
<indentOptions>
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="CLASS_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="liquid">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ruby">
<option name="SPACE_WITHIN_BRACES" value="true" />
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="yaml">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,770 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project" />
<inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true">
<option name="nonThreadSafeClasses">
<value />
</option>
<option name="nonThreadSafeTypes" value="" />
</inspection_tool>
<inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AddOperatorModifier" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="AmbiguousFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AmbiguousMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssertsWithoutMessagesTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentOrReturnOfFieldWithMutableType" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreTransformationOfOriginalParameter" value="true" />
</inspection_tool>
<inspection_tool class="AssignmentToStaticFieldFromInstanceMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AutoBoxing" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAddedToCollection" value="false" />
</inspection_tool>
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredTypes" value="java.util.stream.Stream,java.util.stream.IntStream,java.util.stream.LongStream,java.util.stream.DoubleStream,net.minecraft.client.Minecraft,net.minecraft.client.MainWindow" />
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,net.minecraft.client.MinecraftClient,getInstance|getWindow" />
</inspection_tool>
<inspection_tool class="AutoUnboxing" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AwaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AwaitWithoutCorrespondingSignal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BigDecimalEquals" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BigDecimalLegacyMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CallableParameterUseCaseInTypeContextInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CascadeStringReplacementInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="USE_SHORT_ARRAYS_SYNTAX" value="true" />
</inspection_tool>
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="ClassIndependentOfModule" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassLoaderInstantiation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassMayBeInterface" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="reportClassesWithNonAbstractMethods" value="true" />
</inspection_tool>
<inspection_tool class="ClassMethodNameMatchesFieldNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassNewInstance" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassOnlyUsedInOneModule" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassOnlyUsedInOnePackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassOverridesFieldOfSuperClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_PRIVATE_REDEFINITION" value="false" />
</inspection_tool>
<inspection_tool class="ClassUnconnectedToPackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CloneableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CollectionsFieldAccessReplaceableByMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ComparatorNotSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ComposeMissingKeys" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConditionalExpression" enabled="true" level="INFORMATION" enabled_by_default="true">
<option name="ignoreSimpleAssignmentsAndReturns" value="true" />
</inspection_tool>
<inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConfusingElse" enabled="false" level="WEAK WARNING" enabled_by_default="false">
<option name="reportWhenNoStatementFollow" value="true" />
</inspection_tool>
<inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConfusingPlusesOrMinusesJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConstantMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConstantTestNGAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ConvertJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConvertLambdaToReference" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ConvertOldAnnotations" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CssConvertColorToHexInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="CssConvertColorToRgbInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="CssMissingSemicolon" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CssReplaceWithShorthandUnsafely" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="CustomClassloader" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CustomSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CyclicPackageDependency" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DateToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DisallowWritingIntoStaticPropertiesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="DisconnectedForeachInstructionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="DisjointPackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DivideByZeroJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DocumentWriteJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreOnVolatileVariables" value="false" />
</inspection_tool>
<inspection_tool class="DriverManagerGetConnection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DuplicateConditionJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DynamicallyGeneratedCodeJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ES6ConvertIndexedForToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ES6ConvertToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ES6TopLevelAwaitExpression" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="EmptyDirectory" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_reportEmptyBlocks" value="true" />
<option name="commentsAreContent" value="true" />
</inspection_tool>
<inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoreSwitchStatementsWithDefault" value="true" />
</inspection_tool>
<inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ErrorRethrown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExceptionNameDoesntEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExceptionPackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExpectedExceptionNeverThrownTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExplicitArgumentCanBeLambda" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExternalizableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
<option name="countGettersAndSetters" value="false" />
</inspection_tool>
<inspection_tool class="FieldDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="FieldHidesSuperclassField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInvisibleFields" value="false" />
<option name="ignoreStaticFields" value="false" />
</inspection_tool>
<inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreTrivialFinalizers" value="true" />
</inspection_tool>
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FixedTimeStartWithInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ForLoopThatDoesntUseLoopVariableJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="configuration">
<list>
<option value="\Codeception\Util\Debug::debug" />
<option value="\Codeception\Util\Debug::pause" />
<option value="\Doctrine\Common\Util\Debug::dump" />
<option value="\Doctrine\Common\Util\Debug::export" />
<option value="\Illuminate\Support\Debug\Dumper::dump" />
<option value="\Symfony\Component\Debug\Debug::enable" />
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
<option value="\Zend\Debug\Debug::dump" />
<option value="\Zend\Di\Display\Console::export" />
<option value="dd" />
<option value="debug_print_backtrace" />
<option value="debug_zval_dump" />
<option value="dpm" />
<option value="dpq" />
<option value="dsm" />
<option value="dump" />
<option value="dvm" />
<option value="error_log" />
<option value="kpr" />
<option value="phpinfo" />
<option value="print_r" />
<option value="var_dump" />
<option value="var_export" />
<option value="wp_die" />
<option value="xdebug_break" />
<option value="xdebug_call_class" />
<option value="xdebug_call_file" />
<option value="xdebug_call_function" />
<option value="xdebug_call_line" />
<option value="xdebug_code_coverage_started" />
<option value="xdebug_debug_zval" />
<option value="xdebug_debug_zval_stdout" />
<option value="xdebug_dump_superglobals" />
<option value="xdebug_enable" />
<option value="xdebug_get_code_coverage" />
<option value="xdebug_get_collected_errors" />
<option value="xdebug_get_declared_vars" />
<option value="xdebug_get_function_stack" />
<option value="xdebug_get_headers" />
<option value="xdebug_get_monitored_functions" />
<option value="xdebug_get_profiler_filename" />
<option value="xdebug_get_stack_depth" />
<option value="xdebug_get_tracefile_name" />
<option value="xdebug_is_enabled" />
<option value="xdebug_memory_usage" />
<option value="xdebug_peak_memory_usage" />
<option value="xdebug_print_function_stack" />
<option value="xdebug_start_code_coverage" />
<option value="xdebug_start_error_collection" />
<option value="xdebug_start_function_monitor" />
<option value="xdebug_start_trace" />
<option value="xdebug_stop_code_coverage" />
<option value="xdebug_stop_error_collection" />
<option value="xdebug_stop_function_monitor" />
<option value="xdebug_stop_trace" />
<option value="xdebug_time_index" />
<option value="xdebug_var_dump" />
</list>
</option>
<option name="migratedIntoUserSpace" value="true" />
</inspection_tool>
<inspection_tool class="FunctionNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[a-z][A-Za-z]*" />
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="99" />
</inspection_tool>
<inspection_tool class="FunctionWithInconsistentReturnsJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="HibernateResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="HtmlMissingClosingTag" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="HtmlPresentationalElement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="HtmlRequiredTitleAttribute" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="IfStatementWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ImplicitDefaultCharsetUsage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InconsistentLanguageLevel" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IncrementDecrementOperationEquivalentInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="InnerClassOnInterface" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInnerInterfaces" value="true" />
</inspection_tool>
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInvisibleFields" value="false" />
</inspection_tool>
<inspection_tool class="InsertLiteralUnderscores" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IsEmptyFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false">
<option name="SUGGEST_TO_USE_COUNT_CHECK" value="true" />
</inspection_tool>
<inspection_tool class="IsNullFunctionUsageInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JDBCExecuteWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JDBCPrepareStatementWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="JNDIResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="JSClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="99" />
</inspection_tool>
<inspection_tool class="JSConstructorReturnsPrimitive" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JSEqualityComparisonWithCoercion.TS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="mySeverity" value="Always" />
</inspection_tool>
<inspection_tool class="JSJoinVariableDeclarationAndAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="JSMissingSwitchBranches" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="JSNonASCIINames" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myAllowOnlyAscii" value="true" />
</inspection_tool>
<inspection_tool class="JSNonStrictModeUsed" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JSOctalInteger" enabled="true" level="ERROR" enabled_by_default="true">
<option name="myReportNonStrictEs5" value="true" />
</inspection_tool>
<inspection_tool class="JSUndeclaredVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="myCheckGlobalDefinitions" value="true" />
</inspection_tool>
<inspection_tool class="JSUnusedGlobalSymbols" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myReportUnusedDefinitions" value="true" />
<option name="myReportUnusedProperties" value="true" />
</inspection_tool>
<inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnitTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="JoinDeclarationAndAssignmentJava" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="LambdaCanBeMethodCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="LoadLibraryWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_VARIABLES" value="true" />
<option name="REPORT_PARAMETERS" value="true" />
</inspection_tool>
<inspection_tool class="LocalVariableDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="LocalVariableNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[a-z][A-Za-z]*" />
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="99" />
</inspection_tool>
<inspection_tool class="MalformedSetUpTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodCallSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_onlyPrivateOrFinal" value="false" />
<option name="m_ignoreEmptyMethods" value="true" />
</inspection_tool>
<inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true">
<option name="reportIncompatibleParameters" value="true" />
</inspection_tool>
<inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="MisorderedAssertEqualsArgumentsTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MisorderedModifiersInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreObjectMethods" value="false" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_requireAnnotationsFirst" value="true" />
</inspection_tool>
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NakedNotify" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreNegatedNullComparison" value="true" />
</inspection_tool>
<inspection_tool class="NestedAssignment" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NestedClassSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NestedConditionalExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NestedSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NestedTernaryOperatorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NewExpressionSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NonBlockStatementBodyJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonFinalClone" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonReproducibleMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSerializableFieldInSerializableClass" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignorableAnnotations">
<value />
</option>
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="NonSerializableObjectBoundToHttpSession" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSerializableObjectPassedToObjectStream" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSerializableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NotOptimalIfConditionsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_DUPLICATE_CONDITIONS" value="false" />
<option name="SUGGEST_OPTIMIZING_CONDITIONS" value="false" />
</inspection_tool>
<inspection_tool class="NotifyCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NotifyWithoutCorrespondingWait" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NumericToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="OCInconsistentNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ObjectAllocationIgnoredJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreRequiredObsoleteCollectionTypes" value="true" />
</inspection_tool>
<inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="OffsetOperationsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="OneTimeUseVariablesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ALLOW_LONG_STATEMENTS" value="true" />
</inspection_tool>
<inspection_tool class="OverloadedVarargsMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PackageInMultipleModules" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInvisibleFields" value="true" />
<option name="m_ignoreStaticMethodParametersHidingInstanceFields" value="true" />
<option name="m_ignoreForConstructors" value="true" />
<option name="m_ignoreForPropertySetters" value="true" />
<option name="m_ignoreForAbstractMethods" value="false" />
</inspection_tool>
<inspection_tool class="ParameterNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[a-z][A-Za-z]*" />
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="99" />
</inspection_tool>
<inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpAssignmentReplaceableWithOperatorAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpAssignmentReplaceableWithPrefixExpressionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpCSValidationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
<option name="EXTENSIONS" value="php,js,css,inc" />
</inspection_tool>
<inspection_tool class="PhpClassNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpClosureCanBeConvertedToShortArrowFunctionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpCompoundNamespaceDepthInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpConstantNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpConstantReassignmentInspection" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="SKIP_ON_EMPTY_PHPDOC" value="false" />
</inspection_tool>
<inspection_tool class="PhpFunctionNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpInconsistentReturnPointsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ALLOW_RETURN_NULL_IN_VOID" value="false" />
</inspection_tool>
<inspection_tool class="PhpLongTypeFormInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpLoopCanBeConvertedToArrayFillInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpLoopCanBeConvertedToArrayFilterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpLoopCanBeConvertedToArrayMapInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMethodNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingParentCallMagicInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingReturnTypeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingStrictTypesDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpNewClassMissingParameterListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpNonCanonicalElementsOrderInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpOverridingMethodVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpPropertyNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[a-z][_a-z\d]*" />
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpSeparateElseIfInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpSingleStatementWithBracesInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myCommentsCountAsContent" value="true" />
</inspection_tool>
<inspection_tool class="PhpStatementWithoutBracesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpStaticAsDynamicMethodCallInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="SHOW_FOR_MAGIC" value="false" />
</inspection_tool>
<inspection_tool class="PhpTraditionalSyntaxArrayLiteralInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpTraitsUseListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpUndefinedCallbackInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpUndefinedClassConstantInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="DOWNGRADE_SEVERITY" value="true" />
</inspection_tool>
<inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="DOWNGRADE_SEVERITY" value="true" />
</inspection_tool>
<inspection_tool class="PhpUnused" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="SHOW_UNUSED_BY_ENTRIES" value="false" />
</inspection_tool>
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="DONT_REPORT_ANONYMOUS" value="true" />
</inspection_tool>
<inspection_tool class="PhpVarUsageInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpVariableNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpVariableVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PointlessBitwiseExpressionJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreExpressionsContainingConstants" value="false" />
</inspection_tool>
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ProblematicWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PropertyCanBeStaticInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="m_onlyWhenTypesAreKnown" value="false" />
</inspection_tool>
<inspection_tool class="RawTypeCanBeGeneric" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReadObjectAndWriteObjectPrivate" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ReadObjectInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ReadResolveAndWriteReplaceProtected" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RedundantElseClauseInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSerializable" value="true" />
<option name="ignoreCloneable" value="true" />
</inspection_tool>
<inspection_tool class="RedundantObjectTypeCheck" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true">
<option name="IGNORE_ALL" value="true" />
</inspection_tool>
<inspection_tool class="ReferencingObjectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RegExpOctalEscape" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreLazyOperators" value="true" />
<option name="ignoreObscureOperators" value="true" />
</inspection_tool>
<inspection_tool class="ReplaceAssignmentWithOperatorAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceStringFormatWithLiteral" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithDropLast" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithIndexingOperation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithSubstringAfter" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithSubstringBefore" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithTake" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ResultSetIndexZero" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RsSimplifyBooleanExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RuntimeExec" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RuntimeExecWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="optionConfiguration">
<list>
<option value="barryvdh/laravel-debugbar" />
<option value="behat/behat" />
<option value="brianium/paratest" />
<option value="codeception/codeception" />
<option value="codedungeon/phpunit-result-printer" />
<option value="composer/composer" />
<option value="doctrine/coding-standard" />
<option value="filp/whoops" />
<option value="friendsofphp/php-cs-fixer" />
<option value="humbug/humbug" />
<option value="infection/infection" />
<option value="jakub-onderka/php-parallel-lint" />
<option value="johnkary/phpunit-speedtrap" />
<option value="kalessil/production-dependencies-guard" />
<option value="mikey179/vfsStream" />
<option value="mockery/mockery" />
<option value="mybuilder/phpunit-accelerator" />
<option value="orchestra/testbench" />
<option value="pdepend/pdepend" />
<option value="phan/phan" />
<option value="phing/phing" />
<option value="phpcompatibility/php-compatibility" />
<option value="phpmd/phpmd" />
<option value="phpro/grumphp" />
<option value="phpspec/phpspec" />
<option value="phpspec/prophecy" />
<option value="phpstan/phpstan" />
<option value="phpunit/phpunit" />
<option value="povils/phpmnd" />
<option value="roave/security-advisories" />
<option value="satooshi/php-coveralls" />
<option value="sebastian/phpcpd" />
<option value="slevomat/coding-standard" />
<option value="spatie/phpunit-watcher" />
<option value="squizlabs/php_codesniffer" />
<option value="sstalle/php7cc" />
<option value="symfony/debug" />
<option value="symfony/maker-bundle" />
<option value="symfony/phpunit-bridge" />
<option value="symfony/var-dumper" />
<option value="vimeo/psalm" />
<option value="wimg/php-compatibility" />
<option value="wp-coding-standards/wpcs" />
<option value="yiisoft/yii2-coding-standards" />
<option value="yiisoft/yii2-debug" />
<option value="yiisoft/yii2-gii" />
<option value="zendframework/zend-coding-standard" />
<option value="zendframework/zend-debug" />
<option value="zendframework/zend-test" />
</list>
</option>
</inspection_tool>
<inspection_tool class="SerialPersistentFieldsWithWrongSignature" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SerialVersionUIDNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="SerializableHasSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="SerializableInnerClassHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="SerializableInnerClassWithNonSerializableOuterClass" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="SerializableStoresNonSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SerializableWithUnconstructableAncestor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SeveralTargetsMessage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SharedThreadLocalRandom" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ShortEchoTagCanBeUsedInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SignalWithoutCorrespondingAwait" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SimplifiableIfStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SingleStatementInBlock" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="SqlGotoInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlWithoutWhereInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myDontWarnForLimit" value="false" />
</inspection_tool>
<inspection_tool class="StrTrUsageAsStrReplaceInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StringConcatenationInMessageFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true">
<option name="SUPPRESS_FOR_VALUES_WHICH_COULD_BE_NULL" value="true" />
</inspection_tool>
<inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
<option name="onlyWarnOnLoop" value="true" />
</inspection_tool>
<inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SubStrUsedAsStrPosInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SuspiciousLiteralUnderscore" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SynchronizationOnStaticField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_includeNativeMethods" value="true" />
<option name="ignoreSynchronizedSuperMethods" value="true" />
</inspection_tool>
<inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemProperties" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemRunFinalizersOnExit" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemSetSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSupers" value="true" />
</inspection_tool>
<inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TextLabelInSwitchStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadLocalNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreRethrownExceptions" value="true" />
</inspection_tool>
<inspection_tool class="ThrowRawExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="TimeToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TooBroadScope" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="m_allowConstructorAsInitializer" value="false" />
<option name="m_onlyLookAtBlocks" value="true" />
</inspection_tool>
<inspection_tool class="TransientFieldInNonSerializableClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TransientFieldNotInitialized" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnNecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnclearBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnconstrainedVariableType" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UndeclaredTests" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnknownInspectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreStaticFieldAccesses" value="false" />
<option name="m_ignoreStaticMethodCalls" value="false" />
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryBoxing" enabled="true" level="WARNING" enabled_by_default="true">
<option name="onlyReportSuperfluouslyBoxed" value="true" />
</inspection_tool>
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnnotations" value="true" />
</inspection_tool>
<inspection_tool class="UnnecessaryLocalVariable" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
<option name="m_ignoreAnnotatedVariables" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
<option name="m_ignoreAnnotatedVariables" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessaryParentheses" enabled="true" level="INFORMATION" enabled_by_default="true">
<option name="ignoreClarifyingParentheses" value="true" />
<option name="ignoreParenthesesOnConditionals" value="false" />
<option name="ignoreParenthesesOnLambdaParameter" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryUnboxing" enabled="true" level="WARNING" enabled_by_default="true">
<option name="onlyReportSuperfluouslyUnboxed" value="true" />
</inspection_tool>
<inspection_tool class="UnsetConstructsCanBeMergedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSemicolonAtEndOfBlock" value="false" />
</inspection_tool>
<inspection_tool class="UnusedCatchParameterJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreCatchBlocksWithComments" value="true" />
</inspection_tool>
<inspection_tool class="UnusedProperty" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UseOfAWTPeerClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfProcessBuilder" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfSunClasses" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UsingInclusionReturnValueInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignorableAnnotations">
<value />
</option>
<option name="ignoreClassesWithOnlyMain" value="true" />
</inspection_tool>
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="VariableUseSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="VoidExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitNotifyNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitOrAwaitWithoutTimeout" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreNonEmtpyLoops" value="false" />
</inspection_tool>
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project" />
<version value="1.0" />
</settings>
</component>

View 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>

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="TweetDuck" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/bin/x86/Debug/TweetDuck.exe" />
<option name="PROGRAM_PARAMETERS" value="-datafolder TweetDuckDebug -nogdpr" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/bin/x86/Debug" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/TweetDuck.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="Console" />
<option name="PROJECT_TFM" value=".NETFramework,Version=v4.7.2" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>

11
.idea/.idea.TweetDuck/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitSharedSettings">
<option name="FORCE_PUSH_PROHIBITED_PATTERNS">
<list />
</option>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -1,58 +0,0 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using TweetDuck.Utils;
using TweetLib.Core.Application;
namespace TweetDuck.Application{
class LockHandler : IAppLockHandler{
private const int WaitRetryDelay = 250;
private const int RestoreFailTimeout = 2000;
private const int CloseNaturallyTimeout = 10000;
private const int CloseKillTimeout = 5000;
bool IAppLockHandler.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)){
return true;
}
}
return false;
}
bool IAppLockHandler.CloseProcess(Process process){
try{
if (process.CloseMainWindow()){
// ReSharper disable once AccessToDisposedClosure
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseNaturallyTimeout, WaitRetryDelay);
}
if (!process.HasExited){
process.Kill();
// ReSharper disable once AccessToDisposedClosure
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseKillTimeout, WaitRetryDelay);
}
if (process.HasExited){
process.Dispose();
return true;
}
else{
return false;
}
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
bool hasExited = CheckProcessExited(process);
process.Dispose();
return hasExited;
}
}
private static bool CheckProcessExited(Process process){
process.Refresh();
return process.HasExited;
}
}
}

View File

@@ -3,26 +3,26 @@ using System.Diagnostics;
using System.IO; using System.IO;
using TweetLib.Core.Application; using TweetLib.Core.Application;
namespace TweetDuck.Application{ namespace TweetDuck.Application {
class SystemHandler : IAppSystemHandler{ class SystemHandler : IAppSystemHandler {
void IAppSystemHandler.OpenAssociatedProgram(string path){ void IAppSystemHandler.OpenAssociatedProgram(string path) {
try{ try {
using(Process.Start(new ProcessStartInfo{ using (Process.Start(new ProcessStartInfo {
FileName = path, FileName = path,
ErrorDialog = true ErrorDialog = true
})){} })) {}
}catch(Exception e){ } catch (Exception e) {
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e); Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e);
} }
} }
void IAppSystemHandler.OpenFileExplorer(string path){ void IAppSystemHandler.OpenFileExplorer(string path) {
if (File.Exists(path)){ if (File.Exists(path)) {
using(Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")){} using (Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")) {}
} }
else if (Directory.Exists(path)){ else if (Directory.Exists(path)) {
using(Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')){} using (Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')) {}
} }
} }
} }
} }

View File

@@ -1,41 +1,78 @@
using System.IO; using System.Collections.Generic;
using System.IO;
using CefSharp; using CefSharp;
using TweetDuck.Utils;
using TweetLib.Core.Browser; using TweetLib.Core.Browser;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Adapters{ namespace TweetDuck.Browser.Adapters {
sealed class CefScriptExecutor : IScriptExecutor{ sealed class CefScriptExecutor : IScriptExecutor {
private readonly IWebBrowser browser; private readonly IWebBrowser browser;
public CefScriptExecutor(IWebBrowser browser){ public CefScriptExecutor(IWebBrowser browser) {
this.browser = browser; this.browser = browser;
} }
public void RunFunction(string name, params object[] args){ public void RunFunction(string name, params object[] args) {
browser.ExecuteScriptAsync(name, args); browser.ExecuteJsAsync(name, args);
} }
public void RunScript(string identifier, string script){ public void RunScript(string identifier, string script) {
using IFrame frame = browser.GetMainFrame(); using IFrame frame = browser.GetMainFrame();
RunScript(frame, script, identifier); RunScript(frame, script, identifier);
} }
public bool RunFile(string file){ public void RunBootstrap(string moduleNamespace) {
using IFrame frame = browser.GetMainFrame(); using IFrame frame = browser.GetMainFrame();
return RunFile(frame, file); RunBootstrap(frame, moduleNamespace);
} }
// Helpers // Helpers
public static void RunScript(IFrame frame, string script, string identifier){ public static void RunScript(IFrame frame, string script, string identifier) {
if (script != null){ if (script != null) {
frame.ExecuteJavaScriptAsync(script, identifier, 1); frame.ExecuteJavaScriptAsync(script, identifier, 1);
} }
} }
public static bool RunFile(IFrame frame, string file){ public static void RunBootstrap(IFrame frame, string moduleNamespace) {
string script = Program.Resources.Load(file); string script = GetBootstrapScript(moduleNamespace, includeStylesheets: true);
RunScript(frame, script, "root:" + Path.GetFileNameWithoutExtension(file));
return script != null; 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;
}
}
} }

View File

@@ -3,36 +3,37 @@ using TweetDuck.Configuration;
using TweetLib.Core; using TweetLib.Core;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Bridge{ namespace TweetDuck.Browser.Bridge {
static class PropertyBridge{ static class PropertyBridge {
public enum Environment{ public enum Environment {
Browser, Notification Browser,
} Notification
}
public static string GenerateScript(Environment environment){ public static string GenerateScript(Environment environment) {
static string Bool(bool value) => value ? "true;" : "false;"; static string Bool(bool value) => value ? "true;" : "false;";
static string Str(string value) => $"\"{value}\";"; static string Str(string value) => $"\"{value}\";";
UserConfig config = Program.Config.User; UserConfig config = Program.Config.User;
StringBuilder build = new StringBuilder(384).Append("(function(x){"); StringBuilder build = new StringBuilder(384).Append("(function(x){");
build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover)); build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
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));
build.Append("x.muteNotifications=").Append(Bool(config.MuteNotifications));
build.Append("x.notificationMediaPreviews=").Append(Bool(config.NotificationMediaPreviews));
build.Append("x.translationTarget=").Append(Str(config.TranslationTarget));
build.Append("x.firstDayOfWeek=").Append(config.CalendarFirstDay == -1 ? LocaleUtils.GetJQueryDayOfWeek(Lib.Culture.DateTimeFormat.FirstDayOfWeek) : config.CalendarFirstDay);
}
if (environment == Environment.Notification){ if (environment == Environment.Browser) {
build.Append("x.skipOnLinkClick=").Append(Bool(config.NotificationSkipOnLinkClick)); build.Append("x.focusDmInput=").Append(Bool(config.FocusDmInput));
} build.Append("x.openSearchInFirstColumn=").Append(Bool(config.OpenSearchInFirstColumn));
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(config.KeepLikeFollowDialogsOpen));
return build.Append("})(window.$TDX=window.$TDX||{});if(window.TDGF_onPropertiesUpdated)window.TDGF_onPropertiesUpdated()").ToString(); build.Append("x.muteNotifications=").Append(Bool(config.MuteNotifications));
} build.Append("x.notificationMediaPreviews=").Append(Bool(config.NotificationMediaPreviews));
} build.Append("x.translationTarget=").Append(Str(config.TranslationTarget));
build.Append("x.firstDayOfWeek=").Append(config.CalendarFirstDay == -1 ? LocaleUtils.GetJQueryDayOfWeek(Lib.Culture.DateTimeFormat.FirstDayOfWeek) : config.CalendarFirstDay);
}
if (environment == Environment.Notification) {
build.Append("x.skipOnLinkClick=").Append(Bool(config.NotificationSkipOnLinkClick));
}
return build.Append("})(window.$TDX=window.$TDX||{});if(window.TDGF_onPropertiesUpdated)window.TDGF_onPropertiesUpdated()").ToString();
}
}
} }

View File

@@ -12,150 +12,154 @@ using TweetDuck.Utils;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Bridge{ namespace TweetDuck.Browser.Bridge {
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
class TweetDeckBridge{ class TweetDeckBridge {
public static void ResetStaticProperties(){ public static void ResetStaticProperties() {
FormNotificationBase.FontSize = null; FormNotificationBase.FontSize = null;
FormNotificationBase.HeadLayout = null; FormNotificationBase.HeadLayout = null;
} }
private readonly FormBrowser form; private readonly FormBrowser form;
private readonly FormNotificationMain notification; private readonly FormNotificationMain notification;
protected TweetDeckBridge(FormBrowser form, FormNotificationMain notification){ private TweetDeckBridge(FormBrowser form, FormNotificationMain notification) {
this.form = form; this.form = form;
this.notification = notification; this.notification = notification;
} }
// Browser only // Browser only
public sealed class Browser : TweetDeckBridge{ public sealed class Browser : TweetDeckBridge {
public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification){} public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification) {}
public void OpenContextMenu(){ public void OnModulesLoaded(string moduleNamespace) {
form.InvokeAsyncSafe(form.OpenContextMenu); form.InvokeAsyncSafe(() => form.OnModulesLoaded(moduleNamespace));
} }
public void OpenProfileImport(){ public void OpenContextMenu() {
form.InvokeAsyncSafe(form.OpenProfileImport); form.InvokeAsyncSafe(form.OpenContextMenu);
} }
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide, allowDataCollection));
}
public void LoadNotificationLayout(string fontSize, string headLayout){ public void OpenProfileImport() {
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(form.OpenProfileImport);
FormNotificationBase.FontSize = fontSize; }
FormNotificationBase.HeadLayout = headLayout;
});
}
public void SetRightClickedLink(string type, string url){ public void OnIntroductionClosed(bool showGuide) {
ContextMenuBase.CurrentInfo.SetLink(type, url); form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide));
} }
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){ public void LoadNotificationLayout(string fontSize, string headLayout) {
ContextMenuBase.CurrentInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages); form.InvokeAsyncSafe(() => {
} FormNotificationBase.FontSize = fontSize;
FormNotificationBase.HeadLayout = headLayout;
});
}
public void DisplayTooltip(string text){ public void SetRightClickedLink(string type, string url) {
form.InvokeAsyncSafe(() => form.DisplayTooltip(text)); ContextMenuBase.CurrentInfo.SetLink(type, url);
} }
}
// Notification only 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 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)); form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
} }
}
public void LoadNextNotification(){ // Notification only
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
}
public void ShowTweetDetail(){ public sealed class Notification : TweetDeckBridge {
notification.InvokeAsyncSafe(notification.ShowTweetDetail); public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification) {}
}
}
// Global public void DisplayTooltip(string text) {
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
}
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){ public void LoadNextNotification() {
notification.InvokeAsyncSafe(() => { notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
form.OnTweetNotification(); }
notification.ShowNotification(new DesktopNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
});
}
public void OnTweetSound(){ public void ShowTweetDetail() {
form.InvokeAsyncSafe(() => { notification.InvokeAsyncSafe(notification.ShowTweetDetail);
form.OnTweetNotification(); }
form.OnTweetSound(); }
});
}
public void ScreenshotTweet(string html, int width){ // Global
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
}
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay){ public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl) {
form.InvokeAsyncSafe(() => form.PlayVideo(videoUrl, tweetUrl, username, callShowOverlay)); notification.InvokeAsyncSafe(() => {
} form.OnTweetNotification();
notification.ShowNotification(new DesktopNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
public void StopVideo(){ });
form.InvokeAsyncSafe(form.StopVideo); }
}
public void FixClipboard(){ public void OnTweetSound() {
form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles); form.InvokeAsyncSafe(() => {
} form.OnTweetNotification();
form.OnTweetSound();
});
}
public void OpenBrowser(string url){ public void ScreenshotTweet(string html, int width) {
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url)); form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
} }
public void MakeGetRequest(string url, IJavascriptCallback onSuccess, IJavascriptCallback onError){ public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
Task.Run(async () => { form.InvokeAsyncSafe(() => form.PlayVideo(videoUrl, tweetUrl, username, callShowOverlay));
var client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla); }
try{ public void StopVideo() {
var result = await client.DownloadStringTaskAsync(url); form.InvokeAsyncSafe(form.StopVideo);
await onSuccess.ExecuteAsync(result); }
}catch(Exception e){
await onError.ExecuteAsync(e.Message);
}finally{
onSuccess.Dispose();
onError.Dispose();
client.Dispose();
}
});
}
public int GetIdleSeconds(){ public void FixClipboard() {
return NativeMethods.GetIdleSeconds(); form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles);
} }
public void Alert(string type, string contents){ public void OpenBrowser(string url) {
MessageBoxIcon icon = type switch{ form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
"error" => MessageBoxIcon.Error, }
"warning" => MessageBoxIcon.Warning,
"info" => MessageBoxIcon.Information,
_ => MessageBoxIcon.None
};
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK); public void MakeGetRequest(string url, IJavascriptCallback onSuccess, IJavascriptCallback onError) {
} Task.Run(async () => {
var client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
public void CrashDebug(string message){ try {
#if DEBUG var result = await client.DownloadStringTaskAsync(url);
System.Diagnostics.Debug.WriteLine(message); await onSuccess.ExecuteAsync(result);
System.Diagnostics.Debugger.Break(); } catch (Exception e) {
#endif await onError.ExecuteAsync(e.Message);
} } finally {
} onSuccess.Dispose();
onError.Dispose();
client.Dispose();
}
});
}
public int GetIdleSeconds() {
return NativeMethods.GetIdleSeconds();
}
public void Alert(string type, string contents) {
MessageBoxIcon icon = type switch {
"error" => MessageBoxIcon.Error,
"warning" => MessageBoxIcon.Warning,
"info" => MessageBoxIcon.Information,
_ => MessageBoxIcon.None
};
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
}
public void CrashDebug(string message) {
#if DEBUG
System.Diagnostics.Debug.WriteLine(message);
System.Diagnostics.Debugger.Break();
#endif
}
}
} }

View File

@@ -4,62 +4,62 @@ using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Core.Systems.Updates; using TweetLib.Core.Systems.Updates;
namespace TweetDuck.Browser.Bridge{ namespace TweetDuck.Browser.Bridge {
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
class UpdateBridge{ class UpdateBridge {
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
private readonly Control sync; private readonly Control sync;
private UpdateInfo nextUpdate = null; private UpdateInfo nextUpdate = null;
public event EventHandler<UpdateInfo> UpdateAccepted; public event EventHandler<UpdateInfo> UpdateAccepted;
public event EventHandler<UpdateInfo> UpdateDismissed; public event EventHandler<UpdateInfo> UpdateDismissed;
public UpdateBridge(UpdateHandler updates, Control sync){ public UpdateBridge(UpdateHandler updates, Control sync) {
this.sync = sync; this.sync = sync;
this.updates = updates; this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished; this.updates.CheckFinished += updates_CheckFinished;
} }
internal void Cleanup(){ internal void Cleanup() {
updates.CheckFinished -= updates_CheckFinished; updates.CheckFinished -= updates_CheckFinished;
nextUpdate?.DeleteInstaller(); 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; UpdateInfo foundUpdate = e.Result.HasValue ? e.Result.Value : null;
if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)){ if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)) {
nextUpdate.DeleteInstaller(); nextUpdate.DeleteInstaller();
} }
nextUpdate = foundUpdate; nextUpdate = foundUpdate;
} }
private void HandleInteractionEvent(EventHandler<UpdateInfo> eventHandler){ private void HandleInteractionEvent(EventHandler<UpdateInfo> eventHandler) {
UpdateInfo tmpInfo = nextUpdate; UpdateInfo tmpInfo = nextUpdate;
if (tmpInfo != null){ if (tmpInfo != null) {
sync.InvokeAsyncSafe(() => eventHandler?.Invoke(this, tmpInfo)); sync.InvokeAsyncSafe(() => eventHandler?.Invoke(this, tmpInfo));
} }
} }
// Bridge methods // Bridge methods
public void TriggerUpdateCheck(){ public void TriggerUpdateCheck() {
updates.Check(false); updates.Check(false);
} }
public void OnUpdateAccepted(){ public void OnUpdateAccepted() {
HandleInteractionEvent(UpdateAccepted); HandleInteractionEvent(UpdateAccepted);
} }
public void OnUpdateDismissed(){ public void OnUpdateDismissed() {
HandleInteractionEvent(UpdateDismissed); HandleInteractionEvent(UpdateDismissed);
nextUpdate?.DeleteInstaller(); nextUpdate?.DeleteInstaller();
nextUpdate = null; nextUpdate = null;
} }
} }
} }

View File

@@ -2,160 +2,165 @@
using CefSharp; using CefSharp;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Data{ namespace TweetDuck.Browser.Data {
sealed class ContextInfo{ sealed class ContextInfo {
private LinkInfo link; private LinkInfo link;
private ChirpInfo? chirp; private ChirpInfo? chirp;
public ContextInfo(){
Reset();
}
public void SetLink(string type, string url){
link = string.IsNullOrEmpty(url) ? null : new LinkInfo(type, url);
}
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){ public ContextInfo() {
chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?)null : new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages); Reset();
} }
public ContextData Reset(){ public void SetLink(string type, string url) {
link = null; link = string.IsNullOrEmpty(url) ? null : new LinkInfo(type, url);
chirp = null; }
return ContextData.Empty;
}
public ContextData Create(IContextMenuParams parameters){ public void SetChirp(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
ContextData.Builder builder = new ContextData.Builder(); chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?) null : new ChirpInfo(columnId, chirpId, tweetUrl, quoteUrl, chirpAuthors, chirpImages);
builder.AddContext(parameters); }
if (link != null){ public ContextData Reset() {
builder.AddOverride(link.Type, link.Url); link = null;
} chirp = null;
return ContextData.Empty;
if (chirp.HasValue){ }
builder.AddChirp(chirp.Value);
}
return builder.Build(); public ContextData Create(IContextMenuParams parameters) {
} ContextData.Builder builder = new ContextData.Builder();
builder.AddContext(parameters);
// Data structures if (link != null) {
builder.AddOverride(link.Type, link.Url);
}
private sealed class LinkInfo{ if (chirp.HasValue) {
public string Type { get; } builder.AddChirp(chirp.Value);
public string Url { get; } }
public LinkInfo(string type, string url){ return builder.Build();
this.Type = type; }
this.Url = url;
}
}
public struct ChirpInfo{ // Data structures
public string TweetUrl { get; }
public string QuoteUrl { get; }
public string[] Authors => chirpAuthors?.Split(';') ?? StringUtils.EmptyArray; private sealed class LinkInfo {
public string[] Images => chirpImages?.Split(';') ?? StringUtils.EmptyArray; public string Type { get; }
public string Url { get; }
private readonly string chirpAuthors;
private readonly string chirpImages;
public ChirpInfo(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){ public LinkInfo(string type, string url) {
this.TweetUrl = tweetUrl; this.Type = type;
this.QuoteUrl = quoteUrl; this.Url = url;
this.chirpAuthors = chirpAuthors; }
this.chirpImages = chirpImages; }
}
}
// Constructed context public readonly struct ChirpInfo {
public string ColumnId { get; }
public string ChirpId { get; }
[Flags] public string TweetUrl { get; }
public enum ContextType{ public string QuoteUrl { get; }
Unknown = 0,
Link = 0b0001,
Image = 0b0010,
Video = 0b0100,
Chirp = 0b1000
}
public sealed class ContextData{ public string[] Authors => chirpAuthors?.Split(';') ?? StringUtils.EmptyArray;
public static readonly ContextData Empty = new Builder().Build(); public string[] Images => chirpImages?.Split(';') ?? StringUtils.EmptyArray;
public ContextType Types { get; } private readonly string chirpAuthors;
private readonly string chirpImages;
public string LinkUrl { get; } public ChirpInfo(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
public string UnsafeLinkUrl { get; } this.ColumnId = columnId;
public string MediaUrl { get; } this.ChirpId = chirpId;
this.TweetUrl = tweetUrl;
this.QuoteUrl = quoteUrl;
this.chirpAuthors = chirpAuthors;
this.chirpImages = chirpImages;
}
}
public ChirpInfo Chirp { get; } // Constructed context
public ContextData(ContextType types, string linkUrl, string unsafeLinkUrl, string mediaUrl, ChirpInfo chirp){ [Flags]
Types = types; public enum ContextType {
LinkUrl = linkUrl; Unknown = 0,
UnsafeLinkUrl = unsafeLinkUrl; Link = 0b0001,
MediaUrl = mediaUrl; Image = 0b0010,
Chirp = chirp; Video = 0b0100,
} Chirp = 0b1000
}
public sealed class Builder{ public sealed class ContextData {
private ContextType types = ContextType.Unknown; public static readonly ContextData Empty = new Builder().Build();
private string linkUrl = string.Empty; public ContextType Types { get; }
private string unsafeLinkUrl = string.Empty;
private string mediaUrl = string.Empty;
private ChirpInfo chirp = default; public string LinkUrl { get; }
public string UnsafeLinkUrl { get; }
public string MediaUrl { get; }
public void AddContext(IContextMenuParams parameters){ public ChirpInfo Chirp { get; }
ContextMenuType flags = parameters.TypeFlags;
if (flags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){ private ContextData(ContextType types, string linkUrl, string unsafeLinkUrl, string mediaUrl, ChirpInfo chirp) {
types |= ContextType.Image; Types = types;
types &= ~ContextType.Video; LinkUrl = linkUrl;
mediaUrl = parameters.SourceUrl; UnsafeLinkUrl = unsafeLinkUrl;
} MediaUrl = mediaUrl;
Chirp = chirp;
}
if (flags.HasFlag(ContextMenuType.Link)){ public sealed class Builder {
types |= ContextType.Link; private ContextType types = ContextType.Unknown;
linkUrl = parameters.LinkUrl;
unsafeLinkUrl = parameters.UnfilteredLinkUrl;
}
}
public void AddOverride(string type, string url){ private string linkUrl = string.Empty;
switch(type){ private string unsafeLinkUrl = string.Empty;
case "link": private string mediaUrl = string.Empty;
types |= ContextType.Link;
linkUrl = url;
unsafeLinkUrl = url;
break;
case "image": private ChirpInfo chirp = default;
types |= ContextType.Image;
types &= ~(ContextType.Video | ContextType.Link);
mediaUrl = url;
break;
case "video": public void AddContext(IContextMenuParams parameters) {
types |= ContextType.Video; ContextMenuType flags = parameters.TypeFlags;
types &= ~(ContextType.Image | ContextType.Link);
mediaUrl = url;
break;
}
}
public void AddChirp(ChirpInfo chirp){ if (flags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) {
this.types |= ContextType.Chirp; types |= ContextType.Image;
this.chirp = chirp; types &= ~ContextType.Video;
} mediaUrl = parameters.SourceUrl;
}
public ContextData Build(){ if (flags.HasFlag(ContextMenuType.Link)) {
return new ContextData(types, linkUrl, unsafeLinkUrl, mediaUrl, chirp); types |= ContextType.Link;
} linkUrl = parameters.LinkUrl;
} unsafeLinkUrl = parameters.UnfilteredLinkUrl;
} }
} }
public void AddOverride(string type, string url) {
switch (type) {
case "link":
types |= ContextType.Link;
linkUrl = url;
unsafeLinkUrl = url;
break;
case "image":
types |= ContextType.Image;
types &= ~(ContextType.Video | ContextType.Link);
mediaUrl = url;
break;
case "video":
types |= ContextType.Video;
types &= ~(ContextType.Image | ContextType.Link);
mediaUrl = url;
break;
}
}
public void AddChirp(ChirpInfo chirp) {
this.types |= ContextType.Chirp;
this.chirp = chirp;
}
public ContextData Build() {
return new ContextData(types, linkUrl, unsafeLinkUrl, mediaUrl, chirp);
}
}
}
}
} }

View File

@@ -2,37 +2,49 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using CefSharp; using CefSharp;
namespace TweetDuck.Browser.Data{ namespace TweetDuck.Browser.Data {
sealed class ResourceHandlers{ sealed class ResourceHandlers {
private readonly ConcurrentDictionary<string, IResourceHandler> handlers = new ConcurrentDictionary<string, IResourceHandler>(StringComparer.OrdinalIgnoreCase); 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); return handlers.ContainsKey(request.Url);
} }
public IResourceHandler GetHandler(IRequest request){ public IResourceHandler GetHandler(IRequest request) {
return handlers.TryGetValue(request.Url, out var handler) ? handler : null; return handlers.TryGetValue(request.Url, out var factory) ? factory() : null;
} }
public bool Register(string url, IResourceHandler handler){ public bool Register(string url, Func<IResourceHandler> factory) {
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){ if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
handlers.AddOrUpdate(uri.AbsoluteUri, handler, (key, prev) => handler); handlers.AddOrUpdate(uri.AbsoluteUri, factory, (key, prev) => factory);
return true; return true;
} }
return false; return false;
} }
public bool Register(ResourceLink link){ public bool Register(ResourceLink link) {
return Register(link.Url, link.Handler); return Register(link.Url, link.Factory);
} }
public bool Unregister(string url){ public bool Unregister(string url) {
return handlers.TryRemove(url, out IResourceHandler _); return handlers.TryRemove(url, out _);
} }
public bool Unregister(ResourceLink link){ public bool Unregister(ResourceLink link) {
return Unregister(link.Url); return Unregister(link.Url);
} }
}
public static Func<IResourceHandler> ForString(string str) {
return () => ResourceHandler.FromString(str);
}
public static Func<IResourceHandler> ForString(string str, string mimeType) {
return () => ResourceHandler.FromString(str, mimeType: mimeType);
}
public static Func<IResourceHandler> ForBytes(byte[] bytes, string mimeType) {
return () => ResourceHandler.FromByteArray(bytes, mimeType);
}
}
} }

View File

@@ -1,13 +1,14 @@
using CefSharp; using System;
using CefSharp;
namespace TweetDuck.Browser.Data{ namespace TweetDuck.Browser.Data {
sealed class ResourceLink{ sealed class ResourceLink {
public string Url { get; } public string Url { get; }
public IResourceHandler Handler { get; } public Func<IResourceHandler> Factory { get; }
public ResourceLink(string url, IResourceHandler handler){ public ResourceLink(string url, Func<IResourceHandler> factory) {
this.Url = url; this.Url = url;
this.Handler = handler; this.Factory = factory;
} }
} }
} }

View File

@@ -4,39 +4,39 @@ using TweetDuck.Controls;
using TweetLib.Core.Serialization.Converters; using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Data{ namespace TweetDuck.Browser.Data {
sealed class WindowState{ sealed class WindowState {
private Rectangle rect; private Rectangle rect;
private bool isMaximized; private bool isMaximized;
public void Save(Form form){ public void Save(Form form) {
rect = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds; rect = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds;
isMaximized = form.WindowState == FormWindowState.Maximized; isMaximized = form.WindowState == FormWindowState.Maximized;
} }
public void Restore(Form form, bool firstTimeFullscreen){ public void Restore(Form form, bool firstTimeFullscreen) {
if (rect != Rectangle.Empty){ if (rect != Rectangle.Empty) {
form.DesktopBounds = rect; form.DesktopBounds = rect;
form.WindowState = isMaximized ? FormWindowState.Maximized : FormWindowState.Normal; 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.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
form.WindowState = FormWindowState.Maximized; form.WindowState = FormWindowState.Maximized;
Save(form); 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}", ConvertToString = value => $"{(value.isMaximized ? 'M' : '_')}{value.rect.X} {value.rect.Y} {value.rect.Width} {value.rect.Height}",
ConvertToObject = value => { ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value.Substring(1), ' '); int[] elements = StringUtils.ParseInts(value.Substring(1), ' ');
return new WindowState{ return new WindowState {
rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]), rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]),
isMaximized = value[0] == 'M' isMaximized = value[0] == 'M'
}; };
} }
}; };
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Drawing;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
@@ -10,235 +11,220 @@ using TweetDuck.Configuration;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Management; using TweetDuck.Management;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler {
public static ContextInfo CurrentInfo { get; } = new ContextInfo(); public static ContextInfo CurrentInfo { get; } = new ContextInfo();
protected static UserConfig Config => Program.Config.User; protected static UserConfig Config => Program.Config.User;
private static ImageQuality ImageQuality => Config.TwitterImageQuality; 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;
protected ContextInfo.ContextData Context { get; private set; }
private readonly AnalyticsFile.IProvider analytics; 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 ContextMenuBase(AnalyticsFile.IProvider analytics){ protected ContextInfo.ContextData Context { get; private set; }
this.analytics = analytics;
}
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading){ if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading) {
Context = CurrentInfo.Reset(); Context = CurrentInfo.Reset();
} }
else{ else {
Context = CurrentInfo.Create(parameters); Context = CurrentInfo.Create(parameters);
} }
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
model.AddItem(MenuSearchInBrowser, "Search in browser");
model.AddSeparator();
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
model.AddSeparator();
}
static string TextOpen(string name) => "Open " + name + " in browser"; if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)) {
static string TextCopy(string name) => "Copy " + name + " address"; model.AddItem(MenuSearchInBrowser, "Search in browser");
static string TextSave(string name) => "Save " + name + " as..."; model.AddSeparator();
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){ model.AddSeparator();
if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){ }
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
model.AddItem(MenuCopyUsername, "Copy account username");
}
else{
model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
}
model.AddSeparator(); static string TextOpen(string name) => "Open " + name + " in browser";
} static string TextCopy(string name) => "Copy " + name + " address";
static string TextSave(string name) => "Save " + name + " as...";
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)){ if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)) {
model.AddItem(MenuOpenMediaUrl, TextOpen("video")); if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)) {
model.AddItem(MenuCopyMediaUrl, TextCopy("video")); model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
model.AddItem(MenuSaveMedia, TextSave("video")); model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
model.AddSeparator(); model.AddItem(MenuCopyUsername, "Copy account username");
} }
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != FormNotificationBase.AppLogo.Url){ else {
model.AddItem(MenuViewImage, "View image in photo viewer"); model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
model.AddItem(MenuOpenMediaUrl, TextOpen("image")); model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
model.AddItem(MenuCopyMediaUrl, TextCopy("image")); }
model.AddItem(MenuCopyImage, "Copy image");
model.AddItem(MenuSaveMedia, TextSave("image"));
if (Context.Chirp.Images.Length > 1){ model.AddSeparator();
model.AddItem(MenuSaveTweetImages, TextSave("all images")); }
}
model.AddSeparator(); if (Context.Types.HasFlag(ContextInfo.ContextType.Video)) {
} model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
} model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
model.AddItem(MenuSaveMedia, TextSave("video"));
model.AddSeparator();
}
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != 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"));
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ if (Context.Chirp.Images.Length > 1) {
Control control = browserControl.AsControl(); model.AddItem(MenuSaveTweetImages, TextSave("all images"));
}
switch(commandId){ model.AddSeparator();
case MenuOpenLinkUrl: }
OpenBrowser(control, Context.LinkUrl); }
break;
case MenuCopyLinkUrl: public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
SetClipboardText(control, Context.UnsafeLinkUrl); Control control = browserControl.AsControl();
break;
case MenuCopyUsername: { switch (commandId) {
string url = Context.UnsafeLinkUrl; case MenuOpenLinkUrl:
Match match = TwitterUrls.RegexAccount.Match(url); OpenBrowser(control, Context.LinkUrl);
break;
SetClipboardText(control, match.Success ? match.Groups[1].Value : url); case MenuCopyLinkUrl:
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger); SetClipboardText(control, Context.UnsafeLinkUrl);
break; break;
}
case MenuOpenMediaUrl: case MenuCopyUsername: {
OpenBrowser(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality)); string url = Context.UnsafeLinkUrl;
break; Match match = TwitterUrls.RegexAccount.Match(url);
case MenuCopyMediaUrl: SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
SetClipboardText(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality)); break;
break; }
case MenuCopyImage: { case MenuOpenMediaUrl:
string url = Context.MediaUrl; OpenBrowser(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
break;
control.InvokeAsyncSafe(() => { case MenuCopyMediaUrl:
TwitterUtils.CopyImage(url, ImageQuality); SetClipboardText(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
}); break;
break;
}
case MenuViewImage: { case MenuCopyImage: {
string url = Context.MediaUrl; string url = Context.MediaUrl;
control.InvokeAsyncSafe(() => { control.InvokeAsyncSafe(() => { TwitterUtils.CopyImage(url, ImageQuality); });
TwitterUtils.ViewImage(url, ImageQuality);
analytics.AnalyticsFile.ViewedImages.Trigger();
});
break;
}
case MenuSaveMedia: { break;
bool isVideo = Context.Types.HasFlag(ContextInfo.ContextType.Video); }
string url = Context.MediaUrl;
string username = Context.Chirp.Authors.LastOrDefault();
control.InvokeAsyncSafe(() => {
if (isVideo){
TwitterUtils.DownloadVideo(url, username);
analytics.AnalyticsFile.DownloadedVideos.Trigger();
}
else{
TwitterUtils.DownloadImage(url, username, ImageQuality);
analytics.AnalyticsFile.DownloadedImages.Trigger();
}
});
break; case MenuViewImage: {
} string url = Context.MediaUrl;
case MenuSaveTweetImages: { control.InvokeAsyncSafe(() => {
string[] urls = Context.Chirp.Images; TwitterUtils.ViewImage(url, ImageQuality);
string username = Context.Chirp.Authors.LastOrDefault(); });
control.InvokeAsyncSafe(() => {
TwitterUtils.DownloadImages(urls, username, ImageQuality);
analytics.AnalyticsFile.DownloadedImages.Trigger();
});
break; break;
} }
case MenuReadApplyROT13: case MenuSaveMedia: {
string selection = parameters.SelectionText; bool isVideo = Context.Types.HasFlag(ContextInfo.ContextType.Video);
control.InvokeAsyncSafe(() => FormMessage.Information("ROT13", StringUtils.ConvertRot13(selection), FormMessage.OK)); string url = Context.MediaUrl;
control.InvokeAsyncSafe(analytics.AnalyticsFile.UsedROT13.Trigger); string username = Context.Chirp.Authors.LastOrDefault();
return true;
case MenuSearchInBrowser: control.InvokeAsyncSafe(() => {
string query = parameters.SelectionText; if (isVideo) {
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query)); TwitterUtils.DownloadVideo(url, username);
DeselectAll(frame); }
break; else {
TwitterUtils.DownloadImage(url, username, ImageQuality);
case MenuOpenDevTools: }
browserControl.OpenDevToolsCustom(); });
break;
}
return false; break;
} }
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){ case MenuSaveTweetImages: {
Context = CurrentInfo.Reset(); string[] urls = Context.Chirp.Images;
} string username = Context.Chirp.Authors.LastOrDefault();
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){ control.InvokeAsyncSafe(() => {
return false; TwitterUtils.DownloadImages(urls, username, ImageQuality);
} });
protected static void DeselectAll(IFrame frame){ break;
CefScriptExecutor.RunScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect"); }
}
protected static void OpenBrowser(Control control, string url){ case MenuReadApplyROT13:
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url)); string selection = parameters.SelectionText;
} control.InvokeAsyncSafe(() => FormMessage.Information("ROT13", StringUtils.ConvertRot13(selection), FormMessage.OK));
return true;
protected static void SetClipboardText(Control control, string text){ case MenuSearchInBrowser:
control.InvokeAsyncSafe(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText)); string query = parameters.SelectionText;
} control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
DeselectAll(frame);
break;
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){ case MenuOpenDevTools:
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser) + 1, insertCommand, insertLabel); browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
} break;
}
protected static void AddDebugMenuItems(IMenuModel model){
if (BrowserUtils.HasDevTools){
AddSeparator(model);
model.AddItem(MenuOpenDevTools, "Open dev tools");
}
}
protected static void RemoveSeparatorIfLast(IMenuModel model){ return false;
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator){ }
model.RemoveAt(model.Count - 1);
}
}
protected static void AddSeparator(IMenuModel model){ public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate Context = CurrentInfo.Reset();
model.AddSeparator(); }
}
} public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
} return false;
}
protected static void DeselectAll(IFrame frame) {
CefScriptExecutor.RunScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
}
protected static void OpenBrowser(Control control, string url) {
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
}
protected static void SetClipboardText(Control control, string text) {
control.InvokeAsyncSafe(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
}
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel) {
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser) + 1, insertCommand, insertLabel);
}
protected static void AddDebugMenuItems(IMenuModel model) {
if (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) {
model.RemoveAt(model.Count - 1);
}
}
protected static void AddSeparator(IMenuModel model) {
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
model.AddSeparator();
}
}
}
} }

View File

@@ -4,176 +4,176 @@ using TweetDuck.Browser.Data;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class ContextMenuBrowser : ContextMenuBase{ sealed class ContextMenuBrowser : ContextMenuBase {
private const CefMenuCommand MenuGlobal = (CefMenuCommand)26600; private const CefMenuCommand MenuGlobal = (CefMenuCommand) 26600;
private const CefMenuCommand MenuMute = (CefMenuCommand)26601; private const CefMenuCommand MenuMute = (CefMenuCommand) 26601;
private const CefMenuCommand MenuSettings = (CefMenuCommand)26602; private const CefMenuCommand MenuSettings = (CefMenuCommand) 26602;
private const CefMenuCommand MenuPlugins = (CefMenuCommand)26003; private const CefMenuCommand MenuPlugins = (CefMenuCommand) 26003;
private const CefMenuCommand MenuAbout = (CefMenuCommand)26604; private const CefMenuCommand MenuAbout = (CefMenuCommand) 26604;
private const CefMenuCommand MenuOpenTweetUrl = (CefMenuCommand)26610;
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26611;
private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
private const CefMenuCommand MenuWriteApplyROT13 = (CefMenuCommand)26615;
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand)26616;
private const string TitleReloadBrowser = "Reload browser"; private const CefMenuCommand MenuOpenTweetUrl = (CefMenuCommand) 26610;
private const string TitleMuteNotifications = "Mute notifications"; private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand) 26611;
private const string TitleSettings = "Options"; private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand) 26612;
private const string TitlePlugins = "Plugins"; private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand) 26613;
private const string TitleAboutProgram = "About " + Program.BrandName; private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand) 26614;
private const CefMenuCommand MenuWriteApplyROT13 = (CefMenuCommand) 26615;
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand) 26616;
private readonly FormBrowser form; private const string TitleReloadBrowser = "Reload browser";
private const string TitleMuteNotifications = "Mute notifications";
public ContextMenuBrowser(FormBrowser form) : base(form){ private const string TitleSettings = "Options";
this.form = form; private const string TitlePlugins = "Plugins";
} private const string TitleAboutProgram = "About " + Program.BrandName;
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ private readonly FormBrowser form;
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
model.Remove(CefMenuCommand.Back); public ContextMenuBrowser(FormBrowser form) {
model.Remove(CefMenuCommand.Forward); this.form = form;
model.Remove(CefMenuCommand.Print); }
model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model);
if (isSelecting){ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
if (isEditing){ bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
model.AddSeparator(); bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
}
model.AddSeparator(); model.Remove(CefMenuCommand.Back);
} model.Remove(CefMenuCommand.Forward);
model.Remove(CefMenuCommand.Print);
model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model);
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); if (isSelecting) {
if (isEditing) {
model.AddSeparator();
model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
}
if (isSelecting && !isEditing && TwitterUrls.IsTweetDeck(frame.Url)){ model.AddSeparator();
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column"); }
}
if (Context.Types.HasFlag(ContextInfo.ContextType.Chirp) && !isSelecting && !isEditing){ base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
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 (isSelecting && !isEditing && TwitterUrls.IsTweetDeck(frame.Url)) {
model.AddSeparator(); InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser"); }
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
model.AddSeparator(); 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 (!isSelecting && !isEditing){ if (!string.IsNullOrEmpty(Context.Chirp.QuoteUrl)) {
AddSeparator(model); model.AddSeparator();
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName); model.AddSeparator();
}
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
globalMenu.AddSeparator();
globalMenu.AddItem(MenuSettings, TitleSettings); if (!isSelecting && !isEditing) {
globalMenu.AddItem(MenuPlugins, TitlePlugins); AddSeparator(model);
globalMenu.AddItem(MenuAbout, TitleAboutProgram);
AddDebugMenuItems(globalMenu);
}
RemoveSeparatorIfLast(model); IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
form.InvokeAsyncSafe(form.AnalyticsFile.BrowserContextMenus.Trigger);
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){ globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
return true; globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
} globalMenu.AddSeparator();
switch(commandId){ globalMenu.AddItem(MenuSettings, TitleSettings);
case CefMenuCommand.Reload: globalMenu.AddItem(MenuPlugins, TitlePlugins);
form.InvokeAsyncSafe(form.ReloadToTweetDeck); globalMenu.AddItem(MenuAbout, TitleAboutProgram);
return true;
case MenuSettings: AddDebugMenuItems(globalMenu);
form.InvokeAsyncSafe(form.OpenSettings); }
return true;
case MenuAbout: RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(form.OpenAbout); }
return true;
case MenuPlugins: public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
form.InvokeAsyncSafe(form.OpenPlugins); if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
return true; return true;
}
case MenuMute: switch (commandId) {
form.InvokeAsyncSafe(ToggleMuteNotifications); case CefMenuCommand.Reload:
return true; form.InvokeAsyncSafe(form.ReloadToTweetDeck);
return true;
case MenuOpenTweetUrl: case MenuSettings:
OpenBrowser(form, Context.Chirp.TweetUrl); form.InvokeAsyncSafe(form.OpenSettings);
return true; return true;
case MenuCopyTweetUrl: case MenuAbout:
SetClipboardText(form, Context.Chirp.TweetUrl); form.InvokeAsyncSafe(form.OpenAbout);
return true; return true;
case MenuScreenshotTweet: case MenuPlugins:
form.InvokeAsyncSafe(form.TriggerTweetScreenshot); form.InvokeAsyncSafe(form.OpenPlugins);
return true; return true;
case MenuOpenQuotedTweetUrl: case MenuMute:
OpenBrowser(form, Context.Chirp.QuoteUrl); form.InvokeAsyncSafe(ToggleMuteNotifications);
return true; return true;
case MenuCopyQuotedTweetUrl: case MenuOpenTweetUrl:
SetClipboardText(form, Context.Chirp.QuoteUrl); OpenBrowser(form, Context.Chirp.TweetUrl);
return true; return true;
case MenuWriteApplyROT13: case MenuCopyTweetUrl:
form.InvokeAsyncSafe(form.ApplyROT13); SetClipboardText(form, Context.Chirp.TweetUrl);
return true; return true;
case MenuSearchInColumn: case MenuScreenshotTweet:
string query = parameters.SelectionText; var chirp = Context.Chirp;
form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
DeselectAll(frame);
break;
}
return false; form.InvokeAsyncSafe(() => form.TriggerTweetScreenshot(chirp.ColumnId, chirp.ChirpId));
}
public static ContextMenu CreateMenu(FormBrowser form){ return true;
ContextMenu menu = new ContextMenu();
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck()); case MenuOpenQuotedTweetUrl:
menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications()); OpenBrowser(form, Context.Chirp.QuoteUrl);
menu.MenuItems.Add("-"); return true;
menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings());
menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins());
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
menu.Popup += (sender, args) => { case MenuCopyQuotedTweetUrl:
menu.MenuItems[1].Checked = Config.MuteNotifications; SetClipboardText(form, Context.Chirp.QuoteUrl);
form.AnalyticsFile.BrowserContextMenus.Trigger(); return true;
};
return menu; case MenuWriteApplyROT13:
} form.InvokeAsyncSafe(form.ApplyROT13);
return true;
private static void ToggleMuteNotifications(){ case MenuSearchInColumn:
Config.MuteNotifications = !Config.MuteNotifications; string query = parameters.SelectionText;
Config.Save(); form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
} DeselectAll(frame);
} break;
}
return false;
}
public static ContextMenu CreateMenu(FormBrowser form) {
ContextMenu menu = new ContextMenu();
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications());
menu.MenuItems.Add("-");
menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings());
menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins());
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
menu.Popup += (sender, args) => {
menu.MenuItems[1].Checked = Config.MuteNotifications;
};
return menu;
}
private static void ToggleMuteNotifications() {
Config.MuteNotifications = !Config.MuteNotifications;
Config.Save();
}
}
} }

View File

@@ -1,14 +1,11 @@
using CefSharp; using CefSharp;
using TweetDuck.Management.Analytics;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class ContextMenuGuide : ContextMenuBase{ sealed class ContextMenuGuide : ContextMenuBase {
public ContextMenuGuide(AnalyticsFile.IProvider analytics) : base(analytics){} public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
model.Clear();
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
model.Clear(); AddDebugMenuItems(model);
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); }
AddDebugMenuItems(model); }
}
}
} }

View File

@@ -2,93 +2,92 @@
using TweetDuck.Browser.Notification; using TweetDuck.Browser.Notification;
using TweetDuck.Controls; using TweetDuck.Controls;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class ContextMenuNotification : ContextMenuBase{ sealed class ContextMenuNotification : ContextMenuBase {
private const CefMenuCommand MenuViewDetail = (CefMenuCommand)26600; private const CefMenuCommand MenuViewDetail = (CefMenuCommand) 26600;
private const CefMenuCommand MenuSkipTweet = (CefMenuCommand)26601; private const CefMenuCommand MenuSkipTweet = (CefMenuCommand) 26601;
private const CefMenuCommand MenuFreeze = (CefMenuCommand)26602; private const CefMenuCommand MenuFreeze = (CefMenuCommand) 26602;
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26603; private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand) 26603;
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604; private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand) 26604;
private readonly FormNotificationBase form; private readonly FormNotificationBase form;
private readonly bool enableCustomMenu; private readonly bool enableCustomMenu;
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){ public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) {
this.form = form; this.form = form;
this.enableCustomMenu = enableCustomMenu; 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(); model.Clear();
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){ if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)) {
model.AddItem(CefMenuCommand.Copy, "Copy"); model.AddItem(CefMenuCommand.Copy, "Copy");
model.AddSeparator(); model.AddSeparator();
} }
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (enableCustomMenu){ if (enableCustomMenu) {
if (form.CanViewDetail){ if (form.CanViewDetail) {
model.AddItem(MenuViewDetail, "View detail"); model.AddItem(MenuViewDetail, "View detail");
} }
model.AddItem(MenuSkipTweet, "Skip tweet"); model.AddItem(MenuSkipTweet, "Skip tweet");
model.AddCheckItem(MenuFreeze, "Freeze"); model.AddCheckItem(MenuFreeze, "Freeze");
model.SetChecked(MenuFreeze, form.FreezeTimer); model.SetChecked(MenuFreeze, form.FreezeTimer);
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){ if (!string.IsNullOrEmpty(form.CurrentTweetUrl)) {
model.AddSeparator(); model.AddSeparator();
model.AddItem(MenuCopyTweetUrl, "Copy tweet address"); model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){ if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)) {
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address"); model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
} }
} }
} }
AddDebugMenuItems(model);
RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(() => { AddDebugMenuItems(model);
form.ContextMenuOpen = true; RemoveSeparatorIfLast(model);
form.AnalyticsFile.NotificationContextMenus.Trigger();
});
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ form.InvokeAsyncSafe(() => {
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){ form.ContextMenuOpen = true;
return true; });
} }
switch(commandId){ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
case MenuSkipTweet: if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
form.InvokeAsyncSafe(form.FinishCurrentNotification); return true;
return true; }
case MenuFreeze: switch (commandId) {
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer); case MenuSkipTweet:
return true; form.InvokeAsyncSafe(form.FinishCurrentNotification);
return true;
case MenuViewDetail: case MenuFreeze:
form.InvokeSafe(form.ShowTweetDetail); form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
return true; return true;
case MenuCopyTweetUrl: case MenuViewDetail:
SetClipboardText(form, form.CurrentTweetUrl); form.InvokeSafe(form.ShowTweetDetail);
return true; return true;
case MenuCopyQuotedTweetUrl: case MenuCopyTweetUrl:
SetClipboardText(form, form.CurrentQuoteUrl); SetClipboardText(form, form.CurrentTweetUrl);
return true; return true;
}
return false; case MenuCopyQuotedTweetUrl:
} SetClipboardText(form, form.CurrentQuoteUrl);
return true;
}
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){ return false;
base.OnContextMenuDismissed(browserControl, browser, frame); }
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
} public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
} base.OnContextMenuDismissed(browserControl, browser, frame);
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
}
}
} }

View File

@@ -1,35 +1,36 @@
using System.Collections.Generic; using System.Collections.Generic;
using CefSharp; using CefSharp;
using CefSharp.Enums; using CefSharp.Enums;
using TweetDuck.Utils;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class DragHandlerBrowser : IDragHandler{ sealed class DragHandlerBrowser : IDragHandler {
private readonly RequestHandlerBrowser requestHandler; private readonly RequestHandlerBrowser requestHandler;
public DragHandlerBrowser(RequestHandlerBrowser requestHandler){ public DragHandlerBrowser(RequestHandlerBrowser requestHandler) {
this.requestHandler = requestHandler; this.requestHandler = requestHandler;
} }
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){
void TriggerDragStart(string type, string data = null){
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
}
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
if (dragData.IsLink){ public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
TriggerDragStart("link", dragData.LinkUrl); void TriggerDragStart(string type, string data = null) {
} browserControl.ExecuteJsAsync("window.TDGF_onGlobalDragStart", type, data);
else if (dragData.IsFragment){ }
TriggerDragStart("text", dragData.FragmentText.Trim());
}
else{
TriggerDragStart("unknown");
}
return false; requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
}
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions){} if (dragData.IsLink) {
} TriggerDragStart("link", dragData.LinkUrl);
}
else if (dragData.IsFragment) {
TriggerDragStart("text", dragData.FragmentText.Trim());
}
else {
TriggerDragStart("unknown");
}
return false;
}
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions) {}
}
} }

View File

@@ -3,70 +3,72 @@ using System.IO;
using System.Text; using System.Text;
using CefSharp; using CefSharp;
namespace TweetDuck.Browser.Handling.Filters{ namespace TweetDuck.Browser.Handling.Filters {
abstract class ResponseFilterBase : IResponseFilter{ abstract class ResponseFilterBase : IResponseFilter {
private enum State{ private enum State {
Reading, Writing, Done Reading,
} Writing,
Done
}
private readonly Encoding encoding; private readonly Encoding encoding;
private byte[] responseData; private byte[] responseData;
private State state; private State state;
private int offset; private int offset;
protected ResponseFilterBase(int totalBytes, Encoding encoding){ protected ResponseFilterBase(int totalBytes, Encoding encoding) {
this.responseData = new byte[totalBytes]; this.responseData = new byte[totalBytes];
this.encoding = encoding; this.encoding = encoding;
this.state = State.Reading; this.state = State.Reading;
} }
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten){
int responseLength = responseData.Length;
if (state == State.Reading){ FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) {
int bytesToRead = Math.Min(responseLength - offset, (int)Math.Min(dataIn?.Length ?? 0, int.MaxValue)); int responseLength = responseData.Length;
dataIn?.Read(responseData, offset, bytesToRead);
offset += bytesToRead;
dataInRead = bytesToRead;
dataOutWritten = 0;
if (offset >= responseLength){
responseData = encoding.GetBytes(ProcessResponse(encoding.GetString(responseData)));
state = State.Writing;
offset = 0;
}
return FilterStatus.NeedMoreData; if (state == State.Reading) {
} int bytesToRead = Math.Min(responseLength - offset, (int) Math.Min(dataIn?.Length ?? 0, int.MaxValue));
else if (state == State.Writing){
int bytesToWrite = Math.Min(responseLength - offset, (int)Math.Min(dataOut.Length, int.MaxValue));
if (bytesToWrite > 0){ dataIn?.Read(responseData, offset, bytesToRead);
dataOut.Write(responseData, offset, bytesToWrite); offset += bytesToRead;
offset += bytesToWrite;
}
dataOutWritten = bytesToWrite; dataInRead = bytesToRead;
dataInRead = 0; dataOutWritten = 0;
if (offset < responseLength){ if (offset >= responseLength) {
return FilterStatus.NeedMoreData; responseData = encoding.GetBytes(ProcessResponse(encoding.GetString(responseData)));
} state = State.Writing;
else{ offset = 0;
state = State.Done; }
return FilterStatus.Done;
} return FilterStatus.NeedMoreData;
} }
else{ else if (state == State.Writing) {
throw new InvalidOperationException("This resource filter cannot be reused."); int bytesToWrite = Math.Min(responseLength - offset, (int) Math.Min(dataOut.Length, int.MaxValue));
}
} if (bytesToWrite > 0) {
dataOut.Write(responseData, offset, bytesToWrite);
public abstract bool InitFilter(); offset += bytesToWrite;
protected abstract string ProcessResponse(string text); }
public abstract void Dispose();
} dataOutWritten = bytesToWrite;
dataInRead = 0;
if (offset < responseLength) {
return FilterStatus.NeedMoreData;
}
else {
state = State.Done;
return FilterStatus.Done;
}
}
else {
throw new InvalidOperationException("This resource filter cannot be reused.");
}
}
public abstract bool InitFilter();
protected abstract string ProcessResponse(string text);
public abstract void Dispose();
}
} }

View File

@@ -1,20 +1,20 @@
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace TweetDuck.Browser.Handling.Filters{ namespace TweetDuck.Browser.Handling.Filters {
sealed class ResponseFilterVendor : ResponseFilterBase{ sealed class ResponseFilterVendor : ResponseFilterBase {
private static readonly Regex RegexRestoreJQuery = new Regex(@"(\w+)\.fn=\1\.prototype", RegexOptions.Compiled); 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; return true;
} }
protected override string ProcessResponse(string text){
return RegexRestoreJQuery.Replace(text, "window.$$=$1;$&", 1);
}
public override void Dispose(){} protected override string ProcessResponse(string text) {
} return RegexRestoreJQuery.Replace(text, "window.$$=$1;$&", 1);
}
public override void Dispose() {}
}
} }

View File

@@ -3,26 +3,26 @@ using System.Threading.Tasks;
using CefSharp; using CefSharp;
using TweetDuck.Configuration; using TweetDuck.Configuration;
namespace TweetDuck.Browser.Handling.General{ namespace TweetDuck.Browser.Handling.General {
sealed class BrowserProcessHandler : IBrowserProcessHandler{ sealed class BrowserProcessHandler : IBrowserProcessHandler {
public static Task UpdatePrefs(){ public static Task UpdatePrefs() {
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal); return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
} }
private static void UpdatePrefsInternal(){ private static void UpdatePrefsInternal() {
UserConfig config = Program.Config.User; UserConfig config = Program.Config.User;
using IRequestContext ctx = Cef.GetGlobalRequestContext(); using IRequestContext ctx = Cef.GetGlobalRequestContext();
ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _); ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _);
ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _); ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _);
ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _); ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
} }
void IBrowserProcessHandler.OnContextInitialized(){ void IBrowserProcessHandler.OnContextInitialized() {
UpdatePrefsInternal(); UpdatePrefsInternal();
} }
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){} void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay) {}
void IDisposable.Dispose(){} void IDisposable.Dispose() {}
} }
} }

View 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;
}
}
}

View File

@@ -3,59 +3,59 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Handling.General{ namespace TweetDuck.Browser.Handling.General {
sealed class FileDialogHandler : IDialogHandler{ 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){ 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){ if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple) {
string allFilters = string.Join(";", acceptFilters.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter)); 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, AutoUpgradeEnabled = true,
DereferenceLinks = true, DereferenceLinks = true,
Multiselect = mode == CefFileDialogMode.OpenMultiple, Multiselect = mode == CefFileDialogMode.OpenMultiple,
Title = "Open Files", Title = "Open Files",
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*" Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
}){ };
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{
callback.Cancel();
}
callback.Dispose(); 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 {
callback.Cancel();
}
return true; callback.Dispose();
} return true;
else{ }
callback.Dispose(); else {
return false; callback.Dispose();
} return false;
} }
}
private static IEnumerable<string> ParseFileType(string type){ private static IEnumerable<string> ParseFileType(string type) {
if (string.IsNullOrEmpty(type)){ if (string.IsNullOrEmpty(type)) {
return new string[0]; return StringUtils.EmptyArray;
} }
if (type[0] == '.'){
return new string[]{ type };
}
switch(type){ if (type[0] == '.') {
case "image/jpeg": return new string[]{ ".jpg", ".jpeg" }; return new string[] { type };
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(); switch (type) {
return new string[0]; 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 StringUtils.EmptyArray;
}
}
} }

View File

@@ -5,89 +5,89 @@ using TweetDuck.Controls;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Browser.Handling.General{ namespace TweetDuck.Browser.Handling.General {
sealed class JavaScriptDialogHandler : IJsDialogHandler{ sealed class JavaScriptDialogHandler : IJsDialogHandler {
private static FormMessage CreateMessageForm(string caption, string text){ private static FormMessage CreateMessageForm(string caption, string text) {
MessageBoxIcon icon = MessageBoxIcon.None; MessageBoxIcon icon = MessageBoxIcon.None;
int pipe = text.IndexOf('|'); int pipe = text.IndexOf('|');
if (pipe != -1){ if (pipe != -1) {
icon = text.Substring(0, pipe) switch{ icon = text.Substring(0, pipe) switch {
"error" => MessageBoxIcon.Error, "error" => MessageBoxIcon.Error,
"warning" => MessageBoxIcon.Warning, "warning" => MessageBoxIcon.Warning,
"info" => MessageBoxIcon.Information, "info" => MessageBoxIcon.Information,
"question" => MessageBoxIcon.Question, "question" => MessageBoxIcon.Question,
_ => MessageBoxIcon.None _ => MessageBoxIcon.None
}; };
if (icon != MessageBoxIcon.None){ if (icon != MessageBoxIcon.None) {
text = text.Substring(pipe + 1); text = text.Substring(pipe + 1);
} }
} }
return new FormMessage(caption, text, icon); 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(() => { browserControl.AsControl().InvokeSafe(() => {
FormMessage form; FormMessage form;
TextBox input = null; TextBox input = null;
if (dialogType == CefJsDialogType.Alert){ if (dialogType == CefJsDialogType.Alert) {
form = CreateMessageForm("Browser Message", messageText); form = CreateMessageForm("Browser Message", messageText);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused); form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
} }
else if (dialogType == CefJsDialogType.Confirm){ else if (dialogType == CefJsDialogType.Confirm) {
form = CreateMessageForm("Browser Confirmation", messageText); form = CreateMessageForm("Browser Confirmation", messageText);
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel); form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
form.AddButton(FormMessage.Yes, ControlType.Focused); form.AddButton(FormMessage.Yes, ControlType.Focused);
} }
else if (dialogType == CefJsDialogType.Prompt){ else if (dialogType == CefJsDialogType.Prompt) {
form = CreateMessageForm("Browser Prompt", messageText); form = CreateMessageForm("Browser Prompt", messageText);
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel); form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused); form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
float dpiScale = form.GetDPIScale(); float dpiScale = form.GetDPIScale();
int inputPad = form.HasIcon ? 43 : 0; int inputPad = form.HasIcon ? 43 : 0;
input = new TextBox{ input = new TextBox {
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom, Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Font = SystemFonts.MessageBoxFont, Font = SystemFonts.MessageBoxFont,
Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)), Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)),
Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale)) Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
}; };
form.Controls.Add(input); form.Controls.Add(input);
form.ActiveControl = input; form.ActiveControl = input;
form.Height += input.Size.Height + input.Margin.Vertical; form.Height += input.Size.Height + input.Margin.Vertical;
} }
else{ else {
callback.Continue(false); callback.Continue(false);
return; return;
} }
bool success = form.ShowDialog() == DialogResult.OK; bool success = form.ShowDialog() == DialogResult.OK;
if (input == null){ if (input == null) {
callback.Continue(success); callback.Continue(success);
} }
else{ else {
callback.Continue(success, input.Text); callback.Continue(success, input.Text);
input.Dispose(); input.Dispose();
} }
form.Dispose(); form.Dispose();
}); });
return true; 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(); callback.Dispose();
return false; return false;
} }
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){} void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser) {}
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){} void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser) {}
} }
} }

View File

@@ -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){}
}
}

View File

@@ -1,47 +1,28 @@
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
class KeyboardHandlerBase : IKeyboardHandler{ class KeyboardHandlerBase : IKeyboardHandler {
protected virtual bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){ protected virtual bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I){ if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I) {
if (BrowserUtils.HasDevTools){ browserControl.OpenDevToolsCustom();
browserControl.OpenDevToolsCustom(); return true;
} }
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 false;
}); }
}
return true; 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; return false;
} }
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){ bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) {
if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("devtools://")){ return false;
return HandleRawKey(browserControl, browser, (Keys)windowsKeyCode, modifiers); }
} }
return false;
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
} }

View File

@@ -1,20 +1,16 @@
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class KeyboardHandlerBrowser : KeyboardHandlerBase{ sealed class KeyboardHandlerBrowser : KeyboardHandlerBase {
private readonly FormBrowser form; private readonly FormBrowser form;
public KeyboardHandlerBrowser(FormBrowser form){ public KeyboardHandlerBrowser(FormBrowser form) {
this.form = form; this.form = form;
} }
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){ protected override bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
if (base.HandleRawKey(browserControl, browser, key, modifiers)){ return base.HandleRawKey(browserControl, key, modifiers) || form.ProcessBrowserKey(key);
return true; }
} }
return form.ProcessBrowserKey(key);
}
}
} }

View File

@@ -3,42 +3,35 @@ using CefSharp;
using TweetDuck.Browser.Notification; using TweetDuck.Browser.Notification;
using TweetDuck.Controls; using TweetDuck.Controls;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class KeyboardHandlerNotification : KeyboardHandlerBase{ sealed class KeyboardHandlerNotification : KeyboardHandlerBase {
private readonly FormNotificationBase notification; private readonly FormNotificationBase notification;
public KeyboardHandlerNotification(FormNotificationBase notification){ public KeyboardHandlerNotification(FormNotificationBase notification) {
this.notification = notification; this.notification = notification;
} }
private void TriggerKeyboardShortcutAnalytics(){ protected override bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
notification.InvokeAsyncSafe(notification.AnalyticsFile.NotificationKeyboardShortcuts.Trigger); if (base.HandleRawKey(browserControl, key, modifiers)) {
} return true;
}
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){ switch (key) {
if (base.HandleRawKey(browserControl, browser, key, modifiers)){ case Keys.Enter:
return true; notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
} return true;
switch(key){ case Keys.Escape:
case Keys.Enter: notification.InvokeAsyncSafe(notification.HideNotification);
notification.InvokeAsyncSafe(notification.FinishCurrentNotification); return true;
TriggerKeyboardShortcutAnalytics();
return true;
case Keys.Escape: case Keys.Space:
notification.InvokeAsyncSafe(notification.HideNotification); notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
TriggerKeyboardShortcutAnalytics(); return true;
return true;
case Keys.Space: default:
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer); return false;
TriggerKeyboardShortcutAnalytics(); }
return true; }
}
default:
return false;
}
}
}
} }

View File

@@ -2,22 +2,22 @@
using CefSharp.Handler; using CefSharp.Handler;
using TweetDuck.Browser.Handling.General; using TweetDuck.Browser.Handling.General;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
class RequestHandlerBase : RequestHandler{ class RequestHandlerBase : RequestHandler {
private readonly bool autoReload; private readonly bool autoReload;
public RequestHandlerBase(bool autoReload){ public RequestHandlerBase(bool autoReload) {
this.autoReload = autoReload; this.autoReload = autoReload;
} }
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){ protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl); return CustomLifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
} }
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){ protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
if (autoReload){ if (autoReload) {
browser.Reload(); browser.Reload();
} }
} }
} }
} }

View File

@@ -1,23 +1,23 @@
using CefSharp; using CefSharp;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class RequestHandlerBrowser : RequestHandlerBase{ sealed class RequestHandlerBrowser : RequestHandlerBase {
public string BlockNextUserNavUrl { get; set; } 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){ protected override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect) {
if (userGesture && request.TransitionType == TransitionType.LinkClicked){ if (userGesture && request.TransitionType == TransitionType.LinkClicked) {
bool block = request.Url == BlockNextUserNavUrl; bool block = request.Url == BlockNextUserNavUrl;
BlockNextUserNavUrl = string.Empty; BlockNextUserNavUrl = string.Empty;
return block; 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; return true;
} }
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect); return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
} }
} }
} }

View File

@@ -5,75 +5,75 @@ using System.Text;
using CefSharp; using CefSharp;
using CefSharp.Callback; using CefSharp.Callback;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class ResourceHandlerNotification : IResourceHandler{ sealed class ResourceHandlerNotification : IResourceHandler {
private readonly NameValueCollection headers = new NameValueCollection(0); private readonly NameValueCollection headers = new NameValueCollection(0);
private MemoryStream dataIn; private MemoryStream dataIn;
public void SetHTML(string html){ public void SetHTML(string html) {
dataIn?.Dispose(); dataIn?.Dispose();
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8); dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
} }
public void Dispose(){ public void Dispose() {
if (dataIn != null){ if (dataIn != null) {
dataIn.Dispose(); dataIn.Dispose();
dataIn = null; dataIn = null;
} }
} }
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback){ bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback) {
callback.Dispose(); callback.Dispose();
handleRequest = true; handleRequest = true;
if (dataIn != null){ if (dataIn != null) {
dataIn.Position = 0; dataIn.Position = 0;
} }
return true; 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; redirectUrl = null;
response.MimeType = "text/html"; response.MimeType = "text/html";
response.StatusCode = 200; response.StatusCode = 200;
response.StatusText = "OK"; response.StatusText = "OK";
response.Headers = headers; response.Headers = headers;
responseLength = dataIn?.Length ?? 0; 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 callback?.Dispose(); // TODO unnecessary null check once ReadResponse is removed
try{ try {
byte[] buffer = new byte[Math.Min(dataIn.Length - dataIn.Position, dataOut.Length)]; byte[] buffer = new byte[Math.Min(dataIn.Length - dataIn.Position, dataOut.Length)];
int length = buffer.Length; int length = buffer.Length;
dataIn.Read(buffer, 0, length); dataIn.Read(buffer, 0, length);
dataOut.Write(buffer, 0, length); dataOut.Write(buffer, 0, length);
bytesRead = length; bytesRead = length;
}catch{ // catch IOException, possibly NullReferenceException if dataIn is null } catch { // catch IOException, possibly NullReferenceException if dataIn is null
bytesRead = 0; bytesRead = 0;
} }
return 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 bytesSkipped = -2; // ERR_FAILED
callback.Dispose(); callback.Dispose();
return false; return false;
} }
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback){ bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback) {
return ((IResourceHandler)this).Open(request, out bool _, callback); return ((IResourceHandler) this).Open(request, out bool _, callback);
} }
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback){ bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback) {
return ((IResourceHandler)this).Read(dataOut, out bytesRead, null); return ((IResourceHandler) this).Read(dataOut, out bytesRead, null);
} }
void IResourceHandler.Cancel(){} void IResourceHandler.Cancel() {}
} }
} }

View 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);
}
}
}
}

View File

@@ -2,34 +2,34 @@
using CefSharp; using CefSharp;
using TweetDuck.Browser.Data; using TweetDuck.Browser.Data;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
abstract class ResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler{ abstract class ResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler {
private class SelfFactoryImpl : IResourceRequestHandlerFactory{ private class SelfFactoryImpl : IResourceRequestHandlerFactory {
private readonly ResourceRequestHandler me; private readonly ResourceRequestHandler me;
public SelfFactoryImpl(ResourceRequestHandler me){ public SelfFactoryImpl(ResourceRequestHandler me) {
this.me = me; this.me = me;
} }
bool IResourceRequestHandlerFactory.HasHandlers { get; } = true; bool IResourceRequestHandlerFactory.HasHandlers => true;
[SuppressMessage("ReSharper", "RedundantAssignment")] [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); disableDefaultHandling = me.ResourceHandlers.HasHandler(request);
return me; return me;
} }
} }
public IResourceRequestHandlerFactory SelfFactory { get; } public IResourceRequestHandlerFactory SelfFactory { get; }
public ResourceHandlers ResourceHandlers { get; } public ResourceHandlers ResourceHandlers { get; }
protected ResourceRequestHandler(){ protected ResourceRequestHandler() {
this.SelfFactory = new SelfFactoryImpl(this); this.SelfFactory = new SelfFactoryImpl(this);
this.ResourceHandlers = new ResourceHandlers(); 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); return ResourceHandlers.GetHandler(request);
} }
} }
} }

View File

@@ -4,66 +4,63 @@ using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using CefSharp; using CefSharp;
using TweetDuck.Utils;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
class ResourceRequestHandlerBase : ResourceRequestHandler{ class ResourceRequestHandlerBase : ResourceRequestHandler {
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$"); private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$");
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4); private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
public static void LoadResourceRewriteRules(string rules){ public static void LoadResourceRewriteRules(string rules) {
if (string.IsNullOrEmpty(rules)){ if (string.IsNullOrEmpty(rules)) {
return; return;
} }
TweetDeckHashes.Clear(); 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); 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); TweetDeckHashes.Add(key, hash);
} }
else{ else {
throw new ArgumentException("Invalid hash characters: " + rule); throw new ArgumentException("Invalid hash characters: " + rule);
} }
} }
} }
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){ protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
if (request.ResourceType == ResourceType.CspReport){ if (request.ResourceType == ResourceType.CspReport) {
callback.Dispose(); callback.Dispose();
return CefReturnValue.Cancel; return CefReturnValue.Cancel;
} }
if (BrowserUtils.HasDevTools){ NameValueCollection headers = request.Headers;
NameValueCollection headers = request.Headers; headers.Remove("x-devtools-emulate-network-conditions-client-id");
headers.Remove("x-devtools-emulate-network-conditions-client-id"); request.Headers = headers;
request.Headers = headers;
}
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback); return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
} }
protected override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){ 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){ if ((request.ResourceType == ResourceType.Script || request.ResourceType == ResourceType.Stylesheet) && TweetDeckHashes.Count > 0) {
string url = request.Url; string url = request.Url;
Match match = TweetDeckResourceUrl.Match(url); Match match = TweetDeckResourceUrl.Match(url);
if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)){ if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)) {
if (match.Groups[2].Value == hash){ if (match.Groups[2].Value == hash) {
Program.Reporter.LogVerbose("[RequestHandlerBase] Accepting " + url); Program.Reporter.LogVerbose("[RequestHandlerBase] Accepting " + url);
} }
else{ else {
Program.Reporter.LogVerbose("[RequestHandlerBase] Replacing " + url + " hash with " + hash); Program.Reporter.LogVerbose("[RequestHandlerBase] Replacing " + url + " hash with " + hash);
request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3"); request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3");
return true; return true;
} }
} }
} }
return base.OnResourceResponse(browserControl, browser, frame, request, response); return base.OnResourceResponse(browserControl, browser, frame, request, response);
} }
} }
} }

View File

@@ -3,50 +3,50 @@ using TweetDuck.Browser.Handling.Filters;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
class ResourceRequestHandlerBrowser : ResourceRequestHandlerBase{ class ResourceRequestHandlerBrowser : ResourceRequestHandlerBase {
private const string UrlVendorResource = "/dist/vendor"; private const string UrlVendorResource = "/dist/vendor";
private const string UrlLoadingSpinner = "/backgrounds/spinner_blue"; private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
private const string UrlVersionCheck = "/web/dist/version.json"; private const string UrlVersionCheck = "/web/dist/version.json";
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){ protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
if (request.ResourceType == ResourceType.MainFrame){ if (request.ResourceType == ResourceType.MainFrame) {
if (request.Url.EndsWith("//twitter.com/")){ if (request.Url.EndsWith("//twitter.com/")) {
request.Url = TwitterUrls.TweetDeck; // redirect plain twitter.com requests, fixes bugs with login 2FA request.Url = TwitterUrls.TweetDeck; // redirect plain twitter.com requests, fixes bugs with login 2FA
} }
} }
else if (request.ResourceType == ResourceType.Image){ else if (request.ResourceType == ResourceType.Image) {
if (request.Url.Contains(UrlLoadingSpinner)){ if (request.Url.Contains(UrlLoadingSpinner)) {
request.Url = TwitterUtils.LoadingSpinner.Url; request.Url = TwitterUtils.LoadingSpinner.Url;
} }
} }
else if (request.ResourceType == ResourceType.Script){ else if (request.ResourceType == ResourceType.Script) {
string url = request.Url; string url = request.Url;
if (url.Contains("analytics.")){ if (url.Contains("analytics.")) {
callback.Dispose(); callback.Dispose();
return CefReturnValue.Cancel; return CefReturnValue.Cancel;
} }
else if (url.Contains(UrlVendorResource)){ else if (url.Contains(UrlVendorResource)) {
request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true); request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true);
} }
} }
else if (request.ResourceType == ResourceType.Xhr){ else if (request.ResourceType == ResourceType.Xhr) {
if (request.Url.Contains(UrlVersionCheck)){ if (request.Url.Contains(UrlVersionCheck)) {
callback.Dispose(); callback.Dispose();
return CefReturnValue.Cancel; return CefReturnValue.Cancel;
} }
} }
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback); return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
} }
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){ 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)){ if (request.ResourceType == ResourceType.Script && request.Url.Contains(UrlVendorResource) && int.TryParse(response.Headers["Content-Length"], out int totalBytes)) {
return new ResponseFilterVendor(totalBytes); return new ResponseFilterVendor(totalBytes);
} }
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response); return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
} }
} }
} }

View File

@@ -1,68 +1,70 @@
using System; using System;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Notification.Example{ namespace TweetDuck.Browser.Notification.Example {
sealed class FormNotificationExample : FormNotificationMain{ sealed class FormNotificationExample : FormNotificationMain {
public override bool RequiresResize => true; public override bool RequiresResize => true;
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom; protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
protected override FormBorderStyle NotificationBorderStyle{ protected override FormBorderStyle NotificationBorderStyle {
get{ get {
if (Config.NotificationSize == DesktopNotification.Size.Custom){ if (Config.NotificationSize == DesktopNotification.Size.Custom) {
switch(base.NotificationBorderStyle){ switch (base.NotificationBorderStyle) {
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable; case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow; case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
} }
} }
return base.NotificationBorderStyle; return base.NotificationBorderStyle;
} }
} }
protected override string BodyClasses => base.BodyClasses + " td-example"; protected override string BodyClasses => base.BodyClasses + " td-example";
public event EventHandler Ready; public event EventHandler Ready;
private readonly DesktopNotification exampleNotification; 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; 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 #if DEBUG
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>"); exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
#endif #endif
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty); exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
} }
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
if (!e.IsLoading){ if (!e.IsLoading) {
Ready?.Invoke(this, EventArgs.Empty); Ready?.Invoke(this, EventArgs.Empty);
browser.LoadingStateChanged -= browser_LoadingStateChanged; browser.LoadingStateChanged -= browser_LoadingStateChanged;
} }
} }
public override void HideNotification(){ public override void HideNotification() {
Location = ControlExtensions.InvisibleLocation; Location = ControlExtensions.InvisibleLocation;
} }
public override void FinishCurrentNotification(){} public override void FinishCurrentNotification() {}
public void ShowExampleNotification(bool reset){ public void ShowExampleNotification(bool reset) {
if (reset){ if (reset) {
LoadTweet(exampleNotification); LoadTweet(exampleNotification);
} }
else{ else {
PrepareAndDisplayWindow(); PrepareAndDisplayWindow();
} }
UpdateTitle(); UpdateTitle();
} }
} }
} }

View File

@@ -1,259 +1,253 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDuck.Browser.Data; using TweetDuck.Browser.Data;
using TweetDuck.Browser.Handling; using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General; using TweetDuck.Browser.Handling.General;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Notification{ namespace TweetDuck.Browser.Notification {
abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{ abstract partial class FormNotificationBase : Form {
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png")); 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"; protected const string BlankURL = TwitterUrls.TweetDeck + "/?blank";
public static string FontSize = null; public static string FontSize = null;
public static string HeadLayout = null; public static string HeadLayout = null;
protected static UserConfig Config => Program.Config.User; protected static UserConfig Config => Program.Config.User;
protected static int FontSizeLevel{
get => FontSize switch{
"largest" => 4,
"large" => 3,
"small" => 1,
"smallest" => 0,
_ => 2
};
}
protected virtual Point PrimaryLocation{ protected static int FontSizeLevel {
get{ get => FontSize switch {
Screen screen; "largest" => 4,
"large" => 3,
"small" => 1,
"smallest" => 0,
_ => 2
};
}
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length){ protected virtual Point PrimaryLocation {
screen = Screen.AllScreens[Config.NotificationDisplay - 1]; get {
} Screen screen;
else{
screen = Screen.FromControl(owner);
}
int edgeDist = Config.NotificationEdgeDistance;
switch(Config.NotificationPosition){ if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length) {
case DesktopNotification.Position.TopLeft: screen = Screen.AllScreens[Config.NotificationDisplay - 1];
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist); }
else {
screen = Screen.FromControl(owner);
}
case DesktopNotification.Position.TopRight: int edgeDist = Config.NotificationEdgeDistance;
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
case DesktopNotification.Position.BottomLeft: switch (Config.NotificationPosition) {
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height); case DesktopNotification.Position.TopLeft:
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist);
case DesktopNotification.Position.BottomRight: case DesktopNotification.Position.TopRight:
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height); return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
case DesktopNotification.Position.Custom: case DesktopNotification.Position.BottomLeft:
if (!Config.IsCustomNotificationPositionSet){ return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
Config.Save();
}
return Config.CustomNotificationPosition; case DesktopNotification.Position.BottomRight:
} return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
return Location; case DesktopNotification.Position.Custom:
} if (!Config.IsCustomNotificationPositionSet) {
} Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
Config.Save();
}
protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation; return Config.CustomNotificationPosition;
protected virtual bool CanDragWindow => true; }
public new Point Location{ return Location;
get{ }
return base.Location; }
}
set{ protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation; protected virtual bool CanDragWindow => true;
FormBorderStyle = NotificationBorderStyle;
}
}
protected virtual FormBorderStyle NotificationBorderStyle{ public new Point Location {
get{ get { return base.Location; }
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
return FormBorderStyle.FixedSingle;
}
else{
return FormBorderStyle.FixedToolWindow;
}
}
}
public AnalyticsFile AnalyticsFile => owner.AnalyticsFile; set {
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
protected override bool ShowWithoutActivation => true; FormBorderStyle = NotificationBorderStyle;
}
protected float DpiScale { get; } }
protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0;
protected readonly FormBrowser owner; protected virtual FormBorderStyle NotificationBorderStyle {
get {
if (WindowsUtils.ShouldAvoidToolWindow && Visible) { // Visible = workaround for alt+tab
return FormBorderStyle.FixedSingle;
}
else {
return FormBorderStyle.FixedToolWindow;
}
}
}
#pragma warning disable IDE0069 // Disposable fields should be disposed protected override bool ShowWithoutActivation => true;
protected readonly ChromiumWebBrowser browser;
#pragma warning restore IDE0069 // Disposable fields should be disposed
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification(); protected float DpiScale { get; }
protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0;
private DesktopNotification currentNotification; private readonly FormBrowser owner;
private int pauseCounter;
public string CurrentTweetUrl => currentNotification?.TweetUrl;
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId); #pragma warning disable IDE0069 // Disposable fields should be disposed
protected readonly ChromiumWebBrowser browser;
#pragma warning restore IDE0069 // Disposable fields should be disposed
protected bool IsPaused => pauseCounter > 0; private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; }
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){ private DesktopNotification currentNotification;
InitializeComponent(); private int pauseCounter;
this.owner = owner; public string CurrentTweetUrl => currentNotification?.TweetUrl;
this.owner.FormClosed += owner_FormClosed; public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
var resourceRequestHandler = new ResourceRequestHandlerBase();
var resourceHandlers = resourceRequestHandler.ResourceHandlers;
resourceHandlers.Register(BlankURL, ResourceHandler.FromString(string.Empty)); public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
resourceHandlers.Register(TwitterUrls.TweetDeck, this.resourceHandler);
resourceHandlers.Register(AppLogo);
this.browser = new ChromiumWebBrowser(BlankURL){ protected bool IsPaused => pauseCounter > 0;
MenuHandler = new ContextMenuNotification(this, enableContextMenu), protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBase(false),
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
};
this.browser.Dock = DockStyle.None; public bool FreezeTimer { get; set; }
this.browser.ClientSize = ClientSize; public bool ContextMenuOpen { get; set; }
this.browser.SetupZoomEvents();
Controls.Add(browser); protected FormNotificationBase(FormBrowser owner, bool enableContextMenu) {
InitializeComponent();
Disposed += (sender, args) => { this.owner = owner;
this.owner.FormClosed -= owner_FormClosed; this.owner.FormClosed += owner_FormClosed;
this.browser.Dispose();
};
DpiScale = this.GetDPIScale(); var resourceRequestHandler = new ResourceRequestHandlerBase();
var resourceHandlers = resourceRequestHandler.ResourceHandlers;
// ReSharper disable once VirtualMemberCallInContructor resourceHandlers.Register(BlankURL, ResourceHandlers.ForString(string.Empty));
UpdateTitle(); resourceHandlers.Register(TwitterUrls.TweetDeck, () => this.resourceHandler);
} resourceHandlers.Register(AppLogo);
protected override void Dispose(bool disposing){ this.browser = new ChromiumWebBrowser(BlankURL) {
if (disposing){ MenuHandler = new ContextMenuNotification(this, enableContextMenu),
components?.Dispose(); JsDialogHandler = new JavaScriptDialogHandler(),
resourceHandler.Dispose(); LifeSpanHandler = new CustomLifeSpanHandler(),
} RequestHandler = new RequestHandlerBase(false),
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
};
base.Dispose(disposing); this.browser.Dock = DockStyle.None;
} this.browser.ClientSize = ClientSize;
this.browser.SetupZoomEvents();
protected override void WndProc(ref Message m){ Controls.Add(browser);
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow){ // WM_SYSCOMMAND, SC_MOVE
return;
}
base.WndProc(ref m); Disposed += (sender, args) => {
} this.owner.FormClosed -= owner_FormClosed;
this.browser.Dispose();
};
// event handlers DpiScale = this.GetDPIScale();
private void owner_FormClosed(object sender, FormClosedEventArgs e){ // ReSharper disable once VirtualMemberCallInContructor
Close(); UpdateTitle();
} }
// notification methods protected override void Dispose(bool disposing) {
if (disposing) {
components?.Dispose();
resourceHandler.Dispose();
}
public virtual void HideNotification(){ base.Dispose(disposing);
browser.Load(BlankURL); }
DisplayTooltip(null);
Location = ControlExtensions.InvisibleLocation; protected override void WndProc(ref Message m) {
currentNotification = null; if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow) { // WM_SYSCOMMAND, SC_MOVE
} return;
}
public virtual void FinishCurrentNotification(){} base.WndProc(ref m);
}
public virtual void PauseNotification(){ // event handlers
if (pauseCounter++ == 0 && IsNotificationVisible){
Location = ControlExtensions.InvisibleLocation;
}
}
public virtual void ResumeNotification(){ private void owner_FormClosed(object sender, FormClosedEventArgs e) {
if (pauseCounter > 0){ Close();
--pauseCounter; }
}
}
protected abstract string GetTweetHTML(DesktopNotification tweet); // notification methods
protected virtual void LoadTweet(DesktopNotification tweet){ public virtual void HideNotification() {
currentNotification = tweet; browser.Load(BlankURL);
resourceHandler.SetHTML(GetTweetHTML(tweet)); DisplayTooltip(null);
browser.Load(TwitterUrls.TweetDeck); Location = ControlExtensions.InvisibleLocation;
DisplayTooltip(null); currentNotification = null;
} }
protected virtual void SetNotificationSize(int width, int height){ public virtual void FinishCurrentNotification() {}
browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
}
protected virtual void UpdateTitle(){ public virtual void PauseNotification() {
string title = currentNotification?.ColumnTitle; if (pauseCounter++ == 0 && IsNotificationVisible) {
Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}"; Location = ControlExtensions.InvisibleLocation;
} }
}
public void ShowTweetDetail(){ public virtual void ResumeNotification() {
if (currentNotification != null){ if (pauseCounter > 0) {
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl); --pauseCounter;
} }
} }
public void MoveToVisibleLocation(){ protected abstract string GetTweetHTML(DesktopNotification tweet);
bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
Location = PrimaryLocation;
if (needsReactivating){ protected virtual void LoadTweet(DesktopNotification tweet) {
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE); currentNotification = tweet;
} resourceHandler.SetHTML(GetTweetHTML(tweet));
}
public void DisplayTooltip(string text){ browser.Load(TwitterUrls.TweetDeck);
if (string.IsNullOrEmpty(text)){ DisplayTooltip(null);
toolTip.Hide(this); }
}
else{ protected virtual void SetNotificationSize(int width, int height) {
Point position = PointToClient(Cursor.Position); browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
position.Offset(20, 5); }
toolTip.Show(text, this, position);
} protected virtual void UpdateTitle() {
} string title = currentNotification?.ColumnTitle;
} Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}";
}
public void ShowTweetDetail() {
if (currentNotification != null) {
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
}
}
public void MoveToVisibleLocation() {
bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
Location = PrimaryLocation;
if (needsReactivating) {
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
}
}
public void DisplayTooltip(string text) {
if (string.IsNullOrEmpty(text)) {
toolTip.Hide(this);
}
else {
Point position = PointToClient(Cursor.Position);
position.Offset(20, 5);
toolTip.Show(text, this, position);
}
}
}
} }

View File

@@ -8,292 +8,275 @@ using TweetDuck.Browser.Handling;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums; using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Notification{ namespace TweetDuck.Browser.Notification {
abstract partial class FormNotificationMain : FormNotificationBase{ abstract partial class FormNotificationMain : FormNotificationBase {
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly int timerBarHeight; private readonly int timerBarHeight;
protected int timeLeft, totalTime; protected int timeLeft, totalTime;
protected bool pausedDuringNotification; protected bool pausedDuringNotification;
private readonly NativeMethods.HookProc mouseHookDelegate;
private IntPtr mouseHook;
private bool blockXButtonUp;
private int currentOpacity; private readonly NativeMethods.HookProc mouseHookDelegate;
private IntPtr mouseHook;
private bool blockXButtonUp;
private bool? prevDisplayTimer; private int currentOpacity;
private int? prevFontSize;
public virtual bool RequiresResize{ private bool? prevDisplayTimer;
get{ private int? prevFontSize;
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
}
set{ public virtual bool RequiresResize {
if (value){ get { return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel; }
prevDisplayTimer = null;
prevFontSize = null;
}
else{
prevDisplayTimer = Config.DisplayNotificationTimer;
prevFontSize = FontSizeLevel;
}
}
}
private int BaseClientWidth{ set {
get => Config.NotificationSize switch{ if (value) {
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width, prevDisplayTimer = null;
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel)) prevFontSize = null;
}; }
} else {
prevDisplayTimer = Config.DisplayNotificationTimer;
prevFontSize = FontSizeLevel;
}
}
}
private int BaseClientHeight{ private int BaseClientWidth {
get => Config.NotificationSize switch{ get => Config.NotificationSize switch {
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height, DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width,
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel)) _ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
}; };
} }
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification"; private int BaseClientHeight {
get => Config.NotificationSize switch {
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize; DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height,
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
};
}
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){ protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
InitializeComponent();
this.plugins = pluginManager; public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize;
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
browser.RegisterJsBridge("$TD", new TweetDeckBridge.Notification(owner, this));
browser.LoadingStateChanged += Browser_LoadingStateChanged; protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu) {
browser.FrameLoadEnd += Browser_FrameLoadEnd; InitializeComponent();
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser, url => TwitterUrls.IsTweetDeck(url) && url != BlankURL)); this.plugins = pluginManager;
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
mouseHookDelegate = MouseHookProc; browser.KeyboardHandler = new KeyboardHandlerNotification(this);
Disposed += (sender, args) => StopMouseHook(true); browser.RegisterJsBridge("$TD", new TweetDeckBridge.Notification(owner, this));
}
// helpers browser.LoadingStateChanged += Browser_LoadingStateChanged;
private void SetOpacity(int opacity){ plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser, url => TwitterUrls.IsTweetDeck(url) && url != BlankURL));
if (currentOpacity != opacity){
currentOpacity = opacity;
Opacity = opacity / 100.0;
}
}
// mouse wheel hook mouseHookDelegate = MouseHookProc;
Disposed += (sender, args) => StopMouseHook(true);
}
private void StartMouseHook(){ // helpers
if (mouseHook == IntPtr.Zero){
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
}
}
private void StopMouseHook(bool force){ private void SetOpacity(int opacity) {
if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)){ if (currentOpacity != opacity) {
NativeMethods.UnhookWindowsHookEx(mouseHook); currentOpacity = opacity;
mouseHook = IntPtr.Zero; Opacity = opacity / 100.0;
blockXButtonUp = false; }
} }
}
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){ // mouse wheel hook
if (nCode == 0){
int eventType = wParam.ToInt32();
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){ private void StartMouseHook() {
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01); if (mouseHook == IntPtr.Zero) {
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
}
}
if (Config.EnableSmoothScrolling){ private void StopMouseHook(bool force) {
browser.ExecuteScriptAsync("window.TDGF_scrollSmoothly", (int)Math.Round(-delta / 0.6)); if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)) {
} NativeMethods.UnhookWindowsHookEx(mouseHook);
else{ mouseHook = IntPtr.Zero;
browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None); blockXButtonUp = false;
} }
}
return NativeMethods.HOOK_HANDLED; private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
} if (nCode == 0) {
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){ int eventType = wParam.ToInt32();
int extraButton = NativeMethods.GetMouseHookData(lParam);
if (extraButton == 2){ // forward button if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser) {
this.InvokeAsyncSafe(FinishCurrentNotification); int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01);
}
else if (extraButton == 1){ // back button
this.InvokeAsyncSafe(Close);
}
blockXButtonUp = true;
this.InvokeAsyncSafe(AnalyticsFile.NotificationExtraMouseButtons.Trigger);
return NativeMethods.HOOK_HANDLED;
}
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
blockXButtonUp = false;
if (!Visible){ if (Config.EnableSmoothScrolling) {
StopMouseHook(false); browser.ExecuteJsAsync("window.TDGF_scrollSmoothly", (int) Math.Round(-delta / 0.6));
} }
else {
browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None);
}
return NativeMethods.HOOK_HANDLED; return NativeMethods.HOOK_HANDLED;
} }
} else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)) {
int extraButton = NativeMethods.GetMouseHookData(lParam);
return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam); if (extraButton == 2) { // forward button
} this.InvokeAsyncSafe(FinishCurrentNotification);
}
else if (extraButton == 1) { // back button
this.InvokeAsyncSafe(Close);
}
// event handlers blockXButtonUp = true;
return NativeMethods.HOOK_HANDLED;
}
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp) {
blockXButtonUp = false;
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){ if (!Visible) {
if (e.CloseReason == CloseReason.UserClosing){ StopMouseHook(false);
HideNotification(); }
e.Cancel = true;
}
}
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ return NativeMethods.HOOK_HANDLED;
if (!e.IsLoading && browser.Address != BlankURL){ }
this.InvokeSafe(() => { }
Visible = true; // ensures repaint before moving the window to a visible location
timerDisplayDelay.Start();
});
}
}
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
IFrame frame = e.Frame; }
if (frame.IsMain && browser.Address != BlankURL){ // event handlers
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
CefScriptExecutor.RunFile(frame, "notification.js");
}
}
private void timerDisplayDelay_Tick(object sender, EventArgs e){ private void FormNotification_FormClosing(object sender, FormClosingEventArgs e) {
OnNotificationReady(); if (e.CloseReason == CloseReason.UserClosing) {
timerDisplayDelay.Stop(); HideNotification();
} e.Cancel = true;
}
}
private void timerHideProgress_Tick(object sender, EventArgs e){ private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
bool isCursorInside = Bounds.Contains(Cursor.Position); if (!e.IsLoading && browser.Address != BlankURL) {
this.InvokeSafe(() => {
Visible = true; // ensures repaint before moving the window to a visible location
timerDisplayDelay.Start();
});
}
}
if (isCursorInside){ private void timerDisplayDelay_Tick(object sender, EventArgs e) {
StartMouseHook(); OnNotificationReady();
SetOpacity(100); timerDisplayDelay.Stop();
} }
else{
StopMouseHook(false);
SetOpacity(Config.NotificationWindowOpacity);
}
if (isCursorInside || FreezeTimer || ContextMenuOpen){ private void timerHideProgress_Tick(object sender, EventArgs e) {
return; bool isCursorInside = Bounds.Contains(Cursor.Position);
}
timeLeft -= timerProgress.Interval; if (isCursorInside) {
StartMouseHook();
SetOpacity(100);
}
else {
StopMouseHook(false);
SetOpacity(Config.NotificationWindowOpacity);
}
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double)totalTime); if (isCursorInside || FreezeTimer || ContextMenuOpen) {
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value); return;
}
if (timeLeft <= 0){ timeLeft -= timerProgress.Interval;
FinishCurrentNotification();
}
}
// notification methods int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double) totalTime);
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value);
public virtual void ShowNotification(DesktopNotification notification){ if (timeLeft <= 0) {
LoadTweet(notification); FinishCurrentNotification();
} }
}
public override void HideNotification(){ // notification methods
base.HideNotification();
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
timerProgress.Stop();
totalTime = 0;
StopMouseHook(false); public virtual void ShowNotification(DesktopNotification notification) {
} LoadTweet(notification);
}
public override void FinishCurrentNotification(){ public override void HideNotification() {
timerProgress.Stop(); base.HideNotification();
}
public override void PauseNotification(){ progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
if (!IsPaused){ timerProgress.Stop();
pausedDuringNotification = IsNotificationVisible; totalTime = 0;
timerProgress.Stop();
StopMouseHook(true);
}
base.PauseNotification(); StopMouseHook(false);
} }
public override void ResumeNotification(){ public override void FinishCurrentNotification() {
bool wasPaused = IsPaused; timerProgress.Stop();
base.ResumeNotification(); }
if (wasPaused && !IsPaused && pausedDuringNotification){ public override void PauseNotification() {
OnNotificationReady(); if (!IsPaused) {
} pausedDuringNotification = IsNotificationVisible;
} timerProgress.Stop();
StopMouseHook(true);
}
protected override string GetTweetHTML(DesktopNotification tweet){ base.PauseNotification();
string html = tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS); }
foreach(InjectedHTML injection in plugins.NotificationInjections){ public override void ResumeNotification() {
html = injection.InjectInto(html); bool wasPaused = IsPaused;
} base.ResumeNotification();
return html; if (wasPaused && !IsPaused && pausedDuringNotification) {
} OnNotificationReady();
}
}
protected override void LoadTweet(DesktopNotification tweet){ protected override string GetTweetHTML(DesktopNotification tweet) {
timerProgress.Stop(); return tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, new string[] {
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue); PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification),
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum; CefScriptExecutor.GetBootstrapScript("notification", includeStylesheets: false)
});
}
base.LoadTweet(tweet); protected override void LoadTweet(DesktopNotification tweet) {
} timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
protected override void SetNotificationSize(int width, int height){ base.LoadTweet(tweet);
if (Config.DisplayNotificationTimer){ }
ClientSize = new Size(width, height + timerBarHeight);
progressBarTimer.Visible = true;
}
else{
ClientSize = new Size(width, height);
progressBarTimer.Visible = false;
}
browser.ClientSize = new Size(width, height); protected override void SetNotificationSize(int width, int height) {
} if (Config.DisplayNotificationTimer) {
ClientSize = new Size(width, height + timerBarHeight);
protected void PrepareAndDisplayWindow(){ progressBarTimer.Visible = true;
if (RequiresResize){ }
RequiresResize = false; else {
SetNotificationSize(BaseClientWidth, BaseClientHeight); ClientSize = new Size(width, height);
} progressBarTimer.Visible = false;
}
SetOpacity(IsCursorOverBrowser ? 100 : Config.NotificationWindowOpacity); browser.ClientSize = new Size(width, height);
MoveToVisibleLocation(); }
}
protected virtual void OnNotificationReady(){ protected void PrepareAndDisplayWindow() {
PrepareAndDisplayWindow(); if (RequiresResize) {
timerProgress.Start(); RequiresResize = false;
} SetNotificationSize(BaseClientWidth, BaseClientHeight);
} }
SetOpacity(IsCursorOverBrowser ? 100 : Config.NotificationWindowOpacity);
MoveToVisibleLocation();
}
protected virtual void OnNotificationReady() {
PrepareAndDisplayWindow();
timerProgress.Start();
}
}
} }

View File

@@ -6,161 +6,160 @@ using TweetDuck.Utils;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Browser.Notification{ namespace TweetDuck.Browser.Notification {
sealed partial class FormNotificationTweet : FormNotificationMain{ sealed partial class FormNotificationTweet : FormNotificationMain {
private const int NonIntrusiveIdleLimit = 30; private const int NonIntrusiveIdleLimit = 30;
private const int TrimMinimum = 32; private const int TrimMinimum = 32;
protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation; protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation;
private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position); private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
protected override bool CanDragWindow{ protected override bool CanDragWindow {
get{ get {
if (ModifierKeys.HasFlag(Keys.Alt)){ if (ModifierKeys.HasFlag(Keys.Alt)) {
hasTemporarilyMoved = true; hasTemporarilyMoved = true;
return true; return true;
} }
else{ else {
return false; return false;
} }
} }
} }
private readonly Queue<DesktopNotification> tweetQueue = new Queue<DesktopNotification>(4); private readonly Queue<DesktopNotification> tweetQueue = new Queue<DesktopNotification>(4);
private bool needsTrim; private bool needsTrim;
private bool hasTemporarilyMoved; private bool hasTemporarilyMoved;
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){ public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true) {
InitializeComponent(); InitializeComponent();
Config.MuteToggled += Config_MuteToggled; Config.MuteToggled += Config_MuteToggled;
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled; Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
if (Config.MuteNotifications){ if (Config.MuteNotifications) {
PauseNotification(); PauseNotification();
} }
} }
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m) {
if (m.Msg == 0x00A7){ // WM_NCMBUTTONDOWN if (m.Msg == 0x00A7) { // WM_NCMBUTTONDOWN
int hitTest = m.WParam.ToInt32(); int hitTest = m.WParam.ToInt32();
if (hitTest == 2 || hitTest == 20){ // HTCAPTION, HTCLOSE if (hitTest == 2 || hitTest == 20) { // HTCAPTION, HTCLOSE
hasTemporarilyMoved = false; hasTemporarilyMoved = false;
MoveToVisibleLocation(); MoveToVisibleLocation();
return; return;
} }
} }
base.WndProc(ref m); base.WndProc(ref m);
} }
// event handlers // event handlers
private void Config_MuteToggled(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e) {
if (Config.MuteNotifications){ if (Config.MuteNotifications) {
PauseNotification(); PauseNotification();
} }
else{ else {
ResumeNotification(); ResumeNotification();
} }
} }
private void timerCursorCheck_Tick(object sender, EventArgs e){ private void timerCursorCheck_Tick(object sender, EventArgs e) {
if (!IsCursorOverNotificationArea){ if (!IsCursorOverNotificationArea) {
ResumeNotification(); ResumeNotification();
timerCursorCheck.Stop(); timerCursorCheck.Stop();
} }
} }
private void timerIdlePauseCheck_Tick(object sender, EventArgs e){ private void timerIdlePauseCheck_Tick(object sender, EventArgs e) {
if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds){ if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds) {
ResumeNotification(); ResumeNotification();
timerIdlePauseCheck.Stop(); timerIdlePauseCheck.Stop();
} }
} }
// notification methods // notification methods
public override void ShowNotification(DesktopNotification notification){ public override void ShowNotification(DesktopNotification notification) {
tweetQueue.Enqueue(notification); tweetQueue.Enqueue(notification);
if (!IsPaused){
UpdateTitle();
if (totalTime == 0){ if (!IsPaused) {
LoadNextNotification(); UpdateTitle();
}
}
needsTrim |= tweetQueue.Count >= TrimMinimum; if (totalTime == 0) {
AnalyticsFile.DesktopNotifications.Trigger(); LoadNextNotification();
} }
}
public override void HideNotification(){ needsTrim |= tweetQueue.Count >= TrimMinimum;
base.HideNotification(); }
tweetQueue.Clear();
if (needsTrim){ public override void HideNotification() {
tweetQueue.TrimExcess(); base.HideNotification();
needsTrim = false; tweetQueue.Clear();
}
hasTemporarilyMoved = false; if (needsTrim) {
} tweetQueue.TrimExcess();
needsTrim = false;
}
public override void FinishCurrentNotification(){ hasTemporarilyMoved = false;
if (tweetQueue.Count > 0){ }
LoadNextNotification();
}
else{
HideNotification();
}
}
public override void ResumeNotification(){ public override void FinishCurrentNotification() {
bool wasPaused = IsPaused; if (tweetQueue.Count > 0) {
base.ResumeNotification(); LoadNextNotification();
}
else {
HideNotification();
}
}
if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0){ public override void ResumeNotification() {
LoadNextNotification(); bool wasPaused = IsPaused;
} base.ResumeNotification();
}
private void LoadNextNotification(){ if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0) {
if (!IsNotificationVisible){ LoadNextNotification();
if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){ }
if (!timerCursorCheck.Enabled){ }
PauseNotification();
timerCursorCheck.Start();
}
return; private void LoadNextNotification() {
} if (!IsNotificationVisible) {
else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds){ if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit) {
if (!timerIdlePauseCheck.Enabled){ if (!timerCursorCheck.Enabled) {
PauseNotification(); PauseNotification();
timerIdlePauseCheck.Start(); timerCursorCheck.Start();
} }
return; return;
} }
} else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds) {
if (!timerIdlePauseCheck.Enabled) {
LoadTweet(tweetQueue.Dequeue()); PauseNotification();
} timerIdlePauseCheck.Start();
}
protected override void UpdateTitle(){ return;
base.UpdateTitle(); }
}
if (tweetQueue.Count > 0){ LoadTweet(tweetQueue.Dequeue());
Text = Text + " (" + tweetQueue.Count + " more left)"; }
}
}
protected override void OnNotificationReady(){ protected override void UpdateTitle() {
UpdateTitle(); base.UpdateTitle();
base.OnNotificationReady();
} if (tweetQueue.Count > 0) {
} Text = Text + " (" + tweetQueue.Count + " more left)";
}
}
protected override void OnNotificationReady() {
UpdateTitle();
base.OnNotificationReady();
}
}
} }

View File

@@ -1,98 +1,91 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.IO;
using System.Windows.Forms; using System.Threading.Tasks;
using CefSharp; using CefSharp;
using CefSharp.DevTools.Page;
using TweetDuck.Browser.Adapters; using TweetDuck.Browser.Adapters;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Notification.Screenshot{ namespace TweetDuck.Browser.Notification.Screenshot {
sealed class FormNotificationScreenshotable : FormNotificationBase{ sealed class FormNotificationScreenshotable : FormNotificationBase {
protected override bool CanDragWindow => false; protected override bool CanDragWindow => false;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private int height; 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; this.plugins = pluginManager;
int realWidth = BrowserUtils.Scale(width, DpiScale); int realWidth = BrowserUtils.Scale(width, DpiScale);
browser.RegisterJsBridge("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback)); browser.RegisterJsBridge("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
browser.LoadingStateChanged += (sender, args) => { browser.LoadingStateChanged += (sender, args) => {
if (args.IsLoading){ if (args.IsLoading) {
return; return;
} }
string script = Program.Resources.LoadSilent("screenshot.js"); string script = FileUtils.ReadFileOrNull(Path.Combine(Program.ResourcesPath, "notification/screenshot/screenshot.js"));
if (script == null){
this.InvokeAsyncSafe(callback);
return;
}
using IFrame frame = args.Browser.MainFrame; if (script == null) {
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot"); this.InvokeAsyncSafe(callback);
}; return;
}
SetNotificationSize(realWidth, 1024);
LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
}
protected override string GetTweetHTML(DesktopNotification tweet){ using IFrame frame = args.Browser.MainFrame;
string html = tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS); CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("1/*FRAMES*/", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
};
foreach(InjectedHTML injection in plugins.NotificationInjections){ SetNotificationSize(realWidth, 1024);
html = injection.InjectInto(html); LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
} }
return html; protected override string GetTweetHTML(DesktopNotification tweet) {
} return tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, Array.Empty<string>());
}
private void SetScreenshotHeight(int browserHeight){ private void SetScreenshotHeight(int browserHeight) {
this.height = BrowserUtils.Scale(browserHeight, SizeScale); this.height = BrowserUtils.Scale(browserHeight, SizeScale);
} }
public bool TakeScreenshot(bool ignoreHeightError = false){ public Task<Image> TakeScreenshot(bool ignoreHeightError = false) {
if (!ignoreHeightError){ if (!ignoreHeightError) {
if (height == 0){ if (height == 0) {
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK); 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); FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK);
return false; return null;
} }
} }
if (!WindowsUtils.IsAeroEnabled){ return Task.Run(TakeScreenshotImpl);
MoveToVisibleLocation(); // TODO make this look nicer I guess }
}
IntPtr context = NativeMethods.GetDC(this.Handle);
if (context == IntPtr.Zero){ private async Task<Image> TakeScreenshotImpl() {
FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK); if (this.height == 0) {
return false; return null;
} }
else{
using Bitmap bmp = new Bitmap(ClientSize.Width, Math.Max(1, height), PixelFormat.Format32bppRgb);
try{ Viewport viewport = new Viewport {
NativeMethods.RenderSourceIntoBitmap(context, bmp); Width = this.ClientSize.Width,
}finally{ Height = this.height,
NativeMethods.ReleaseDC(this.Handle, context); Scale = 1
} };
Clipboard.SetImage(bmp); byte[] data;
return true; using (var devToolsClient = browser.GetDevToolsClient()) {
} data = (await devToolsClient.Page.CaptureScreenshotAsync(CaptureScreenshotFormat.Png, clip: viewport)).Data;
} }
}
return Image.FromStream(new MemoryStream(data));
}
}
} }

View File

@@ -3,26 +3,26 @@ using System.Diagnostics.CodeAnalysis;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
namespace TweetDuck.Browser.Notification.Screenshot{ namespace TweetDuck.Browser.Notification.Screenshot {
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
sealed class ScreenshotBridge{ sealed class ScreenshotBridge {
private readonly Control owner; private readonly Control owner;
private readonly Action<int> safeSetHeight; private readonly Action<int> safeSetHeight;
private readonly Action safeTriggerScreenshot; 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.owner = owner;
this.safeSetHeight = safeSetHeight; this.safeSetHeight = safeSetHeight;
this.safeTriggerScreenshot = safeTriggerScreenshot; this.safeTriggerScreenshot = safeTriggerScreenshot;
} }
public void SetHeight(int tweetHeight){ public void SetHeight(int tweetHeight) {
owner.InvokeSafe(() => safeSetHeight(tweetHeight)); owner.InvokeSafe(() => safeSetHeight(tweetHeight));
} }
public void TriggerScreenshot(){ public void TriggerScreenshot() {
owner.InvokeSafe(safeTriggerScreenshot); owner.InvokeSafe(safeTriggerScreenshot);
} }
} }
} }

View File

@@ -7,142 +7,157 @@
#endif #endif
using System; using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using TweetDuck.Core.Utils; using TweetDuck.Utils;
#endif #endif
namespace TweetDuck.Browser.Notification.Screenshot{ namespace TweetDuck.Browser.Notification.Screenshot {
sealed class TweetScreenshotManager : IDisposable{ sealed class TweetScreenshotManager : IDisposable {
private readonly FormBrowser owner; private readonly FormBrowser owner;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly Timer timeout; private readonly Timer timeout;
private readonly Timer disposer; private readonly Timer disposer;
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
private readonly Timer debugger; private readonly Timer debugger;
private int frameCounter; private int frameCounter;
public const int WaitFrames = 60; public const int WaitFrames = 60;
#else #else
public const int WaitFrames = 5; public const int WaitFrames = 5;
#endif #endif
private FormNotificationScreenshotable screenshot;
public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){ private FormNotificationScreenshotable screenshot;
this.owner = owner;
this.plugins = pluginManager;
this.timeout = new Timer{ Interval = 8000 }; public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager) {
this.timeout.Tick += timeout_Tick; this.owner = owner;
this.plugins = pluginManager;
this.disposer = new Timer{ Interval = 1 }; this.timeout = new Timer { Interval = 8000 };
this.disposer.Tick += disposer_Tick; this.timeout.Tick += timeout_Tick;
#if GEN_SCREENSHOT_FRAMES this.disposer = new Timer { Interval = 1 };
this.debugger = new Timer{ Interval = 16 }; this.disposer.Tick += disposer_Tick;
this.debugger.Tick += debugger_Tick;
#endif
}
private void timeout_Tick(object sender, EventArgs e){ #if GEN_SCREENSHOT_FRAMES
timeout.Stop(); this.debugger = new Timer { Interval = 16 };
OnFinished(); this.debugger.Tick += debugger_Tick;
} #endif
}
private void disposer_Tick(object sender, EventArgs e){ private void timeout_Tick(object sender, EventArgs e) {
disposer.Stop(); timeout.Stop();
screenshot.Dispose(); OnFinished();
screenshot = null; }
}
public void Trigger(string html, int width){ private void disposer_Tick(object sender, EventArgs e) {
if (screenshot != null){ disposer.Stop();
return; screenshot.Dispose();
} screenshot = null;
}
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins, html, width); public void Trigger(string html, int width) {
screenshot.Show(); if (screenshot != null) {
timeout.Start(); return;
}
#if GEN_SCREENSHOT_FRAMES screenshot = new FormNotificationScreenshotable(Callback, owner, plugins, html, width);
StartDebugger(); screenshot.Show();
#endif timeout.Start();
#if !NO_HIDE_SCREENSHOTS #if GEN_SCREENSHOT_FRAMES
owner.IsWaiting = true; StartDebugger();
#endif #endif
}
private void Callback(){ #if !NO_HIDE_SCREENSHOTS
if (!timeout.Enabled){ owner.IsWaiting = true;
return; #endif
} }
timeout.Stop(); private void Callback() {
screenshot.TakeScreenshot(); if (!timeout.Enabled) {
return;
#if !NO_HIDE_SCREENSHOTS }
OnFinished();
#else
screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start();
#endif
}
private void OnFinished(){ timeout.Stop();
#if GEN_SCREENSHOT_FRAMES screenshot.TakeScreenshot().ContinueWith(HandleResult, TaskScheduler.FromCurrentSynchronizationContext());
debugger.Stop(); }
#endif
screenshot.Location = ControlExtensions.InvisibleLocation; private void HandleResult(Task<Image> task) {
owner.IsWaiting = false; if (task.IsFaulted) {
disposer.Start(); 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
screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start();
#endif
}
}
public void Dispose(){ private void OnFinished() {
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
debugger.Dispose(); debugger.Stop();
#endif #endif
timeout.Dispose(); screenshot.Location = ControlExtensions.InvisibleLocation;
disposer.Dispose(); owner.IsWaiting = false;
screenshot?.Dispose(); disposer.Start();
} }
#if GEN_SCREENSHOT_FRAMES public void Dispose() {
private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots"); #if GEN_SCREENSHOT_FRAMES
debugger.Dispose();
#endif
private void StartDebugger(){ timeout.Dispose();
frameCounter = 0; disposer.Dispose();
screenshot?.Dispose();
}
try{ #if GEN_SCREENSHOT_FRAMES
Directory.Delete(DebugScreenshotPath, true); private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots");
WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10);
}catch(DirectoryNotFoundException){}
Directory.CreateDirectory(DebugScreenshotPath);
debugger.Start();
}
private void debugger_Tick(object sender, EventArgs e){ private void StartDebugger() {
if (frameCounter < 63 && screenshot.TakeScreenshot(true)){ frameCounter = 0;
try{
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frameCounter) + ".png"), ImageFormat.Png); try {
}catch{ Directory.Delete(DebugScreenshotPath, true);
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frameCounter); WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10);
} } catch (DirectoryNotFoundException) {}
}
else{ Directory.CreateDirectory(DebugScreenshotPath);
debugger.Stop(); debugger.Start();
} }
}
#endif 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 {
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
}
} }

View File

@@ -1,48 +1,50 @@
using System.Drawing; using System;
using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Browser.Data;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Dialogs.Settings; using TweetDuck.Dialogs.Settings;
using TweetDuck.Management; using TweetDuck.Management;
namespace TweetDuck.Browser.Notification{ namespace TweetDuck.Browser.Notification {
static class SoundNotification{ static class SoundNotification {
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm"; public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
public static IResourceHandler CreateFileHandler(string path){
string mimeType = Path.GetExtension(path) switch{
".weba" => "audio/webm",
".webm" => "audio/webm",
".wav" => "audio/wav",
".ogg" => "audio/ogg",
".mp3" => "audio/mp3",
".flac" => "audio/flac",
".opus" => "audio/ogg; codecs=opus",
_ => null
};
try{ public static Func<IResourceHandler> CreateFileHandler(string path) {
return ResourceHandler.FromFilePath(path, mimeType); string mimeType = Path.GetExtension(path) switch {
}catch{ ".weba" => "audio/webm",
FormBrowser browser = FormManager.TryFind<FormBrowser>(); ".webm" => "audio/webm",
".wav" => "audio/wav",
".ogg" => "audio/ogg",
".mp3" => "audio/mp3",
".flac" => "audio/flac",
".opus" => "audio/ogg; codecs=opus",
_ => null
};
browser?.InvokeAsyncSafe(() => { try {
using FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n" + path, MessageBoxIcon.Error); return ResourceHandlers.ForBytes(File.ReadAllBytes(path), mimeType);
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused); } catch {
FormBrowser browser = FormManager.TryFind<FormBrowser>();
Button btnViewOptions = form.AddButton("View Options");
btnViewOptions.Width += 16;
btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y);
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){ browser?.InvokeAsyncSafe(() => {
browser.OpenSettings(typeof(TabSettingsSounds)); using FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n" + path, MessageBoxIcon.Error);
} form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
});
Button btnViewOptions = form.AddButton("View Options");
return null; btnViewOptions.Width += 16;
} btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y);
}
} if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions) {
browser.OpenSettings(typeof(TabSettingsSounds));
}
});
return null;
}
}
}
} }

View File

@@ -4,121 +4,121 @@ using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using Res = TweetDuck.Properties.Resources; using Res = TweetDuck.Properties.Resources;
namespace TweetDuck.Browser{ namespace TweetDuck.Browser {
sealed partial class TrayIcon : Component{ sealed partial class TrayIcon : Component {
public enum Behavior{ // keep order public enum Behavior { // keep order
Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined Disabled,
} DisplayOnly,
MinimizeToTray,
CloseToTray,
Combined
}
private static UserConfig Config => Program.Config.User; private static UserConfig Config => Program.Config.User;
public event EventHandler ClickRestore; public event EventHandler ClickRestore;
public event EventHandler ClickClose; public event EventHandler ClickClose;
public bool Visible{ public bool Visible {
get{ get { return notifyIcon.Visible; }
return notifyIcon.Visible;
}
set{ set {
notifyIcon.Visible = value; notifyIcon.Visible = value;
hasNotifications = false; hasNotifications = false;
UpdateIcon(); UpdateIcon();
} }
} }
public bool HasNotifications{ public bool HasNotifications {
get{ get { return hasNotifications; }
return hasNotifications;
}
set{ set {
if (hasNotifications != value){ if (hasNotifications != value) {
hasNotifications = value; hasNotifications = value;
UpdateIcon(); UpdateIcon();
} }
} }
} }
private readonly ContextMenu contextMenu; private readonly ContextMenu contextMenu;
private bool hasNotifications; private bool hasNotifications;
public TrayIcon(){ public TrayIcon() {
InitializeComponent(); InitializeComponent();
this.contextMenu = new ContextMenu(); this.contextMenu = new ContextMenu();
this.contextMenu.MenuItems.Add("Restore", menuItemRestore_Click); this.contextMenu.MenuItems.Add("Restore", menuItemRestore_Click);
this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click); this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click);
this.contextMenu.MenuItems.Add("Close", menuItemClose_Click); this.contextMenu.MenuItems.Add("Close", menuItemClose_Click);
this.contextMenu.Popup += contextMenu_Popup; this.contextMenu.Popup += contextMenu_Popup;
this.notifyIcon.ContextMenu = contextMenu;
this.notifyIcon.Text = Program.BrandName;
Config.MuteToggled += Config_MuteToggled; this.notifyIcon.ContextMenu = contextMenu;
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled; this.notifyIcon.Text = Program.BrandName;
}
public TrayIcon(IContainer container) : this(){ Config.MuteToggled += Config_MuteToggled;
container.Add(this); Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
} }
protected override void Dispose(bool disposing){ public TrayIcon(IContainer container) : this() {
if (disposing){ container.Add(this);
components?.Dispose(); }
contextMenu.Dispose();
}
base.Dispose(disposing); protected override void Dispose(bool disposing) {
} if (disposing) {
components?.Dispose();
contextMenu.Dispose();
}
private void UpdateIcon(){ base.Dispose(disposing);
if (Visible){ }
notifyIcon.Icon = hasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray;
}
}
// event handlers private void UpdateIcon() {
if (Visible) {
notifyIcon.Icon = hasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray;
}
}
private void Config_MuteToggled(object sender, EventArgs e){ // event handlers
UpdateIcon();
}
private void trayIcon_MouseClick(object sender, MouseEventArgs e){ private void Config_MuteToggled(object sender, EventArgs e) {
if (e.Button == MouseButtons.Left){ UpdateIcon();
menuItemRestore_Click(sender, e); }
}
}
private void contextMenu_Popup(object sender, EventArgs e){ private void trayIcon_MouseClick(object sender, MouseEventArgs e) {
contextMenu.MenuItems[1].Checked = Config.MuteNotifications; if (e.Button == MouseButtons.Left) {
} menuItemRestore_Click(sender, e);
}
}
private void menuItemRestore_Click(object sender, EventArgs e){ private void contextMenu_Popup(object sender, EventArgs e) {
ClickRestore?.Invoke(this, e); contextMenu.MenuItems[1].Checked = Config.MuteNotifications;
} }
private void menuItemMuteNotifications_Click(object sender, EventArgs e){ private void menuItemRestore_Click(object sender, EventArgs e) {
Config.MuteNotifications = !contextMenu.MenuItems[1].Checked; ClickRestore?.Invoke(this, e);
Config.Save(); }
}
private void menuItemClose_Click(object sender, EventArgs e){ private void menuItemMuteNotifications_Click(object sender, EventArgs e) {
ClickClose?.Invoke(this, e); Config.MuteNotifications = !contextMenu.MenuItems[1].Checked;
} Config.Save();
} }
static class BehaviorExtensions{ private void menuItemClose_Click(object sender, EventArgs e) {
public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior){ ClickClose?.Invoke(this, e);
return behavior != TrayIcon.Behavior.Disabled; }
} }
public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior){ static class BehaviorExtensions {
return behavior == TrayIcon.Behavior.MinimizeToTray || behavior == TrayIcon.Behavior.Combined; public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior) {
} return behavior != TrayIcon.Behavior.Disabled;
}
public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior){ public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior) {
return behavior == TrayIcon.Behavior.CloseToTray || behavior == TrayIcon.Behavior.Combined; return behavior == TrayIcon.Behavior.MinimizeToTray || behavior == TrayIcon.Behavior.Combined;
} }
}
public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior) {
return behavior == TrayIcon.Behavior.CloseToTray || behavior == TrayIcon.Behavior.Combined;
}
}
} }

View File

@@ -19,271 +19,254 @@ using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Browser{ namespace TweetDuck.Browser {
sealed class TweetDeckBrowser : IDisposable{ sealed class TweetDeckBrowser : IDisposable {
private static UserConfig Config => Program.Config.User; private static UserConfig Config => Program.Config.User;
private const string ErrorUrl = "http://td/error"; private const string NamespaceTweetDeck = "tweetdeck";
private const string TwitterStyleUrl = "https://abs.twimg.com/tduck/css";
public bool Ready { get; private set; } public bool Ready { get; private set; }
public bool Enabled{ public bool Enabled {
get => browser.Enabled; get => browser.Enabled;
set => browser.Enabled = value; set => browser.Enabled = value;
} }
public bool IsTweetDeckWebsite{ public bool IsTweetDeckWebsite {
get{ get {
if (!Ready){ if (!Ready) {
return false; return false;
} }
using IFrame frame = browser.GetBrowser().MainFrame; using IFrame frame = browser.GetBrowser().MainFrame;
return TwitterUrls.IsTweetDeck(frame.Url); return TwitterUrls.IsTweetDeck(frame.Url);
} }
} }
private readonly ChromiumWebBrowser browser;
private readonly ResourceHandlers resourceHandlers;
private string prevSoundNotificationPath = null; private readonly ChromiumWebBrowser browser;
private readonly ResourceHandlers resourceHandlers;
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge){ private string prevSoundNotificationPath = null;
var resourceRequestHandler = new ResourceRequestHandlerBrowser();
resourceHandlers = resourceRequestHandler.ResourceHandlers;
resourceHandlers.Register(FormNotificationBase.AppLogo); public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge) {
resourceHandlers.Register(TwitterUtils.LoadingSpinner); var resourceRequestHandler = new ResourceRequestHandlerBrowser();
resourceHandlers = resourceRequestHandler.ResourceHandlers;
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser(); resourceHandlers.Register(FormNotificationBase.AppLogo);
resourceHandlers.Register(TwitterUtils.LoadingSpinner);
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(),
RequestHandler = requestHandler,
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
};
this.browser.LoadingStateChanged += browser_LoadingStateChanged; RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
this.browser.FrameLoadStart += browser_FrameLoadStart;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
this.browser.RegisterJsBridge("$TD", tdBridge); this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
this.browser.RegisterJsBridge("$TDU", updateBridge); DialogHandler = new FileDialogHandler(),
DragHandler = new DragHandlerBrowser(requestHandler),
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb(); MenuHandler = new ContextMenuBrowser(owner),
this.browser.Dock = DockStyle.None; JsDialogHandler = new JavaScriptDialogHandler(),
this.browser.Location = ControlExtensions.InvisibleLocation; KeyboardHandler = new KeyboardHandlerBrowser(owner),
this.browser.SetupZoomEvents(); LifeSpanHandler = new CustomLifeSpanHandler(),
RequestHandler = requestHandler,
owner.Controls.Add(browser); ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(browser, TwitterUrls.IsTweetDeck)); };
Config.MuteToggled += Config_MuteToggled;
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;
}
// setup and management this.browser.LoadingStateChanged += browser_LoadingStateChanged;
this.browser.FrameLoadStart += browser_FrameLoadStart;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
public void PrepareSize(Size size){ this.browser.RegisterJsBridge("$TD", tdBridge);
if (!Ready){ this.browser.RegisterJsBridge("$TDU", updateBridge);
browser.Size = size;
}
}
private void OnBrowserReady(){ this.browser.Dock = DockStyle.None;
if (!Ready){ this.browser.Location = ControlExtensions.InvisibleLocation;
browser.Location = Point.Empty; this.browser.SetupZoomEvents();
browser.Dock = DockStyle.Fill;
Ready = true;
}
}
public void Focus(){ owner.Controls.Add(browser);
browser.Focus(); plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(browser, TwitterUrls.IsTweetDeck));
}
public void Dispose(){ Config.MuteToggled += Config_MuteToggled;
Config.MuteToggled -= Config_MuteToggled; Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;
Config.SoundNotificationChanged -= Config_SoundNotificationInfoChanged; }
browser.Dispose();
}
// event handlers // setup and management
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ public void PrepareSize(Size size) {
if (!e.IsLoading){ if (!Ready) {
foreach(string word in TwitterUtils.DictionaryWords){ browser.Size = size;
browser.AddWordToDictionary(word); }
} }
browser.BeginInvoke(new Action(OnBrowserReady)); private void OnBrowserReady() {
browser.LoadingStateChanged -= browser_LoadingStateChanged; if (!Ready) {
} browser.Location = Point.Empty;
} browser.Dock = DockStyle.Fill;
Ready = true;
}
}
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){ public void Focus() {
IFrame frame = e.Frame; browser.Focus();
}
if (frame.IsMain){ public void Dispose() {
string url = frame.Url; Config.MuteToggled -= Config_MuteToggled;
Config.SoundNotificationChanged -= Config_SoundNotificationInfoChanged;
if (TwitterUrls.IsTwitter(url)){ browser.Dispose();
string css = Program.Resources.Load("styles/twitter.css"); }
resourceHandlers.Register(TwitterStyleUrl, ResourceHandler.FromString(css, mimeType: "text/css"));
CefScriptExecutor.RunFile(frame, "twitter.js"); // event handlers
}
if (!TwitterUrls.IsTwitterLogin2Factor(url)){
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
IFrame frame = e.Frame; if (!e.IsLoading) {
string url = frame.Url; foreach (string word in TwitterUtils.DictionaryWords) {
browser.AddWordToDictionary(word);
}
if (frame.IsMain){ browser.BeginInvoke(new Action(OnBrowserReady));
if (TwitterUrls.IsTweetDeck(url)){ browser.LoadingStateChanged -= browser_LoadingStateChanged;
UpdateProperties(); }
CefScriptExecutor.RunFile(frame, "code.js"); }
InjectBrowserCSS(); private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e) {
ReinjectCustomCSS(Config.CustomBrowserCSS); IFrame frame = e.Frame;
Config_SoundNotificationInfoChanged(null, EventArgs.Empty);
TweetDeckBridge.ResetStaticProperties(); if (frame.IsMain) {
string url = frame.Url;
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){ if (TwitterUrls.IsTweetDeck(url) || (TwitterUrls.IsTwitter(url) && !TwitterUrls.IsTwitterLogin2Factor(url))) {
CefScriptExecutor.RunScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr"); frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
} }
}
}
if (Config.FirstRun){ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
CefScriptExecutor.RunFile(frame, "introduction.js"); IFrame frame = e.Frame;
} string url = frame.Url;
}
CefScriptExecutor.RunFile(frame, "update.js"); if (frame.IsMain) {
} if (TwitterUrls.IsTweetDeck(url)) {
UpdateProperties();
CefScriptExecutor.RunBootstrap(frame, NamespaceTweetDeck);
if (url == ErrorUrl){ TweetDeckBridge.ResetStaticProperties();
resourceHandlers.Unregister(ErrorUrl);
}
}
private void browser_LoadError(object sender, LoadErrorEventArgs e){ if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
if (e.ErrorCode == CefErrorCode.Aborted){ CefScriptExecutor.RunScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
return; }
}
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){ if (Config.FirstRun) {
string errorPage = Program.Resources.LoadSilent("pages/error.html"); CefScriptExecutor.RunBootstrap(frame, "introduction");
}
}
else if (TwitterUrls.IsTwitter(url)) {
CefScriptExecutor.RunBootstrap(frame, "login");
}
if (errorPage != null){ CefScriptExecutor.RunBootstrap(frame, "update");
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode); }
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty); }
resourceHandlers.Register(ErrorUrl, ResourceHandler.FromString(errorPage.Replace("{err}", errorTitle))); private void browser_LoadError(object sender, LoadErrorEventArgs e) {
browser.Load(ErrorUrl); if (e.ErrorCode == CefErrorCode.Aborted) {
} return;
} }
}
private void Config_MuteToggled(object sender, EventArgs e){ if (!e.FailedUrl.StartsWith("td://", StringComparison.Ordinal)) {
UpdateProperties(); string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
} string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
browser.Load("td://resources/error/error.html#" + Uri.EscapeDataString(errorTitle));
}
}
private void Config_SoundNotificationInfoChanged(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e) {
const string soundUrl = "https://ton.twimg.com/tduck/updatesnd"; UpdateProperties();
}
bool hasCustomSound = Config.IsCustomSoundNotificationSet; private void Config_SoundNotificationInfoChanged(object sender, EventArgs e) {
string newNotificationPath = Config.NotificationSoundPath; const string soundUrl = "https://ton.twimg.com/tduck/updatesnd";
if (prevSoundNotificationPath != newNotificationPath){
prevSoundNotificationPath = newNotificationPath;
if (hasCustomSound){ bool hasCustomSound = Config.IsCustomSoundNotificationSet;
resourceHandlers.Register(soundUrl, SoundNotification.CreateFileHandler(newNotificationPath)); string newNotificationPath = Config.NotificationSoundPath;
}
else{
resourceHandlers.Unregister(soundUrl);
}
}
browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Config.NotificationSoundVolume); if (prevSoundNotificationPath != newNotificationPath) {
} prevSoundNotificationPath = newNotificationPath;
// external handling if (hasCustomSound) {
resourceHandlers.Register(soundUrl, SoundNotification.CreateFileHandler(newNotificationPath));
}
else {
resourceHandlers.Unregister(soundUrl);
}
}
public void HideVideoOverlay(bool focus){ browser.ExecuteJsAsync("TDGF_setSoundNotificationData", hasCustomSound, Config.NotificationSoundVolume);
if (focus){ }
browser.GetBrowser().GetHost().SendFocusEvent(true);
}
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()"); // external handling
}
// javascript calls public void HideVideoOverlay(bool focus) {
if (focus) {
browser.GetBrowser().GetHost().SendFocusEvent(true);
}
public void ReloadToTweetDeck(){ browser.ExecuteJsAsync("$('#td-video-player-overlay').remove()");
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUrls.TweetDeck}'"); }
}
public void UpdateProperties(){ // javascript calls
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser));
}
public void InjectBrowserCSS(){ public void ReloadToTweetDeck() {
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", Program.Resources.Load("styles/browser.css")?.TrimEnd() ?? string.Empty); browser.ExecuteJsAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUrls.TweetDeck}'");
} }
public void ReinjectCustomCSS(string css){ public void OnModulesLoaded(string moduleNamespace) {
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty); if (moduleNamespace == NamespaceTweetDeck) {
} ReinjectCustomCSS(Config.CustomBrowserCSS);
Config_SoundNotificationInfoChanged(null, EventArgs.Empty);
}
}
public void OnMouseClickExtra(IntPtr param){ public void UpdateProperties() {
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF); browser.ExecuteJsAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser));
} }
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){ public void ReinjectCustomCSS(string css) {
browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl); browser.ExecuteJsAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
} }
public void AddSearchColumn(string query){ public void OnMouseClickExtra(IntPtr param) {
browser.ExecuteScriptAsync("TDGF_performSearch", query); browser.ExecuteJsAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF);
} }
public void TriggerTweetScreenshot(){ public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()"); browser.ExecuteJsAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
} }
public void ReloadColumns(){ public void AddSearchColumn(string query) {
browser.ExecuteScriptAsync("TDGF_reloadColumns()"); browser.ExecuteJsAsync("TDGF_performSearch", query);
} }
public void PlaySoundNotification(){ public void TriggerTweetScreenshot(string columnId, string chirpId) {
browser.ExecuteScriptAsync("TDGF_playSoundNotification()"); browser.ExecuteJsAsync("TDGF_triggerScreenshot", columnId, chirpId);
} }
public void ApplyROT13(){ public void ReloadColumns() {
browser.ExecuteScriptAsync("TDGF_applyROT13()"); browser.ExecuteJsAsync("TDGF_reloadColumns()");
} }
public void ShowUpdateNotification(string versionTag, string releaseNotes){ public void PlaySoundNotification() {
browser.ExecuteScriptAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes))); browser.ExecuteJsAsync("TDGF_playSoundNotification()");
} }
public void OpenDevTools(){ public void ApplyROT13() {
browser.OpenDevToolsCustom(); browser.ExecuteJsAsync("TDGF_applyROT13()");
} }
}
public void ShowUpdateNotification(string versionTag, string releaseNotes) {
browser.ExecuteJsAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes)));
}
public void OpenDevTools() {
browser.OpenDevToolsCustom();
}
}
} }

View File

@@ -1,48 +1,48 @@
using System; using System;
using TweetLib.Core.Collections; using TweetLib.Core.Collections;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration {
static class Arguments{ static class Arguments {
// public args // public args
public const string ArgDataFolder = "-datafolder"; public const string ArgDataFolder = "-datafolder";
public const string ArgLogging = "-log"; public const string ArgLogging = "-log";
public const string ArgIgnoreGDPR = "-nogdpr"; public const string ArgIgnoreGDPR = "-nogdpr";
public const string ArgFreeze = "-freeze"; public const string ArgFreeze = "-freeze";
// internal args // internal args
public const string ArgRestart = "-restart"; public const string ArgRestart = "-restart";
public const string ArgImportCookies = "-importcookies"; public const string ArgImportCookies = "-importcookies";
public const string ArgDeleteCookies = "-deletecookies"; public const string ArgDeleteCookies = "-deletecookies";
public const string ArgUpdated = "-updated"; public const string ArgUpdated = "-updated";
// class data and methods // class data and methods
private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs()); 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); return Current.HasFlag(flag);
} }
public static string GetValue(string key){ public static string GetValue(string key) {
return Current.GetValue(key); return Current.GetValue(key);
} }
public static CommandLineArgs GetCurrentClean(){ public static CommandLineArgs GetCurrentClean() {
CommandLineArgs args = Current.Clone(); CommandLineArgs args = Current.Clone();
args.RemoveFlag(ArgRestart); args.RemoveFlag(ArgRestart);
args.RemoveFlag(ArgImportCookies); args.RemoveFlag(ArgImportCookies);
args.RemoveFlag(ArgDeleteCookies); args.RemoveFlag(ArgDeleteCookies);
args.RemoveFlag(ArgUpdated); args.RemoveFlag(ArgUpdated);
return args; return args;
} }
public static CommandLineArgs GetCurrentForInstaller(){ public static CommandLineArgs GetCurrentForInstaller() {
CommandLineArgs args = GetCurrentClean(); CommandLineArgs args = GetCurrentClean();
args.AddFlag(ArgUpdated); args.AddFlag(ArgUpdated);
return args; return args;
} }
public static string GetCurrentForInstallerCmd(){ public static string GetCurrentForInstallerCmd() {
return GetCurrentForInstaller().ToString().Replace("\"", "::"); return GetCurrentForInstaller().ToString().Replace("\"", "::");
} }
} }
} }

View File

@@ -6,77 +6,77 @@ using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Systems.Configuration; using TweetLib.Core.Systems.Configuration;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration {
sealed class ConfigManager : IConfigManager{ sealed class ConfigManager : IConfigManager {
public UserConfig User { get; } public UserConfig User { get; }
public SystemConfig System { get; } public SystemConfig System { get; }
public PluginConfig Plugins { get; } public PluginConfig Plugins { get; }
public event EventHandler ProgramRestartRequested;
private readonly FileConfigInstance<UserConfig> infoUser; public event EventHandler ProgramRestartRequested;
private readonly FileConfigInstance<SystemConfig> infoSystem;
private readonly PluginConfigInstance<PluginConfig> infoPlugins;
private readonly IConfigInstance<BaseConfig>[] infoList; private readonly FileConfigInstance<UserConfig> infoUser;
private readonly FileConfigInstance<SystemConfig> infoSystem;
private readonly PluginConfigInstance<PluginConfig> infoPlugins;
public ConfigManager(){ private readonly IConfigInstance<BaseConfig>[] infoList;
User = new UserConfig(this);
System = new SystemConfig(this);
Plugins = new PluginConfig(this);
infoList = new IConfigInstance<BaseConfig>[]{
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins)
};
// TODO refactor further public ConfigManager() {
User = new UserConfig(this);
System = new SystemConfig(this);
Plugins = new PluginConfig(this);
infoUser.Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter); 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)
};
infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{ // TODO refactor further
ConvertToString = value => $"{value.X} {value.Y}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Point(elements[0], elements[1]);
}
});
infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
ConvertToString = value => $"{value.Width} {value.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Size(elements[0], elements[1]);
}
});
}
public void LoadAll(){ infoUser.Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
infoUser.Load();
infoSystem.Load();
infoPlugins.Load();
}
public void SaveAll(){ infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point> {
infoUser.Save(); ConvertToString = value => $"{value.X} {value.Y}",
infoSystem.Save(); ConvertToObject = value => {
infoPlugins.Save(); int[] elements = StringUtils.ParseInts(value, ' ');
} return new Point(elements[0], elements[1]);
}
});
public void ReloadAll(){ infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size> {
infoUser.Reload(); ConvertToString = value => $"{value.Width} {value.Height}",
infoSystem.Reload(); ConvertToObject = value => {
infoPlugins.Reload(); int[] elements = StringUtils.ParseInts(value, ' ');
} return new Size(elements[0], elements[1]);
}
});
}
void IConfigManager.TriggerProgramRestartRequested(){ public void LoadAll() {
ProgramRestartRequested?.Invoke(this, EventArgs.Empty); infoUser.Load();
} infoSystem.Load();
infoPlugins.Load();
}
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){ public void SaveAll() {
Type instanceType = instance.GetType(); infoUser.Save();
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null infoSystem.Save();
} infoPlugins.Save();
} }
public void ReloadAll() {
infoUser.Reload();
infoSystem.Reload();
infoPlugins.Reload();
}
void IConfigManager.TriggerProgramRestartRequested() {
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
}
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance) {
Type instanceType = instance.GetType();
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
}
}
} }

View File

@@ -5,47 +5,47 @@ using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.Plugins.Events; using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Systems.Configuration; using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration {
sealed class PluginConfig : BaseConfig, IPluginConfig{ sealed class PluginConfig : BaseConfig, IPluginConfig {
private static readonly string[] DefaultDisabled = { private static readonly string[] DefaultDisabled = {
"official/clear-columns", "official/clear-columns",
"official/reply-account" "official/reply-account"
}; };
// CONFIGURATION DATA // CONFIGURATION DATA
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled); private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
// EVENTS // EVENTS
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
// END OF CONFIG
public PluginConfig(IConfigManager configManager) : base(configManager){} public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){ // END OF CONFIG
return new PluginConfig(configManager);
}
// INTERFACE IMPLEMENTATION public PluginConfig(IConfigManager configManager) : base(configManager) {}
IEnumerable<string> IPluginConfig.DisabledPlugins => disabled; protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
return new PluginConfig(configManager);
}
void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins){ // INTERFACE IMPLEMENTATION
disabled.Clear();
disabled.UnionWith(newDisabledPlugins);
}
public void SetEnabled(Plugin plugin, bool enabled){ IEnumerable<string> IPluginConfig.DisabledPlugins => disabled;
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
Save();
}
}
public bool IsEnabled(Plugin plugin){ void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins) {
return !disabled.Contains(plugin.Identifier); disabled.Clear();
} disabled.UnionWith(newDisabledPlugins);
} }
public void SetEnabled(Plugin plugin, bool enabled) {
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))) {
PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
Save();
}
}
public bool IsEnabled(Plugin plugin) {
return !disabled.Contains(plugin.Identifier);
}
}
} }

View File

@@ -1,28 +1,27 @@
using TweetLib.Core.Systems.Configuration; using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration {
sealed class SystemConfig : BaseConfig{ sealed class SystemConfig : BaseConfig {
// CONFIGURATION DATA
// CONFIGURATION DATA private bool _hardwareAcceleration = true;
public bool _hardwareAcceleration = true;
public bool ClearCacheAutomatically { get; set; } = true;
public int ClearCacheThreshold { get; set; } = 250;
// SPECIAL PROPERTIES public bool ClearCacheAutomatically { get; set; } = true;
public int ClearCacheThreshold { get; set; } = 250;
public bool HardwareAcceleration{
get => _hardwareAcceleration;
set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value);
}
// END OF CONFIG
public SystemConfig(IConfigManager configManager) : base(configManager){} // SPECIAL PROPERTIES
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){ public bool HardwareAcceleration {
return new SystemConfig(configManager); get => _hardwareAcceleration;
} set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value);
} }
// END OF CONFIG
public SystemConfig(IConfigManager configManager) : base(configManager) {}
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
return new SystemConfig(configManager);
}
}
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing; using System.Drawing;
using TweetDuck.Browser; using TweetDuck.Browser;
using TweetDuck.Browser.Data; using TweetDuck.Browser.Data;
@@ -7,146 +8,161 @@ using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Configuration; using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration {
sealed class UserConfig : BaseConfig{ sealed class UserConfig : BaseConfig {
// CONFIGURATION DATA
// CONFIGURATION DATA
public bool FirstRun { get; set; } = true; public bool FirstRun { get; set; } = true;
public bool AllowDataCollection { get; set; } = false;
public WindowState BrowserWindow { get; set; } = new WindowState(); [SuppressMessage("ReSharper", "UnusedMember.Global")]
public Size PluginsWindowSize { get; set; } = Size.Empty; public bool AllowDataCollection { get; set; } = false;
public bool ExpandLinksOnHover { get; set; } = true; public WindowState BrowserWindow { get; set; } = new WindowState();
public bool FocusDmInput { get; set; } = true; public Size PluginsWindowSize { get; set; } = Size.Empty;
public bool OpenSearchInFirstColumn { get; set; } = true;
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool BestImageQuality { get; set; } = true;
public bool EnableAnimatedImages { get; set; } = true;
private bool _enableSmoothScrolling = true; public bool ExpandLinksOnHover { get; set; } = true;
private bool _enableTouchAdjustment = false; public bool FocusDmInput { get; set; } = true;
private string _customCefArgs = null; public bool OpenSearchInFirstColumn { get; set; } = true;
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool BestImageQuality { get; set; } = true;
public bool EnableAnimatedImages { get; set; } = true;
public string BrowserPath { get; set; } = null; private bool _enableSmoothScrolling = true;
public string BrowserPathArgs { get; set; } = null; private bool _enableTouchAdjustment = false;
public bool IgnoreTrackingUrlWarning { get; set; } = false; private bool _enableColorProfileDetection = false;
public string SearchEngineUrl { get; set; } = null; private string _customCefArgs = null;
private int _zoomLevel = 100;
public string VideoPlayerPath { get; set; } = null;
public string VideoPlayerPathArgs { get; set; } = null;
public int VideoPlayerVolume { get; set; } = 50;
public bool EnableSpellCheck { get; set; } = false;
private string _spellCheckLanguage = "en-US";
public string TranslationTarget { get; set; } = "en"; public string BrowserPath { get; set; } = null;
public int CalendarFirstDay { get; set; } = -1; public string BrowserPathArgs { get; set; } = null;
public bool IgnoreTrackingUrlWarning { get; set; } = false;
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled; public string SearchEngineUrl { get; set; } = null;
public bool EnableTrayHighlight { get; set; } = true; private int _zoomLevel = 100;
public bool EnableUpdateCheck { get; set; } = true; public string VideoPlayerPath { get; set; } = null;
public string DismissedUpdate { get; set; } = null; public string VideoPlayerPathArgs { get; set; } = null;
public int VideoPlayerVolume { get; set; } = 50;
public bool DisplayNotificationColumn { get; set; } = false; public bool EnableSpellCheck { get; set; } = false;
public bool NotificationMediaPreviews { get; set; } = true; private string _spellCheckLanguage = "en-US";
public bool NotificationSkipOnLinkClick { get; set; } = false;
public bool NotificationNonIntrusiveMode { get; set; } = true;
public int NotificationIdlePauseSeconds { get; set; } = 0;
public bool DisplayNotificationTimer { get; set; } = true; public string TranslationTarget { get; set; } = "en";
public bool NotificationTimerCountDown { get; set; } = false; public int CalendarFirstDay { get; set; } = -1;
public int NotificationDurationValue { get; set; } = 25;
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight; private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation; public bool EnableTrayHighlight { get; set; } = true;
public int NotificationDisplay { get; set; } = 0;
public int NotificationEdgeDistance { get; set; } = 8;
public int NotificationWindowOpacity { get; set; } = 100;
public DesktopNotification.Size NotificationSize { get; set; } = DesktopNotification.Size.Auto; public bool EnableUpdateCheck { get; set; } = true;
public Size CustomNotificationSize { get; set; } = Size.Empty; public string DismissedUpdate { get; set; } = null;
public int NotificationScrollSpeed { get; set; } = 100;
private string _notificationSoundPath;
private int _notificationSoundVolume = 100;
private bool _muteNotifications; public bool DisplayNotificationColumn { get; set; } = false;
public bool NotificationMediaPreviews { get; set; } = true;
public bool NotificationSkipOnLinkClick { get; set; } = false;
public bool NotificationNonIntrusiveMode { get; set; } = true;
public int NotificationIdlePauseSeconds { get; set; } = 0;
public string CustomBrowserCSS { get; set; } = null; public bool DisplayNotificationTimer { get; set; } = true;
public string CustomNotificationCSS { get; set; } = null; public bool NotificationTimerCountDown { get; set; } = false;
public int NotificationDurationValue { get; set; } = 25;
public bool DevToolsWindowOnTop { get; set; } = true; public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
// SPECIAL PROPERTIES public int NotificationDisplay { get; set; } = 0;
public int NotificationEdgeDistance { get; set; } = 8;
public int NotificationWindowOpacity { get; set; } = 100;
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation; public DesktopNotification.Size NotificationSize { get; set; } = DesktopNotification.Size.Auto;
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty; public Size CustomNotificationSize { get; set; } = Size.Empty;
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty; public int NotificationScrollSpeed { get; set; } = 100;
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default; private string _notificationSoundPath;
private int _notificationSoundVolume = 100;
public string NotificationSoundPath{
get => _notificationSoundPath ?? string.Empty;
set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
}
public int NotificationSoundVolume{
get => _notificationSoundVolume;
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
}
public bool MuteNotifications{ private bool _muteNotifications;
get => _muteNotifications;
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
}
public int ZoomLevel{ public string CustomBrowserCSS { get; set; } = null;
get => _zoomLevel; public string CustomNotificationCSS { get; set; } = null;
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
}
public TrayIcon.Behavior TrayBehavior{
get => _trayBehavior;
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
}
public bool EnableSmoothScrolling{
get => _enableSmoothScrolling;
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value);
}
public bool EnableTouchAdjustment{ private bool _useSystemProxyForAllConnections;
get => _enableTouchAdjustment;
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
}
public string CustomCefArgs{ public bool DevToolsInContextMenu { get; set; } = false;
get => _customCefArgs; public bool DevToolsWindowOnTop { get; set; } = true;
set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value);
}
public string SpellCheckLanguage{ // SPECIAL PROPERTIES
get => _spellCheckLanguage;
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value);
}
// EVENTS public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
public event EventHandler MuteToggled; public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
public event EventHandler ZoomLevelChanged;
public event EventHandler TrayBehaviorChanged;
public event EventHandler SoundNotificationChanged;
// END OF CONFIG public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
public UserConfig(IConfigManager configManager) : base(configManager){}
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){ public string NotificationSoundPath {
return new UserConfig(configManager); get => _notificationSoundPath ?? string.Empty;
} set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
} }
public int NotificationSoundVolume {
get => _notificationSoundVolume;
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
}
public bool MuteNotifications {
get => _muteNotifications;
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
}
public int ZoomLevel {
get => _zoomLevel;
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
}
public TrayIcon.Behavior TrayBehavior {
get => _trayBehavior;
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
}
public bool EnableSmoothScrolling {
get => _enableSmoothScrolling;
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value);
}
public bool EnableTouchAdjustment {
get => _enableTouchAdjustment;
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
}
public 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 {
get => _spellCheckLanguage;
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value);
}
// EVENTS
public event EventHandler MuteToggled;
public event EventHandler ZoomLevelChanged;
public event EventHandler TrayBehaviorChanged;
public event EventHandler SoundNotificationChanged;
// END OF CONFIG
public UserConfig(IConfigManager configManager) : base(configManager) {}
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
return new UserConfig(configManager);
}
}
} }

View File

@@ -3,77 +3,77 @@ using System.Drawing;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
static class ControlExtensions{ static class ControlExtensions {
public static readonly Point InvisibleLocation = new Point(-32000, -32000); public static readonly Point InvisibleLocation = new Point(-32000, -32000);
public static void InvokeSafe(this Control control, Action func){ public static void InvokeSafe(this Control control, Action func) {
if (control.InvokeRequired){ if (control.InvokeRequired) {
control.Invoke(func); control.Invoke(func);
} }
else{ else {
func(); func();
} }
} }
public static void InvokeAsyncSafe(this Control control, Action func){ public static void InvokeAsyncSafe(this Control control, Action func) {
control.BeginInvoke(func); control.BeginInvoke(func);
} }
public static float GetDPIScale(this Control control){ public static float GetDPIScale(this Control control) {
using Graphics graphics = control.CreateGraphics(); using Graphics graphics = control.CreateGraphics();
return graphics.DpiY / 96F; 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)); 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)); 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){ public static void SetValueInstant(this ProgressBar bar, int value) {
if (value == bar.Maximum){ if (value == bar.Maximum) {
bar.Value = value; bar.Value = value;
bar.Value = value - 1; bar.Value = value - 1;
bar.Value = value; bar.Value = value;
} }
else{ else {
bar.Value = value + 1; bar.Value = value + 1;
bar.Value = value; bar.Value = value;
} }
} }
public static void SetValueSafe(this NumericUpDown numUpDown, int value){ public static void SetValueSafe(this NumericUpDown numUpDown, int value) {
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){ if (value >= numUpDown.Minimum && value <= numUpDown.Maximum) {
numUpDown.Value = value; numUpDown.Value = value;
} }
} }
public static void SetValueSafe(this TrackBar trackBar, int value){ public static void SetValueSafe(this TrackBar trackBar, int value) {
if (value >= trackBar.Minimum && value <= trackBar.Maximum){ if (value >= trackBar.Minimum && value <= trackBar.Maximum) {
trackBar.Value = value; trackBar.Value = value;
} }
} }
public static bool AlignValueToTick(this TrackBar trackBar){ public static bool AlignValueToTick(this TrackBar trackBar) {
if (trackBar.Value % trackBar.SmallChange != 0){ if (trackBar.Value % trackBar.SmallChange != 0) {
trackBar.Value = trackBar.SmallChange * (int)Math.Floor(((double)trackBar.Value / trackBar.SmallChange) + 0.5); trackBar.Value = trackBar.SmallChange * (int) Math.Floor(((double) trackBar.Value / trackBar.SmallChange) + 0.5);
return false; return false;
} }
return true; return true;
} }
public static void EnableMultilineShortcuts(this TextBox textBox){ public static void EnableMultilineShortcuts(this TextBox textBox) {
textBox.KeyDown += (sender, args) => { textBox.KeyDown += (sender, args) => {
if (args.Control && args.KeyCode == Keys.A){ if (args.Control && args.KeyCode == Keys.A) {
((TextBox)sender).SelectAll(); ((TextBox) sender).SelectAll();
args.SuppressKeyPress = true; args.SuppressKeyPress = true;
args.Handled = true; args.Handled = true;
} }
}; };
} }
} }
} }

View File

@@ -1,16 +1,16 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class FlatButton : Button{ sealed class FlatButton : Button {
protected override bool ShowFocusCues => false; protected override bool ShowFocusCues => false;
public FlatButton(){ public FlatButton() {
GotFocus += FlatButton_GotFocus; 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); NotifyDefault(false);
} }
} }
} }

View File

@@ -2,37 +2,37 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class FlatProgressBar : ProgressBar{ sealed class FlatProgressBar : ProgressBar {
private readonly SolidBrush brush; private readonly SolidBrush brush;
public FlatProgressBar(){ public FlatProgressBar() {
brush = new SolidBrush(Color.White); brush = new SolidBrush(Color.White);
SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, 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))); ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
} }
protected override void OnPaint(PaintEventArgs e){ protected override void OnPaint(PaintEventArgs e) {
if (brush.Color != ForeColor){ if (brush.Color != ForeColor) {
brush.Color = ForeColor; brush.Color = ForeColor;
} }
Rectangle rect = e.ClipRectangle; 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); e.Graphics.FillRectangle(brush, rect);
} }
protected override void Dispose(bool disposing){ protected override void Dispose(bool disposing) {
base.Dispose(disposing); base.Dispose(disposing);
if (disposing){ if (disposing) {
brush.Dispose(); brush.Dispose();
} }
} }
} }
} }

View File

@@ -1,14 +1,14 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel{ sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel {
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m) {
if (m.Msg == 0x85){ // WM_NCPAINT if (m.Msg == 0x85) { // WM_NCPAINT
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
} }
base.WndProc(ref m); base.WndProc(ref m);
} }
} }
} }

View File

@@ -2,21 +2,21 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class LabelVertical : Label{ sealed class LabelVertical : Label {
public int LineHeight { get; set; } public int LineHeight { get; set; }
protected override void OnPaint(PaintEventArgs e){ protected override void OnPaint(PaintEventArgs e) {
int y = (int)Math.Floor((ClientRectangle.Height - Text.Length * LineHeight) / 2F) - 1; int y = (int) Math.Floor((ClientRectangle.Height - Text.Length * LineHeight) / 2F) - 1;
using Brush brush = new SolidBrush(ForeColor); using Brush brush = new SolidBrush(ForeColor);
foreach(char chr in Text){ foreach (char chr in Text) {
string str = chr.ToString(); string str = chr.ToString();
float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F; float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F;
e.Graphics.DrawString(str, Font, brush, x, y); e.Graphics.DrawString(str, Font, brush, x, y);
y += LineHeight; y += LineHeight;
} }
} }
} }
} }

View File

@@ -1,18 +1,18 @@
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class NumericUpDownEx : NumericUpDown{ sealed class NumericUpDownEx : NumericUpDown {
public string TextSuffix { get; set ; } public string TextSuffix { get; set ; }
protected override void UpdateEditText(){ protected override void UpdateEditText() {
base.UpdateEditText(); base.UpdateEditText();
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){ if (LicenseManager.UsageMode != LicenseUsageMode.Designtime) {
ChangingText = true; ChangingText = true;
Text += TextSuffix; Text += TextSuffix;
ChangingText = false; ChangingText = false;
} }
} }
} }
} }

View File

@@ -5,43 +5,43 @@ using System.Windows.Forms;
using TweetDuck.Management; using TweetDuck.Management;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
sealed partial class FormAbout : Form, FormManager.IAppDialog{ sealed partial class FormAbout : Form, FormManager.IAppDialog {
private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki"; private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues"; private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
public FormAbout(){ public FormAbout() {
InitializeComponent(); InitializeComponent();
Text = "About " + Program.BrandName + " " + Program.VersionTag; Text = "About " + Program.BrandName + " " + Program.VersionTag;
labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license."; labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
MemoryStream logoStream = new MemoryStream(Properties.Resources.avatar); labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
pictureLogo.Image = Image.FromStream(logoStream); labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
Disposed += (sender, args) => logoStream.Dispose(); labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
}
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){ MemoryStream logoStream = new MemoryStream(Properties.Resources.avatar);
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string); pictureLogo.Image = Image.FromStream(logoStream);
} Disposed += (sender, args) => logoStream.Dispose();
}
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){ private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
ShowGuide(); BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
} }
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){ private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent) {
e.Cancel = true; ShowGuide();
ShowGuide(); }
}
private void ShowGuide(){ private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e) {
FormGuide.Show(); e.Cancel = true;
Close(); ShowGuide();
} }
}
private void ShowGuide() {
FormGuide.Show();
Close();
}
}
} }

View File

@@ -1,10 +1,8 @@
using System.Drawing; using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDuck.Browser; using TweetDuck.Browser;
using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Data; using TweetDuck.Browser.Data;
using TweetDuck.Browser.Handling; using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General; using TweetDuck.Browser.Handling.General;
@@ -12,121 +10,95 @@ using TweetDuck.Controls;
using TweetDuck.Management; using TweetDuck.Management;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
sealed partial class FormGuide : Form, FormManager.IAppDialog{ sealed partial class FormGuide : Form, FormManager.IAppDialog {
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/"; private const string GuideUrl = @"td://guide/index.html";
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandler.FromString(""));
public static bool CheckGuideUrl(string url, out string hash){ private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandlers.ForString(string.Empty));
if (!url.Contains("//tweetduck.chylex.com/guide")){
hash = null;
return false;
}
string path = url.Substring(url.IndexOf("/guide")+1); public static void Show(string hash = null) {
Match match = Regex.Match(path, GuidePathRegex); string url = GuideUrl + (string.IsNullOrEmpty(hash) ? string.Empty : "#" + hash);
FormGuide guide = FormManager.TryFind<FormGuide>();
if (match.Success){ if (guide == null) {
hash = match.Groups[1].Value; FormBrowser owner = FormManager.TryFind<FormBrowser>();
return true;
}
else{
hash = null;
return false;
}
}
public static void Show(string hash = null){ if (owner != null) {
string url = GuideUrl + (hash ?? string.Empty); new FormGuide(url, owner).Show(owner);
FormGuide guide = FormManager.TryFind<FormGuide>(); }
}
if (guide == null){ else {
FormBrowser owner = FormManager.TryFind<FormBrowser>(); guide.Reload(url);
guide.Activate();
}
}
if (owner != null){ #pragma warning disable IDE0069 // Disposable fields should be disposed
owner.AnalyticsFile.OpenGuide.Trigger(); private readonly ChromiumWebBrowser browser;
new FormGuide(url, owner).Show(owner); #pragma warning restore IDE0069 // Disposable fields should be disposed
}
}
else{
guide.Reload(url);
guide.Activate();
}
}
#pragma warning disable IDE0069 // Disposable fields should be disposed private string nextUrl;
private readonly ChromiumWebBrowser browser;
#pragma warning restore IDE0069 // Disposable fields should be disposed
private string nextUrl; private FormGuide(string url, FormBrowser owner) {
InitializeComponent();
private FormGuide(string url, FormBrowser owner){ Text = Program.BrandName + " Guide";
InitializeComponent(); Size = new Size(owner.Size.Width * 3 / 4, owner.Size.Height * 3 / 4);
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
Text = Program.BrandName + " Guide"; var resourceRequestHandler = new ResourceRequestHandlerBase();
Size = new Size(owner.Size.Width * 3 / 4, owner.Size.Height * 3 / 4); resourceRequestHandler.ResourceHandlers.Register(DummyPage);
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
var resourceRequestHandler = new ResourceRequestHandlerBase(); this.browser = new ChromiumWebBrowser(url) {
resourceRequestHandler.ResourceHandlers.Register(DummyPage); MenuHandler = new ContextMenuGuide(),
JsDialogHandler = new JavaScriptDialogHandler(),
this.browser = new ChromiumWebBrowser(url){ KeyboardHandler = new KeyboardHandlerBase(),
MenuHandler = new ContextMenuGuide(owner), LifeSpanHandler = new CustomLifeSpanHandler(),
JsDialogHandler = new JavaScriptDialogHandler(), RequestHandler = new RequestHandlerBase(true),
KeyboardHandler = new KeyboardHandlerBase(), ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
LifeSpanHandler = new LifeSpanHandler(), };
RequestHandler = new RequestHandlerBase(true),
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
};
browser.LoadingStateChanged += browser_LoadingStateChanged; browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.FrameLoadEnd += browser_FrameLoadEnd;
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
browser.SetupZoomEvents();
Controls.Add(browser); browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
Disposed += (sender, args) => browser.Dispose(); browser.Dock = DockStyle.None;
} browser.Location = ControlExtensions.InvisibleLocation;
browser.SetupZoomEvents();
protected override void Dispose(bool disposing){ Controls.Add(browser);
if (disposing){ Disposed += (sender, args) => browser.Dispose();
components?.Dispose(); }
}
base.Dispose(disposing); protected override void Dispose(bool disposing) {
} if (disposing) {
components?.Dispose();
}
private void Reload(string url){ base.Dispose(disposing);
nextUrl = url; }
browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
browser.Load(DummyPage.Url);
}
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ private void Reload(string url) {
if (!e.IsLoading){ nextUrl = url;
if (browser.Address == DummyPage.Url){ browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.Load(nextUrl); browser.Dock = DockStyle.None;
} browser.Location = ControlExtensions.InvisibleLocation;
else{ browser.Load(DummyPage.Url);
this.InvokeAsyncSafe(() => { }
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
});
browser.LoadingStateChanged -= browser_LoadingStateChanged; private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
} if (!e.IsLoading) {
} if (browser.Address == DummyPage.Url) {
} browser.Load(nextUrl);
}
else {
this.InvokeAsyncSafe(() => {
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
});
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ browser.LoadingStateChanged -= browser_LoadingStateChanged;
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"); }
} }
} }
}
} }

View File

@@ -4,219 +4,219 @@ using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
[Flags] [Flags]
enum ControlType{ enum ControlType {
None = 0, None = 0,
Accept = 1, // triggered by pressing enter when a non-button is focused Accept = 1, // triggered by pressing enter when a non-button is focused
Cancel = 2, // triggered by closing the dialog without pressing a button Cancel = 2, // triggered by closing the dialog without pressing a button
Focused = 4 // active control after the dialog is showed 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 OK = "OK";
public const string Yes = "Yes"; public const string Yes = "Yes";
public const string No = "No"; public const string No = "No";
public const string Cancel = "Cancel"; public const string Cancel = "Cancel";
public const string Retry = "Retry"; public const string Retry = "Retry";
public const string Ignore = "Ignore"; public const string Ignore = "Ignore";
public const string Exit = "Exit"; 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); 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); 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); 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); 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); 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); using FormMessage message = new FormMessage(caption, text, icon);
if (buttonCancel == null){ if (buttonCancel == null) {
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused); message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused);
} }
else{ else {
message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel); message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel);
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused); message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused);
} }
return message.ShowDialog() == DialogResult.OK; return message.ShowDialog() == DialogResult.OK;
} }
// Instance // Instance
public Button ClickedButton { get; private set; } public Button ClickedButton { get; private set; }
public bool HasIcon => icon != null; public bool HasIcon => icon != null;
public int ActionPanelY => panelActions.Location.Y; public int ActionPanelY => panelActions.Location.Y;
private int ClientWidth{ private int ClientWidth {
get => ClientSize.Width; get => ClientSize.Width;
set => ClientSize = new Size(value, ClientSize.Height); set => ClientSize = new Size(value, ClientSize.Height);
} }
private int ButtonDistance{ private int ButtonDistance {
get => BrowserUtils.Scale(96, dpiScale); get => BrowserUtils.Scale(96, dpiScale);
} }
private readonly Icon icon; private readonly Icon icon;
private readonly bool isReady; private readonly bool isReady;
private readonly float dpiScale; private readonly float dpiScale;
private int realFormWidth, minFormWidth; private int realFormWidth, minFormWidth;
private int buttonCount; private int buttonCount;
private int prevLabelWidth, prevLabelHeight; private int prevLabelWidth, prevLabelHeight;
private bool wasLabelMultiline; private bool wasLabelMultiline;
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){ public FormMessage(string caption, string text, MessageBoxIcon messageIcon) {
InitializeComponent(); InitializeComponent();
this.dpiScale = this.GetDPIScale(); this.dpiScale = this.GetDPIScale();
this.prevLabelWidth = labelMessage.Width; this.prevLabelWidth = labelMessage.Width;
this.prevLabelHeight = labelMessage.Height; this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = BrowserUtils.Scale(42, dpiScale); this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
switch(messageIcon){ switch (messageIcon) {
case MessageBoxIcon.Information: case MessageBoxIcon.Information:
icon = SystemIcons.Information; icon = SystemIcons.Information;
break; break;
case MessageBoxIcon.Warning: case MessageBoxIcon.Warning:
icon = SystemIcons.Warning; icon = SystemIcons.Warning;
break; break;
case MessageBoxIcon.Error:
icon = SystemIcons.Error;
break;
case MessageBoxIcon.Question: case MessageBoxIcon.Error:
icon = SystemIcons.Question; icon = SystemIcons.Error;
break; break;
default: case MessageBoxIcon.Question:
icon = null; icon = SystemIcons.Question;
labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height break;
break;
}
this.isReady = true; default:
icon = null;
labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height
break;
}
this.Text = caption; this.isReady = true;
this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
}
private void FormMessage_SizeChanged(object sender, EventArgs e){ this.Text = caption;
RecalculateButtonLocation(); this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
} }
public Button AddButton(string title, ControlType type){ private void FormMessage_SizeChanged(object sender, EventArgs e) {
return AddButton(title, DialogResult.OK, type); RecalculateButtonLocation();
} }
public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None){ public Button AddButton(string title, ControlType type) {
Button button = new Button{ return AddButton(title, DialogResult.OK, type);
Anchor = AnchorStyles.Bottom, }
Font = SystemFonts.MessageBoxFont,
Location = new Point(0, 12),
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
TabIndex = 256 - buttonCount,
Text = title,
UseVisualStyleBackColor = true
};
button.Click += (sender, args) => { public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None) {
ClickedButton = (Button)sender; Button button = new Button {
DialogResult = result; Anchor = AnchorStyles.Bottom,
Close(); Font = SystemFonts.MessageBoxFont,
}; Location = new Point(0, 12),
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
TabIndex = 256 - buttonCount,
Text = title,
UseVisualStyleBackColor = true
};
panelActions.Controls.Add(button); button.Click += (sender, args) => {
++buttonCount; ClickedButton = (Button) sender;
DialogResult = result;
Close();
};
minFormWidth += ButtonDistance; panelActions.Controls.Add(button);
ClientWidth = Math.Max(realFormWidth, minFormWidth); ++buttonCount;
RecalculateButtonLocation();
if (type.HasFlag(ControlType.Accept)){ minFormWidth += ButtonDistance;
AcceptButton = button; ClientWidth = Math.Max(realFormWidth, minFormWidth);
} RecalculateButtonLocation();
if (type.HasFlag(ControlType.Cancel)){ if (type.HasFlag(ControlType.Accept)) {
CancelButton = button; AcceptButton = button;
} }
if (type.HasFlag(ControlType.Focused)){ if (type.HasFlag(ControlType.Cancel)) {
ActiveControl = button; CancelButton = button;
} }
return button; if (type.HasFlag(ControlType.Focused)) {
} ActiveControl = button;
}
public void AddActionControl(Control control){ return button;
panelActions.Controls.Add(control); }
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
minFormWidth += control.Width + control.Margin.Horizontal; public void AddActionControl(Control control) {
ClientWidth = Math.Max(realFormWidth, minFormWidth); panelActions.Controls.Add(control);
}
private void RecalculateButtonLocation(){
int dist = ButtonDistance;
int start = ClientWidth - dist;
for(int index = 0; index < buttonCount; index++){ control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
Control control = panelActions.Controls[index];
control.Location = new Point(start - index * dist, control.Location.Y);
}
}
private void labelMessage_SizeChanged(object sender, EventArgs e){ minFormWidth += control.Width + control.Margin.Horizontal;
if (!isReady){ ClientWidth = Math.Max(realFormWidth, minFormWidth);
return; }
}
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height; private void RecalculateButtonLocation() {
int labelOffset = BrowserUtils.Scale(8, dpiScale); int dist = ButtonDistance;
int start = ClientWidth - dist;
if (isMultiline && !wasLabelMultiline){ for (int index = 0; index < buttonCount; index++) {
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y - labelOffset); Control control = panelActions.Controls[index];
prevLabelHeight += labelOffset; control.Location = new Point(start - index * dist, control.Location.Y);
} }
else if (!isMultiline && wasLabelMultiline){ }
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y + labelOffset);
prevLabelHeight -= labelOffset;
}
realFormWidth = ClientWidth - (icon == null ? BrowserUtils.Scale(50, dpiScale) : 0) + labelMessage.Width - prevLabelWidth; private void labelMessage_SizeChanged(object sender, EventArgs e) {
ClientWidth = Math.Max(realFormWidth, minFormWidth); if (!isReady) {
Height += labelMessage.Height - prevLabelHeight; return;
}
prevLabelWidth = labelMessage.Width; bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
prevLabelHeight = labelMessage.Height; int labelOffset = BrowserUtils.Scale(8, dpiScale);
wasLabelMultiline = isMultiline;
}
protected override void OnPaint(PaintEventArgs e){ if (isMultiline && !wasLabelMultiline) {
if (icon != null){ labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y - labelOffset);
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1 + BrowserUtils.Scale(25, dpiScale)); prevLabelHeight += labelOffset;
} }
else if (!isMultiline && wasLabelMultiline) {
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y + labelOffset);
prevLabelHeight -= labelOffset;
}
base.OnPaint(e); realFormWidth = ClientWidth - (icon == null ? BrowserUtils.Scale(50, dpiScale) : 0) + labelMessage.Width - prevLabelWidth;
} ClientWidth = Math.Max(realFormWidth, minFormWidth);
} Height += labelMessage.Height - prevLabelHeight;
prevLabelWidth = labelMessage.Width;
prevLabelHeight = labelMessage.Height;
wasLabelMultiline = isMultiline;
}
protected override void OnPaint(PaintEventArgs e) {
if (icon != null) {
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1 + BrowserUtils.Scale(25, dpiScale));
}
base.OnPaint(e);
}
}
} }

View File

@@ -8,106 +8,102 @@ using TweetDuck.Plugins;
using TweetLib.Core; using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
sealed partial class FormPlugins : Form, FormManager.IAppDialog{ sealed partial class FormPlugins : Form, FormManager.IAppDialog {
private static UserConfig Config => Program.Config.User; private static UserConfig Config => Program.Config.User;
private readonly PluginManager pluginManager; private readonly PluginManager pluginManager;
public FormPlugins(){
InitializeComponent();
Text = Program.BrandName + " Plugins"; public FormPlugins() {
} InitializeComponent();
public FormPlugins(PluginManager pluginManager) : this(){ Text = Program.BrandName + " Plugins";
this.pluginManager = pluginManager; }
if (!Config.PluginsWindowSize.IsEmpty){ public FormPlugins(PluginManager pluginManager) : this() {
Size targetSize = Config.PluginsWindowSize; this.pluginManager = pluginManager;
Size = new Size(Math.Max(MinimumSize.Width, targetSize.Width), Math.Max(MinimumSize.Height, targetSize.Height));
}
Shown += (sender, args) => {
ReloadPluginList();
};
FormClosed += (sender, args) => { if (!Config.PluginsWindowSize.IsEmpty) {
Config.PluginsWindowSize = Size; Size targetSize = Config.PluginsWindowSize;
Config.Save(); Size = new Size(Math.Max(MinimumSize.Width, targetSize.Width), Math.Max(MinimumSize.Height, targetSize.Height));
}; }
ResizeEnd += (sender, args) => { Shown += (sender, args) => { ReloadPluginList(); };
timerLayout.Start();
};
}
private int GetPluginOrderIndex(Plugin plugin){ FormClosed += (sender, args) => {
return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2; Config.PluginsWindowSize = Size;
} Config.Save();
};
private void ReloadPluginList(){ ResizeEnd += (sender, args) => { timerLayout.Start(); };
flowLayoutPlugins.Controls.Clear(); }
flowLayoutPlugins.SuspendLayout();
foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){ private int GetPluginOrderIndex(Plugin plugin) {
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin)); return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2;
}
flowLayoutPlugins.Controls.Add(new Panel{ private void ReloadPluginList() {
BackColor = Color.DimGray, flowLayoutPlugins.Controls.Clear();
Margin = new Padding(0), flowLayoutPlugins.SuspendLayout();
Size = new Size(1, 1)
});
}
flowLayoutPlugins.ResumeLayout(true); foreach (Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)) {
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));
timerLayout_Tick(null, EventArgs.Empty);
timerLayout.Start();
}
private void timerLayout_Tick(object sender, EventArgs e){ flowLayoutPlugins.Controls.Add(new Panel {
timerLayout.Stop(); BackColor = Color.DimGray,
Margin = new Padding(0),
// stupid WinForms scrollbars and panels Size = new Size(1, 1)
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right + 1, Padding.Bottom + 1); });
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right - 1, Padding.Bottom - 1); }
}
public void flowLayoutPlugins_Resize(object sender, EventArgs e){ flowLayoutPlugins.ResumeLayout(true);
Control lastPlugin = flowLayoutPlugins.Controls.OfType<PluginControl>().LastOrDefault();
if (lastPlugin == null){
return;
}
bool showScrollBar = lastPlugin.Location.Y + lastPlugin.Height + 1 >= flowLayoutPlugins.Height; timerLayout_Tick(null, EventArgs.Empty);
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0; timerLayout.Start();
}
flowLayoutPlugins.AutoScroll = showScrollBar;
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
foreach(Control control in flowLayoutPlugins.Controls){ private void timerLayout_Tick(object sender, EventArgs e) {
control.Width = flowLayoutPlugins.Width - control.Margin.Horizontal - horizontalOffset; timerLayout.Stop();
}
flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count - 1].Visible = !showScrollBar;
flowLayoutPlugins.Focus();
}
private void btnOpenFolder_Click(object sender, EventArgs e){ // stupid WinForms scrollbars and panels
App.SystemHandler.OpenFileExplorer(pluginManager.PathCustomPlugins); Padding = new Padding(Padding.Left, Padding.Top, Padding.Right + 1, Padding.Bottom + 1);
} Padding = new Padding(Padding.Left, Padding.Top, Padding.Right - 1, Padding.Bottom - 1);
}
private void btnReload_Click(object sender, EventArgs e){ private void flowLayoutPlugins_Resize(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)){ Control lastPlugin = flowLayoutPlugins.Controls.OfType<PluginControl>().LastOrDefault();
pluginManager.Reload();
ReloadPluginList();
}
}
private void btnClose_Click(object sender, EventArgs e){ if (lastPlugin == null) {
Close(); return;
} }
}
bool showScrollBar = lastPlugin.Location.Y + lastPlugin.Height + 1 >= flowLayoutPlugins.Height;
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
flowLayoutPlugins.AutoScroll = showScrollBar;
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
foreach (Control control in flowLayoutPlugins.Controls) {
control.Width = flowLayoutPlugins.Width - control.Margin.Horizontal - horizontalOffset;
}
flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count - 1].Visible = !showScrollBar;
flowLayoutPlugins.Focus();
}
private void btnOpenFolder_Click(object sender, EventArgs e) {
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)) {
pluginManager.Reload();
ReloadPluginList();
}
}
private void btnClose_Click(object sender, EventArgs e) {
Close();
}
}
} }

View File

@@ -9,235 +9,234 @@ using TweetDuck.Configuration;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Dialogs.Settings; using TweetDuck.Dialogs.Settings;
using TweetDuck.Management; using TweetDuck.Management;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Systems.Updates; using TweetLib.Core.Systems.Updates;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
sealed partial class FormSettings : Form, FormManager.IAppDialog{ sealed partial class FormSettings : Form, FormManager.IAppDialog {
public bool ShouldReloadBrowser { get; private set; } public bool ShouldReloadBrowser { get; private set; }
private readonly FormBrowser browser; private readonly FormBrowser browser;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly int buttonHeight; private readonly int buttonHeight;
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(8); private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(8);
private SettingsTab currentTab; 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(); InitializeComponent();
Text = Program.BrandName + " Options"; Text = Program.BrandName + " Options";
this.browser = browser; this.browser = browser;
this.browser.PauseNotification(); this.browser.PauseNotification();
this.plugins = plugins; this.plugins = plugins;
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
PrepareLoad(); this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates)); PrepareLoad();
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("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS, this.browser.OpenDevTools));
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]); AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates));
} AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification));
AddButton("Tray", () => new TabSettingsTray());
AddButton("Feedback", () => new TabSettingsFeedback());
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS, this.browser.OpenDevTools));
private void PrepareLoad(){ SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested; }
}
private void PrepareUnload(){ // TODO refactor this further later private void PrepareLoad() {
currentTab.Control.OnClosing(); Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
}
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
Program.Config.SaveAll();
}
private void Config_ProgramRestartRequested(object sender, EventArgs e){ private void PrepareUnload() { // TODO refactor this further later
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)){ currentTab.Control.OnClosing();
Program.Restart();
}
}
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){ Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
PrepareUnload(); Program.Config.SaveAll();
}
foreach(SettingsTab tab in tabs.Values){ private void Config_ProgramRestartRequested(object sender, EventArgs e) {
if (tab.IsInitialized){ 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)) {
tab.Control.Dispose(); Program.Restart();
} }
} }
browser.ResumeNotification();
}
private void btnManageOptions_Click(object sender, EventArgs e){ private void FormSettings_FormClosing(object sender, FormClosingEventArgs e) {
PrepareUnload(); PrepareUnload();
using DialogSettingsManage dialog = new DialogSettingsManage(plugins); foreach (SettingsTab tab in tabs.Values) {
FormClosing -= FormSettings_FormClosing; if (tab.IsInitialized) {
tab.Control.Dispose();
if (dialog.ShowDialog() == DialogResult.OK){ }
if (!dialog.IsRestarting){ }
browser.ResumeNotification();
if (dialog.ShouldReloadBrowser){ browser.ResumeNotification();
BrowserProcessHandler.UpdatePrefs(); }
ShouldReloadBrowser = true;
}
}
Close(); private void btnManageOptions_Click(object sender, EventArgs e) {
} PrepareUnload();
else{
FormClosing += FormSettings_FormClosing;
PrepareLoad();
}
}
private void btnClose_Click(object sender, EventArgs e){ using DialogSettingsManage dialog = new DialogSettingsManage(plugins);
Close(); FormClosing -= FormSettings_FormClosing;
}
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTab{ if (dialog.ShowDialog() == DialogResult.OK) {
FlatButton btn = new FlatButton{ if (!dialog.IsRestarting) {
BackColor = SystemColors.Control, browser.ResumeNotification();
FlatStyle = FlatStyle.Flat,
Font = SystemFonts.MessageBoxFont,
Location = new Point(0, (buttonHeight + 1) * (panelButtons.Controls.Count / 2)),
Margin = new Padding(0),
Size = new Size(panelButtons.Width, buttonHeight),
Text = title,
UseVisualStyleBackColor = true
};
btn.FlatAppearance.BorderSize = 0; if (dialog.ShouldReloadBrowser) {
btn.FlatAppearance.MouseDownBackColor = Color.FromArgb(179, 213, 232); BrowserProcessHandler.UpdatePrefs();
btn.FlatAppearance.MouseOverBackColor = Color.FromArgb(216, 230, 237); ShouldReloadBrowser = true;
}
panelButtons.Controls.Add(btn); }
panelButtons.Controls.Add(new Panel{ Close();
BackColor = Color.DimGray, }
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count - 1].Location.Y + buttonHeight), else {
Margin = new Padding(0), FormClosing += FormSettings_FormClosing;
Size = new Size(panelButtons.Width, 1) PrepareLoad();
}); }
}
tabs.Add(typeof(T), new SettingsTab(btn, constructor)); private void btnClose_Click(object sender, EventArgs e) {
Close();
}
btn.Click += (sender, args) => SelectTab<T>(); 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,
Location = new Point(0, (buttonHeight + 1) * (panelButtons.Controls.Count / 2)),
Margin = new Padding(0),
Size = new Size(panelButtons.Width, buttonHeight),
Text = title,
UseVisualStyleBackColor = true
};
private void SelectTab<T>() where T : BaseTab{ btn.FlatAppearance.BorderSize = 0;
SelectTab(tabs[typeof(T)]); btn.FlatAppearance.MouseDownBackColor = Color.FromArgb(179, 213, 232);
} btn.FlatAppearance.MouseOverBackColor = Color.FromArgb(216, 230, 237);
private void SelectTab(SettingsTab tab){ panelButtons.Controls.Add(btn);
if (currentTab != null){
currentTab.Button.BackColor = SystemColors.Control;
currentTab.Control.OnClosing();
}
tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
if (!tab.IsInitialized){ panelButtons.Controls.Add(new Panel {
foreach(Control control in tab.Control.InteractiveControls){ BackColor = Color.DimGray,
if (control is ComboBox){ Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count - 1].Location.Y + buttonHeight),
control.MouseLeave += control_MouseLeave; Margin = new Padding(0),
} Size = new Size(panelButtons.Width, 1)
else if (control is TrackBar){ });
control.MouseWheel += control_MouseWheel;
}
}
if (tab.Control.Height < panelContents.Height - 2){ tabs.Add(typeof(T), new SettingsTab(btn, constructor));
tab.Control.Height = panelContents.Height - 2; // fixes off-by-pixel error on high DPI
}
tab.Control.OnReady(); btn.Click += (sender, args) => SelectTab<T>();
} }
panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything
panelContents.PerformLayout();
panelContents.SuspendLayout(); private void SelectTab<T>() where T : BaseTab {
panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte SelectTab(tabs[typeof(T)]);
panelContents.Controls.Clear(); }
panelContents.Controls.Add(tab.Control);
panelContents.ResumeLayout(true);
panelContents.VerticalScroll.Enabled = true; private void SelectTab(SettingsTab tab) {
panelContents.Focus(); if (currentTab != null) {
currentTab.Button.BackColor = SystemColors.Control;
currentTab.Control.OnClosing();
}
currentTab = tab; tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
}
private void control_MouseLeave(object sender, EventArgs e){ if (!tab.IsInitialized) {
if (sender is ComboBox cb && cb.DroppedDown){ foreach (Control control in tab.Control.InteractiveControls) {
return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation if (control is ComboBox) {
} control.MouseLeave += control_MouseLeave;
}
else if (control is TrackBar) {
control.MouseWheel += control_MouseWheel;
}
}
panelContents.Focus(); if (tab.Control.Height < panelContents.Height - 2) {
} tab.Control.Height = panelContents.Height - 2; // fixes off-by-pixel error on high DPI
}
private void control_MouseWheel(object sender, MouseEventArgs e){ tab.Control.OnReady();
((HandledMouseEventArgs)e).Handled = true; }
panelContents.Focus();
}
private sealed class SettingsTab{ panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything
public Button Button { get; } panelContents.PerformLayout();
public BaseTab Control => control ??= constructor(); panelContents.SuspendLayout();
public bool IsInitialized => control != null; panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
panelContents.Controls.Clear();
panelContents.Controls.Add(tab.Control);
panelContents.ResumeLayout(true);
private readonly Func<BaseTab> constructor; panelContents.VerticalScroll.Enabled = true;
private BaseTab control; panelContents.Focus();
public SettingsTab(Button button, Func<BaseTab> constructor){ currentTab = tab;
this.Button = button; }
this.constructor = constructor;
}
}
internal abstract class BaseTab : UserControl{ private void control_MouseLeave(object sender, EventArgs e) {
protected static UserConfig Config => Program.Config.User; if (sender is ComboBox cb && cb.DroppedDown) {
protected static SystemConfig SysConfig => Program.Config.System; return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
}
public IEnumerable<Control> InteractiveControls{ panelContents.Focus();
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{
yield return control;
}
}
}
return FindInteractiveControls(this); private void control_MouseWheel(object sender, MouseEventArgs e) {
} ((HandledMouseEventArgs) e).Handled = true;
} panelContents.Focus();
}
protected BaseTab(){ private sealed class SettingsTab {
Padding = new Padding(6); public Button Button { get; }
}
public virtual void OnReady(){} public BaseTab Control => control ??= constructor();
public virtual void OnClosing(){} public bool IsInitialized => control != null;
}
} private readonly Func<BaseTab> constructor;
private BaseTab control;
public SettingsTab(Button button, Func<BaseTab> constructor) {
this.Button = button;
this.constructor = constructor;
}
}
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)) {
yield return subControl;
}
}
else {
yield return control;
}
}
}
return FindInteractiveControls(this);
}
}
protected BaseTab() {
Padding = new Padding(6);
}
public virtual void OnReady() {}
public virtual void OnClosing() {}
}
}
} }

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -3,140 +3,135 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Utils;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsCSS : Form{ sealed partial class DialogSettingsCSS : Form {
public string BrowserCSS => textBoxBrowserCSS.Text; public string BrowserCSS => textBoxBrowserCSS.Text;
public string NotificationCSS => textBoxNotificationCSS.Text; public string NotificationCSS => textBoxNotificationCSS.Text;
private readonly Action<string> reinjectBrowserCSS; private readonly Action<string> reinjectBrowserCSS;
private readonly Action openDevTools; 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(); InitializeComponent();
Text = Program.BrandName + " Options - CSS";
this.reinjectBrowserCSS = reinjectBrowserCSS; Text = Program.BrandName + " Options - CSS";
this.openDevTools = openDevTools;
textBoxBrowserCSS.EnableMultilineShortcuts();
textBoxBrowserCSS.Text = browserCSS ?? "";
textBoxNotificationCSS.EnableMultilineShortcuts(); this.reinjectBrowserCSS = reinjectBrowserCSS;
textBoxNotificationCSS.Text = notificationCSS ?? ""; this.openDevTools = openDevTools;
if (!BrowserUtils.HasDevTools){ textBoxBrowserCSS.EnableMultilineShortcuts();
btnOpenDevTools.Enabled = false; textBoxBrowserCSS.Text = browserCSS ?? "";
}
ActiveControl = textBoxBrowserCSS; textBoxNotificationCSS.EnableMultilineShortcuts();
textBoxBrowserCSS.Select(textBoxBrowserCSS.TextLength, 0); textBoxNotificationCSS.Text = notificationCSS ?? "";
}
private void tabPanel_SelectedIndexChanged(object sender, EventArgs e){ ActiveControl = textBoxBrowserCSS;
TextBox tb = tabPanel.SelectedTab.Controls.OfType<TextBox>().FirstOrDefault(); textBoxBrowserCSS.Select(textBoxBrowserCSS.TextLength, 0);
}
if (tb != null){ private void tabPanel_SelectedIndexChanged(object sender, EventArgs e) {
tb.Focus(); TextBox tb = tabPanel.SelectedTab.Controls.OfType<TextBox>().FirstOrDefault();
tb.Select(tb.TextLength, 0);
}
}
private void textBoxCSS_KeyDown(object sender, KeyEventArgs e){ if (tb != null) {
TextBox tb = (TextBox)sender; tb.Focus();
string text = tb.Text; tb.Select(tb.TextLength, 0);
}
}
if (e.KeyCode == Keys.Back && e.Modifiers == Keys.Control){ private void textBoxCSS_KeyDown(object sender, KeyEventArgs e) {
e.SuppressKeyPress = true; TextBox tb = (TextBox) sender;
string text = tb.Text;
int deleteTo = tb.SelectionStart; if (e.KeyCode == Keys.Back && e.Modifiers == Keys.Control) {
e.SuppressKeyPress = true;
if (deleteTo > 0){ int deleteTo = tb.SelectionStart;
char initialChar = text[--deleteTo];
bool shouldDeleteAlphanumeric = char.IsLetterOrDigit(initialChar);
while(--deleteTo >= 0){
if ((shouldDeleteAlphanumeric && !char.IsLetterOrDigit(text[deleteTo])) ||
(!shouldDeleteAlphanumeric && text[deleteTo] != initialChar)){
break;
}
}
if (!(deleteTo < text.Length - 1 && text[deleteTo] == '\r' && text[deleteTo + 1] == '\n')){
++deleteTo;
}
tb.Select(deleteTo, tb.SelectionLength + tb.SelectionStart - deleteTo); if (deleteTo > 0) {
tb.SelectedText = string.Empty; char initialChar = text[--deleteTo];
} bool shouldDeleteAlphanumeric = char.IsLetterOrDigit(initialChar);
}
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){
int deleteTo = tb.SelectionStart;
if (deleteTo > 1 && text[deleteTo - 1] == ' ' && text[deleteTo - 2] == ' '){ while (--deleteTo >= 0) {
e.SuppressKeyPress = true; if ((shouldDeleteAlphanumeric && !char.IsLetterOrDigit(text[deleteTo])) ||
(!shouldDeleteAlphanumeric && text[deleteTo] != initialChar)) {
break;
}
}
tb.Select(deleteTo - 2, 2); if (!(deleteTo < text.Length - 1 && text[deleteTo] == '\r' && text[deleteTo + 1] == '\n')) {
tb.SelectedText = string.Empty; ++deleteTo;
} }
}
else if (e.KeyCode == Keys.Enter && e.Modifiers == Keys.None && tb.SelectionLength == 0){
int insertAt = tb.SelectionStart, cursorOffset = 0;
string insertText;
if (insertAt == 0){ tb.Select(deleteTo, tb.SelectionLength + tb.SelectionStart - deleteTo);
return; tb.SelectedText = string.Empty;
} }
else if (text[insertAt - 1] == '{'){ }
insertText = Environment.NewLine + " "; else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None) {
int deleteTo = tb.SelectionStart;
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt + 1) : -1; if (deleteTo > 1 && text[deleteTo - 1] == ' ' && text[deleteTo - 2] == ' ') {
e.SuppressKeyPress = true;
if (nextBracket == -1 || text[nextBracket] == '{'){ tb.Select(deleteTo - 2, 2);
string insertExtra = Environment.NewLine + "}"; tb.SelectedText = string.Empty;
insertText += insertExtra; }
cursorOffset -= insertExtra.Length; }
} else if (e.KeyCode == Keys.Enter && e.Modifiers == Keys.None && tb.SelectionLength == 0) {
} int insertAt = tb.SelectionStart, cursorOffset = 0;
else{ string insertText;
int lineStart = text.LastIndexOf('\n', tb.SelectionStart - 1);
Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart + 1), "^([ \t]+)"); if (insertAt == 0) {
insertText = match.Success ? Environment.NewLine + match.Groups[1].Value : null; return;
} }
else if (text[insertAt - 1] == '{') {
insertText = Environment.NewLine + " ";
if (!string.IsNullOrEmpty(insertText)){ int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[] { '{', '}' }, insertAt + 1) : -1;
e.SuppressKeyPress = true;
tb.Text = text.Insert(insertAt, insertText);
tb.SelectionStart = insertAt + cursorOffset + insertText.Length;
}
}
}
private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){ if (nextBracket == -1 || text[nextBracket] == '{') {
timerTestBrowser.Stop(); string insertExtra = Environment.NewLine + "}";
timerTestBrowser.Start(); insertText += insertExtra;
} cursorOffset -= insertExtra.Length;
}
}
else {
int lineStart = text.LastIndexOf('\n', tb.SelectionStart - 1);
private void timerTestBrowser_Tick(object sender, EventArgs e){ Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart + 1), "^([ \t]+)");
reinjectBrowserCSS(textBoxBrowserCSS.Text); insertText = match.Success ? Environment.NewLine + match.Groups[1].Value : null;
timerTestBrowser.Stop(); }
}
private void btnOpenDevTools_Click(object sender, EventArgs e){ if (!string.IsNullOrEmpty(insertText)) {
openDevTools(); e.SuppressKeyPress = true;
} tb.Text = text.Insert(insertAt, insertText);
tb.SelectionStart = insertAt + cursorOffset + insertText.Length;
}
}
}
private void btnApply_Click(object sender, EventArgs e){ private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e) {
DialogResult = DialogResult.OK; timerTestBrowser.Stop();
Close(); timerTestBrowser.Start();
} }
private void btnCancel_Click(object sender, EventArgs e){ private void timerTestBrowser_Tick(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel; reinjectBrowserCSS(textBoxBrowserCSS.Text);
Close(); timerTestBrowser.Stop();
} }
}
private void btnOpenDevTools_Click(object sender, EventArgs e) {
openDevTools();
}
private void btnApply_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel;
Close();
}
}
} }

View File

@@ -4,45 +4,45 @@ using TweetDuck.Controls;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Collections; using TweetLib.Core.Collections;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsCefArgs : Form{ sealed partial class DialogSettingsCefArgs : Form {
public string CefArgs => textBoxArgs.Text; public string CefArgs => textBoxArgs.Text;
private readonly string initialArgs; private readonly string initialArgs;
public DialogSettingsCefArgs(string args){ public DialogSettingsCefArgs(string args) {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Options - CEF Arguments";
textBoxArgs.EnableMultilineShortcuts();
textBoxArgs.Text = initialArgs = args ?? "";
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
}
private void btnHelp_Click(object sender, EventArgs e){ Text = Program.BrandName + " Options - CEF Arguments";
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
}
private void btnApply_Click(object sender, EventArgs e){ textBoxArgs.EnableMultilineShortcuts();
if (CefArgs == initialArgs){ textBoxArgs.Text = initialArgs = args ?? "";
DialogResult = DialogResult.Cancel; textBoxArgs.Select(textBoxArgs.Text.Length, 0);
Close(); }
return;
}
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count; private void btnHelp_Click(object sender, EventArgs e) {
string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count + (count == 1 ? " argument was" : " arguments were") + " detected. Continue?"; BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
}
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){ private void btnApply_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK; if (CefArgs == initialArgs) {
Close(); DialogResult = DialogResult.Cancel;
} Close();
} return;
}
private void btnCancel_Click(object sender, EventArgs e){ int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
DialogResult = DialogResult.Cancel; string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count + (count == 1 ? " argument was" : " arguments were") + " detected. Continue?";
Close();
} if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)) {
} DialogResult = DialogResult.OK;
Close();
}
}
private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel;
Close();
}
}
} }

View File

@@ -3,53 +3,53 @@ using System.Windows.Forms;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
using IOPath = System.IO.Path; using IOPath = System.IO.Path;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsExternalProgram : Form{ sealed partial class DialogSettingsExternalProgram : Form {
public string Path{ public string Path {
get => StringUtils.NullIfEmpty(textBoxPath.Text); get => StringUtils.NullIfEmpty(textBoxPath.Text);
set => textBoxPath.Text = value ?? string.Empty; set => textBoxPath.Text = value ?? string.Empty;
} }
public string Args{ public string Args {
get => StringUtils.NullIfEmpty(textBoxArgs.Text); get => StringUtils.NullIfEmpty(textBoxArgs.Text);
set => textBoxArgs.Text = value ?? string.Empty; set => textBoxArgs.Text = value ?? string.Empty;
} }
private readonly string fileDialogTitle; private readonly string fileDialogTitle;
public DialogSettingsExternalProgram(string windowTitle, string fileDialogTitle){ public DialogSettingsExternalProgram(string windowTitle, string fileDialogTitle) {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Options - " + windowTitle;
AcceptButton = btnApply;
CancelButton = btnCancel;
this.fileDialogTitle = fileDialogTitle; Text = Program.BrandName + " Options - " + windowTitle;
} AcceptButton = btnApply;
CancelButton = btnCancel;
private void btnBrowse_Click(object sender, EventArgs e){ this.fileDialogTitle = fileDialogTitle;
using OpenFileDialog dialog = new OpenFileDialog{ }
AutoUpgradeEnabled = true,
DereferenceLinks = true,
InitialDirectory = IOPath.GetDirectoryName(Path), // returns null if argument is null
Title = fileDialogTitle,
Filter = "Executables (*.exe;*.bat;*.cmd)|*.exe;*.bat;*.cmd|All Files (*.*)|*.*"
};
if (dialog.ShowDialog() == DialogResult.OK && Path != dialog.FileName){ private void btnBrowse_Click(object sender, EventArgs e) {
Path = dialog.FileName; using OpenFileDialog dialog = new OpenFileDialog {
Args = string.Empty; AutoUpgradeEnabled = true,
} DereferenceLinks = true,
} InitialDirectory = IOPath.GetDirectoryName(Path), // returns null if argument is null
Title = fileDialogTitle,
private void btnApply_Click(object sender, EventArgs e){ Filter = "Executables (*.exe;*.bat;*.cmd)|*.exe;*.bat;*.cmd|All Files (*.*)|*.*"
DialogResult = DialogResult.OK; };
Close();
}
private void btnCancel_Click(object sender, EventArgs e){ if (dialog.ShowDialog() == DialogResult.OK && Path != dialog.FileName) {
DialogResult = DialogResult.Cancel; Path = dialog.FileName;
Close(); Args = string.Empty;
} }
} }
private void btnApply_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel;
Close();
}
}
} }

View File

@@ -7,238 +7,241 @@ using TweetDuck.Management;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsManage : Form{ sealed partial class DialogSettingsManage : Form {
private enum State{ private enum State {
Deciding, Reset, Import, Export Deciding,
} Reset,
Import,
Export
}
private ProfileManager.Items SelectedItems{ private ProfileManager.Items SelectedItems {
get => _selectedItems; get => _selectedItems;
set{ set {
// this will call events and SetFlag, which also updates the UI // 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); kvp.Key.Checked = value.HasFlag(kvp.Value);
} }
} }
} }
private bool SelectedItemsForceRestart{ private bool SelectedItemsForceRestart {
get => _selectedItems.HasFlag(ProfileManager.Items.Session); get => _selectedItems.HasFlag(ProfileManager.Items.Session);
} }
public bool IsRestarting { get; private set; }
public bool ShouldReloadBrowser { get; private set; }
private readonly PluginManager plugins;
private readonly Dictionary<CheckBox, ProfileManager.Items> checkBoxMap = new Dictionary<CheckBox, ProfileManager.Items>(4);
private readonly bool openImportImmediately;
private State currentState; public bool IsRestarting { get; private set; }
private ProfileManager importManager; public bool ShouldReloadBrowser { get; private set; }
private bool requestedRestartFromConfig;
private ProfileManager.Items _selectedItems = ProfileManager.Items.None; private readonly PluginManager plugins;
private readonly Dictionary<CheckBox, ProfileManager.Items> checkBoxMap = new Dictionary<CheckBox, ProfileManager.Items>(4);
private readonly bool openImportImmediately;
public DialogSettingsManage(PluginManager plugins, bool openImportImmediately = false){ private State currentState;
InitializeComponent(); private ProfileManager importManager;
private bool requestedRestartFromConfig;
this.plugins = plugins; private ProfileManager.Items _selectedItems = ProfileManager.Items.None;
this.currentState = State.Deciding;
this.checkBoxMap[cbProgramConfig] = ProfileManager.Items.UserConfig; public DialogSettingsManage(PluginManager plugins, bool openImportImmediately = false) {
this.checkBoxMap[cbSystemConfig] = ProfileManager.Items.SystemConfig; InitializeComponent();
this.checkBoxMap[cbSession] = ProfileManager.Items.Session;
this.checkBoxMap[cbPluginData] = ProfileManager.Items.PluginData;
this.openImportImmediately = openImportImmediately; this.plugins = plugins;
this.currentState = State.Deciding;
if (openImportImmediately){ this.checkBoxMap[cbProgramConfig] = ProfileManager.Items.UserConfig;
radioImport.Checked = true; this.checkBoxMap[cbSystemConfig] = ProfileManager.Items.SystemConfig;
btnContinue_Click(null, EventArgs.Empty); this.checkBoxMap[cbSession] = ProfileManager.Items.Session;
} this.checkBoxMap[cbPluginData] = ProfileManager.Items.PluginData;
}
private void radioDecision_CheckedChanged(object sender, EventArgs e){ this.openImportImmediately = openImportImmediately;
btnContinue.Enabled = true;
}
private void checkBoxSelection_CheckedChanged(object sender, EventArgs e){ if (openImportImmediately) {
CheckBox cb = (CheckBox)sender; radioImport.Checked = true;
SetFlag(checkBoxMap[cb], cb.Checked); btnContinue_Click(null, EventArgs.Empty);
} }
}
private void btnContinue_Click(object sender, EventArgs e){ private void radioDecision_CheckedChanged(object sender, EventArgs e) {
string file; btnContinue.Enabled = true;
}
switch(currentState){ private void checkBoxSelection_CheckedChanged(object sender, EventArgs e) {
case State.Deciding: CheckBox cb = (CheckBox) sender;
// Reset SetFlag(checkBoxMap[cb], cb.Checked);
if (radioReset.Checked){ }
currentState = State.Reset;
Text = "Restore Defaults"; private void btnContinue_Click(object sender, EventArgs e) {
SelectedItems = ProfileManager.Items.UserConfig; string file;
}
// Import switch (currentState) {
else if (radioImport.Checked){ case State.Deciding:
using(OpenFileDialog dialog = new OpenFileDialog{ // Reset
AutoUpgradeEnabled = true, if (radioReset.Checked) {
DereferenceLinks = true, currentState = State.Reset;
Title = "Import TweetDuck Profile",
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){
if (dialog.ShowDialog() != DialogResult.OK){
if (openImportImmediately){
Close();
}
return; Text = "Restore Defaults";
} SelectedItems = ProfileManager.Items.UserConfig;
}
file = dialog.FileName; // Import
} else if (radioImport.Checked) {
using (OpenFileDialog dialog = new OpenFileDialog {
AutoUpgradeEnabled = true,
DereferenceLinks = true,
Title = "Import TweetDuck Profile",
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}) {
if (dialog.ShowDialog() != DialogResult.OK) {
if (openImportImmediately) {
Close();
}
importManager = new ProfileManager(file, plugins); return;
currentState = State.Import; }
Text = "Import Profile"; file = dialog.FileName;
SelectedItems = importManager.FindImportItems(); }
foreach(CheckBox cb in checkBoxMap.Keys){ importManager = new ProfileManager(file, plugins);
cb.Enabled = cb.Checked; currentState = State.Import;
}
}
// Export Text = "Import Profile";
else if (radioExport.Checked){ SelectedItems = importManager.FindImportItems();
currentState = State.Export;
Text = "Export Profile"; foreach (CheckBox cb in checkBoxMap.Keys) {
btnContinue.Text = "Export Profile"; cb.Enabled = cb.Checked;
SelectedItems = ProfileManager.Items.UserConfig | ProfileManager.Items.PluginData; }
} }
// Continue...
panelDecision.Visible = false;
panelSelection.Visible = true;
Height += panelSelection.Height - panelDecision.Height;
break;
case State.Reset: // Export
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)){ else if (radioExport.Checked) {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested; currentState = State.Export;
if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)){ Text = "Export Profile";
Program.Config.User.Reset(); btnContinue.Text = "Export Profile";
} SelectedItems = ProfileManager.Items.UserConfig | ProfileManager.Items.PluginData;
}
if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){ // Continue...
Program.Config.System.Reset(); panelDecision.Visible = false;
} panelSelection.Visible = true;
Height += panelSelection.Height - panelDecision.Height;
break;
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested; case State.Reset:
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)) {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)){ if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)) {
Program.Config.Plugins.Reset(); Program.Config.User.Reset();
}
try{ if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)) {
Directory.Delete(Program.PluginDataPath, true); Program.Config.System.Reset();
}catch(Exception ex){ }
Program.Reporter.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
}
}
if (SelectedItemsForceRestart){ Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[]{ Arguments.ArgDeleteCookies } : StringUtils.EmptyArray);
}
else if (requestedRestartFromConfig){
if (FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
RestartProgram();
}
}
ShouldReloadBrowser = true; if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)) {
Program.Config.Plugins.Reset();
DialogResult = DialogResult.OK; try {
Close(); Directory.Delete(Program.PluginDataPath, true);
} } catch (Exception ex) {
Program.Reporter.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
}
}
break; if (SelectedItemsForceRestart) {
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[] { Arguments.ArgDeleteCookies } : StringUtils.EmptyArray);
}
else if (requestedRestartFromConfig) {
if (FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram();
}
}
case State.Import: ShouldReloadBrowser = true;
if (importManager.Import(SelectedItems)){
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
Program.Config.ReloadAll();
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
if (SelectedItemsForceRestart){
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[]{ Arguments.ArgImportCookies } : StringUtils.EmptyArray);
}
else if (requestedRestartFromConfig){
if (FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
RestartProgram();
}
}
}
ShouldReloadBrowser = true;
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
break; }
case State.Export: break;
using(SaveFileDialog dialog = new SaveFileDialog{
AddExtension = true,
AutoUpgradeEnabled = true,
OverwritePrompt = true,
DefaultExt = "tdsettings",
FileName = "TweetDuck.tdsettings",
Title = "Export TweetDuck Profile",
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
file = dialog.FileName; case State.Import:
} if (importManager.Import(SelectedItems)) {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
new ProfileManager(file, plugins).Export(SelectedItems); Program.Config.ReloadAll();
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
DialogResult = DialogResult.OK; if (SelectedItemsForceRestart) {
Close(); RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[] { Arguments.ArgImportCookies } : StringUtils.EmptyArray);
break; }
} 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();
}
}
}
private void btnCancel_Click(object sender, EventArgs e){ ShouldReloadBrowser = true;
DialogResult = DialogResult.Cancel;
Close();
}
private void Config_ProgramRestartRequested(object sender, EventArgs e){ DialogResult = DialogResult.OK;
requestedRestartFromConfig = true; Close();
} break;
private void SetFlag(ProfileManager.Items flag, bool enable){ case State.Export:
_selectedItems = enable ? _selectedItems | flag : _selectedItems & ~flag; using (SaveFileDialog dialog = new SaveFileDialog {
btnContinue.Enabled = _selectedItems != ProfileManager.Items.None; AddExtension = true,
AutoUpgradeEnabled = true,
if (currentState == State.Import){ OverwritePrompt = true,
btnContinue.Text = SelectedItemsForceRestart ? "Import && Restart" : "Import Profile"; DefaultExt = "tdsettings",
} FileName = "TweetDuck.tdsettings",
else if (currentState == State.Reset){ Title = "Export TweetDuck Profile",
btnContinue.Text = SelectedItemsForceRestart ? "Restore && Restart" : "Restore Defaults"; Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
} }) {
} if (dialog.ShowDialog() != DialogResult.OK) {
return;
}
private void RestartProgram(params string[] extraArgs){ file = dialog.FileName;
IsRestarting = true; }
Program.Restart(extraArgs);
} new ProfileManager(file, plugins).Export(SelectedItems);
}
DialogResult = DialogResult.OK;
Close();
break;
}
}
private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel;
Close();
}
private void Config_ProgramRestartRequested(object sender, EventArgs e) {
requestedRestartFromConfig = true;
}
private void SetFlag(ProfileManager.Items flag, bool enable) {
_selectedItems = enable ? _selectedItems | flag : _selectedItems & ~flag;
btnContinue.Enabled = _selectedItems != ProfileManager.Items.None;
if (currentState == State.Import) {
btnContinue.Text = SelectedItemsForceRestart ? "Import && Restart" : "Import Profile";
}
else if (currentState == State.Reset) {
btnContinue.Text = SelectedItemsForceRestart ? "Restore && Restart" : "Restore Defaults";
}
}
private void RestartProgram(params string[] extraArgs) {
IsRestarting = true;
Program.Restart(extraArgs);
}
}
} }

View File

@@ -3,59 +3,59 @@ using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetLib.Core.Collections; using TweetLib.Core.Collections;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsRestart : Form{ sealed partial class DialogSettingsRestart : Form {
public CommandLineArgs Args { get; private set; } public CommandLineArgs Args { get; private set; }
public DialogSettingsRestart(CommandLineArgs currentArgs){ public DialogSettingsRestart(CommandLineArgs currentArgs) {
InitializeComponent(); InitializeComponent();
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging); cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbLogging.CheckedChanged += control_Change; cbLogging.CheckedChanged += control_Change;
if (Program.IsPortable){
tbDataFolder.Text = "Not available in portable version";
tbDataFolder.Enabled = false;
}
else{
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
tbDataFolder.TextChanged += control_Change;
}
control_Change(this, EventArgs.Empty); if (Program.IsPortable) {
tbDataFolder.Text = "Not available in portable version";
tbDataFolder.Enabled = false;
}
else {
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
tbDataFolder.TextChanged += control_Change;
}
Text = Program.BrandName + " Arguments"; control_Change(this, EventArgs.Empty);
}
private void control_Change(object sender, EventArgs e){ Text = Program.BrandName + " Arguments";
Args = new CommandLineArgs(); }
if (cbLogging.Checked){
Args.AddFlag(Arguments.ArgLogging);
}
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
}
tbShortcutTarget.Text = $@"""{Program.ExecutablePath}""{(Args.Count > 0 ? " " : "")}{Args}"; private void control_Change(object sender, EventArgs e) {
tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0); Args = new CommandLineArgs();
}
private void tbShortcutTarget_Click(object sender, EventArgs e){ if (cbLogging.Checked) {
if (tbShortcutTarget.SelectionLength == 0){ Args.AddFlag(Arguments.ArgLogging);
tbShortcutTarget.SelectAll(); }
}
}
private void btnRestart_Click(object sender, EventArgs e){ if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled) {
DialogResult = DialogResult.OK; Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
Close(); }
}
private void btnCancel_Click(object sender, EventArgs e){ tbShortcutTarget.Text = $@"""{Program.ExecutablePath}""{(Args.Count > 0 ? " " : "")}{Args}";
DialogResult = DialogResult.Cancel; tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0);
Close(); }
}
} private void tbShortcutTarget_Click(object sender, EventArgs e) {
if (tbShortcutTarget.SelectionLength == 0) {
tbShortcutTarget.SelectAll();
}
}
private void btnRestart_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel;
Close();
}
}
} }

View File

@@ -1,27 +1,27 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsSearchEngine : Form{ sealed partial class DialogSettingsSearchEngine : Form {
public string Url => textBoxUrl.Text; public string Url => textBoxUrl.Text;
public DialogSettingsSearchEngine(){ public DialogSettingsSearchEngine() {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Options - Custom Search Engine";
textBoxUrl.Text = Program.Config.User.SearchEngineUrl ?? "";
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
}
private void btnApply_Click(object sender, EventArgs e){
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e){ Text = Program.BrandName + " Options - Custom Search Engine";
DialogResult = DialogResult.Cancel;
Close(); textBoxUrl.Text = Program.Config.User.SearchEngineUrl ?? "";
} textBoxUrl.Select(textBoxUrl.Text.Length, 0);
} }
private void btnApply_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel;
Close();
}
}
} }

View File

@@ -1,11 +1,11 @@
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {
partial class TabSettingsAdvanced { partial class TabSettingsAdvanced {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
/// </summary> /// </summary>
private System.ComponentModel.IContainer components = null; private System.ComponentModel.IContainer components = null;
/// <summary> /// <summary>
/// Clean up any resources being used. /// Clean up any resources being used.
/// </summary> /// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
@@ -18,8 +18,8 @@
#region Component Designer generated code #region Component Designer generated code
/// <summary> /// <summary>
/// Required method for Designer support - do not modify /// Required method for Designer support - do not modify
/// the contents of this method with the code editor. /// the contents of this method with the code editor.
/// </summary> /// </summary>
private void InitializeComponent() { private void InitializeComponent() {
@@ -41,7 +41,10 @@
this.panelConfiguration = new System.Windows.Forms.Panel(); this.panelConfiguration = new System.Windows.Forms.Panel();
this.labelConfiguration = new System.Windows.Forms.Label(); this.labelConfiguration = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel(); 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.labelDevTools = new System.Windows.Forms.Label();
this.checkDevToolsInContextMenu = new System.Windows.Forms.CheckBox();
this.checkDevToolsWindowOnTop = new System.Windows.Forms.CheckBox(); this.checkDevToolsWindowOnTop = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit();
this.panelAppButtons.SuspendLayout(); this.panelAppButtons.SuspendLayout();
@@ -242,7 +245,10 @@
this.flowPanel.Controls.Add(this.panelClearCacheAuto); this.flowPanel.Controls.Add(this.panelClearCacheAuto);
this.flowPanel.Controls.Add(this.labelConfiguration); this.flowPanel.Controls.Add(this.labelConfiguration);
this.flowPanel.Controls.Add(this.panelConfiguration); 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.labelDevTools);
this.flowPanel.Controls.Add(this.checkDevToolsInContextMenu);
this.flowPanel.Controls.Add(this.checkDevToolsWindowOnTop); this.flowPanel.Controls.Add(this.checkDevToolsWindowOnTop);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9); this.flowPanel.Location = new System.Drawing.Point(9, 9);
@@ -251,26 +257,61 @@
this.flowPanel.TabIndex = 0; this.flowPanel.TabIndex = 0;
this.flowPanel.WrapContents = false; 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 // labelDevTools
// //
this.labelDevTools.AutoSize = true; this.labelDevTools.AutoSize = true;
this.labelDevTools.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); 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.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelDevTools.Name = "labelDevTools"; this.labelDevTools.Name = "labelDevTools";
this.labelDevTools.Size = new System.Drawing.Size(156, 19); this.labelDevTools.Size = new System.Drawing.Size(156, 19);
this.labelDevTools.TabIndex = 7; this.labelDevTools.TabIndex = 9;
this.labelDevTools.Text = "DEVELOPMENT TOOLS"; 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 // checkDevToolsWindowOnTop
// //
this.checkDevToolsWindowOnTop.AutoSize = true; this.checkDevToolsWindowOnTop.AutoSize = true;
this.checkDevToolsWindowOnTop.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkDevToolsWindowOnTop.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkDevToolsWindowOnTop.Location = new System.Drawing.Point(6, 328); this.checkDevToolsWindowOnTop.Location = new System.Drawing.Point(6, 429);
this.checkDevToolsWindowOnTop.Margin = new System.Windows.Forms.Padding(6, 6, 0, 2); this.checkDevToolsWindowOnTop.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkDevToolsWindowOnTop.Name = "checkDevToolsWindowOnTop"; this.checkDevToolsWindowOnTop.Name = "checkDevToolsWindowOnTop";
this.checkDevToolsWindowOnTop.Size = new System.Drawing.Size(168, 19); 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.Text = "Dev Tools Window On Top";
this.checkDevToolsWindowOnTop.UseVisualStyleBackColor = true; this.checkDevToolsWindowOnTop.UseVisualStyleBackColor = true;
// //
@@ -312,6 +353,9 @@
private System.Windows.Forms.CheckBox checkClearCacheAuto; private System.Windows.Forms.CheckBox checkClearCacheAuto;
private System.Windows.Forms.FlowLayoutPanel flowPanel; private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.Label labelDevTools; private System.Windows.Forms.Label labelDevTools;
private System.Windows.Forms.CheckBox checkDevToolsInContextMenu;
private System.Windows.Forms.CheckBox checkDevToolsWindowOnTop; private System.Windows.Forms.CheckBox checkDevToolsWindowOnTop;
private System.Windows.Forms.Label labelProxy;
private System.Windows.Forms.CheckBox checkUseSystemProxyForAllConnections;
} }
} }

View File

@@ -7,169 +7,187 @@ using TweetDuck.Management;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core; using TweetLib.Core;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class TabSettingsAdvanced : FormSettings.BaseTab{ sealed partial class TabSettingsAdvanced : FormSettings.BaseTab {
private readonly Action<string> reinjectBrowserCSS; private readonly Action<string> reinjectBrowserCSS;
private readonly Action openDevTools; private readonly Action openDevTools;
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools){ public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools) {
InitializeComponent(); InitializeComponent();
this.reinjectBrowserCSS = reinjectBrowserCSS; this.reinjectBrowserCSS = reinjectBrowserCSS;
this.openDevTools = openDevTools; this.openDevTools = openDevTools;
// application // application
toolTip.SetToolTip(btnOpenAppFolder, "Opens the folder where the app is located.");
toolTip.SetToolTip(btnOpenDataFolder, "Opens the folder where your profile data is located.");
toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
// browser cache toolTip.SetToolTip(btnOpenAppFolder, "Opens the folder where the app is located.");
toolTip.SetToolTip(btnOpenDataFolder, "Opens the folder where your profile data is located.");
toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources."); // browser cache
toolTip.SetToolTip(checkClearCacheAuto, "Automatically clears cache when its size exceeds the set threshold. Note that cache can only be cleared when closing TweetDuck.");
checkClearCacheAuto.Checked = SysConfig.ClearCacheAutomatically; toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked; toolTip.SetToolTip(checkClearCacheAuto, "Automatically clears cache when its size exceeds the set threshold. Note that cache can only be cleared when closing TweetDuck.");
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
BrowserCache.GetCacheSize(task => {
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result / (1024.0 * 1024.0)) + " MB" : "unknown";
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
});
// configuration checkClearCacheAuto.Checked = SysConfig.ClearCacheAutomatically;
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework."); BrowserCache.GetCacheSize(task => {
toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows."); string text = task.Status == TaskStatus.RanToCompletion ? (int) Math.Ceiling(task.Result / (1024.0 * 1024.0)) + " MB" : "unknown";
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
});
// development tools // configuration
toolTip.SetToolTip(checkDevToolsWindowOnTop, "Sets whether dev tool windows appears on top of other windows."); toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows.");
checkDevToolsWindowOnTop.Checked = Config.DevToolsWindowOnTop; // proxy
if (!BrowserUtils.HasDevTools){ 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.");
checkDevToolsWindowOnTop.Enabled = false;
}
}
public override void OnReady(){ checkUseSystemProxyForAllConnections.Checked = Config.UseSystemProxyForAllConnections;
btnOpenAppFolder.Click += btnOpenAppFolder_Click;
btnOpenDataFolder.Click += btnOpenDataFolder_Click;
btnRestart.Click += btnRestart_Click;
btnRestartArgs.Click += btnRestartArgs_Click;
btnClearCache.Click += btnClearCache_Click; // development tools
checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click;
checkDevToolsWindowOnTop.CheckedChanged += checkDevToolsWindowOnTop_CheckedChanged; 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.");
public override void OnClosing(){ checkDevToolsInContextMenu.Checked = Config.DevToolsInContextMenu;
SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked; checkDevToolsWindowOnTop.Checked = Config.DevToolsWindowOnTop;
SysConfig.ClearCacheThreshold = (int)numClearCacheThreshold.Value; }
}
#region Application public override void OnReady() {
btnOpenAppFolder.Click += btnOpenAppFolder_Click;
private void btnOpenAppFolder_Click(object sender, EventArgs e){ btnOpenDataFolder.Click += btnOpenDataFolder_Click;
App.SystemHandler.OpenFileExplorer(Program.ProgramPath); btnRestart.Click += btnRestart_Click;
} btnRestartArgs.Click += btnRestartArgs_Click;
private void btnOpenDataFolder_Click(object sender, EventArgs e){ btnClearCache.Click += btnClearCache_Click;
App.SystemHandler.OpenFileExplorer(Program.StoragePath); checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
}
private void btnRestart_Click(object sender, EventArgs e){ btnEditCefArgs.Click += btnEditCefArgs_Click;
Program.Restart(); btnEditCSS.Click += btnEditCSS_Click;
}
private void btnRestartArgs_Click(object sender, EventArgs e){ checkUseSystemProxyForAllConnections.CheckedChanged += checkUseSystemProxyForAllConnections_CheckedChanged;
using DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean());
if (dialog.ShowDialog() == DialogResult.OK){ checkDevToolsWindowOnTop.CheckedChanged += checkDevToolsWindowOnTop_CheckedChanged;
Program.RestartWithArgs(dialog.Args); checkDevToolsInContextMenu.CheckedChanged += checkDevToolsInContextMenuOnCheckedChanged;
} }
}
#endregion public override void OnClosing() {
#region Browser Cache SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked;
SysConfig.ClearCacheThreshold = (int) numClearCacheThreshold.Value;
}
private void btnClearCache_Click(object sender, EventArgs e){ #region Application
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 btnOpenAppFolder_Click(object sender, EventArgs e) {
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked; App.SystemHandler.OpenFileExplorer(Program.ProgramPath);
} }
#endregion private void btnOpenDataFolder_Click(object sender, EventArgs e) {
#region Configuration App.SystemHandler.OpenFileExplorer(Program.StoragePath);
}
private void btnEditCefArgs_Click(object sender, EventArgs e){ private void btnRestart_Click(object sender, EventArgs e) {
DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs); Program.Restart();
}
form.VisibleChanged += (sender2, args2) => { private void btnRestartArgs_Click(object sender, EventArgs e) {
form.MoveToCenter(ParentForm); using DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean());
};
form.FormClosed += (sender2, args2) => { if (dialog.ShowDialog() == DialogResult.OK) {
RestoreParentForm(); Program.RestartWithArgs(dialog.Args);
}
}
if (form.DialogResult == DialogResult.OK){ #endregion
Config.CustomCefArgs = form.CefArgs;
}
form.Dispose(); #region Browser Cache
};
form.Show(ParentForm);
NativeMethods.SetFormDisabled(ParentForm, true);
}
private void btnEditCSS_Click(object sender, EventArgs e){ private void btnClearCache_Click(object sender, EventArgs e) {
DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools); btnClearCache.Enabled = false;
BrowserCache.SetClearOnExit();
FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
}
form.VisibleChanged += (sender2, args2) => { private void checkClearCacheAuto_CheckedChanged(object sender, EventArgs e) {
form.MoveToCenter(ParentForm); numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
}; }
form.FormClosed += (sender2, args2) => { #endregion
RestoreParentForm();
if (form.DialogResult == DialogResult.OK){ #region Configuration
Config.CustomBrowserCSS = form.BrowserCSS;
Config.CustomNotificationCSS = form.NotificationCSS;
}
reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing private void btnEditCefArgs_Click(object sender, EventArgs e) {
form.Dispose(); DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs);
};
form.Show(ParentForm);
NativeMethods.SetFormDisabled(ParentForm, true);
}
private void RestoreParentForm(){ form.VisibleChanged += (sender2, args2) => { form.MoveToCenter(ParentForm); };
if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event
NativeMethods.SetFormDisabled(ParentForm, false);
}
}
#endregion form.FormClosed += (sender2, args2) => {
#region Development Tools RestoreParentForm();
private void checkDevToolsWindowOnTop_CheckedChanged(object sender, EventArgs e){ if (form.DialogResult == DialogResult.OK) {
Config.DevToolsWindowOnTop = checkDevToolsWindowOnTop.Checked; Config.CustomCefArgs = form.CefArgs;
} }
#endregion form.Dispose();
} };
form.Show(ParentForm);
NativeMethods.SetFormDisabled(ParentForm, true);
}
private void btnEditCSS_Click(object sender, EventArgs e) {
DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools);
form.VisibleChanged += (sender2, args2) => { form.MoveToCenter(ParentForm); };
form.FormClosed += (sender2, args2) => {
RestoreParentForm();
if (form.DialogResult == DialogResult.OK) {
Config.CustomBrowserCSS = form.BrowserCSS;
Config.CustomNotificationCSS = form.NotificationCSS;
}
reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing
form.Dispose();
};
form.Show(ParentForm);
NativeMethods.SetFormDisabled(ParentForm, true);
}
private void RestoreParentForm() {
if (ParentForm != null) { // when the parent is closed first, ParentForm is null in FormClosed event
NativeMethods.SetFormDisabled(ParentForm, false);
}
}
#endregion
#region Proxy
private void checkUseSystemProxyForAllConnections_CheckedChanged(object sender, EventArgs e) {
Config.UseSystemProxyForAllConnections = checkUseSystemProxyForAllConnections.Checked;
}
#endregion
#region Development Tools
private void checkDevToolsInContextMenuOnCheckedChanged(object sender, EventArgs e) {
Config.DevToolsInContextMenu = checkDevToolsInContextMenu.Checked;
}
private void checkDevToolsWindowOnTop_CheckedChanged(object sender, EventArgs e) {
Config.DevToolsWindowOnTop = checkDevToolsWindowOnTop.Checked;
}
#endregion
}
} }

View File

@@ -1,11 +1,11 @@
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {
partial class TabSettingsFeedback { partial class TabSettingsFeedback {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
/// </summary> /// </summary>
private System.ComponentModel.IContainer components = null; private System.ComponentModel.IContainer components = null;
/// <summary> /// <summary>
/// Clean up any resources being used. /// Clean up any resources being used.
/// </summary> /// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
@@ -18,165 +18,72 @@
#region Component Designer generated code #region Component Designer generated code
/// <summary> /// <summary>
/// Required method for Designer support - do not modify /// Required method for Designer support - do not modify
/// the contents of this method with the code editor. /// the contents of this method with the code editor.
/// </summary> /// </summary>
private void InitializeComponent() { private void InitializeComponent() {
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
this.panelDataCollection = new System.Windows.Forms.Panel(); this.btnSendFeedback = new System.Windows.Forms.Button();
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel(); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.checkDataCollection = new System.Windows.Forms.CheckBox(); this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.labelDataCollectionMessage = new System.Windows.Forms.Label(); this.labelFeedback = new System.Windows.Forms.Label();
this.btnViewReport = new System.Windows.Forms.Button(); this.flowPanel.SuspendLayout();
this.btnSendFeedback = new System.Windows.Forms.Button(); this.SuspendLayout();
this.labelDataCollection = new System.Windows.Forms.Label(); //
this.labelFeedback = new System.Windows.Forms.Label(); // btnSendFeedback
this.toolTip = new System.Windows.Forms.ToolTip(this.components); //
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel(); this.btnSendFeedback.AutoSize = true;
this.panelDataCollection.SuspendLayout(); this.btnSendFeedback.Font = new System.Drawing.Font("Segoe UI", 9F);
this.flowPanel.SuspendLayout(); this.btnSendFeedback.Location = new System.Drawing.Point(5, 23);
this.SuspendLayout(); this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
// this.btnSendFeedback.Name = "btnSendFeedback";
// panelDataCollection this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
// this.btnSendFeedback.Size = new System.Drawing.Size(170, 25);
this.panelDataCollection.Controls.Add(this.labelDataCollectionLink); this.btnSendFeedback.TabIndex = 1;
this.panelDataCollection.Controls.Add(this.checkDataCollection); this.btnSendFeedback.Text = "Send Feedback / Bug Report";
this.panelDataCollection.Location = new System.Drawing.Point(0, 78); this.btnSendFeedback.UseVisualStyleBackColor = true;
this.panelDataCollection.Margin = new System.Windows.Forms.Padding(0); //
this.panelDataCollection.Name = "panelDataCollection"; // flowPanel
this.panelDataCollection.Size = new System.Drawing.Size(300, 28); //
this.panelDataCollection.TabIndex = 3; 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);
// labelDataCollectionLink this.flowPanel.Controls.Add(this.btnSendFeedback);
// this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.labelDataCollectionLink.AutoSize = true; this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.labelDataCollectionLink.Font = new System.Drawing.Font("Segoe UI", 9F); this.flowPanel.Name = "flowPanel";
this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10); this.flowPanel.Size = new System.Drawing.Size(300, 462);
this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline; this.flowPanel.TabIndex = 0;
this.labelDataCollectionLink.Location = new System.Drawing.Point(153, 4); this.flowPanel.WrapContents = false;
this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0); //
this.labelDataCollectionLink.Name = "labelDataCollectionLink"; // labelFeedback
this.labelDataCollectionLink.Size = new System.Drawing.Size(71, 21); //
this.labelDataCollectionLink.TabIndex = 1; this.labelFeedback.AutoSize = true;
this.labelDataCollectionLink.TabStop = true; this.labelFeedback.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelDataCollectionLink.Text = "(learn more)"; this.labelFeedback.Location = new System.Drawing.Point(0, 0);
this.labelDataCollectionLink.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelDataCollectionLink.UseCompatibleTextRendering = true; this.labelFeedback.Name = "labelFeedback";
// this.labelFeedback.Size = new System.Drawing.Size(75, 19);
// checkDataCollection this.labelFeedback.TabIndex = 0;
// this.labelFeedback.Text = "FEEDBACK";
this.checkDataCollection.AutoSize = true; //
this.checkDataCollection.Font = new System.Drawing.Font("Segoe UI", 9F); // TabSettingsFeedback
this.checkDataCollection.Location = new System.Drawing.Point(6, 6); //
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 6, 0, 2); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.checkDataCollection.Name = "checkDataCollection"; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.checkDataCollection.Size = new System.Drawing.Size(147, 19); this.Controls.Add(this.flowPanel);
this.checkDataCollection.TabIndex = 0; this.Name = "TabSettingsFeedback";
this.checkDataCollection.Text = "Send Anonymous Data"; this.Size = new System.Drawing.Size(631, 480);
this.checkDataCollection.UseVisualStyleBackColor = true; this.flowPanel.ResumeLayout(false);
// this.flowPanel.PerformLayout();
// labelDataCollectionMessage this.ResumeLayout(false);
//
this.labelDataCollectionMessage.Font = new System.Drawing.Font("Segoe UI", 9F);
this.labelDataCollectionMessage.Location = new System.Drawing.Point(6, 143);
this.labelDataCollectionMessage.Margin = new System.Windows.Forms.Padding(6);
this.labelDataCollectionMessage.Name = "labelDataCollectionMessage";
this.labelDataCollectionMessage.Size = new System.Drawing.Size(288, 67);
this.labelDataCollectionMessage.TabIndex = 5;
//
// btnViewReport
//
this.btnViewReport.AutoSize = true;
this.btnViewReport.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnViewReport.Location = new System.Drawing.Point(5, 109);
this.btnViewReport.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnViewReport.Name = "btnViewReport";
this.btnViewReport.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnViewReport.Size = new System.Drawing.Size(155, 25);
this.btnViewReport.TabIndex = 4;
this.btnViewReport.Text = "View My Analytics Report";
this.btnViewReport.UseVisualStyleBackColor = true;
//
// btnSendFeedback
//
this.btnSendFeedback.AutoSize = true;
this.btnSendFeedback.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnSendFeedback.Location = new System.Drawing.Point(5, 23);
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnSendFeedback.Name = "btnSendFeedback";
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnSendFeedback.Size = new System.Drawing.Size(170, 25);
this.btnSendFeedback.TabIndex = 1;
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
this.btnSendFeedback.UseVisualStyleBackColor = true;
//
// labelDataCollection
//
this.labelDataCollection.AutoSize = true;
this.labelDataCollection.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelDataCollection.Location = new System.Drawing.Point(3, 63);
this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataCollection.Name = "labelDataCollection";
this.labelDataCollection.Size = new System.Drawing.Size(88, 15);
this.labelDataCollection.TabIndex = 2;
this.labelDataCollection.Text = "Data Collection";
//
// labelFeedback
//
this.labelFeedback.AutoSize = true;
this.labelFeedback.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelFeedback.Location = new System.Drawing.Point(0, 0);
this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelFeedback.Name = "labelFeedback";
this.labelFeedback.Size = new System.Drawing.Size(75, 19);
this.labelFeedback.TabIndex = 0;
this.labelFeedback.Text = "FEEDBACK";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelFeedback);
this.flowPanel.Controls.Add(this.btnSendFeedback);
this.flowPanel.Controls.Add(this.labelDataCollection);
this.flowPanel.Controls.Add(this.panelDataCollection);
this.flowPanel.Controls.Add(this.btnViewReport);
this.flowPanel.Controls.Add(this.labelDataCollectionMessage);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(300, 462);
this.flowPanel.TabIndex = 0;
this.flowPanel.WrapContents = false;
//
// TabSettingsFeedback
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsFeedback";
this.Size = new System.Drawing.Size(631, 480);
this.panelDataCollection.ResumeLayout(false);
this.panelDataCollection.PerformLayout();
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
} }
#endregion
private System.Windows.Forms.Panel panelDataCollection;
private System.Windows.Forms.CheckBox checkDataCollection;
private System.Windows.Forms.Label labelDataCollection;
private System.Windows.Forms.Label labelFeedback;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
private System.Windows.Forms.Button btnSendFeedback; private System.Windows.Forms.Button btnSendFeedback;
private System.Windows.Forms.Button btnViewReport;
private System.Windows.Forms.Label labelDataCollectionMessage;
private System.Windows.Forms.FlowLayoutPanel flowPanel; private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.Label labelFeedback;
#endregion
} }
} }

View File

@@ -1,58 +1,22 @@
using System; using System;
using System.Windows.Forms;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class TabSettingsFeedback : FormSettings.BaseTab{ sealed partial class TabSettingsFeedback : FormSettings.BaseTab {
private readonly AnalyticsFile analyticsFile; public TabSettingsFeedback() {
private readonly AnalyticsReportGenerator.ExternalInfo analyticsInfo; InitializeComponent();
private readonly PluginManager plugins; }
public TabSettingsFeedback(AnalyticsManager analytics, AnalyticsReportGenerator.ExternalInfo analyticsInfo, PluginManager plugins){ public override void OnReady() {
InitializeComponent(); btnSendFeedback.Click += btnSendFeedback_Click;
}
this.analyticsFile = analytics?.File ?? AnalyticsFile.Load(Program.AnalyticsFilePath);
this.analyticsInfo = analyticsInfo;
this.plugins = plugins;
// feedback #region Feedback
checkDataCollection.Checked = Config.AllowDataCollection; private void btnSendFeedback_Click(object sender, EventArgs e) {
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/issues/new");
}
if (analytics != null){ #endregion
string collectionTime = analyticsFile.LastCollectionMessage; }
labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: " + collectionTime;
}
}
public override void OnReady(){
btnSendFeedback.Click += btnSendFeedback_Click;
checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged;
labelDataCollectionLink.LinkClicked += labelDataCollectionLink_LinkClicked;
btnViewReport.Click += btnViewReport_Click;
}
#region Feedback
private void btnSendFeedback_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/issues/new");
}
private void checkDataCollection_CheckedChanged(object sender, EventArgs e){
Config.AllowDataCollection = checkDataCollection.Checked;
}
private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
}
private void btnViewReport_Click(object sender, EventArgs e){
using DialogSettingsAnalytics dialog = new DialogSettingsAnalytics(AnalyticsReportGenerator.Create(analyticsFile, analyticsInfo, plugins));
dialog.ShowDialog();
}
#endregion
}
} }

View File

@@ -1,11 +1,11 @@
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {
partial class TabSettingsGeneral { partial class TabSettingsGeneral {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
/// </summary> /// </summary>
private System.ComponentModel.IContainer components = null; private System.ComponentModel.IContainer components = null;
/// <summary> /// <summary>
/// Clean up any resources being used. /// Clean up any resources being used.
/// </summary> /// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
@@ -18,8 +18,8 @@
#region Component Designer generated code #region Component Designer generated code
/// <summary> /// <summary>
/// Required method for Designer support - do not modify /// Required method for Designer support - do not modify
/// the contents of this method with the code editor. /// the contents of this method with the code editor.
/// </summary> /// </summary>
private void InitializeComponent() { private void InitializeComponent() {
@@ -44,6 +44,7 @@
this.labelBrowserSettings = new System.Windows.Forms.Label(); this.labelBrowserSettings = new System.Windows.Forms.Label();
this.checkSmoothScrolling = new System.Windows.Forms.CheckBox(); this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
this.checkTouchAdjustment = 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.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
this.labelBrowserPath = new System.Windows.Forms.Label(); this.labelBrowserPath = new System.Windows.Forms.Label();
this.comboBoxCustomBrowser = new System.Windows.Forms.ComboBox(); this.comboBoxCustomBrowser = new System.Windows.Forms.ComboBox();
@@ -73,9 +74,9 @@
this.panelCustomBrowser.SuspendLayout(); this.panelCustomBrowser.SuspendLayout();
this.panelCustomVideoPlayer.SuspendLayout(); this.panelCustomVideoPlayer.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// checkExpandLinks // checkExpandLinks
// //
this.checkExpandLinks.AutoSize = true; this.checkExpandLinks.AutoSize = true;
this.checkExpandLinks.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkExpandLinks.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkExpandLinks.Location = new System.Drawing.Point(6, 26); this.checkExpandLinks.Location = new System.Drawing.Point(6, 26);
@@ -85,34 +86,34 @@
this.checkExpandLinks.TabIndex = 1; this.checkExpandLinks.TabIndex = 1;
this.checkExpandLinks.Text = "Expand Links When Hovered"; this.checkExpandLinks.Text = "Expand Links When Hovered";
this.checkExpandLinks.UseVisualStyleBackColor = true; this.checkExpandLinks.UseVisualStyleBackColor = true;
// //
// checkUpdateNotifications // checkUpdateNotifications
// //
this.checkUpdateNotifications.AutoSize = true; this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Font = new System.Drawing.Font("Segoe UI", 9F); 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.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkUpdateNotifications.Name = "checkUpdateNotifications"; this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(182, 19); 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.Text = "Check Updates Automatically";
this.checkUpdateNotifications.UseVisualStyleBackColor = true; this.checkUpdateNotifications.UseVisualStyleBackColor = true;
// //
// btnCheckUpdates // btnCheckUpdates
// //
this.btnCheckUpdates.AutoSize = true; this.btnCheckUpdates.AutoSize = true;
this.btnCheckUpdates.Font = new System.Drawing.Font("Segoe UI", 9F); 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.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnCheckUpdates.Name = "btnCheckUpdates"; this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0); this.btnCheckUpdates.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnCheckUpdates.Size = new System.Drawing.Size(128, 25); 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.Text = "Check Updates Now";
this.btnCheckUpdates.UseVisualStyleBackColor = true; this.btnCheckUpdates.UseVisualStyleBackColor = true;
// //
// labelZoomValue // labelZoomValue
// //
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent; this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Font = new System.Drawing.Font("Segoe UI", 9F); this.labelZoomValue.Font = new System.Drawing.Font("Segoe UI", 9F);
this.labelZoomValue.Location = new System.Drawing.Point(176, 4); this.labelZoomValue.Location = new System.Drawing.Point(176, 4);
@@ -122,9 +123,9 @@
this.labelZoomValue.TabIndex = 1; this.labelZoomValue.TabIndex = 1;
this.labelZoomValue.Text = "100%"; this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
// //
// checkBestImageQuality // checkBestImageQuality
// //
this.checkBestImageQuality.AutoSize = true; this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkBestImageQuality.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 122); this.checkBestImageQuality.Location = new System.Drawing.Point(6, 122);
@@ -134,9 +135,9 @@
this.checkBestImageQuality.TabIndex = 5; this.checkBestImageQuality.TabIndex = 5;
this.checkBestImageQuality.Text = "Best Image Quality"; this.checkBestImageQuality.Text = "Best Image Quality";
this.checkBestImageQuality.UseVisualStyleBackColor = true; this.checkBestImageQuality.UseVisualStyleBackColor = true;
// //
// checkOpenSearchInFirstColumn // checkOpenSearchInFirstColumn
// //
this.checkOpenSearchInFirstColumn.AutoSize = true; this.checkOpenSearchInFirstColumn.AutoSize = true;
this.checkOpenSearchInFirstColumn.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkOpenSearchInFirstColumn.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 74); this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 74);
@@ -146,9 +147,9 @@
this.checkOpenSearchInFirstColumn.TabIndex = 3; this.checkOpenSearchInFirstColumn.TabIndex = 3;
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column"; this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true; this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
// //
// trackBarZoom // trackBarZoom
// //
this.trackBarZoom.AutoSize = false; this.trackBarZoom.AutoSize = false;
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control; this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
this.trackBarZoom.LargeChange = 25; this.trackBarZoom.LargeChange = 25;
@@ -161,25 +162,25 @@
this.trackBarZoom.TabIndex = 0; this.trackBarZoom.TabIndex = 0;
this.trackBarZoom.TickFrequency = 25; this.trackBarZoom.TickFrequency = 25;
this.trackBarZoom.Value = 100; this.trackBarZoom.Value = 100;
// //
// labelZoom // labelZoom
// //
this.labelZoom.AutoSize = true; this.labelZoom.AutoSize = true;
this.labelZoom.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); 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.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom"; this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(39, 15); this.labelZoom.Size = new System.Drawing.Size(39, 15);
this.labelZoom.TabIndex = 11; this.labelZoom.TabIndex = 12;
this.labelZoom.Text = "Zoom"; this.labelZoom.Text = "Zoom";
// //
// zoomUpdateTimer // zoomUpdateTimer
// //
this.zoomUpdateTimer.Interval = 250; this.zoomUpdateTimer.Interval = 250;
this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick); this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick);
// //
// labelUI // labelUI
// //
this.labelUI.AutoSize = true; this.labelUI.AutoSize = true;
this.labelUI.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelUI.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelUI.Location = new System.Drawing.Point(0, 0); this.labelUI.Location = new System.Drawing.Point(0, 0);
@@ -188,19 +189,19 @@
this.labelUI.Size = new System.Drawing.Size(118, 19); this.labelUI.Size = new System.Drawing.Size(118, 19);
this.labelUI.TabIndex = 0; this.labelUI.TabIndex = 0;
this.labelUI.Text = "USER INTERFACE"; this.labelUI.Text = "USER INTERFACE";
// //
// panelZoom // panelZoom
// //
this.panelZoom.Controls.Add(this.trackBarZoom); this.panelZoom.Controls.Add(this.trackBarZoom);
this.panelZoom.Controls.Add(this.labelZoomValue); 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.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0);
this.panelZoom.Name = "panelZoom"; this.panelZoom.Name = "panelZoom";
this.panelZoom.Size = new System.Drawing.Size(300, 35); this.panelZoom.Size = new System.Drawing.Size(300, 35);
this.panelZoom.TabIndex = 12; this.panelZoom.TabIndex = 13;
// //
// checkAnimatedAvatars // checkAnimatedAvatars
// //
this.checkAnimatedAvatars.AutoSize = true; this.checkAnimatedAvatars.AutoSize = true;
this.checkAnimatedAvatars.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkAnimatedAvatars.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 146); this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 146);
@@ -210,21 +211,21 @@
this.checkAnimatedAvatars.TabIndex = 6; this.checkAnimatedAvatars.TabIndex = 6;
this.checkAnimatedAvatars.Text = "Enable Animated Avatars"; this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
this.checkAnimatedAvatars.UseVisualStyleBackColor = true; this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
// //
// labelUpdates // labelUpdates
// //
this.labelUpdates.AutoSize = true; this.labelUpdates.AutoSize = true;
this.labelUpdates.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); 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.Location = new System.Drawing.Point(0, 383);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1); this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 7, 0, 1);
this.labelUpdates.Name = "labelUpdates"; this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(69, 19); this.labelUpdates.Size = new System.Drawing.Size(69, 19);
this.labelUpdates.TabIndex = 13; this.labelUpdates.TabIndex = 14;
this.labelUpdates.Text = "UPDATES"; this.labelUpdates.Text = "UPDATES";
// //
// flowPanelLeft // flowPanelLeft
// //
this.flowPanelLeft.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.flowPanelLeft.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left))); | System.Windows.Forms.AnchorStyles.Left)));
this.flowPanelLeft.Controls.Add(this.labelUI); this.flowPanelLeft.Controls.Add(this.labelUI);
this.flowPanelLeft.Controls.Add(this.checkExpandLinks); this.flowPanelLeft.Controls.Add(this.checkExpandLinks);
@@ -236,6 +237,7 @@
this.flowPanelLeft.Controls.Add(this.labelBrowserSettings); this.flowPanelLeft.Controls.Add(this.labelBrowserSettings);
this.flowPanelLeft.Controls.Add(this.checkSmoothScrolling); this.flowPanelLeft.Controls.Add(this.checkSmoothScrolling);
this.flowPanelLeft.Controls.Add(this.checkTouchAdjustment); this.flowPanelLeft.Controls.Add(this.checkTouchAdjustment);
this.flowPanelLeft.Controls.Add(this.checkAutomaticallyDetectColorProfile);
this.flowPanelLeft.Controls.Add(this.checkHardwareAcceleration); this.flowPanelLeft.Controls.Add(this.checkHardwareAcceleration);
this.flowPanelLeft.Controls.Add(this.labelZoom); this.flowPanelLeft.Controls.Add(this.labelZoom);
this.flowPanelLeft.Controls.Add(this.panelZoom); this.flowPanelLeft.Controls.Add(this.panelZoom);
@@ -248,9 +250,9 @@
this.flowPanelLeft.Size = new System.Drawing.Size(300, 462); this.flowPanelLeft.Size = new System.Drawing.Size(300, 462);
this.flowPanelLeft.TabIndex = 0; this.flowPanelLeft.TabIndex = 0;
this.flowPanelLeft.WrapContents = false; this.flowPanelLeft.WrapContents = false;
// //
// checkFocusDmInput // checkFocusDmInput
// //
this.checkFocusDmInput.AutoSize = true; this.checkFocusDmInput.AutoSize = true;
this.checkFocusDmInput.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkFocusDmInput.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkFocusDmInput.Location = new System.Drawing.Point(6, 50); this.checkFocusDmInput.Location = new System.Drawing.Point(6, 50);
@@ -260,9 +262,9 @@
this.checkFocusDmInput.TabIndex = 2; this.checkFocusDmInput.TabIndex = 2;
this.checkFocusDmInput.Text = "Focus Input Field When Opening Direct Message"; this.checkFocusDmInput.Text = "Focus Input Field When Opening Direct Message";
this.checkFocusDmInput.UseVisualStyleBackColor = true; this.checkFocusDmInput.UseVisualStyleBackColor = true;
// //
// checkKeepLikeFollowDialogsOpen // checkKeepLikeFollowDialogsOpen
// //
this.checkKeepLikeFollowDialogsOpen.AutoSize = true; this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
this.checkKeepLikeFollowDialogsOpen.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkKeepLikeFollowDialogsOpen.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 98); this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 98);
@@ -272,9 +274,9 @@
this.checkKeepLikeFollowDialogsOpen.TabIndex = 4; this.checkKeepLikeFollowDialogsOpen.TabIndex = 4;
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open"; this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true; this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
// //
// labelBrowserSettings // labelBrowserSettings
// //
this.labelBrowserSettings.AutoSize = true; this.labelBrowserSettings.AutoSize = true;
this.labelBrowserSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelBrowserSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelBrowserSettings.Location = new System.Drawing.Point(0, 192); this.labelBrowserSettings.Location = new System.Drawing.Point(0, 192);
@@ -283,9 +285,9 @@
this.labelBrowserSettings.Size = new System.Drawing.Size(143, 19); this.labelBrowserSettings.Size = new System.Drawing.Size(143, 19);
this.labelBrowserSettings.TabIndex = 7; this.labelBrowserSettings.TabIndex = 7;
this.labelBrowserSettings.Text = "BROWSER SETTINGS"; this.labelBrowserSettings.Text = "BROWSER SETTINGS";
// //
// checkSmoothScrolling // checkSmoothScrolling
// //
this.checkSmoothScrolling.AutoSize = true; this.checkSmoothScrolling.AutoSize = true;
this.checkSmoothScrolling.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkSmoothScrolling.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 218); this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 218);
@@ -295,9 +297,9 @@
this.checkSmoothScrolling.TabIndex = 8; this.checkSmoothScrolling.TabIndex = 8;
this.checkSmoothScrolling.Text = "Smooth Scrolling"; this.checkSmoothScrolling.Text = "Smooth Scrolling";
this.checkSmoothScrolling.UseVisualStyleBackColor = true; this.checkSmoothScrolling.UseVisualStyleBackColor = true;
// //
// checkTouchAdjustment // checkTouchAdjustment
// //
this.checkTouchAdjustment.AutoSize = true; this.checkTouchAdjustment.AutoSize = true;
this.checkTouchAdjustment.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkTouchAdjustment.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkTouchAdjustment.Location = new System.Drawing.Point(6, 242); this.checkTouchAdjustment.Location = new System.Drawing.Point(6, 242);
@@ -307,21 +309,33 @@
this.checkTouchAdjustment.TabIndex = 9; this.checkTouchAdjustment.TabIndex = 9;
this.checkTouchAdjustment.Text = "Touch Screen Adjustment"; this.checkTouchAdjustment.Text = "Touch Screen Adjustment";
this.checkTouchAdjustment.UseVisualStyleBackColor = true; 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 // checkHardwareAcceleration
// //
this.checkHardwareAcceleration.AutoSize = true; this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Font = new System.Drawing.Font("Segoe UI", 9F); 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.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration"; this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(146, 19); this.checkHardwareAcceleration.Size = new System.Drawing.Size(146, 19);
this.checkHardwareAcceleration.TabIndex = 10; this.checkHardwareAcceleration.TabIndex = 11;
this.checkHardwareAcceleration.Text = "Hardware Acceleration"; this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.checkHardwareAcceleration.UseVisualStyleBackColor = true; this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
// //
// labelBrowserPath // labelBrowserPath
// //
this.labelBrowserPath.AutoSize = true; this.labelBrowserPath.AutoSize = true;
this.labelBrowserPath.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); this.labelBrowserPath.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelBrowserPath.Location = new System.Drawing.Point(3, 275); this.labelBrowserPath.Location = new System.Drawing.Point(3, 275);
@@ -330,9 +344,9 @@
this.labelBrowserPath.Size = new System.Drawing.Size(104, 15); this.labelBrowserPath.Size = new System.Drawing.Size(104, 15);
this.labelBrowserPath.TabIndex = 9; this.labelBrowserPath.TabIndex = 9;
this.labelBrowserPath.Text = "Open Links With..."; this.labelBrowserPath.Text = "Open Links With...";
// //
// comboBoxCustomBrowser // comboBoxCustomBrowser
// //
this.comboBoxCustomBrowser.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxCustomBrowser.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxCustomBrowser.Font = new System.Drawing.Font("Segoe UI", 9F); this.comboBoxCustomBrowser.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxCustomBrowser.FormattingEnabled = true; this.comboBoxCustomBrowser.FormattingEnabled = true;
@@ -341,9 +355,9 @@
this.comboBoxCustomBrowser.Name = "comboBoxCustomBrowser"; this.comboBoxCustomBrowser.Name = "comboBoxCustomBrowser";
this.comboBoxCustomBrowser.Size = new System.Drawing.Size(173, 23); this.comboBoxCustomBrowser.Size = new System.Drawing.Size(173, 23);
this.comboBoxCustomBrowser.TabIndex = 0; this.comboBoxCustomBrowser.TabIndex = 0;
// //
// labelSearchEngine // labelSearchEngine
// //
this.labelSearchEngine.AutoSize = true; this.labelSearchEngine.AutoSize = true;
this.labelSearchEngine.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); this.labelSearchEngine.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelSearchEngine.Location = new System.Drawing.Point(3, 389); this.labelSearchEngine.Location = new System.Drawing.Point(3, 389);
@@ -352,9 +366,9 @@
this.labelSearchEngine.Size = new System.Drawing.Size(82, 15); this.labelSearchEngine.Size = new System.Drawing.Size(82, 15);
this.labelSearchEngine.TabIndex = 13; this.labelSearchEngine.TabIndex = 13;
this.labelSearchEngine.Text = "Search Engine"; this.labelSearchEngine.Text = "Search Engine";
// //
// comboBoxSearchEngine // comboBoxSearchEngine
// //
this.comboBoxSearchEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxSearchEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxSearchEngine.Font = new System.Drawing.Font("Segoe UI", 9F); this.comboBoxSearchEngine.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxSearchEngine.FormattingEnabled = true; this.comboBoxSearchEngine.FormattingEnabled = true;
@@ -363,10 +377,10 @@
this.comboBoxSearchEngine.Name = "comboBoxSearchEngine"; this.comboBoxSearchEngine.Name = "comboBoxSearchEngine";
this.comboBoxSearchEngine.Size = new System.Drawing.Size(173, 23); this.comboBoxSearchEngine.Size = new System.Drawing.Size(173, 23);
this.comboBoxSearchEngine.TabIndex = 14; this.comboBoxSearchEngine.TabIndex = 14;
// //
// flowPanelRight // flowPanelRight
// //
this.flowPanelRight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.flowPanelRight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left))); | System.Windows.Forms.AnchorStyles.Left)));
this.flowPanelRight.Controls.Add(this.labelLocales); this.flowPanelRight.Controls.Add(this.labelLocales);
this.flowPanelRight.Controls.Add(this.checkSpellCheck); this.flowPanelRight.Controls.Add(this.checkSpellCheck);
@@ -389,9 +403,9 @@
this.flowPanelRight.Size = new System.Drawing.Size(300, 462); this.flowPanelRight.Size = new System.Drawing.Size(300, 462);
this.flowPanelRight.TabIndex = 1; this.flowPanelRight.TabIndex = 1;
this.flowPanelRight.WrapContents = false; this.flowPanelRight.WrapContents = false;
// //
// labelLocales // labelLocales
// //
this.labelLocales.AutoSize = true; this.labelLocales.AutoSize = true;
this.labelLocales.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelLocales.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelLocales.Location = new System.Drawing.Point(0, 0); this.labelLocales.Location = new System.Drawing.Point(0, 0);
@@ -400,9 +414,9 @@
this.labelLocales.Size = new System.Drawing.Size(67, 19); this.labelLocales.Size = new System.Drawing.Size(67, 19);
this.labelLocales.TabIndex = 0; this.labelLocales.TabIndex = 0;
this.labelLocales.Text = "LOCALES"; this.labelLocales.Text = "LOCALES";
// //
// checkSpellCheck // checkSpellCheck
// //
this.checkSpellCheck.AutoSize = true; this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkSpellCheck.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkSpellCheck.Location = new System.Drawing.Point(6, 26); this.checkSpellCheck.Location = new System.Drawing.Point(6, 26);
@@ -412,9 +426,9 @@
this.checkSpellCheck.TabIndex = 1; this.checkSpellCheck.TabIndex = 1;
this.checkSpellCheck.Text = "Enable Spell Check"; this.checkSpellCheck.Text = "Enable Spell Check";
this.checkSpellCheck.UseVisualStyleBackColor = true; this.checkSpellCheck.UseVisualStyleBackColor = true;
// //
// labelSpellCheckLanguage // labelSpellCheckLanguage
// //
this.labelSpellCheckLanguage.AutoSize = true; this.labelSpellCheckLanguage.AutoSize = true;
this.labelSpellCheckLanguage.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); this.labelSpellCheckLanguage.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelSpellCheckLanguage.Location = new System.Drawing.Point(3, 59); this.labelSpellCheckLanguage.Location = new System.Drawing.Point(3, 59);
@@ -423,9 +437,9 @@
this.labelSpellCheckLanguage.Size = new System.Drawing.Size(123, 15); this.labelSpellCheckLanguage.Size = new System.Drawing.Size(123, 15);
this.labelSpellCheckLanguage.TabIndex = 2; this.labelSpellCheckLanguage.TabIndex = 2;
this.labelSpellCheckLanguage.Text = "Spell Check Language"; this.labelSpellCheckLanguage.Text = "Spell Check Language";
// //
// comboBoxSpellCheckLanguage // comboBoxSpellCheckLanguage
// //
this.comboBoxSpellCheckLanguage.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxSpellCheckLanguage.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxSpellCheckLanguage.Font = new System.Drawing.Font("Segoe UI", 9F); this.comboBoxSpellCheckLanguage.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxSpellCheckLanguage.FormattingEnabled = true; this.comboBoxSpellCheckLanguage.FormattingEnabled = true;
@@ -434,9 +448,9 @@
this.comboBoxSpellCheckLanguage.Name = "comboBoxSpellCheckLanguage"; this.comboBoxSpellCheckLanguage.Name = "comboBoxSpellCheckLanguage";
this.comboBoxSpellCheckLanguage.Size = new System.Drawing.Size(290, 23); this.comboBoxSpellCheckLanguage.Size = new System.Drawing.Size(290, 23);
this.comboBoxSpellCheckLanguage.TabIndex = 3; this.comboBoxSpellCheckLanguage.TabIndex = 3;
// //
// labelTranslationTarget // labelTranslationTarget
// //
this.labelTranslationTarget.AutoSize = true; this.labelTranslationTarget.AutoSize = true;
this.labelTranslationTarget.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); this.labelTranslationTarget.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelTranslationTarget.Location = new System.Drawing.Point(3, 116); this.labelTranslationTarget.Location = new System.Drawing.Point(3, 116);
@@ -445,9 +459,9 @@
this.labelTranslationTarget.Size = new System.Drawing.Size(142, 15); this.labelTranslationTarget.Size = new System.Drawing.Size(142, 15);
this.labelTranslationTarget.TabIndex = 4; this.labelTranslationTarget.TabIndex = 4;
this.labelTranslationTarget.Text = "Bing Translator Language"; this.labelTranslationTarget.Text = "Bing Translator Language";
// //
// comboBoxTranslationTarget // comboBoxTranslationTarget
// //
this.comboBoxTranslationTarget.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxTranslationTarget.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTranslationTarget.Font = new System.Drawing.Font("Segoe UI", 9F); this.comboBoxTranslationTarget.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxTranslationTarget.FormattingEnabled = true; this.comboBoxTranslationTarget.FormattingEnabled = true;
@@ -456,9 +470,9 @@
this.comboBoxTranslationTarget.Name = "comboBoxTranslationTarget"; this.comboBoxTranslationTarget.Name = "comboBoxTranslationTarget";
this.comboBoxTranslationTarget.Size = new System.Drawing.Size(290, 23); this.comboBoxTranslationTarget.Size = new System.Drawing.Size(290, 23);
this.comboBoxTranslationTarget.TabIndex = 5; this.comboBoxTranslationTarget.TabIndex = 5;
// //
// labelFirstDayOfWeek // labelFirstDayOfWeek
// //
this.labelFirstDayOfWeek.AutoSize = true; this.labelFirstDayOfWeek.AutoSize = true;
this.labelFirstDayOfWeek.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); this.labelFirstDayOfWeek.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelFirstDayOfWeek.Location = new System.Drawing.Point(3, 173); this.labelFirstDayOfWeek.Location = new System.Drawing.Point(3, 173);
@@ -467,9 +481,9 @@
this.labelFirstDayOfWeek.Size = new System.Drawing.Size(125, 15); this.labelFirstDayOfWeek.Size = new System.Drawing.Size(125, 15);
this.labelFirstDayOfWeek.TabIndex = 6; this.labelFirstDayOfWeek.TabIndex = 6;
this.labelFirstDayOfWeek.Text = "First Day Of The Week"; this.labelFirstDayOfWeek.Text = "First Day Of The Week";
// //
// comboBoxFirstDayOfWeek // comboBoxFirstDayOfWeek
// //
this.comboBoxFirstDayOfWeek.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxFirstDayOfWeek.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxFirstDayOfWeek.Font = new System.Drawing.Font("Segoe UI", 9F); this.comboBoxFirstDayOfWeek.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxFirstDayOfWeek.FormattingEnabled = true; this.comboBoxFirstDayOfWeek.FormattingEnabled = true;
@@ -478,9 +492,9 @@
this.comboBoxFirstDayOfWeek.Name = "comboBoxFirstDayOfWeek"; this.comboBoxFirstDayOfWeek.Name = "comboBoxFirstDayOfWeek";
this.comboBoxFirstDayOfWeek.Size = new System.Drawing.Size(173, 23); this.comboBoxFirstDayOfWeek.Size = new System.Drawing.Size(173, 23);
this.comboBoxFirstDayOfWeek.TabIndex = 7; this.comboBoxFirstDayOfWeek.TabIndex = 7;
// //
// labelExternalApplications // labelExternalApplications
// //
this.labelExternalApplications.AutoSize = true; this.labelExternalApplications.AutoSize = true;
this.labelExternalApplications.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelExternalApplications.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelExternalApplications.Location = new System.Drawing.Point(0, 243); this.labelExternalApplications.Location = new System.Drawing.Point(0, 243);
@@ -489,9 +503,9 @@
this.labelExternalApplications.Size = new System.Drawing.Size(176, 19); this.labelExternalApplications.Size = new System.Drawing.Size(176, 19);
this.labelExternalApplications.TabIndex = 8; this.labelExternalApplications.TabIndex = 8;
this.labelExternalApplications.Text = "EXTERNAL APPLICATIONS"; this.labelExternalApplications.Text = "EXTERNAL APPLICATIONS";
// //
// panelCustomBrowser // panelCustomBrowser
// //
this.panelCustomBrowser.Controls.Add(this.comboBoxCustomBrowser); this.panelCustomBrowser.Controls.Add(this.comboBoxCustomBrowser);
this.panelCustomBrowser.Controls.Add(this.btnCustomBrowserChange); this.panelCustomBrowser.Controls.Add(this.btnCustomBrowserChange);
this.panelCustomBrowser.Location = new System.Drawing.Point(0, 293); this.panelCustomBrowser.Location = new System.Drawing.Point(0, 293);
@@ -499,9 +513,9 @@
this.panelCustomBrowser.Name = "panelCustomBrowser"; this.panelCustomBrowser.Name = "panelCustomBrowser";
this.panelCustomBrowser.Size = new System.Drawing.Size(300, 24); this.panelCustomBrowser.Size = new System.Drawing.Size(300, 24);
this.panelCustomBrowser.TabIndex = 10; this.panelCustomBrowser.TabIndex = 10;
// //
// btnCustomBrowserChange // btnCustomBrowserChange
// //
this.btnCustomBrowserChange.AutoSize = true; this.btnCustomBrowserChange.AutoSize = true;
this.btnCustomBrowserChange.Font = new System.Drawing.Font("Segoe UI", 9F); this.btnCustomBrowserChange.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnCustomBrowserChange.Location = new System.Drawing.Point(186, 0); this.btnCustomBrowserChange.Location = new System.Drawing.Point(186, 0);
@@ -513,9 +527,9 @@
this.btnCustomBrowserChange.Text = "Change..."; this.btnCustomBrowserChange.Text = "Change...";
this.btnCustomBrowserChange.UseVisualStyleBackColor = true; this.btnCustomBrowserChange.UseVisualStyleBackColor = true;
this.btnCustomBrowserChange.Visible = false; this.btnCustomBrowserChange.Visible = false;
// //
// labelVideoPlayerPath // labelVideoPlayerPath
// //
this.labelVideoPlayerPath.AutoSize = true; this.labelVideoPlayerPath.AutoSize = true;
this.labelVideoPlayerPath.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); this.labelVideoPlayerPath.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelVideoPlayerPath.Location = new System.Drawing.Point(3, 332); this.labelVideoPlayerPath.Location = new System.Drawing.Point(3, 332);
@@ -524,9 +538,9 @@
this.labelVideoPlayerPath.Size = new System.Drawing.Size(106, 15); this.labelVideoPlayerPath.Size = new System.Drawing.Size(106, 15);
this.labelVideoPlayerPath.TabIndex = 11; this.labelVideoPlayerPath.TabIndex = 11;
this.labelVideoPlayerPath.Text = "Play Videos With..."; this.labelVideoPlayerPath.Text = "Play Videos With...";
// //
// panelCustomVideoPlayer // panelCustomVideoPlayer
// //
this.panelCustomVideoPlayer.Controls.Add(this.comboBoxCustomVideoPlayer); this.panelCustomVideoPlayer.Controls.Add(this.comboBoxCustomVideoPlayer);
this.panelCustomVideoPlayer.Controls.Add(this.btnCustomVideoPlayerChange); this.panelCustomVideoPlayer.Controls.Add(this.btnCustomVideoPlayerChange);
this.panelCustomVideoPlayer.Location = new System.Drawing.Point(0, 350); this.panelCustomVideoPlayer.Location = new System.Drawing.Point(0, 350);
@@ -534,9 +548,9 @@
this.panelCustomVideoPlayer.Name = "panelCustomVideoPlayer"; this.panelCustomVideoPlayer.Name = "panelCustomVideoPlayer";
this.panelCustomVideoPlayer.Size = new System.Drawing.Size(300, 24); this.panelCustomVideoPlayer.Size = new System.Drawing.Size(300, 24);
this.panelCustomVideoPlayer.TabIndex = 12; this.panelCustomVideoPlayer.TabIndex = 12;
// //
// comboBoxCustomVideoPlayer // comboBoxCustomVideoPlayer
// //
this.comboBoxCustomVideoPlayer.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxCustomVideoPlayer.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxCustomVideoPlayer.Font = new System.Drawing.Font("Segoe UI", 9F); this.comboBoxCustomVideoPlayer.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxCustomVideoPlayer.FormattingEnabled = true; this.comboBoxCustomVideoPlayer.FormattingEnabled = true;
@@ -545,9 +559,9 @@
this.comboBoxCustomVideoPlayer.Name = "comboBoxCustomVideoPlayer"; this.comboBoxCustomVideoPlayer.Name = "comboBoxCustomVideoPlayer";
this.comboBoxCustomVideoPlayer.Size = new System.Drawing.Size(173, 23); this.comboBoxCustomVideoPlayer.Size = new System.Drawing.Size(173, 23);
this.comboBoxCustomVideoPlayer.TabIndex = 0; this.comboBoxCustomVideoPlayer.TabIndex = 0;
// //
// btnCustomVideoPlayerChange // btnCustomVideoPlayerChange
// //
this.btnCustomVideoPlayerChange.AutoSize = true; this.btnCustomVideoPlayerChange.AutoSize = true;
this.btnCustomVideoPlayerChange.Font = new System.Drawing.Font("Segoe UI", 9F); this.btnCustomVideoPlayerChange.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnCustomVideoPlayerChange.Location = new System.Drawing.Point(186, 0); this.btnCustomVideoPlayerChange.Location = new System.Drawing.Point(186, 0);
@@ -559,10 +573,10 @@
this.btnCustomVideoPlayerChange.Text = "Change..."; this.btnCustomVideoPlayerChange.Text = "Change...";
this.btnCustomVideoPlayerChange.UseVisualStyleBackColor = true; this.btnCustomVideoPlayerChange.UseVisualStyleBackColor = true;
this.btnCustomVideoPlayerChange.Visible = false; this.btnCustomVideoPlayerChange.Visible = false;
// //
// panelSeparator // panelSeparator
// //
this.panelSeparator.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.panelSeparator.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left))); | System.Windows.Forms.AnchorStyles.Left)));
this.panelSeparator.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(200)))), ((int)(((byte)(200))))); this.panelSeparator.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(200)))), ((int)(((byte)(200)))));
this.panelSeparator.Location = new System.Drawing.Point(312, 0); this.panelSeparator.Location = new System.Drawing.Point(312, 0);
@@ -570,9 +584,9 @@
this.panelSeparator.Name = "panelSeparator"; this.panelSeparator.Name = "panelSeparator";
this.panelSeparator.Size = new System.Drawing.Size(1, 480); this.panelSeparator.Size = new System.Drawing.Size(1, 480);
this.panelSeparator.TabIndex = 2; this.panelSeparator.TabIndex = 2;
// //
// TabSettingsGeneral // TabSettingsGeneral
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelSeparator); this.Controls.Add(this.panelSeparator);
@@ -619,6 +633,7 @@
private System.Windows.Forms.Label labelSearchEngine; private System.Windows.Forms.Label labelSearchEngine;
private System.Windows.Forms.ComboBox comboBoxSearchEngine; private System.Windows.Forms.ComboBox comboBoxSearchEngine;
private System.Windows.Forms.CheckBox checkTouchAdjustment; private System.Windows.Forms.CheckBox checkTouchAdjustment;
private System.Windows.Forms.CheckBox checkAutomaticallyDetectColorProfile;
private System.Windows.Forms.FlowLayoutPanel flowPanelRight; private System.Windows.Forms.FlowLayoutPanel flowPanelRight;
private System.Windows.Forms.Panel panelSeparator; private System.Windows.Forms.Panel panelSeparator;
private System.Windows.Forms.Label labelLocales; private System.Windows.Forms.Label labelLocales;

View File

@@ -8,416 +8,426 @@ using TweetDuck.Utils;
using TweetLib.Core.Systems.Updates; using TweetLib.Core.Systems.Updates;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class TabSettingsGeneral : FormSettings.BaseTab{ sealed partial class TabSettingsGeneral : FormSettings.BaseTab {
private readonly Action reloadColumns; private readonly Action reloadColumns;
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
private int updateCheckEventId = -1; private int updateCheckEventId = -1;
private readonly int browserListIndexDefault; private readonly int browserListIndexDefault;
private readonly int browserListIndexCustom; private readonly int browserListIndexCustom;
private readonly int videoPlayerListIndexDefault; private readonly int videoPlayerListIndexDefault;
private readonly int videoPlayerListIndexCustom; private readonly int videoPlayerListIndexCustom;
private readonly int searchEngineIndexDefault; private readonly int searchEngineIndexDefault;
private readonly int searchEngineIndexCustom; private readonly int searchEngineIndexCustom;
public TabSettingsGeneral(Action reloadColumns, UpdateHandler updates){ public TabSettingsGeneral(Action reloadColumns, UpdateHandler updates) {
InitializeComponent(); InitializeComponent();
this.reloadColumns = reloadColumns; this.reloadColumns = reloadColumns;
this.updates = updates; this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished; this.updates.CheckFinished += updates_CheckFinished;
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished; Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
// user interface // user interface
toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead."); toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
toolTip.SetToolTip(checkFocusDmInput, "Places cursor into Direct Message input\r\nfield when opening a conversation."); toolTip.SetToolTip(checkFocusDmInput, "Places cursor into Direct Message input\r\nfield when opening a conversation.");
toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead."); toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action."); toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action.");
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL)."); toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
toolTip.SetToolTip(checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs."); toolTip.SetToolTip(checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs.");
toolTip.SetToolTip(labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots."); toolTip.SetToolTip(labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue)); toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
checkExpandLinks.Checked = Config.ExpandLinksOnHover; checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkFocusDmInput.Checked = Config.FocusDmInput; checkFocusDmInput.Checked = Config.FocusDmInput;
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn; checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen; checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
checkBestImageQuality.Checked = Config.BestImageQuality; checkBestImageQuality.Checked = Config.BestImageQuality;
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages; checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
trackBarZoom.SetValueSafe(Config.ZoomLevel); trackBarZoom.SetValueSafe(Config.ZoomLevel);
labelZoomValue.Text = trackBarZoom.Value + "%"; labelZoomValue.Text = trackBarZoom.Value + "%";
// updates // updates
toolTip.SetToolTip(checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again."); toolTip.SetToolTip(checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again.");
toolTip.SetToolTip(btnCheckUpdates, "Forces an update check, even for updates that had been dismissed."); toolTip.SetToolTip(btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
checkUpdateNotifications.Checked = Config.EnableUpdateCheck; checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
// browser settings // browser settings
toolTip.SetToolTip(checkSmoothScrolling, "Toggles smooth mouse wheel scrolling."); 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(checkTouchAdjustment, "Toggles Chromium touch screen adjustment.\r\nDisabled by default, because it is very imprecise with TweetDeck.");
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(checkAutomaticallyDetectColorProfile, "Automatically detects the color profile of your system.\r\nUses the sRGB profile if disabled.");
toolTip.SetToolTip(comboBoxCustomBrowser, "Sets the default browser for opening links."); 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(comboBoxCustomVideoPlayer, "Sets the default application for playing videos."); toolTip.SetToolTip(comboBoxCustomBrowser, "Sets the default browser for opening links.");
toolTip.SetToolTip(comboBoxSearchEngine, "Sets the default website for opening searches."); toolTip.SetToolTip(comboBoxCustomVideoPlayer, "Sets the default application for playing videos.");
toolTip.SetToolTip(comboBoxSearchEngine, "Sets the default website for opening searches.");
checkSmoothScrolling.Checked = Config.EnableSmoothScrolling;
checkTouchAdjustment.Checked = Config.EnableTouchAdjustment; checkSmoothScrolling.Checked = Config.EnableSmoothScrolling;
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration; checkTouchAdjustment.Checked = Config.EnableTouchAdjustment;
checkAutomaticallyDetectColorProfile.Checked = Config.EnableColorProfileDetection;
foreach(WindowsUtils.Browser browserInfo in WindowsUtils.FindInstalledBrowsers()){ checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
comboBoxCustomBrowser.Items.Add(browserInfo);
} foreach (WindowsUtils.Browser browserInfo in WindowsUtils.FindInstalledBrowsers()) {
comboBoxCustomBrowser.Items.Add(browserInfo);
browserListIndexDefault = comboBoxCustomBrowser.Items.Add("(default browser)"); }
browserListIndexCustom = comboBoxCustomBrowser.Items.Add("(custom program...)");
UpdateBrowserPathSelection(); browserListIndexDefault = comboBoxCustomBrowser.Items.Add("(default browser)");
browserListIndexCustom = comboBoxCustomBrowser.Items.Add("(custom program...)");
videoPlayerListIndexDefault = comboBoxCustomVideoPlayer.Items.Add("(default TweetDuck player)"); UpdateBrowserPathSelection();
videoPlayerListIndexCustom = comboBoxCustomVideoPlayer.Items.Add("(custom program...)");
UpdateVideoPlayerPathSelection(); videoPlayerListIndexDefault = comboBoxCustomVideoPlayer.Items.Add("(default TweetDuck player)");
videoPlayerListIndexCustom = comboBoxCustomVideoPlayer.Items.Add("(custom program...)");
comboBoxSearchEngine.Items.Add(new SearchEngine("DuckDuckGo", "https://duckduckgo.com/?q=")); UpdateVideoPlayerPathSelection();
comboBoxSearchEngine.Items.Add(new SearchEngine("Google", "https://www.google.com/search?q="));
comboBoxSearchEngine.Items.Add(new SearchEngine("Bing", "https://www.bing.com/search?q=")); comboBoxSearchEngine.Items.Add(new SearchEngine("DuckDuckGo", "https://duckduckgo.com/?q="));
comboBoxSearchEngine.Items.Add(new SearchEngine("Yahoo", "https://search.yahoo.com/search?p=")); comboBoxSearchEngine.Items.Add(new SearchEngine("Google", "https://www.google.com/search?q="));
searchEngineIndexDefault = comboBoxSearchEngine.Items.Add("(no engine set)"); comboBoxSearchEngine.Items.Add(new SearchEngine("Bing", "https://www.bing.com/search?q="));
searchEngineIndexCustom = comboBoxSearchEngine.Items.Add("(custom url...)"); comboBoxSearchEngine.Items.Add(new SearchEngine("Yahoo", "https://search.yahoo.com/search?p="));
UpdateSearchEngineSelection(); searchEngineIndexDefault = comboBoxSearchEngine.Items.Add("(no engine set)");
searchEngineIndexCustom = comboBoxSearchEngine.Items.Add("(custom url...)");
// locales UpdateSearchEngineSelection();
toolTip.SetToolTip(checkSpellCheck, "Underlines words that are spelled incorrectly."); // locales
toolTip.SetToolTip(comboBoxSpellCheckLanguage, "Language used for spell check.");
toolTip.SetToolTip(comboBoxTranslationTarget, "Language tweets are translated into."); toolTip.SetToolTip(checkSpellCheck, "Underlines words that are spelled incorrectly.");
toolTip.SetToolTip(comboBoxFirstDayOfWeek, "First day of week used in the date picker."); toolTip.SetToolTip(comboBoxSpellCheckLanguage, "Language used for spell check.");
toolTip.SetToolTip(comboBoxTranslationTarget, "Language tweets are translated into.");
checkSpellCheck.Checked = Config.EnableSpellCheck; toolTip.SetToolTip(comboBoxFirstDayOfWeek, "First day of week used in the date picker.");
try{ checkSpellCheck.Checked = Config.EnableSpellCheck;
foreach(LocaleUtils.Item item in LocaleUtils.SpellCheckLanguages){
comboBoxSpellCheckLanguage.Items.Add(item); try {
} foreach (LocaleUtils.Item item in LocaleUtils.SpellCheckLanguages) {
}catch{ comboBoxSpellCheckLanguage.Items.Add(item);
comboBoxSpellCheckLanguage.Items.Add(new LocaleUtils.Item("en-US")); }
} } catch {
comboBoxSpellCheckLanguage.Items.Add(new LocaleUtils.Item("en-US"));
comboBoxSpellCheckLanguage.SelectedItem = new LocaleUtils.Item(Config.SpellCheckLanguage); }
foreach(LocaleUtils.Item item in LocaleUtils.TweetDeckTranslationLocales){ comboBoxSpellCheckLanguage.SelectedItem = new LocaleUtils.Item(Config.SpellCheckLanguage);
comboBoxTranslationTarget.Items.Add(item);
} foreach (LocaleUtils.Item item in LocaleUtils.TweetDeckTranslationLocales) {
comboBoxTranslationTarget.Items.Add(item);
comboBoxTranslationTarget.SelectedItem = new LocaleUtils.Item(Config.TranslationTarget); }
var daysOfWeek = comboBoxFirstDayOfWeek.Items; comboBoxTranslationTarget.SelectedItem = new LocaleUtils.Item(Config.TranslationTarget);
daysOfWeek.Add("(based on system locale)");
daysOfWeek.Add(new DayOfWeekItem("Monday", DayOfWeek.Monday)); var daysOfWeek = comboBoxFirstDayOfWeek.Items;
daysOfWeek.Add(new DayOfWeekItem("Tuesday", DayOfWeek.Tuesday)); daysOfWeek.Add("(based on system locale)");
daysOfWeek.Add(new DayOfWeekItem("Wednesday", DayOfWeek.Wednesday)); daysOfWeek.Add(new DayOfWeekItem("Monday", DayOfWeek.Monday));
daysOfWeek.Add(new DayOfWeekItem("Thursday", DayOfWeek.Thursday)); daysOfWeek.Add(new DayOfWeekItem("Tuesday", DayOfWeek.Tuesday));
daysOfWeek.Add(new DayOfWeekItem("Friday", DayOfWeek.Friday)); daysOfWeek.Add(new DayOfWeekItem("Wednesday", DayOfWeek.Wednesday));
daysOfWeek.Add(new DayOfWeekItem("Saturday", DayOfWeek.Saturday)); daysOfWeek.Add(new DayOfWeekItem("Thursday", DayOfWeek.Thursday));
daysOfWeek.Add(new DayOfWeekItem("Sunday", DayOfWeek.Sunday)); daysOfWeek.Add(new DayOfWeekItem("Friday", DayOfWeek.Friday));
comboBoxFirstDayOfWeek.SelectedItem = daysOfWeek.OfType<DayOfWeekItem>().FirstOrDefault(dow => dow.Id == Config.CalendarFirstDay) ?? daysOfWeek[0]; daysOfWeek.Add(new DayOfWeekItem("Saturday", DayOfWeek.Saturday));
} daysOfWeek.Add(new DayOfWeekItem("Sunday", DayOfWeek.Sunday));
comboBoxFirstDayOfWeek.SelectedItem = daysOfWeek.OfType<DayOfWeekItem>().FirstOrDefault(dow => dow.Id == Config.CalendarFirstDay) ?? daysOfWeek[0];
public override void OnReady(){ }
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkFocusDmInput.CheckedChanged += checkFocusDmInput_CheckedChanged; public override void OnReady() {
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged; checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged; checkFocusDmInput.CheckedChanged += checkFocusDmInput_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged; checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged; checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged; checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged; trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
btnCheckUpdates.Click += btnCheckUpdates_Click;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged; btnCheckUpdates.Click += btnCheckUpdates_Click;
checkTouchAdjustment.CheckedChanged += checkTouchAdjustment_CheckedChanged;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged; checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged;
comboBoxCustomBrowser.SelectedIndexChanged += comboBoxCustomBrowser_SelectedIndexChanged; checkTouchAdjustment.CheckedChanged += checkTouchAdjustment_CheckedChanged;
btnCustomBrowserChange.Click += btnCustomBrowserChange_Click; checkAutomaticallyDetectColorProfile.CheckedChanged += checkAutomaticallyDetectColorProfile_CheckedChanged;
comboBoxCustomVideoPlayer.SelectedIndexChanged += comboBoxCustomVideoPlayer_SelectedIndexChanged; checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
btnCustomVideoPlayerChange.Click += btnCustomVideoPlayerChange_Click; comboBoxCustomBrowser.SelectedIndexChanged += comboBoxCustomBrowser_SelectedIndexChanged;
comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged; btnCustomBrowserChange.Click += btnCustomBrowserChange_Click;
comboBoxCustomVideoPlayer.SelectedIndexChanged += comboBoxCustomVideoPlayer_SelectedIndexChanged;
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged; btnCustomVideoPlayerChange.Click += btnCustomVideoPlayerChange_Click;
comboBoxSpellCheckLanguage.SelectedValueChanged += comboBoxSpellCheckLanguage_SelectedValueChanged; comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged;
comboBoxTranslationTarget.SelectedValueChanged += comboBoxTranslationTarget_SelectedValueChanged;
comboBoxFirstDayOfWeek.SelectedValueChanged += comboBoxFirstDayOfWeek_SelectedValueChanged; checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
} comboBoxSpellCheckLanguage.SelectedValueChanged += comboBoxSpellCheckLanguage_SelectedValueChanged;
comboBoxTranslationTarget.SelectedValueChanged += comboBoxTranslationTarget_SelectedValueChanged;
public override void OnClosing(){ comboBoxFirstDayOfWeek.SelectedValueChanged += comboBoxFirstDayOfWeek_SelectedValueChanged;
Config.ZoomLevel = trackBarZoom.Value; }
}
public override void OnClosing() {
#region User Interface Config.ZoomLevel = trackBarZoom.Value;
}
private void checkExpandLinks_CheckedChanged(object sender, EventArgs e){
Config.ExpandLinksOnHover = checkExpandLinks.Checked; #region User Interface
}
private void checkExpandLinks_CheckedChanged(object sender, EventArgs e) {
private void checkFocusDmInput_CheckedChanged(object sender, EventArgs e){ Config.ExpandLinksOnHover = checkExpandLinks.Checked;
Config.FocusDmInput = checkFocusDmInput.Checked; }
}
private void checkFocusDmInput_CheckedChanged(object sender, EventArgs e) {
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){ Config.FocusDmInput = checkFocusDmInput.Checked;
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked; }
}
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e) {
private void checkKeepLikeFollowDialogsOpen_CheckedChanged(object sender, EventArgs e){ Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
Config.KeepLikeFollowDialogsOpen = checkKeepLikeFollowDialogsOpen.Checked; }
}
private void checkKeepLikeFollowDialogsOpen_CheckedChanged(object sender, EventArgs e) {
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){ Config.KeepLikeFollowDialogsOpen = checkKeepLikeFollowDialogsOpen.Checked;
Config.BestImageQuality = checkBestImageQuality.Checked; }
}
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e) {
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){ Config.BestImageQuality = checkBestImageQuality.Checked;
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked; }
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
} private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e) {
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
private void trackBarZoom_ValueChanged(object sender, EventArgs e){ BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
if (trackBarZoom.AlignValueToTick()){ }
zoomUpdateTimer.Stop();
zoomUpdateTimer.Start(); private void trackBarZoom_ValueChanged(object sender, EventArgs e) {
labelZoomValue.Text = trackBarZoom.Value + "%"; if (trackBarZoom.AlignValueToTick()) {
} zoomUpdateTimer.Stop();
} zoomUpdateTimer.Start();
labelZoomValue.Text = trackBarZoom.Value + "%";
private void zoomUpdateTimer_Tick(object sender, EventArgs e){ }
Config.ZoomLevel = trackBarZoom.Value; }
zoomUpdateTimer.Stop();
} private void zoomUpdateTimer_Tick(object sender, EventArgs e) {
Config.ZoomLevel = trackBarZoom.Value;
#endregion zoomUpdateTimer.Stop();
#region Updates }
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){ #endregion
Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
} #region Updates
private void btnCheckUpdates_Click(object sender, EventArgs e){ private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e) {
Config.DismissedUpdate = null; Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
}
btnCheckUpdates.Enabled = false;
updateCheckEventId = updates.Check(true); private void btnCheckUpdates_Click(object sender, EventArgs e) {
} Config.DismissedUpdate = null;
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){ btnCheckUpdates.Enabled = false;
if (e.EventId == updateCheckEventId){ updateCheckEventId = updates.Check(true);
btnCheckUpdates.Enabled = true; }
e.Result.Handle(update => { private void updates_CheckFinished(object sender, UpdateCheckEventArgs e) {
if (update.VersionTag == Program.VersionTag){ if (e.EventId == updateCheckEventId) {
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK); btnCheckUpdates.Enabled = true;
}
}, ex => { e.Result.Handle(update => {
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex); if (update.VersionTag == Program.VersionTag) {
}); FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
} }
} }, ex => {
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
#endregion });
#region Browser Settings }
}
private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e){
Config.EnableSmoothScrolling = checkSmoothScrolling.Checked; #endregion
}
#region Browser Settings
private void checkTouchAdjustment_CheckedChanged(object sender, EventArgs e){
Config.EnableTouchAdjustment = checkTouchAdjustment.Checked; private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e) {
} Config.EnableSmoothScrolling = checkSmoothScrolling.Checked;
}
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked; private void checkTouchAdjustment_CheckedChanged(object sender, EventArgs e) {
} Config.EnableTouchAdjustment = checkTouchAdjustment.Checked;
}
private void UpdateBrowserChangeButton(){
btnCustomBrowserChange.Visible = comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom; private void checkAutomaticallyDetectColorProfile_CheckedChanged(object sender, EventArgs e) {
} Config.EnableColorProfileDetection = checkAutomaticallyDetectColorProfile.Checked;
}
private void UpdateBrowserPathSelection(){
if (string.IsNullOrEmpty(Config.BrowserPath) || !File.Exists(Config.BrowserPath)){ private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e) {
comboBoxCustomBrowser.SelectedIndex = browserListIndexDefault; SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
} }
else{
WindowsUtils.Browser browserInfo = comboBoxCustomBrowser.Items.OfType<WindowsUtils.Browser>().FirstOrDefault(browser => browser.Path == Config.BrowserPath); private void UpdateBrowserChangeButton() {
btnCustomBrowserChange.Visible = comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom;
if (browserInfo == null || Config.BrowserPathArgs != null){ }
comboBoxCustomBrowser.SelectedIndex = browserListIndexCustom;
} private void UpdateBrowserPathSelection() {
else{ if (string.IsNullOrEmpty(Config.BrowserPath) || !File.Exists(Config.BrowserPath)) {
comboBoxCustomBrowser.SelectedItem = browserInfo; comboBoxCustomBrowser.SelectedIndex = browserListIndexDefault;
} }
} else {
WindowsUtils.Browser browserInfo = comboBoxCustomBrowser.Items.OfType<WindowsUtils.Browser>().FirstOrDefault(browser => browser.Path == Config.BrowserPath);
UpdateBrowserChangeButton();
} if (browserInfo == null || Config.BrowserPathArgs != null) {
comboBoxCustomBrowser.SelectedIndex = browserListIndexCustom;
private void comboBoxCustomBrowser_SelectedIndexChanged(object sender, EventArgs e){ }
if (comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom){ else {
btnCustomBrowserChange_Click(sender, e); comboBoxCustomBrowser.SelectedItem = browserInfo;
} }
else{ }
Config.BrowserPath = (comboBoxCustomBrowser.SelectedItem as WindowsUtils.Browser)?.Path; // default browser item is a string and casts to null
Config.BrowserPathArgs = null; UpdateBrowserChangeButton();
UpdateBrowserChangeButton(); }
}
} private void comboBoxCustomBrowser_SelectedIndexChanged(object sender, EventArgs e) {
if (comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom) {
private void btnCustomBrowserChange_Click(object sender, EventArgs e){ btnCustomBrowserChange_Click(sender, e);
using(DialogSettingsExternalProgram dialog = new DialogSettingsExternalProgram("External Browser", "Open Links With..."){ }
Path = Config.BrowserPath, else {
Args = Config.BrowserPathArgs Config.BrowserPath = (comboBoxCustomBrowser.SelectedItem as WindowsUtils.Browser)?.Path; // default browser item is a string and casts to null
}){ Config.BrowserPathArgs = null;
if (dialog.ShowDialog() == DialogResult.OK){ UpdateBrowserChangeButton();
Config.BrowserPath = dialog.Path; }
Config.BrowserPathArgs = dialog.Args; }
}
} private void btnCustomBrowserChange_Click(object sender, EventArgs e) {
using (DialogSettingsExternalProgram dialog = new DialogSettingsExternalProgram("External Browser", "Open Links With...") {
comboBoxCustomBrowser.SelectedIndexChanged -= comboBoxCustomBrowser_SelectedIndexChanged; Path = Config.BrowserPath,
UpdateBrowserPathSelection(); Args = Config.BrowserPathArgs
comboBoxCustomBrowser.SelectedIndexChanged += comboBoxCustomBrowser_SelectedIndexChanged; }) {
} if (dialog.ShowDialog() == DialogResult.OK) {
Config.BrowserPath = dialog.Path;
private void UpdateVideoPlayerChangeButton(){ Config.BrowserPathArgs = dialog.Args;
btnCustomVideoPlayerChange.Visible = comboBoxCustomVideoPlayer.SelectedIndex == videoPlayerListIndexCustom; }
} }
private void UpdateVideoPlayerPathSelection(){ comboBoxCustomBrowser.SelectedIndexChanged -= comboBoxCustomBrowser_SelectedIndexChanged;
if (string.IsNullOrEmpty(Config.VideoPlayerPath) || !File.Exists(Config.VideoPlayerPath)){ UpdateBrowserPathSelection();
comboBoxCustomVideoPlayer.SelectedIndex = videoPlayerListIndexDefault; comboBoxCustomBrowser.SelectedIndexChanged += comboBoxCustomBrowser_SelectedIndexChanged;
} }
else{
comboBoxCustomVideoPlayer.SelectedIndex = videoPlayerListIndexCustom; private void UpdateVideoPlayerChangeButton() {
} btnCustomVideoPlayerChange.Visible = comboBoxCustomVideoPlayer.SelectedIndex == videoPlayerListIndexCustom;
}
UpdateVideoPlayerChangeButton();
} private void UpdateVideoPlayerPathSelection() {
if (string.IsNullOrEmpty(Config.VideoPlayerPath) || !File.Exists(Config.VideoPlayerPath)) {
private void comboBoxCustomVideoPlayer_SelectedIndexChanged(object sender, EventArgs e){ comboBoxCustomVideoPlayer.SelectedIndex = videoPlayerListIndexDefault;
if (comboBoxCustomVideoPlayer.SelectedIndex == videoPlayerListIndexCustom){ }
btnCustomVideoPlayerChange_Click(sender, e); else {
} comboBoxCustomVideoPlayer.SelectedIndex = videoPlayerListIndexCustom;
else{ }
Config.VideoPlayerPath = null;
Config.VideoPlayerPathArgs = null; UpdateVideoPlayerChangeButton();
UpdateVideoPlayerChangeButton(); }
}
} private void comboBoxCustomVideoPlayer_SelectedIndexChanged(object sender, EventArgs e) {
if (comboBoxCustomVideoPlayer.SelectedIndex == videoPlayerListIndexCustom) {
private void btnCustomVideoPlayerChange_Click(object sender, EventArgs e){ btnCustomVideoPlayerChange_Click(sender, e);
using(DialogSettingsExternalProgram dialog = new DialogSettingsExternalProgram("External Video Player", "Play Videos With..."){ }
Path = Config.VideoPlayerPath, else {
Args = Config.VideoPlayerPathArgs Config.VideoPlayerPath = null;
}){ Config.VideoPlayerPathArgs = null;
if (dialog.ShowDialog() == DialogResult.OK){ UpdateVideoPlayerChangeButton();
Config.VideoPlayerPath = dialog.Path; }
Config.VideoPlayerPathArgs = dialog.Args; }
}
} private void btnCustomVideoPlayerChange_Click(object sender, EventArgs e) {
using (DialogSettingsExternalProgram dialog = new DialogSettingsExternalProgram("External Video Player", "Play Videos With...") {
comboBoxCustomVideoPlayer.SelectedIndexChanged -= comboBoxCustomVideoPlayer_SelectedIndexChanged; Path = Config.VideoPlayerPath,
UpdateVideoPlayerPathSelection(); Args = Config.VideoPlayerPathArgs
comboBoxCustomVideoPlayer.SelectedIndexChanged += comboBoxCustomVideoPlayer_SelectedIndexChanged; }) {
} if (dialog.ShowDialog() == DialogResult.OK) {
Config.VideoPlayerPath = dialog.Path;
private void comboBoxSearchEngine_SelectedIndexChanged(object sender, EventArgs e){ Config.VideoPlayerPathArgs = dialog.Args;
if (comboBoxSearchEngine.SelectedIndex == searchEngineIndexCustom){ }
using(DialogSettingsSearchEngine dialog = new DialogSettingsSearchEngine()){ }
if (dialog.ShowDialog() == DialogResult.OK){
Config.SearchEngineUrl = dialog.Url.Trim(); comboBoxCustomVideoPlayer.SelectedIndexChanged -= comboBoxCustomVideoPlayer_SelectedIndexChanged;
} UpdateVideoPlayerPathSelection();
} comboBoxCustomVideoPlayer.SelectedIndexChanged += comboBoxCustomVideoPlayer_SelectedIndexChanged;
}
comboBoxSearchEngine.SelectedIndexChanged -= comboBoxSearchEngine_SelectedIndexChanged;
UpdateSearchEngineSelection(); private void comboBoxSearchEngine_SelectedIndexChanged(object sender, EventArgs e) {
comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged; if (comboBoxSearchEngine.SelectedIndex == searchEngineIndexCustom) {
} using (DialogSettingsSearchEngine dialog = new DialogSettingsSearchEngine()) {
else{ if (dialog.ShowDialog() == DialogResult.OK) {
Config.SearchEngineUrl = (comboBoxSearchEngine.SelectedItem as SearchEngine)?.Url; // default search engine item is a string and casts to null Config.SearchEngineUrl = dialog.Url.Trim();
} }
} }
private void UpdateSearchEngineSelection(){ comboBoxSearchEngine.SelectedIndexChanged -= comboBoxSearchEngine_SelectedIndexChanged;
if (string.IsNullOrEmpty(Config.SearchEngineUrl)){ UpdateSearchEngineSelection();
comboBoxSearchEngine.SelectedIndex = searchEngineIndexDefault; comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged;
} }
else{ else {
SearchEngine engineInfo = comboBoxSearchEngine.Items.OfType<SearchEngine>().FirstOrDefault(engine => engine.Url == Config.SearchEngineUrl); Config.SearchEngineUrl = (comboBoxSearchEngine.SelectedItem as SearchEngine)?.Url; // default search engine item is a string and casts to null
}
if (engineInfo == null){ }
comboBoxSearchEngine.SelectedIndex = searchEngineIndexCustom;
} private void UpdateSearchEngineSelection() {
else{ if (string.IsNullOrEmpty(Config.SearchEngineUrl)) {
comboBoxSearchEngine.SelectedItem = engineInfo; comboBoxSearchEngine.SelectedIndex = searchEngineIndexDefault;
} }
} else {
} SearchEngine engineInfo = comboBoxSearchEngine.Items.OfType<SearchEngine>().FirstOrDefault(engine => engine.Url == Config.SearchEngineUrl);
private sealed class SearchEngine{ if (engineInfo == null) {
private string Name { get; } comboBoxSearchEngine.SelectedIndex = searchEngineIndexCustom;
public string Url { get; } }
else {
public SearchEngine(string name, string url){ comboBoxSearchEngine.SelectedItem = engineInfo;
Name = name; }
Url = url; }
} }
public override int GetHashCode() => Name.GetHashCode(); private sealed class SearchEngine {
public override bool Equals(object obj) => obj is SearchEngine other && Name == other.Name; private string Name { get; }
public override string ToString() => Name; public string Url { get; }
}
public SearchEngine(string name, string url) {
#endregion Name = name;
#region Locales Url = url;
}
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
Config.EnableSpellCheck = checkSpellCheck.Checked; public override int GetHashCode() => Name.GetHashCode();
BrowserProcessHandler.UpdatePrefs(); public override bool Equals(object obj) => obj is SearchEngine other && Name == other.Name;
} public override string ToString() => Name;
}
private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e){
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code ?? "en-US"; #endregion
}
#region Locales
private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e){
Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code ?? "en"; private void checkSpellCheck_CheckedChanged(object sender, EventArgs e) {
} Config.EnableSpellCheck = checkSpellCheck.Checked;
BrowserProcessHandler.UpdatePrefs();
private void comboBoxFirstDayOfWeek_SelectedValueChanged(object sender, EventArgs e){ }
Config.CalendarFirstDay = (comboBoxFirstDayOfWeek.SelectedItem as DayOfWeekItem)?.Id ?? -1;
} private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e) {
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code ?? "en-US";
private sealed class DayOfWeekItem{ }
private string Name { get; }
public int Id { get; } private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e) {
Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code ?? "en";
public DayOfWeekItem(string name, DayOfWeek dow){ }
Name = name;
Id = LocaleUtils.GetJQueryDayOfWeek(dow); private void comboBoxFirstDayOfWeek_SelectedValueChanged(object sender, EventArgs e) {
} Config.CalendarFirstDay = (comboBoxFirstDayOfWeek.SelectedItem as DayOfWeekItem)?.Id ?? -1;
}
public override int GetHashCode() => Name.GetHashCode();
public override bool Equals(object obj) => obj is DayOfWeekItem other && Name == other.Name; private sealed class DayOfWeekItem {
public override string ToString() => Name; private string Name { get; }
} public int Id { get; }
#endregion public DayOfWeekItem(string name, DayOfWeek dow) {
} Name = name;
Id = LocaleUtils.GetJQueryDayOfWeek(dow);
}
public override int GetHashCode() => Name.GetHashCode();
public override bool Equals(object obj) => obj is DayOfWeekItem other && Name == other.Name;
public override string ToString() => Name;
}
#endregion
}
} }

View File

@@ -4,302 +4,319 @@ using TweetDuck.Browser.Notification.Example;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class TabSettingsNotifications : FormSettings.BaseTab{ sealed partial class TabSettingsNotifications : FormSettings.BaseTab {
private static readonly int[] IdlePauseSeconds = { 0, 30, 60, 120, 300 }; private static readonly int[] IdlePauseSeconds = { 0, 30, 60, 120, 300 };
private readonly FormNotificationExample notification; private readonly FormNotificationExample notification;
public TabSettingsNotifications(FormNotificationExample notification){ public TabSettingsNotifications(FormNotificationExample notification) {
InitializeComponent(); InitializeComponent();
this.notification = notification; this.notification = notification;
this.notification.Ready += (sender, args) => {
this.InvokeAsyncSafe(() => {
this.notification.ShowExampleNotification(true);
this.notification.Move += notification_Move;
this.notification.ResizeEnd += notification_ResizeEnd;
});
};
this.notification.Show(); this.notification.Ready += (sender, args) => {
this.InvokeAsyncSafe(() => {
Disposed += (sender, args) => this.notification.Dispose(); this.notification.ShowExampleNotification(true);
this.notification.Move += notification_Move;
// general this.notification.ResizeEnd += notification_ResizeEnd;
});
};
toolTip.SetToolTip(checkColumnName, "Shows column name each notification originated\r\nfrom in the notification window title."); this.notification.Show();
toolTip.SetToolTip(checkMediaPreviews, "Shows image and video thumbnails in the notification window.");
toolTip.SetToolTip(checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
toolTip.SetToolTip(checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be delayed until the cursor moves away to prevent accidental clicks.");
toolTip.SetToolTip(comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
checkColumnName.Checked = Config.DisplayNotificationColumn;
checkMediaPreviews.Checked = Config.NotificationMediaPreviews;
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick;
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
comboBoxIdlePause.Items.Add("Disabled");
comboBoxIdlePause.Items.Add("30 seconds");
comboBoxIdlePause.Items.Add("1 minute");
comboBoxIdlePause.Items.Add("2 minutes");
comboBoxIdlePause.Items.Add("5 minutes");
comboBoxIdlePause.SelectedIndex = Math.Max(0, Array.FindIndex(IdlePauseSeconds, val => val == Config.NotificationIdlePauseSeconds));
trackBarOpacity.SetValueSafe(Config.NotificationWindowOpacity); Disposed += (sender, args) => this.notification.Dispose();
labelOpacityValue.Text = Config.NotificationWindowOpacity + "%";
// timer // general
toolTip.SetToolTip(checkTimerCountDown, "The notification timer counts down instead of up."); toolTip.SetToolTip(checkColumnName, "Shows column name each notification originated\r\nfrom in the notification window title.");
toolTip.SetToolTip(labelDurationValue, "Milliseconds per character."); toolTip.SetToolTip(checkMediaPreviews, "Shows image and video thumbnails in the notification window.");
toolTip.SetToolTip(trackBarDuration, toolTip.GetToolTip(labelDurationValue)); toolTip.SetToolTip(checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
toolTip.SetToolTip(checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be delayed until the cursor moves away to prevent accidental clicks.");
checkNotificationTimer.Checked = Config.DisplayNotificationTimer; toolTip.SetToolTip(comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
trackBarDuration.SetValueSafe(Config.NotificationDurationValue); checkColumnName.Checked = Config.DisplayNotificationColumn;
labelDurationValue.Text = Config.NotificationDurationValue + " ms/c"; checkMediaPreviews.Checked = Config.NotificationMediaPreviews;
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick;
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
// location comboBoxIdlePause.Items.Add("Disabled");
comboBoxIdlePause.Items.Add("30 seconds");
comboBoxIdlePause.Items.Add("1 minute");
comboBoxIdlePause.Items.Add("2 minutes");
comboBoxIdlePause.Items.Add("5 minutes");
comboBoxIdlePause.SelectedIndex = Math.Max(0, Array.FindIndex(IdlePauseSeconds, val => val == Config.NotificationIdlePauseSeconds));
toolTip.SetToolTip(radioLocCustom, "Drag the example notification window to the desired location."); trackBarOpacity.SetValueSafe(Config.NotificationWindowOpacity);
labelOpacityValue.Text = Config.NotificationWindowOpacity + "%";
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; // timer
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
foreach(Screen screen in Screen.AllScreens){ toolTip.SetToolTip(checkTimerCountDown, "The notification timer counts down instead of up.");
comboBoxDisplay.Items.Add($"{screen.DeviceName.TrimStart('\\', '.')} ({screen.Bounds.Width}x{screen.Bounds.Height})"); toolTip.SetToolTip(labelDurationValue, "Milliseconds per character.");
} toolTip.SetToolTip(trackBarDuration, toolTip.GetToolTip(labelDurationValue));
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count - 1, Config.NotificationDisplay); checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance); trackBarDuration.SetValueSafe(Config.NotificationDurationValue);
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px"; labelDurationValue.Text = Config.NotificationDurationValue + " ms/c";
// size // location
toolTip.SetToolTip(radioSizeAuto, "Notification size is based on the font size and browser zoom level."); toolTip.SetToolTip(radioLocCustom, "Drag the example notification window to the desired location.");
toolTip.SetToolTip(radioSizeCustom, "Resize the example notification window to the desired size.");
switch(Config.NotificationSize){ switch (Config.NotificationPosition) {
case DesktopNotification.Size.Auto: radioSizeAuto.Checked = true; break; case DesktopNotification.Position.TopLeft:
case DesktopNotification.Size.Custom: radioSizeCustom.Checked = true; break; 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;
}
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed); comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%"; comboBoxDisplay.Items.Add("(Same as TweetDuck)");
}
public override void OnReady(){ foreach (Screen screen in Screen.AllScreens) {
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged; comboBoxDisplay.Items.Add($"{screen.DeviceName.TrimStart('\\', '.')} ({screen.Bounds.Width}x{screen.Bounds.Height})");
checkMediaPreviews.CheckedChanged += checkMediaPreviews_CheckedChanged; }
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged;
comboBoxIdlePause.SelectedValueChanged += comboBoxIdlePause_SelectedValueChanged;
trackBarOpacity.ValueChanged += trackBarOpacity_ValueChanged;
checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged; comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count - 1, Config.NotificationDisplay);
checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged;
trackBarDuration.ValueChanged += trackBarDuration_ValueChanged;
btnDurationShort.Click += btnDurationShort_Click;
btnDurationMedium.Click += btnDurationMedium_Click;
btnDurationLong.Click += btnDurationLong_Click;
radioLocTL.CheckedChanged += radioLoc_CheckedChanged; trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
radioLocTR.CheckedChanged += radioLoc_CheckedChanged; labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
radioLocBL.CheckedChanged += radioLoc_CheckedChanged;
radioLocBR.CheckedChanged += radioLoc_CheckedChanged;
radioLocCustom.Click += radioLocCustom_Click;
comboBoxDisplay.SelectedValueChanged += comboBoxDisplay_SelectedValueChanged;
trackBarEdgeDistance.ValueChanged += trackBarEdgeDistance_ValueChanged;
radioSizeAuto.CheckedChanged += radioSize_CheckedChanged; // size
radioSizeCustom.Click += radioSizeCustom_Click;
trackBarScrollSpeed.ValueChanged += trackBarScrollSpeed_ValueChanged;
}
private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){ toolTip.SetToolTip(radioSizeAuto, "Notification size is based on the font size and browser zoom level.");
if (Parent == null){ toolTip.SetToolTip(radioSizeCustom, "Resize the example notification window to the desired size.");
notification.HideNotification();
}
else{
notification.ShowExampleNotification(true);
}
}
private void notification_Move(object sender, EventArgs e){ switch (Config.NotificationSize) {
if (radioLocCustom.Checked && notification.Location != ControlExtensions.InvisibleLocation){ case DesktopNotification.Size.Auto:
Config.CustomNotificationPosition = notification.Location; radioSizeAuto.Checked = true;
} break;
} case DesktopNotification.Size.Custom:
radioSizeCustom.Checked = true;
break;
}
private void notification_ResizeEnd(object sender, EventArgs e){ trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
if (radioSizeCustom.Checked){ labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
Config.CustomNotificationSize = notification.BrowserSize; }
notification.ShowExampleNotification(false);
}
}
#region General public override void OnReady() {
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged;
checkMediaPreviews.CheckedChanged += checkMediaPreviews_CheckedChanged;
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged;
comboBoxIdlePause.SelectedValueChanged += comboBoxIdlePause_SelectedValueChanged;
trackBarOpacity.ValueChanged += trackBarOpacity_ValueChanged;
private void checkColumnName_CheckedChanged(object sender, EventArgs e){ checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged;
Config.DisplayNotificationColumn = checkColumnName.Checked; checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged;
notification.ShowExampleNotification(false); trackBarDuration.ValueChanged += trackBarDuration_ValueChanged;
} btnDurationShort.Click += btnDurationShort_Click;
btnDurationMedium.Click += btnDurationMedium_Click;
btnDurationLong.Click += btnDurationLong_Click;
private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e){ radioLocTL.CheckedChanged += radioLoc_CheckedChanged;
Config.NotificationMediaPreviews = checkMediaPreviews.Checked; radioLocTR.CheckedChanged += radioLoc_CheckedChanged;
} radioLocBL.CheckedChanged += radioLoc_CheckedChanged;
radioLocBR.CheckedChanged += radioLoc_CheckedChanged;
radioLocCustom.Click += radioLocCustom_Click;
comboBoxDisplay.SelectedValueChanged += comboBoxDisplay_SelectedValueChanged;
trackBarEdgeDistance.ValueChanged += trackBarEdgeDistance_ValueChanged;
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){ radioSizeAuto.CheckedChanged += radioSize_CheckedChanged;
Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked; radioSizeCustom.Click += radioSizeCustom_Click;
} trackBarScrollSpeed.ValueChanged += trackBarScrollSpeed_ValueChanged;
}
private void checkNonIntrusive_CheckedChanged(object sender, EventArgs e){ private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e) {
Config.NotificationNonIntrusiveMode = checkNonIntrusive.Checked; if (Parent == null) {
} notification.HideNotification();
}
else {
notification.ShowExampleNotification(true);
}
}
private void comboBoxIdlePause_SelectedValueChanged(object sender, EventArgs e){ private void notification_Move(object sender, EventArgs e) {
Config.NotificationIdlePauseSeconds = IdlePauseSeconds[comboBoxIdlePause.SelectedIndex]; if (radioLocCustom.Checked && notification.Location != ControlExtensions.InvisibleLocation) {
} Config.CustomNotificationPosition = notification.Location;
}
}
private void trackBarOpacity_ValueChanged(object sender, EventArgs e){ private void notification_ResizeEnd(object sender, EventArgs e) {
if (trackBarOpacity.AlignValueToTick()){ if (radioSizeCustom.Checked) {
Config.NotificationWindowOpacity = trackBarOpacity.Value; Config.CustomNotificationSize = notification.BrowserSize;
labelOpacityValue.Text = Config.NotificationWindowOpacity + "%"; notification.ShowExampleNotification(false);
} }
} }
#endregion #region General
#region Timer
private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){ private void checkColumnName_CheckedChanged(object sender, EventArgs e) {
Config.DisplayNotificationTimer = checkNotificationTimer.Checked; Config.DisplayNotificationColumn = checkColumnName.Checked;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked; notification.ShowExampleNotification(false);
notification.ShowExampleNotification(true); }
}
private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e){ private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e) {
Config.NotificationTimerCountDown = checkTimerCountDown.Checked; Config.NotificationMediaPreviews = checkMediaPreviews.Checked;
notification.ShowExampleNotification(true); }
}
private void trackBarDuration_ValueChanged(object sender, EventArgs e){ private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e) {
durationUpdateTimer.Stop(); Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked;
durationUpdateTimer.Start(); }
Config.NotificationDurationValue = trackBarDuration.Value; private void checkNonIntrusive_CheckedChanged(object sender, EventArgs e) {
labelDurationValue.Text = Config.NotificationDurationValue + " ms/c"; Config.NotificationNonIntrusiveMode = checkNonIntrusive.Checked;
} }
private void btnDurationShort_Click(object sender, EventArgs e){ private void comboBoxIdlePause_SelectedValueChanged(object sender, EventArgs e) {
trackBarDuration.Value = 15; Config.NotificationIdlePauseSeconds = IdlePauseSeconds[comboBoxIdlePause.SelectedIndex];
} }
private void btnDurationMedium_Click(object sender, EventArgs e){ private void trackBarOpacity_ValueChanged(object sender, EventArgs e) {
trackBarDuration.Value = 25; if (trackBarOpacity.AlignValueToTick()) {
} Config.NotificationWindowOpacity = trackBarOpacity.Value;
labelOpacityValue.Text = Config.NotificationWindowOpacity + "%";
}
}
private void btnDurationLong_Click(object sender, EventArgs e){ #endregion
trackBarDuration.Value = 35;
}
private void durationUpdateTimer_Tick(object sender, EventArgs e){ #region Timer
notification.ShowExampleNotification(true);
durationUpdateTimer.Stop();
}
#endregion private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e) {
#region Location Config.DisplayNotificationTimer = checkNotificationTimer.Checked;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
notification.ShowExampleNotification(true);
}
private void radioLoc_CheckedChanged(object sender, EventArgs e){ private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e) {
if (radioLocTL.Checked){ Config.NotificationTimerCountDown = checkTimerCountDown.Checked;
Config.NotificationPosition = DesktopNotification.Position.TopLeft; notification.ShowExampleNotification(true);
} }
else if (radioLocTR.Checked){
Config.NotificationPosition = DesktopNotification.Position.TopRight;
}
else if (radioLocBL.Checked){
Config.NotificationPosition = DesktopNotification.Position.BottomLeft;
}
else if (radioLocBR.Checked){
Config.NotificationPosition = DesktopNotification.Position.BottomRight;
}
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true; private void trackBarDuration_ValueChanged(object sender, EventArgs e) {
notification.ShowExampleNotification(false); durationUpdateTimer.Stop();
} durationUpdateTimer.Start();
private void radioLocCustom_Click(object sender, EventArgs e){ Config.NotificationDurationValue = trackBarDuration.Value;
if (!Config.IsCustomNotificationPositionSet){ labelDurationValue.Text = Config.NotificationDurationValue + " ms/c";
Config.CustomNotificationPosition = notification.Location; }
}
Config.NotificationPosition = DesktopNotification.Position.Custom; private void btnDurationShort_Click(object sender, EventArgs e) {
trackBarDuration.Value = 15;
}
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false; private void btnDurationMedium_Click(object sender, EventArgs e) {
notification.ShowExampleNotification(false); trackBarDuration.Value = 25;
}
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)){ private void btnDurationLong_Click(object sender, EventArgs e) {
Config.NotificationPosition = DesktopNotification.Position.TopRight; trackBarDuration.Value = 35;
notification.MoveToVisibleLocation(); }
Config.CustomNotificationPosition = notification.Location; private void durationUpdateTimer_Tick(object sender, EventArgs e) {
notification.ShowExampleNotification(true);
durationUpdateTimer.Stop();
}
Config.NotificationPosition = DesktopNotification.Position.Custom; #endregion
notification.MoveToVisibleLocation();
}
}
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){ #region Location
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
notification.ShowExampleNotification(false);
}
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){ private void radioLoc_CheckedChanged(object sender, EventArgs e) {
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px"; if (radioLocTL.Checked) {
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value; Config.NotificationPosition = DesktopNotification.Position.TopLeft;
notification.ShowExampleNotification(false); }
} else if (radioLocTR.Checked) {
Config.NotificationPosition = DesktopNotification.Position.TopRight;
}
else if (radioLocBL.Checked) {
Config.NotificationPosition = DesktopNotification.Position.BottomLeft;
}
else if (radioLocBR.Checked) {
Config.NotificationPosition = DesktopNotification.Position.BottomRight;
}
#endregion comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true;
#region Size notification.ShowExampleNotification(false);
}
private void radioSize_CheckedChanged(object sender, EventArgs e){ private void radioLocCustom_Click(object sender, EventArgs e) {
if (radioSizeAuto.Checked){ if (!Config.IsCustomNotificationPositionSet) {
Config.NotificationSize = DesktopNotification.Size.Auto; Config.CustomNotificationPosition = notification.Location;
} }
notification.ShowExampleNotification(false);
}
private void radioSizeCustom_Click(object sender, EventArgs e){
if (!Config.IsCustomNotificationSizeSet){
Config.CustomNotificationSize = notification.BrowserSize;
}
Config.NotificationSize = DesktopNotification.Size.Custom; Config.NotificationPosition = DesktopNotification.Position.Custom;
notification.ShowExampleNotification(false);
}
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){ comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
if (trackBarScrollSpeed.AlignValueToTick()){ notification.ShowExampleNotification(false);
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
}
}
#endregion 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();
Config.CustomNotificationPosition = notification.Location;
Config.NotificationPosition = DesktopNotification.Position.Custom;
notification.MoveToVisibleLocation();
}
}
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e) {
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
notification.ShowExampleNotification(false);
}
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) {
Config.NotificationSize = DesktopNotification.Size.Auto;
}
notification.ShowExampleNotification(false);
}
private void radioSizeCustom_Click(object sender, EventArgs e) {
if (!Config.IsCustomNotificationSizeSet) {
Config.CustomNotificationSize = notification.BrowserSize;
}
Config.NotificationSize = DesktopNotification.Size.Custom;
notification.ShowExampleNotification(false);
}
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e) {
if (trackBarScrollSpeed.AlignValueToTick()) {
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
}
}
#endregion
}
} }

View File

@@ -6,91 +6,99 @@ using TweetDuck.Browser.Notification;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class TabSettingsSounds : FormSettings.BaseTab{ sealed partial class TabSettingsSounds : FormSettings.BaseTab {
private readonly Action playSoundNotification; private readonly Action playSoundNotification;
public TabSettingsSounds(Action playSoundNotification){ public TabSettingsSounds(Action playSoundNotification) {
InitializeComponent(); InitializeComponent();
this.playSoundNotification = playSoundNotification; this.playSoundNotification = playSoundNotification;
// sound notification
toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used."); // sound notification
trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
labelVolumeValue.Text = trackBarVolume.Value + "%";
tbCustomSound.Text = Config.NotificationSoundPath; toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
NativeMethods.SendMessage(tbCustomSound.Handle, NativeMethods.EM_SETCUEBANNER, 0, "(default TweetDeck sound)");
}
public override void OnReady(){ trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
tbCustomSound.TextChanged += tbCustomSound_TextChanged; labelVolumeValue.Text = trackBarVolume.Value + "%";
btnPlaySound.Click += btnPlaySound_Click;
btnBrowseSound.Click += btnBrowseSound_Click;
btnResetSound.Click += btnResetSound_Click;
}
public override void OnClosing(){ tbCustomSound.Text = Config.NotificationSoundPath;
Config.NotificationSoundPath = tbCustomSound.Text; tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
Config.NotificationSoundVolume = trackBarVolume.Value; NativeMethods.SendMessage(tbCustomSound.Handle, NativeMethods.EM_SETCUEBANNER, 0, "(default TweetDeck sound)");
} }
#region Sound Notification public override void OnReady() {
tbCustomSound.TextChanged += tbCustomSound_TextChanged;
btnPlaySound.Click += btnPlaySound_Click;
btnBrowseSound.Click += btnBrowseSound_Click;
btnResetSound.Click += btnResetSound_Click;
}
private bool RefreshCanPlay(){ public override void OnClosing() {
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text); Config.NotificationSoundPath = tbCustomSound.Text;
bool canPlay = isEmpty || File.Exists(tbCustomSound.Text); Config.NotificationSoundVolume = trackBarVolume.Value;
}
tbCustomSound.ForeColor = canPlay ? SystemColors.WindowText : Color.Red; #region Sound Notification
btnPlaySound.Enabled = canPlay;
btnResetSound.Enabled = !isEmpty;
return canPlay;
}
private void tbCustomSound_TextChanged(object sender, EventArgs e){ private bool RefreshCanPlay() {
RefreshCanPlay(); bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
} bool canPlay = isEmpty || File.Exists(tbCustomSound.Text);
private void btnPlaySound_Click(object sender, EventArgs e){ tbCustomSound.ForeColor = canPlay ? SystemColors.WindowText : Color.Red;
if (RefreshCanPlay()){ btnPlaySound.Enabled = canPlay;
Config.NotificationSoundPath = tbCustomSound.Text; btnResetSound.Enabled = !isEmpty;
Config.NotificationSoundVolume = trackBarVolume.Value; return canPlay;
playSoundNotification(); }
}
}
private void btnBrowseSound_Click(object sender, EventArgs e){ private void tbCustomSound_TextChanged(object sender, EventArgs e) {
using OpenFileDialog dialog = new OpenFileDialog{ RefreshCanPlay();
AutoUpgradeEnabled = true, }
DereferenceLinks = true,
Title = "Custom Notification Sound",
Filter = $"Sound file ({SoundNotification.SupportedFormats})|{SoundNotification.SupportedFormats}|All files (*.*)|*.*"
};
if (dialog.ShowDialog() == DialogResult.OK){ private void btnPlaySound_Click(object sender, EventArgs e) {
tbCustomSound.Text = dialog.FileName; if (RefreshCanPlay()) {
} Config.NotificationSoundPath = tbCustomSound.Text;
} Config.NotificationSoundVolume = trackBarVolume.Value;
playSoundNotification();
}
}
private void btnResetSound_Click(object sender, EventArgs e){ private void btnBrowseSound_Click(object sender, EventArgs e) {
tbCustomSound.Text = string.Empty; using OpenFileDialog dialog = new OpenFileDialog {
} AutoUpgradeEnabled = true,
DereferenceLinks = true,
Title = "Custom Notification Sound",
Filter = $"Sound file ({SoundNotification.SupportedFormats})|{SoundNotification.SupportedFormats}|All files (*.*)|*.*"
};
private void trackBarVolume_ValueChanged(object sender, EventArgs e){ if (dialog.ShowDialog() == DialogResult.OK) {
volumeUpdateTimer.Stop(); try {
volumeUpdateTimer.Start(); 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)) {
labelVolumeValue.Text = trackBarVolume.Value + "%"; return;
} }
} catch {
// ignore
}
private void volumeUpdateTimer_Tick(object sender, EventArgs e){ tbCustomSound.Text = dialog.FileName;
Config.NotificationSoundVolume = trackBarVolume.Value; }
volumeUpdateTimer.Stop(); }
}
#endregion private void btnResetSound_Click(object sender, EventArgs e) {
} tbCustomSound.Text = string.Empty;
}
private void trackBarVolume_ValueChanged(object sender, EventArgs e) {
volumeUpdateTimer.Stop();
volumeUpdateTimer.Start();
labelVolumeValue.Text = trackBarVolume.Value + "%";
}
private void volumeUpdateTimer_Tick(object sender, EventArgs e) {
Config.NotificationSoundVolume = trackBarVolume.Value;
volumeUpdateTimer.Stop();
}
#endregion
}
} }

View File

@@ -1,43 +1,43 @@
using System; using System;
using TweetDuck.Browser; using TweetDuck.Browser;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class TabSettingsTray : FormSettings.BaseTab{ sealed partial class TabSettingsTray : FormSettings.BaseTab {
public TabSettingsTray(){ public TabSettingsTray() {
InitializeComponent(); InitializeComponent();
// system tray // system tray
toolTip.SetToolTip(comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
toolTip.SetToolTip(checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
comboBoxTrayType.Items.Add("Disabled"); toolTip.SetToolTip(comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
comboBoxTrayType.Items.Add("Display Icon Only"); toolTip.SetToolTip(checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
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);
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon(); comboBoxTrayType.Items.Add("Disabled");
checkTrayHighlight.Checked = Config.EnableTrayHighlight; comboBoxTrayType.Items.Add("Display Icon Only");
} 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);
public override void OnReady(){ checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged; checkTrayHighlight.Checked = Config.EnableTrayHighlight;
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged; }
}
#region System Tray public override void OnReady() {
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){ checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex; }
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
}
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){ #region System Tray
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
}
#endregion 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) {
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
}
#endregion
}
} }

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}));
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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(){}
}
}
}

View File

@@ -4,73 +4,73 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace TweetDuck.Management{ namespace TweetDuck.Management {
static class BrowserCache{ static class BrowserCache {
public static string CacheFolder => Path.Combine(Program.StoragePath, "Cache"); public static string CacheFolder => Path.Combine(Program.StoragePath, "Cache");
private static bool ClearOnExit;
private static Timer AutoClearTimer;
private static long CalculateCacheSize(){ private static bool clearOnExit;
return new DirectoryInfo(CacheFolder).EnumerateFiles().Select(file => { private static Timer autoClearTimer;
try{
return file.Length;
}catch{
return 0L;
}
}).Sum();
}
public static void GetCacheSize(Action<Task<long>> callbackBytes){ private static long CalculateCacheSize() {
Task<long> task = new Task<long>(CalculateCacheSize); return new DirectoryInfo(CacheFolder).EnumerateFiles().Select(file => {
task.ContinueWith(callbackBytes); try {
task.Start(); return file.Length;
} } catch {
return 0L;
public static void RefreshTimer(){ }
bool shouldRun = Program.Config.System.ClearCacheAutomatically && !ClearOnExit; }).Sum();
}
if (!shouldRun && AutoClearTimer != null){ public static void GetCacheSize(Action<Task<long>> callbackBytes) {
AutoClearTimer.Dispose(); Task<long> task = new Task<long>(CalculateCacheSize);
AutoClearTimer = null; task.ContinueWith(callbackBytes);
} task.Start();
else if (shouldRun && AutoClearTimer == null){ }
AutoClearTimer = new Timer(state => {
if (AutoClearTimer != null){
try{
if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold * 1024L * 1024L){
SetClearOnExit();
}
}catch(Exception){
// TODO should probably log errors and report them at some point
}
}
}, null, TimeSpan.FromSeconds(30), TimeSpan.FromHours(4));
}
}
public static void SetClearOnExit(){ public static void RefreshTimer() {
ClearOnExit = true; bool shouldRun = Program.Config.System.ClearCacheAutomatically && !clearOnExit;
RefreshTimer();
}
public static void TryClearNow(){ if (!shouldRun && autoClearTimer != null) {
try{ autoClearTimer.Dispose();
Directory.Delete(CacheFolder, true); autoClearTimer = null;
}catch{ }
// welp, too bad else if (shouldRun && autoClearTimer == null) {
} autoClearTimer = new Timer(state => {
} if (autoClearTimer != null) {
try {
if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold * 1024L * 1024L) {
SetClearOnExit();
}
} catch (Exception) {
// TODO should probably log errors and report them at some point
}
}
}, null, TimeSpan.FromSeconds(30), TimeSpan.FromHours(4));
}
}
public static void Exit(){ public static void SetClearOnExit() {
if (AutoClearTimer != null){ clearOnExit = true;
AutoClearTimer.Dispose(); RefreshTimer();
AutoClearTimer = null; }
}
if (ClearOnExit){ public static void TryClearNow() {
TryClearNow(); try {
} Directory.Delete(CacheFolder, true);
} } catch {
} // welp, too bad
}
}
public static void Exit() {
if (autoClearTimer != null) {
autoClearTimer.Dispose();
autoClearTimer = null;
}
if (clearOnExit) {
TryClearNow();
}
}
}
} }

View File

@@ -4,52 +4,52 @@ using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Management{ namespace TweetDuck.Management {
static class ClipboardManager{ static class ClipboardManager {
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false); 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); private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
public static void SetText(string text, TextDataFormat format){ public static void SetText(string text, TextDataFormat format) {
if (string.IsNullOrEmpty(text)){ if (string.IsNullOrEmpty(text)) {
return; return;
} }
DataObject obj = new DataObject(); DataObject obj = new DataObject();
obj.SetText(text, format); obj.SetText(text, format);
SetClipboardData(obj); SetClipboardData(obj);
} }
public static void SetImage(Image image){ public static void SetImage(Image image) {
DataObject obj = new DataObject(); DataObject obj = new DataObject();
obj.SetImage(image); obj.SetImage(image);
SetClipboardData(obj); SetClipboardData(obj);
} }
private static void SetClipboardData(DataObject obj){ private static void SetClipboardData(DataObject obj) {
try{ try {
Clipboard.SetDataObject(obj); 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); 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(){ public static void StripHtmlStyles() {
if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)){ if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)) {
return; return;
} }
string originalText = Clipboard.GetText(TextDataFormat.UnicodeText); string originalText = Clipboard.GetText(TextDataFormat.UnicodeText);
string originalHtml = Clipboard.GetText(TextDataFormat.Html); string originalHtml = Clipboard.GetText(TextDataFormat.Html);
string updatedHtml = RegexStripHtmlStyles.Value.Replace(originalHtml, string.Empty); string updatedHtml = RegexStripHtmlStyles.Value.Replace(originalHtml, string.Empty);
int removed = originalHtml.Length - updatedHtml.Length; int removed = originalHtml.Length - updatedHtml.Length;
updatedHtml = RegexOffsetClipboardHtml.Value.Replace(updatedHtml, match => (int.Parse(match.Value) - removed).ToString().PadLeft(match.Value.Length, '0')); updatedHtml = RegexOffsetClipboardHtml.Value.Replace(updatedHtml, match => (int.Parse(match.Value) - removed).ToString().PadLeft(match.Value.Length, '0'));
DataObject obj = new DataObject(); DataObject obj = new DataObject();
obj.SetText(originalText, TextDataFormat.UnicodeText); obj.SetText(originalText, TextDataFormat.UnicodeText);
obj.SetText(updatedHtml, TextDataFormat.Html); obj.SetText(updatedHtml, TextDataFormat.Html);
SetClipboardData(obj); SetClipboardData(obj);
} }
} }
} }

View File

@@ -1,32 +1,32 @@
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Management{ namespace TweetDuck.Management {
static class FormManager{ static class FormManager {
private static FormCollection OpenForms => System.Windows.Forms.Application.OpenForms; 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(); 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>(); T form = TryFind<T>();
if (form != null){ if (form != null) {
form.BringToFront(); form.BringToFront();
return true; return true;
} }
else{ else {
return false; return false;
} }
} }
public static void CloseAllDialogs(){ public static void CloseAllDialogs() {
foreach(IAppDialog dialog in OpenForms.OfType<IAppDialog>().Reverse()){ foreach (IAppDialog dialog in OpenForms.OfType<IAppDialog>().Reverse()) {
((Form)dialog).Close(); ((Form) dialog).Close();
} }
} }
public interface IAppDialog{} public interface IAppDialog {}
} }
} }

129
Management/LockManager.cs Normal file
View File

@@ -0,0 +1,129 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using TweetDuck.Dialogs;
using TweetDuck.Utils;
using TweetLib.Core.Systems.Startup;
namespace TweetDuck.Management {
sealed class LockManager {
private const int WaitRetryDelay = 250;
private const int RestoreFailTimeout = 2000;
private const int CloseNaturallyTimeout = 10000;
private const int CloseKillTimeout = 5000;
private readonly LockFile lockFile;
public LockManager(string path) {
this.lockFile = new LockFile(path);
}
public bool Lock(bool wasRestarted) {
return wasRestarted ? LaunchAfterRestart() : LaunchNormally();
}
public bool Unlock() {
return lockFile.Unlock();
}
// Locking
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)) {
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
return false;
}
info.Dispose();
lockResult = lockFile.Lock();
}
else {
return false;
}
}
if (lockResult is LockResult.Fail fail) {
ShowGenericException(fail);
return false;
}
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;
}
return true;
}
private bool LaunchAfterRestart() {
LockResult lockResult = lockFile.LockWait(10000, WaitRetryDelay);
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)) {
return false;
}
lockResult = lockFile.LockWait(5000, WaitRetryDelay);
}
return true;
}
// Helpers
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);
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)) {
return true;
}
}
return false;
}
private static bool CloseProcess(Process process) {
try {
if (process.CloseMainWindow()) {
// ReSharper disable once AccessToDisposedClosure
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseNaturallyTimeout, WaitRetryDelay);
}
if (!process.HasExited) {
process.Kill();
// ReSharper disable once AccessToDisposedClosure
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseKillTimeout, WaitRetryDelay);
}
if (process.HasExited) {
process.Dispose();
return true;
}
else {
return false;
}
} catch (Exception ex) when (ex is InvalidOperationException || ex is Win32Exception) {
bool hasExited = CheckProcessExited(process);
process.Dispose();
return hasExited;
}
}
private static bool CheckProcessExited(Process process) {
process.Refresh();
return process.HasExited;
}
}
}

View File

@@ -7,208 +7,236 @@ using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums; using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Management{ namespace TweetDuck.Management {
sealed class ProfileManager{ sealed class ProfileManager {
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies"); private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp"); private static readonly string LocalPrefsPath = Path.Combine(Program.StoragePath, "LocalPrefs.json");
[Flags] private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
public enum Items{ private static readonly string TempLocalPrefsPath = Path.Combine(Program.StoragePath, "LocalPrefsTmp.json");
None = 0,
UserConfig = 1,
SystemConfig = 2,
Session = 4,
PluginData = 8,
All = UserConfig|SystemConfig|Session|PluginData
}
private readonly string file;
private readonly PluginManager plugins;
public ProfileManager(string file, PluginManager plugins){ private static readonly int SessionFileCount = 2;
this.file = file;
this.plugins = plugins;
}
public bool Export(Items items){ [Flags]
try{ public enum Items {
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ None = 0,
if (items.HasFlag(Items.UserConfig)){ UserConfig = 1,
stream.WriteFile("config", Program.UserConfigFilePath); SystemConfig = 2,
} Session = 4,
PluginData = 8,
All = UserConfig | SystemConfig | Session | PluginData
}
if (items.HasFlag(Items.SystemConfig)){ private readonly string file;
stream.WriteFile("system", Program.SystemConfigFilePath); private readonly PluginManager plugins;
}
if (items.HasFlag(Items.PluginData)){ public ProfileManager(string file, PluginManager plugins) {
stream.WriteFile("plugin.config", Program.PluginConfigFilePath); this.file = file;
this.plugins = plugins;
}
foreach(Plugin plugin in plugins.Plugins){ public bool Export(Items items) {
foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){ try {
try{ using CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None));
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.UserConfig)) {
stream.WriteFile("cookies", CookiesPath); stream.WriteFile("config", Program.UserConfigFilePath);
} }
stream.Flush(); if (items.HasFlag(Items.SystemConfig)) {
} stream.WriteFile("system", Program.SystemConfigFilePath);
}
return true; if (items.HasFlag(Items.PluginData)) {
}catch(Exception e){ stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
return false;
}
}
public Items FindImportItems(){ foreach (Plugin plugin in plugins.Plugins) {
Items items = Items.None; 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);
}
}
}
}
try{ if (items.HasFlag(Items.Session)) {
using CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None)); stream.WriteFile("cookies", CookiesPath);
string key; stream.WriteFile("localprefs", LocalPrefsPath);
}
while((key = stream.SkipFile()) != null){ stream.Flush();
switch(key){ return true;
case "config": } catch (Exception e) {
items |= Items.UserConfig; Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
break; return false;
}
}
case "system": public Items FindImportItems() {
items |= Items.SystemConfig; Items items = Items.None;
break;
case "plugin.config": try {
case "plugin.data": using CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None));
items |= Items.PluginData; string key;
break;
case "cookies": while ((key = stream.SkipFile()) != null) {
items |= Items.Session; switch (key) {
break; case "config":
} items |= Items.UserConfig;
} break;
}catch(Exception){
items = Items.None;
}
return items; case "system":
} items |= Items.SystemConfig;
break;
public bool Import(Items items){ case "plugin.config":
try{ case "plugin.data":
HashSet<string> missingPlugins = new HashSet<string>(); items |= Items.PluginData;
break;
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){ case "cookies":
CombinedFileStream.Entry entry; case "localprefs":
items |= Items.Session;
break;
}
}
} catch (Exception) {
items = Items.None;
}
while((entry = stream.ReadFile()) != null){ return items;
switch(entry.KeyName){ }
case "config":
if (items.HasFlag(Items.UserConfig)){
entry.WriteToFile(Program.UserConfigFilePath);
}
break; public bool Import(Items items) {
try {
var missingPlugins = new HashSet<string>();
var sessionFiles = new HashSet<string>();
case "system": using (CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))) {
if (items.HasFlag(Items.SystemConfig)){ CombinedFileStream.Entry entry;
entry.WriteToFile(Program.SystemConfigFilePath);
}
break; while ((entry = stream.ReadFile()) != null) {
switch (entry.KeyName) {
case "config":
if (items.HasFlag(Items.UserConfig)) {
entry.WriteToFile(Program.UserConfigFilePath);
}
case "plugin.config": break;
if (items.HasFlag(Items.PluginData)){
entry.WriteToFile(Program.PluginConfigFilePath);
}
break; case "system":
if (items.HasFlag(Items.SystemConfig)) {
entry.WriteToFile(Program.SystemConfigFilePath);
}
case "plugin.data": break;
if (items.HasFlag(Items.PluginData)){
string[] value = entry.KeyValue;
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true); case "plugin.config":
if (items.HasFlag(Items.PluginData)) {
entry.WriteToFile(Program.PluginConfigFilePath);
}
if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))){ break;
missingPlugins.Add(value[0]);
}
}
break; case "plugin.data":
if (items.HasFlag(Items.PluginData)) {
string[] value = entry.KeyValue;
case "cookies": entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
if (items.HasFlag(Items.Session)){
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
}
break; if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))) {
} missingPlugins.Add(value[0]);
} }
} }
if (missingPlugins.Count > 0){ break;
FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n" + string.Join("\n", missingPlugins), FormMessage.OK);
}
return true; case "cookies":
}catch(Exception e){ if (items.HasFlag(Items.Session)) {
Program.Reporter.HandleException("Profile Import", "An exception happened while importing TweetDuck profile.", true, e); entry.WriteToFile(TempCookiesPath);
return false; sessionFiles.Add(entry.KeyName);
} }
}
public static void ImportCookies(){ break;
if (File.Exists(TempCookiesPath)){
try{
if (File.Exists(CookiesPath)){
File.Delete(CookiesPath);
}
File.Move(TempCookiesPath, CookiesPath); case "localprefs":
}catch(Exception e){ if (items.HasFlag(Items.Session)) {
Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e); entry.WriteToFile(TempLocalPrefsPath);
} sessionFiles.Add(entry.KeyName);
} }
}
public static void DeleteCookies(){ break;
try{ }
if (File.Exists(CookiesPath)){ }
File.Delete(CookiesPath); }
}
}catch(Exception e){
Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
}
}
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){ if (items.HasFlag(Items.Session) && sessionFiles.Count != SessionFileCount) {
if (Directory.Exists(root)){ FormMessage.Error("Profile Import Error", "Cannot import login session from an older version of TweetDuck.", FormMessage.OK);
int rootLength = root.Length; File.Delete(TempCookiesPath);
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength)); File.Delete(TempLocalPrefsPath);
} return false;
else{ }
return Enumerable.Empty<PathInfo>();
}
}
private sealed class PathInfo{ if (missingPlugins.Count > 0) {
public string Full { get; } FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n" + string.Join("\n", missingPlugins), FormMessage.OK);
public string Relative { get; } }
public PathInfo(string fullPath, int rootLength){ return true;
this.Full = fullPath; } catch (Exception e) {
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character Program.Reporter.HandleException("Profile Import", "An exception happened while importing TweetDuck profile.", true, e);
} return false;
} }
} }
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);
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)) {
File.Delete(CookiesPath);
}
} catch (Exception e) {
Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
}
}
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root) {
if (Directory.Exists(root)) {
int rootLength = root.Length;
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength));
}
else {
return Enumerable.Empty<PathInfo>();
}
}
private sealed class PathInfo {
public string Full { get; }
public string Relative { get; }
public PathInfo(string fullPath, int rootLength) {
this.Full = fullPath;
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
}
}
}
} }

View File

@@ -7,210 +7,210 @@ using TweetDuck.Configuration;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Communication; using TweetLib.Communication.Pipe;
namespace TweetDuck.Management{ namespace TweetDuck.Management {
sealed class VideoPlayer : IDisposable{ sealed class VideoPlayer : IDisposable {
private static UserConfig Config => Program.Config.User; private static UserConfig Config => Program.Config.User;
public bool Running => currentInstance != null && currentInstance.Running; public bool Running => currentInstance != null && currentInstance.Running;
public event EventHandler ProcessExited; public event EventHandler ProcessExited;
private readonly FormBrowser owner; private readonly FormBrowser owner;
private Instance currentInstance; private Instance currentInstance;
private bool isClosing; private bool isClosing;
public VideoPlayer(FormBrowser owner){ public VideoPlayer(FormBrowser owner) {
this.owner = owner; this.owner = owner;
this.owner.FormClosing += owner_FormClosing; this.owner.FormClosing += owner_FormClosing;
} }
public void Launch(string videoUrl, string tweetUrl, string username){ public void Launch(string videoUrl, string tweetUrl, string username) {
if (Running){ if (Running) {
Destroy(); Destroy();
isClosing = false; isClosing = false;
} }
try{
DuplexPipe.Server pipe = DuplexPipe.CreateServer();
pipe.DataIn += pipe_DataIn;
Process process; try {
DuplexPipe.Server pipe = DuplexPipe.CreateServer();
pipe.DataIn += pipe_DataIn;
if ((process = Process.Start(new ProcessStartInfo{ ProcessStartInfo startInfo = new ProcessStartInfo {
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"), 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, UseShellExecute = false,
RedirectStandardOutput = true RedirectStandardOutput = true
})) != null){ };
currentInstance = new Instance(process, pipe, videoUrl, tweetUrl, username);
process.EnableRaisingEvents = true; Process process;
process.Exited += process_Exited; if ((process = Process.Start(startInfo)) != null) {
currentInstance = new Instance(process, pipe, videoUrl, tweetUrl, username);
process.BeginOutputReadLine();
process.OutputDataReceived += process_OutputDataReceived;
pipe.DisposeToken(); process.EnableRaisingEvents = true;
} process.Exited += process_Exited;
else{
pipe.DataIn -= pipe_DataIn;
pipe.Dispose();
}
}catch(Exception e){
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
}
}
public void SendKeyEvent(Keys key){ process.BeginOutputReadLine();
currentInstance?.Pipe.Write("key", ((int)key).ToString()); process.OutputDataReceived += process_OutputDataReceived;
}
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){ pipe.DisposeToken();
owner.InvokeSafe(() => { }
switch(e.Key){ else {
case "vol": pipe.DataIn -= pipe_DataIn;
if (int.TryParse(e.Data, out int volume) && volume != Config.VideoPlayerVolume){ pipe.Dispose();
Config.VideoPlayerVolume = volume; }
Config.Save(); } catch (Exception e) {
} Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
}
}
break; public void SendKeyEvent(Keys key) {
currentInstance?.Pipe.Write("key", ((int) key).ToString());
}
case "download": private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e) {
if (currentInstance != null){ owner.InvokeSafe(() => {
owner.AnalyticsFile.DownloadedVideos.Trigger(); switch (e.Key) {
TwitterUtils.DownloadVideo(currentInstance.VideoUrl, currentInstance.Username); case "vol":
} if (int.TryParse(e.Data, out int volume) && volume != Config.VideoPlayerVolume) {
Config.VideoPlayerVolume = volume;
Config.Save();
}
break; break;
case "rip": case "download":
currentInstance?.Dispose(); if (currentInstance != null) {
currentInstance = null; TwitterUtils.DownloadVideo(currentInstance.VideoUrl, currentInstance.Username);
}
isClosing = false; break;
TriggerProcessExitEventUnsafe();
break;
}
});
}
public void Close(){ case "rip":
if (currentInstance != null){ currentInstance?.Dispose();
if (isClosing){ currentInstance = null;
Destroy();
isClosing = false;
}
else{
isClosing = true;
currentInstance.Process.Exited -= process_Exited;
currentInstance.Pipe.Write("die");
}
}
}
public void Dispose(){ isClosing = false;
ProcessExited = null; TriggerProcessExitEventUnsafe();
break;
}
});
}
isClosing = true; public void Close() {
Destroy(); if (currentInstance != null) {
} if (isClosing) {
Destroy();
isClosing = false;
}
else {
isClosing = true;
currentInstance.Process.Exited -= process_Exited;
currentInstance.Pipe.Write("die");
}
}
}
private void Destroy(){ public void Dispose() {
if (currentInstance != null){ ProcessExited = null;
currentInstance.KillAndDispose();
currentInstance = null;
TriggerProcessExitEventUnsafe(); isClosing = true;
} Destroy();
} }
private void owner_FormClosing(object sender, FormClosingEventArgs e){ private void Destroy() {
if (currentInstance != null){ if (currentInstance != null) {
currentInstance.Process.Exited -= process_Exited; currentInstance.KillAndDispose();
} currentInstance = null;
}
private void process_OutputDataReceived(object sender, DataReceivedEventArgs e){ TriggerProcessExitEventUnsafe();
if (!string.IsNullOrEmpty(e.Data)){ }
Program.Reporter.LogVerbose("[VideoPlayer] " + e.Data); }
}
}
private void process_Exited(object sender, EventArgs e){ private void owner_FormClosing(object sender, FormClosingEventArgs e) {
if (currentInstance == null){ if (currentInstance != null) {
return; currentInstance.Process.Exited -= process_Exited;
} }
}
int exitCode = currentInstance.Process.ExitCode; private void process_OutputDataReceived(object sender, DataReceivedEventArgs e) {
string tweetUrl = currentInstance.TweetUrl; if (!string.IsNullOrEmpty(e.Data)) {
Program.Reporter.LogVerbose("[VideoPlayer] " + e.Data);
}
}
currentInstance.Dispose(); private void process_Exited(object sender, EventArgs e) {
currentInstance = null; if (currentInstance == null) {
return;
}
switch(exitCode){ int exitCode = currentInstance.Process.ExitCode;
case 3: // CODE_LAUNCH_FAIL string tweetUrl = currentInstance.TweetUrl;
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; currentInstance.Dispose();
currentInstance = null;
case 4: // CODE_MEDIA_ERROR switch (exitCode) {
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)){ case 3: // CODE_LAUNCH_FAIL
BrowserUtils.OpenExternalBrowser(tweetUrl); 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; break;
}
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
}
private void TriggerProcessExitEventUnsafe(){ case 4: // CODE_MEDIA_ERROR
ProcessExited?.Invoke(this, EventArgs.Empty); 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);
}
private sealed class Instance : IDisposable{ break;
public bool Running{ }
get{
Process.Refresh();
return !Process.HasExited;
}
}
public Process Process { get; } owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
public DuplexPipe.Server Pipe { get; } }
public string VideoUrl { get; } private void TriggerProcessExitEventUnsafe() {
public string TweetUrl { get; } ProcessExited?.Invoke(this, EventArgs.Empty);
public string Username { get; } }
public Instance(Process process, DuplexPipe.Server pipe, string videoUrl, string tweetUrl, string username){ private sealed class Instance : IDisposable {
this.Process = process; public bool Running {
this.Pipe = pipe; get {
this.VideoUrl = videoUrl; Process.Refresh();
this.TweetUrl = tweetUrl; return !Process.HasExited;
this.Username = username; }
} }
public void KillAndDispose(){ public Process Process { get; }
try{ public DuplexPipe.Server Pipe { get; }
Process.Kill();
}catch{
// kill me instead then
}
Dispose(); public string VideoUrl { get; }
} public string TweetUrl { get; }
public string Username { get; }
public void Dispose(){ public Instance(Process process, DuplexPipe.Server pipe, string videoUrl, string tweetUrl, string username) {
Process.Dispose(); this.Process = process;
Pipe.Dispose(); this.Pipe = pipe;
} this.VideoUrl = videoUrl;
} this.TweetUrl = tweetUrl;
} this.Username = username;
}
public void KillAndDispose() {
try {
Process.Kill();
} catch {
// kill me instead then
}
Dispose();
}
public void Dispose() {
Process.Dispose();
Pipe.Dispose();
}
}
}
} }

View File

@@ -6,124 +6,124 @@ using TweetDuck.Utils;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums; using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins {
sealed partial class PluginControl : UserControl{ sealed partial class PluginControl : UserControl {
private readonly PluginManager pluginManager; private readonly PluginManager pluginManager;
private readonly Plugin plugin; private readonly Plugin plugin;
private readonly bool isConfigurable; private readonly bool isConfigurable;
private int nextHeight;
public PluginControl(){ private int nextHeight;
InitializeComponent();
}
public PluginControl(PluginManager pluginManager, Plugin plugin) : this(){ public PluginControl() {
this.pluginManager = pluginManager; InitializeComponent();
this.plugin = plugin; }
this.isConfigurable = pluginManager.IsPluginConfigurable(plugin); public PluginControl(PluginManager pluginManager, Plugin plugin) : this() {
this.pluginManager = pluginManager;
this.plugin = plugin;
float dpiScale = this.GetDPIScale(); this.isConfigurable = pluginManager.IsPluginConfigurable(plugin);
if (dpiScale > 1F){ float dpiScale = this.GetDPIScale();
Size = MaximumSize = new Size(MaximumSize.Width, MaximumSize.Height + 3);
}
this.labelName.Text = plugin.Name;
this.labelDescription.Text = plugin.CanRun ? plugin.Description : $"This plugin requires TweetDuck {plugin.RequiredVersion} or newer.";
this.labelAuthor.Text = string.IsNullOrWhiteSpace(plugin.Author) ? string.Empty : $"by {plugin.Author}";
this.labelWebsite.Text = plugin.Website;
this.labelVersion.Text = plugin.Version;
this.labelType.LineHeight = BrowserUtils.Scale(11, dpiScale);
UpdatePluginState(); if (dpiScale > 1F) {
Size = MaximumSize = new Size(MaximumSize.Width, MaximumSize.Height + 3);
}
if (labelDescription.Text.Length == 0){ this.labelName.Text = plugin.Name;
labelDescription.Visible = false; this.labelDescription.Text = plugin.CanRun ? plugin.Description : $"This plugin requires TweetDuck {plugin.RequiredVersion} or newer.";
} this.labelAuthor.Text = string.IsNullOrWhiteSpace(plugin.Author) ? string.Empty : $"by {plugin.Author}";
this.labelWebsite.Text = plugin.Website;
panelDescription_Resize(panelDescription, EventArgs.Empty); this.labelVersion.Text = plugin.Version;
}
private void timerLayout_Tick(object sender, EventArgs e){ this.labelType.LineHeight = BrowserUtils.Scale(11, dpiScale);
timerLayout.Stop();
Height = nextHeight;
ResumeLayout();
}
private void panelDescription_Resize(object sender, EventArgs e){ UpdatePluginState();
SuspendLayout();
int maxWidth = panelDescription.Width - (panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue);
Font font = labelDescription.Font; if (labelDescription.Text.Length == 0) {
int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height / (font.Height - 1); labelDescription.Visible = false;
}
int requiredLines = Math.Max(descriptionLines, 1 + (string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1) + (isConfigurable ? 1 : 0));
nextHeight = requiredLines switch{
1 => MaximumSize.Height - 2 * (font.Height - 1),
2 => MaximumSize.Height - 1 * (font.Height - 1),
_ => MaximumSize.Height
};
if (nextHeight != Height){ panelDescription_Resize(panelDescription, EventArgs.Empty);
timerLayout.Start(); }
}
else{
ResumeLayout();
}
}
private void labelWebsite_Click(object sender, EventArgs e){ private void timerLayout_Tick(object sender, EventArgs e) {
if (labelWebsite.Text.Length > 0){ timerLayout.Stop();
BrowserUtils.OpenExternalBrowser(labelWebsite.Text); Height = nextHeight;
} ResumeLayout();
} }
private void btnConfigure_Click(object sender, EventArgs e){ private void panelDescription_Resize(object sender, EventArgs e) {
pluginManager.ConfigurePlugin(plugin); SuspendLayout();
ParentForm?.Close();
}
private void btnToggleState_Click(object sender, EventArgs e){ int maxWidth = panelDescription.Width - (panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
pluginManager.Config.SetEnabled(plugin, !pluginManager.Config.IsEnabled(plugin)); labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue);
UpdatePluginState();
}
private void UpdatePluginState(){ Font font = labelDescription.Font;
bool isEnabled = pluginManager.Config.IsEnabled(plugin) && plugin.CanRun; int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height / (font.Height - 1);
Color textColor = isEnabled ? Color.Black : Color.FromArgb(90, 90, 90);
labelVersion.ForeColor = textColor;
labelAuthor.ForeColor = textColor;
labelWebsite.ForeColor = isEnabled ? Color.Blue : Color.FromArgb(90, 90, 249);
if (plugin.Group == PluginGroup.Official){ int requiredLines = Math.Max(descriptionLines, 1 + (string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1) + (isConfigurable ? 1 : 0));
labelType.Text = "CORE";
labelType.BackColor = isEnabled ? Color.FromArgb(154, 195, 217) : Color.FromArgb(185, 185, 185);
}
else{
labelType.Text = "USER";
labelType.BackColor = isEnabled ? Color.FromArgb(208, 154, 217) : Color.FromArgb(185, 185, 185);
}
if (plugin.CanRun){ nextHeight = requiredLines switch {
labelName.ForeColor = textColor; 1 => MaximumSize.Height - 2 * (font.Height - 1),
labelDescription.ForeColor = textColor; 2 => MaximumSize.Height - 1 * (font.Height - 1),
btnToggleState.Text = isEnabled ? "Disable" : "Enable"; _ => MaximumSize.Height
btnConfigure.Visible = isConfigurable; };
btnConfigure.Enabled = isEnabled;
} if (nextHeight != Height) {
else{ timerLayout.Start();
labelName.ForeColor = Color.DarkRed; }
labelDescription.ForeColor = Color.DarkRed; else {
btnToggleState.Visible = false; ResumeLayout();
btnConfigure.Visible = false; }
} }
}
} 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) {
pluginManager.ConfigurePlugin(plugin);
ParentForm?.Close();
}
private void btnToggleState_Click(object sender, EventArgs e) {
pluginManager.Config.SetEnabled(plugin, !pluginManager.Config.IsEnabled(plugin));
UpdatePluginState();
}
private void UpdatePluginState() {
bool isEnabled = pluginManager.Config.IsEnabled(plugin) && plugin.CanRun;
Color textColor = isEnabled ? Color.Black : Color.FromArgb(90, 90, 90);
labelVersion.ForeColor = textColor;
labelAuthor.ForeColor = textColor;
labelWebsite.ForeColor = isEnabled ? Color.Blue : Color.FromArgb(90, 90, 249);
if (plugin.Group == PluginGroup.Official) {
labelType.Text = "CORE";
labelType.BackColor = isEnabled ? Color.FromArgb(154, 195, 217) : Color.FromArgb(185, 185, 185);
}
else {
labelType.Text = "USER";
labelType.BackColor = isEnabled ? Color.FromArgb(208, 154, 217) : Color.FromArgb(185, 185, 185);
}
if (plugin.CanRun) {
labelName.ForeColor = textColor;
labelDescription.ForeColor = textColor;
btnToggleState.Text = isEnabled ? "Disable" : "Enable";
btnConfigure.Visible = isConfigurable;
btnConfigure.Enabled = isEnabled;
}
else {
labelName.ForeColor = Color.DarkRed;
labelDescription.ForeColor = Color.DarkRed;
btnToggleState.Visible = false;
btnConfigure.Visible = false;
}
}
}
} }

View File

@@ -5,33 +5,32 @@ using TweetDuck.Utils;
using TweetLib.Core.Browser; using TweetLib.Core.Browser;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Events; using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins {
sealed class PluginDispatcher : IPluginDispatcher{ sealed class PluginDispatcher : IPluginDispatcher {
public event EventHandler<PluginDispatchEventArgs> Ready; public event EventHandler<PluginDispatchEventArgs> Ready;
private readonly IWebBrowser browser; private readonly IWebBrowser browser;
private readonly IScriptExecutor executor; private readonly IScriptExecutor executor;
private readonly Func<string, bool> executeOnUrl; 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.executeOnUrl = executeOnUrl;
this.browser = browser; this.browser = browser;
this.browser.FrameLoadEnd += browser_FrameLoadEnd; this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.executor = new CefScriptExecutor(browser); this.executor = new CefScriptExecutor(browser);
} }
void IPluginDispatcher.AttachBridge(string name, object bridge){ void IPluginDispatcher.AttachBridge(string name, object bridge) {
browser.RegisterJsBridge(name, 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; IFrame frame = e.Frame;
if (frame.IsMain && executeOnUrl(frame.Url)){ if (frame.IsMain && executeOnUrl(frame.Url)) {
Ready?.Invoke(this, new PluginDispatchEventArgs(executor)); Ready?.Invoke(this, new PluginDispatchEventArgs(executor));
} }
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More