A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2010 Robert Bieber
11 *
12 * This program is free software; can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your optiyouon) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "skindocument.h"
23
24#include <QFile>
25#include <QSettings>
26#include <QColor>
27#include <QMessageBox>
28#include <QFileDialog>
29
30#include <iostream>
31
32#include <QDebug>
33
34const int SkinDocument::updateInterval = 500;
35
36SkinDocument::SkinDocument(QLabel* statusLabel, ProjectModel* project,
37 DeviceState* device, QWidget *parent)
38 :TabContent(parent), statusLabel(statusLabel),
39 project(project), device(device),
40 treeInSync(true)
41{
42 setupUI();
43
44 titleText = "Untitled";
45 fileName = "";
46 saved = "";
47 parseStatus = tr("Empty document");
48 blockUpdate = false;
49 currentLine = -1;
50}
51
52SkinDocument::SkinDocument(QLabel* statusLabel, QString file,
53 ProjectModel* project, DeviceState* device,
54 QWidget *parent)
55 :TabContent(parent), fileName(file),
56 statusLabel(statusLabel), project(project),
57 device(device), treeInSync(true)
58{
59 setupUI();
60 blockUpdate = false;
61
62 /* Loading the file */
63 if(QFile::exists(fileName))
64 {
65 QFile fin(fileName);
66 fin.open(QFile::ReadOnly);
67 editor->document()->setPlainText(QString(fin.readAll()));
68 saved = editor->document()->toPlainText();
69 editor->setTextCursor(QTextCursor(editor->document()->begin()));
70 fin.close();
71 }
72
73 /* Setting the title */
74 QStringList decomposed = fileName.split('/');
75 titleText = decomposed.last();
76
77 /* Setting the current screen device setting */
78 QString extension = titleText.split(".").last().toLower().right(3);
79 if(extension == "wps")
80 {
81 device->setData("cs", "WPS");
82 }
83 else if(extension == "sbs")
84 {
85 device->setData("cs", "Menus");
86 }
87 else if(extension == "fms")
88 {
89 device->setData("cs", "FM Radio Screen");
90 }
91
92 lastUpdate = QTime::currentTime();
93}
94
95SkinDocument::~SkinDocument()
96{
97 highlighter->deleteLater();
98 model->deleteLater();
99}
100
101void SkinDocument::connectPrefs(PreferencesDialog* prefs)
102{
103 QObject::connect(prefs, SIGNAL(accepted()),
104 this, SLOT(settingsChanged()));
105 QObject::connect(prefs, SIGNAL(accepted()),
106 highlighter, SLOT(loadSettings()));
107}
108
109bool SkinDocument::requestClose()
110{
111 /* Storing the response in blockUpdate will also block updates to the
112 status bar if the tab is being closed */
113 if(editor->document()->toPlainText() != saved)
114 {
115 /* Spawning the "Are you sure?" dialog */
116 QMessageBox confirm(this);
117 confirm.setWindowTitle(tr("Confirm Close"));
118 confirm.setText(titleText + tr(" has been modified."));
119 confirm.setInformativeText(tr("Do you want to save your changes?"));
120 confirm.setStandardButtons(QMessageBox::Save | QMessageBox::Discard
121 | QMessageBox::Cancel);
122 confirm.setDefaultButton(QMessageBox::Save);
123 int confirmation = confirm.exec();
124
125 switch(confirmation)
126 {
127 case QMessageBox::Save:
128 save();
129 /* After calling save, make sure the user actually went through */
130 if(editor->document()->toPlainText() != saved)
131 blockUpdate = false;
132 else
133 blockUpdate = true;
134 break;
135
136 case QMessageBox::Discard:
137 blockUpdate = true;
138 break;
139
140 case QMessageBox::Cancel:
141 blockUpdate = false;
142 break;
143 }
144 }
145 else
146 blockUpdate = true;
147
148 return blockUpdate;
149}
150
151void SkinDocument::setupUI()
152{
153 /* Setting up the text edit */
154 layout = new QHBoxLayout;
155 editor = new CodeEditor(this);
156 editor->setLineWrapMode(QPlainTextEdit::NoWrap);
157 layout->addWidget(editor);
158
159 setLayout(layout);
160
161 /* Attaching the syntax highlighter */
162 highlighter = new SkinHighlighter(editor->document());
163
164 /* Setting up the model */
165 model = new ParseTreeModel("");
166
167 QObject::connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
168 this, SLOT(modelChanged()));
169
170 /* Connecting the editor's signal */
171 QObject::connect(editor, SIGNAL(textChanged()),
172 this, SLOT(codeChanged()));
173 QObject::connect(editor, SIGNAL(cursorPositionChanged()),
174 this, SLOT(cursorChanged()));
175
176 /* Connecting to device setting changes */
177 QObject::connect(device, SIGNAL(settingsChanged()),
178 this, SLOT(deviceChanged()));
179
180 /* Attaching the find/replace dialog */
181 findReplace = new FindReplaceDialog(this);
182 findReplace->setModal(false);
183 findReplace->setTextEdit(editor);
184 findReplace->hide();
185
186 settingsChanged();
187
188 /* Setting up a timer to check for updates */
189 checkUpdate.setInterval(500);
190 QObject::connect(&checkUpdate, SIGNAL(timeout()),
191 this, SLOT(codeChanged()));
192}
193
194void SkinDocument::settingsChanged()
195{
196 /* Setting the editor colors */
197 QSettings settings;
198 settings.beginGroup("SkinDocument");
199
200 QColor fg = settings.value("fgColor", QColor(Qt::black)).value<QColor>();
201 QColor bg = settings.value("bgColor", QColor(Qt::white)).value<QColor>();
202 QPalette palette;
203 palette.setColor(QPalette::All, QPalette::Base, bg);
204 palette.setColor(QPalette::All, QPalette::Text, fg);
205 editor->setPalette(palette);
206
207 QColor highlight = settings.value("errorColor", QColor(Qt::red)).value<QColor>();
208 editor->setErrorColor(highlight);
209
210 /* Setting the font */
211 QFont def("Monospace");
212 def.setStyleHint(QFont::TypeWriter);
213 QFont family = settings.value("fontFamily", def).value<QFont>();
214 family.setPointSize(settings.value("fontSize", 12).toInt());
215 editor->setFont(family);
216
217 editor->repaint();
218
219 settings.endGroup();
220
221}
222
223void SkinDocument::cursorChanged()
224{
225 if(editor->isError(editor->textCursor().blockNumber() + 1))
226 {
227 QTextCursor line = editor->textCursor();
228 line.movePosition(QTextCursor::StartOfLine);
229 line.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
230 skin_parse(line.selectedText().toLatin1());
231 if(skin_error_line() > 0)
232 parseStatus = tr("Error on line ") +
233 QString::number(line.blockNumber() + 1)
234 + tr(", column ") + QString::number(skin_error_col())
235 + tr(": ") +
236 skin_error_message();
237 statusLabel->setText(parseStatus);
238 }
239 else if(editor->hasErrors())
240 {
241 parseStatus = tr("Errors in document");
242 statusLabel->setText(parseStatus);
243 }
244 else if(editor->textCursor().blockNumber() != currentLine)
245 {
246 currentLine = editor->textCursor().blockNumber();
247 emit lineChanged(editor->textCursor().blockNumber() + 1);
248 }
249
250}
251
252void SkinDocument::codeChanged()
253{
254 if(blockUpdate)
255 return;
256
257 if(editor->document()->isEmpty())
258 {
259 parseStatus = tr("Empty document");
260 statusLabel->setText(parseStatus);
261 return;
262 }
263
264 editor->clearErrors();
265 parseStatus = model->changeTree(editor->document()->
266 toPlainText().toLatin1());
267
268 treeInSync = true;
269 emit antiSync(false);
270
271 if(skin_error_line() > 0)
272 parseStatus = tr("Errors in document");
273 statusLabel->setText(parseStatus);
274
275 /* Highlighting if an error was found */
276 if(skin_error_line() > 0)
277 {
278 editor->addError(skin_error_line());
279
280 /* Now we're going to attempt parsing again at each line, until we find
281 one that won't error out*/
282 QTextDocument doc(editor->document()->toPlainText());
283 int base = 0;
284 while(skin_error_line() > 0 && !doc.isEmpty())
285 {
286 QTextCursor rest(&doc);
287
288 for(int i = 0; i < skin_error_line(); i++)
289 rest.movePosition(QTextCursor::NextBlock,
290 QTextCursor::KeepAnchor);
291 if(skin_error_line() == doc.blockCount())
292 rest.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
293
294 rest.removeSelectedText();
295 base += skin_error_line();
296
297 skin_parse(doc.toPlainText().toLatin1());
298
299 if(skin_error_line() > 0)
300 editor->addError(base + skin_error_line());
301
302 }
303 }
304
305 if(editor->document()->toPlainText() != saved)
306 emit titleChanged(titleText + QChar('*'));
307 else
308 emit titleChanged(titleText);
309
310 if(lastUpdate.msecsTo(QTime::currentTime()) >= updateInterval)
311 {
312 model->render(project, device, this, &fileName);
313 checkUpdate.stop();
314 lastUpdate = QTime::currentTime();
315 }
316 else
317 {
318 checkUpdate.start();
319 }
320 cursorChanged();
321
322}
323
324void SkinDocument::modelChanged()
325{
326 treeInSync = false;
327 emit antiSync(true);
328}
329
330void SkinDocument::save()
331{
332 QFile fout(fileName);
333
334 if(!fout.exists())
335 {
336 saveAs();
337 return;
338 }
339
340 fout.open(QFile::WriteOnly);
341 fout.write(editor->document()->toPlainText().toLatin1());
342 fout.close();
343
344 saved = editor->document()->toPlainText();
345 QStringList decompose = fileName.split('/');
346 titleText = decompose.last();
347 emit titleChanged(titleText);
348
349 scene();
350
351}
352
353void SkinDocument::saveAs()
354{
355 /* Determining the directory to open */
356 QString directory = fileName;
357
358 QSettings settings;
359 settings.beginGroup("SkinDocument");
360 if(directory == "")
361 directory = settings.value("defaultDirectory", "").toString();
362
363 fileName = QFileDialog::getSaveFileName(this, tr("Save Document"),
364 directory, fileFilter());
365 directory = fileName;
366 if(fileName == "")
367 return;
368
369 directory.chop(fileName.length() - fileName.lastIndexOf('/') - 1);
370 settings.setValue("defaultDirectory", directory);
371 settings.endGroup();
372
373 QFile fout(fileName);
374 fout.open(QFile::WriteOnly);
375 fout.write(editor->document()->toPlainText().toLatin1());
376 fout.close();
377
378 saved = editor->document()->toPlainText();
379 QStringList decompose = fileName.split('/');
380 titleText = decompose[decompose.count() - 1];
381 emit titleChanged(titleText);
382
383 scene();
384
385}
386
387QString SkinDocument::findSetting(QString key, QString fallback)
388{
389 if(!project)
390 return fallback;
391 else
392 return project->getSetting(key, fallback);
393}