2017-02-03 20:07:26 +00:00
/*
This file is part of Telegram Desktop ,
2018-01-03 10:23:14 +00:00
the official desktop application for the Telegram messaging service .
2017-02-03 20:07:26 +00:00
2018-01-03 10:23:14 +00:00
For license and copyright information please follow this link :
https : //github.com/telegramdesktop/tdesktop/blob/master/LEGAL
2017-02-03 20:07:26 +00:00
*/
# include "window/themes/window_theme_editor.h"
# include "window/themes/window_theme.h"
# include "window/themes/window_theme_editor_block.h"
2019-09-03 08:25:19 +00:00
# include "window/themes/window_theme_editor_box.h"
2019-08-26 16:36:23 +00:00
# include "window/themes/window_themes_embedded.h"
2019-09-03 08:25:19 +00:00
# include "window/window_controller.h"
# include "main/main_account.h"
2017-02-03 20:07:26 +00:00
# include "mainwindow.h"
2017-03-04 10:23:56 +00:00
# include "storage/localstorage.h"
2017-04-06 14:38:10 +00:00
# include "boxes/confirm_box.h"
2017-02-03 20:07:26 +00:00
# include "ui/widgets/scroll_area.h"
# include "ui/widgets/shadow.h"
# include "ui/widgets/buttons.h"
# include "ui/widgets/multi_select.h"
2019-09-09 14:44:08 +00:00
# include "ui/widgets/dropdown_menu.h"
2018-10-23 09:44:42 +00:00
# include "ui/toast/toast.h"
2019-09-13 12:22:54 +00:00
# include "ui/ui_utility.h"
2017-04-06 14:38:10 +00:00
# include "base/parse_helper.h"
# include "base/zlib_help.h"
2019-09-26 10:55:35 +00:00
# include "base/call_delayed.h"
2017-02-28 14:05:30 +00:00
# include "core/file_utilities.h"
2019-08-26 16:36:23 +00:00
# include "core/application.h"
2017-04-06 14:38:10 +00:00
# include "boxes/edit_color_box.h"
2017-04-13 08:27:10 +00:00
# include "lang/lang_keys.h"
2019-09-13 06:06:02 +00:00
# include "facades.h"
# include "app.h"
2019-09-09 14:44:08 +00:00
# include "styles/style_window.h"
# include "styles/style_dialogs.h"
2019-09-18 11:19:05 +00:00
# include "styles/style_layers.h"
2019-09-09 14:44:08 +00:00
# include "styles/style_boxes.h"
2017-02-03 20:07:26 +00:00
namespace Window {
namespace Theme {
namespace {
2019-09-05 20:21:44 +00:00
template < size_t Size >
QByteArray qba ( const char ( & string ) [ Size ] ) {
return QByteArray : : fromRawData ( string , Size - 1 ) ;
}
const auto kCloudInTextStart = qba ( " // THEME EDITOR SERVICE INFO START \n " ) ;
const auto kCloudInTextEnd = qba ( " // THEME EDITOR SERVICE INFO END \n \n " ) ;
2017-02-03 20:07:26 +00:00
struct ReadColorResult {
ReadColorResult ( QColor color , bool error = false ) : color ( color ) , error ( error ) {
}
QColor color ;
bool error = false ;
} ;
ReadColorResult colorError ( const QString & name ) {
return { QColor ( ) , true } ;
}
ReadColorResult readColor ( const QString & name , const char * data , int size ) {
if ( size ! = 6 & & size ! = 8 ) {
return colorError ( name ) ;
}
auto readHex = [ ] ( char ch ) {
if ( ch > = ' 0 ' & & ch < = ' 9 ' ) {
return ( ch - ' 0 ' ) ;
} else if ( ch > = ' a ' & & ch < = ' f ' ) {
return ( ch - ' a ' + 10 ) ;
} else if ( ch > = ' A ' & & ch < = ' F ' ) {
return ( ch - ' A ' + 10 ) ;
}
return - 1 ;
} ;
auto readValue = [ readHex ] ( const char * data ) {
auto high = readHex ( data [ 0 ] ) ;
auto low = readHex ( data [ 1 ] ) ;
return ( high > = 0 & & low > = 0 ) ? ( high * 0x10 + low ) : - 1 ;
} ;
auto r = readValue ( data ) ;
auto g = readValue ( data + 2 ) ;
auto b = readValue ( data + 4 ) ;
auto a = ( size = = 8 ) ? readValue ( data + 6 ) : 255 ;
if ( r < 0 | | g < 0 | | b < 0 | | a < 0 ) {
return colorError ( name ) ;
}
return { QColor ( r , g , b , a ) } ;
}
bool skipComment ( const char * & data , const char * end ) {
if ( data = = end ) return false ;
if ( * data = = ' / ' & & data + 1 ! = end ) {
if ( * ( data + 1 ) = = ' / ' ) {
data + = 2 ;
while ( data ! = end & & * data ! = ' \n ' ) {
+ + data ;
}
return true ;
} else if ( * ( data + 1 ) = = ' * ' ) {
data + = 2 ;
while ( true ) {
while ( data ! = end & & * data ! = ' * ' ) {
+ + data ;
}
2017-02-09 13:53:28 +00:00
if ( data ! = end ) {
+ + data ;
if ( data ! = end & & * data = = ' / ' ) {
+ + data ;
break ;
}
}
if ( data = = end ) {
2017-02-03 20:07:26 +00:00
break ;
}
}
return true ;
}
}
return false ;
}
void skipWhitespacesAndComments ( const char * & data , const char * end ) {
while ( data ! = end ) {
if ( ! base : : parse : : skipWhitespaces ( data , end ) ) return ;
if ( ! skipComment ( data , end ) ) return ;
}
}
QLatin1String readValue ( const char * & data , const char * end ) {
auto start = data ;
if ( data ! = end & & * data = = ' # ' ) {
+ + data ;
}
base : : parse : : readName ( data , end ) ;
return QLatin1String ( start , data - start ) ;
}
bool isValidColorValue ( QLatin1String value ) {
auto isValidHexChar = [ ] ( char ch ) {
return ( ch > = ' 0 ' & & ch < = ' 9 ' ) | | ( ch > = ' A ' & & ch < = ' F ' ) | | ( ch > = ' a ' & & ch < = ' f ' ) ;
} ;
auto data = value . data ( ) ;
auto size = value . size ( ) ;
if ( ( size ! = 7 & & size ! = 9 ) | | data [ 0 ] ! = ' # ' ) {
return false ;
}
for ( auto i = 1 ; i ! = size ; + + i ) {
if ( ! isValidHexChar ( data [ i ] ) ) {
return false ;
}
}
return true ;
}
2019-09-05 20:21:44 +00:00
[[nodiscard]] QByteArray ColorizeInContent (
2019-08-26 16:36:23 +00:00
QByteArray content ,
2019-08-27 13:59:15 +00:00
const Colorizer & colorizer ) {
2019-08-26 16:36:23 +00:00
auto validNames = OrderedSet < QLatin1String > ( ) ;
content . detach ( ) ;
auto start = content . constBegin ( ) , data = start , end = data + content . size ( ) ;
while ( data ! = end ) {
skipWhitespacesAndComments ( data , end ) ;
if ( data = = end ) break ;
auto foundName = base : : parse : : readName ( data , end ) ;
skipWhitespacesAndComments ( data , end ) ;
if ( data = = end | | * data ! = ' : ' ) {
return " error " ;
}
+ + data ;
skipWhitespacesAndComments ( data , end ) ;
auto valueStart = data ;
auto value = readValue ( data , end ) ;
auto valueEnd = data ;
if ( value . size ( ) = = 0 ) {
return " error " ;
}
if ( isValidColorValue ( value ) ) {
const auto colorized = Colorize ( value , colorizer ) ;
Assert ( colorized . size ( ) = = value . size ( ) ) ;
memcpy (
content . data ( ) + ( data - start ) - value . size ( ) ,
colorized . data ( ) ,
value . size ( ) ) ;
}
skipWhitespacesAndComments ( data , end ) ;
if ( data = = end | | * data ! = ' ; ' ) {
return " error " ;
}
+ + data ;
}
return content ;
}
2017-02-03 20:07:26 +00:00
QString bytesToUtf8 ( QLatin1String bytes ) {
return QString : : fromUtf8 ( bytes . data ( ) , bytes . size ( ) ) ;
}
} // namespace
class Editor : : Inner : public TWidget , private base : : Subscriber {
public :
Inner ( QWidget * parent , const QString & path ) ;
2018-06-04 15:35:11 +00:00
void setErrorCallback ( Fn < void ( ) > callback ) {
2017-02-21 13:45:56 +00:00
_errorCallback = std : : move ( callback ) ;
2017-02-03 20:07:26 +00:00
}
2018-06-04 15:35:11 +00:00
void setFocusCallback ( Fn < void ( ) > callback ) {
2017-02-21 13:45:56 +00:00
_focusCallback = std : : move ( callback ) ;
2017-02-03 20:07:26 +00:00
}
2018-06-04 15:35:11 +00:00
void setScrollCallback ( Fn < void ( int top , int bottom ) > callback ) {
2017-02-21 13:45:56 +00:00
_scrollCallback = std : : move ( callback ) ;
2017-02-03 20:07:26 +00:00
}
void prepare ( ) ;
2019-09-03 08:25:19 +00:00
[[nodiscard]] QByteArray paletteContent ( ) const {
return _paletteContent ;
}
2017-02-03 20:07:26 +00:00
void filterRows ( const QString & query ) ;
void chooseRow ( ) ;
void selectSkip ( int direction ) ;
void selectSkipPage ( int delta , int direction ) ;
2019-09-09 20:58:41 +00:00
void applyNewPalette ( const QByteArray & newContent ) ;
void recreateRows ( ) ;
2017-02-03 20:07:26 +00:00
~ Inner ( ) {
if ( _context . box ) _context . box - > closeBox ( ) ;
}
protected :
void paintEvent ( QPaintEvent * e ) override ;
int resizeGetHeight ( int newWidth ) override ;
private :
bool readData ( ) ;
bool readExistingRows ( ) ;
bool feedExistingRow ( const QString & name , QLatin1String value ) ;
void error ( ) {
if ( _errorCallback ) {
_errorCallback ( ) ;
}
}
void applyEditing ( const QString & name , const QString & copyOf , QColor value ) ;
2019-08-20 16:03:20 +00:00
void sortByAccentDistance ( ) ;
2017-02-03 20:07:26 +00:00
EditorBlock : : Context _context ;
QString _path ;
QByteArray _paletteContent ;
2018-06-04 15:35:11 +00:00
Fn < void ( ) > _errorCallback ;
Fn < void ( ) > _focusCallback ;
Fn < void ( int top , int bottom ) > _scrollCallback ;
2017-02-03 20:07:26 +00:00
object_ptr < EditorBlock > _existingRows ;
object_ptr < EditorBlock > _newRows ;
bool _applyingUpdate = false ;
} ;
2019-09-08 18:00:31 +00:00
QByteArray ColorHexString ( const QColor & color ) {
auto result = QByteArray ( ) ;
result . reserve ( 9 ) ;
result . append ( ' # ' ) ;
const auto addHex = [ & ] ( int code ) {
if ( code > = 0 & & code < 10 ) {
result . append ( ' 0 ' + code ) ;
} else if ( code > = 10 & & code < 16 ) {
result . append ( ' a ' + ( code - 10 ) ) ;
}
} ;
const auto addValue = [ & ] ( int code ) {
addHex ( code / 16 ) ;
addHex ( code % 16 ) ;
} ;
addValue ( color . red ( ) ) ;
addValue ( color . green ( ) ) ;
addValue ( color . blue ( ) ) ;
if ( color . alpha ( ) ! = 255 ) {
addValue ( color . alpha ( ) ) ;
}
return result ;
}
QByteArray ReplaceValueInPaletteContent (
const QByteArray & content ,
const QByteArray & name ,
const QByteArray & value ) {
auto validNames = OrderedSet < QLatin1String > ( ) ;
auto start = content . constBegin ( ) , data = start , end = data + content . size ( ) ;
auto lastValidValueStart = end , lastValidValueEnd = end ;
while ( data ! = end ) {
skipWhitespacesAndComments ( data , end ) ;
if ( data = = end ) break ;
auto foundName = base : : parse : : readName ( data , end ) ;
skipWhitespacesAndComments ( data , end ) ;
if ( data = = end | | * data ! = ' : ' ) {
return " error " ;
}
+ + data ;
skipWhitespacesAndComments ( data , end ) ;
auto valueStart = data ;
auto value = readValue ( data , end ) ;
auto valueEnd = data ;
if ( value . size ( ) = = 0 ) {
return " error " ;
}
auto validValue = validNames . contains ( value ) | | isValidColorValue ( value ) ;
if ( validValue ) {
validNames . insert ( foundName ) ;
if ( foundName = = name ) {
lastValidValueStart = valueStart ;
lastValidValueEnd = valueEnd ;
}
}
skipWhitespacesAndComments ( data , end ) ;
if ( data = = end | | * data ! = ' ; ' ) {
return " error " ;
}
+ + data ;
}
if ( lastValidValueStart ! = end ) {
auto result = QByteArray ( ) ;
result . reserve ( ( lastValidValueStart - start ) + value . size ( ) + ( end - lastValidValueEnd ) ) ;
result . append ( start , lastValidValueStart - start ) ;
result . append ( value ) ;
if ( end - lastValidValueEnd > 0 ) result . append ( lastValidValueEnd , end - lastValidValueEnd ) ;
return result ;
}
2019-09-09 21:36:16 +00:00
auto newline = ( content . indexOf ( " \r \n " ) > = 0 ? " \r \n " : " \n " ) ;
auto addedline = ( content . endsWith ( ' \n ' ) ? " " : newline ) ;
return content + addedline + name + " : " + value + " ; " + newline ;
2019-09-08 18:00:31 +00:00
}
2019-09-05 20:21:44 +00:00
[[nodiscard]] QByteArray WriteCloudToText ( const Data : : CloudTheme & cloud ) {
auto result = QByteArray ( ) ;
const auto add = [ & ] ( const QByteArray & key , const QString & value ) {
result . append ( " // " + key + " : " + value . toLatin1 ( ) + " \n " ) ;
} ;
result . append ( kCloudInTextStart ) ;
add ( " ID " , QString : : number ( cloud . id ) ) ;
add ( " ACCESS " , QString : : number ( cloud . accessHash ) ) ;
result . append ( kCloudInTextEnd ) ;
return result ;
}
[[nodiscard]] Data : : CloudTheme ReadCloudFromText ( const QByteArray & text ) {
const auto index = text . indexOf ( kCloudInTextEnd ) ;
if ( index < = 1 ) {
return Data : : CloudTheme ( ) ;
}
auto result = Data : : CloudTheme ( ) ;
const auto list = text . mid ( 0 , index - 1 ) . split ( ' \n ' ) ;
const auto take = [ & ] ( uint64 & value , int index ) {
if ( list . size ( ) < = index ) {
return false ;
2019-08-26 16:36:23 +00:00
}
2019-09-05 20:21:44 +00:00
const auto & entry = list [ index ] ;
const auto position = entry . indexOf ( " : " ) ;
if ( position < 0 ) {
2019-08-26 16:36:23 +00:00
return false ;
}
2019-09-05 20:21:44 +00:00
value = QString : : fromLatin1 ( entry . mid ( position + 2 ) ) . toULongLong ( ) ;
return true ;
} ;
if ( ! take ( result . id , 1 ) | | ! take ( result . accessHash , 2 ) ) {
return Data : : CloudTheme ( ) ;
2019-08-26 16:36:23 +00:00
}
2019-09-05 20:21:44 +00:00
return result ;
2019-08-26 16:36:23 +00:00
}
2019-09-08 18:21:54 +00:00
QByteArray StripCloudTextFields ( const QByteArray & text ) {
const auto firstValue = text . indexOf ( " : # " ) ;
auto start = 0 ;
while ( true ) {
const auto index = text . indexOf ( kCloudInTextEnd , start ) ;
if ( index < 0 | | index > firstValue ) {
break ;
}
start = index + kCloudInTextEnd . size ( ) ;
}
return ( start > 0 ) ? text . mid ( start ) : text ;
}
2017-02-03 20:07:26 +00:00
Editor : : Inner : : Inner ( QWidget * parent , const QString & path ) : TWidget ( parent )
, _path ( path )
, _existingRows ( this , EditorBlock : : Type : : Existing , & _context )
, _newRows ( this , EditorBlock : : Type : : New , & _context ) {
resize ( st : : windowMinWidth , st : : windowMinHeight ) ;
subscribe ( _context . resized , [ this ] {
resizeToWidth ( width ( ) ) ;
} ) ;
subscribe ( _context . pending , [ this ] ( const EditorBlock : : Context : : EditionData & data ) {
applyEditing ( data . name , data . copyOf , data . value ) ;
} ) ;
subscribe ( _context . updated , [ this ] {
if ( _context . name . isEmpty ( ) & & _focusCallback ) {
_focusCallback ( ) ;
}
} ) ;
subscribe ( _context . scroll , [ this ] ( const EditorBlock : : Context : : ScrollData & data ) {
if ( _scrollCallback ) {
auto top = ( data . type = = EditorBlock : : Type : : Existing ? _existingRows : _newRows ) - > y ( ) ;
top + = data . position ;
_scrollCallback ( top , top + data . height ) ;
}
} ) ;
subscribe ( Background ( ) , [ this ] ( const BackgroundUpdate & update ) {
2019-09-06 16:29:10 +00:00
if ( _applyingUpdate | | ! Background ( ) - > editingTheme ( ) ) {
return ;
}
2017-02-03 20:07:26 +00:00
if ( update . type = = BackgroundUpdate : : Type : : TestingTheme ) {
Revert ( ) ;
2019-09-26 10:55:35 +00:00
base : : call_delayed ( st : : slideDuration , this , [ ] {
2019-09-06 16:29:10 +00:00
Ui : : show ( Box < InformBox > (
tr : : lng_theme_editor_cant_change_theme ( tr : : now ) ) ) ;
2017-02-03 20:07:26 +00:00
} ) ;
}
} ) ;
}
2019-09-09 20:58:41 +00:00
void Editor : : Inner : : recreateRows ( ) {
_existingRows . create ( this , EditorBlock : : Type : : Existing , & _context ) ;
_existingRows - > show ( ) ;
_newRows . create ( this , EditorBlock : : Type : : New , & _context ) ;
_newRows - > show ( ) ;
if ( ! readData ( ) ) {
error ( ) ;
}
}
2017-02-03 20:07:26 +00:00
void Editor : : Inner : : prepare ( ) {
2019-09-09 20:58:41 +00:00
QFile f ( _path ) ;
if ( ! f . open ( QIODevice : : ReadOnly ) ) {
LOG ( ( " Theme Error: could not open color palette file '%1' " ) . arg ( _path ) ) ;
error ( ) ;
return ;
}
_paletteContent = f . readAll ( ) ;
if ( f . error ( ) ! = QFileDevice : : NoError ) {
LOG ( ( " Theme Error: could not read content from palette file '%1' " ) . arg ( _path ) ) ;
error ( ) ;
return ;
}
f . close ( ) ;
2017-02-03 20:07:26 +00:00
if ( ! readData ( ) ) {
error ( ) ;
}
}
void Editor : : Inner : : filterRows ( const QString & query ) {
2019-08-26 14:08:33 +00:00
if ( query = = " :sort-for-accent " ) {
2019-08-20 16:03:20 +00:00
sortByAccentDistance ( ) ;
filterRows ( QString ( ) ) ;
return ;
}
2017-02-03 20:07:26 +00:00
_existingRows - > filterRows ( query ) ;
_newRows - > filterRows ( query ) ;
}
void Editor : : Inner : : chooseRow ( ) {
if ( ! _existingRows - > hasSelected ( ) & & ! _newRows - > hasSelected ( ) ) {
selectSkip ( 1 ) ;
}
if ( _existingRows - > hasSelected ( ) ) {
_existingRows - > chooseRow ( ) ;
} else if ( _newRows - > hasSelected ( ) ) {
_newRows - > chooseRow ( ) ;
}
}
// Block::selectSkip(-1) removes the selection if it can't select anything
// Block::selectSkip(1) leaves the selection if it can't select anything
void Editor : : Inner : : selectSkip ( int direction ) {
if ( direction > 0 ) {
if ( _newRows - > hasSelected ( ) ) {
_existingRows - > clearSelected ( ) ;
_newRows - > selectSkip ( direction ) ;
} else if ( _existingRows - > hasSelected ( ) ) {
if ( ! _existingRows - > selectSkip ( direction ) ) {
if ( _newRows - > selectSkip ( direction ) ) {
_existingRows - > clearSelected ( ) ;
}
}
} else {
if ( ! _existingRows - > selectSkip ( direction ) ) {
_newRows - > selectSkip ( direction ) ;
}
}
} else {
if ( _existingRows - > hasSelected ( ) ) {
_newRows - > clearSelected ( ) ;
_existingRows - > selectSkip ( direction ) ;
} else if ( _newRows - > hasSelected ( ) ) {
if ( ! _newRows - > selectSkip ( direction ) ) {
_existingRows - > selectSkip ( direction ) ;
}
}
}
}
void Editor : : Inner : : selectSkipPage ( int delta , int direction ) {
auto defaultRowHeight = st : : themeEditorMargin . top ( )
+ st : : themeEditorSampleSize . height ( )
+ st : : themeEditorDescriptionSkip
+ st : : defaultTextStyle . font - > height
+ st : : themeEditorMargin . bottom ( ) ;
for ( auto i = 0 , count = ceilclamp ( delta , defaultRowHeight , 1 , delta ) ; i ! = count ; + + i ) {
selectSkip ( direction ) ;
}
}
void Editor : : Inner : : paintEvent ( QPaintEvent * e ) {
Painter p ( this ) ;
2018-09-11 19:00:23 +00:00
p . setFont ( st : : boxTitleFont ) ;
2017-02-03 20:07:26 +00:00
p . setPen ( st : : windowFg ) ;
if ( ! _newRows - > isHidden ( ) ) {
2019-11-02 17:06:47 +00:00
p . drawTextLeft ( st : : themeEditorMargin . left ( ) , _existingRows - > y ( ) + _existingRows - > height ( ) + st : : boxTitlePosition . y ( ) , width ( ) , tr : : lng_theme_editor_new_keys ( tr : : now ) ) ;
2017-02-03 20:07:26 +00:00
}
}
int Editor : : Inner : : resizeGetHeight ( int newWidth ) {
auto rowsWidth = newWidth ;
_existingRows - > resizeToWidth ( rowsWidth ) ;
_newRows - > resizeToWidth ( rowsWidth ) ;
_existingRows - > moveToLeft ( 0 , 0 ) ;
2019-11-02 17:06:47 +00:00
_newRows - > moveToLeft ( 0 , _existingRows - > height ( ) + st : : boxTitleHeight ) ;
2017-02-03 20:07:26 +00:00
auto lowest = ( _newRows - > isHidden ( ) ? _existingRows : _newRows ) . data ( ) ;
return lowest - > y ( ) + lowest - > height ( ) ;
}
bool Editor : : Inner : : readData ( ) {
if ( ! readExistingRows ( ) ) {
return false ;
}
2019-08-20 16:03:20 +00:00
const auto rows = style : : main_palette : : data ( ) ;
for ( const auto & row : rows ) {
2017-02-03 20:07:26 +00:00
auto name = bytesToUtf8 ( row . name ) ;
auto description = bytesToUtf8 ( row . description ) ;
if ( ! _existingRows - > feedDescription ( name , description ) ) {
if ( row . value . data ( ) [ 0 ] = = ' # ' ) {
auto result = readColor ( name , row . value . data ( ) + 1 , row . value . size ( ) - 1 ) ;
2017-08-17 09:06:26 +00:00
Assert ( ! result . error ) ;
2017-02-03 20:07:26 +00:00
_newRows - > feed ( name , result . color ) ;
2020-01-29 09:44:37 +00:00
//if (!_newRows->feedFallbackName(name, row.fallback.utf16())) {
2017-03-17 12:05:50 +00:00
// Unexpected("Row for fallback not found");
2017-02-03 20:07:26 +00:00
//}
} else {
auto copyOf = bytesToUtf8 ( row . value ) ;
if ( auto result = _existingRows - > find ( copyOf ) ) {
_newRows - > feed ( name , * result , copyOf ) ;
} else if ( ! _newRows - > feedCopy ( name , copyOf ) ) {
2017-03-17 12:05:50 +00:00
Unexpected ( " Copy of unknown value in the default palette " ) ;
2017-02-03 20:07:26 +00:00
}
2017-08-17 09:06:26 +00:00
Assert ( row . fallback . size ( ) = = 0 ) ;
2017-02-03 20:07:26 +00:00
}
if ( ! _newRows - > feedDescription ( name , description ) ) {
2017-03-17 12:05:50 +00:00
Unexpected ( " Row for description not found " ) ;
2017-02-03 20:07:26 +00:00
}
}
}
2019-08-20 16:03:20 +00:00
2017-02-03 20:07:26 +00:00
return true ;
}
2019-08-20 16:03:20 +00:00
void Editor : : Inner : : sortByAccentDistance ( ) {
const auto accent = * _existingRows - > find ( " windowBgActive " ) ;
_existingRows - > sortByDistance ( accent ) ;
_newRows - > sortByDistance ( accent ) ;
}
2017-02-03 20:07:26 +00:00
bool Editor : : Inner : : readExistingRows ( ) {
return ReadPaletteValues ( _paletteContent , [ this ] ( QLatin1String name , QLatin1String value ) {
return feedExistingRow ( name , value ) ;
} ) ;
}
bool Editor : : Inner : : feedExistingRow ( const QString & name , QLatin1String value ) {
auto data = value . data ( ) ;
auto size = value . size ( ) ;
if ( data [ 0 ] ! = ' # ' ) {
return _existingRows - > feedCopy ( name , QString ( value ) ) ;
}
auto result = readColor ( name , data + 1 , size - 1 ) ;
if ( result . error ) {
LOG ( ( " Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme) " ) . arg ( name ) . arg ( value ) ) ;
} else {
_existingRows - > feed ( name , result . color ) ;
}
return true ;
}
void Editor : : Inner : : applyEditing ( const QString & name , const QString & copyOf , QColor value ) {
auto plainName = name . toLatin1 ( ) ;
2019-09-08 18:00:31 +00:00
auto plainValue = copyOf . isEmpty ( ) ? ColorHexString ( value ) : copyOf . toLatin1 ( ) ;
auto newContent = ReplaceValueInPaletteContent ( _paletteContent , plainName , plainValue ) ;
2017-02-03 20:07:26 +00:00
if ( newContent = = " error " ) {
2019-09-08 18:00:31 +00:00
LOG ( ( " Theme Error: could not replace '%1: %2' in content " ) . arg ( name ) . arg ( copyOf . isEmpty ( ) ? QString : : fromLatin1 ( ColorHexString ( value ) ) : copyOf ) ) ;
2017-02-03 20:07:26 +00:00
error ( ) ;
return ;
}
2019-09-09 20:58:41 +00:00
applyNewPalette ( newContent ) ;
}
void Editor : : Inner : : applyNewPalette ( const QByteArray & newContent ) {
2017-02-03 20:07:26 +00:00
QFile f ( _path ) ;
if ( ! f . open ( QIODevice : : WriteOnly ) ) {
LOG ( ( " Theme Error: could not open '%1' for writing a palette update. " ) . arg ( _path ) ) ;
error ( ) ;
return ;
}
if ( f . write ( newContent ) ! = newContent . size ( ) ) {
LOG ( ( " Theme Error: could not write all content to '%1' while writing a palette update. " ) . arg ( _path ) ) ;
error ( ) ;
return ;
}
f . close ( ) ;
_applyingUpdate = true ;
2019-09-05 06:51:46 +00:00
if ( ! ApplyEditedPalette ( newContent ) ) {
2017-02-03 20:07:26 +00:00
LOG ( ( " Theme Error: could not apply newly composed content :( " ) ) ;
error ( ) ;
return ;
}
_applyingUpdate = false ;
_paletteContent = newContent ;
}
2019-09-05 10:51:36 +00:00
Editor : : Editor (
QWidget * ,
not_null < Window : : Controller * > window ,
const Data : : CloudTheme & cloud )
2019-09-03 08:25:19 +00:00
: _window ( window )
2019-09-05 10:51:36 +00:00
, _cloud ( cloud )
2019-09-03 08:25:19 +00:00
, _scroll ( this , st : : themesScroll )
2020-11-29 18:26:49 +00:00
, _close ( this , st : : defaultMultiSelect . fieldCancel )
2019-09-09 14:44:08 +00:00
, _menuToggle ( this , st : : themesMenuToggle )
2020-11-29 18:26:49 +00:00
, _select ( this , st : : defaultMultiSelect , tr : : lng_country_ph ( ) )
2017-02-03 20:07:26 +00:00
, _leftShadow ( this )
, _topShadow ( this )
2019-09-03 08:25:19 +00:00
, _save ( this , tr : : lng_theme_editor_save_button ( tr : : now ) . toUpper ( ) , st : : dialogsUpdateButton ) {
2019-09-02 16:10:18 +00:00
const auto path = EditingPalettePath ( ) ;
2017-02-03 20:07:26 +00:00
_inner = _scroll - > setOwnedWidget ( object_ptr < Inner > ( this , path ) ) ;
2019-09-03 08:25:19 +00:00
_save - > setClickedCallback ( App : : LambdaDelayed (
st : : defaultRippleAnimation . hideDuration ,
this ,
[ = ] { save ( ) ; } ) ) ;
2017-02-03 20:07:26 +00:00
_inner - > setErrorCallback ( [ this ] {
2019-06-19 15:09:03 +00:00
Ui : : show ( Box < InformBox > ( tr : : lng_theme_editor_error ( tr : : now ) ) ) ;
2017-02-03 20:07:26 +00:00
// This could be from inner->_context observable notification.
// We should not destroy it while iterating in subscribers.
2017-12-30 21:28:38 +00:00
crl : : on_main ( this , [ = ] {
closeEditor ( ) ;
} ) ;
2017-02-03 20:07:26 +00:00
} ) ;
_inner - > setFocusCallback ( [ this ] {
2019-09-26 10:55:35 +00:00
base : : call_delayed ( 2 * st : : boxDuration , this , [ this ] {
_select - > setInnerFocus ( ) ;
} ) ;
2017-02-03 20:07:26 +00:00
} ) ;
_inner - > setScrollCallback ( [ this ] ( int top , int bottom ) {
_scroll - > scrollToY ( top , bottom ) ;
} ) ;
2019-09-09 14:44:08 +00:00
_menuToggle - > setClickedCallback ( [ = ] {
showMenu ( ) ;
} ) ;
2019-09-05 06:51:46 +00:00
_close - > setClickedCallback ( [ = ] {
2019-09-08 13:40:15 +00:00
closeWithConfirmation ( ) ;
2019-09-05 06:51:46 +00:00
} ) ;
2017-11-27 11:43:57 +00:00
_close - > show ( anim : : type : : instant ) ;
2017-02-03 20:07:26 +00:00
_select - > resizeToWidth ( st : : windowMinWidth ) ;
_select - > setQueryChangedCallback ( [ this ] ( const QString & query ) { _inner - > filterRows ( query ) ; _scroll - > scrollToY ( 0 ) ; } ) ;
2018-05-31 12:20:28 +00:00
_select - > setSubmittedCallback ( [ this ] ( Qt : : KeyboardModifiers ) { _inner - > chooseRow ( ) ; } ) ;
2017-02-03 20:07:26 +00:00
_inner - > prepare ( ) ;
resizeToWidth ( st : : windowMinWidth ) ;
}
2019-09-09 14:44:08 +00:00
void Editor : : showMenu ( ) {
if ( _menu ) {
return ;
}
2020-06-05 10:00:06 +00:00
_menu = base : : make_unique_q < Ui : : DropdownMenu > ( this ) ;
_menu - > setHiddenCallback ( [ weak = Ui : : MakeWeak ( this ) , menu = _menu . get ( ) ] {
2019-09-09 14:44:08 +00:00
menu - > deleteLater ( ) ;
if ( weak & & weak - > _menu = = menu ) {
weak - > _menu = nullptr ;
weak - > _menuToggle - > setForceRippled ( false ) ;
}
} ) ;
2020-06-05 10:00:06 +00:00
_menu - > setShowStartCallback ( crl : : guard ( this , [ this , menu = _menu . get ( ) ] {
2019-09-09 14:44:08 +00:00
if ( _menu = = menu ) {
_menuToggle - > setForceRippled ( true ) ;
}
} ) ) ;
2020-06-05 10:00:06 +00:00
_menu - > setHideStartCallback ( crl : : guard ( this , [ this , menu = _menu . get ( ) ] {
2019-09-09 14:44:08 +00:00
if ( _menu = = menu ) {
_menuToggle - > setForceRippled ( false ) ;
}
} ) ) ;
_menuToggle - > installEventFilter ( _menu ) ;
_menu - > addAction ( tr : : lng_theme_editor_menu_export ( tr : : now ) , [ = ] {
2019-09-26 10:55:35 +00:00
base : : call_delayed ( st : : defaultRippleAnimation . hideDuration , this , [ = ] {
2019-09-09 14:44:08 +00:00
exportTheme ( ) ;
} ) ;
} ) ;
2019-09-09 20:58:41 +00:00
_menu - > addAction ( tr : : lng_theme_editor_menu_import ( tr : : now ) , [ = ] {
2019-09-26 10:55:35 +00:00
base : : call_delayed ( st : : defaultRippleAnimation . hideDuration , this , [ = ] {
2019-09-09 20:58:41 +00:00
importTheme ( ) ;
} ) ;
} ) ;
2019-09-09 14:44:08 +00:00
_menu - > addAction ( tr : : lng_theme_editor_menu_show ( tr : : now ) , [ = ] {
File : : ShowInFolder ( EditingPalettePath ( ) ) ;
} ) ;
_menu - > moveToRight ( st : : themesMenuPosition . x ( ) , st : : themesMenuPosition . y ( ) ) ;
_menu - > showAnimated ( Ui : : PanelAnimation : : Origin : : TopRight ) ;
}
void Editor : : exportTheme ( ) {
auto caption = tr : : lng_theme_editor_choose_name ( tr : : now ) ;
auto filter = " Themes (*.tdesktop-theme) " ;
auto name = " awesome.tdesktop-theme " ;
FileDialog : : GetWritePath ( this , caption , filter , name , crl : : guard ( this , [ = ] ( const QString & path ) {
const auto result = CollectForExport ( _inner - > paletteContent ( ) ) ;
QFile f ( path ) ;
if ( ! f . open ( QIODevice : : WriteOnly ) ) {
LOG ( ( " Theme Error: could not open zip-ed theme file '%1' for writing " ) . arg ( path ) ) ;
Ui : : show ( Box < InformBox > ( tr : : lng_theme_editor_error ( tr : : now ) ) ) ;
return ;
}
if ( f . write ( result ) ! = result . size ( ) ) {
LOG ( ( " Theme Error: could not write zip-ed theme to file '%1' " ) . arg ( path ) ) ;
Ui : : show ( Box < InformBox > ( tr : : lng_theme_editor_error ( tr : : now ) ) ) ;
return ;
}
Ui : : Toast : : Show ( tr : : lng_theme_editor_done ( tr : : now ) ) ;
} ) ) ;
}
2019-09-09 20:58:41 +00:00
void Editor : : importTheme ( ) {
auto filters = QStringList (
qsl ( " Theme files (*.tdesktop-theme *.tdesktop-palette) " ) ) ;
filters . push_back ( FileDialog : : AllFilesFilter ( ) ) ;
const auto callback = crl : : guard ( this , [ = ] (
const FileDialog : : OpenResult & result ) {
const auto path = result . paths . isEmpty ( )
? QString ( )
: result . paths . front ( ) ;
if ( path . isEmpty ( ) ) {
return ;
}
auto f = QFile ( path ) ;
if ( ! f . open ( QIODevice : : ReadOnly ) ) {
return ;
}
auto object = Object ( ) ;
object . pathAbsolute = QFileInfo ( path ) . absoluteFilePath ( ) ;
object . pathRelative = QDir ( ) . relativeFilePath ( path ) ;
object . content = f . readAll ( ) ;
if ( object . content . isEmpty ( ) ) {
return ;
}
_select - > clearQuery ( ) ;
const auto parsed = ParseTheme ( object , false , false ) ;
_inner - > applyNewPalette ( parsed . palette ) ;
_inner - > recreateRows ( ) ;
updateControlsGeometry ( ) ;
auto image = App : : readImage ( parsed . background ) ;
if ( ! image . isNull ( ) & & ! image . size ( ) . isEmpty ( ) ) {
Background ( ) - > set ( Data : : CustomWallPaper ( ) , std : : move ( image ) ) ;
Background ( ) - > setTile ( parsed . tiled ) ;
Ui : : ForceFullRepaint ( _window - > widget ( ) ) ;
}
} ) ;
FileDialog : : GetOpenPath (
this ,
tr : : lng_theme_editor_menu_import ( tr : : now ) ,
filters . join ( qsl ( " ;; " ) ) ,
crl : : guard ( this , callback ) ) ;
}
2019-09-05 20:21:44 +00:00
QByteArray Editor : : ColorizeInContent (
QByteArray content ,
const Colorizer & colorizer ) {
return Window : : Theme : : ColorizeInContent ( content , colorizer ) ;
}
2019-09-03 08:25:19 +00:00
void Editor : : save ( ) {
2019-09-09 17:26:53 +00:00
if ( Core : : App ( ) . passcodeLocked ( ) ) {
Ui : : Toast : : Show ( tr : : lng_theme_editor_need_unlock ( tr : : now ) ) ;
return ;
} else if ( ! _window - > account ( ) . sessionExists ( ) ) {
2019-09-05 10:51:36 +00:00
Ui : : Toast : : Show ( tr : : lng_theme_editor_need_auth ( tr : : now ) ) ;
2019-09-03 08:25:19 +00:00
return ;
2019-09-05 20:21:44 +00:00
} else if ( _saving ) {
return ;
2019-09-03 08:25:19 +00:00
}
2019-09-05 20:21:44 +00:00
_saving = true ;
const auto unlock = crl : : guard ( this , [ = ] { _saving = false ; } ) ;
SaveTheme ( _window , _cloud , _inner - > paletteContent ( ) , unlock ) ;
2019-09-03 08:25:19 +00:00
}
2017-02-03 20:07:26 +00:00
void Editor : : resizeEvent ( QResizeEvent * e ) {
2019-09-09 20:58:41 +00:00
updateControlsGeometry ( ) ;
}
void Editor : : updateControlsGeometry ( ) {
2019-09-03 08:25:19 +00:00
_save - > resizeToWidth ( width ( ) ) ;
2017-02-03 20:07:26 +00:00
_close - > moveToRight ( 0 , 0 ) ;
2019-09-09 14:44:08 +00:00
_menuToggle - > moveToRight ( _close - > width ( ) , 0 ) ;
2017-02-03 20:07:26 +00:00
_select - > resizeToWidth ( width ( ) ) ;
_select - > moveToLeft ( 0 , _close - > height ( ) ) ;
auto shadowTop = _select - > y ( ) + _select - > height ( ) ;
_topShadow - > resize ( width ( ) - st : : lineWidth , st : : lineWidth ) ;
_topShadow - > moveToLeft ( st : : lineWidth , shadowTop ) ;
_leftShadow - > resize ( st : : lineWidth , height ( ) ) ;
_leftShadow - > moveToLeft ( 0 , 0 ) ;
2019-09-03 08:25:19 +00:00
auto scrollSize = QSize ( width ( ) , height ( ) - shadowTop - _save - > height ( ) ) ;
2017-02-03 20:07:26 +00:00
if ( _scroll - > size ( ) ! = scrollSize ) {
_scroll - > resize ( scrollSize ) ;
}
_inner - > resizeToWidth ( width ( ) ) ;
_scroll - > moveToLeft ( 0 , shadowTop ) ;
if ( ! _scroll - > isHidden ( ) ) {
auto scrollTop = _scroll - > scrollTop ( ) ;
_inner - > setVisibleTopBottom ( scrollTop , scrollTop + _scroll - > height ( ) ) ;
}
2019-09-03 08:25:19 +00:00
_save - > moveToLeft ( 0 , _scroll - > y ( ) + _scroll - > height ( ) ) ;
2017-02-03 20:07:26 +00:00
}
void Editor : : keyPressEvent ( QKeyEvent * e ) {
if ( e - > key ( ) = = Qt : : Key_Escape ) {
if ( ! _select - > getQuery ( ) . isEmpty ( ) ) {
_select - > clearQuery ( ) ;
} else if ( auto window = App : : wnd ( ) ) {
window - > setInnerFocus ( ) ;
}
} else if ( e - > key ( ) = = Qt : : Key_Down ) {
_inner - > selectSkip ( 1 ) ;
} else if ( e - > key ( ) = = Qt : : Key_Up ) {
_inner - > selectSkip ( - 1 ) ;
} else if ( e - > key ( ) = = Qt : : Key_PageDown ) {
_inner - > selectSkipPage ( _scroll - > height ( ) , 1 ) ;
} else if ( e - > key ( ) = = Qt : : Key_PageUp ) {
_inner - > selectSkipPage ( _scroll - > height ( ) , - 1 ) ;
}
}
void Editor : : focusInEvent ( QFocusEvent * e ) {
_select - > setInnerFocus ( ) ;
}
void Editor : : paintEvent ( QPaintEvent * e ) {
Painter p ( this ) ;
p . fillRect ( e - > rect ( ) , st : : dialogsBg ) ;
2018-09-11 19:00:23 +00:00
p . setFont ( st : : boxTitleFont ) ;
2017-02-03 20:07:26 +00:00
p . setPen ( st : : windowFg ) ;
2019-06-19 15:09:03 +00:00
p . drawTextLeft ( st : : themeEditorMargin . left ( ) , st : : themeEditorMargin . top ( ) , width ( ) , tr : : lng_theme_editor_title ( tr : : now ) ) ;
2017-02-03 20:07:26 +00:00
}
2019-09-08 13:40:15 +00:00
void Editor : : closeWithConfirmation ( ) {
if ( ! PaletteChanged ( _inner - > paletteContent ( ) , _cloud ) ) {
Background ( ) - > clearEditingTheme ( ClearEditing : : KeepChanges ) ;
closeEditor ( ) ;
return ;
}
2020-09-24 12:48:02 +00:00
const auto close = crl : : guard ( this , [ = ] ( Fn < void ( ) > & & close ) {
2019-09-08 13:40:15 +00:00
Background ( ) - > clearEditingTheme ( ClearEditing : : RevertChanges ) ;
closeEditor ( ) ;
2020-09-24 12:48:02 +00:00
close ( ) ;
2019-09-08 13:40:15 +00:00
} ) ;
2020-09-24 12:48:02 +00:00
_window - > show ( Box < ConfirmBox > (
2019-09-08 13:40:15 +00:00
tr : : lng_theme_editor_sure_close ( tr : : now ) ,
tr : : lng_close ( tr : : now ) ,
close ) ) ;
}
2017-02-03 20:07:26 +00:00
void Editor : : closeEditor ( ) {
2019-09-03 08:25:19 +00:00
if ( const auto window = App : : wnd ( ) ) {
2017-02-03 20:07:26 +00:00
window - > showRightColumn ( nullptr ) ;
2019-09-06 15:30:44 +00:00
Background ( ) - > clearEditingTheme ( ) ;
2017-02-03 20:07:26 +00:00
}
}
} // namespace Theme
} // namespace Window