This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
a97dc47c3ad965c793346e5f4cb7365ce7a1fea1
[perl5.git] / symbian / PerlApp.cpp
1 /* Copyright (c) 2004-2005 Nokia. All rights reserved. */
2
3 /* The PerlApp application is licensed under the same terms as Perl itself. */
4
5 #include "PerlApp.h"
6
7 #include <avkon.hrh>
8 #include <aknnotewrappers.h> 
9 #include <apparc.h>
10 #include <e32base.h>
11 #include <e32cons.h>
12 #include <eikenv.h>
13 #include <bautils.h>
14 #include <eikappui.h>
15 #include <utf.h>
16 #include <f32file.h>
17
18 #include <AknCommonDialogs.h>
19
20 #ifndef __SERIES60_1X__
21 #include <CAknFileSelectionDialog.h>
22 #endif
23
24 #include <coemain.h>
25
26 #include "PerlApp.hrh"
27 #include "PerlApp.rsg"
28
29 #include "EXTERN.h"
30 #include "perl.h"
31 #include "PerlBase.h"
32
33 const TUid KPerlAppUid = { 0x102015F6 };
34
35 // This is like the Symbian _LIT() but without the embedded L prefix,
36 // which enables using #defined constants (which need to carry their
37 // own L prefix).
38 #ifndef _LIT_NO_L
39 #define _LIT_NO_L(n, s) static const TLitC<sizeof(s)/2> n={sizeof(s)/2-1,s}
40 #endif // #ifndef _LIT_NO_L
41
42 _LIT(KAppName, "PerlApp");
43 _LIT_NO_L(KFlavor, PERL_SYMBIANSDK_FLAVOR);
44 _LIT(KAboutFormat,
45      "Perl %d.%d.%d, Symbian port %d.%d.%d, built for %S SDK %d.%d");
46 _LIT(KCopyrightFormat,
47      "Copyright 1987-2005 Larry Wall and others, Symbian port Copyright Nokia 2004-2005");
48 _LIT(KInboxPrefix, "\\System\\Mail\\");
49 _LIT(KScriptPrefix, "\\Perl\\");
50
51 _LIT8(KModulePrefix, SITELIB); // SITELIB from Perl config.h
52
53 typedef TBuf<256>  TMessageBuffer;
54 typedef TBuf8<256> TPeekBuffer;
55 typedef TBuf8<256> TFileName8;
56
57 // Usage: DEBUG_PRINTF((_L("%S"), &aStr))
58 #if 1
59 #define DEBUG_PRINTF(s) {TMessageBuffer message; message.Format s; YesNoDialogL(message);}
60 #endif
61
62 TUid CPerlAppApplication::AppDllUid() const
63 {
64     return KPerlAppUid;
65 }
66
67 enum TPerlAppPanic 
68 {
69     EPerlAppCommandUnknown = 1
70 };
71
72 void Panic(TPerlAppPanic aReason)
73 {
74     User::Panic(KAppName, aReason);
75 }
76
77 void CPerlAppUi::ConstructL()
78 {
79     BaseConstructL();
80     iAppView = CPerlAppView::NewL(ClientRect());
81     AddToStackL(iAppView);
82     iFs = NULL;
83     CEikonEnv::Static()->DisableExitChecks(ETrue); // Symbian FAQ-0577.
84 }
85
86 CPerlAppUi::~CPerlAppUi()
87 {
88     if (iAppView) {
89         iEikonEnv->RemoveFromStack(iAppView);
90         delete iAppView;
91         iAppView = NULL;
92     }
93     if (iFs) {
94         delete iFs;
95         iFs = NULL;
96     }
97     if (iDoorObserver) // Otherwise the embedding application waits forever.
98         iDoorObserver->NotifyExit(MApaEmbeddedDocObserver::EEmpty);
99 }
100
101 static TBool DlgOk(CAknNoteDialog* dlg)
102 {
103     return dlg && dlg->RunDlgLD() == EAknSoftkeyOk;
104 }
105
106 static TBool OkCancelDialogL(TDesC& aMessage)
107 {
108     CAknNoteDialog* dlg =
109         new (ELeave) CAknNoteDialog(CAknNoteDialog::EConfirmationTone);
110     dlg->PrepareLC(R_OK_CANCEL_DIALOG);
111     dlg->SetTextL(aMessage);
112     return DlgOk(dlg);
113 }
114
115 static TBool YesNoDialogL(TDesC& aMessage)
116 {
117     CAknNoteDialog* dlg =
118         new (ELeave) CAknNoteDialog(CAknNoteDialog::EConfirmationTone);
119     dlg->PrepareLC(R_YES_NO_DIALOG);
120     dlg->SetTextL(aMessage);
121     return DlgOk(dlg);
122 }
123
124 static TInt InformationNoteL(TDesC& aMessage)
125 {
126     CAknInformationNote* note = new (ELeave) CAknInformationNote;
127     return note->ExecuteLD(aMessage);
128 }
129
130 static TInt ConfirmationNoteL(TDesC& aMessage)
131 {
132     CAknConfirmationNote* note = new (ELeave) CAknConfirmationNote;
133     return note->ExecuteLD(aMessage);
134 }
135
136 static TInt WarningNoteL(TDesC& aMessage)
137 {
138     CAknWarningNote* note = new (ELeave) CAknWarningNote;
139     return note->ExecuteLD(aMessage);
140 }
141
142 static TInt TextQueryDialogL(const TDesC& aPrompt, TDes& aData, const TInt aMaxLength)
143 {
144     CAknTextQueryDialog* dlg =
145         new (ELeave) CAknTextQueryDialog(aData);
146     dlg->SetPromptL(aPrompt);
147     dlg->SetMaxLength(aMaxLength);
148     return dlg->ExecuteLD(R_TEXT_QUERY_DIALOG);
149 }
150
151 // The isXXX() come from the Perl headers.
152 #define FILENAME_IS_ABSOLUTE(n) \
153         (isALPHA(((n)[0])) && ((n)[1]) == ':' && ((n)[2]) == '\\')
154
155 static TBool IsInPerl(TFileName aFileName)
156 {
157     TInt offset = aFileName.FindF(KScriptPrefix);
158     return ((offset == 0 && // \foo
159              aFileName[0] == '\\')
160             ||
161             (offset == 2 && // x:\foo
162              FILENAME_IS_ABSOLUTE(aFileName)));
163 }
164
165 static TBool IsInInbox(TFileName aFileName)
166 {
167     TInt offset = aFileName.FindF(KInboxPrefix);
168     return ((offset == 0 && // \foo
169              aFileName[0] == '\\')
170             ||
171             (offset == 2 && // x:\foo
172              FILENAME_IS_ABSOLUTE(aFileName)));
173 }
174
175 static TBool IsPerlModule(TParsePtrC aParsed)
176 {
177     return aParsed.Ext().CompareF(_L(".pm")) == 0; 
178 }
179
180 static TBool IsPerlScript(TParsePtrC aParsed)
181 {
182     return aParsed.Ext().CompareF(_L(".pl")) == 0; 
183 }
184
185 static void CopyFromInboxL(RFs aFs, const TFileName& aSrc, const TFileName& aDst)
186 {
187     TBool proceed = ETrue;
188     TMessageBuffer message;
189
190     message.Format(_L("%S is untrusted. Install only if you trust provider."), &aDst);
191     if (OkCancelDialogL(message)) {
192         message.Format(_L("Install as %S?"), &aDst);
193         if (OkCancelDialogL(message)) {
194             if (BaflUtils::FileExists(aFs, aDst)) {
195                 message.Format(_L("Replace old %S?"), &aDst);
196                 if (!OkCancelDialogL(message))
197                     proceed = EFalse;
198             }
199             if (proceed) {
200                 // Create directory?
201                 TInt err = BaflUtils::CopyFile(aFs, aSrc, aDst);
202                 if (err == KErrNone) {
203                     message.Format(_L("Installed %S"), &aDst);
204                     ConfirmationNoteL(message);
205                 }
206                 else {
207                     message.Format(_L("Failure %d installing %S"), err, &aDst);
208                     WarningNoteL(message);
209                 }
210             }
211         }
212     }
213 }
214
215 static TBool FindPerlPackageName(TPeekBuffer aPeekBuffer, TInt aOff, TFileName& aFn)
216 {
217     aFn.SetMax();
218     TInt m = aFn.MaxLength();
219     TInt n = aPeekBuffer.Length();
220     TInt i = 0;
221     TInt j = aOff;
222
223     aFn.SetMax();
224     // The following is a little regular expression
225     // engine that matches Perl package names.
226     if (j < n && isSPACE(aPeekBuffer[j])) {
227         while (j < n && isSPACE(aPeekBuffer[j])) j++;
228         if (j < n && isALPHA(aPeekBuffer[j])) {
229             while (j < n && isALNUM(aPeekBuffer[j])) {
230                 while (j < n &&
231                        isALNUM(aPeekBuffer[j]) &&
232                        i < m)
233                     aFn[i++] = aPeekBuffer[j++];
234                 if (j + 1 < n &&
235                     aPeekBuffer[j    ] == ':' &&
236                     aPeekBuffer[j + 1] == ':' &&
237                     i < m) {
238                     aFn[i++] = '\\';
239                     j += 2;
240                     if (j < n &&
241                         isALPHA(aPeekBuffer[j])) {
242                         while (j < n &&
243                                isALNUM(aPeekBuffer[j]) &&
244                                i < m) 
245                             aFn[i++] = aPeekBuffer[j++];
246                     }
247                 }
248             }
249             while (j < n && isSPACE(aPeekBuffer[j])) j++;
250             if (j < n && aPeekBuffer[j] == ';' && i + 3 < m) {
251                 aFn.SetLength(i);
252                 aFn.Append(_L(".pm"));
253                 return ETrue;
254             }
255         }
256     }
257     return EFalse;
258 }
259
260 static void GuessPerlModule(TFileName& aGuess, TPeekBuffer aPeekBuffer, TParse aDrive)
261 {
262    TInt offset = aPeekBuffer.Find(_L8("package"));
263    if (offset != KErrNotFound) {
264        const TInt KPackageLen = 7;
265        TFileName q;
266
267        if (!FindPerlPackageName(aPeekBuffer, offset + KPackageLen, q))
268            return;
269
270        TFileName8 p;
271        p.Copy(aDrive.Drive());
272        p.Append(KModulePrefix);
273
274        aGuess.SetMax();
275        if (p.Length() + 1 + q.Length() < aGuess.MaxLength()) {
276            TInt i = 0, j;
277
278            for (j = 0; j < p.Length(); j++)
279                aGuess[i++] = p[j];
280            aGuess[i++] = '\\';
281            for (j = 0; j < q.Length(); j++)
282                aGuess[i++] = q[j];
283            aGuess.SetLength(i);
284        }
285        else
286            aGuess.SetLength(0);
287    }
288 }
289
290 static TBool LooksLikePerlL(TPeekBuffer aPeekBuffer)
291 {
292     return aPeekBuffer.Left(2).Compare(_L8("#!")) == 0 &&
293            aPeekBuffer.Find(_L8("perl")) != KErrNotFound;
294 }
295
296 static TBool InstallStuffL(const TFileName &aSrc, TParse aDrive, TParse aFile, TPeekBuffer aPeekBuffer, RFs aFs)
297 {
298     TFileName aDst;
299     TPtrC drive  = aDrive.Drive();
300     TPtrC namext = aFile.NameAndExt(); 
301
302     aDst.Format(_L("%S%S%S"), &drive, &KScriptPrefix, &namext);
303     if (!IsPerlScript(aDst) && !LooksLikePerlL(aPeekBuffer)) {
304         aDst.SetLength(0);
305         if (IsPerlModule(aDst))
306             GuessPerlModule(aDst, aPeekBuffer, aDrive);
307     }
308     if (aDst.Length() > 0) {
309         CopyFromInboxL(aFs, aSrc, aDst);
310         return ETrue;
311     }
312
313     return EFalse;
314 }
315
316 static void DoRunScriptL(TFileName aScriptName)
317 {
318     CPerlBase* perl = CPerlBase::NewInterpreterLC();
319     TRAPD(error, perl->RunScriptL(aScriptName));
320     if (error != KErrNone) {
321         TMessageBuffer message;
322         message.Format(_L("Error %d"), error);
323         YesNoDialogL(message);
324     }
325     CleanupStack::PopAndDestroy(perl);
326 }
327
328 static TBool RunStuffL(const TFileName& aScriptName, TPeekBuffer aPeekBuffer)
329 {
330     TBool isModule = EFalse;
331
332     if (IsInPerl(aScriptName) &&
333         (IsPerlScript(aScriptName) ||
334          (isModule = IsPerlModule(aScriptName)) ||
335          LooksLikePerlL(aPeekBuffer))) {
336         TMessageBuffer message;
337
338         if (isModule)
339             message.Format(_L("Really run module %S?"), &aScriptName);
340         else 
341             message.Format(_L("Run %S?"), &aScriptName);
342         if (YesNoDialogL(message))
343             DoRunScriptL(aScriptName);
344
345         return ETrue;
346     }
347
348     return EFalse;
349 }
350
351 void CPerlAppUi::InstallOrRunL(const TFileName& aFileName)
352 {
353     TParse aFile;
354     TParse aDrive;
355     TMessageBuffer message;
356
357     aFile.Set(aFileName, NULL, NULL);
358     if (FILENAME_IS_ABSOLUTE(aFileName)) {
359         aDrive.Set(aFileName, NULL, NULL);
360     } else {
361         TFileName appName =
362           CEikonEnv::Static()->EikAppUi()->Application()->AppFullName();
363         aDrive.Set(appName, NULL, NULL);
364     }
365     if (!iFs)
366         iFs = &CEikonEnv::Static()->FsSession();
367     RFile f;
368     TInt err = f.Open(*iFs, aFileName, EFileRead);
369     if (err == KErrNone) {
370         TPeekBuffer aPeekBuffer;
371         err = f.Read(aPeekBuffer);
372         f.Close();  // Release quickly.
373         if (err == KErrNone) {
374             if (!(IsInInbox(aFileName) ?
375                   InstallStuffL(aFileName, aDrive, aFile, aPeekBuffer, *iFs) :
376                   RunStuffL(aFileName, aPeekBuffer))) {
377                 message.Format(_L("Failed for file %S"), &aFileName);
378                 WarningNoteL(message);
379             }
380         } else {
381             message.Format(_L("Error %d reading %S"), err, &aFileName);
382             WarningNoteL(message);
383         }
384     } else {
385         message.Format(_L("Error %d opening %S"), err, &aFileName);
386         WarningNoteL(message);
387     }
388     if (iDoorObserver)
389         delete CEikonEnv::Static()->EikAppUi();
390     else
391         Exit();
392 }
393
394 void CPerlAppUi::OpenFileL(const TDesC& aFileName)
395 {
396     InstallOrRunL(aFileName);
397     return;
398 }
399
400 TBool CPerlAppUi::ProcessCommandParametersL(TApaCommand aCommand, TFileName& /* aDocumentName */, const TDesC8& /* aTail */)
401 {
402     return aCommand == EApaCommandOpen ? ETrue : EFalse;
403 }
404
405 void CPerlAppUi::SetFs(const RFs& aFs)
406 {
407     iFs = (RFs*) &aFs;
408 }
409
410 void CPerlAppUi::HandleCommandL(TInt aCommand)
411 {
412     TMessageBuffer message;
413
414     switch(aCommand)
415     {
416     case EEikCmdExit:
417     case EAknSoftkeyExit:
418         Exit();
419         break;
420     case EPerlAppCommandAbout:
421         {
422             message.Format(KAboutFormat,
423                            PERL_REVISION,
424                            PERL_VERSION,
425                            PERL_SUBVERSION,
426                            PERL_SYMBIANPORT_MAJOR,
427                            PERL_SYMBIANPORT_MINOR,
428                            PERL_SYMBIANPORT_PATCH,
429                            &KFlavor,
430                            PERL_SYMBIANSDK_MAJOR,
431                            PERL_SYMBIANSDK_MINOR
432                            );
433             InformationNoteL(message);
434         }
435         break;
436     case EPerlAppCommandTime:
437         {
438             CPerlBase* perl = CPerlBase::NewInterpreterLC();
439             const char *const argv[] =
440               { "perl", "-le",
441                 "print 'Running in ', $^O, \"\\n\", scalar localtime" };
442             perl->ParseAndRun(sizeof(argv)/sizeof(char*), (char **)argv, 0);
443             CleanupStack::PopAndDestroy(perl);
444         }
445         break;
446      case EPerlAppCommandRunFile:
447         {
448             InformationNoteL(message);
449             TFileName aScriptUtf16;
450             if (AknCommonDialogs::RunSelectDlgLD(aScriptUtf16,
451                                                  R_MEMORY_SELECTION_DIALOG))
452                 DoRunScriptL(aScriptUtf16);
453         }
454         break;
455      case EPerlAppCommandOneLiner:
456         {
457             _LIT(prompt, "Oneliner:");
458             if (TextQueryDialogL(prompt, iOneLiner, KPerlAppOneLinerSize)) {
459                 const TUint KPerlAppUtf8Multi = 3;
460                 TBuf8<KPerlAppUtf8Multi * KPerlAppOneLinerSize> utf8;
461
462                 CnvUtfConverter::ConvertFromUnicodeToUtf8(utf8, iOneLiner);
463                 CPerlBase* perl = CPerlBase::NewInterpreterLC();
464                 int argc = 3;
465                 char **argv = (char**) malloc(argc * sizeof(char *));
466                 User::LeaveIfNull(argv);
467
468                 TCleanupItem argvCleanupItem = TCleanupItem(free, argv);
469                 CleanupStack::PushL(argvCleanupItem);
470                 argv[0] = (char *) "perl";
471                 argv[1] = (char *) "-le";
472                 argv[2] = (char *) utf8.PtrZ();
473                 perl->ParseAndRun(argc, argv);
474                 CleanupStack::PopAndDestroy(2, perl);
475             }
476         }
477         break;
478      case EPerlAppCommandCopyright:
479         {
480             message.Format(KCopyrightFormat);
481             InformationNoteL(message);
482         }
483         break;
484
485     default:
486         Panic(EPerlAppCommandUnknown);
487         break;
488     }
489 }
490
491 CPerlAppView* CPerlAppView::NewL(const TRect& aRect)
492 {
493     CPerlAppView* self = CPerlAppView::NewLC(aRect);
494     CleanupStack::Pop(self);
495     return self;
496 }
497
498 CPerlAppView* CPerlAppView::NewLC(const TRect& aRect)
499 {
500     CPerlAppView* self = new (ELeave) CPerlAppView;
501     CleanupStack::PushL(self);
502     self->ConstructL(aRect);
503     return self;
504 }
505
506 void CPerlAppView::ConstructL(const TRect& aRect)
507 {
508     CreateWindowL();
509     SetRect(aRect);
510     ActivateL();
511 }
512
513 void CPerlAppView::Draw(const TRect& /*aRect*/) const
514 {
515     CWindowGc& gc = SystemGc();
516     TRect rect = Rect();
517     gc.Clear(rect);
518 }
519
520 CApaDocument* CPerlAppApplication::CreateDocumentL() 
521 {
522     CPerlAppDocument* document = new (ELeave) CPerlAppDocument(*this);
523     return document;
524 }
525
526 CEikAppUi* CPerlAppDocument::CreateAppUiL()
527 {
528     CPerlAppUi* appui = new (ELeave) CPerlAppUi();
529     return appui;
530 }
531
532 CFileStore* CPerlAppDocument::OpenFileL(TBool /* aDoOpen */, const TDesC& aFileName, RFs& aFs)
533 {
534     CPerlAppUi* appui =
535       STATIC_CAST(CPerlAppUi*, CEikonEnv::Static()->EikAppUi());
536     appui->SetFs(aFs);
537     appui->OpenFileL(aFileName);
538     return NULL;
539 }
540
541 EXPORT_C CApaApplication* NewApplication() 
542 {
543     return new CPerlAppApplication;
544 }
545
546 GLDEF_C TInt E32Dll(TDllReason /*aReason*/)
547 {
548     return KErrNone;
549 }
550