diff options
Diffstat (limited to 'src/editortabs.cpp')
-rw-r--r-- | src/editortabs.cpp | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/src/editortabs.cpp b/src/editortabs.cpp new file mode 100644 index 0000000..5af9be1 --- /dev/null +++ b/src/editortabs.cpp @@ -0,0 +1,640 @@ +/*************************************************************************** + * + * Copyright (C) 2005 Elad Lahav ([email protected]) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ***************************************************************************/ + +#include <qfileinfo.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kurldrag.h> +#include <kate/document.h> +#include "editortabs.h" +#include "kscopepixmaps.h" +#include "queryview.h" + +/** + * Class constructor. + * @param pParent The parent widget + * @param szName The widget's name + */ +EditorTabs::EditorTabs(QWidget* pParent, const char* szName) : + TabWidget(pParent, szName), + m_pCurPage(NULL), + m_pWindowMenu(NULL), + m_nWindowMenuItems(0), + m_nNewFiles(0) +{ + // Display close buttons + setHoverCloseButton(true); + + // Accept file drops + setAcceptDrops(true); + + // Close an editor page when its close button is clicked + connect(this, SIGNAL(closeRequest(QWidget*)), this, + SLOT(slotRemovePage(QWidget*))); + + // Set an editor page as the active part, when its tab is selected + connect(this, SIGNAL(currentChanged(QWidget*)), this, + SLOT(slotCurrentChanged(QWidget*))); + + // Start dragging a file from a tab + connect(this, SIGNAL(initiateDrag(QWidget*)), this, + SLOT(slotInitiateDrag(QWidget*))); +} + +/** + * Class destructor. + */ +EditorTabs::~EditorTabs() +{ +} + +/** + * @param pWindowMenu Pointer to the main window's "Window" menu (used to + * add an activation menu item for each editor page) + */ +void EditorTabs::setWindowMenu(QPopupMenu* pWindowMenu) +{ + m_pWindowMenu = pWindowMenu; + connect(pWindowMenu, SIGNAL(aboutToShow()), this, + SLOT(slotFillWindowMenu())); + connect(pWindowMenu, SIGNAL(activated(int)), this, + SLOT(slotSetCurrentPage(int))); +} + +/** + * Adds a new editor page to the tab widget. + * @param pNewPage The page to add + */ +void EditorTabs::addEditorPage(EditorPage* pNewPage) +{ + // Create a new tab and set is as the current one + insertTab(pNewPage, ""); + showPage(pNewPage); + + // Add the file edited by this page to the map, and display its name, + // once the file is opened + connect(pNewPage, SIGNAL(fileOpened(EditorPage*, const QString&)), this, + SLOT(slotAttachFile(EditorPage*, const QString&))); + + // Handle new unnamed files + connect(pNewPage, SIGNAL(newFile(EditorPage*)), this, + SLOT(slotNewFile(EditorPage*))); + + // Change tab icon when a file is modified + connect(pNewPage, SIGNAL(modified(EditorPage*, bool)), this, + SLOT(slotFileModified(EditorPage*, bool))); + + // If this is the first page, the current page will not be set by the + // signal handler, so we need to do it manually + if (count() == 1) + slotCurrentChanged(pNewPage); +} + +/** + * Finds and displays a page editing the given file. + * NOTE: The bForceChange parameters is used as a fix for the GUI merge + * problem arising when the found page is the current one. + * @param sFileName The name of the file to search + * @param bForceChange If set to true, the method will emit the signal + * editorChanged() even if the found page is the + * current one + * @return The editor page object, if found, NULL otherwise + */ +EditorPage* EditorTabs::findEditorPage(const QString& sFileName, + bool bForceChange) +{ + EditorMap::iterator itr; + EditorPage* pPage; + bool bEmit; + + // Find the page according to the associated file name + itr = m_mapEdit.find(sFileName); + if (itr == m_mapEdit.end()) + return NULL; + + // Set the page as the current one + pPage = *itr; + bEmit = (bForceChange && (pPage == m_pCurPage)); + showPage(pPage); + + // Emit the editorChanged() signal, if required + if (bEmit) + emit editorChanged(NULL, m_pCurPage); + + return *itr; +} + +/** + * Returns the page associated with the selected tab. + * @return The current editor page + */ +EditorPage* EditorTabs::getCurrentPage() +{ + return (EditorPage*)currentPage(); +} + +/** + * Deletes the currently active page. + * Finds the current page, closes its editor window and deletes the page. + * If other editors are open, another page becomes active. + */ +void EditorTabs::removeCurrentPage() +{ + QWidget* pPage; + + // Get the active page, if any + pPage = currentPage(); + if (pPage == NULL) + return; + + // Close the editor window + removePage(pPage, false); +} + +/** + * Removes all editor pages. + * @return true if successful, false if the user aborts the operation + */ +bool EditorTabs::removeAllPages() +{ + QWidget* pPage; + + // Check if there are any modified files + if (getModifiedFilesCount()) { + // Prompt the user to save these files + switch (KMessageBox::questionYesNoCancel(NULL, + i18n("Some files contain unsaved changes.\nWould you like to " + "save these files?"))) { + case KMessageBox::Yes: + // Save files + slotSaveAll(); + break; + + case KMessageBox::No: + // Close files, ignoring changes + break; + + case KMessageBox::Cancel: + // Abort + return false; + } + } + + // Avoid warning about modification on disk + Kate::Document::setFileChangedDialogsActivated(false); + + // Iterate pages until none is left + while ((pPage = currentPage()) != NULL) + removePage(pPage, true); + + // Restore kate warning if enabled + Kate::Document::setFileChangedDialogsActivated( + Config().getWarnModifiedOnDisk()); + + // All pages were successfully removed + return true; +} + +/** + * Keeps track of the currently active editor page, and notifies on a change + * in the active page. + * This slot is connected to the currentChanged() signal of the QTabWidget + * object. + * @param pWidget The new active page + */ +void EditorTabs::slotCurrentChanged(QWidget* pWidget) +{ + EditorPage* pOldPage; + + // TODO: + // For some reason, this slot is being called twice for every external + // tab activation (e.g., through the Window menu). + // We avoid it, but this really needs to be fixed properly. + if (pWidget == m_pCurPage) + return; + + // Set the new active page + pOldPage = m_pCurPage; + m_pCurPage = (EditorPage*)pWidget; + + if (m_pCurPage) { + // Set the keyboard focus to the editor part of the page + m_pCurPage->setEditorFocus(); + + // Adjust the splitter sizes + m_pCurPage->setLayout(Config().getShowTagList(), + Config().getEditorSizes()); + } + + /* Notify the main window */ + emit editorChanged(pOldPage, m_pCurPage); +} + +/** + * Updates the tab of an editor page to reflect the newly opened file. + * This slot is attached to the fileOpened() signal of an EditorPage object. + * @param pEditPage Pointer to the calling object + * @param sFilePath The full path of the file edited in this page + */ +void EditorTabs::slotAttachFile(EditorPage* pEditPage, + const QString& sFilePath) +{ + // Set the appropriate tab icon, according to the file permissions + if (pEditPage->isWritable()) + setTabIconSet(pEditPage, Pixmaps().getPixmap(KScopePixmaps::TabRW)); + else + setTabIconSet(pEditPage, Pixmaps().getPixmap(KScopePixmaps::TabRO)); + + // Do nothing if the file name has not changed + if (m_mapEdit[sFilePath] == pEditPage) + return; + + // Set the tab caption to the file name, and a tool-tip to the full path + changeTab(pEditPage, pEditPage->getFileName()); + setTabToolTip(pEditPage, sFilePath); + + // Associate the EditorPage object with its file name + m_mapEdit[sFilePath] = pEditPage; +} + +/** + * Marks a page as containing a new unnamed file. + * This slot is attached to the newFile() signal of an EditorPage object. + * @param pEditPage Pointer to the calling object + */ +void EditorTabs::slotNewFile(EditorPage* pEditPage) +{ + QString sCaption; + + // Set the tab caption to mark a new file + m_nNewFiles++; + sCaption = i18n("Untitled ") + QString::number(m_nNewFiles); + changeTab(pEditPage, + Pixmaps().getPixmap(KScopePixmaps::TabRW), + sCaption); + setTabToolTip(pEditPage, i18n("New unsaved file")); +} + +/** + * Applies the user's colour and font preferences to all pages. + */ +void EditorTabs::applyPrefs() +{ + EditorPage* pPage; + int i; + + // Iterate editor pages + for (i = 0; i < count(); i++) { + pPage = (EditorPage*)page(i); + pPage->applyPrefs(); + setTabIconSet(pPage, Pixmaps().getPixmap(pPage->isWritable() ? + KScopePixmaps::TabRW : KScopePixmaps::TabRO)); + } +} + +/** + * Fills a list with the paths and cursor positions of all files currently + * open. + * @param list The list to fill + */ +void EditorTabs::getOpenFiles(FileLocationList& list) +{ + int i; + EditorPage* pPage; + uint nLine, nCol; + + // Iterate over all editor pages + for (i = 0; i < count(); i++) { + // Obtain file and cursor position information + pPage = (EditorPage*)page(i); + if (!pPage->getCursorPos(nLine, nCol)) { + nLine = 1; + nCol = 1; + } + + // Create a new list item + list.append(new FileLocation(pPage->getFilePath(), nLine, nCol)); + } +} + +/** + * Constructs a list bookmarks set to open files. + * Used to store all currently set bookmarks when a session is closed. + * @param fll The list to fill + */ +void EditorTabs::getBookmarks(FileLocationList& fll) +{ + int i; + EditorPage* pPage; + + // Iterate over all editor pages + for (i = 0; i < count(); i++) { + pPage = (EditorPage*)page(i); + pPage->getBookmarks(fll); + } +} + +/** + * Assigns bookmarks to open files. + * Called when a session is opened, to restore any bookmarks set to existing + * editor pages. + * @param fll A list of bookmark positions + */ +void EditorTabs::setBookmarks(FileLocationList& fll) +{ + FileLocation* pLoc; + EditorMap::iterator itr; + EditorPage* pPage; + + // Iterate over the list of bookmarks + for (pLoc = fll.first(); pLoc; pLoc = fll.next()) { + itr = m_mapEdit.find(pLoc->m_sPath); + // Get the relevant page, if any + if (itr != m_mapEdit.end()) { + pPage = *itr; + pPage->addBookmark(pLoc->m_nLine); + } + } +} + +/** + * Fills a QueryView object with the list of currently active bookmarks. + * @param pView The widget to use for displaying bookmarks + */ +void EditorTabs::showBookmarks(QueryView* pView) +{ + int i; + EditorPage* pPage; + FileLocationList fll; + FileLocation* pLoc; + + fll.setAutoDelete(true); + + // Iterate over all editor pages + for (i = 0; i < count(); i++) { + // Obtain file and cursor position information + pPage = (EditorPage*)page(i); + pPage->getBookmarks(fll); + + // Populate the view + for (pLoc = fll.first(); pLoc; pLoc = fll.next()) { + pView->addRecord("", pLoc->m_sPath, + QString::number(pLoc->m_nLine + 1), + pPage->getLineContents(pLoc->m_nLine + 1)); + } + + fll.clear(); + } +} + +/** + * Removes an editor page. + * If there are unsaved changes, the user is prompted, and the file is closed + * according to the user's choice. + * This slot is connected to the clicked() signal of the tab's close button. + * @param pPage The EditorPage object to remove + */ +void EditorTabs::slotRemovePage(QWidget* pPage) +{ + removePage(pPage, false); +} + +/** + * Handles the "View->Show/Hide Tag List" menu item. + * Shows/hides the tag list for the current page, and sets the default values + * for all pages. + */ +void EditorTabs::slotToggleTagList() +{ + EditorPage* pPage; + + // Change the default value + Config().setShowTagList(!Config().getShowTagList()); + + // Apply for the current page, if any + if ((pPage = (EditorPage*)currentPage()) != NULL) { + pPage->setLayout(Config().getShowTagList(), + Config().getEditorSizes()); + } +} + +/** + * Handles drag events over an empty tab widget, or over the tab bar. + * The event is accepted if the dragged object is a list of file paths. + * @param pEvent The drag move event object + */ +void EditorTabs::dragMoveEvent(QDragMoveEvent* pEvent) +{ + KURL::List list; + bool bAccept; + + bAccept = KURLDrag::decode(pEvent, list); + pEvent->accept(bAccept); +} + +/** + * Handles file drops over an empty tab widget, or over the tab bar. + * @param pEvent The drop event object + */ +void EditorTabs::dropEvent(QDropEvent* pEvent) +{ + emit filesDropped(pEvent); +} + +/** + * Called when an editor tab is dragged from the tab widget. + * Initialises the drag operation with a URL that corresponds to the path of + * the file being edited in the corresponding page. + * This slot is connected to the initiateDrag() signal emitted by the tab + * widget. + * @param pWidget The page whose tab is being dragged + */ +void EditorTabs::slotInitiateDrag(QWidget* pWidget) +{ + KURL url; + KURLDrag* pDrag; + + // Create a URL list containing the appropriate file path + url.setPath(((EditorPage*)pWidget)->getFilePath()); + pDrag = new KURLDrag(KURL::List(url), this); + + // Start the drag + pDrag->dragCopy(); +} + +/** + * Changes the tab icon of a modified file. + * @param pEditPage The editor page whose file was modified + * @param bModified true if the file has changed its status to modified, + * false otherwise (i.e., when undo operations restore it + * to its original contents.) + */ +void EditorTabs::slotFileModified(EditorPage* pEditPage, bool bModified) +{ + if (bModified) + setTabIconSet(pEditPage, Pixmaps().getPixmap(KScopePixmaps::TabSave)); + else + setTabIconSet(pEditPage, Pixmaps().getPixmap(KScopePixmaps::TabRW)); +} + +/** + * Counts the number of pages containing modified files. + * @return The number of modified files + */ +int EditorTabs::getModifiedFilesCount() +{ + int i, nResult; + + // Iterate through pages + for (i = 0, nResult = 0; i < count(); i++) { + if (((EditorPage*)page(i))->isModified()) + nResult++; + } + + return nResult; +} + +/** + * Saves all files open for editing. + */ +void EditorTabs::slotSaveAll() +{ + int i; + + // Iterate through pages + for (i = 0; i < count(); i++) + ((EditorPage*)page(i))->save(); +} + +/** + * Selects the page to the left of the current one. + */ +void EditorTabs::slotGoLeft() +{ + int nIndex; + + nIndex = currentPageIndex(); + if (nIndex > 0) { + nIndex--; + setCurrentPage(nIndex); + } +} + +/** + * Selects the page to the right of the current one. + */ +void EditorTabs::slotGoRight() +{ + int nIndex; + + nIndex = currentPageIndex(); + if (nIndex < count() - 1) { + nIndex++; + setCurrentPage(nIndex); + } +} + +/** + * Fills the main window's "Window" menu with the current list of file tabs. + * This slot is attached to the aboutToShow() signal, emitted by the Window + * popup menu. + */ +void EditorTabs::slotFillWindowMenu() +{ + QString sLabel; + int i; + + // Delete old menu items + // NOTE: We can't use aboutToHide() to do that, since it is emitted + // _before_ the activated() signal + for (i = 0; i < m_nWindowMenuItems; i++) + m_pWindowMenu->removeItem(i); + + // Add new items + for (i = 0; i < count(); i++) { + sLabel = (i < 10) ? QString("&%1 %2").arg(i).arg(label(i)) : label(i); + m_pWindowMenu->insertItem(sLabel, i); + } + + // Store the number of items added + m_nWindowMenuItems = i; +} + +/** + * Sets the current page to the given one. + * This slot is attached to the activated() signal, emitted by the "Window" + * popup menu. The tab number to switch to is given by the menu item ID. + * Note that we do not trust setCurrentPage() to filter out the IDs of other + * menu items (which are supposed to be negative numbers). + */ +void EditorTabs::slotSetCurrentPage(int nId) +{ + if (nId >= 0 && nId < count()) + setCurrentPage(nId); +} + +/** + * Closes an edited file, and removes its page. + * Once a file has been closed, its page is removed from the tab widget stack, + * its menu item in the "Windows" menu is deleted and all other references to + * it are removed. + * Note that the operation may fail if the user chooses not to close the file + * when prompted for unsaved changes. + * @param pPage The EditorPage object to remove + * @param bForce true to close the page even if there are unsaved changes, + * false otherwise + * @return true if the page was removed, false otherwise + */ +bool EditorTabs::removePage(QWidget* pPage, bool bForce) +{ + EditorPage* pEditPage; + QString sFilePath; + + // Store the file path for later + pEditPage = (EditorPage*)pPage; + sFilePath = pEditPage->getFilePath(); + + // Close the edited file (may fail if the user aborts the action) + if (!pEditPage->close(bForce)) + return false; + + // Remove the page and all references to it + m_mapEdit.remove(sFilePath); + TabWidget::removePage(pPage); + + // Update the new state if no other page exists (if another page has + // become active, it will update the new state, so there is no need for + // special handling) + if (currentPage() == NULL) + slotCurrentChanged(NULL); + + // Notify the page has been removed + emit editorRemoved(pEditPage); + return true; +} + +#include "editortabs.moc" |