<commit_before>#!/usr/bin/env python
"""
.. module:: note
   :synopsis: The Note class.
"""

import datetime
import glob
import os
import uuid
import shutil

from PyQt4 import QtCore, QtGui

from xml.etree import ElementTree

import converters
import logger
import misc


class NoteContent(object):
    """
    The content, keywords, attachments, etc.. of a single version of a single note.
    There can be many of these associated with a single NoteStandardItem.
    """
    @logger.logFn
    def __init__(self, note, version_number, xml = None):
        """
        Creates a blank note and fills in the fields from the xml if it is provided.
        """
        self.attachments = []
        self.content = ""
        self.content_type = "markdown"
        self.date = None
        self.html_converter = None
        self.keywords = []
        self.link_converter = None
        self.name = note.getName()
        self.note = note
        self.unsaved_content = None
        self.version_number = version_number

        if xml is not None:
            self.date = xml.find("date").text
            self.content = xml.find("content").text
            self.content_type = xml.find("content_type").text
            self.name = xml.find("name").text

            attach_xml = xml.find("attachments")
            if attach_xml is not None:
                for xml in attach_xml:
                    self.attachments.append(xml.text)

            keyword_xml = xml.find("keywords")
            if keyword_xml is not None:
                for xml in keyword_xml:
                    self.keywords.append(xml.text)

        self.setConverters(self.content_type)

    @logger.logFn        
    def addAttachment(self, attachment_fullname):
        self.attachments.append(attachment_fullname)
                    
    @logger.logFn
    def contentToXML(self, xml, use_current_time):
        """
        Add the note contents to the xml.
        """
        name_xml = ElementTree.SubElement(xml, "name")
        name_xml.text = self.name

        date_xml = ElementTree.SubElement(xml, "date")
        if use_current_time:
            date_xml.text = str(datetime.datetime.now())
        else:
            date_xml.text = self.date
        
        if (len(self.attachments) > 0):
            attachments_xml = ElementTree.SubElement(xml, "attachments")
            for attached in self.attachments:
                file_xml = ElementTree.SubElement(attachments_xml, "file")
                file_xml.text = attached

        if (len(self.keywords) > 0):
            keywords_xml = ElementTree.SubElement(xml, "keywords")
            for word in self.keywords:
                word_xml = ElementTree.SubElement(keywords_xml, "word")
                word_xml.text = word

        content_xml = ElementTree.SubElement(xml, "content")
        content_xml.text = self.content

        content_type_xml = ElementTree.SubElement(xml, "content_type")
        content_type_xml.text = self.content_type

        # Delete backup..

    @logger.logFn    
    def convertToHTML(self, content):
        return self.html_converter(content)

    @logger.logFn
    def formatLink(self, link_name, link_url):
        return self.link_converter(link_name, link_url)
    
    @logger.logFn    
    def getAttachments(self):
        return self.attachments

    @logger.logFn    
    def getContent(self):
        return self.content

    @logger.logFn    
    def getContentType(self):
        return self.content_type

    @logger.logFn
    def getDate(self):
        if self.date is not None:
            return datetime.datetime.strptime(self.date, '%Y-%m-%d %H:%M:%S.%f')
    
    @logger.logFn
    def getKeywords(self):
        return self.keywords

    @logger.logFn    
    def getName(self):
        return self.name
    
    @logger.logFn
    def getNoteStandardItem(self):
        return self.note

    @logger.logFn    
    def getUnsavedContent(self):
        return self.unsaved_content
    
    @logger.logFn    
    def getVersionNumber(self):
        return self.version_number    

    @logger.logFn
    def removeAttachment(self, attachment_fullname):
        self.attachments.remove(attachment_fullname)
        
    @logger.logFn        
    def saveBackup(self):
        backup_name = self.note.getFullname()[:-4] + "_backup.txt"
        with open(backup_name, "w") as fp:
            fp.write(self.content)

    @logger.logFn        
    def setContent(self, new_content):
        self.content = new_content

    @logger.logFn
    def setContentType(self, content_type):
        self.content_type = str(content_type)
        self.setConverters(self.content_type)
        
    @logger.logFn
    def setConverters(self, content_type):
        self.html_converter = converters.getHTMLConverter(content_type)
        self.link_converter = converters.getLinkConverter(content_type)
                         
    @logger.logFn
    def setKeywords(self, new_keywords):
        self.keywords = new_keywords

    @logger.logFn
    def setName(self, new_name):
        self.name = new_name
        
    @logger.logFn        
    def setUnsavedContent(self, new_content):
        self.unsaved_content = new_content
        

class NoteListViewDelegate(QtGui.QStyledItemDelegate):
    """
    A custom look for each note item.
    """
    def __init__(self, model, proxy_model):
        QtGui.QStyledItemDelegate.__init__(self)
        self.model = model
        self.proxy_model = proxy_model

    def itemFromProxyIndex(self, proxy_index):
        source_index = self.proxy_model.mapToSource(proxy_index)
        return self.model.itemFromIndex(source_index)
    
    def paint(self, painter, option, index):
        note = self.itemFromProxyIndex(index)

        # Draw correct background.
        style = option.widget.style()
        style.drawControl(QtGui.QStyle.CE_ItemViewItem, option, painter, option.widget)

        # Draw text.
        upper_rect = QtCore.QRect(option.rect.left(),
                                  option.rect.top(),
                                  option.rect.width(),
                                  option.rect.height()/2)
        
        lower_rect = QtCore.QRect(option.rect.left(),
                                  option.rect.top() + option.rect.height()/2,
                                  option.rect.width(),
                                  option.rect.height()/2)
        
        painter.drawText(upper_rect, QtCore.Qt.AlignLeft, " " + note.getName())
        painter.drawText(upper_rect, QtCore.Qt.AlignRight, "(" + str(note.getNumberOfVersions()) + " versions) ")
        painter.drawText(lower_rect,
                         QtCore.Qt.AlignLeft,
                         " created on " + datetime.datetime.strftime(note.date_created, '%Y-%m-%d'))

    def sizeHint(self, option, index):
        result = QtGui.QStyledItemDelegate.sizeHint(self, option, index)
        result.setHeight(2 * result.height())
        return result
        
    
class NoteMVC(QtGui.QListView):
    """
    Encapsulates a list view specialized for notes and it's associated model.
    """
    addNewNote = QtCore.pyqtSignal()
    copyNote = QtCore.pyqtSignal(object)
    editNote = QtCore.pyqtSignal(object, object)
    moveNote = QtCore.pyqtSignal(object)
    noteKeywordsChanged = QtCore.pyqtSignal(list, list)
    selectedNoteChanged = QtCore.pyqtSignal(object)

    @logger.logFn    
    def __init__(self, parent = None):
        QtGui.QListView.__init__(self, parent)
        self.notes = {}
        self.right_clicked = None

        # Context menu
        self.copyLinkAction = QtGui.QAction(self.tr("Copy Link to Clipboard"), self)
        self.copyLinkAction.triggered.connect(self.handleCopyLink)
        self.copyNoteAction = QtGui.QAction(self.tr("Copy Note"), self)
        self.copyNoteAction.triggered.connect(self.handleCopyNote)
        self.deleteNoteAction = QtGui.QAction(self.tr("Delete Note"), self)
        self.deleteNoteAction.triggered.connect(self.handleDeleteNote)
        self.editNoteAction = QtGui.QAction(self.tr("Edit Note"), self)
        self.editNoteAction.triggered.connect(self.handleEditNote)
        self.moveNoteAction = QtGui.QAction(self.tr("Move Note"), self)
        self.moveNoteAction.triggered.connect(self.handleMoveNote)
        self.newNoteAction = QtGui.QAction(self.tr("New Note"), self)
        self.newNoteAction.triggered.connect(self.handleNewNote)
        self.renameNoteAction = QtGui.QAction(self.tr("Rename Note"), self)
        self.renameNoteAction.triggered.connect(self.handleRenameNote)

        self.note_popup_menu = QtGui.QMenu(self)
        self.note_popup_menu.addAction(self.copyLinkAction)
        self.note_popup_menu.addAction(self.copyNoteAction)
        self.note_popup_menu.addAction(self.deleteNoteAction)
        self.note_popup_menu.addAction(self.editNoteAction)
        self.note_popup_menu.addAction(self.moveNoteAction)
        self.note_popup_menu.addAction(self.newNoteAction)
        self.note_popup_menu.addAction(self.renameNoteAction)        

        self.no_note_popup_menu = QtGui.QMenu(self)
        self.no_note_popup_menu.addAction(self.newNoteAction)

        # Note model
        self.note_model = NoteStandardItemModel(self)
        self.note_proxy_model = NoteSortFilterProxyModel(self)
        self.note_proxy_model.setSourceModel(self.note_model)
        self.setModel(self.note_proxy_model)

        # Rendering
        self.setItemDelegate(NoteListViewDelegate(self.note_model, self.note_proxy_model))
        
        # Get selection changes.
        self.selectionModel().selectionChanged.connect(self.handleSelectionChange)

    @logger.logFn        
    def addNote(self, notebook, name):
        """
        Add a new blank note.
        """
        a_note = NoteStandardItem(notebook, self.noteKeywordsChanged, note_name = name)
        self.notes[a_note.getFileName()] = a_note
        self.note_model.appendRow(a_note)
        self.note_proxy_model.sort(0)

    @logger.logFn        
    def clearNotes(self):
        self.note_model.clear()

    @logger.logFn
    def copyANote(self, notebook, old_note):
        # FIXME: Should save note history?
        new_note = NoteStandardItem(notebook,
                                    self.noteKeywordsChanged,
                                    note_name = old_note.getName() + " copy")
        self.notes[new_note.getFileName()] = new_note
        new_note.saveNote(old_note.loadNoteContent(old_note.getLatestVersionNumber()))
        self.note_model.appendRow(new_note)
        self.note_proxy_model.sort(0)
        
    @logger.logFn
    def handleCopyLink(self, boolean):
        """
        This returns the filename and location which can be used to create a hyperlink.
        """
        clipboard = QtGui.QApplication.clipboard()
        a_note = self.noteFromProxyIndex(self.right_clicked)
        clipboard.setText("<note_link><split>" + a_note.getName() + "<split>" + a_note.getLink() + "<split></note_link>")

    @logger.logFn
    def handleCopyNote(self, boolean):
        self.copyNote.emit(self.noteFromProxyIndex(self.right_clicked))

    @logger.logFn
    def handleDeleteNote(self, boolean):
        a_note = self.noteFromProxyIndex(self.right_clicked)
        a_note_name = a_note.getName()

        reply = QtGui.QMessageBox.question(self,
                                           "Warning!",
                                           "Really delete note '" + a_note_name + "'?",
                                           QtGui.QMessageBox.Yes,
                                           QtGui.QMessageBox.No)
        if (reply == QtGui.QMessageBox.Yes):
            source_index = self.note_proxy_model.mapToSource(self.right_clicked)
            self.note_model.removeRow(source_index.row())
            del self.notes[a_note.getFileName()]            
            a_note.deleteNote()

            if (len(self.selectedIndexes()) == 0):
                self.selectedNoteChanged.emit(None)

    @logger.logFn
    def handleEditNote(self, boolean):
        a_note = self.noteFromProxyIndex(self.right_clicked)
        note_content = a_note.loadNoteContent(a_note.getLatestVersionNumber())
        self.editNote.emit(a_note, note_content)
        
    @logger.logFn
    def handleNewNote(self, boolean):
        self.addNewNote.emit()

    @logger.logFn
    def handleMoveNote(self, boolean):
        self.moveNote.emit(self.noteFromProxyIndex(self.right_clicked))
    
    @logger.logFn    
    def handleNoteLinkClicked(self, note_filename, note_version):
        note_filename = str(note_filename)
        if note_filename in self.notes:<commit_msg>Keep methods in alphabetical order.<commit_after>#!/usr/bin/env python
"""
.. module:: note
   :synopsis: The Note class.
"""

import datetime
import glob
import os
import uuid
import shutil

from PyQt4 import QtCore, QtGui

from xml.etree import ElementTree

import converters
import logger
import misc


class NoteContent(object):
    """
    The content, keywords, attachments, etc.. of a single version of a single note.
    There can be many of these associated with a single NoteStandardItem.
    """
    @logger.logFn
    def __init__(self, note, version_number, xml = None):
        """
        Creates a blank note and fills in the fields from the xml if it is provided.
        """
        self.attachments = []
        self.content = ""
        self.content_type = "markdown"
        self.date = None
        self.html_converter = None
        self.keywords = []
        self.link_converter = None
        self.name = note.getName()
        self.note = note
        self.unsaved_content = None
        self.version_number = version_number

        if xml is not None:
            self.date = xml.find("date").text
            self.content = xml.find("content").text
            self.content_type = xml.find("content_type").text
            self.name = xml.find("name").text

            attach_xml = xml.find("attachments")
            if attach_xml is not None:
                for xml in attach_xml:
                    self.attachments.append(xml.text)

            keyword_xml = xml.find("keywords")
            if keyword_xml is not None:
                for xml in keyword_xml:
                    self.keywords.append(xml.text)

        self.setConverters(self.content_type)

    @logger.logFn        
    def addAttachment(self, attachment_fullname):
        self.attachments.append(attachment_fullname)
                    
    @logger.logFn
    def contentToXML(self, xml, use_current_time):
        """
        Add the note contents to the xml.
        """
        name_xml = ElementTree.SubElement(xml, "name")
        name_xml.text = self.name

        date_xml = ElementTree.SubElement(xml, "date")
        if use_current_time:
            date_xml.text = str(datetime.datetime.now())
        else:
            date_xml.text = self.date
        
        if (len(self.attachments) > 0):
            attachments_xml = ElementTree.SubElement(xml, "attachments")
            for attached in self.attachments:
                file_xml = ElementTree.SubElement(attachments_xml, "file")
                file_xml.text = attached

        if (len(self.keywords) > 0):
            keywords_xml = ElementTree.SubElement(xml, "keywords")
            for word in self.keywords:
                word_xml = ElementTree.SubElement(keywords_xml, "word")
                word_xml.text = word

        content_xml = ElementTree.SubElement(xml, "content")
        content_xml.text = self.content

        content_type_xml = ElementTree.SubElement(xml, "content_type")
        content_type_xml.text = self.content_type

        # Delete backup..

    @logger.logFn    
    def convertToHTML(self, content):
        return self.html_converter(content)

    @logger.logFn
    def formatLink(self, link_name, link_url):
        return self.link_converter(link_name, link_url)
    
    @logger.logFn    
    def getAttachments(self):
        return self.attachments

    @logger.logFn    
    def getContent(self):
        return self.content

    @logger.logFn    
    def getContentType(self):
        return self.content_type

    @logger.logFn
    def getDate(self):
        if self.date is not None:
            return datetime.datetime.strptime(self.date, '%Y-%m-%d %H:%M:%S.%f')
    
    @logger.logFn
    def getKeywords(self):
        return self.keywords

    @logger.logFn    
    def getName(self):
        return self.name
    
    @logger.logFn
    def getNoteStandardItem(self):
        return self.note

    @logger.logFn    
    def getUnsavedContent(self):
        return self.unsaved_content
    
    @logger.logFn    
    def getVersionNumber(self):
        return self.version_number    

    @logger.logFn
    def removeAttachment(self, attachment_fullname):
        self.attachments.remove(attachment_fullname)
        
    @logger.logFn        
    def saveBackup(self):
        backup_name = self.note.getFullname()[:-4] + "_backup.txt"
        with open(backup_name, "w") as fp:
            fp.write(self.content)

    @logger.logFn        
    def setContent(self, new_content):
        self.content = new_content

    @logger.logFn
    def setContentType(self, content_type):
        self.content_type = str(content_type)
        self.setConverters(self.content_type)
        
    @logger.logFn
    def setConverters(self, content_type):
        self.html_converter = converters.getHTMLConverter(content_type)
        self.link_converter = converters.getLinkConverter(content_type)
                         
    @logger.logFn
    def setKeywords(self, new_keywords):
        self.keywords = new_keywords

    @logger.logFn
    def setName(self, new_name):
        self.name = new_name
        
    @logger.logFn        
    def setUnsavedContent(self, new_content):
        self.unsaved_content = new_content
        

class NoteListViewDelegate(QtGui.QStyledItemDelegate):
    """
    A custom look for each note item.
    """
    def __init__(self, model, proxy_model):
        QtGui.QStyledItemDelegate.__init__(self)
        self.model = model
        self.proxy_model = proxy_model

    def itemFromProxyIndex(self, proxy_index):
        source_index = self.proxy_model.mapToSource(proxy_index)
        return self.model.itemFromIndex(source_index)
    
    def paint(self, painter, option, index):
        note = self.itemFromProxyIndex(index)

        # Draw correct background.
        style = option.widget.style()
        style.drawControl(QtGui.QStyle.CE_ItemViewItem, option, painter, option.widget)

        # Draw text.
        upper_rect = QtCore.QRect(option.rect.left(),
                                  option.rect.top(),
                                  option.rect.width(),
                                  option.rect.height()/2)
        
        lower_rect = QtCore.QRect(option.rect.left(),
                                  option.rect.top() + option.rect.height()/2,
                                  option.rect.width(),
                                  option.rect.height()/2)
        
        painter.drawText(upper_rect, QtCore.Qt.AlignLeft, " " + note.getName())
        painter.drawText(upper_rect, QtCore.Qt.AlignRight, "(" + str(note.getNumberOfVersions()) + " versions) ")
        painter.drawText(lower_rect,
                         QtCore.Qt.AlignLeft,
                         " created on " + datetime.datetime.strftime(note.date_created, '%Y-%m-%d'))

    def sizeHint(self, option, index):
        result = QtGui.QStyledItemDelegate.sizeHint(self, option, index)
        result.setHeight(2 * result.height())
        return result
        
    
class NoteMVC(QtGui.QListView):
    """
    Encapsulates a list view specialized for notes and it's associated model.
    """
    addNewNote = QtCore.pyqtSignal()
    copyNote = QtCore.pyqtSignal(object)
    editNote = QtCore.pyqtSignal(object, object)
    moveNote = QtCore.pyqtSignal(object)
    noteKeywordsChanged = QtCore.pyqtSignal(list, list)
    selectedNoteChanged = QtCore.pyqtSignal(object)

    @logger.logFn    
    def __init__(self, parent = None):
        QtGui.QListView.__init__(self, parent)
        self.notes = {}
        self.right_clicked = None

        # Context menu
        self.copyLinkAction = QtGui.QAction(self.tr("Copy Link to Clipboard"), self)
        self.copyLinkAction.triggered.connect(self.handleCopyLink)
        self.copyNoteAction = QtGui.QAction(self.tr("Copy Note"), self)
        self.copyNoteAction.triggered.connect(self.handleCopyNote)
        self.deleteNoteAction = QtGui.QAction(self.tr("Delete Note"), self)
        self.deleteNoteAction.triggered.connect(self.handleDeleteNote)
        self.editNoteAction = QtGui.QAction(self.tr("Edit Note"), self)
        self.editNoteAction.triggered.connect(self.handleEditNote)
        self.moveNoteAction = QtGui.QAction(self.tr("Move Note"), self)
        self.moveNoteAction.triggered.connect(self.handleMoveNote)
        self.newNoteAction = QtGui.QAction(self.tr("New Note"), self)
        self.newNoteAction.triggered.connect(self.handleNewNote)
        self.renameNoteAction = QtGui.QAction(self.tr("Rename Note"), self)
        self.renameNoteAction.triggered.connect(self.handleRenameNote)

        self.note_popup_menu = QtGui.QMenu(self)
        self.note_popup_menu.addAction(self.copyLinkAction)
        self.note_popup_menu.addAction(self.copyNoteAction)
        self.note_popup_menu.addAction(self.deleteNoteAction)
        self.note_popup_menu.addAction(self.editNoteAction)
        self.note_popup_menu.addAction(self.moveNoteAction)
        self.note_popup_menu.addAction(self.newNoteAction)
        self.note_popup_menu.addAction(self.renameNoteAction)        

        self.no_note_popup_menu = QtGui.QMenu(self)
        self.no_note_popup_menu.addAction(self.newNoteAction)

        # Note model
        self.note_model = NoteStandardItemModel(self)
        self.note_proxy_model = NoteSortFilterProxyModel(self)
        self.note_proxy_model.setSourceModel(self.note_model)
        self.setModel(self.note_proxy_model)

        # Rendering
        self.setItemDelegate(NoteListViewDelegate(self.note_model, self.note_proxy_model))
        
        # Get selection changes.
        self.selectionModel().selectionChanged.connect(self.handleSelectionChange)

    @logger.logFn        
    def addNote(self, notebook, name):
        """
        Add a new blank note.
        """
        a_note = NoteStandardItem(notebook, self.noteKeywordsChanged, note_name = name)
        self.notes[a_note.getFileName()] = a_note
        self.note_model.appendRow(a_note)
        self.note_proxy_model.sort(0)

    @logger.logFn        
    def clearNotes(self):
        self.note_model.clear()

    @logger.logFn
    def copyANote(self, notebook, old_note):
        # FIXME: Should save note history?
        new_note = NoteStandardItem(notebook,
                                    self.noteKeywordsChanged,
                                    note_name = old_note.getName() + " copy")
        self.notes[new_note.getFileName()] = new_note
        new_note.saveNote(old_note.loadNoteContent(old_note.getLatestVersionNumber()))
        self.note_model.appendRow(new_note)
        self.note_proxy_model.sort(0)
        
    @logger.logFn
    def handleCopyLink(self, boolean):
        """
        This returns the filename and location which can be used to create a hyperlink.
        """
        clipboard = QtGui.QApplication.clipboard()
        a_note = self.noteFromProxyIndex(self.right_clicked)
        clipboard.setText("<note_link><split>" + a_note.getName() + "<split>" + a_note.getLink() + "<split></note_link>")

    @logger.logFn
    def handleCopyNote(self, boolean):
        self.copyNote.emit(self.noteFromProxyIndex(self.right_clicked))

    @logger.logFn
    def handleDeleteNote(self, boolean):
        a_note = self.noteFromProxyIndex(self.right_clicked)
        a_note_name = a_note.getName()

        reply = QtGui.QMessageBox.question(self,
                                           "Warning!",
                                           "Really delete note '" + a_note_name + "'?",
                                           QtGui.QMessageBox.Yes,
                                           QtGui.QMessageBox.No)
        if (reply == QtGui.QMessageBox.Yes):
            source_index = self.note_proxy_model.mapToSource(self.right_clicked)
            self.note_model.removeRow(source_index.row())
            del self.notes[a_note.getFileName()]            
            a_note.deleteNote()

            if (len(self.selectedIndexes()) == 0):
                self.selectedNoteChanged.emit(None)

    @logger.logFn
    def handleEditNote(self, boolean):
        a_note = self.noteFromProxyIndex(self.right_clicked)
        note_content = a_note.loadNoteContent(a_note.getLatestVersionNumber())
        self.editNote.emit(a_note, note_content)

    @logger.logFn
    def handleMoveNote(self, boolean):
        self.moveNote.emit(self.noteFromProxyIndex(self.right_clicked))
        
    @logger.logFn
    def handleNewNote(self, boolean):
        self.addNewNote.emit()
    
    @logger.logFn    
    def handleNoteLinkClicked(self, note_filename, note_version):
        note_filename = str(note_filename)
        if note_filename in self.notes:
{'fraction_non_alphanumeric': 0.0971904348512351, 'fraction_numerical': 0.000868124062820614, 'mean_word_length': 2.2566178360318685, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 90}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 32, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1719991', 'n_tokens_mistral': 7455, 'n_tokens_neox': 7295, 'n_words': 1311}