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"
# include "mainwindow.h"
2018-01-11 19:33:26 +00:00
# include "layout.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 "styles/style_window.h"
# include "styles/style_dialogs.h"
# include "styles/style_boxes.h"
# include "ui/widgets/scroll_area.h"
# include "ui/widgets/shadow.h"
# include "ui/widgets/buttons.h"
# include "ui/widgets/checkbox.h"
# include "ui/widgets/multi_select.h"
2018-10-23 09:44:42 +00:00
# include "ui/image/image_prepare.h"
# include "ui/toast/toast.h"
2017-04-06 14:38:10 +00:00
# include "base/parse_helper.h"
# include "base/zlib_help.h"
2017-02-28 14:05:30 +00:00
# include "core/file_utilities.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"
2017-02-03 20:07:26 +00:00
namespace Window {
namespace Theme {
namespace {
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 ;
}
QByteArray replaceValueInContent ( 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 ;
}
return QByteArray ( ) ;
}
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 ( ) ;
2018-06-04 15:35:11 +00:00
Fn < void ( ) > exportCallback ( ) ;
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 ) ;
~ 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 ) ;
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 ;
} ;
class ThemeExportBox : public BoxContent {
public :
ThemeExportBox ( QWidget * , const QByteArray & paletteContent , const QImage & background , const QByteArray & backgroundContent , bool tileBackground ) ;
protected :
void prepare ( ) override ;
void paintEvent ( QPaintEvent * e ) override ;
void resizeEvent ( QResizeEvent * e ) override ;
private :
void updateThumbnail ( ) ;
void chooseBackgroundFromFile ( ) ;
void exportTheme ( ) ;
QByteArray _paletteContent ;
QImage _background ;
QByteArray _backgroundContent ;
bool _isPng = false ;
QString _imageText ;
QPixmap _thumbnail ;
object_ptr < Ui : : LinkButton > _chooseFromFile ;
object_ptr < Ui : : Checkbox > _tileBackground ;
} ;
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 ) {
if ( _applyingUpdate ) return ;
if ( update . type = = BackgroundUpdate : : Type : : TestingTheme ) {
Revert ( ) ;
App : : CallDelayed ( st : : slideDuration , this , [ ] {
Ui : : show ( Box < InformBox > ( lang ( lng_theme_editor_cant_change_theme ) ) ) ;
} ) ;
}
} ) ;
}
void Editor : : Inner : : prepare ( ) {
if ( ! readData ( ) ) {
error ( ) ;
}
}
2018-06-04 15:35:11 +00:00
Fn < void ( ) > Editor : : Inner : : exportCallback ( ) {
2019-01-17 08:18:23 +00:00
return App : : LambdaDelayed ( st : : defaultRippleAnimation . hideDuration , this , [ = ] {
auto background = Background ( ) - > createCurrentImage ( ) ;
2017-02-03 20:07:26 +00:00
auto backgroundContent = QByteArray ( ) ;
auto tiled = Background ( ) - > tile ( ) ;
{
QBuffer buffer ( & backgroundContent ) ;
background . save ( & buffer , " JPG " , 87 ) ;
}
Ui : : show ( Box < ThemeExportBox > ( _paletteContent , background , backgroundContent , tiled ) ) ;
} ) ;
}
void Editor : : Inner : : filterRows ( const QString & query ) {
_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 ( ) ) {
2018-09-11 19:00:23 +00:00
p . drawTextLeft ( st : : themeEditorMargin . left ( ) , _existingRows - > y ( ) + _existingRows - > height ( ) + st : : boxLayerTitlePosition . y ( ) , width ( ) , lang ( lng_theme_editor_new_keys ) ) ;
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 ) ;
2018-09-11 19:00:23 +00:00
_newRows - > moveToLeft ( 0 , _existingRows - > height ( ) + st : : boxLayerTitleHeight ) ;
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 ;
}
auto rows = style : : main_palette : : data ( ) ;
for_const ( auto & row , rows ) {
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 ) ;
//if (!_newRows->feedFallbackName(name, str_const_toString(row.fallback))) {
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
}
}
}
return true ;
}
bool Editor : : Inner : : readExistingRows ( ) {
QFile f ( _path ) ;
if ( ! f . open ( QIODevice : : ReadOnly ) ) {
LOG ( ( " Theme Error: could not open color palette file '%1' " ) . arg ( _path ) ) ;
return false ;
}
_paletteContent = f . readAll ( ) ;
if ( f . error ( ) ! = QFileDevice : : NoError ) {
LOG ( ( " Theme Error: could not read content from palette file '%1' " ) . arg ( _path ) ) ;
return false ;
}
f . close ( ) ;
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 ;
}
QString colorString ( QColor color ) {
auto result = QString ( ) ;
result . reserve ( 9 ) ;
result . append ( ' # ' ) ;
auto addHex = [ & result ] ( int code ) {
if ( code > = 0 & & code < 10 ) {
result . append ( ' 0 ' + code ) ;
} else if ( code > = 10 & & code < 16 ) {
result . append ( ' a ' + ( code - 10 ) ) ;
}
} ;
auto addValue = [ addHex ] ( 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 ;
}
void Editor : : Inner : : applyEditing ( const QString & name , const QString & copyOf , QColor value ) {
auto plainName = name . toLatin1 ( ) ;
auto plainValue = ( copyOf . isEmpty ( ) ? colorString ( value ) : copyOf ) . toLatin1 ( ) ;
auto newContent = replaceValueInContent ( _paletteContent , plainName , plainValue ) ;
if ( newContent = = " error " ) {
LOG ( ( " Theme Error: could not replace '%1: %2' in content " ) . arg ( name ) . arg ( copyOf . isEmpty ( ) ? colorString ( value ) : copyOf ) ) ;
error ( ) ;
return ;
}
if ( newContent . isEmpty ( ) ) {
auto newline = ( _paletteContent . indexOf ( " \r \n " ) > = 0 ? " \r \n " : " \n " ) ;
auto addedline = ( _paletteContent . endsWith ( ' \n ' ) ? " " : newline ) ;
newContent = _paletteContent + addedline + plainName + " : " + plainValue + " ; " + newline ;
}
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 ;
if ( ! ApplyEditedPalette ( _path , newContent ) ) {
LOG ( ( " Theme Error: could not apply newly composed content :( " ) ) ;
error ( ) ;
return ;
}
_applyingUpdate = false ;
_paletteContent = newContent ;
}
void writeDefaultPalette ( const QString & path ) {
QFile f ( path ) ;
if ( ! f . open ( QIODevice : : WriteOnly ) ) {
LOG ( ( " Theme Error: could not open '%1' for writing. " ) . arg ( path ) ) ;
return ;
}
QTextStream stream ( & f ) ;
stream . setCodec ( " UTF-8 " ) ;
auto rows = style : : main_palette : : data ( ) ;
for_const ( auto & row , rows ) {
stream < < bytesToUtf8 ( row . name ) < < " : " < < bytesToUtf8 ( row . value ) < < " ; // " < < bytesToUtf8 ( row . description ) . replace ( ' \n ' , ' ' ) . replace ( ' \r ' , ' ' ) < < " \n " ;
}
}
ThemeExportBox : : ThemeExportBox ( QWidget * , const QByteArray & paletteContent , const QImage & background , const QByteArray & backgroundContent , bool tileBackground ) : BoxContent ( )
, _paletteContent ( paletteContent )
, _background ( background )
, _backgroundContent ( backgroundContent )
, _chooseFromFile ( this , lang ( lng_settings_bg_from_file ) , st : : boxLinkButton )
, _tileBackground ( this , lang ( lng_settings_bg_tile ) , tileBackground , st : : defaultBoxCheckbox ) {
_imageText = lng_theme_editor_saved_to_jpg ( lt_size , formatSizeText ( _backgroundContent . size ( ) ) ) ;
_chooseFromFile - > setClickedCallback ( [ this ] { chooseBackgroundFromFile ( ) ; } ) ;
}
void ThemeExportBox : : prepare ( ) {
2017-05-30 15:21:05 +00:00
setTitle ( langFactory ( lng_theme_editor_background_image ) ) ;
2017-02-03 20:07:26 +00:00
2017-05-30 15:21:05 +00:00
addButton ( langFactory ( lng_theme_editor_export ) , [ this ] { exportTheme ( ) ; } ) ;
addButton ( langFactory ( lng_cancel ) , [ this ] { closeBox ( ) ; } ) ;
2017-02-03 20:07:26 +00:00
2018-09-11 19:00:23 +00:00
auto height = st : : themesSmallSkip + st : : themesBackgroundSize + st : : themesSmallSkip + _tileBackground - > height ( ) ;
2017-02-03 20:07:26 +00:00
setDimensions ( st : : boxWideWidth , height ) ;
updateThumbnail ( ) ;
}
void ThemeExportBox : : paintEvent ( QPaintEvent * e ) {
BoxContent : : paintEvent ( e ) ;
Painter p ( this ) ;
2018-09-11 19:00:23 +00:00
auto linkLeft = st : : boxPadding . left ( ) + st : : themesBackgroundSize + st : : themesSmallSkip ;
2017-02-03 20:07:26 +00:00
p . setPen ( st : : boxTextFg ) ;
p . setFont ( st : : boxTextFont ) ;
2018-09-11 19:00:23 +00:00
p . drawTextLeft ( linkLeft , st : : themesSmallSkip , width ( ) , _imageText ) ;
2017-02-03 20:07:26 +00:00
2018-09-11 19:00:23 +00:00
p . drawPixmapLeft ( st : : boxPadding . left ( ) , st : : themesSmallSkip , width ( ) , _thumbnail ) ;
2017-02-03 20:07:26 +00:00
}
void ThemeExportBox : : resizeEvent ( QResizeEvent * e ) {
2018-09-11 19:00:23 +00:00
auto linkLeft = st : : boxPadding . left ( ) + st : : themesBackgroundSize + st : : themesSmallSkip ;
_chooseFromFile - > moveToLeft ( linkLeft , st : : themesSmallSkip + st : : boxTextFont - > height + st : : themesSmallSkip ) ;
_tileBackground - > moveToLeft ( st : : boxPadding . left ( ) , st : : themesSmallSkip + st : : themesBackgroundSize + 2 * st : : themesSmallSkip ) ;
2017-02-03 20:07:26 +00:00
}
void ThemeExportBox : : updateThumbnail ( ) {
2018-09-11 19:00:23 +00:00
int32 size = st : : themesBackgroundSize * cIntRetinaFactor ( ) ;
2017-02-03 20:07:26 +00:00
QImage back ( size , size , QImage : : Format_ARGB32_Premultiplied ) ;
back . setDevicePixelRatio ( cRetinaFactor ( ) ) ;
{
Painter p ( & back ) ;
PainterHighQualityEnabler hq ( p ) ;
auto & pix = _background ;
int sx = ( pix . width ( ) > pix . height ( ) ) ? ( ( pix . width ( ) - pix . height ( ) ) / 2 ) : 0 ;
int sy = ( pix . height ( ) > pix . width ( ) ) ? ( ( pix . height ( ) - pix . width ( ) ) / 2 ) : 0 ;
int s = ( pix . width ( ) > pix . height ( ) ) ? pix . height ( ) : pix . width ( ) ;
2018-09-11 19:00:23 +00:00
p . drawImage ( QRect ( 0 , 0 , st : : themesBackgroundSize , st : : themesBackgroundSize ) , pix , QRect ( sx , sy , s , s ) ) ;
2017-02-03 20:07:26 +00:00
}
Images : : prepareRound ( back , ImageRoundRadius : : Small ) ;
2017-02-21 13:45:56 +00:00
_thumbnail = App : : pixmapFromImageInPlace ( std : : move ( back ) ) ;
2017-02-03 20:07:26 +00:00
_thumbnail . setDevicePixelRatio ( cRetinaFactor ( ) ) ;
update ( ) ;
}
void ThemeExportBox : : chooseBackgroundFromFile ( ) {
2018-06-04 15:35:11 +00:00
FileDialog : : GetOpenPath ( this , lang ( lng_theme_editor_choose_image ) , " Image files (*.jpeg *.jpg *.png) " , crl : : guard ( this , [ this ] ( const FileDialog : : OpenResult & result ) {
2017-02-03 20:07:26 +00:00
auto content = result . remoteContent ;
if ( ! result . paths . isEmpty ( ) ) {
QFile f ( result . paths . front ( ) ) ;
if ( f . open ( QIODevice : : ReadOnly ) ) {
content = f . readAll ( ) ;
f . close ( ) ;
}
}
if ( ! content . isEmpty ( ) ) {
auto format = QByteArray ( ) ;
auto image = App : : readImage ( content , & format ) ;
if ( ! image . isNull ( ) & & ( format = = " jpeg " | | format = = " jpg " | | format = = " png " ) ) {
_background = image ;
_backgroundContent = content ;
_isPng = ( format = = " png " ) ;
2017-06-20 16:03:18 +00:00
auto sizeText = formatSizeText ( _backgroundContent . size ( ) ) ;
_imageText = _isPng ? lng_theme_editor_read_from_png ( lt_size , sizeText ) : lng_theme_editor_read_from_jpg ( lt_size , sizeText ) ;
2017-02-03 20:07:26 +00:00
_tileBackground - > setChecked ( false ) ;
updateThumbnail ( ) ;
}
}
} ) ) ;
}
void ThemeExportBox : : exportTheme ( ) {
App : : CallDelayed ( st : : defaultRippleAnimation . hideDuration , this , [ this ] {
auto caption = lang ( lng_theme_editor_choose_name ) ;
auto filter = " Themes (*.tdesktop-theme) " ;
auto name = " awesome.tdesktop-theme " ;
2018-06-04 15:35:11 +00:00
FileDialog : : GetWritePath ( this , caption , filter , name , crl : : guard ( this , [ this ] ( const QString & path ) {
2017-02-03 20:07:26 +00:00
zlib : : FileToWrite zip ;
zip_fileinfo zfi = { { 0 , 0 , 0 , 0 , 0 , 0 } , 0 , 0 , 0 } ;
auto background = std : : string ( _tileBackground - > checked ( ) ? " tiled " : " background " ) + ( _isPng ? " .png " : " .jpg " ) ;
zip . openNewFile ( background . c_str ( ) , & zfi , nullptr , 0 , nullptr , 0 , nullptr , Z_DEFLATED , Z_DEFAULT_COMPRESSION ) ;
zip . writeInFile ( _backgroundContent . constData ( ) , _backgroundContent . size ( ) ) ;
zip . closeFile ( ) ;
auto scheme = " colors.tdesktop-theme " ;
zip . openNewFile ( scheme , & zfi , nullptr , 0 , nullptr , 0 , nullptr , Z_DEFLATED , Z_DEFAULT_COMPRESSION ) ;
zip . writeInFile ( _paletteContent . constData ( ) , _paletteContent . size ( ) ) ;
zip . closeFile ( ) ;
zip . close ( ) ;
if ( zip . error ( ) ! = ZIP_OK ) {
LOG ( ( " Theme Error: could not export zip-ed theme, status: %1 " ) . arg ( zip . error ( ) ) ) ;
Ui : : show ( Box < InformBox > ( lang ( lng_theme_editor_error ) ) ) ;
return ;
}
auto result = zip . result ( ) ;
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 > ( lang ( lng_theme_editor_error ) ) ) ;
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 > ( lang ( lng_theme_editor_error ) ) ) ;
return ;
}
Ui : : hideLayer ( ) ;
Ui : : Toast : : Show ( lang ( lng_theme_editor_done ) ) ;
} ) ) ;
} ) ;
}
Editor : : Editor ( QWidget * , const QString & path )
2018-09-11 19:00:23 +00:00
: _scroll ( this , st : : themesScroll )
2017-02-03 20:07:26 +00:00
, _close ( this , st : : contactsMultiSelect . fieldCancel )
2017-05-30 15:21:05 +00:00
, _select ( this , st : : contactsMultiSelect , langFactory ( lng_country_ph ) )
2017-02-03 20:07:26 +00:00
, _leftShadow ( this )
, _topShadow ( this )
, _export ( this , lang ( lng_theme_editor_export_button ) . toUpper ( ) , st : : dialogsUpdateButton ) {
_inner = _scroll - > setOwnedWidget ( object_ptr < Inner > ( this , path ) ) ;
_export - > setClickedCallback ( _inner - > exportCallback ( ) ) ;
_inner - > setErrorCallback ( [ this ] {
Ui : : show ( Box < InformBox > ( lang ( lng_theme_editor_error ) ) ) ;
// 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 ] {
App : : CallDelayed ( 2 * st : : boxDuration , this , [ this ] { _select - > setInnerFocus ( ) ; } ) ;
} ) ;
_inner - > setScrollCallback ( [ this ] ( int top , int bottom ) {
_scroll - > scrollToY ( top , bottom ) ;
} ) ;
_close - > setClickedCallback ( [ this ] { closeEditor ( ) ; } ) ;
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 ) ;
}
void Editor : : resizeEvent ( QResizeEvent * e ) {
_export - > resizeToWidth ( width ( ) ) ;
_close - > moveToRight ( 0 , 0 ) ;
_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 ) ;
auto scrollSize = QSize ( width ( ) , height ( ) - shadowTop - _export - > height ( ) ) ;
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 ( ) ) ;
}
_export - > moveToLeft ( 0 , _scroll - > y ( ) + _scroll - > height ( ) ) ;
}
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 ) ;
p . drawTextLeft ( st : : themeEditorMargin . left ( ) , st : : themeEditorMargin . top ( ) , width ( ) , lang ( lng_theme_editor_title ) ) ;
}
2017-02-07 17:39:47 +00:00
void Editor : : Start ( ) {
2018-07-19 14:58:40 +00:00
const auto path = Background ( ) - > themeAbsolutePath ( ) ;
if ( path . isEmpty ( ) | | ! Window : : Theme : : IsPaletteTestingPath ( path ) ) {
const auto start = [ ] ( const QString & path ) {
2017-02-07 17:39:47 +00:00
if ( ! Local : : copyThemeColorsToPalette ( path ) ) {
writeDefaultPalette ( path ) ;
}
if ( ! Apply ( path ) ) {
Ui : : show ( Box < InformBox > ( lang ( lng_theme_editor_error ) ) ) ;
return ;
}
KeepApplied ( ) ;
if ( auto window = App : : wnd ( ) ) {
window - > showRightColumn ( Box < Editor > ( path ) ) ;
}
2018-07-19 14:58:40 +00:00
} ;
FileDialog : : GetWritePath (
App : : wnd ( ) ,
lang ( lng_theme_editor_save_palette ) ,
" Palette (*.tdesktop-palette) " ,
" colors.tdesktop-palette " ,
start ) ;
2017-02-07 17:39:47 +00:00
} else if ( auto window = App : : wnd ( ) ) {
2018-07-19 14:58:40 +00:00
window - > showRightColumn ( Box < Editor > ( path ) ) ;
2017-02-03 20:07:26 +00:00
}
}
void Editor : : closeEditor ( ) {
if ( auto window = App : : wnd ( ) ) {
window - > showRightColumn ( nullptr ) ;
}
}
} // namespace Theme
} // namespace Window