1 //////////////////////////////////////////////////////////////////////
2 //
3 // BeeBEEP Copyright (C) 2010-2021 Marco Mastroddi
4 //
5 // BeeBEEP is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published
7 // by the Free Software Foundation, either version 3 of the License,
8 // or (at your option) any later version.
9 //
10 // BeeBEEP is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with BeeBEEP. If not, see <http://www.gnu.org/licenses/>.
17 //
18 // Author: Marco Mastroddi <marco.mastroddi(AT)gmail.com>
19 //
20 // $Id: BeeUtils.cpp 1469 2021-01-05 15:03:48Z mastroddi $
21 //
22 //////////////////////////////////////////////////////////////////////
23
24 #include "Avatar.h"
25 #include "BeeUtils.h"
26 #include "ChatMessage.h"
27 #include "IconManager.h"
28 #include "MessageManager.h"
29 #include "PluginManager.h"
30 #include "Protocol.h"
31 #include "User.h"
32 #include "Settings.h"
33 #if QT_VERSION < 0x050000
34 #ifdef Q_OS_WIN
35 #include <Windows.h>
36 #endif
37 #endif
38 #ifndef Q_OS_WIN
39 #include <utime.h>
40 #include <errno.h>
41 #endif
42
43
userStatusIconFileName(int user_status)44 QString Bee::userStatusIconFileName( int user_status )
45 {
46 switch( user_status )
47 {
48 case User::Offline:
49 return IconManager::instance().iconPath( "user-offline.png" );
50 case User::Away:
51 return IconManager::instance().iconPath( "user-away.png" );
52 case User::Busy:
53 return IconManager::instance().iconPath( "user-busy.png" );
54 default:
55 return IconManager::instance().iconPath( "user-online.png" );
56 }
57 }
58
menuUserStatusIconFileName(int user_status)59 QString Bee::menuUserStatusIconFileName( int user_status )
60 {
61 switch( user_status )
62 {
63 case User::Offline:
64 return IconManager::instance().iconPath( "menu-user-offline.png" );
65 case User::Away:
66 return IconManager::instance().iconPath( "menu-user-away.png" );
67 case User::Busy:
68 return IconManager::instance().iconPath( "menu-user-busy.png" );
69 default:
70 return IconManager::instance().iconPath( "menu-user-online.png" );
71 }
72 }
73
74 static const char* UserStatusToString[] =
75 {
76 QT_TRANSLATE_NOOP( "User", "offline" ),
77 QT_TRANSLATE_NOOP( "User", "available" ),
78 QT_TRANSLATE_NOOP( "User", "busy" ),
79 QT_TRANSLATE_NOOP( "User", "away" ),
80 QT_TRANSLATE_NOOP( "User", "status error" ),
81 };
82
userStatusToString(int user_status)83 QString Bee::userStatusToString( int user_status )
84 {
85 if( user_status < 0 || user_status > User::NumStatus )
86 user_status = User::NumStatus;
87 return qApp->translate( "User", UserStatusToString[ user_status ] );
88 }
89
userStatusColor(int user_status)90 QColor Bee::userStatusColor( int user_status )
91 {
92 switch( user_status )
93 {
94 case User::Online:
95 return QColor( Qt::green );
96 case User::Away:
97 return QColor( Qt::yellow );
98 case User::Busy:
99 return QColor( Qt::red );
100 case User::Offline:
101 return QColor( Qt::gray );
102 default:
103 return QColor( Qt::black );
104 }
105 }
106
userStatusBackgroundColor(int user_status)107 QColor Bee::userStatusBackgroundColor( int user_status )
108 {
109 switch( user_status )
110 {
111 case User::Online:
112 return defaultBackgroundBrush().color();
113 case User::Away:
114 return QColor( Qt::darkYellow );
115 case User::Busy:
116 return QColor( Qt::darkRed );
117 case User::Offline:
118 return QColor( Qt::gray );
119 default:
120 return defaultBackgroundBrush().color();
121 }
122 }
123
userStatusForegroundColor(int user_status)124 QColor Bee::userStatusForegroundColor( int user_status )
125 {
126 switch( user_status )
127 {
128 case User::Online:
129 return defaultBackgroundBrush().color();
130 case User::Away:
131 return QColor( Qt::white );
132 case User::Busy:
133 return QColor( Qt::white );
134 case User::Offline:
135 return QColor( Qt::black );
136 default:
137 return defaultBackgroundBrush().color();
138 }
139 }
140
userNameToShow(const User & u,bool to_html)141 QString Bee::userNameToShow( const User& u, bool to_html )
142 {
143 QString user_name = Settings::instance().useUserFullName() && u.vCard().hasFullName() ? u.vCard().fullName( Settings::instance().useUserFirstNameFirstInFullName() ) : u.name();
144 if( Settings::instance().appendHostNameToUserName() && !u.localHostName().isEmpty() )
145 user_name.append( QString( " [%1]" ).arg( u.localHostName() ) );
146 return to_html ? Bee::replaceHtmlSpecialCharacters( user_name ) : user_name;
147 }
148
uniqueFilePath(const QString & file_path,bool add_date_time)149 QString Bee::uniqueFilePath( const QString& file_path, bool add_date_time )
150 {
151 int counter = add_date_time ? 0 : 1;
152 QFileInfo fi( file_path );
153 QString dir_path = fi.absoluteDir().absolutePath();
154 QString file_base_name = fi.completeBaseName();
155 QString file_suffix = fi.suffix();
156 QString new_file_name;
157
158 while( fi.exists() )
159 {
160 new_file_name = QString( "%1%2%3%4" )
161 .arg( file_base_name )
162 .arg( add_date_time ? QString( "-%1" ).arg( QDateTime::currentDateTime().toString( "yyyyMMddHHmmss" ) ) : QString( "" ) )
163 .arg( counter > 0 ? ( add_date_time ? QString( "-%1" ).arg( counter ) : QString( " (%1)" ).arg( counter ) ) : QString( "" ) )
164 .arg( file_suffix.isEmpty() ? QString( "" ) : QString( ".%1" ) ).arg( file_suffix );
165 fi.setFile( dir_path, new_file_name );
166 counter++;
167
168 if( counter > 98 )
169 {
170 qWarning() << "Unable to find a unique file name from path" << file_path << "(so overwrite the last one)";
171 break;
172 }
173 }
174
175 return QDir::toNativeSeparators( fi.absoluteFilePath() );
176 }
177
suffixFromFile(const QString & file_path)178 QString Bee::suffixFromFile( const QString& file_path )
179 {
180 if( file_path.isEmpty() )
181 return "";
182 QStringList sl = file_path.split( "." );
183 if( sl.size() > 1 )
184 return sl.last();
185 else
186 return "";
187 }
188
isFileTypeAudio(const QString & file_suffix)189 bool Bee::isFileTypeAudio( const QString& file_suffix )
190 {
191 QString sx = file_suffix.toLower();
192 return sx == "mp3" || sx == "wav" || sx == "wma" || sx == "flac" || sx == "aiff" || sx == "aac" || sx == "m4a" || sx == "m4p" ||
193 sx == "ogg" || sx == "oga" || sx == "ra" || sx == "rm";
194 }
195
isFileTypeVideo(const QString & file_suffix)196 bool Bee::isFileTypeVideo( const QString& file_suffix )
197 {
198 QString sx = file_suffix.toLower();
199 return sx == "mpeg" || sx == "mpg" || sx == "mp4" || sx == "avi" || sx == "mkv" || sx == "wmv" || sx == "flv" || sx == "mov" ||
200 sx == "3gp" || sx == "mpe";
201 }
202
isFileTypeImage(const QString & file_suffix)203 bool Bee::isFileTypeImage( const QString& file_suffix )
204 {
205 QString sx = file_suffix.toLower();
206 return sx == "jpg" || sx == "jpeg" || sx == "gif" || sx == "bmp" || sx == "png" || sx == "tiff" || sx == "tif" || sx == "psd" ||
207 sx == "nef" || sx == "cr2" || sx == "dng" || sx == "dcr" || sx == "3fr" || sx == "raf" || sx == "orf" || sx == "pef" ||
208 sx == "arw" || sx == "svg" || sx == "ico" || sx == "ppm" || sx == "pgm" || sx == "pbm" || sx == "pnm" || sx == "webp";
209 }
210
isFileTypeDocument(const QString & file_suffix)211 bool Bee::isFileTypeDocument( const QString& file_suffix )
212 {
213 QString sx = file_suffix.toLower();
214 return sx == "pdf" || sx.startsWith( "doc" ) || sx.startsWith( "xls" ) || sx.startsWith( "ppt" ) || sx.startsWith( "pps" ) ||
215 sx == "rtf" || sx == "txt" || sx == "odt" || sx == "odp" || sx == "ods" || sx == "csv" || sx == "log" ||
216 sx == "mobi" || sx == "epub";
217 }
218
isFileTypeExe(const QString & file_suffix)219 bool Bee::isFileTypeExe( const QString& file_suffix )
220 {
221 QString sx = file_suffix.toLower();
222 return sx == "exe" || sx == "bat" || sx == "inf" || sx == "com" || sx == "sh" || sx == "cab" || sx == "cmd" || sx == "bin";
223 }
224
isFileTypeBundle(const QString & file_suffix)225 bool Bee::isFileTypeBundle( const QString& file_suffix )
226 {
227 QString sx = file_suffix.toLower();
228 return sx == "app" || sx == "dmg";
229 }
230
isFileTypeCompressed(const QString & file_suffix)231 bool Bee::isFileTypeCompressed( const QString& file_suffix )
232 {
233 QString sx = file_suffix.toLower();
234 return sx == "zip" || sx == "rar" || sx == "7z" || sx == "gz";
235 }
236
fileTypeFromSuffix(const QString & file_suffix)237 Bee::FileType Bee::fileTypeFromSuffix( const QString& file_suffix )
238 {
239 if( isFileTypeDocument( file_suffix ) )
240 return Bee::FileDocument;
241
242 if( isFileTypeImage( file_suffix ) )
243 return Bee::FileImage;
244
245 if( isFileTypeAudio( file_suffix ) )
246 return Bee::FileAudio;
247
248 if( isFileTypeVideo( file_suffix ) )
249 return Bee::FileVideo;
250
251 if( isFileTypeCompressed( file_suffix ) )
252 return Bee::FileCompressed;
253
254 if( isFileTypeExe( file_suffix ) )
255 return Bee::FileExe;
256
257 if( isFileTypeBundle( file_suffix ) )
258 return Bee::FileBundle;
259
260 return Bee::FileOther;
261 }
262
263 static const char* FileTypeToString[] =
264 {
265 QT_TRANSLATE_NOOP( "File", "Audio" ),
266 QT_TRANSLATE_NOOP( "File", "Video" ),
267 QT_TRANSLATE_NOOP( "File", "Image" ),
268 QT_TRANSLATE_NOOP( "File", "Document" ),
269 QT_TRANSLATE_NOOP( "File", "Other" ),
270 QT_TRANSLATE_NOOP( "File", "Executable" ),
271 QT_TRANSLATE_NOOP( "File", "MacOSX" ),
272 QT_TRANSLATE_NOOP( "File", "Compressed" )
273 };
274
fileTypeToString(Bee::FileType ft)275 QString Bee::fileTypeToString( Bee::FileType ft )
276 {
277 if( static_cast<int>( ft ) < 0 || ft > Bee::NumFileType )
278 ft = Bee::FileOther;
279 return qApp->translate( "File", FileTypeToString[ ft ] );
280 }
281
282 static const char* ChatMessageTypeToString[] =
283 {
284 QT_TRANSLATE_NOOP( "ChatMessage", "Header" ),
285 QT_TRANSLATE_NOOP( "ChatMessage", "System" ),
286 QT_TRANSLATE_NOOP( "ChatMessage", "Chat" ),
287 QT_TRANSLATE_NOOP( "ChatMessage", "Connection" ),
288 QT_TRANSLATE_NOOP( "ChatMessage", "User Information" ),
289 QT_TRANSLATE_NOOP( "ChatMessage", "File Transfer" ),
290 QT_TRANSLATE_NOOP( "ChatMessage", "History" ),
291 QT_TRANSLATE_NOOP( "ChatMessage", "Other" ),
292 QT_TRANSLATE_NOOP( "ChatMessage", "Image Preview" ),
293 QT_TRANSLATE_NOOP( "ChatMessage", "Autoresponder" ),
294 QT_TRANSLATE_NOOP( "ChatMessage", "Voice message" )
295 };
296
chatMessageTypeToString(int cmt)297 QString Bee::chatMessageTypeToString( int cmt )
298 {
299 if( cmt < 0 || cmt > ChatMessage::NumTypes )
300 cmt = ChatMessage::Other;
301 return qApp->translate( "ChatMessage", ChatMessageTypeToString[ cmt ] );
302 }
303
dateTimeStringSuffix(const QDateTime & dt)304 QString Bee::dateTimeStringSuffix( const QDateTime& dt )
305 {
306 QString s = dt.toString( "yyyyMMdd-hhmmss" );
307 return s;
308 }
309
capitalizeFirstLetter(const QString & txt,bool all_chars_after_space,bool lower_all_characters)310 QString Bee::capitalizeFirstLetter( const QString& txt, bool all_chars_after_space, bool lower_all_characters )
311 {
312 if( txt.isEmpty() )
313 return txt;
314
315 QString capitalized = "";
316 QString tmp = lower_all_characters ? txt.toLower() : txt;
317 if( all_chars_after_space )
318 {
319 QChar c;
320 bool apply_title_case = true;
321 for( int i = 0; i < tmp.size(); i++ )
322 {
323 c = tmp.at( i );
324 if( c.isSpace() )
325 {
326 capitalized += c;
327 apply_title_case = true;
328 }
329 else if( apply_title_case )
330 {
331 capitalized += c.toTitleCase();
332 apply_title_case = false;
333 }
334 else
335 capitalized += c;
336 }
337 }
338 else
339 {
340 tmp.remove( 0, 1 );
341 capitalized += txt.at( 0 ).toTitleCase();
342 capitalized += tmp;
343 }
344 return capitalized;
345 }
346
lowerFirstLetter(const QString & txt)347 QString Bee::lowerFirstLetter( const QString& txt )
348 {
349 if( !txt.isEmpty() && txt.at( 0 ).isUpper() )
350 return txt.at( 0 ).toLower() + txt.mid( 1 );
351 else
352 return txt;
353 }
354
invertColor(const QColor & c)355 QColor Bee::invertColor( const QColor& c )
356 {
357 int r, g, b;
358 c.getRgb( &r, &g, &b );
359 int i_r = 255 - r;
360 int i_g = 255 - g;
361 int i_b = 255 - b;
362
363 int s_r = r - i_r;
364 int s_g = g - i_g;
365 int s_b = b - i_b;
366
367 if( qAbs( s_r ) < 30 && qAbs( s_g ) < 30 && qAbs( s_b ) < 30 ) // gray on gray
368 return QColor( 0, 0, 0 );
369 else
370 return QColor( i_r, i_g, i_b );
371 }
372
isColorNear(const QColor & c1,const QColor & c2)373 bool Bee::isColorNear( const QColor& c1, const QColor& c2 )
374 {
375 int r_diff = c1.red() - c2.red();
376 int g_diff = c1.green() - c2.green();
377 int b_diff = c1.blue() - c2.blue();
378
379 return qAbs( r_diff ) < 30 && qAbs( g_diff ) < 30 && qAbs( b_diff ) < 30;
380 }
381
removeHtmlTags(const QString & s)382 QString Bee::removeHtmlTags( const QString& s )
383 {
384 QTextDocument text_document;
385 text_document.setHtml( s );
386 return text_document.toPlainText();
387 }
388
defaultTextBrush()389 QBrush Bee::defaultTextBrush()
390 {
391 return qApp->palette().text();
392 }
393
defaultBackgroundBrush()394 QBrush Bee::defaultBackgroundBrush()
395 {
396 return QBrush( Qt::transparent );
397 }
398
defaultHighlightedTextBrush()399 QBrush Bee::defaultHighlightedTextBrush()
400 {
401 return qApp->palette().highlightedText();
402 }
403
defaultHighlightBrush()404 QBrush Bee::defaultHighlightBrush()
405 {
406 return qApp->palette().highlight();
407 }
408
userStatusBackgroundBrush(int user_status)409 QBrush Bee::userStatusBackgroundBrush( int user_status )
410 {
411 if( user_status == User::Away || user_status == User::Busy )
412 return QBrush( userStatusColor( user_status ) );
413 else
414 return defaultBackgroundBrush();
415 }
416
convertToGrayScale(const QIcon & icon_to_convert,const QSize & pixmap_size)417 QPixmap Bee::convertToGrayScale( const QIcon& icon_to_convert, const QSize& pixmap_size )
418 {
419 return convertToGrayScale( icon_to_convert.pixmap( pixmap_size ) );
420 }
421
convertToGrayScale(const QPixmap & pix)422 QPixmap Bee::convertToGrayScale( const QPixmap& pix )
423 {
424 QImage img = pix.toImage();
425 if( img.isNull() )
426 return QPixmap();
427
428 int pixels = img.width() * img.height();
429 if( pixels*static_cast<int>( sizeof( QRgb ) ) <= img.byteCount() )
430 {
431 QRgb *data = reinterpret_cast<QRgb*>( img.bits() );
432 for (int i = 0; i < pixels; i++)
433 {
434 int val = qGray(data[i]);
435 data[i] = qRgba(val, val, val, qAlpha(data[i]));
436 }
437 }
438
439 QPixmap ret_pix;
440
441 #if QT_VERSION < 0x040700
442 ret_pix = QPixmap::fromImage( img );
443 #else
444 ret_pix.convertFromImage( img );
445 #endif
446 return ret_pix;
447 }
448
nativeFolderSeparator()449 QChar Bee::nativeFolderSeparator()
450 {
451 return QDir::separator();
452 }
453
convertToNativeFolderSeparator(const QString & raw_path)454 QString Bee::convertToNativeFolderSeparator( const QString& raw_path )
455 {
456 QString path_converted( raw_path );
457 QChar from_char;
458 #if defined( Q_OS_WIN ) || defined( Q_OS_OS2 ) || defined( Q_OS_OS2EMX ) || defined( Q_OS_SYMBIAN )
459 from_char = QLatin1Char( '/' );
460 #else
461 from_char = QLatin1Char( '\\' );
462 #endif
463 QChar to_char = QDir::separator();
464
465 for( int i = 0; i < path_converted.length(); i++ )
466 {
467 if( path_converted[ i ] == from_char )
468 path_converted[ i ] = to_char;
469 }
470 // Do not remove "double slash" because some paths can start with them
471 // Remove trailing slash
472 if( path_converted.endsWith( QDir::separator() ) )
473 path_converted.chop( 1 );
474 return path_converted;
475 }
476
folderCdUp(const QString & folder_path)477 QString Bee::folderCdUp( const QString& folder_path )
478 {
479 QStringList sl = Bee::convertToNativeFolderSeparator( folder_path ).split( nativeFolderSeparator() );
480 if( sl.isEmpty() )
481 return folder_path;
482
483 sl.removeLast();
484 return sl.join( nativeFolderSeparator() );
485 }
486
setLastModifiedToFile(const QString & to_path,const QDateTime & dt_last_modified)487 bool Bee::setLastModifiedToFile( const QString& to_path, const QDateTime& dt_last_modified )
488 {
489 QFileInfo file_info_to( to_path );
490 if( !file_info_to.exists() )
491 {
492 qWarning() << "Unable to set last modidified time to not existing file" << qPrintable( to_path );
493 return false;
494 }
495
496 uint mod_time = dt_last_modified.toTime_t();
497 uint ac_time = dt_last_modified.toTime_t();
498
499 if( mod_time == (uint)-1 || ac_time == (uint)-1 )
500 {
501 qWarning() << "Unable to set invalid last modidified time to file" << qPrintable( to_path );
502 return false;
503 }
504
505 bool ok = false;
506
507 #ifdef Q_OS_WIN
508
509 uint cr_time = mod_time;
510
511 FILETIME ft_modified, ft_creation, ft_access;
512
513 LPCWSTR to_file_name = (const WCHAR*)to_path.utf16();
514
515 HANDLE h_file = ::CreateFileW( to_file_name, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL );
516
517 if( h_file != INVALID_HANDLE_VALUE )
518 {
519 LONGLONG ll = Int32x32To64( cr_time, 10000000) + 116444736000000000;
520 ft_creation.dwLowDateTime = (DWORD) ll;
521 ft_creation.dwHighDateTime = ll >> 32;
522 LONGLONG ll2 = Int32x32To64( mod_time, 10000000) + 116444736000000000;
523 ft_modified.dwLowDateTime = (DWORD) ll2;
524 ft_modified.dwHighDateTime = ll2 >> 32;
525 LONGLONG ll3 = Int32x32To64( ac_time, 10000000) + 116444736000000000;
526 ft_access.dwLowDateTime = (DWORD) ll3;
527 ft_access.dwHighDateTime = ll3 >> 32;
528
529 if( !::SetFileTime( h_file, &ft_creation, &ft_access, &ft_modified ) )
530 {
531 QString s_error = QString( "0x%1" ).arg( (unsigned long)GetLastError() );
532 qWarning() << "Function SetFileTime has error" << s_error << "for file" << qPrintable( to_path );
533 }
534 else
535 ok = true;
536 }
537 else
538 qWarning() << "Unable to get the HANDLE (CreateFile) of file" << qPrintable( to_path );
539
540 CloseHandle( h_file );
541
542 #else
543
544 struct utimbuf from_time_buffer;
545 from_time_buffer.modtime = mod_time;
546 from_time_buffer.actime = ac_time;
547
548 const char *to_file_name = to_path.toUtf8().constData();
549 ok = utime( to_file_name, &from_time_buffer ) == 0;
550 if( !ok )
551 qWarning() << "Function utime error" << errno << ":" << qPrintable( QString::fromLatin1( strerror( errno ) ) ) << "for file" << qPrintable( to_path );
552
553 #endif
554
555 return ok;
556 }
557
showFileInGraphicalShell(const QString & file_path)558 bool Bee::showFileInGraphicalShell( const QString& file_path )
559 {
560 QFileInfo file_info( file_path );
561 if( !file_info.exists() )
562 return false;
563
564 #ifdef Q_OS_WIN
565 QString explorer_path = QLatin1String( "c:\\windows\\explorer.exe" );
566
567 if( QFile::exists( explorer_path ) )
568 {
569 QStringList explorer_args;
570 if( !file_info.isDir() )
571 explorer_args += QLatin1String("/select,");
572 explorer_args += Bee::convertToNativeFolderSeparator( file_info.canonicalFilePath() );
573 if( QProcess::startDetached( explorer_path, explorer_args ) )
574 return true;
575 else
576 qWarning() << "Unable to start process:" << qPrintable( explorer_path ) << qPrintable( explorer_args.join( " " ) );
577 }
578 #endif
579
580 #ifdef Q_OS_MAC
581
582 QStringList script_args;
583 script_args << QLatin1String( "-e" )
584 << QString::fromLatin1( "tell application \"Finder\" to reveal POSIX file \"%1\"" ).arg( file_info.canonicalFilePath() );
585 QProcess::execute( QLatin1String( "/usr/bin/osascript" ), script_args );
586 script_args.clear();
587 script_args << QLatin1String( "-e" )
588 << QLatin1String( "tell application \"Finder\" to activate" );
589 QProcess::execute( QLatin1String( "/usr/bin/osascript" ), script_args );
590 return true;
591
592 #endif
593
594 return false;
595 }
596
folderIsWriteable(const QString & folder_path,bool create_folder_if_not_exists)597 bool Bee::folderIsWriteable( const QString& folder_path, bool create_folder_if_not_exists )
598 {
599 QDir folder_to_test( folder_path );
600 if( !folder_to_test.exists() )
601 {
602 if( create_folder_if_not_exists )
603 return folder_to_test.mkpath( "." );
604 else
605 return false;
606 }
607
608 QFile test_file( QString( "%1/%2" ).arg( folder_path ).arg( "beetestfile.txt" ) );
609 if( test_file.open( QFile::WriteOnly ) )
610 {
611 bool ok = test_file.write( QByteArray( "BeeBEEP" ) ) > 0;
612 test_file.close();
613 return ok && test_file.remove();
614 }
615 else
616 return false;
617 }
618
GetBoxSize(int pix_size)619 static int GetBoxSize( int pix_size )
620 {
621 int box_size = pix_size > 10 ? pix_size / 10 : 1;
622 if( box_size % 2 > 7 )
623 box_size++;
624 box_size = qMax( 1, box_size );
625 return box_size;
626 }
627
avatarForUser(const User & u,const QSize & avatar_size,bool use_available_user_image,int user_status)628 QPixmap Bee::avatarForUser( const User& u, const QSize& avatar_size, bool use_available_user_image, int user_status )
629 {
630 QPixmap user_avatar;
631 bool default_avatar_used = false;
632 if( user_status < 0 )
633 user_status = u.status();
634 if( u.vCard().photo().isNull() || !use_available_user_image )
635 {
636 default_avatar_used = true;
637 Avatar av;
638 av.setName( u.isValid() ? (Settings::instance().useUserFullName() && u.vCard().hasFullName() ? u.vCard().fullName( Settings::instance().useUserFirstNameFirstInFullName() ) : u.name()) : "??" );
639 if( u.isStatusConnected() )
640 av.setColor( u.color() );
641 else
642 av.setColor( QColor( Qt::gray ).name() );
643 av.setSize( avatar_size );
644 if( !av.create() )
645 {
646 user_avatar = QIcon( Bee::menuUserStatusIconFileName( user_status ) ).pixmap( avatar_size );
647 return user_avatar;
648 }
649 else
650 user_avatar = av.pixmap();
651 }
652 else
653 {
654 user_avatar = u.vCard().photo().scaled( avatar_size );
655 if( !u.isStatusConnected() )
656 {
657 user_avatar = convertToGrayScale(user_avatar );
658 return user_avatar;
659 }
660 }
661
662 int pix_height = user_avatar.height();
663 int pix_width = user_avatar.width();
664 int box_height = GetBoxSize( pix_height );
665 int box_width = GetBoxSize( pix_width );
666 int box_start_height = qMax( 1, box_height / 2 );
667 int box_start_width = qMax( 1, box_width / 2 );
668
669 QPixmap pix( pix_width, pix_height );
670 QPainter p( &pix );
671 if( !default_avatar_used )
672 {
673 pix.fill( Bee::userStatusColor( user_status ) );
674 p.drawPixmap( box_start_width, box_start_height, pix_width - box_width, pix_height - box_height, user_avatar.scaled( pix_width - box_width, pix_height - box_height ) );
675 }
676 else
677 {
678 p.drawPixmap( 0, 0, pix_width, pix_height, user_avatar );
679 p.setPen( Bee::userStatusColor( user_status ) );
680 for( int i = 0; i < box_height; i++ )
681 {
682 p.drawLine( 0, i, pix_width, i );
683 p.drawLine( 0, pix_height-box_height+i, pix_width, pix_height-box_height+i );
684 }
685
686 for( int i = 0; i < box_width; i++ )
687 {
688 p.drawLine( i, 0, i, pix_height );
689 p.drawLine( pix_width-box_width+i, 0, pix_width-box_width+i, pix_height );
690 }
691 }
692
693 return pix;
694 }
695
toolTipForUser(const User & u,bool only_status)696 QString Bee::toolTipForUser( const User& u, bool only_status )
697 {
698 QString tool_tip = u.isLocal() ? QObject::tr( "You are %1" ).arg( Bee::userStatusToString( u.status() ) ) : QObject::tr( "%1 is %2" ).arg( Bee::userNameToShow( u, false ), Bee::userStatusToString( u.status() ) );
699
700 if( only_status )
701 return tool_tip;
702
703 if( !u.vCard().birthday().isNull() )
704 {
705 QString text = userBirthdayToText( u );
706 if( !text.isEmpty() )
707 tool_tip += QString( "\n* %1 *" ).arg( text );
708 }
709
710 if( u.isStatusConnected() )
711 {
712 if( !u.statusDescription().isEmpty() )
713 tool_tip += QString( "\n%1" ).arg( u.statusDescription() );
714
715 if( !u.workgroups().isEmpty() )
716 tool_tip += QString( "\n%1: %2" ).arg( QObject::tr( "Workgroups" ) ).arg( Bee::stringListToTextString( u.workgroups(), true, 9 ) );
717
718 if( !u.vCard().info().isEmpty() )
719 {
720 tool_tip += QString( "\n~~~\n" );
721 tool_tip += u.vCard().info();
722 tool_tip += QString( "\n~~~" );
723 }
724
725 if( u.statusChangedIn().isValid() )
726 tool_tip += QString( "\n%1: %2" ).arg( QObject::tr( "Last update" ) ).arg( Bee::dateTimeToString( u.statusChangedIn() ) );
727 }
728 else
729 {
730 if( u.lastConnection().isValid() )
731 tool_tip += QString( "\n%1: %2" ).arg( QObject::tr( "Last connection" ) ).arg( Bee::dateTimeToString( u.lastConnection() ) );
732
733 int unsent_messages = MessageManager::instance().countMessagesToSendToUserId( u.id() );
734 if( unsent_messages > 0 )
735 tool_tip += QString( "\n%1" ).arg( QObject::tr( "%n unsent message(s)", "", unsent_messages ) );
736 }
737 tool_tip += QString( "\n" );
738 return tool_tip;
739 }
740
userBirthdayToText(const User & u)741 QString Bee::userBirthdayToText( const User& u )
742 {
743 QString birthday_text;
744 if( !u.isLocal() )
745 {
746 // Do not use Bee::userNameToShow( u ) to avoid computer name
747 QString user_name = Settings::instance().useUserFullName() && u.vCard().hasFullName() ? u.vCard().fullName( Settings::instance().useUserFirstNameFirstInFullName() ) : u.name();
748 user_name = Bee::removeHtmlTags( user_name );
749 user_name = Bee::replaceHtmlSpecialCharacters( user_name );
750 int days_to = u.daysToBirthDay();
751 if( days_to == 0 )
752 birthday_text = QObject::tr( "Today is %1's birthday" ).arg( user_name );
753 else if( days_to == 1 )
754 birthday_text = QObject::tr( "Tomorrow is %1's birthday" ).arg( user_name );
755 else if( days_to > 1 && days_to < 4 )
756 birthday_text= QObject::tr( "%1's birthday is in %2 days" ).arg( user_name ).arg( days_to );
757 else if( days_to == -1 )
758 birthday_text = QObject::tr( "Yesterday was %1's birthday" ).arg( user_name );
759 else
760 birthday_text = "";
761 }
762 else
763 {
764 if( u.isBirthDay() )
765 birthday_text = QObject::tr( "Happy Birthday to you!" );
766 else
767 birthday_text = "";
768 }
769 return birthday_text;
770 }
771
stringListToTextString(const QStringList & sl,bool strip_html_tags,int max_items)772 QString Bee::stringListToTextString( const QStringList& sl, bool strip_html_tags, int max_items )
773 {
774 QStringList sl_parsed;
775 foreach( QString s, sl )
776 {
777 if( !s.isEmpty() )
778 {
779 if( strip_html_tags )
780 sl_parsed.append( Bee::removeHtmlTags( s ) );
781 else
782 sl_parsed.append( s );
783 }
784 }
785
786 if( sl_parsed.isEmpty() )
787 return "";
788 if( sl_parsed.size() == 1 )
789 return sl_parsed.first();
790 if( sl_parsed.size() == 2 )
791 return sl_parsed.join( QString( " %1 " ).arg( QObject::tr( "and" ) ) );
792
793 QStringList sl_to_join;
794 if( max_items < 1 || max_items > (sl_parsed.size()-1))
795 max_items = sl_parsed.size()-1;
796 int num_items = 0;
797
798 foreach( QString s, sl_parsed )
799 {
800 if( num_items >= max_items )
801 break;
802 num_items++;
803 if( strip_html_tags )
804 sl_to_join.append( Bee::removeHtmlTags( s ) );
805 else
806 sl_to_join.append( s );
807 }
808
809 QString s_joined = sl_to_join.join( ", " );
810 int diff_items = sl_parsed.size() - sl_to_join.size();
811 if( diff_items == 1 )
812 {
813 if( !sl_parsed.last().isEmpty() )
814 {
815 s_joined.append( QString( " %1 " ).arg( QObject::tr( "and" ) ) );
816 s_joined.append( sl_parsed.last() );
817 }
818 }
819 else
820 s_joined.append( QString( " %1" ).arg( QObject::tr( "and %1 others" ).arg( diff_items ) ) );
821
822 return s_joined;
823 }
824
removeContextHelpButton(QWidget * w)825 void Bee::removeContextHelpButton( QWidget* w )
826 {
827 Qt::WindowFlags w_flags = w->windowFlags();
828 w_flags &= ~Qt::WindowContextHelpButtonHint;
829 w->setWindowFlags( w_flags );
830 }
831
showUp(QWidget * w)832 void Bee::showUp( QWidget* w )
833 {
834 if( !w->isVisible() )
835 w->show();
836
837 if( w->isMinimized() )
838 w->showNormal();
839
840 w->raise();
841 }
842
raiseOnTop(QWidget * w)843 void Bee::raiseOnTop( QWidget* w )
844 {
845 #if defined( Q_OS_WIN )
846 Bee::showUp( w );
847 if( !(w->windowFlags() & Qt::WindowStaysOnTopHint) )
848 {
849 ::SetWindowPos( (HWND)w->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
850 ::SetWindowPos( (HWND)w->winId(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
851 }
852 #elif defined( Q_OS_LINUX ) && QT_VERSION >= 0x050000
853 Bee::showUp( w );
854 bool on_top_flag_added = false;
855 if( !(w->windowFlags() & Qt::WindowStaysOnTopHint) )
856 {
857 Bee::setWindowStaysOnTop( w, true );
858 on_top_flag_added = true;
859 w->raise();
860 }
861 if( on_top_flag_added )
862 Bee::setWindowStaysOnTop( w, false );
863 #else
864 bool on_top_flag_added = false;
865 if( !(w->windowFlags() & Qt::WindowStaysOnTopHint) )
866 {
867 Bee::setWindowStaysOnTop( w, true );
868 on_top_flag_added = true;
869 }
870 Bee::showUp( w );
871 if( on_top_flag_added )
872 Bee::setWindowStaysOnTop( w, false );
873 #endif
874 }
875
setWindowStaysOnTop(QWidget * w,bool enable)876 void Bee::setWindowStaysOnTop( QWidget* w, bool enable )
877 {
878 bool w_was_visible = w->isVisible();
879 if( w_was_visible )
880 w->hide();
881
882 Qt::WindowFlags w_flags = w->windowFlags();
883 if( enable )
884 w_flags |= Qt::WindowStaysOnTopHint;
885 else
886 w_flags &= ~Qt::WindowStaysOnTopHint;
887 w->setWindowFlags( w_flags );
888
889 if( w_was_visible )
890 QMetaObject::invokeMethod( w, "show", Qt::QueuedConnection );
891 }
892
areStringListEqual(const QStringList & sl1,const QStringList & sl2,Qt::CaseSensitivity cs)893 bool Bee::areStringListEqual( const QStringList& sl1, const QStringList& sl2, Qt::CaseSensitivity cs )
894 {
895 if( sl1.size() != sl2.size() )
896 return false;
897
898 foreach( QString s, sl1 )
899 {
900 if( !sl2.contains( s, cs ) )
901 return false;
902 }
903 return true;
904 }
905
dateTimeToString(const QDateTime & dt)906 QString Bee::dateTimeToString( const QDateTime& dt )
907 {
908 return dt.date() == QDate::currentDate() ? dt.time().toString( Qt::SystemLocaleShortDate ) : dt.toString( Qt::SystemLocaleShortDate );
909 }
910
setBackgroundColor(QWidget * w,const QColor & c)911 void Bee::setBackgroundColor( QWidget* w, const QColor& c )
912 {
913 QPalette pal = w->palette();
914 pal.setBrush( QPalette::Base, QBrush( c ) );
915 w->setPalette( pal );
916 }
917
selectColor(QWidget * w,const QColor & default_color)918 QColor Bee::selectColor( QWidget* w, const QColor& default_color )
919 {
920 return QColorDialog::getColor( default_color, w );
921 }
922
pluginFileExtension()923 QString Bee::pluginFileExtension()
924 {
925 #if defined Q_OS_MAC
926 return QLatin1String( "dylib" );
927 #elif defined Q_OS_UNIX
928 return QLatin1String( "so" );
929 #else
930 return QLatin1String( "dll" );
931 #endif
932 }
933
removeInvalidCharactersForFilePath(const QString & s)934 QString Bee::removeInvalidCharactersForFilePath( const QString& s )
935 {
936 QString valid_path = s.simplified();
937 valid_path.replace( QRegExp( "[" + QRegExp::escape( "\\/:*?\"<>|$[]+,;=" ) + "]" ), QString( "_" ) );
938 return valid_path;
939 }
940
replaceHtmlSpecialCharacters(const QString & s)941 QString Bee::replaceHtmlSpecialCharacters( const QString& s )
942 {
943 // It is different from Protocol::formatHtmlText(...)
944 QString text = s.trimmed();
945 QString html_text = "";
946 bool there_is_a_space_before_it = false;
947 QChar c;
948
949 for( int i = 0; i < text.length(); i++ )
950 {
951 c = text.at( i );
952 if( c == QLatin1Char( ' ' ) )
953 {
954 if( there_is_a_space_before_it )
955 html_text += QLatin1String( " " );
956 else
957 html_text += QLatin1Char( ' ' );
958 there_is_a_space_before_it = true;
959 }
960 else
961 {
962 there_is_a_space_before_it = false;
963 if( c == QLatin1Char( '\n' ) )
964 html_text += QLatin1String( "<br>" );
965 else if( c == QLatin1Char( '<' ) )
966 html_text += QLatin1String( "<" );
967 else if( c == QLatin1Char( '>' ) )
968 html_text += QLatin1String( ">" );
969 else if( c == QLatin1Char( '\t' ) )
970 html_text += QLatin1String( " " );
971 else
972 html_text += c;
973 }
974 }
975 return html_text;
976 }
977
roundFromDouble(double d)978 qint64 Bee::roundFromDouble( double d )
979 {
980 qint64 i = static_cast<qint64>( d );
981 return ((d - i) >= 0.5) ? ++i : i;
982 }
983
bytesPerSecond(FileSizeType transferred_byte_size,int time_elapsed_ms)984 qint64 Bee::bytesPerSecond( FileSizeType transferred_byte_size, int time_elapsed_ms )
985 {
986 double dtbs = static_cast<double>( transferred_byte_size );
987 double dte = static_cast<double>( time_elapsed_ms );
988 if( dtbs > 0 && dte > 0 )
989 return qMax( static_cast<qint64>(1), roundFromDouble( (dtbs*1000.0)/dte ) );
990 else
991 return 1;
992 }
993
bytesToString(FileSizeType bytes,int precision)994 QString Bee::bytesToString( FileSizeType bytes, int precision )
995 {
996 QString suffix;
997 double result = 0;
998 int prec = 1;
999 if( bytes > 1000000000000 )
1000 {
1001 suffix = "Tb";
1002 result = bytes / 1000000000000.0;
1003 prec = 5;
1004 }
1005 else if( bytes > 1000000000 )
1006 {
1007 suffix = "Gb";
1008 result = bytes / 1000000000.0;
1009 prec = 3;
1010 }
1011 else if( bytes > 1000000 )
1012 {
1013 suffix = "Mb";
1014 result = bytes / 1000000.0;
1015 prec = 2;
1016 }
1017 else if( bytes > 1000 )
1018 {
1019 suffix = "kb";
1020 result = bytes / 1000.0;
1021 prec = result >= 10 ? 0 : 1;
1022 }
1023 else
1024 {
1025 suffix = "b";
1026 result = bytes;
1027 prec = 0;
1028 }
1029 return QString( "%1 %2").arg( result, 0, 'f', prec > 0 ? (precision >= 0 ? precision : prec) : 0 ).arg( suffix );
1030 }
1031
timeToString(qint64 ms)1032 QString Bee::timeToString( qint64 ms )
1033 {
1034 if( ms < 0 )
1035 return QT_TRANSLATE_NOOP( "Not available", "n.a." );
1036 if( ms == 0 )
1037 return QString( "0 s" );
1038
1039 qint64 d = ms / 86400000;
1040 if( d > 0 )
1041 ms -= d*86400000;
1042
1043 QTime t = QTime( 0, 0, 0, 0 );
1044 t = t.addMSecs( static_cast<int>( ms ) );
1045 qint64 h = d * 24 + t.hour();
1046
1047 if( h < 24 )
1048 {
1049 if( h > 0 )
1050 return t.toString( "%1:mm:ss" ).arg( QString::number( h ).rightJustified( 2, QLatin1Char( '0' ) ) );
1051 else if( t.minute() > 0 )
1052 return t.toString( "m:ss" ) + QString( " m" );
1053 else if( t.second() > 0 )
1054 return QString( "%1 s" ).arg( (t.msec() > 500 && t.second() <= 59 ? t.second() + 1 : t.second()) );
1055 else
1056 return QString( "%1 ms" ).arg( t.msec() );
1057 }
1058 else
1059 {
1060 QStringList sl;
1061 if( d > 0 )
1062 sl.append( QString( "%1 d" ).arg( d ) );
1063 if( t.hour() > 0 )
1064 sl.append( QString( "%1 h" ).arg( t.hour() ) );
1065 if( t.minute() > 0 )
1066 sl.append( QString( "%1 m" ).arg( t.minute() ) );
1067 if( t.second() > 0 )
1068 sl.append( QString( "%1 s" ).arg( t.second() ) );
1069 if( sl.empty() )
1070 sl.append( QString( "%1 ms" ).arg( t.msec() ) );
1071 return sl.join( ", " );
1072 }
1073 }
1074
transferTimeLeft(FileSizeType bytes_transferred,FileSizeType total_bytes,FileSizeType starting_position,int elapsed_time)1075 QString Bee::transferTimeLeft( FileSizeType bytes_transferred, FileSizeType total_bytes, FileSizeType starting_position, int elapsed_time )
1076 {
1077 qint64 bytes_per_second;
1078 if( starting_position > 0 && bytes_transferred > starting_position )
1079 bytes_per_second = bytesPerSecond( bytes_transferred - starting_position, elapsed_time );
1080 else
1081 bytes_per_second = bytesPerSecond( bytes_transferred, elapsed_time );
1082 FileSizeType bytes_left = bytes_transferred >= total_bytes ? 0 : total_bytes - bytes_transferred;
1083 int ms_left = static_cast<int>( (bytes_left * 1000) / bytes_per_second );
1084 if( ms_left < 1000 )
1085 return timeToString( 1000 );
1086 else if( ms_left < 2000 )
1087 return timeToString( 2000 );
1088 else
1089 return timeToString( ms_left );
1090 }
1091
imagePreviewPath(const QString & source_image_path)1092 QString Bee::imagePreviewPath( const QString& source_image_path )
1093 {
1094 QFileInfo fi( source_image_path );
1095 if( fi.exists() && fi.isReadable() )
1096 {
1097 QString file_png_path = QString( "%1-%2.png" ).arg( "img" ).arg( Protocol::instance().fileInfoHash( fi ) );
1098 QString image_preview_path = Bee::convertToNativeFolderSeparator( QString( "%1/%2" ).arg( Settings::instance().cacheFolder() ).arg( file_png_path ) );
1099 if( QFile::exists( image_preview_path ) )
1100 {
1101 #ifdef BEEBEEP_DEBUG
1102 qDebug() << "Image preview of" << source_image_path << "cached in file" << qPrintable( image_preview_path );
1103 #endif
1104 return image_preview_path;
1105 }
1106 QImage img;
1107 QImageReader img_reader( source_image_path );
1108 img_reader.setAutoDetectImageFormat( true );
1109 if( img_reader.read( &img ) )
1110 {
1111 if( img.height() > Settings::instance().imagePreviewHeight() )
1112 {
1113 #ifdef BEEBEEP_DEBUG
1114 qDebug() << "Image preview scaled and saving to" << qPrintable( image_preview_path );
1115 #endif
1116 // PNG for transparency (always)
1117 QImage img_scaled = img.scaledToHeight( Settings::instance().imagePreviewHeight(), Qt::SmoothTransformation );
1118 if( img_scaled.save( image_preview_path, "png" ) )
1119 return image_preview_path;
1120 #ifdef BEEBEEP_DEBUG
1121 else
1122 qDebug() << "Unable to save scaled image preview to" << qPrintable( image_preview_path );
1123 #endif
1124 }
1125 else
1126 return source_image_path;
1127 }
1128 }
1129 return QString( "" );
1130 }
1131
comboBoxData(const QComboBox * box)1132 QVariant Bee::comboBoxData( const QComboBox* box )
1133 {
1134 int box_index = box->currentIndex();
1135 return box_index == -1 ? QVariant() : box->itemData( box_index );
1136 }
1137
selectComboBoxData(QComboBox * box,const QVariant & item_data)1138 bool Bee::selectComboBoxData( QComboBox* box, const QVariant& item_data )
1139 {
1140 int box_index = box->findData( item_data );
1141 if( box_index >= 0 )
1142 {
1143 box->setCurrentIndex( box_index );
1144 return true;
1145 }
1146 else
1147 return false;
1148 }
1149
colorDarkGrey()1150 QColor Bee::colorDarkGrey() { return QColor( 64, 64, 64 ); }
colorGrey()1151 QColor Bee::colorGrey() { return QColor( 128, 128, 128 ); }
colorBlack()1152 QColor Bee::colorBlack() { return QColor( 25, 25, 25 ); }
colorBlue()1153 QColor Bee::colorBlue() { return QColor( 42, 130, 218 ); }
colorWhite()1154 QColor Bee::colorWhite() { return QColor( 238, 238, 238 ); }
colorYellow()1155 QColor Bee::colorYellow() { return QColor( "#dede00" ); }
colorOrange()1156 QColor Bee::colorOrange() { return QColor( "#ffcf04" ); }
1157
1158
beeColorsToHtmlText(const QString & txt)1159 QString Bee::beeColorsToHtmlText( const QString& txt )
1160 {
1161 QString bee_txt = "";
1162 QString c_y = colorYellow().name();
1163 for( int i = 0; i < txt.size(); i++ )
1164 bee_txt.append( QString( "<font color=%1>%2</font>" ).arg( (i % 2 == 0) ? "#000000" : c_y ).arg( txt.at( i ) ) );
1165 return bee_txt;
1166 }
1167
darkPalette()1168 QPalette Bee::darkPalette()
1169 {
1170 QPalette darkPalette;
1171 darkPalette.setColor( QPalette::Window, colorGrey() );
1172 darkPalette.setColor( QPalette::WindowText, colorWhite() );
1173 darkPalette.setColor( QPalette::Base, colorDarkGrey() );
1174 darkPalette.setColor( QPalette::AlternateBase, colorDarkGrey().lighter() );
1175 darkPalette.setColor( QPalette::ToolTipBase, colorGrey().lighter() );
1176 darkPalette.setColor( QPalette::ToolTipText, colorBlack() );
1177 darkPalette.setColor( QPalette::Text, colorWhite() );
1178 darkPalette.setColor( QPalette::BrightText, Qt::yellow );
1179 darkPalette.setColor( QPalette::Button, colorDarkGrey() );
1180 darkPalette.setColor( QPalette::ButtonText, colorWhite() );
1181 darkPalette.setColor( QPalette::Link, colorBlue() );
1182 darkPalette.setColor( QPalette::LinkVisited, colorBlue().darker() );
1183 darkPalette.setColor( QPalette::Highlight, colorGrey() );
1184 darkPalette.setColor( QPalette::HighlightedText, colorBlack() );
1185
1186 darkPalette.setColor( QPalette::Active, QPalette::Button, colorGrey() );
1187 darkPalette.setColor( QPalette::Disabled, QPalette::ButtonText, colorGrey() );
1188 darkPalette.setColor( QPalette::Disabled, QPalette::WindowText, colorGrey() );
1189 darkPalette.setColor( QPalette::Disabled, QPalette::Text, colorDarkGrey() );
1190 darkPalette.setColor( QPalette::Disabled, QPalette::Light, colorGrey() );
1191
1192 return darkPalette;
1193 }
1194