comparison libgui/src/m-editor/file-editor-tab.cc @ 15848:424edeca3c66

Redo portions of file editor to use more signals/slots rather than casting. * file-editor-tab.cc, file-editor-tab.h (file_editor_tab::~file_editor_tab): Add. Delete lexer to prevent memory leak. Delete _edit_area to prevent memory leak. (file_editor_tab::conditional_close): Add. Simple slot that uses QWidget pointer as unique ID, not for function call. (file_editor_tab::file_name_query): Add. Simple slot that signals file name to whomever is connected. (file_editor_tab::find, file_editor_tab : public QWidget): Removed use of exec() and keep a pointer to the find_dialog as a member. Toggle hide()/show() via a connected slot to toggle visibility as desired. * file-editor.cc, file-editor.h, file-editor-tab.cc file-editor-tab.h (file_editor : public file_editor_interface, file_editor_tab : public QWidget, file_editor_tab::file_editor_tab, file_editor_tab::closeEvent, file_editor_tab::load_file, file_editor_tab::new_file, file_editor_tab::run_file): Remove _file_editor pointer member from file_editor_tab and rid file_editor::get_main_window from file_editor. There should be no need for such information about higher-level hierarchy inside lower-level objects. (file_editor::request_open_file, file_editor_tab::open_file): Move QFileDialog to file_editor::request_open_file and delete file_editor_tab::open_file since most of the remaining functionality is in file_editor_tab::load_file. (file_editor::active_editor_tab): Deleted. (file_editor::fetab_change_request, file_editor_tab::change_editor_state): Added to initiate a request for the editor tab to change focus. (file_editor_tab::editor_state_changed): Added arguments to pass the copy status and the directory path so that editor doesn't have to call functions for such information. (file_editor::handle_editor_state_changed): Add copying directory of the file_editor_tab to the current editing directory. (file_editor::check_conflict_save, file_editor_tab::editor_check_conflict_save, file_editor_tab::save_file_as, file_editor_tab::handle_save_file_as_answer): Moved a portion of the save-file-as dialog mechanism to the file_editor where all file names can be obtained to check for conflict with already open files. The new signal indicates to the editor that a name check be done, and in turn the editor signals the tab to save the file. * main-window.cc, file-editor.cc, file-editor.h, file-editor-interface.h (file_editor::terminal, file_editor : public file_editor_interface, file_editor_interface : public QDockWidget): Since file_editor_tab no longer looks up to main_window, remove _main_window and _terminal from file_editor and file_editor_interface, as well as file_editor::terminal. * file-editor-tab.cc (file_editor_tab::file_has_changed): Make the dialog boxes non-modal and use slots to handle user answers. (file_editor_tab::closeEvent): Remove portion that accesses upper hierarchy member functions, can find better approaches. (file_editor_tab::file_editor_tab): Make there no parent for QsciScintilla so that window modality can be set to individual editor. * file-editor-tab.cc, file-editor.cc (file_editor_tab::load_file): Use show() member rather than exec() and set modality to window so that rest of application may function. Return a QString with message rather than a boolean. * file-editor-tab.cc, (file_editor_tab::file_has_changed): Remove static variable alreadyAsking. Multiple file_editor_tabs are using this code so do not want to block recognition of multiple file having changed on disk (bug #37406). Instead, simply stop tracking via the file watcher. (file_editor_tab::save_file, file_editor_tab::save_file_as, file_editor_tab::handle_save_file_as_answer, file_editor_tab::handle_save_file_as_answer_close): Added a remove_on_success variable. Changed the QFileDialog to WindowModal and created slots to handle file selected signal and finished signal. Signal/slot connects vary based upon remove_on_success setting. (file_editor_tab::check_file_modified): Changed the QFileDialog to NonModal and attach some slots. Editor tab can't be parent in case deleted, so use read-only state of the editor area. * file-editor-tab.h (file_editor_tab : public QWidget): New signals for file_editor for tab and file name management. (file_editor_tab::get_file_name): Delete. * file-editor.h (file_editor : public file_editor_interface): Make QStringList sessionFileNames a member of file_editor so that it can retain data between file_editor_tab signals. Also can be used for checking precense of filenames and prevent opening multiple copies (bug #36869) Added signals for file editor tabs--settings_changed, fetab_close_request, and query_file_name. Three new slots for tab and file name management. * file-editor-interface.h, file-editor.h (file_editor_interface::add_file_editor_tab, file_editor::add_file_editor_tab): Made the text name for the tab an input variable. * file-editor.cc (file_editor::~file_editor): Replace dynamic_cast with simple signal querying all file editor tabs for file names which end up in savedSessionTabs. (file_editor::handle_file_name_changed): Dynamic cast not necessary since QObject and QWidget are compatible. (file_editor::handle_tab_close_request): Replace dynamic_cast with signal to request file_editor_tab with associated tabID tag should close. (file_editor::handle_tab_remove_request): Rename of handle_tab_close_request. Instead of dynamic cast, loop through pointers comparing QWidget* to QObject*, if same tag remove tab via index and also delete which fixes a memory leak. (file_editor::handle_add_filename_to_list): Simple slot that uses append() of the list member functions. (file_editor::notice_settings): Rather than dynamic cast, emit signal for the file_editor_tabs. (file_editor::add_file_editor_tab): New variety of connections for improved flow. (file_editor::request_open_file): Given error messages are made WindowModal, the tab shouldn't be delete if file open is not successful. The file_editor_tab takes care of that. (file_editor::request_open_file): Added check and message box for the requested file already open in editor. For the non-interactive overloaded version, open a message dialog box to tell the user file not found, e.g., could not find file in the settings when launched. (file_editor::request_open_file): Inquire file names and update list before checking for existence of files. Supply empty title to editor tab then have file_editor_tab update name. * file-editor-tab.h, file-editor-tab.cc, file-editor.cc (file_editor_tab::run_file): New signal process_octave_code. (file_editor::add_file_editor_tab): Connect signal process_octave_code to file_editor's parent's slot handle_command_double_clicked.
author Daniel J Sebald <daniel.sebald@ieee.org>
date Sun, 23 Dec 2012 14:33:48 -0600
parents 63dd6c30b294
children e55a64f49346
comparison
equal deleted inserted replaced
15847:13d1e9bfa362 15848:424edeca3c66
40 #include <QTextStream> 40 #include <QTextStream>
41 #include <QVBoxLayout> 41 #include <QVBoxLayout>
42 42
43 #include "file-editor-tab.h" 43 #include "file-editor-tab.h"
44 #include "file-editor.h" 44 #include "file-editor.h"
45 #include "find-dialog.h"
46 #include "octave-link.h" 45 #include "octave-link.h"
47 46
48 #include "debug.h" 47 #include "debug.h"
49 #include "oct-env.h" 48 #include "oct-env.h"
50 49
51 file_editor_tab::file_editor_tab(file_editor *fileEditor) 50 // Make parent null for the file editor tab so that warning
52 : QWidget ((QWidget*)fileEditor) 51 // WindowModal messages don't affect grandparents.
53 { 52 file_editor_tab::file_editor_tab (QString directory)
54 _file_editor = fileEditor; 53 {
55 _file_name = ""; 54 // Make sure there is a slash at the end of the directory name
55 // for identification when saved later.
56 if (directory.count () && directory.at (directory.count () - 1) != '/')
57 directory.append ("/");
58 _file_name = directory;
56 _edit_area = new QsciScintilla (this); 59 _edit_area = new QsciScintilla (this);
60
61 // Leave the find dialog box out of memory until requested.
62 _find_dialog = 0;
63 _find_dialog_is_visible = false;
57 64
58 // symbols 65 // symbols
59 _edit_area->setMarginType (1, QsciScintilla::SymbolMargin); 66 _edit_area->setMarginType (1, QsciScintilla::SymbolMargin);
60 _edit_area->setMarginSensitivity (1, true); 67 _edit_area->setMarginSensitivity (1, true);
61 _edit_area->markerDefine (QsciScintilla::RightTriangle, bookmark); 68 _edit_area->markerDefine (QsciScintilla::RightTriangle, bookmark);
104 connect (_edit_area, SIGNAL (copyAvailable (bool)), 111 connect (_edit_area, SIGNAL (copyAvailable (bool)),
105 this, SLOT (handle_copy_available (bool))); 112 this, SLOT (handle_copy_available (bool)));
106 connect (&_file_system_watcher, SIGNAL (fileChanged (QString)), 113 connect (&_file_system_watcher, SIGNAL (fileChanged (QString)),
107 this, SLOT (file_has_changed (QString))); 114 this, SLOT (file_has_changed (QString)));
108 115
109 _file_name = "";
110
111 notice_settings (); 116 notice_settings ();
112 } 117 }
113 118
114 bool 119 file_editor_tab::~file_editor_tab ()
115 file_editor_tab::copy_available () 120 {
116 { 121 // Destroy items attached to _edit_area.
117 return _copy_available; 122 QsciLexer *lexer = _edit_area->lexer ();
123 if (lexer)
124 {
125 delete lexer;
126 _edit_area->setLexer(0);
127 }
128 if (_find_dialog)
129 {
130 delete _find_dialog;
131 _find_dialog = 0;
132 }
133
134 // Destroy _edit_area.
135 delete _edit_area;
118 } 136 }
119 137
120 void 138 void
121 file_editor_tab::closeEvent (QCloseEvent *e) 139 file_editor_tab::closeEvent (QCloseEvent *e)
122 { 140 {
123 if (_file_editor->get_main_window ()->is_closing ()) 141 // ignore close event if file is not saved and user cancels
124 { 142 // closing this window
125 // close whole application: save file or not if modified 143 if (check_file_modified ("Close File",
126 check_file_modified ("Closing Octave", 0); // no cancel possible 144 QMessageBox::Cancel) == QMessageBox::Cancel)
127 e->accept (); 145 {
128 } 146 e->ignore ();
129 else 147 }
130 { 148 else
131 // ignore close event if file is not saved and user cancels 149 {
132 // closing this window 150 e->accept();
133 if (check_file_modified ("Close File",
134 QMessageBox::Cancel) == QMessageBox::Cancel)
135 {
136 e->ignore ();
137 }
138 else
139 {
140 e->accept();
141 }
142 } 151 }
143 } 152 }
144 153
145 void 154 void
146 file_editor_tab::set_file_name (const QString& fileName) 155 file_editor_tab::set_file_name (const QString& fileName)
147 { 156 {
148 if (fileName != UNNAMED_FILE) 157 // update tracked file if we really have a file on disk
149 { 158 QStringList trackedFiles = _file_system_watcher.files ();
150 // update tracked file if wie really hae a file on disk 159 if (!trackedFiles.isEmpty ())
151 QStringList trackedFiles = _file_system_watcher.files (); 160 _file_system_watcher.removePath (_file_name);
152 if (!trackedFiles.isEmpty ()) 161 if (!fileName.isEmpty ())
153 _file_system_watcher.removePath (_file_name); 162 _file_system_watcher.addPath (fileName);
154 _file_system_watcher.addPath (fileName);
155 }
156 _file_name = fileName; 163 _file_name = fileName;
157 164
158 // update lexer after _file_name change 165 // update lexer after _file_name change
159 update_lexer (); 166 update_lexer ();
167
168 // update the file editor with current editing directory
169 emit editor_state_changed (_copy_available, QDir::cleanPath (_file_name));
160 } 170 }
161 171
162 void 172 void
163 file_editor_tab::handle_margin_clicked(int margin, int line, 173 file_editor_tab::handle_margin_clicked(int margin, int line,
164 Qt::KeyboardModifiers state) 174 Qt::KeyboardModifiers state)
189 } 199 }
190 200
191 void 201 void
192 file_editor_tab::update_lexer () 202 file_editor_tab::update_lexer ()
193 { 203 {
194 QsciLexer *lexer = _edit_area->lexer (); 204 QsciLexer *lexer = _edit_area->lexer ();
195 delete lexer; 205 delete lexer;
196 206
197 if (_file_name.endsWith (".m") || _file_name.endsWith (".M")) 207 if (_file_name.endsWith (".m") || _file_name.endsWith (".M"))
198 { 208 {
199 lexer = new lexer_octave_gui (); 209 lexer = new lexer_octave_gui ();
244 254
245 _edit_area->setLexer (lexer); 255 _edit_area->setLexer (lexer);
246 } 256 }
247 257
248 void 258 void
259 file_editor_tab::undo (const QWidget* ID)
260 {
261 if (ID != this)
262 return;
263
264 _edit_area->undo ();
265 }
266
267 void
268 file_editor_tab::redo (const QWidget* ID)
269 {
270 if (ID != this)
271 return;
272
273 _edit_area->redo ();
274 }
275
276 void
277 file_editor_tab::copy (const QWidget* ID)
278 {
279 if (ID != this)
280 return;
281
282 _edit_area->copy ();
283 }
284
285 void
286 file_editor_tab::cut (const QWidget* ID)
287 {
288 if (ID != this)
289 return;
290
291 _edit_area->cut ();
292 }
293
294 void
295 file_editor_tab::paste (const QWidget* ID)
296 {
297 if (ID != this)
298 return;
299
300 _edit_area->paste ();
301 }
302
303 void
304 file_editor_tab::save_file (const QWidget* ID)
305 {
306 if (ID != this)
307 return;
308
309 save_file (_file_name);
310 }
311 void
312
313 file_editor_tab::save_file (const QWidget* ID, const QString& fileName, bool remove_on_success)
314 {
315 if (ID != this)
316 return;
317
318 save_file (fileName, remove_on_success);
319 }
320
321 void
322 file_editor_tab::save_file_as (const QWidget* ID)
323 {
324 if (ID != this)
325 return;
326
327 save_file_as ();
328 }
329
330 void
331 file_editor_tab::run_file_callback (void)
332 {
333 // Maybe someday we will do something here?
334 }
335
336 void
337 file_editor_tab::run_file (const QWidget* ID)
338 {
339 if (ID != this)
340 return;
341
342 if (_edit_area->isModified ())
343 save_file (_file_name);
344
345 QFileInfo file_info (_file_name);
346 QString path = file_info.absolutePath ();
347 QString current_path
348 = QString::fromStdString (octave_link::last_working_directory ());
349 QString function_name = file_info.fileName ();
350
351 // We have to cut off the suffix, because octave appends it.
352 function_name.chop (file_info.suffix ().length () + 1);
353 emit process_octave_code (QString ("cd \'%1\'\n%2\n")
354 .arg(path).arg (function_name));
355
356 // TODO: Sending a run event crashes for long scripts. Find out why.
357 // octave_link::post_event
358 // (this, &file_editor_tab::run_file_callback, _file_name.toStdString ()));
359 }
360
361 void
362 file_editor_tab::toggle_bookmark (const QWidget* ID)
363 {
364 if (ID != this)
365 return;
366
367 int line, cur;
368 _edit_area->getCursorPosition (&line,&cur);
369 if ( _edit_area->markersAtLine (line) && (1 << bookmark) )
370 _edit_area->markerDelete (line, bookmark);
371 else
372 _edit_area->markerAdd (line, bookmark);
373 }
374
375 void
376 file_editor_tab::next_bookmark (const QWidget* ID)
377 {
378 if (ID != this)
379 return;
380
381 int line, cur, nextline;
382 _edit_area->getCursorPosition (&line, &cur);
383 if ( _edit_area->markersAtLine (line) && (1 << bookmark) )
384 line++; // we have a breakpoint here, so start search from next line
385 nextline = _edit_area->markerFindNext (line, (1 << bookmark));
386 _edit_area->setCursorPosition (nextline, 0);
387 }
388
389 void
390 file_editor_tab::previous_bookmark (const QWidget* ID)
391 {
392 if (ID != this)
393 return;
394
395 int line, cur, prevline;
396 _edit_area->getCursorPosition (&line, &cur);
397 if ( _edit_area->markersAtLine (line) && (1 << bookmark) )
398 line--; // we have a breakpoint here, so start search from prev line
399 prevline = _edit_area->markerFindPrevious (line, (1 << bookmark));
400 _edit_area->setCursorPosition (prevline, 0);
401 }
402
403 void
404 file_editor_tab::remove_bookmark (const QWidget* ID)
405 {
406 if (ID != this)
407 return;
408
409 _edit_area->markerDeleteAll (bookmark);
410 }
411
412 void
413 file_editor_tab::add_breakpoint_callback (const bp_info& info)
414 {
415 bp_table::intmap intmap;
416 intmap[0] = info.line + 1;
417
418 std::string previous_directory = octave_env::get_current_directory ();
419 octave_env::chdir (info.path);
420 intmap = bp_table::add_breakpoint (info.function_name, intmap);
421 octave_env::chdir (previous_directory);
422
423 if (intmap.size () > 0)
424 {
425 // FIXME -- Check file.
426 _edit_area->markerAdd (info.line, breakpoint);
427 }
428 }
429
430 void
431 file_editor_tab::remove_breakpoint_callback (const bp_info& info)
432 {
433 bp_table::intmap intmap;
434 intmap[0] = info.line;
435
436 std::string previous_directory = octave_env::get_current_directory ();
437 octave_env::chdir (info.path);
438 bp_table::remove_breakpoint (info.function_name, intmap);
439 octave_env::chdir (previous_directory);
440
441 // FIXME -- check result
442 bool success = true;
443
444 if (success)
445 {
446 // FIXME -- check file.
447 _edit_area->markerDelete (info.line, breakpoint);
448 }
449 }
450
451 void
452 file_editor_tab::remove_all_breakpoints_callback (const bp_info& info)
453 {
454 bp_table::intmap intmap;
455 std::string previous_directory = octave_env::get_current_directory ();
456 octave_env::chdir (info.path);
457 intmap = bp_table::remove_all_breakpoints_in_file (info.function_name, true);
458 octave_env::chdir (previous_directory);
459
460 if (intmap.size() > 0)
461 _edit_area->markerDeleteAll (breakpoint);
462 }
463
464 void
249 file_editor_tab::request_add_breakpoint (int line) 465 file_editor_tab::request_add_breakpoint (int line)
250 { 466 {
251 QFileInfo file_info (_file_name); 467 QFileInfo file_info (_file_name);
252 QString path = file_info.absolutePath (); 468 QString path = file_info.absolutePath ();
253 QString function_name = file_info.fileName (); 469 QString function_name = file_info.fileName ();
276 octave_link::post_event 492 octave_link::post_event
277 (this, &file_editor_tab::remove_breakpoint_callback, info); 493 (this, &file_editor_tab::remove_breakpoint_callback, info);
278 } 494 }
279 495
280 void 496 void
281 file_editor_tab::comment_selected_text () 497 file_editor_tab::toggle_breakpoint (const QWidget* ID)
282 { 498 {
499 if (ID != this)
500 return;
501
502 int line, cur;
503 _edit_area->getCursorPosition (&line, &cur);
504 if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
505 request_remove_breakpoint (line);
506 else
507 request_add_breakpoint (line);
508 }
509
510 void
511 file_editor_tab::next_breakpoint (const QWidget* ID)
512 {
513 if (ID != this)
514 return;
515
516 int line, cur, nextline;
517 _edit_area->getCursorPosition (&line, &cur);
518 if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
519 line++; // we have a breakpoint here, so start search from next line
520 nextline = _edit_area->markerFindNext (line, (1 << breakpoint));
521 _edit_area->setCursorPosition (nextline, 0);
522 }
523
524 void
525 file_editor_tab::previous_breakpoint (const QWidget* ID)
526 {
527 if (ID != this)
528 return;
529
530 int line, cur, prevline;
531 _edit_area->getCursorPosition (&line, &cur);
532 if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
533 line--; // we have a breakpoint here, so start search from prev line
534 prevline = _edit_area->markerFindPrevious (line, (1 << breakpoint));
535 _edit_area->setCursorPosition (prevline, 0);
536 }
537
538 void
539 file_editor_tab::remove_all_breakpoints (const QWidget* ID)
540 {
541 if (ID != this)
542 return;
543
544 QFileInfo file_info (_file_name);
545 QString path = file_info.absolutePath ();
546 QString function_name = file_info.fileName ();
547
548 // We have to cut off the suffix, because octave appends it.
549 function_name.chop (file_info.suffix ().length () + 1);
550
551 bp_info info (path, function_name, 0);
552
553 octave_link::post_event
554 (this, &file_editor_tab::remove_all_breakpoints_callback, info);
555 }
556
557 void
558 file_editor_tab::comment_selected_text (const QWidget* ID)
559 {
560 if (ID != this)
561 return;
562
283 do_comment_selected_text (true); 563 do_comment_selected_text (true);
284 } 564 }
285 565
286 void 566 void
287 file_editor_tab::uncomment_selected_text () 567 file_editor_tab::uncomment_selected_text (const QWidget* ID)
288 { 568 {
569 if (ID != this)
570 return;
571
289 do_comment_selected_text (false); 572 do_comment_selected_text (false);
573 }
574
575 void
576 file_editor_tab::handle_find_dialog_finished (int)
577 {
578 // Find dialog is going to hide. Save location of window for
579 // when it is reshown.
580 _find_dialog_geometry = _find_dialog->geometry ();
581 _find_dialog_is_visible = false;
582 }
583
584 void
585 file_editor_tab::find (const QWidget* ID)
586 {
587 if (ID != this)
588 return;
589
590 // The find_dialog feature doesn't need a slot for return info.
591 // Rather than Qt::DeleteOnClose, let the find feature hang about
592 // in case it contains useful information like previous searches
593 // and so on. Perhaps one find dialog for the whole editor is
594 // better, but individual find dialogs has the nice feature of
595 // retaining position per file editor tabs, which can be undocked.
596
597 if (!_find_dialog)
598 {
599 _find_dialog = new find_dialog (_edit_area);
600 connect (_find_dialog, SIGNAL (finished (int)),
601 this, SLOT (handle_find_dialog_finished (int)));
602 _find_dialog->setWindowModality (Qt::NonModal);
603 _find_dialog_geometry = _find_dialog->geometry ();
604 }
605
606 if (!_find_dialog->isVisible ())
607 {
608 _find_dialog->setGeometry (_find_dialog_geometry);
609 _find_dialog->show ();
610 _find_dialog_is_visible = true;
611 }
612
613 _find_dialog->activateWindow ();
290 } 614 }
291 615
292 void 616 void
293 file_editor_tab::do_comment_selected_text (bool comment) 617 file_editor_tab::do_comment_selected_text (bool comment)
294 { 618 {
316 _edit_area->endUndoAction (); 640 _edit_area->endUndoAction ();
317 } 641 }
318 } 642 }
319 643
320 void 644 void
321 file_editor_tab::find ()
322 {
323 find_dialog dialog (_edit_area);
324 dialog.exec ();
325 }
326
327 void
328 file_editor_tab::update_window_title (bool modified) 645 file_editor_tab::update_window_title (bool modified)
329 { 646 {
330 QString title(_file_name); 647 QString title ("");
648 if (_file_name.isEmpty () || _file_name.at (_file_name.count () - 1) == '/')
649 title = UNNAMED_FILE;
650 else
651 title = _file_name;
331 if ( !_long_title ) 652 if ( !_long_title )
332 { 653 {
333 QFileInfo file(_file_name); 654 QFileInfo file(_file_name);
334 title = file.fileName(); 655 title = file.fileName();
335 } 656 }
344 665
345 void 666 void
346 file_editor_tab::handle_copy_available(bool enableCopy) 667 file_editor_tab::handle_copy_available(bool enableCopy)
347 { 668 {
348 _copy_available = enableCopy; 669 _copy_available = enableCopy;
349 emit editor_state_changed (); 670 emit editor_state_changed (_copy_available, QDir::cleanPath (_file_name));
350 } 671 }
351 672
352 int 673 int
353 file_editor_tab::check_file_modified (const QString& msg, int cancelButton) 674 file_editor_tab::check_file_modified (const QString& msg, int cancelButton)
354 { 675 {
355 int decision = QMessageBox::Yes; 676 int decision = QMessageBox::Yes;
356 if (_edit_area->isModified ()) 677 if (_edit_area->isModified ())
357 { 678 {
358 // file is modified but not saved, ask user what to do 679 // File is modified but not saved, ask user what to do. The file
359 decision = QMessageBox::warning (this, 680 // editor tab can't be made parent because it may be deleted depending
360 msg, 681 // upon the response. Instead, change the _edit_area to read only.
361 tr ("The file %1\n" 682 QMessageBox* msgBox = new QMessageBox (
362 "has been modified. Do you want to save the changes?"). 683 QMessageBox::Warning, tr ("Octave Editor"),
363 arg (_file_name), 684 tr ("The file \'%1\' has been modified. Do you want to save the changes?").
364 QMessageBox::Save, 685 arg (_file_name), QMessageBox::Yes | QMessageBox::No, 0);
365 QMessageBox::Discard, cancelButton ); 686 _edit_area->setReadOnly (true);
366 if (decision == QMessageBox::Save) 687 connect (msgBox, SIGNAL (finished (int)),
367 { 688 this, SLOT (handle_file_modified_answer (int)));
368 save_file (); 689 msgBox->setWindowModality (Qt::NonModal);
369 if (_edit_area->isModified ()) 690 msgBox->setAttribute (Qt::WA_DeleteOnClose);
370 { 691 msgBox->show ();
371 // If the user attempted to save the file, but it's still 692 return (QMessageBox::Cancel);
372 // modified, then probably something went wrong, so return 693 }
373 // cancel for cancel this operation or try to save files 694 else
374 // as if cancel not possible 695 {
375 if ( cancelButton ) 696 // Nothing was modified, just remove from editor.
376 return (QMessageBox::Cancel); 697 emit tab_remove_request ();
377 else 698 }
378 save_file_as (); 699
379 }
380 }
381 }
382 return (decision); 700 return (decision);
383 } 701 }
384 702
385 void 703 void
386 file_editor_tab::remove_bookmark () 704 file_editor_tab::handle_file_modified_answer (int decision)
387 { 705 {
388 _edit_area->markerDeleteAll (bookmark); 706 if (decision == QMessageBox::Yes)
389 } 707 {
390 708 // Save file, then remove from editor.
391 void 709 save_file (_file_name, true);
392 file_editor_tab::toggle_bookmark () 710 }
393 { 711 else if (decision == QMessageBox::No)
394 int line, cur; 712 {
395 _edit_area->getCursorPosition (&line,&cur); 713 // User doesn't want to save, just remove from editor.
396 if ( _edit_area->markersAtLine (line) && (1 << bookmark) ) 714 emit tab_remove_request ();
397 _edit_area->markerDelete (line, bookmark); 715 }
398 else 716 else
399 _edit_area->markerAdd (line, bookmark); 717 {
400 } 718 // User canceled, allow editing again.
401 719 _edit_area->setReadOnly (false);
402 void
403 file_editor_tab::next_bookmark()
404 {
405 int line, cur, nextline;
406 _edit_area->getCursorPosition (&line, &cur);
407 if ( _edit_area->markersAtLine (line) && (1 << bookmark) )
408 line++; // we have a breakpoint here, so start search from next line
409 nextline = _edit_area->markerFindNext (line, (1 << bookmark));
410 _edit_area->setCursorPosition (nextline, 0);
411 }
412
413 void
414 file_editor_tab::previous_bookmark ()
415 {
416 int line, cur, prevline;
417 _edit_area->getCursorPosition (&line, &cur);
418 if ( _edit_area->markersAtLine (line) && (1 << bookmark) )
419 line--; // we have a breakpoint here, so start search from prev line
420 prevline = _edit_area->markerFindPrevious (line, (1 << bookmark));
421 _edit_area->setCursorPosition (prevline, 0);
422 }
423
424 void
425 file_editor_tab::remove_all_breakpoints ()
426 {
427 QFileInfo file_info (_file_name);
428 QString path = file_info.absolutePath ();
429 QString function_name = file_info.fileName ();
430
431 // We have to cut off the suffix, because octave appends it.
432 function_name.chop (file_info.suffix ().length () + 1);
433
434 bp_info info (path, function_name, 0);
435
436 octave_link::post_event
437 (this, &file_editor_tab::remove_all_breakpoints_callback, info);
438 }
439
440 void
441 file_editor_tab::toggle_breakpoint ()
442 {
443 int line, cur;
444 _edit_area->getCursorPosition (&line, &cur);
445 if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
446 request_remove_breakpoint (line);
447 else
448 request_add_breakpoint (line);
449 }
450
451 void
452 file_editor_tab::next_breakpoint ()
453 {
454 int line, cur, nextline;
455 _edit_area->getCursorPosition (&line, &cur);
456 if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
457 line++; // we have a breakpoint here, so start search from next line
458 nextline = _edit_area->markerFindNext (line, (1 << breakpoint));
459 _edit_area->setCursorPosition (nextline, 0);
460 }
461
462 void
463 file_editor_tab::previous_breakpoint ()
464 {
465 int line, cur, prevline;
466 _edit_area->getCursorPosition (&line, &cur);
467 if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
468 line--; // we have a breakpoint here, so start search from prev line
469 prevline = _edit_area->markerFindPrevious (line, (1 << breakpoint));
470 _edit_area->setCursorPosition (prevline, 0);
471 }
472
473 void
474 file_editor_tab::cut ()
475 {
476 _edit_area->cut ();
477 }
478
479 void
480 file_editor_tab::copy ()
481 {
482 _edit_area->copy ();
483 }
484
485 void
486 file_editor_tab::paste ()
487 {
488 _edit_area->paste ();
489 }
490
491 void
492 file_editor_tab::undo ()
493 {
494 _edit_area->undo ();
495 }
496
497 void
498 file_editor_tab::redo ()
499 {
500 _edit_area->redo ();
501 }
502
503 void
504 file_editor_tab::set_debugger_position (int line)
505 {
506 _edit_area->markerDeleteAll (debugger_position);
507 if (line > 0)
508 {
509 _edit_area->markerAdd (line, debugger_position);
510 } 720 }
511 } 721 }
512 722
513 void 723 void
514 file_editor_tab::set_modified (bool modified) 724 file_editor_tab::set_modified (bool modified)
515 { 725 {
516 _edit_area->setModified (modified); 726 _edit_area->setModified (modified);
517 } 727 }
518 728
519 bool 729 QString
520 file_editor_tab::open_file (const QString& dir) 730 file_editor_tab::load_file(const QString& fileName)
521 { 731 {
522 QString openFileName;
523 QFileDialog fileDialog(this);
524 fileDialog.setNameFilter(SAVE_FILE_FILTER);
525 fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
526 fileDialog.setViewMode(QFileDialog::Detail);
527 fileDialog.setDirectory(dir);
528 if (fileDialog.exec () == QDialog::Accepted)
529 {
530 openFileName = fileDialog.selectedFiles().at(0);
531 if (openFileName.isEmpty ())
532 return false;
533
534 return load_file(openFileName);
535 }
536 else
537 {
538 return false;
539 }
540 }
541
542 bool
543 file_editor_tab::load_file(const QString& fileName, bool silent)
544 {
545 if (!_file_editor->isVisible ())
546 {
547 _file_editor->show ();
548 }
549
550 QFile file (fileName); 732 QFile file (fileName);
551 if (!file.open (QFile::ReadOnly)) 733 if (!file.open (QFile::ReadOnly))
552 { 734 {
553 if (silent==false) 735 return file.errorString ();
554 QMessageBox::warning (this, tr ("Octave Editor"),
555 tr ("Could not open file %1 for read:\n%2.").arg (fileName).
556 arg (file.errorString ()));
557 return false;
558 } 736 }
559 737
560 QTextStream in (&file); 738 QTextStream in (&file);
561 QApplication::setOverrideCursor (Qt::WaitCursor); 739 QApplication::setOverrideCursor (Qt::WaitCursor);
562 _edit_area->setText (in.readAll ()); 740 _edit_area->setText (in.readAll ());
564 742
565 set_file_name (fileName); 743 set_file_name (fileName);
566 update_window_title (false); // window title (no modification) 744 update_window_title (false); // window title (no modification)
567 _edit_area->setModified (false); // loaded file is not modified yet 745 _edit_area->setModified (false); // loaded file is not modified yet
568 746
569 return true; 747 return QString ();
570 } 748 }
571 749
572 void 750 void
573 file_editor_tab::new_file () 751 file_editor_tab::new_file ()
574 { 752 {
575 if (!_file_editor->isVisible ())
576 {
577 _file_editor->show ();
578 }
579
580 set_file_name (UNNAMED_FILE);
581 update_window_title (false); // window title (no modification) 753 update_window_title (false); // window title (no modification)
582 _edit_area->setText (""); 754 _edit_area->setText ("");
583 _edit_area->setModified (false); // new file is not modified yet 755 _edit_area->setModified (false); // new file is not modified yet
584 } 756 }
585 757
586 bool file_editor_tab::save_file() 758 void
587 { 759 file_editor_tab::save_file (const QString& saveFileName, bool remove_on_success)
588 return save_file (_file_name); 760 {
589 } 761 // If it is a new file with no name, signal that saveFileAs
590 762 // should be performed.
591 bool 763 if (saveFileName.isEmpty () || saveFileName.at (saveFileName.count () - 1) == '/')
592 file_editor_tab::save_file (const QString& saveFileName) 764 {
593 { 765 save_file_as (remove_on_success);
594 // it is a new file with the name "<unnamed>" -> call saveFielAs 766 return;
595 if (saveFileName == UNNAMED_FILE || saveFileName.isEmpty ()) 767 }
596 { 768
597 return save_file_as(); 769 // stop watching file
598 } 770 QStringList trackedFiles = _file_system_watcher.files ();
599 771 if (!trackedFiles.isEmpty ())
600 // remove the file to save from the tracker since we will change it on disk now 772 _file_system_watcher.removePath (saveFileName);
601 QStringList watched_files = _file_system_watcher.files(); 773
602 if (!watched_files.isEmpty ())
603 _file_system_watcher.removePath(saveFileName);
604
605 // open the file for writing 774 // open the file for writing
606 QFile file (saveFileName); 775 QFile file (saveFileName);
607 if (!file.open (QFile::WriteOnly)) 776 if (!file.open (QIODevice::WriteOnly))
608 { 777 {
609 QMessageBox::warning (this, tr ("Octave Editor"), 778 // Unsuccessful, begin watching file again if it was being
610 tr ("Could not open file %1 for write:\n%2."). 779 // watched previously.
611 arg (saveFileName).arg (file.errorString ())); 780 if (trackedFiles.contains (saveFileName))
612 return false; 781 _file_system_watcher.addPath (saveFileName);
782
783 // Create a NonModal message about error.
784 QMessageBox* msgBox = new QMessageBox (
785 QMessageBox::Critical, tr ("Octave Editor"),
786 tr ("Could not open file %1 for write:\n%2.").
787 arg (saveFileName).arg (file.errorString ()),
788 QMessageBox::Ok, 0);
789 msgBox->setWindowModality (Qt::NonModal);
790 msgBox->setAttribute (Qt::WA_DeleteOnClose);
791 msgBox->show ();
792 return;
613 } 793 }
614 794
615 // save the contents into the file 795 // save the contents into the file
616 QTextStream out (&file); 796 QTextStream out (&file);
617 QApplication::setOverrideCursor (Qt::WaitCursor); 797 QApplication::setOverrideCursor (Qt::WaitCursor);
618 out << _edit_area->text (); 798 out << _edit_area->text ();
619 QApplication::restoreOverrideCursor (); 799 QApplication::restoreOverrideCursor ();
620 file.close(); 800 file.close();
621 801
622 // save file name after closing file otherwise tracker will notice file change 802 // save file name after closing file as set_file_name starts watching again
623 set_file_name (saveFileName); 803 set_file_name (saveFileName);
624 // set the window title to actual file name (not modified) 804 // set the window title to actual file name (not modified)
625 update_window_title (false); 805 update_window_title (false);
626 // files is save -> not modified 806 // files is save -> not modified
627 _edit_area->setModified (false); 807 _edit_area->setModified (false);
628 808
629 return true; 809 if (remove_on_success)
630 } 810 {
631 811 emit tab_remove_request ();
632 bool 812 return; // Don't touch member variables after removal
633 file_editor_tab::save_file_as () 813 }
634 { 814 }
635 QString saveFileName(_file_name); 815
636 QFileDialog fileDialog(this); 816 void
637 if (saveFileName == UNNAMED_FILE || saveFileName.isEmpty ()) 817 file_editor_tab::save_file_as (bool remove_on_success)
638 { 818 {
639 QString directory = QString::fromStdString 819 // Simply put up the file chooser dialog box with a slot connection
640 (octave_link::last_working_directory ()); 820 // then return control to the system waiting for a file selection.
641 821
642 if (directory.isEmpty ()) 822 // If the tab is removed in response to a QFileDialog signal, the tab
823 // can't be a parent.
824 QFileDialog* fileDialog;
825 if (remove_on_success)
826 {
827 // If tab is closed, "this" cannot be parent in which case modality
828 // has no effect. Disable editing instead.
829 _edit_area->setReadOnly (true);
830 fileDialog = new QFileDialog ();
831 }
832 else
833 fileDialog = new QFileDialog (this);
834
835 if (!_file_name.isEmpty () && _file_name.at (_file_name.count () - 1) != '/')
836 {
837 fileDialog->selectFile (_file_name);
838 }
839 else
840 {
841 fileDialog->selectFile ("");
842 if (_file_name.isEmpty ())
643 { 843 {
644 directory = QDir::homePath (); 844 fileDialog->setDirectory (QDir::currentPath ());
645 }
646
647 fileDialog.setDirectory (directory);
648 }
649 else
650 {
651 fileDialog.selectFile (saveFileName);
652 }
653 fileDialog.setNameFilter (SAVE_FILE_FILTER);
654 fileDialog.setDefaultSuffix ("m");
655 fileDialog.setAcceptMode (QFileDialog::AcceptSave);
656 fileDialog.setViewMode (QFileDialog::Detail);
657
658 if (fileDialog.exec ())
659 {
660 saveFileName = fileDialog.selectedFiles ().at (0);
661 if (saveFileName.isEmpty ())
662 return false;
663
664 return save_file (saveFileName);
665 }
666
667 return false;
668 }
669
670 void
671 file_editor_tab::run_file ()
672 {
673 if (_edit_area->isModified ())
674 save_file(_file_name);
675
676 QFileInfo file_info (_file_name);
677 QString path = file_info.absolutePath ();
678 QString current_path
679 = QString::fromStdString (octave_link::last_working_directory ());
680 QString function_name = file_info.fileName ();
681
682 // We have to cut off the suffix, because octave appends it.
683 function_name.chop (file_info.suffix ().length () + 1);
684 _file_editor->terminal ()->sendText (QString ("cd \'%1\'\n%2\n")
685 .arg(path).arg (function_name));
686 // TODO: Sending a run event crashes for long scripts. Find out why.
687 // octave_link::post_event
688 // (this, &file_editor_tab::run_file_callback, _file_name.toStdString ()));
689 }
690
691 void
692 file_editor_tab::file_has_changed (const QString&)
693 {
694 if (QFile::exists (_file_name))
695 {
696 // Prevent popping up multiple message boxes when the file has
697 // been changed multiple times.
698 static bool alreadyAsking = false;
699 if (!alreadyAsking)
700 {
701 alreadyAsking = true;
702
703 int decision =
704 QMessageBox::warning (this, tr ("Octave Editor"),
705 tr ("It seems that \'%1\' has been modified by another application. Do you want to reload it?").
706 arg (_file_name), QMessageBox::Yes,
707 QMessageBox::No);
708
709 if (decision == QMessageBox::Yes)
710 {
711 load_file (_file_name);
712 }
713
714 alreadyAsking = false;
715 }
716 }
717 else
718 {
719 int decision =
720 QMessageBox::warning (this, tr ("Octave Editor"),
721 tr ("It seems that \'%1\' has been deleted or renamed. Do you want to save it now?").
722 arg (_file_name), QMessageBox::Save,
723 QMessageBox::Close);
724 if (decision == QMessageBox::Save)
725 {
726 if (!save_file_as ())
727 {
728 set_file_name (UNNAMED_FILE);
729 update_window_title (true); // window title (no modification)
730 set_modified (true);
731 }
732 } 845 }
733 else 846 else
734 { 847 {
735 emit close_request (); 848 // The file name is actually the directory name from the
849 // constructor argument.
850 fileDialog->setDirectory (_file_name);
736 } 851 }
852 }
853 fileDialog->setNameFilter (SAVE_FILE_FILTER);
854 fileDialog->setDefaultSuffix ("m");
855 fileDialog->setAcceptMode (QFileDialog::AcceptSave);
856 fileDialog->setViewMode (QFileDialog::Detail);
857 if (remove_on_success)
858 {
859 connect (fileDialog, SIGNAL (fileSelected (const QString&)),
860 this, SLOT (handle_save_file_as_answer_close (const QString&)));
861 connect (fileDialog, SIGNAL (rejected ()),
862 this, SLOT (handle_save_file_as_answer_cancel ()));
863 }
864 else
865 {
866 connect (fileDialog, SIGNAL (fileSelected (const QString&)),
867 this, SLOT (handle_save_file_as_answer (const QString&)));
868 }
869 fileDialog->setWindowModality (Qt::WindowModal);
870 fileDialog->setAttribute (Qt::WA_DeleteOnClose);
871 fileDialog->show ();
872 }
873
874 void
875 file_editor_tab::message_duplicate_file_name (const QString& saveFileName)
876 {
877 // Could overwrite the file here (and tell user the file was
878 // overwritten), but the user could have unintentionally
879 // selected the same name not intending to overwrite.
880
881 // Create a NonModal message about error.
882 QMessageBox* msgBox = new QMessageBox (
883 QMessageBox::Critical, tr ("Octave Editor"),
884 tr ("File not saved! You've selected a file name\n\n %1\n\nwhich is the same as the current file name. Use ""Save"" to overwrite. (Could allow overwriting, with message, if that is what folks want.)").
885 arg (saveFileName),
886 QMessageBox::Ok, 0);
887 msgBox->setWindowModality (Qt::NonModal);
888 msgBox->setAttribute (Qt::WA_DeleteOnClose);
889 msgBox->show ();
890 }
891
892 void
893 file_editor_tab::handle_save_file_as_answer (const QString& saveFileName)
894 {
895 if (saveFileName == _file_name)
896 {
897 message_duplicate_file_name (saveFileName);
898 // Nothing done, allow editing again.
899 _edit_area->setReadOnly (false);
900 }
901 else
902 {
903 // Have editor check for conflict, do not delete tab after save.
904 emit editor_check_conflict_save (saveFileName, false);
905 }
906 }
907
908 void
909 file_editor_tab::handle_save_file_as_answer_close (const QString& saveFileName)
910 {
911 if (saveFileName == _file_name)
912 {
913 message_duplicate_file_name (saveFileName);
914 // Nothing done, allow editing again.
915 _edit_area->setReadOnly (false);
916 }
917 else
918 {
919 // Have editor check for conflict, delete tab after save.
920 emit editor_check_conflict_save (saveFileName, true);
921 }
922 }
923
924 void
925 file_editor_tab::handle_save_file_as_answer_cancel ()
926 {
927 // User canceled, allow editing again.
928 _edit_area->setReadOnly (false);
929 }
930
931 void
932 file_editor_tab::file_has_changed (const QString&)
933 {
934 // Prevent popping up multiple message boxes when the file has
935 // been changed multiple times by temporarily removing from the
936 // file watcher.
937 QStringList trackedFiles = _file_system_watcher.files ();
938 if (!trackedFiles.isEmpty ())
939 _file_system_watcher.removePath (_file_name);
940
941 if (QFile::exists (_file_name))
942 {
943 // Create a WindowModal message that blocks the edit area
944 // by making _edit_area parent.
945 QMessageBox* msgBox = new QMessageBox (
946 QMessageBox::Warning, tr ("Octave Editor"),
947 tr ("It seems that \'%1\' has been modified by another application. Do you want to reload it?").
948 arg (_file_name), QMessageBox::Yes | QMessageBox::No, this);
949 connect (msgBox, SIGNAL (finished (int)),
950 this, SLOT (handle_file_reload_answer (int)));
951 msgBox->setWindowModality (Qt::WindowModal);
952 msgBox->setAttribute (Qt::WA_DeleteOnClose);
953 msgBox->show ();
954 }
955 else
956 {
957 // Create a WindowModal message that blocks the edit area
958 // by making _edit_area parent.
959 QMessageBox* msgBox = new QMessageBox (
960 QMessageBox::Warning, tr ("Octave Editor"),
961 tr ("It seems that \'%1\' has been deleted or renamed. Do you want to save it now?").
962 arg (_file_name), QMessageBox::Save | QMessageBox::Close, this);
963 connect (msgBox, SIGNAL (finished (int)),
964 this, SLOT (handle_file_resave_answer (int)));
965 msgBox->setWindowModality (Qt::WindowModal);
966 msgBox->setAttribute (Qt::WA_DeleteOnClose);
967 msgBox->show ();
737 } 968 }
738 } 969 }
739 970
740 void 971 void
741 file_editor_tab::notice_settings () 972 file_editor_tab::notice_settings ()
773 1004
774 update_window_title (false); 1005 update_window_title (false);
775 } 1006 }
776 1007
777 void 1008 void
778 file_editor_tab::run_file_callback (void) 1009 file_editor_tab::conditional_close (const QWidget* ID)
779 { 1010 {
780 // Maybe someday we will do something here? 1011 if (ID != this)
781 } 1012 return;
782 1013
783 void 1014 close ();
784 file_editor_tab::add_breakpoint_callback (const bp_info& info) 1015 }
785 { 1016
786 bp_table::intmap intmap; 1017 void
787 intmap[0] = info.line + 1; 1018 file_editor_tab::change_editor_state (const QWidget* ID)
788 1019 {
789 std::string previous_directory = octave_env::get_current_directory (); 1020 if (ID != this)
790 octave_env::chdir (info.path); 1021 {
791 intmap = bp_table::add_breakpoint (info.function_name, intmap); 1022 // Widget may be going out of focus. If so, record location.
792 octave_env::chdir (previous_directory); 1023 if (_find_dialog)
793 1024 {
794 if (intmap.size () > 0) 1025 if (_find_dialog->isVisible ())
795 { 1026 {
796 // FIXME -- Check file. 1027 _find_dialog_geometry = _find_dialog->geometry ();
797 _edit_area->markerAdd (info.line, breakpoint); 1028 _find_dialog->hide ();
798 } 1029 }
799 } 1030 }
800 1031 return;
801 void 1032 }
802 file_editor_tab::remove_breakpoint_callback (const bp_info& info) 1033
803 { 1034 if (_find_dialog && _find_dialog_is_visible)
804 bp_table::intmap intmap; 1035 {
805 intmap[0] = info.line; 1036 _find_dialog->setGeometry (_find_dialog_geometry);
806 1037 _find_dialog->show ();
807 std::string previous_directory = octave_env::get_current_directory (); 1038 }
808 octave_env::chdir (info.path); 1039 emit editor_state_changed (_copy_available, QDir::cleanPath (_file_name));
809 bp_table::remove_breakpoint (info.function_name, intmap); 1040 }
810 octave_env::chdir (previous_directory); 1041
811 1042 void
812 // FIXME -- check result 1043 file_editor_tab::file_name_query (const QWidget* ID)
813 bool success = true; 1044 {
814 1045 // A zero (null pointer) means that all file editor tabs
815 if (success) 1046 // should respond, otherwise just the desired file editor tab.
816 { 1047 if (ID != this && ID != 0)
817 // FIXME -- check file. 1048 return;
818 _edit_area->markerDelete (info.line, breakpoint); 1049
819 } 1050 // Unnamed files shouldn't be transmitted.
820 } 1051 if (!_file_name.isEmpty ())
821 1052 emit add_filename_to_list (_file_name);
822 void 1053 }
823 file_editor_tab::remove_all_breakpoints_callback (const bp_info& info) 1054
824 { 1055 void
825 bp_table::intmap intmap; 1056 file_editor_tab::handle_file_reload_answer (int decision)
826 std::string previous_directory = octave_env::get_current_directory (); 1057 {
827 octave_env::chdir (info.path); 1058 if (decision == QMessageBox::Yes)
828 intmap = bp_table::remove_all_breakpoints_in_file (info.function_name, true); 1059 {
829 octave_env::chdir (previous_directory); 1060 load_file (_file_name);
830 1061 }
831 if (intmap.size() > 0) 1062
832 _edit_area->markerDeleteAll (breakpoint); 1063 // Start watching file once again.
833 } 1064 _file_system_watcher.addPath (_file_name);
1065 }
1066
1067 void
1068 file_editor_tab::handle_file_resave_answer (int decision)
1069 {
1070 if (decision == QMessageBox::Save)
1071 {
1072 save_file (_file_name);
1073 }
1074 else
1075 {
1076 if (close ())
1077 {
1078 emit tab_remove_request ();
1079 return; // Don't touch member variables after removal
1080 }
1081 }
1082
1083 // Start watching file once again.
1084 _file_system_watcher.addPath (_file_name);
1085 }
1086
1087 void
1088 file_editor_tab::set_debugger_position (int line)
1089 {
1090 _edit_area->markerDeleteAll (debugger_position);
1091 if (line > 0)
1092 {
1093 _edit_area->markerAdd (line, debugger_position);
1094 }
1095 }