1 #include "v1541commander.h"
2 #include "aboutbox.h"
3 #include "logwindow.h"
4 #include "mainwindow.h"
5 #include "petsciiwindow.h"
6 #include "petsciiedit.h"
7 #include "settings.h"
8 #include "settingsdialog.h"
9 
10 #include <QAction>
11 #include <QCryptographicHash>
12 #include <QFileDialog>
13 #include <QFileInfo>
14 #include <QFont>
15 #include <QFontDatabase>
16 #ifndef _WIN32
17 #include <QIcon>
18 #endif
19 #include <QLocalServer>
20 #include <QLocalSocket>
21 #include <QMessageBox>
22 #include <QPixmap>
23 #include <QSet>
24 #include <QScreen>
25 #include <QTranslator>
26 #include <QWindow>
27 #ifdef _WIN32
28 #include <windows.h>
29 #include <lmcons.h>
30 #else
31 #include <sys/types.h>
32 #include <pwd.h>
33 #include <unistd.h>
34 #endif
35 
36 #include <1541img/log.h>
37 
38 class V1541Commander::priv
39 {
40     public:
41         priv(V1541Commander *commander);
42         V1541Commander *commander;
43         QFont c64font;
44         QFont menufont;
45         QFont statusfont;
46         QPixmap statusLedRed;
47         QPixmap statusLedYellow;
48         QPixmap statusLedGreen;
49 #ifdef _WIN32
50         HICON appIcon;
51 #else
52 	QIcon appIcon;
53 #endif
54         QAction newAction;
55         QAction openAction;
56 	QAction saveAction;
57 	QAction saveAsAction;
58 	QAction exportZipcodeAction;
59 	QAction exportZipcodeD64Action;
60 	QAction exportLynxAction;
61         QAction closeAction;
62 	QAction settingsAction;
63 	QAction aboutAction;
64         QAction exitAction;
65         QAction petsciiWindowAction;
66 	QAction logWindowAction;
67 	QAction fsOptionsAction;
68 	QAction rewriteImageAction;
69 	QAction autoMapLcAction;
70 	QAction mapLcAction;
71 	QAction newFileAction;
72 	QAction deleteFileAction;
73         QAction lowerCaseAction;
74         QVector<MainWindow *> allWindows;
75         MainWindow *lastActiveWindow;
76         PetsciiWindow petsciiWindow;
77 	AboutBox aboutBox;
78 	LogWindow logWindow;
79 	SettingsDialog settingsDialog;
80 	QString instanceServerName;
81 	QLocalServer instanceServer;
82 	bool isPrimaryInstance;
83 	Settings settings;
84 	QSet<QLocalSocket *> activeClients;
85 
86         MainWindow *addWindow(bool show = true);
87         void removeWindow(MainWindow *w);
88 	void updateActions(MainWindow *w);
89 };
90 
priv(V1541Commander * commander)91 V1541Commander::priv::priv(V1541Commander *commander) :
92     commander(commander),
93     c64font("C64 Pro Mono", 10),
94     menufont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)),
95     statusfont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)),
96     statusLedRed(":/statusled_red.png"),
97     statusLedYellow(":/statusled_yellow.png"),
98     statusLedGreen(":/statusled_green.png"),
99     appIcon(),
100     newAction(tr("&New")),
101     openAction(tr("&Open")),
102     saveAction(tr("&Save")),
103     saveAsAction(tr("Save &As")),
104     exportZipcodeAction(tr("&Zipcode")),
105     exportZipcodeD64Action(tr("Zipcode (&D64)")),
106     exportLynxAction(tr("&LyNX")),
107     closeAction(tr("&Close")),
108     settingsAction(tr("Se&ttings")),
109     aboutAction(tr("&About")),
110     exitAction(tr("E&xit")),
111     petsciiWindowAction(tr("&PETSCII Input")),
112     logWindowAction(tr("lib1541img &log")),
113     fsOptionsAction(tr("Filesystem &Options")),
114     rewriteImageAction(tr("&Rewrite Image")),
115     autoMapLcAction(tr("&Auto map on input")),
116     mapLcAction(tr("&Map current disk")),
117     newFileAction(tr("&New File")),
118     deleteFileAction(tr("&Delete File")),
119     lowerCaseAction(tr("&Lowercase")),
120     allWindows(),
121     lastActiveWindow(0),
122     petsciiWindow(c64font),
123     aboutBox(c64font),
124     logWindow(),
125     settingsDialog(),
126     instanceServerName(),
127     instanceServer(),
128     isPrimaryInstance(false),
129     settings(),
130     activeClients()
131 {
132     newAction.setShortcuts(QKeySequence::New);
133     newAction.setStatusTip(tr("Create a new disk image"));
134     openAction.setShortcuts(QKeySequence::Open);
135     openAction.setStatusTip(tr("Open a disk image"));
136     saveAction.setShortcuts(QKeySequence::Save);
137     saveAction.setStatusTip(tr("Save disk image"));
138 #ifdef _WIN32
139     saveAsAction.setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_S));
140 #else
141     saveAsAction.setShortcuts(QKeySequence::SaveAs);
142 #endif
143     saveAsAction.setStatusTip(tr("Save disk image as new file"));
144     exportZipcodeAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Z));
145     exportZipcodeAction.setStatusTip(tr("Export as a set of Zipcode files"));
146     exportZipcodeD64Action.setShortcut(
147 	    QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Z));
148     exportZipcodeD64Action.setStatusTip(
149 	    tr("Export as Zipcode files on a new D64 image"));
150     exportLynxAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Y));
151     exportLynxAction.setStatusTip(tr("Export as a LyNX file"));
152     closeAction.setShortcuts(QKeySequence::Close);
153     closeAction.setStatusTip(tr("Close current file"));
154     settingsAction.setStatusTip(tr("Configure V1541Commander settings"));
155 #ifdef _WIN32
156     exitAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Q));
157 #else
158     exitAction.setShortcuts(QKeySequence::Quit);
159 #endif
160     exitAction.setStatusTip(tr("Exit the application"));
161     petsciiWindowAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_P));
162     petsciiWindowAction.setStatusTip(tr("Show PETSCII input window"));
163     logWindowAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_L));
164     logWindowAction.setStatusTip(tr("Show lib1541img log messages"));
165     fsOptionsAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_F));
166     fsOptionsAction.setStatusTip(tr("Change filesystem options"));
167     rewriteImageAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_R));
168     rewriteImageAction.setStatusTip(tr("Rewrite disk image from scratch"));
169     autoMapLcAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_M));
170     autoMapLcAction.setStatusTip(tr("Toggle automatic mapping of uppercase "
171 		"GFX chars to lowercase compatible codes"));
172     autoMapLcAction.setCheckable(true);
173     mapLcAction.setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_M));
174     mapLcAction.setStatusTip(tr("Map uppercase GFX chars to lowercase "
175 		"compatible codes in current disk"));
176     newFileAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Period));
177     newFileAction.setStatusTip(tr("Create new file at selection"));
178     deleteFileAction.setShortcut(QKeySequence::Delete);
179     deleteFileAction.setStatusTip(tr("Delete selected file"));
180     lowerCaseAction.setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Space));
181     lowerCaseAction.setStatusTip(tr("Toggle lowercase / graphics font mode"));
182     lowerCaseAction.setCheckable(true);
183 #ifdef _WIN32
184     HINSTANCE inst = GetModuleHandleW(0);
185     appIcon = LoadIcon(inst, MAKEINTRESOURCE(1000));
186     SetClassLong(HWND(aboutBox.winId()), GCL_HICON, (LONG)appIcon);
187     NONCLIENTMETRICSW ncm;
188     ncm.cbSize = sizeof ncm;
189     if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof ncm, &ncm, 0))
190     {
191         if (ncm.lfMessageFont.lfHeight < 0) ncm.lfMessageFont.lfHeight *= -1;
192         if (ncm.lfMenuFont.lfHeight < 0) ncm.lfMenuFont.lfHeight *= -1;
193         if (ncm.lfStatusFont.lfHeight < 0) ncm.lfStatusFont.lfHeight *= -1;
194         QFont sysfont(QString::fromWCharArray(ncm.lfMessageFont.lfFaceName));
195         sysfont.setPixelSize(ncm.lfMessageFont.lfHeight);
196         commander->setFont(sysfont);
197         QFont mfont(QString::fromWCharArray(ncm.lfMenuFont.lfFaceName));
198         mfont.setPixelSize(ncm.lfMenuFont.lfHeight);
199         menufont = mfont;
200         QFont sfont(QString::fromWCharArray(ncm.lfStatusFont.lfFaceName));
201         sfont.setPixelSize(ncm.lfStatusFont.lfHeight);
202         statusfont = sfont;
203     }
204 #else
205     appIcon.addPixmap(QPixmap(":/icon_256.png"));
206     appIcon.addPixmap(QPixmap(":/icon_48.png"));
207     appIcon.addPixmap(QPixmap(":/icon_32.png"));
208     appIcon.addPixmap(QPixmap(":/icon_16.png"));
209     logWindow.setWindowIcon(appIcon);
210     aboutBox.setWindowIcon(appIcon);
211     petsciiWindow.setWindowIcon(appIcon);
212     settingsDialog.setWindowIcon(appIcon);
213 #endif
214     QCryptographicHash appData(QCryptographicHash::Sha256);
215     appData.addData(QCoreApplication::organizationName().toUtf8());
216     appData.addData(QCoreApplication::applicationName().toUtf8());
217 #ifdef _WIN32
218     wchar_t username[UNLEN + 1];
219     DWORD usernameLen = UNLEN + 1;
220     if (GetUserNameW(username, &usernameLen))
221     {
222 	appData.addData(QString::fromWCharArray(username).toUtf8());
223     }
224     else
225     {
226 	appData.addData(qgetenv("USERNAME"));
227     }
228 #else
229     QByteArray username;
230     uid_t uid = geteuid();
231     struct passwd *pw = getpwuid(uid);
232     if (pw)
233     {
234 	username = pw->pw_name;
235     }
236     if (username.isEmpty())
237     {
238 	username = qgetenv("USER");
239     }
240     appData.addData(username);
241 #endif
242     instanceServerName = appData.result().toBase64().replace("/","_");
243     bool listening = instanceServer.listen(instanceServerName);
244 #ifdef _WIN32
245     if (listening)
246     {
247 	CreateMutexW(0, true,
248 		reinterpret_cast<LPCWSTR>(instanceServerName.utf16()));
249 	if (GetLastError() == ERROR_ALREADY_EXISTS)
250 	{
251 	    instanceServer.close();
252 	    listening = false;
253 	}
254     }
255 #else
256     if (!listening)
257     {
258 	QLocalSocket sock;
259 	sock.connectToServer(instanceServerName, QIODevice::WriteOnly);
260 	if (sock.state() != QLocalSocket::ConnectedState &&
261 		!sock.waitForConnected(2000))
262 	{
263 	    QLocalServer::removeServer(instanceServerName);
264 	    listening = instanceServer.listen(instanceServerName);
265 	}
266 	else
267 	{
268 	    sock.disconnectFromServer();
269 	}
270     }
271 #endif
272     isPrimaryInstance = listening;
273 }
274 
275 #ifdef _WIN32
stackWinPos(QPoint & pos,const QRect * screenRect)276 static void stackWinPos(QPoint &pos, const QRect *screenRect)
277 {
278     pos.setX(pos.x() + 64);
279     pos.setY(pos.y() + 32);
280     if (screenRect)
281     {
282 	if (pos.x() + 480 > screenRect->right()
283 		|| pos.y() + 120 > screenRect->bottom())
284 	{
285 	    pos.setX(screenRect->left());
286 	    pos.setY(screenRect->top());
287 	}
288     }
289 }
290 #endif
291 
addWindow(bool show)292 MainWindow *V1541Commander::priv::addWindow(bool show)
293 {
294 #ifdef _WIN32
295     MainWindow *newWin = new MainWindow();
296     MainWindow *lastWin = lastActiveWindow;
297     if (!lastWin && allWindows.count() > 0) lastWin = allWindows.first();
298     if (lastWin)
299     {
300 	const QScreen *screen = 0;
301 	QRect screenGeom;
302 	const QRect *screenRect = 0;
303 	const QWindow *winhndl = lastWin->windowHandle();
304 	if (winhndl)
305 	{
306 	    screen = winhndl->screen();
307 	}
308 	if (screen)
309 	{
310 	    screenGeom = screen->availableGeometry();
311 	    screenRect = &screenGeom;
312 	}
313         QPoint pos = lastWin->pos();
314 	stackWinPos(pos, screenRect);
315         bool posOk = false;
316         for (int i = 0; i < 20; ++i)
317         {
318             posOk = true;
319             for (QVector<MainWindow *>::const_iterator w = allWindows.cbegin();
320                     w != allWindows.cend(); ++w)
321             {
322                 if (pos == (*w)->pos())
323                 {
324                     posOk = false;
325                     break;
326                 }
327             }
328             if (posOk) break;
329 	    stackWinPos(pos, screenRect);
330         }
331         newWin->move(pos);
332     }
333     lastActiveWindow = newWin;
334 #else
335     lastActiveWindow = new MainWindow();
336     lastActiveWindow->setWindowIcon(appIcon);
337 #endif
338     if (show) lastActiveWindow->show();
339     allWindows.append(lastActiveWindow);
340     connect(lastActiveWindow, &MainWindow::activated,
341             commander, &V1541Commander::windowActivated);
342     connect(lastActiveWindow, &MainWindow::closed,
343             commander, &V1541Commander::windowClosed);
344     connect(lastActiveWindow, &MainWindow::contentChanged,
345             commander, &V1541Commander::windowContentChanged);
346     connect(lastActiveWindow, &MainWindow::modifiedChanged,
347             commander, &V1541Commander::windowContentChanged);
348     connect(lastActiveWindow, &MainWindow::selectionChanged,
349             commander, &V1541Commander::windowSelectionChanged);
350     return lastActiveWindow;
351 }
352 
removeWindow(MainWindow * w)353 void V1541Commander::priv::removeWindow(MainWindow *w)
354 {
355     if (w == lastActiveWindow) lastActiveWindow = 0;
356     allWindows.removeAll(w);
357     w->close();
358     if (allWindows.count() == 0)
359     {
360 	if (settings.rememberWindowPositions())
361 	{
362 	    settings.setMainGeometry(w->saveGeometry());
363 	    settings.setPetsciiGeometry(petsciiWindow.saveGeometry());
364 	    settings.setLogGeometry(logWindow.saveGeometry());
365 	}
366         closeAllWindows();
367     }
368     w->deleteLater();
369 }
370 
updateActions(MainWindow * w)371 void V1541Commander::priv::updateActions(MainWindow *w)
372 {
373     closeAction.setEnabled(w->content() != MainWindow::Content::None);
374     saveAction.setEnabled(w->hasValidContent() &&
375 	    (w->isWindowModified() || w->filename().isEmpty()));
376     saveAsAction.setEnabled(w->hasValidContent());
377     exportZipcodeAction.setEnabled(w->hasValidContent());
378     exportZipcodeD64Action.setEnabled(w->hasValidContent());
379     exportLynxAction.setEnabled(w->hasValidContent());
380     fsOptionsAction.setEnabled(w->content() == MainWindow::Content::Image
381 	    && w->hasValidContent());
382     rewriteImageAction.setEnabled(w->content() == MainWindow::Content::Image
383 	    && w->hasValidContent());
384     newFileAction.setEnabled(w->content() == MainWindow::Content::Image
385 	    && !w->isReadOnly());
386     mapLcAction.setEnabled(w->content() == MainWindow::Content::Image
387 	    && !w->isReadOnly());
388     deleteFileAction.setEnabled(w->content() == MainWindow::Content::Image
389 	    && !w->isReadOnly() && w->hasValidSelection());
390 }
391 
V1541Commander(int & argc,char ** argv,QTranslator * translator)392 V1541Commander::V1541Commander(int &argc, char **argv, QTranslator *translator)
393     : QApplication(argc, argv)
394 {
395 #ifdef DEBUG
396     setMaxLogLevel(L_DEBUG);
397 #endif
398     QFontDatabase::addApplicationFont(":/C64_Pro_Mono-STYLE.ttf");
399     installTranslator(translator);
400     d = new priv(this);
401     d->addWindow(false);
402     if (d->settings.rememberWindowPositions())
403     {
404 	d->lastActiveWindow->restoreGeometry(d->settings.mainGeometry());
405 	d->petsciiWindow.restoreGeometry(d->settings.petsciiGeometry());
406 	d->logWindow.restoreGeometry(d->settings.logGeometry());
407     }
408     d->lowerCaseAction.setChecked(d->settings.lowercase());
409     d->petsciiWindow.setLowercase(d->settings.lowercase());
410     d->autoMapLcAction.setChecked(d->settings.automapPetsciiToLc());
411     connect(&d->newAction, &QAction::triggered,
412 	    this, &V1541Commander::newImage);
413     connect(&d->openAction, SIGNAL(triggered()),
414 	    this, SLOT(open()));
415     connect(&d->saveAction, &QAction::triggered,
416 	    this, &V1541Commander::save);
417     connect(&d->saveAsAction, &QAction::triggered,
418 	    this, &V1541Commander::saveAs);
419     connect(&d->exportZipcodeAction, &QAction::triggered,
420 	    this, &V1541Commander::exportZipcode);
421     connect(&d->exportZipcodeD64Action, &QAction::triggered,
422 	    this, &V1541Commander::exportZipcodeD64);
423     connect(&d->exportLynxAction, &QAction::triggered,
424 	    this, &V1541Commander::exportLynx);
425     connect(&d->closeAction, &QAction::triggered,
426 	    this, &V1541Commander::close);
427     connect(&d->settingsAction, &QAction::triggered,
428 	    this, &V1541Commander::showSettings);
429     connect(&d->aboutAction, &QAction::triggered,
430 	    this, &V1541Commander::about);
431     connect(&d->exitAction, &QAction::triggered,
432 	    this, &V1541Commander::exit);
433     connect(&d->petsciiWindowAction, &QAction::triggered,
434             this, &V1541Commander::showPetsciiWindow);
435     connect(&d->logWindowAction, &QAction::triggered,
436             this, &V1541Commander::showLogWindow);
437     connect(&d->fsOptionsAction, &QAction::triggered,
438             this, &V1541Commander::fsOptions);
439     connect(&d->rewriteImageAction, &QAction::triggered,
440             this, &V1541Commander::rewriteImage);
441     connect(&d->mapLcAction, &QAction::triggered,
442 	    this, &V1541Commander::mapToLc);
443     connect(&d->newFileAction, &QAction::triggered,
444             this, &V1541Commander::newFile);
445     connect(&d->deleteFileAction, &QAction::triggered,
446             this, &V1541Commander::deleteFile);
447     connect(&d->petsciiWindow, &PetsciiWindow::petsciiInput,
448 	    this, &V1541Commander::petsciiInput);
449     connect(&d->lowerCaseAction, &QAction::triggered, this, [this](){
450 	    d->settings.setLowercase(!d->settings.lowercase());
451             emit lowerCaseChanged(d->settings.lowercase());
452             d->petsciiWindow.setLowercase(d->settings.lowercase());
453         });
454     connect(&d->autoMapLcAction, &QAction::triggered, this, [this](){
455 	    d->settings.setAutomapPetsciiToLc(
456 		    !d->settings.automapPetsciiToLc());
457 	    emit autoMapToLcChanged(d->settings.automapPetsciiToLc());
458 	});
459     connect(&d->logWindow, &LogWindow::logLineAppended,
460 	    this, &V1541Commander::logLineAppended);
461     if (d->isPrimaryInstance)
462     {
463 	connect(&d->instanceServer, &QLocalServer::newConnection,
464 		this, &V1541Commander::newConnection);
465     }
466 }
467 
~V1541Commander()468 V1541Commander::~V1541Commander()
469 {
470     delete d;
471 }
472 
sanitizeWindowRect(QRect * winRect,const QScreen * screen)473 static void sanitizeWindowRect(QRect *winRect, const QScreen *screen)
474 {
475     if (!screen) return;
476     QRect screenRect = screen->availableGeometry();
477     if (winRect->right() > screenRect.right())
478     {
479 	winRect->moveRight(screenRect.right());
480     }
481     if (winRect->bottom() > screenRect.bottom())
482     {
483 	winRect->moveBottom(screenRect.bottom());
484     }
485     if (winRect->top() < screenRect.top())
486     {
487 	winRect->moveTop(screenRect.top());
488     }
489     if (winRect->left() < screenRect.left())
490     {
491 	winRect->moveLeft(screenRect.left());
492     }
493 }
494 
sanitizeWindowPos(QWidget * window)495 static void sanitizeWindowPos(QWidget *window)
496 {
497     const QScreen *screen = 0;
498     const QWindow *currentWin = window->windowHandle();
499     if (currentWin)
500     {
501 	screen = currentWin->screen();
502     }
503     if (screen)
504     {
505 	QRect winRect = window->frameGeometry();
506 	sanitizeWindowRect(&winRect, screen);
507 	window->move(winRect.topLeft());
508     }
509 }
510 
show()511 void V1541Commander::show()
512 {
513     MainWindow *w = d->lastActiveWindow;
514     if (!w) return;
515 
516     w->show();
517     sanitizeWindowPos(w);
518 }
519 
newImage()520 void V1541Commander::newImage()
521 {
522     MainWindow *w = d->lastActiveWindow;
523     if (!w) return;
524 
525     if (w->content() != MainWindow::Content::None)
526     {
527 	w = d->addWindow();
528     }
529     w->newImage();
530     if (w->content() == MainWindow::Content::None)
531     {
532 	if (d->allWindows.count() > 1)
533 	{
534 	    d->removeWindow(w);
535 	}
536     }
537 }
538 
open(const QString & filename)539 void V1541Commander::open(const QString &filename)
540 {
541     MainWindow *w = d->lastActiveWindow;
542     if (!w || w->content() != MainWindow::Content::None)
543     {
544 	w = d->addWindow();
545     }
546     w->show();
547     w->raise();
548     w->activateWindow();
549 
550     w->openImage(filename);
551     if (w->content() == MainWindow::Content::None)
552     {
553 	QMessageBox::critical(w, tr("Error reading file"),
554 		tr("<p>The file you selected couldn't be read.</p>"
555 		    "<p>This means you either haven't permission to read "
556 		    "it or it doesn't contain a valid 1541 disc "
557 		    "image.</p>"));
558 	if (d->allWindows.count() > 1)
559 	{
560 	    d->removeWindow(w);
561 	}
562     }
563 }
564 
open()565 void V1541Commander::open()
566 {
567     MainWindow *w = d->lastActiveWindow;
568     if (!w) return;
569 
570     QString imgFile = QFileDialog::getOpenFileName(w, tr("Open disk image"),
571 	    QString(), tr("1541 disk images (*.d64);;"
572 		"Zipcode files (*!*.prg);;LyNX files (*.lnx);;all files (*)"));
573     if (!imgFile.isEmpty())
574     {
575 	open(imgFile);
576     }
577 }
578 
getFilterForWindowContent(MainWindow::Content content)579 static QString getFilterForWindowContent(MainWindow::Content content)
580 {
581     switch (content)
582     {
583 	case MainWindow::Content::Image:
584 	    return QCoreApplication::translate("V1541Commander",
585                     "1541 disk images (*.d64);;all files (*)");
586 	default:
587 	    return QCoreApplication::translate("V1541Commander",
588                     "all files (*)");
589     }
590 }
591 
save()592 void V1541Commander::save()
593 {
594     MainWindow *w = d->lastActiveWindow;
595     if (!w) return;
596 
597     if (w->filename().isEmpty()) saveAs();
598     else w->save();
599 }
600 
saveAs()601 void V1541Commander::saveAs()
602 {
603     MainWindow *w = d->lastActiveWindow;
604     if (!w) return;
605 
606     QString imgFile = QFileDialog::getSaveFileName(w, tr("Save as ..."),
607 	    QString(), getFilterForWindowContent(w->content()));
608     if (!imgFile.isEmpty())
609     {
610 	w->save(imgFile);
611     }
612 }
613 
exportZipcode()614 void V1541Commander::exportZipcode()
615 {
616     MainWindow *w = d->lastActiveWindow;
617     if (!w) return;
618 
619     QString filename = w->filename();
620     if (!filename.isEmpty())
621     {
622         filename = QFileInfo(filename).completeBaseName();
623     }
624     QString zcFile = QFileDialog::getSaveFileName(w, tr("Export as ..."),
625 	    filename, tr("Zipcode files (*.prg);;all files (*)"));
626 
627     if (!zcFile.isEmpty())
628     {
629 	w->exportZipcode(zcFile);
630     }
631 }
632 
exportZipcodeD64()633 void V1541Commander::exportZipcodeD64()
634 {
635     MainWindow *w = d->lastActiveWindow;
636     if (!w) return;
637     CbmdosVfs *vfs = w->exportZipcodeVfs();
638     if (vfs)
639     {
640 	w = d->addWindow();
641 	w->openVfs(vfs);
642     }
643 }
644 
exportLynx()645 void V1541Commander::exportLynx()
646 {
647     MainWindow *w = d->lastActiveWindow;
648     if (!w) return;
649 
650     QString filename = w->filename();
651     if (!filename.isEmpty())
652     {
653         filename = QFileInfo(filename).completeBaseName();
654     }
655     QString lynxFile = QFileDialog::getSaveFileName(w, tr("Export as ..."),
656 	    filename, tr("LyNX files (*.lnx);;all files (*)"));
657 
658     if (!lynxFile.isEmpty())
659     {
660 	w->exportLynx(lynxFile);
661     }
662 }
663 
close()664 void V1541Commander::close()
665 {
666     MainWindow *w = d->lastActiveWindow;
667     if (!w) return;
668 
669     w->closeDocument();
670     if (d->allWindows.count() > 1)
671     {
672         d->removeWindow(w);
673     }
674 }
675 
showSettings()676 void V1541Commander::showSettings()
677 {
678     MainWindow *w = d->lastActiveWindow;
679     if (!w) return;
680 
681     d->settingsDialog.show();
682     QRect settingsDialogRect = d->settingsDialog.frameGeometry();
683     QRect mainWinRect = w->frameGeometry();
684     settingsDialogRect.moveCenter(mainWinRect.center());
685 
686     const QScreen *screen = 0;
687     const QWindow *currentWin = w->windowHandle();
688     if (currentWin)
689     {
690 	screen = currentWin->screen();
691 	sanitizeWindowRect(&settingsDialogRect, screen);
692     }
693 
694     d->settingsDialog.move(settingsDialogRect.topLeft());
695     d->settingsDialog.activateWindow();
696     d->settingsDialog.raise();
697 }
698 
about()699 void V1541Commander::about()
700 {
701     MainWindow *w = d->lastActiveWindow;
702     if (!w) return;
703 
704     d->aboutBox.show();
705     QRect aboutBoxRect = d->aboutBox.frameGeometry();
706     QRect mainWinRect = w->frameGeometry();
707     aboutBoxRect.moveCenter(mainWinRect.center());
708 
709     const QScreen *screen = 0;
710     const QWindow *currentWin = w->windowHandle();
711     if (currentWin)
712     {
713 	screen = currentWin->screen();
714 	sanitizeWindowRect(&aboutBoxRect, screen);
715     }
716 
717     d->aboutBox.move(aboutBoxRect.topLeft());
718     d->aboutBox.activateWindow();
719     d->aboutBox.raise();
720 }
721 
exit()722 void V1541Commander::exit()
723 {
724     closeAllWindows();
725 }
726 
windowActivated()727 void V1541Commander::windowActivated()
728 {
729     MainWindow *w = static_cast<MainWindow *>(sender());
730     d->lastActiveWindow = w;
731     d->updateActions(w);
732 }
733 
windowClosed()734 void V1541Commander::windowClosed()
735 {
736     d->removeWindow(static_cast<MainWindow*>(sender()));
737 }
738 
windowContentChanged()739 void V1541Commander::windowContentChanged()
740 {
741     MainWindow *w = static_cast<MainWindow*>(sender());
742     if (w == d->lastActiveWindow) d->updateActions(w);
743 }
744 
windowSelectionChanged()745 void V1541Commander::windowSelectionChanged()
746 {
747     MainWindow *w = static_cast<MainWindow*>(sender());
748     if (w == d->lastActiveWindow) d->updateActions(w);
749 }
750 
showPetsciiWindow()751 void V1541Commander::showPetsciiWindow()
752 {
753     QRect geom = d->petsciiWindow.frameGeometry();
754     d->petsciiWindow.show();
755     d->petsciiWindow.move(geom.topLeft());
756     QCoreApplication::processEvents();
757     sanitizeWindowPos(&d->petsciiWindow);
758     d->petsciiWindow.raise();
759 }
760 
showLogWindow()761 void V1541Commander::showLogWindow()
762 {
763     QRect geom = d->logWindow.frameGeometry();
764     d->logWindow.show();
765     d->logWindow.move(geom.topLeft());
766     QCoreApplication::processEvents();
767     sanitizeWindowPos(&d->logWindow);
768     d->logWindow.raise();
769 }
770 
fsOptions()771 void V1541Commander::fsOptions()
772 {
773     MainWindow *w = d->lastActiveWindow;
774     if (!w) return;
775 
776     w->fsOptions();
777 }
778 
rewriteImage()779 void V1541Commander::rewriteImage()
780 {
781     MainWindow *w = d->lastActiveWindow;
782     if (!w) return;
783 
784     w->rewriteImage();
785 }
786 
mapToLc()787 void V1541Commander::mapToLc()
788 {
789     MainWindow *w = d->lastActiveWindow;
790     if (!w) return;
791 
792     w->mapToLc();
793 }
794 
newFile()795 void V1541Commander::newFile()
796 {
797     MainWindow *w = d->lastActiveWindow;
798     if (!w) return;
799 
800     w->newFile();
801 }
802 
deleteFile()803 void V1541Commander::deleteFile()
804 {
805     MainWindow *w = d->lastActiveWindow;
806     if (!w) return;
807 
808     w->deleteFile();
809 }
810 
logLineAppended(const QString & line)811 void V1541Commander::logLineAppended(const QString &line)
812 {
813     MainWindow *w = d->lastActiveWindow;
814     if (!w) return;
815 
816     w->showStatusLine(line);
817 }
818 
newConnection()819 void V1541Commander::newConnection()
820 {
821     QLocalSocket *conn = d->instanceServer.nextPendingConnection();
822     connect(conn, &QIODevice::readyRead, this, &V1541Commander::readyRead);
823     connect(conn, &QLocalSocket::disconnected,
824 	    this, &V1541Commander::disconnected);
825     QDataStream sendStream(conn);
826     sendStream << applicationPid();
827     conn->flush();
828 }
829 
disconnected()830 void V1541Commander::disconnected()
831 {
832     QLocalSocket *sock = qobject_cast<QLocalSocket *>(sender());
833     if (sock)
834     {
835 	if (!d->activeClients.contains(sock))
836 	{
837 	    MainWindow *w = d->lastActiveWindow;
838 	    if (!w) w = d->allWindows.first();
839 	    if (w)
840 	    {
841 		w->show();
842 		w->raise();
843 		w->activateWindow();
844 	    }
845 	    sock->deleteLater();
846 	}
847     }
848 }
849 
readyRead()850 void V1541Commander::readyRead()
851 {
852     QLocalSocket *sock = qobject_cast<QLocalSocket *>(sender());
853     if (sock)
854     {
855 	d->activeClients.insert(sock);
856 	QDataStream recvStream(sock);
857 	QString filename;
858 	for (;;)
859 	{
860 	    recvStream.startTransaction();
861 	    recvStream >> filename;
862 	    if (!recvStream.commitTransaction()) break;
863 	    open(filename);
864 	}
865 	d->activeClients.remove(sock);
866 	if (sock->state() != QLocalSocket::ConnectedState)
867 	{
868 	    sock->deleteLater();
869 	}
870     }
871 }
872 
petsciiInput(ushort val)873 void V1541Commander::petsciiInput(ushort val)
874 {
875     PetsciiEdit *pe = qobject_cast<PetsciiEdit *>(focusWidget());
876     if (pe) pe->petsciiInput(val);
877 }
878 
c64font() const879 const QFont &V1541Commander::c64font() const
880 {
881     return d->c64font;
882 }
883 
menufont() const884 const QFont &V1541Commander::menufont() const
885 {
886     return d->menufont;
887 }
888 
statusfont() const889 const QFont &V1541Commander::statusfont() const
890 {
891     return d->statusfont;
892 }
893 
statusLedRed() const894 const QPixmap &V1541Commander::statusLedRed() const
895 {
896     return d->statusLedRed;
897 }
898 
statusLedYellow() const899 const QPixmap &V1541Commander::statusLedYellow() const
900 {
901     return d->statusLedYellow;
902 }
903 
statusLedGreen() const904 const QPixmap &V1541Commander::statusLedGreen() const
905 {
906     return d->statusLedGreen;
907 }
908 
newAction()909 QAction &V1541Commander::newAction()
910 {
911     return d->newAction;
912 }
913 
openAction()914 QAction &V1541Commander::openAction()
915 {
916     return d->openAction;
917 }
918 
saveAction()919 QAction &V1541Commander::saveAction()
920 {
921     return d->saveAction;
922 }
923 
saveAsAction()924 QAction &V1541Commander::saveAsAction()
925 {
926     return d->saveAsAction;
927 }
928 
exportZipcodeAction()929 QAction &V1541Commander::exportZipcodeAction()
930 {
931     return d->exportZipcodeAction;
932 }
933 
exportZipcodeD64Action()934 QAction &V1541Commander::exportZipcodeD64Action()
935 {
936     return d->exportZipcodeD64Action;
937 }
938 
exportLynxAction()939 QAction &V1541Commander::exportLynxAction()
940 {
941     return d->exportLynxAction;
942 }
943 
closeAction()944 QAction &V1541Commander::closeAction()
945 {
946     return d->closeAction;
947 }
948 
settingsAction()949 QAction &V1541Commander::settingsAction()
950 {
951     return d->settingsAction;
952 }
953 
aboutAction()954 QAction &V1541Commander::aboutAction()
955 {
956     return d->aboutAction;
957 }
958 
exitAction()959 QAction &V1541Commander::exitAction()
960 {
961     return d->exitAction;
962 }
963 
petsciiWindowAction()964 QAction &V1541Commander::petsciiWindowAction()
965 {
966     return d->petsciiWindowAction;
967 }
968 
logWindowAction()969 QAction &V1541Commander::logWindowAction()
970 {
971     return d->logWindowAction;
972 }
973 
fsOptionsAction()974 QAction &V1541Commander::fsOptionsAction()
975 {
976     return d->fsOptionsAction;
977 }
978 
rewriteImageAction()979 QAction &V1541Commander::rewriteImageAction()
980 {
981     return d->rewriteImageAction;
982 }
983 
autoMapLcAction()984 QAction &V1541Commander::autoMapLcAction()
985 {
986     return d->autoMapLcAction;
987 }
988 
mapLcAction()989 QAction &V1541Commander::mapLcAction()
990 {
991     return d->mapLcAction;
992 }
993 
newFileAction()994 QAction &V1541Commander::newFileAction()
995 {
996     return d->newFileAction;
997 }
998 
deleteFileAction()999 QAction &V1541Commander::deleteFileAction()
1000 {
1001     return d->deleteFileAction;
1002 }
1003 
lowerCaseAction()1004 QAction &V1541Commander::lowerCaseAction()
1005 {
1006     return d->lowerCaseAction;
1007 }
1008 
instanceServerName() const1009 const QString &V1541Commander::instanceServerName() const
1010 {
1011     return d->instanceServerName;
1012 }
1013 
isPrimaryInstance() const1014 bool V1541Commander::isPrimaryInstance() const
1015 {
1016     return d->isPrimaryInstance;
1017 }
1018 
settings()1019 Settings &V1541Commander::settings()
1020 {
1021     return d->settings;
1022 }
1023 
instance()1024 V1541Commander &V1541Commander::instance()
1025 {
1026     return *static_cast<V1541Commander *>(QCoreApplication::instance());
1027 }
1028