initial import
[vym] / file.cpp
1 #include <QDir>
2 #include <QMessageBox>
3 #include <QPixmap>
4 #include <QLabel>
5 #include <QTextStream>
6 #include <iostream>
7 #include <QUrl>
8
9 #include "file.h"
10 #include "process.h"
11
12 #if defined(Q_OS_WIN32)
13 #include "mkdtemp.h"
14 #include <windows.h>
15 #endif
16
17 QString maskPath(QString p)
18 {
19         // Change " " to "\ " to enable blanks in filenames
20         p=p.replace(QChar('&'),"\\&");
21         return p.replace(QChar(' '),"\\ ");
22 }
23
24 QString convertToRel (const QString &src, const QString &dst)
25 {
26         QString s=src;
27         QString d=dst;
28         int i;
29
30         if (s==d) 
31         {
32                 // Special case, we just need the name of the file,
33                 // not the complete path
34                 i=d.findRev ("/");
35                 d=d.right (d.length()-i-1);
36         } else
37         {
38                 // Find relative path from src to dst
39
40                 // Remove the first "/"
41                 if (s.section ("/",0,0).isEmpty()) 
42                 {
43                         s=s.right (s.length()-1);
44                         d=d.right (d.length()-1);
45                 }
46                 
47                 // remove identical left parts
48                 while (s.section("/",0,0) == d.section("/",0,0) ) 
49                 {
50                         i=s.find ("/");
51                         s=s.right (s.length()-i-1);
52                         d=d.right (d.length()-i-1);
53                 }
54
55                 // Now take care of paths where we have to go back first
56                 int srcsep=s.count("/");
57                 int dstsep=d.count("/");
58                 if (srcsep <=  dstsep )
59                 {
60                         // find path to go up first and then back to dst
61                         i=1;
62                         while (i<=srcsep) 
63                         {
64                                 d="../"+d;
65                                 i++;
66                         }       
67                 }
68         }       
69         return d;
70 }
71
72 #include <QFileDialog>
73 extern QString vymName;
74 extern QDir lastFileDir;
75
76 QString browseDirectory (QWidget *parent,const QString &caption)
77 {
78         QFileDialog fd(parent,caption);
79         fd.setMode (QFileDialog::DirectoryOnly);
80         fd.setCaption(vymName+ " - "+caption);
81         fd.setDir (lastFileDir);
82         fd.show();
83         
84         if ( fd.exec() == QDialog::Accepted )
85                 return fd.selectedFile();
86         else
87                 return "";
88 }
89
90
91
92 bool reallyWriteDirectory(const QString &dir)
93 {
94         QStringList eList = QDir(dir).entryList();
95         if (eList.first() ==".")  eList.pop_front();    // remove "."
96         if (eList.first() =="..") eList.pop_front();    // remove "."
97         if (!eList.isEmpty())
98         {
99                 QMessageBox mb( vymName,
100                         QObject::tr("The directory %1 is not empty.\nDo you risk to overwrite its contents?","write directory").arg(dir),
101                 QMessageBox::Warning,
102                 QMessageBox::Yes ,
103                 QMessageBox::Cancel | QMessageBox::Default,
104                 QMessageBox::NoButton );
105
106                 mb.setButtonText( QMessageBox::Yes, QObject::tr("Overwrite") );
107                 mb.setButtonText( QMessageBox::No, QObject::tr("Cancel"));
108                 switch( mb.exec() ) 
109                 {
110                         case QMessageBox::Yes:
111                                 // save 
112                                 return true;
113                         case QMessageBox::Cancel:
114                                 // do nothing
115                                 return false;
116                 }
117         }
118         return true;
119 }
120
121 QString makeTmpDir (bool &ok, QString prefix)
122 {
123         bool b;
124         QString path=makeUniqueDir (b,QDir::tempPath()+"/"+prefix+"-XXXXXX");
125         ok=b;
126         return path;
127 }
128
129 bool isInTmpDir(QString fn)
130 {
131         QString temp=QDir::tempPath();
132         int l=temp.length();
133         return fn.left(l)==temp;
134 }
135
136 QString makeUniqueDir (bool &ok,QString s)
137 {
138         // Create unique directory e.g. for s="/tmp/vym-XXXXXX"
139
140         // Convert Separators
141         s=QDir::convertSeparators(s);
142
143         // Convert QString to string 
144         ok=true;
145         char *p;
146         int bytes=s.length();
147         p=(char*) malloc (bytes+1);
148         int i;
149         for (i=0;i<bytes;i++)
150                 p[i]=s.at(i).latin1();
151         p[bytes]=0;     
152
153         QString r=mkdtemp (p);
154         if (r.isEmpty()) ok=false;
155         free (p);
156         return r;
157 }
158
159 void removeDir(QDir d)
160 {
161         // This check should_ not be necessary, but proved to be useful ;-)
162         if (!isInTmpDir(d.path()))
163         {
164                 qWarning ("file.cpp::removeDir should remove "+d.path()+" - aborted.");
165                 return;
166         }
167
168         // Traverse directories
169         d.setFilter( QDir::Dirs| QDir::Hidden | QDir::NoSymLinks );
170         QFileInfoList list = d.entryInfoList();
171         QFileInfo fi;
172
173         for (int i = 0; i < list.size(); ++i) 
174         {
175                 fi=list.at(i);
176                 if (fi.fileName() != "." && fi.fileName() != ".." )
177                 {
178                         if ( !d.cd(fi.fileName()) ) 
179                                 qWarning ("removeDir() cannot find the directory "+fi.fileName());
180                         else 
181                         {
182                                 // Recursively remove subdirs
183                                 removeDir (d);
184                                 d.cdUp();
185                         }
186                 }       
187         }
188
189         // Traverse files
190         d.setFilter( QDir::Files| QDir::Hidden | QDir::NoSymLinks );
191         list = d.entryInfoList();
192
193         for (int i = 0; i < list.size(); ++i) 
194         {
195                 fi=list.at(i);
196                 QFile (fi.filePath()).remove(); 
197         }       
198
199         if (!d.rmdir(d.path()))
200                 qWarning ("removeDir("+d.path()+") failed!");
201 }               
202
203 void copyDir (QDir src, QDir dst)
204 {
205         system ("cp -r "+src.path()+"/* "+dst.path());
206
207         /*
208         ErrorCode err=success;
209
210         Process *cpProc=new Process ();
211         QStringList args;
212         cpProc->setWorkingDirectory (src.path());
213         args <<"-r";
214         args <<src.path();
215         args <<dst.path();
216
217         cpProc->start ("cp",args);
218         if (!cpProc->waitForStarted() )
219         {       
220                 // zip could not be started
221                 QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
222                                            QObject::tr("Couldn't start zip to compress data."));
223                 err=aborted;
224         } else
225         {
226                 // zip could be started
227                 cpProc->waitForFinished();
228                 if (cpProc->exitStatus()!=QProcess::NormalExit )
229                 {
230                         QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
231                                                    QObject::tr("cp didn't exit normally")+
232                                                    "\n" + cpProc->getErrout());
233                         err=aborted;
234                 } else
235                 {
236                         if (cpProc->exitCode()>0)
237                         {
238                                 QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
239                                                    QString("cp exit code:  %1").arg(cpProc->exitCode() )+
240                                                    "\n" + cpProc->getErrout() );
241                                 err=aborted;
242                         }
243                 }
244         }       // cp could be started
245         */
246 }
247
248 void makeSubDirs (const QString &s)
249 {
250         QDir d(s);
251         d.mkdir(s);
252         d.mkdir ("images");     
253         d.mkdir ("flags");      
254 }
255
256 ErrorCode zipDir (const QDir &zipDir, const QString &zipName)
257 {
258         ErrorCode err=success;
259         
260         // zip the temporary directory
261         QStringList args;
262         Process *zipProc=new Process ();
263         zipProc->setWorkingDirectory (zipDir.path());
264         args <<"-r";
265         args <<zipName;
266         args <<".";
267
268         zipProc->start ("zip",args);
269         if (!zipProc->waitForStarted() )
270         {       
271                 // zip could not be started
272                 QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
273                                            QObject::tr("Couldn't start zip to compress data."));
274                 err=aborted;
275         } else
276         {
277                 // zip could be started
278                 zipProc->waitForFinished();
279                 if (zipProc->exitStatus()!=QProcess::NormalExit )
280                 {
281                         QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
282                                                    QObject::tr("zip didn't exit normally")+
283                                                    "\n" + zipProc->getErrout());
284                         err=aborted;
285                 } else
286                 {
287                         if (zipProc->exitCode()>0)
288                         {
289                                 QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
290                                                    QString("zip exit code:  %1").arg(zipProc->exitCode() )+
291                                                    "\n" + zipProc->getErrout() );
292                                 err=aborted;
293                         }
294                 }
295         }       // zip could be started
296         return err;     
297 }
298
299 ErrorCode unzipDir (const QDir &zipDir, const QString &zipName)
300 {
301         ErrorCode err=success;
302
303         // Try to unzip file
304 #if !defined(Q_OS_WIN32)
305         QStringList args;
306         Process *zipProc=new Process ();
307         zipProc->setWorkingDirectory (zipDir.path());
308         args << "-o";   // overwrite existing files!
309         args << zipName ;
310         args << "-d";
311         args << zipDir.path();
312
313         zipProc->start ("unzip",args);
314         if (!zipProc->waitForStarted() )
315         {
316                 QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
317                                            QObject::tr("Couldn't start unzip to decompress data."));
318                 err=aborted;
319                 
320         } else
321         {
322                 zipProc->waitForFinished();
323                 if (zipProc->exitStatus()!=QProcess::NormalExit )
324                 {
325                         QMessageBox::critical( 0,QObject::tr( "Critical Error" ),
326                                                    QObject::tr("unzip didn't exit normally") +
327                                                    zipProc->getErrout() );
328                         err=aborted;
329                 } else
330                 {
331                         if (zipProc->exitCode()>0)
332                         {
333                                 if (zipProc->exitCode()==9)
334                                         // no zipped file, but maybe .xml or old version? Try again.
335                                         err=nozip;
336                                 else    
337                                 {
338                                         QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
339                                                                    QString("unzip exit code:  %1").arg(zipProc->exitCode() ) +
340                                                                    zipProc->getErrout() );
341                                         err=aborted;
342                                 }
343                         } 
344                 }
345         }
346 #else
347     // Do this process creation using Win32 API.
348     //! Create process.
349     PROCESS_INFORMATION piProcInfo;
350     STARTUPINFO siStartInfo;
351
352     // Initialize members of the PROCESS_INFORMATION structure.
353     ::ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
354
355     // Set up members of the STARTUPINFO structure.
356     ::ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
357     siStartInfo.cb = sizeof(STARTUPINFO);
358
359     // Create command line.
360     QString argv("unzip -o ");
361     argv.append(QDir::convertSeparators(zipName));
362     argv.append(" -d ");
363     argv.append(QDir::convertSeparators(zipDir.path()));
364
365     // Create the child process.
366     if( !::CreateProcess(NULL, 
367         (LPWSTR)argv.unicode(), // command line
368         NULL, // process security attributes
369         NULL, // primary thread security attributes
370         TRUE, // handles are inherited
371         0, // creation flags
372         NULL, // use parent's environment
373         NULL, // use parent's current directory
374         &siStartInfo, // STARTUPINFO pointer
375         &piProcInfo) ) // receives PROCESS_INFORMATION
376     {
377         err = aborted;
378     }
379     else
380     {
381         // Wait for it to finish.
382         ::WaitForSingleObject( piProcInfo.hProcess, 10000 );
383     }
384 #endif
385         return err;     
386 }
387
388 bool loadStringFromDisk (const QString &fname, QString &s)
389 {
390         s="";
391         QFile file ( fname);
392         if ( !file.open( QIODevice::ReadOnly ) ) return false;
393
394         QTextStream ts( &file );
395         ts.setEncoding (QTextStream::UnicodeUTF8);
396         while ( !ts.atEnd() ) 
397                 s+=ts.readLine()+"\n"; 
398         file.close();
399         return true;
400 }
401
402 bool saveStringToDisk (const QString &fname, const QString &s)
403 {
404         QFile file( fname);
405
406         file.setName ( fname);
407         if ( !file.open( QIODevice::WriteOnly ) ) 
408         {
409                 file.close();
410                 return false;
411         }       
412
413         // Write it finally, and write in UTF8, no matter what 
414         QTextStream ts( &file );
415         ts.setEncoding (QTextStream::UnicodeUTF8);
416         ts << s;
417         file.close();
418         return true;
419 }
420
421
422 ImagePreview::ImagePreview (QWidget *par=0): QLabel (par)
423 {
424         fdia=(QFileDialog*)par;
425 }
426
427 void ImagePreview::previewUrl( const QUrl &u )
428 {
429     QString path = u.path();
430     QPixmap pix( path );
431     if ( pix.isNull() )
432         {
433                 // Strange: If we have fd->setMode (QFileDialog::ExistingFiles)
434                 // in the filedialog, then there are 3 calls to previewURL 
435                 // for each selection. And only the first is the actual selected file
436                 // while the following 2 point to the directory above the current one.
437                 // So here's my workaround:
438                 
439                 if (fdia && fdia->selectedFiles().count()==0)
440                         setText( QObject::tr("This is not an image.") );
441                 if (fdia &&fdia->selectedFiles().count()>1)
442                         setText( QObject::tr("Sorry, no preview for\nmultiple selected files.") );
443         }       
444     else
445         {
446                 float max_w=300;
447                 float max_h=300;
448                 float r;
449                 if (pix.width()>max_w)
450                 {
451                         r=max_w / pix.width();
452                         pix.resize(qRound(pix.width()*r), qRound(pix.height()*r));
453                         // TODO not a resize, but a shrink/enlarge is needed here...
454                 }
455                 if (pix.height()>max_h)
456                 {
457                         r=max_h / pix.height();
458                         pix.resize(qRound(pix.width()*r), qRound(pix.height()*r));
459                         // TODO not a resize, but a shrink/enlarge is needed here...
460                 }
461         setPixmap( pix );
462         }       
463 }
464
465 ImageIO::ImageIO ()
466 {
467         // Create list with supported image types
468         // foreach (QByteArray format, QImageWriter::supportedImageFormats()) 
469         // imageTypes.append( tr("%1...").arg(QString(format).toUpper()));
470         imageFilters.append ("Images (*.png *.jpg *.jpeg *.bmp *.bmp *.ppm *.xpm *.xbm)");
471         imageTypes.append ("PNG");
472         imageFilters.append ("Portable Network Graphics (*.png)");
473         imageTypes.append ("PNG");
474         imageFilters.append ("Joint Photographic Experts Group (*.jpg)");
475         imageTypes.append ("JPG");
476         imageFilters.append ("Joint Photographic Experts Group (*.jpeg)");
477         imageTypes.append ("JPG");
478         imageFilters.append ("Windows Bitmap (*.bmp)");
479         imageTypes.append ("BMP");
480         imageFilters.append ("Portable Pixmap (*.ppm)");
481         imageTypes.append ("PPM");
482         imageFilters.append ("X11 Bitmap (*.xpm)");
483         imageTypes.append ("XPM");
484         imageFilters.append ("X11 Bitmap (*.xbm)");
485         imageTypes.append ("XBM");
486 }
487
488 QStringList ImageIO::getFilters()
489 {
490         return imageFilters;
491 }
492
493 QString ImageIO::getType(QString filter)
494 {
495         for (int i=0;i<imageFilters.count()+1;i++)
496                 if (imageFilters.at(i)==filter) return imageTypes.at(i);
497         return QString();       
498 }
499
500