aboutsummaryrefslogtreecommitdiff
path: root/lumina-desktop/desktop-plugins/notepad/NotepadPlugin.cpp
blob: 435a57c2b4b4fe42c2820dd01bc5d904da683461 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#include "NotepadPlugin.h"

#include <LuminaXDG.h>
#include "LSession.h"
#include <LuminaUtils.h>
#include <QDir>
#include <QFileDialog>
#include <QInputDialog>
#include <QtConcurrent>

NotePadPlugin::NotePadPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID){
  //qDebug() << "Creating Notepad Plugin:";
  QVBoxLayout *vlay = new QVBoxLayout();
  this->setLayout( new QVBoxLayout() );
    this->layout()->setContentsMargins(0,0,0,0);
    vlay->setContentsMargins(3,3,3,3);
    frame = new QFrame(this);
      frame->setObjectName("notepadbase");
      //frame->setStyleSheet("QFrame#notepadbase{border-width: 1px; background: rgba(255,255,255,50); color: black;} QFrame{ border: none; border-radius: 3px; background: rgba(255,255,255,100); color: black;}");
    this->layout()->addWidget(frame);
    frame->setLayout(vlay);
   
  if(!QFile::exists(QDir::homePath()+"/Notes")){
    //Create the notes directory if non-existant
    QDir dir;
	dir.mkpath(QDir::homePath()+"/Notes"); 
  }
  watcher = new QFileSystemWatcher(this);
     //Always watch the notes directory for new files/changes
     watcher->addPath(QDir::homePath()+"/Notes");
  
  typeTimer = new QTimer(this);
    typeTimer->setInterval(1000); // 1 second before it saves
    typeTimer->setSingleShot(true); //compress lots of signals into a single save
  
  updating = false;
  //Setup the title bar header buttons
  QHBoxLayout *hlay = new QHBoxLayout();
  config = new QToolButton(this);
    config->setAutoRaise(true);
    config->setMenu(new QMenu(this));
    config->setPopupMode(QToolButton::InstantPopup);
  /*open = new QToolButton(this);
    open->setAutoRaise(true);
  add = new QToolButton(this);
    add->setAutoRaise(true);
  rem = new QToolButton(this);
    rem->setAutoRaise(true);*/
  cnote = new QComboBox(this);
	
    hlay->addWidget(cnote);
    hlay->addWidget(config);
    //hlay->addWidget(open);
    //hlay->addWidget(add);
    //hlay->addWidget(rem);
    vlay->addLayout(hlay);
	
  //Setup the main text widget
  edit = new QPlainTextEdit(this);
    edit->setReadOnly(false);
    edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    vlay->addWidget(edit);
	
  //Now load the new file-based system for saving notes
  //qDebug() << "Saving a new setting";
  this->saveSetting("customFile",""); //always clear this when the plugin is initialized (only maintained per-session)
  //qDebug() << "Loading Notes Dir";
  QTimer::singleShot(2000, this, SLOT(notesDirChanged()));
  //qDebug() << "Set Sizing";
  
  //qDebug() << "Connect Signals/slots";
  //Setup the button connections
  /*connect(open, SIGNAL(clicked()), this, SLOT(openNoteClicked()) );
  connect(add, SIGNAL(clicked()), this, SLOT(newNoteClicked()) );
  connect(rem, SIGNAL(clicked()), this, SLOT(remNote()) );*/
  //connect(config, SIGNAL(clicked()), this, SLOT(openConfigMenu()) );
  connect(edit, SIGNAL(textChanged()), this, SLOT(newTextAvailable()) );
  connect(cnote, SIGNAL(currentIndexChanged(QString)), this, SLOT(noteChanged()) );
  connect(typeTimer, SIGNAL(timeout()), this, SLOT(updateContents()) );
  connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(notesDirChanged()) ); //re-load the available notes
  connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(noteChanged()) ); //re-load the current file
  QTimer::singleShot(0,this, SLOT(loadIcons()) );
  //qDebug() << " - Done with init";
}

NotePadPlugin::~NotePadPlugin(){

}


void NotePadPlugin::openNote(){
  //qDebug() << "Open New Note:";
  //Prompt for a name for the new note
  QFileDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
      dlg.setFileMode(QFileDialog::ExistingFile);
      dlg.setAcceptMode(QFileDialog::AcceptOpen);
      dlg.setNameFilters( QStringList() << tr("Note Files (*.note)") << tr("Text Files (*)"));
      dlg.setWindowTitle(tr("Open a note file"));
      dlg.setWindowIcon( LXDG::findIcon("document-open","") );
      dlg.setDirectory(QDir::homePath()); //start in the home directory
      //ensure it is centered on the current screen
      QPoint center = QApplication::desktop()->screenGeometry(this).center();
      dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
  dlg.show();
  while( dlg.isVisible() ){
    QApplication::processEvents();
  }
  QStringList sel = dlg.selectedFiles();
  if(sel.isEmpty()  || dlg.result()!=QDialog::Accepted){ return; } //cancelled
  QString fullpath = sel.first();
  QString name = fullpath.section("/",-1);
  //qDebug() << " - Found Note:" << name << fullpath;
  int index = cnote->findText(name, Qt::MatchExactly | Qt::MatchCaseSensitive);
  if(QFile::exists(fullpath) && index <0){
    //Alternate option of searching for the file in the list
    index = cnote->findText(fullpath, Qt::MatchExactly | Qt::MatchCaseSensitive);
  }
  if(index>=0){
    //This note already exists: just load it
    cnote->setCurrentIndex(index);
  }else{
    //New note - add it to the end of the list and then load it
    cnote->addItem(name, fullpath);
    this->saveSetting("customFile", fullpath); //save this as a custom file
    cnote->setCurrentIndex( cnote->count()-1 ); 
    QTimer::singleShot(1000, this, SLOT(notesDirChanged())); //Make sure to refresh the list (only one custom file at a time)
  }
}

QString NotePadPlugin::newNoteName(QString oldname, bool tryagain){
  //Prompt for a name for the new note
  //qDebug() << "Create new note";
  QInputDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
      dlg.setInputMode(QInputDialog::TextInput);
      dlg.setLabelText(tr("Name:"));
      dlg.setTextEchoMode(QLineEdit::Normal);
      if(tryagain){ dlg.setWindowTitle(tr("Invalid Note Name: Try Again")); }
      else{ dlg.setWindowTitle(tr("Select a Note Name")); }
      dlg.setWindowIcon( LXDG::findIcon("document-new","") );
      dlg.setTextValue(oldname);
      //ensure it is centered on the current screen
      QPoint center = QApplication::desktop()->screenGeometry(this).center();
      dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
  dlg.show();
  while( dlg.isVisible() ){
    //this->thread()->usleep(300000); //300 ms between updates
    QApplication::processEvents();
  }
  QString name = dlg.textValue();
  //make sure to remove any "bad" characters from the name
  name.remove("\""); name.remove(";"); name.remove("\'"); name.replace("/","_");
  if(name.isEmpty()  || dlg.result()!=QDialog::Accepted){ return ""; } //cancelled
  //Check validity of the new note filename
  QString fullpath = QDir::homePath()+"/Notes/"+name;
  if(!fullpath.endsWith(".note")){ fullpath.append(".note"); }
  if(QFile::exists(fullpath)){
    return newNoteName(name, true); //try again
  }
  return name; //good name - go ahead and return it
}

void NotePadPlugin::updateConfigMenu(){
  //Re-create the menu and open it
  config->menu()->clear();
  config->menu()->addAction(LXDG::findIcon("document-open",""), tr("Open Text File"), this, SLOT(openNoteClicked()) );
  config->menu()->addAction(LXDG::findIcon("document-new",""), tr("Create a Note"), this, SLOT(newNoteClicked()) );
  if(cnote->currentIndex()>=0){
    config->menu()->addSeparator();
    config->menu()->addAction(LXDG::findIcon("document-edit",""), tr("Rename Note"), this, SLOT(renameNote()) );
    config->menu()->addAction(LXDG::findIcon("document-close",""), tr("Delete Note"), this, SLOT(remNote()) );
  }
}

void NotePadPlugin::openNoteClicked(){
  openNote();
}

void NotePadPlugin::newNoteClicked(){
  //QtConcurrent::run(this, &NotePadPlugin::newNote);	
  QString name = newNoteName();
  if(name.isEmpty()){ return; }
  QString fullpath = QDir::homePath()+"/Notes/"+name;
  if(!fullpath.endsWith(".note")){ fullpath.append(".note"); }
  //qDebug() << " - New Note:" << name << fullpath;
  int index = cnote->findText(name, Qt::MatchExactly | Qt::MatchCaseSensitive);
  if(QFile::exists(fullpath) && index <0){
    //Alternate option of searching for the file in the list
    index = cnote->findText(fullpath, Qt::MatchExactly | Qt::MatchCaseSensitive);
  }
  if(index>=0){
    //This note already exists: just load it
    cnote->setCurrentIndex(index);
  }else{
    //New note - add it to the end of the list and then load it
    cnote->addItem(name, fullpath);
    cnote->setCurrentIndex( cnote->count()-1 ); 
  }
}

void NotePadPlugin::remNote(){
  QString note = cnote->currentData().toString();
  if(note.isEmpty()){ return; }
  watcher->removePath(note); //remove this file from the watcher
  this->saveSetting("currentFile",""); //reset the internal value
  QFile::remove(note); //remove the file
  //if(!note.startsWith(QDir::homePath()+"/Notes/") ){
    //If the file was not in the notes directory, need to manually prompt for a re-load
    // otherwise, the directory watcher will catch it and trigger a re-load (no need to double-load)
    notesDirChanged();
  //}
}

void NotePadPlugin::renameNote(){
  int item = cnote->currentIndex();
  if(item<0){ return; } //nothing selected
  QString oldpath = cnote->currentData().toString();
  if(oldpath.isEmpty() || !oldpath.endsWith(".note")){ return; }
  QString name = newNoteName(cnote->currentText());
  if(name.isEmpty()){ return; }
  QString fullpath = QDir::homePath()+"/Notes/"+name;
  if(!fullpath.endsWith(".note")){ fullpath.append(".note"); }
  //qDebug() << " - New Note:" << name << fullpath;
  //Update the current item data to point to this file
  cnote->setItemText(item, name);
  cnote->setItemData(item, fullpath);
  //Now move the file over
  QFile::rename(oldpath, fullpath);
  noteChanged();
}

void NotePadPlugin::newTextAvailable(){
  if(updating){ return; } //programmatic change of the widget
  if(typeTimer->isActive()){ typeTimer->stop(); }
  typeTimer->start();  
}

void NotePadPlugin::updateContents(){
  if(updating){ return; } //this was a programmatic change to the widget
  //The text was changed in the plugin - save it in the file
  QString note = cnote->currentData().toString();
  updating = true;
  LUtils::writeFile(note, edit->toPlainText().split("\n"), true);
  QApplication::processEvents(); //make sure to process/discard the file changed signal before disabling the flag
  updating = false;
}

void NotePadPlugin::notesDirChanged(){
  if(updating){ return; }
  QString cfile = this->readSetting("currentFile","").toString();
  QStringList notes;
  QDir dir(QDir::homePath()+"/Notes");
  QStringList files = dir.entryList(QStringList() << "*.note", QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
  for(int i=0; i<files.length(); i++){
    notes << dir.absoluteFilePath(files[i]);
  }
  QString custom = this->readSetting("customFile","").toString();
  if(!custom.isEmpty() && QFile::exists(custom) ){ notes << custom; } 
  //qDebug() << "Available Notes:" << notes << cfile;
  //Now update the UI list
  updating = true; //don't refresh the UI until done changing lists
  cnote->clear();
  bool found = false;
  for(int i=0; i<notes.length(); i++){
    QString name = notes[i].section("/",-1);
    if(name.endsWith(".note")){ name.chop(5); }
    cnote->addItem(name, notes[i]);
    if(notes[i]==cfile){ cnote->setCurrentIndex(i); found = true;}
  }
  if(!found && !cfile.isEmpty() && QFile::exists(cfile)){
    //Current note is a manually-loaded text file
    cnote->addItem(cfile.section("/",-1), cfile);
    cnote->setCurrentIndex( cnote->count()-1 ); //last item
    found = true;
  }
  if(!found && cnote->count()>0){ cnote->setCurrentIndex(0); }
  updating =false;
  noteChanged();
}

void NotePadPlugin::noteChanged(){
  if(updating){ return; }
  updating =true;
  QString note;
  if(cnote->currentIndex()>=0){
    note = cnote->currentData().toString();
  }
  QTimer::singleShot(0, this, SLOT(updateConfigMenu()) );
  if(note.isEmpty() && cnote->count()>0){ 
    updating=false; 
    cnote->setCurrentIndex(0); 
    return; 
  }
  QString oldnote = this->readSetting("currentFile","").toString();
  //qDebug() << "Note Changed:" << note << oldnote;
  if( oldnote!=note ){ 
    //Clear the old note file/setting
    if(!oldnote.isEmpty()){
      watcher->removePath(oldnote); 
      this->saveSetting("currentFile","");
    }
    if(!note.isEmpty()){
      this->saveSetting("currentFile",note);
      watcher->addPath(note);
    }
  }

  if(!note.isEmpty()){
    QString text = LUtils::readFile(note).join("\n");
    if(text!=edit->toPlainText()){
      edit->setPlainText( text );  
    }
  }else{
    edit->clear();
  }
  //If no notes available - disable the editor until a new one is created
  edit->setEnabled(!note.isEmpty());
  //rem->setEnabled(!note.isEmpty());
  cnote->setEnabled(!note.isEmpty());
  //leave the new/open buttons enabled all the time
  updating = false;
}


void NotePadPlugin::loadIcons(){
  /*open->setIcon( LXDG::findIcon("document-open","") );
  add->setIcon( LXDG::findIcon("document-new","") );
  rem->setIcon( LXDG::findIcon("document-close","") );*/
  config->setIcon( LXDG::findIcon("configure","") );
}
bgstack15