import requests
import random
import json
import re
from bs4 import BeautifulSoup
from headers import *

def get_header():
    return {'User-Agent': random.choice(myheaders)}

def get_china_10_year_bond_yield():
    url = 'https://cn.investing.com/rates-bonds/china-10-year-bond-yield/'
    resp = requests.get(url, headers=get_header())
    if resp.ok:
        soup = BeautifulSoup(resp.content, 'html.parser')
        china_10_year_bond_yield = soup.find(
            'span', {'id': 'last_last'}).string
        return china_10_year_bond_yield
    return

def get_all_fund():
    url = 'http://fund.eastmoney.com/js/fundcode_search.js'
    resp = requests.get(url, headers=get_header())
    if resp.ok:
        data = resp.content.decode('utf-8')
        json_data = json.loads(re.search('\[.+\]', data).group(0))
        with open('./all_fund.txt', 'w+') as f:
            index = 0
            for i in json_data:
                string = "['%s', '%s', '%s', '%s']\n" % (
                    index, i[0], i[2], i[3])
                f.write(string)
                index += 1
    return

def get_fund_info(code_list):
    fund_info = []
    for code in code_list:
        url = 'http://www.1234567.com.cn/%s.html?spm=search' % code
        resp = requests.get(url, headers=get_header())
        if resp.ok:
            soup = BeautifulSoup(resp.content, 'html.parser')
            date = re.sub('[\(\)\ ]', '', soup.find(
                'dl', {'class': 'dataItem02'}).find('p').text)
            ls_z = soup.find('dl', {'class': 'dataItem02'}).find(
                'dd', {'class': 'dataNums'}).findAll('span')[0].string
            ls_zzl = soup.find('dl', {'class': 'dataItem02'}).find(
                'dd', {'class': 'dataNums'}).findAll('span')[1].string
            gz_gzc = soup.findAll('span', {'class': 'fix_fcode'})[0].string
            gz_gzn = soup.findAll('span', {'class': 'fix_fname'})[0].string
            gz_gsz = soup.findAll('span', {'id': 'gz_gsz'})[0].string
            gz_gszze = soup.findAll('span', {'id': 'gz_gszze'})[0].string
            gz_gszzl = soup.findAll('span', {'id': 'gz_gszzl'})[0].string
            info = "估算涨幅: %s(%s),  估算净值: %s,  %s: %s(%s),  基金: %s(%s)" % (
                gz_gszzl, gz_gszze, gz_gsz, date, ls_z, ls_zzl, gz_gzn, gz_gzc)
            fund_info.append(
                {'gz_gszzl': gz_gszzl,
                 'gz_gszze': gz_gszze,
                 'gz_gsz': gz_gsz,
                 'date': date,
                 'ls_z': ls_z,
                 'ls_zzl': ls_zzl,
                 'gz_gzn': gz_gzn,
                 'gz_gsc': gz_gzc}
            )
    return sorted(fund_info, key=lambda dic: float(
        re.sub('%', '', dic["gz_gszzl"])), reverse=True)

def Pretty(fund_info_list, rtype='html'):
    import prettytable as pt
    title = ['估算涨幅',  '估算净值', '单位净值', '基金名称']
    tb = pt.PrettyTable()
    tb.field_names = title
    for i in range(4):
        tb.align[title[i]] = 'l'
    for i in fund_info_list:
        data = ['%s(%s)' % (i['gz_gszzl'], i['gz_gszze']),
                '%s' % (i['gz_gsz']),
                '%s(%s)  %s' % (i['ls_z'], i['ls_zzl'],
                                re.search('[\d\-]+', i['date']).group(0)),
                '%s(%s)' % (i['gz_gzn'], i['gz_gsc'])]
        tb.add_row(data)
    tb.set_style(pt.MSWORD_FRIENDLY)
    if rtype == 'html':
        return tb.get_html_string()
    else:
        return tb.get_string()


import configparser
import json
import multiprocessing
import os
import re
from typing import Dict, Any

from bxcommon import constants
from bxcommon.messages.bloxroute.bloxroute_version_manager import bloxroute_version_manager

_working_directory = ""

def set_working_directory(working_directory: str):
    global _working_directory
    _working_directory = working_directory

def get_relative_file(file_path: str) -> str:
    return os.path.join(_working_directory, file_path)

def log_pid(file_name):
    """
    Capture process id for easy termination in multi-thread scenarios.
    """
    with open(file_name, "w") as writable_file:
        writable_file.write(str(os.getpid()))

def get_env_default(key: str) -> str:
    """
    Read the default values for given key from the config file for a specific environment. The environment is stored
    in the hosts environment variables as `BLXR_ENV=local` for example. If the environment isn't set or doesn't exist
    in the config file, the global default values are returned

    :param key: the key to get the env default values for
    :return: the env default value
    """
    config = configparser.ConfigParser()
    config_file_path = get_relative_file(constants.NODE_CONFIG_FILE)
    config.read(config_file_path)

    # If the environment var itself does not exist, return the global default value for the key
    if constants.BLXR_ENV_VAR not in os.environ:
        return config.defaults()[key]

    environment = os.environ[constants.BLXR_ENV_VAR].lower()

    # If the given environment var does not have an entry in the config file, return the global default
    if environment in config.sections():
        return config.get(environment, key)
    else:
        return config.defaults()[key]

def get_thread_pool_parallelism_degree(parallelism_degree_str: str) -> int:
    parallelism_degree = int(parallelism_degree_str)
    if parallelism_degree <= 0:
        parallelism_degree = 1
    return min(
        parallelism_degree,
        max(multiprocessing.cpu_count() - 1, 1)
    )

def is_valid_version(full_version: str) -> bool:
    """
    check if version number in template: {int}.{int}.{int}.{int} and version type is dev/ci/v
    :param full_version: {version_type}{version_number}
    :return: if the version is valid
    """
    index = re.search("\d", full_version)
    if index is None:
        raise ValueError(f"Version is incorrectly formated: {full_version}")
    index = index.start()

    version_number = full_version[index:]
    version_type = full_version[:index]
    return (version_number.count(".") == 3 and all(str(x).isdigit() for x in version_number.split("."))) and \
           (version_type in constants.VERSION_TYPE_LIST)

def read_manifest(manifest_path: str) -> Dict[str, str]:
    """
    Reads manifest file, ensuring that manifest contains all expected properties.
    :param manifest_path:
    :return: manifest
    """
    try:
        with open(manifest_path, "r") as json_file:
            manifest = json.load(json_file)
            version = manifest[constants.MANIFEST_SOURCE_VERSION]

            if not is_valid_version(version):
                raise ValueError(f"Invalid version: {version}")

            if not all(params in manifest for params in constants.REQUIRED_PARAMS_IN_MANIFEST):
                missing_params_str = ", ".join([item for item in constants.REQUIRED_PARAMS_IN_MANIFEST if item not in manifest])
                raise ValueError(f"Missing attributes: {missing_params_str}")

            return manifest
    except Exception as e:
        raise Exception(f"Unexpected error while reading manifest file at: {manifest_path}. Error: {e}")

def append_manifest_args(opts_dict: Dict[Any, Any]):
    manifest_path = get_relative_file(constants.MANIFEST_PATH)
    manifest = read_manifest(manifest_path)
    opts_dict.update(manifest)
    opts_dict.update({constants.PROTOCOL_VERSION: bloxroute_version_manager.CURRENT_PROTOCOL_VERSION})


from selenium import webdriver
from bs4 import BeautifulSoup
import pandas as pd
import chromedriver_binary

names=[]
brands=[]
prices=[]
ogprices=[]
img_urls=[]
ratings=[]
discounts=[]

driver = webdriver.Chrome()
for i in range(1,21):
	driver.get("https://www.flipkart.com/food-nutrition/~gourmet-foods-/pr?sid=7jv&otracker=nmenu_sub_Sports%2C+Books+%26+More_0_Food+Essentials&page=" +str(i))

	content = driver.page_source
	soup = BeautifulSoup(content, features="html.parser")
	for a in soup.findAll('a',href=True, attrs={'class':'_2cLu-l'}):
		try:
			names.append(a.text)
		except:
			names.append('NaN')
	for a in soup.findAll('a',href=True, attrs={'class':'_1Vfi6u'}):
		price=a.find('div', attrs={'class':'_1vC4OE'})
		try:
			prices.append(price.text)
		except:
			prices.append("NaN")
		ogprice=a.find('div', attrs={'class':'_3auQ3N'})
		try:
			ogprices.append(ogprice.text)
		except:
			ogprices.append("NaN")
		disc=a.find('div', attrs={'class':'VGWI6T'})
		try:
			discounts.append(disc.span.text)
		except:
			discounts.append("NaN")

#print(len(names),len(prices),len(ogprices),len(discounts),len(ratings))
df = pd.DataFrame({'Product Name':names,'Price':prices,'Original Prices':ogprices,'Discount rates':discounts})
df.to_csv('food.csv', index=False, encoding='utf-8')


"""
byceps.blueprints.api.v1.user_badge.views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:Copyright: 2006-2021 Jochen Kupperschmidt
:License: Revised BSD (see `LICENSE` file for details)
"""

from flask import abort, request
from marshmallow import ValidationError

from .....services.user import service as user_service
from .....services.user_badge import awarding_service, badge_service
from .....signals import user_badge as user_badge_signals
from .....util.framework.blueprint import create_blueprint
from .....util.views import respond_no_content

from ...decorators import api_token_required

from .schemas import AwardBadgeToUserRequest

blueprint = create_blueprint('api_v1_user_badge', __name__)

@blueprint.route('/awardings', methods=['POST'])
@api_token_required
@respond_no_content
def award_badge_to_user():
    """Award the badge to a user."""
    if not request.is_json:
        abort(415)

    schema = AwardBadgeToUserRequest()
    try:
        req = schema.load(request.get_json())
    except ValidationError as e:
        abort(400, str(e.normalized_messages()))

    badge = badge_service.find_badge_by_slug(req['badge_slug'])
    if not badge:
        abort(400, 'Badge slug unknown')

    user = user_service.find_user(req['user_id'])
    if not user:
        abort(400, 'User ID unknown')

    initiator = user_service.find_user(req['initiator_id'])
    if not initiator:
        abort(400, 'Initiator ID unknown')

    _, event = awarding_service.award_badge_to_user(
        badge.id, user.id, initiator_id=initiator.id
    )

    user_badge_signals.user_badge_awarded.send(None, event=event)


import os
import cv2
from pathlib import Path
import pandas as pd
from lyse import routine_storage

PARAMETERS_PATH = os.path.dirname(os.path.abspath(__file__))

def line_select_callback(eclick, erelease):
    'eclick and erelease are the press and release events'
    x1, y1 = eclick.xdata, eclick.ydata
    x2, y2 = erelease.xdata, erelease.ydata
    print("ROI: (%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))
    with open(PARAMETERS_PATH+ r'\parameter_files\labrad\roi_selection.txt', 'w') as f:
        f.write('x1,y1,x2,y2\n')
        f.write('{:.0f},{:.0f},{:.0f},{:.0f}'.format(x1, y1, x2, y2))

def get_ROI():
    if os.path.exists(PARAMETERS_PATH+ r'\parameter_files\labrad\roi_selection.txt'):

        with open(PARAMETERS_PATH+ r'\parameter_files\labrad\roi_selection.txt', 'r') as f:
            f.readline() # ignore first line
            x1, y1, x2, y2 = f.readline().split(',')

    else:
        x1, y1, x2, y2 = -1, -1, -1, -1

    return int(x1), int(y1), int(x2), int(y2)

def toggle_selector(event):
    print(' Key pressed.')
    if event.key in ['Q', 'q'] and toggle_selector.RS.active:
        print(' RectangleSelector deactivated.')
        toggle_selector.RS.set_active(False)
    if event.key in ['A', 'a'] and not toggle_selector.RS.active:
        print(' RectangleSelector activated.')
        toggle_selector.RS.set_active(True)

def rotate_image(image, deg):
    # load the image and show it
    image = cv2.imread(image)
    #cv2.imshow("Original", image)
    # grab the dimensions of the image and calculate the center of the
    # image
    (h, w) = image.shape[:2]
    (cX, cY) = (w // 2, h // 2)
    # rotate our image by 45 degrees around the center of the image
    M = cv2.getRotationMatrix2D((cX, cY), deg, 1.0)
    rotated = cv2.warpAffine(image, M, (w, h))
    #cv2.imshow("Rotated by 45 Degrees", rotated)
    return rotated

def read_spcm_continuous_monitor(path):
    run_folder = str(Path(path).parent)
    counter_file = str(Path.joinpath(run_folder, 'count_record.txt'))

    if os.path.exists(counter_file):
        data = pd.read_csv(counter_file)
    else:
        data = None
        with open(counter_file, 'w') as f:
            f.write('run,counts\n')

    return data

def write_spcm_continuous_monitor(path, df):
    run_folder = str(Path(path).parent)
    counter_file = str(Path.joinpath(run_folder, 'count_record.txt'))
    data = pd.to_csv(counter_file) #,mode='w+'

    return data


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from subprocess import Popen, PIPE
import pickle
import threading
import time

ps = np.arange(0.1, 1.0, 0.1)
bs = np.arange(1, 17, 2)
lp = len(ps)
lb = len(bs)
ps, bs = np.meshgrid(ps, bs)
print(lp, lb, ps.shape, bs.shape)
accuracy = np.ndarray(shape=(lb, lp))
exec_time = np.ndarray(shape=(lb, lp))

restored_acc_f = open("dump_f", "rb")
restored_time_f = open("dump_f_time", "rb")
ps, bs, accuracy = pickle.load(restored_acc_f)
_, _, exec_time = pickle.load(restored_time_f)
restored_acc_f.close()
restored_acc_f.close()

def test_parameters(params, i, j):
    p, b = params
    results = []
    times = []
    for f in ["0", "1", "2", "3", "4"]:
        time_start = time.time()
        forest = Popen(["./RandomForest.jar",
                        "connect.data_crossvalidation/{}/train_connect.data".format(f),
                        "connect.data_crossvalidation/{}/test_connect.data".format(f),
                        "result", str(p), str(b)], stdout=PIPE, stderr=PIPE)
        out, _ = forest.communicate()
        times.append(time.time()-time_start)
        out = out.decode().strip()
        print("onefold: ", p, b, out)
        results.append(float(out))
    score = sum(results) / len(results)
    print(p, b, score)
    accuracy[i, j] = score
    exec_time[i, j] = sum(times) / len(times)

thread_fifo = []
for i in range(lb):
    if i < 14:
        continue
    for j in range(lp):
        th = threading.Thread(target=lambda: test_parameters((ps[i, j], bs[i, j]), i, j))
        thread_fifo.append(th)
        th.start()
        if len(thread_fifo) > 6:
            for thread in thread_fifo:
                thread.join()
            thread_fifo = []
        with open("dump_f", "bw") as dump:
            pickle.dump((ps, bs, accuracy), dump)
        with open("dump_f_time", "bw") as dump:
            pickle.dump((ps, bs, exec_time), dump)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(ps, bs, accuracy,
                rstride=1, cstride=1,
                linewidth=1, antialiased=False)
ax.set_xlabel('P')
ax.set_ylabel('B')
ax.set_zlabel('accuracy')

with open("dump", "bw") as dump:
    pickle.dump((ps,bs,accuracy), dump)

plt.show()


import SockArp
import SockIcmp
import SockIp
import PacketProcess
import PCAPfilter
import SockUdp
import Sniffer
import SockTcp
import cPickle
import zlib
import gzip
from scapy.utils import PcapReader, PcapWriter
from scapy.arch.pcapdnet import *
from scapy.arch import pcapdnet
from scapy.all import *
from scapy.sendrecv import debug, srp1
from scapy.layers.l2 import Ether, ARP
from scapy.data import MTU, ETHER_BROADCAST, ETH_P_ARP

conf.use_pcap = 1
conf.use_dnet = 1

LOOPBACK_NAME = "lo0"
WINDOWS = True

class PicklablePacket:
    def __init__(self, pkt):
        self.contents = str(pkt)
        self.time = pkt.time

    def __call__(self):
        pkt = scapy.layers.l2.Ether(self.contents)
        pkt.time = self.time
        return pkt

    def dumps(self):
        return gzip.zlib.compress(cPickle.dumps(self)).encode('base64')

    @staticmethod
    def loads(string):
        p = cPickle.loads(gzip.zlib.decompress(string.decode('base64')))
        return p()

def PCAPread(file_name, start, count):
    reader = PcapReader(file_name)
    if start > 0:
        reader.read_all(start)
    if count > 0:
        return reader.read_all(count)
    else:
        return reader.read_all(-1)

def PCAPwrite(file_name, packets):
    writer = PcapWriter(file_name, append=True)
    for p in packets:
        writer.write(p)
    writer.flush()
    writer.close()

if __name__ == '__main__':

    s = SockTcp.sockTCP('192.168.1.102', 14006, '6.7.8.9', 50007,
                        '746869736973666F727463707061636B6574746573', '34b56b2f')
    s.TCP_CWR = 1
    s.TCP_ECE = 1
    s.TCP_Window = 6558
    s.TCP_FIN = 1
    s.TCP_RST = 1
    s2 = SockUdp.sockUDP('192.168.1.102', 14005, '6.7.8.9',
                         8080, '746869736973666F727564707061636B657474657374')
    s3 = SockIp.sockIP('192.168.1.102', '6.7.8.9',
                       '746869736973666F72697074657374', socket.IPPROTO_RAW, '34b56b2f8d7d9099')
    s4 = SockArp.sockARP("6.7.8.9", "192.168.1.102",
                         "ee:ef:45:4f:f3:01", "38:76:fc:c6:b4:11", 2)
    s5 = SockIcmp.sockICMP('192.168.1.102', '6.7.8.9', 8,
                           0, '746869736973666F7269636D7074657374')
    fn = "pcaps/new1.pcap"
    s.appendPCAP(fn)
    s.TCP_Offset = 66
    s.TCP_CWR = 0
    s.TCP_AckNum = 1235
    s = SockTcp.sockTCP('192.168.1.102', 14006, '6.7.8.9', 50007, '746869736973666F727463707061636B65747465',
                        '34b56b2f')
    s.appendPCAP(fn)


#!/usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function, division, absolute_import

import sys
import bench

if __name__ == "__main":

    if len(sys.argv) < 4:
        print("""Pass me:
              1) the command to execute
              2) the confidence level (between 0 and 1)
              3) p (number of iterations)
              4) the percentage that, when reached, the loop breaks""")
    else:
        command = sys.argv[1]
        confidence_level = float(sys.argv[2])
        p_iterations = int(sys.argv[3])
        break_if_error_percentage_is = float(sys.argv[4])
        interval, mean, sdev, error_percentage = bench.startup(command, confidence_level, p_iterations, break_if_error_percentage_is)
        print("Results Startup:")
        print("Interval:", interval)
        print("Mean:", mean)
        print("Standard deviation:", sdev)
        print("Interval percentage:", error_percentage)


# !/usr/bin/env python
# -*- coding:utf-8 -*-
"""
-------------------------------------------------
   File Name：     srv_legacy1
   Description :
   Author :       simplefly
   date：          2018/1/26
-------------------------------------------------
   Change Activity:
                   2018/1/26:
-------------------------------------------------
"""
__author__ = 'simplefly'
# Uses the legacy "socketserver" Standard Library module to write a server.

from socketserver import BaseRequestHandler, TCPServer, ThreadingMixIn
from simple_lib import zen_utils

class ZenHandler(BaseRequestHandler):
    def handle(self):
        zen_utils.handle_conversation(self.request, self.client_address)

class ZenServer(ThreadingMixIn, TCPServer):
    allow_reuse_address = 1
    # address_family = socket.AF_INET6 # uncomment if you need IPv6

if __name__ == '__main__':
    address = zen_utils.parse_command_line('legacy "SocketServer" server')
    server = ZenServer(address, ZenHandler)
    server.serve_forever()


# -*- coding: utf-8 -*-
#
# Copyright © 2009-2010 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)

"""
Editor widget based on QtGui.QPlainTextEdit
"""

# pylint: disable=C0103
# pylint: disable=R0903
# pylint: disable=R0911
# pylint: disable=R0201

from __future__ import division

import sys, os, re, os.path as osp, time

from spyderlib.qt.QtGui import (QMouseEvent, QColor, QMenu, QApplication,
                                QSplitter, QFont, QTextEdit, QTextFormat,
                                QPainter, QTextCursor, QPlainTextEdit, QBrush,
                                QTextDocument, QTextCharFormat, QPixmap,
                                QPrinter, QToolTip, QCursor, QInputDialog,
                                QTextBlockUserData, QLineEdit, QShortcut,
                                QKeySequence, QWidget, QKeyEvent)
from spyderlib.qt.QtCore import (Qt, SIGNAL, QString, QEvent, QTimer, QRect,
                                 QRegExp, PYQT_VERSION_STR, QVariant, QSize)

# For debugging purpose:
STDOUT = sys.stdout

# Local import
#TODO: Try to separate this module from spyderlib to create a self
#      consistent editor module (Qt source code and shell widgets library)
from spyderlib.config import CONF, get_font, get_icon, get_image_path, _
from spyderlib.utils.qthelpers import (add_actions, create_action, keybinding,
                                       mimedata2url)
from spyderlib.utils.dochelpers import getobj
from spyderlib.utils import encoding, sourcecode, is_keyword
from spyderlib.widgets.editortools import PythonCFM, check, OutlineExplorer
from spyderlib.widgets.sourcecode.base import TextEditBaseWidget
from spyderlib.widgets.sourcecode import syntaxhighlighters

#===============================================================================
# Viewport widgets
#===============================================================================
class LineNumberArea(QWidget):
    def __init__(self, editor):
        QWidget.__init__(self, editor)
        self.code_editor = editor

    def sizeHint(self):
        return QSize(self.code_editor.compute_linenumberarea_width(), 0)

    def paintEvent(self, event):
        self.code_editor.linenumberarea_paint_event(event)

    def mousePressEvent(self, event):
        self.code_editor.linenumberarea_mousepress_event(event)

    def mouseDoubleClickEvent(self, event):
        self.code_editor.linenumberarea_mousedoubleclick_event(event)

class ScrollFlagArea(QWidget):
    WIDTH = 12
    FLAGS_DX = 4
    FLAGS_DY = 4
    def __init__(self, editor):
        QWidget.__init__(self, editor)
        self.setAttribute(Qt.WA_OpaquePaintEvent)
        self.code_editor = editor
        self.scrollbar = editor.verticalScrollBar()

    def sizeHint(self):
        return QSize(self.WIDTH, 0)

    def paintEvent(self, event):
        self.code_editor.scrollflagarea_paint_event(event)

    def __get_scale(self):
        sb = self.scrollbar
        return float(self.height())/(sb.maximum()-sb.minimum()+sb.pageStep())

    def scrollflag_to_scrollbar(self, y):
        sb = self.scrollbar
        return sb.minimum()+max([0, y/self.__get_scale()-.5*sb.pageStep()])

    def scrollbar_to_scrollflag(self, y):
        sb = self.scrollbar
        return (y-sb.minimum())*self.__get_scale()

    def make_flag_qrect(self, line_nb):
        y = self.scrollbar_to_scrollflag(line_nb)
        return QRect(self.FLAGS_DX/2, y-self.FLAGS_DY/2,
                     self.WIDTH-self.FLAGS_DX, self.FLAGS_DY)

    def mousePressEvent(self, event):
        y = event.pos().y()
        self.scrollbar.setValue( self.scrollflag_to_scrollbar(y) )

class EdgeLine(QWidget):
    def __init__(self, editor):
        QWidget.__init__(self, editor)
        self.code_editor = editor
        self.column = 79

    def paintEvent(self, event):
        painter = QPainter(self)
        color = QColor(Qt.darkGray)
        color.setAlphaF(.5)
        painter.fillRect(event.rect(), color)

#===============================================================================
# CodeEditor widget
#===============================================================================
class BlockUserData(QTextBlockUserData):
    def __init__(self, editor):
        QTextBlockUserData.__init__(self)
        self.editor = editor
        self.breakpoint = False
        self.breakpoint_condition = None
        self.code_analysis = []
        self.todo = ''
        self.editor.blockuserdata_list.append(self)

    def is_empty(self):
        return not self.breakpoint and not self.code_analysis and not self.todo

    def __del__(self):
        bud_list = self.editor.blockuserdata_list
        bud_list.pop(bud_list.index(self))

def get_primary_at(source_code, offset):
    """Return Python object in *source_code* at *offset*"""
    try:
        import rope.base.worder
        word_finder = rope.base.worder.Worder(source_code, True)
        return word_finder.get_primary_at(offset)
    except ImportError:
        return

def set_scrollflagarea_painter(painter, light_color):
    painter.setPen(QColor(light_color).darker(120))
    painter.setBrush(QBrush(QColor(light_color)))

def get_file_language(filename, text=None):
    ext = osp.splitext(filename)[1]
    if ext.startswith('.'):
        ext = ext[1:] # file extension with leading dot
    language = ext
    if not ext:
        if text is None:
            text, _enc = encoding.read(filename)
        for line in text.splitlines():
            if not line.strip():
                continue
            if line.startswith('#!') and \
               line[2:].split() == ['/usr/bin/env', 'python']:
                    language = 'python'
            else:
                break
    return language

class CodeEditor(TextEditBaseWidget):
    """
    Source Code Editor Widget based exclusively on Qt
    """
    LANGUAGES = {
                 ('py', 'pyw', 'python', 'ipy'): (syntaxhighlighters.PythonSH,
                                                  '#', PythonCFM),
                 ('pyx', 'pxi', 'pxd'): (syntaxhighlighters.CythonSH,
                                         '#', PythonCFM),
                 ('f', 'for'): (syntaxhighlighters.Fortran77SH, 'c', None),
                 ('f90', 'f95', 'f2k'): (syntaxhighlighters.FortranSH,
                                         '!', None),
                 ('diff', 'patch', 'rej'): (syntaxhighlighters.DiffSH, '',
                                            None),
#                 'css': (QsciLexerCSS, '#', None),
                 ('po', 'pot'): (syntaxhighlighters.GetTextSH, '#', None),
#                 ('htm', 'html'): (QsciLexerHTML, '', None),
                 ('c', 'cpp', 'h', 'hpp', 'cxx'): (syntaxhighlighters.CppSH,
                                                   '//', None),
#                 ('bat', 'cmd', 'nt'): (QsciLexerBatch, 'rem ', None),
#                 ('properties', 'session', 'ini', 'inf', 'reg', 'url',
#                  'cfg', 'cnf', 'aut', 'iss'): (QsciLexerProperties, '#', None),
                 }
    TAB_ALWAYS_INDENTS = ('py', 'pyw', 'python', 'c', 'cpp', 'h')

    def __init__(self, parent=None):
        TextEditBaseWidget.__init__(self, parent)

        # Calltips
        calltip_size = CONF.get('editor_appearance', 'calltips/size')
        calltip_font = get_font('editor_appearance', 'calltips')
        self.setup_calltips(calltip_size, calltip_font)

        # Completion
        completion_size = CONF.get('editor_appearance', 'completion/size')
        completion_font = get_font('editor_appearance', 'completion')
        self.completion_widget.setup_appearance(completion_size,
                                                completion_font)

        # Caret (text cursor)
        self.setCursorWidth( CONF.get('editor_appearance', 'cursor/width') )

        # Side areas background color
        self.area_background_color = QColor(Qt.white)

        # 80-col edge line
        self.edge_line_enabled = True
        self.edge_line = EdgeLine(self)

        # Markers
        self.markers_margin = True
        self.markers_margin_width = 15
        self.error_pixmap = QPixmap(get_image_path('error.png'), 'png')
        self.warning_pixmap = QPixmap(get_image_path('warning.png'), 'png')
        self.todo_pixmap = QPixmap(get_image_path('todo.png'), 'png')
        self.bp_pixmap = QPixmap(get_image_path('breakpoint.png'), 'png')
        self.bpc_pixmap = QPixmap(get_image_path('breakpoint_cond.png'), 'png')

        # Line number area management
        self.linenumbers_margin = True
        self.linenumberarea_enabled = None
        self.linenumberarea = LineNumberArea(self)
        self.connect(self, SIGNAL("blockCountChanged(int)"),
                     self.update_linenumberarea_width)
        self.connect(self, SIGNAL("updateRequest(QRect,int)"),
                     self.update_linenumberarea)

        # Syntax highlighting
        self.highlighter_class = syntaxhighlighters.TextSH
        self.highlighter = None
        ccs = 'Spyder'
        if ccs not in syntaxhighlighters.COLOR_SCHEME_NAMES:
            ccs = syntaxhighlighters.COLOR_SCHEME_NAMES[0]
        self.color_scheme = ccs

        #  Background colors: current line, occurences
        self.currentline_color = QColor(Qt.red).lighter(190)
        self.highlight_current_line_enabled = False

        # Scrollbar flag area
        self.scrollflagarea_enabled = None
        self.scrollflagarea = ScrollFlagArea(self)
        self.scrollflagarea.hide()
        self.warning_color = "#FFAD07"
        self.error_color = "#EA2B0E"
        self.todo_color = "#B4D4F3"
        self.breakpoint_color = "#30E62E"

        self.update_linenumberarea_width(0)

        self.document_id = id(self)

        # Indicate occurences of the selected word
        self.connect(self, SIGNAL('cursorPositionChanged()'),
                     self.__cursor_position_changed)
        self.__find_first_pos = None
        self.__find_flags = None

        self.supported_language = None
        self.classfunc_match = None
        self.comment_string = None

        # Block user data
        self.blockuserdata_list = []

        # Mark occurences timer
        self.occurence_highlighting = None
        self.occurence_timer = QTimer(self)
        self.occurence_timer.setSingleShot(True)
        self.occurence_timer.setInterval(1500)
        self.connect(self.occurence_timer, SIGNAL("timeout()"),
                     self.__mark_occurences)
        self.occurences = []
        self.occurence_color = QColor(Qt.yellow).lighter(160)

        # Context menu
        self.setup_context_menu()

        # Tab key behavior
        self.tab_indents = None
        self.tab_mode = True # see CodeEditor.set_tab_mode

        # Intelligent backspace mode
        self.intelligent_backspace = True

        self.go_to_definition_enabled = False
        self.close_parentheses_enabled = True
        self.auto_unindent_enabled = True

        # Mouse tracking
        self.setMouseTracking(True)
        self.__cursor_changed = False
        self.ctrl_click_color = QColor(Qt.blue)

        # Keyboard shortcuts
        self.codecomp_sc = QShortcut(QKeySequence("Ctrl+Space"), self,
                                     self.do_code_completion)
        self.codecomp_sc.setContext(Qt.WidgetWithChildrenShortcut)
        self.duplicate_sc = QShortcut(QKeySequence("Ctrl+Alt+Up"), self,
                                      self.duplicate_line)
        self.duplicate_sc.setContext(Qt.WidgetWithChildrenShortcut)
        self.copyline_sc = QShortcut(QKeySequence("Ctrl+Alt+Down"), self,
                                      self.copy_line)
        self.copyline_sc.setContext(Qt.WidgetWithChildrenShortcut)
        self.deleteline_sc = QShortcut(QKeySequence("Ctrl+D"), self,
                                       self.delete_line)
        self.deleteline_sc.setContext(Qt.WidgetWithChildrenShortcut)
        self.gotodef_sc = QShortcut(QKeySequence("Ctrl+G"), self,
                                    self.do_go_to_definition)
        self.gotodef_sc.setContext(Qt.WidgetWithChildrenShortcut)
        self.comment_sc = QShortcut(QKeySequence("Ctrl+3"), self,
                                    self.comment)
        self.comment_sc.setContext(Qt.WidgetWithChildrenShortcut)
        self.uncomment_sc = QShortcut(QKeySequence("Ctrl+2"), self,
                                      self.uncomment)
        self.uncomment_sc.setContext(Qt.WidgetWithChildrenShortcut)
        self.blockcomment_sc = QShortcut(QKeySequence("Ctrl+4"), self,
                                         self.blockcomment)
        self.blockcomment_sc.setContext(Qt.WidgetWithChildrenShortcut)
        self.unblockcomment_sc = QShortcut(QKeySequence("Ctrl+5"), self,
                                           self.unblockcomment)
        self.unblockcomment_sc.setContext(Qt.WidgetWithChildrenShortcut)

    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [
                (self.codecomp_sc, "Code completion", "Ctrl+Space"),
                (self.duplicate_sc, "Duplicate line", "Ctrl+Alt+Up"),
                (self.copyline_sc, "Copy line", "Ctrl+Alt+Down"),
                (self.deleteline_sc, "Delete line", "Ctrl+D"),
                (self.gotodef_sc, "Go to definition", "Ctrl+G"),
                (self.comment_sc, "Comment", "Ctrl+3"),
                (self.uncomment_sc, "Uncomment", "Ctrl+2"),
                (self.blockcomment_sc, "Blockcomment", "Ctrl+4"),
                (self.unblockcomment_sc, "Unblockcomment", "Ctrl+5"),
                ]

    def closeEvent(self, event):
        TextEditBaseWidget.closeEvent(self, event)
        if PYQT_VERSION_STR.startswith('4.6'):
            self.emit(SIGNAL('destroyed()'))

    def get_document_id(self):
        return self.document_id

    def set_as_clone(self, editor):
        """Set as clone editor"""
        self.setDocument(editor.document())
        self.document_id = editor.get_document_id()
        self.highlighter = editor.highlighter
        self._apply_highlighter_color_scheme()

    #-----Widget setup and options
    def toggle_wrap_mode(self, enable):
        """Enable/disable wrap mode"""
        self.set_wrap_mode('word' if enable else None)

    def setup_editor(self, linenumbers=True, language=None, code_analysis=False,
                     font=None, color_scheme=None, wrap=False, tab_mode=True,
                     intelligent_backspace=True, highlight_current_line=True,
                     occurence_highlighting=True, scrollflagarea=True,
                     edge_line=True, edge_line_column=80, todo_list=True,
                     codecompletion_auto=False, codecompletion_case=True,
                     codecompletion_single=False, codecompletion_enter=False,
                     calltips=None, go_to_definition=False,
                     close_parentheses=True, auto_unindent=True,
                     cloned_from=None):
        # Code completion and calltips
        self.set_codecompletion_auto(codecompletion_auto)
        self.set_codecompletion_case(codecompletion_case)
        self.set_codecompletion_single(codecompletion_single)
        self.set_codecompletion_enter(codecompletion_enter)
        self.set_calltips(calltips)
        self.set_go_to_definition_enabled(go_to_definition)
        self.set_close_parentheses_enabled(close_parentheses)
        self.set_auto_unindent_enabled(auto_unindent)

        # Scrollbar flag area
        self.set_scrollflagarea_enabled(scrollflagarea)

        # Edge line
        self.set_edge_line_enabled(edge_line)
        self.set_edge_line_column(edge_line_column)

        # Line number area
        if cloned_from:
            self.setFont(font) # this is required for line numbers area
        self.setup_margins(linenumbers, code_analysis, todo_list)

        # Lexer
        self.set_language(language)

        # Highlight current line
        self.set_highlight_current_line(highlight_current_line)

        # Occurence highlighting
        self.set_occurence_highlighting(occurence_highlighting)

        # Tab always indents (even when cursor is not at the begin of line)
        self.set_tab_mode(tab_mode)

        # Intelligent backspace
        self.toggle_intelligent_backspace(intelligent_backspace)

        if cloned_from is not None:
            self.set_as_clone(cloned_from)
            self.update_linenumberarea_width(0)
        elif font is not None:
            self.set_font(font, color_scheme)
        elif color_scheme is not None:
            self.set_color_scheme(color_scheme)

        self.toggle_wrap_mode(wrap)

    def set_tab_mode(self, enable):
        """
        enabled = tab always indent
        (otherwise tab indents only when cursor is at the beginning of a line)
        """
        self.tab_mode = enable

    def toggle_intelligent_backspace(self, state):
        self.intelligent_backspace = state

    def set_go_to_definition_enabled(self, enable):
        """Enable/Disable go-to-definition feature, which is implemented in
        child class -> Editor widget"""
        self.go_to_definition_enabled = enable

    def set_close_parentheses_enabled(self, enable):
        """Enable/disable automatic parentheses insertion feature"""
        self.close_parentheses_enabled = enable

    def set_auto_unindent_enabled(self, enable):
        """Enable/disable automatic unindent after else/elif/finally/except"""
        self.auto_unindent_enabled = enable

    def set_occurence_highlighting(self, enable):
        """Enable/disable occurence highlighting"""
        self.occurence_highlighting = enable
        if not enable:
            self.__clear_occurences()

    def set_highlight_current_line(self, enable):
        """Enable/disable current line highlighting"""
        self.highlight_current_line_enabled = enable
        self.highlight_current_line()

    def set_language(self, language):
        self.tab_indents = language in self.TAB_ALWAYS_INDENTS
        self.supported_language = False
        self.comment_string = ''
        sh_class = syntaxhighlighters.TextSH
        if language is not None:
            for key in self.LANGUAGES:
                if language.lower() in key:
                    self.supported_language = True
                    sh_class, comment_string, CFMatch = self.LANGUAGES[key]
                    self.comment_string = comment_string
                    if CFMatch is None:
                        self.classfunc_match = None
                    else:
                        self.classfunc_match = CFMatch()
                    break
        if self.highlighter_class is not sh_class:
            self.highlighter_class = sh_class
            self.apply_highlighter_settings()

    def is_python(self):
        return self.highlighter_class is syntaxhighlighters.PythonSH

    def is_cython(self):
        return self.highlighter_class is syntaxhighlighters.CythonSH

    def rehighlight(self):
        """
        Rehighlight the whole document to rebuild outline explorer data
        and import statements data from scratch
        """
        if self.highlighter is not None:
            self.highlighter.rehighlight()

    def setup_margins(self, linenumbers=True, code_analysis=False,
                      todo_list=True):
        """
        Setup margin settings
        (except font, now set in self.set_font)
        """
        self.linenumbers_margin = linenumbers
        self.markers_margin = code_analysis or todo_list
        enabled = linenumbers or code_analysis or todo_list
        self.set_linenumberarea_enabled(enabled)

    def remove_trailing_spaces(self):
        """Remove trailing spaces"""
        cursor = self.textCursor()
        cursor.beginEditBlock()
        cursor.movePosition(QTextCursor.Start)
        while True:
            cursor.movePosition(QTextCursor.EndOfBlock)
            text = unicode(cursor.block().text())
            length = len(text)-len(text.rstrip())
            if length > 0:
                cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor,
                                    length)
                cursor.removeSelectedText()
            if cursor.atEnd():
                break
            cursor.movePosition(QTextCursor.NextBlock)
        cursor.endEditBlock()

    def fix_indentation(self):
        """Replace tabs by spaces"""
        text_before = unicode(self.toPlainText())
        text_after = sourcecode.fix_indentation(text_before)
        if text_before != text_after:
            self.setPlainText(text_after)
            self.document().setModified(True)

    def get_current_object(self):
        """Return current object (string) -- requires 'rope'"""
        source_code = unicode(self.toPlainText())
        offset = self.get_position('cursor')
        return get_primary_at(source_code, offset)

    #------Find occurences
    def __find_first(self, text):
        """Find first occurence: scan whole document"""
        flags = QTextDocument.FindCaseSensitively|QTextDocument.FindWholeWords
        cursor = self.textCursor()
        # Scanning whole document
        cursor.movePosition(QTextCursor.Start)
        regexp = QRegExp(r"\b%s\b" % QRegExp.escape(text), Qt.CaseSensitive)
        cursor = self.document().find(regexp, cursor, flags)
        self.__find_first_pos = cursor.position()
        return cursor

    def __find_next(self, text, cursor):
        """Find next occurence"""
        flags = QTextDocument.FindCaseSensitively|QTextDocument.FindWholeWords
        regexp = QRegExp(r"\b%s\b" % QRegExp.escape(text), Qt.CaseSensitive)
        cursor = self.document().find(regexp, cursor, flags)
        if cursor.position() != self.__find_first_pos:
            return cursor

    def __cursor_position_changed(self):
        """Cursor position has changed"""
        line, column = self.get_cursor_line_column()
        self.emit(SIGNAL('cursorPositionChanged(int,int)'), line, column)
        self.highlight_current_line()
        if self.occurence_highlighting:
            self.occurence_timer.stop()
            self.occurence_timer.start()

    def __clear_occurences(self):
        """Clear occurence markers"""
        self.occurences = []
        self.clear_extra_selections('occurences')
        self.scrollflagarea.update()

    def __highlight_selection(self, key, cursor, foreground_color=None,
                        background_color=None, underline_color=None,
                        underline_style=QTextCharFormat.SpellCheckUnderline,
                        update=False):
        extra_selections = self.get_extra_selections(key)
        selection = QTextEdit.ExtraSelection()
        if foreground_color is not None:
            selection.format.setForeground(foreground_color)
        if background_color is not None:
            selection.format.setBackground(background_color)
        if underline_color is not None:
            selection.format.setProperty(QTextFormat.TextUnderlineStyle,
                                         QVariant(underline_style))
            selection.format.setProperty(QTextFormat.TextUnderlineColor,
                                         QVariant(underline_color))
        selection.format.setProperty(QTextFormat.FullWidthSelection,
                                     QVariant(True))
        selection.cursor = cursor
        extra_selections.append(selection)
        self.set_extra_selections(key, extra_selections)
        if update:
            self.update_extra_selections()

    def __mark_occurences(self):
        """Marking occurences of the currently selected word"""
        self.__clear_occurences()

        if not self.supported_language:
            return
        if self.has_selected_text():
            block1, block2 = self.get_selection_bounds()
            if block1 != block2:
                # Selection extends to more than one line
                return
            text = self.get_selected_text()
            if not re.match(r'([a-zA-Z_]+[0-9a-zA-Z_]*)$', text):
                # Selection is not a word
                return
        else:
            text = self.get_current_word()
            if text is None:
                return
        if (self.is_python() or self.is_cython()) and \
           (is_keyword(unicode(text)) or unicode(text) == 'self'):
            return

        # Highlighting all occurences of word *text*
        cursor = self.__find_first(text)
        self.occurences = []
        while cursor:
            self.occurences.append(cursor.blockNumber())
            self.__highlight_selection('occurences', cursor,
                                       background_color=self.occurence_color)
            cursor = self.__find_next(text, cursor)
        self.update_extra_selections()
        self.occurences.pop(-1)
        self.scrollflagarea.update()

    #-----markers
    def get_markers_margin(self):
        if self.markers_margin:
            return self.markers_margin_width
        else:
            return 0

    #-----linenumberarea
    def set_linenumberarea_enabled(self, state):
        self.linenumberarea_enabled = state
        self.linenumberarea.setVisible(state)
        self.update_linenumberarea_width(0)

    def get_linenumberarea_width(self):
        """Return current line number area width"""
        return self.linenumberarea.contentsRect().width()

    def compute_linenumberarea_width(self):
        """Compute and return line number area width"""
        if not self.linenumberarea_enabled:
            return 0
        digits = 1
        maxb = max(1, self.blockCount())
        while maxb >= 10:
            maxb /= 10
            digits += 1
        if self.linenumbers_margin:
            linenumbers_margin = 3+self.fontMetrics().width('9')*digits
        else:
            linenumbers_margin = 0
        return linenumbers_margin+self.get_markers_margin()

    def update_linenumberarea_width(self, new_block_count):
        """Update line number area width"""
        self.setViewportMargins(self.compute_linenumberarea_width(), 0,
                                self.get_scrollflagarea_width(), 0)

    def update_linenumberarea(self, qrect, dy):
        """Update line number area"""
        if dy:
            self.linenumberarea.scroll(0, dy)
        else:
            self.linenumberarea.update(0, qrect.y(),
                                       self.linenumberarea.width(),
                                       qrect.height())
        if qrect.contains(self.viewport().rect()):
            self.update_linenumberarea_width(0)

    def linenumberarea_paint_event(self, event):
        font_height = self.fontMetrics().height()
        painter = QPainter(self.linenumberarea)
        painter.fillRect(event.rect(), self.area_background_color)

        block = self.firstVisibleBlock()
        block_number = block.blockNumber()
        top = self.blockBoundingGeometry(block).translated(
                                                    self.contentOffset()).top()
        bottom = top + self.blockBoundingRect(block).height()

        painter.setPen(Qt.darkGray)
        def draw_pixmap(ytop, pixmap):
            painter.drawPixmap(0, ytop+(font_height-pixmap.height())/2, pixmap)
        while block.isValid() and top <= event.rect().bottom():
            if block.isVisible() and bottom >= event.rect().top():
                line_number = block_number+1
                if self.linenumbers_margin:
                    number = QString.number(line_number)
                    painter.drawText(0, top, self.linenumberarea.width(),
                                     font_height, Qt.AlignRight|Qt.AlignBottom,
                                     number)
                data = block.userData()
                if self.markers_margin and data:
                    if data.code_analysis:
                        for _message, error in data.code_analysis:
                            if error:
                                break
                        if error:
                            draw_pixmap(top, self.error_pixmap)
                        else:
                            draw_pixmap(top, self.warning_pixmap)
                    if data.todo:
                        draw_pixmap(top, self.todo_pixmap)
                    if data.breakpoint:
                        if data.breakpoint_condition is None:
                            draw_pixmap(top, self.bp_pixmap)
                        else:
                            draw_pixmap(top, self.bpc_pixmap)

            block = block.next()
            top = bottom
            bottom = top + self.blockBoundingRect(block).height()
            block_number += 1

    def __get_linenumber_from_mouse_event(self, event):
        block = self.firstVisibleBlock()
        line_number = block.blockNumber()
        top = self.blockBoundingGeometry(block).translated(
                                                    self.contentOffset()).top()
        bottom = top + self.blockBoundingRect(block).height()

        while block.isValid() and top < event.pos().y():
            block = block.next()
            top = bottom
            bottom = top + self.blockBoundingRect(block).height()
            line_number += 1

        return line_number

    def linenumberarea_mousepress_event(self, event):
        line_number = self.__get_linenumber_from_mouse_event(event)
        block = self.document().findBlockByNumber(line_number-1)
        data = block.userData()
        if data and data.code_analysis:
            self.__show_code_analysis_results(line_number, data.code_analysis)

    def linenumberarea_mousedoubleclick_event(self, event):
        line_number = self.__get_linenumber_from_mouse_event(event)
        shift = event.modifiers() & Qt.ShiftModifier
        self.add_remove_breakpoint(line_number, edit_condition=shift)

    #------Breakpoints
    def add_remove_breakpoint(self, line_number=None, condition=None,
                              edit_condition=False):
        if not self.is_python() and not self.is_cython():
            return
        if line_number is None:
            block = self.textCursor().block()
        else:
            block = self.document().findBlockByNumber(line_number-1)
        data = block.userData()
        if data:
            data.breakpoint = not data.breakpoint
        else:
            data = BlockUserData(self)
            data.breakpoint = True
        if condition is not None:
            data.breakpoint_condition = condition
        if edit_condition:
            data.breakpoint = True
            condition = data.breakpoint_condition
            if condition is None:
                condition = ''
            condition, valid = QInputDialog.getText(self,
                                        _('Breakpoint'),
                                        _("Condition:"),
                                        QLineEdit.Normal, condition)
            if valid:
                condition = str(condition)
                if not condition:
                    condition = None
                data.breakpoint_condition = condition
            else:
                return
        if data.breakpoint:
            text = unicode(block.text()).strip()
            if len(text) == 0 or text.startswith('#') or text.startswith('"') \
               or text.startswith("'"):
                data.breakpoint = False
        block.setUserData(data)
        self.linenumberarea.update()
        self.scrollflagarea.update()
        self.emit(SIGNAL('breakpoints_changed()'))

    def get_breakpoints(self):
        breakpoints = []
        block = self.document().firstBlock()
        for line_number in xrange(1, self.document().blockCount()+1):
            data = block.userData()
            if data and data.breakpoint:
                breakpoints.append((line_number, data.breakpoint_condition))
            block = block.next()
        return breakpoints

    def clear_breakpoints(self):
        for data in self.blockuserdata_list[:]:
            data.breakpoint = False
#            data.breakpoint_condition = None # not necessary, but logical
            if data.is_empty():
                del data

    def set_breakpoints(self, breakpoints):
        self.clear_breakpoints()
        for line_number, condition in breakpoints:
            self.add_remove_breakpoint(line_number, condition)

    #-----Code introspection
    def do_code_completion(self):
        if not self.is_completion_widget_visible():
            self.emit(SIGNAL('trigger_code_completion(bool)'), False)

    def do_go_to_definition(self):
        self.emit(SIGNAL("go_to_definition(int)"), self.textCursor().position())

    #-----edge line
    def set_edge_line_enabled(self, state):
        self.edge_line_enabled = state
        self.edge_line.setVisible(state)

    def set_edge_line_column(self, column):
        self.edge_line.column = column
        self.edge_line.update()

    #-----scrollflagarea
    def set_scrollflagarea_enabled(self, state):
        self.scrollflagarea_enabled = state
        self.scrollflagarea.setVisible(state)
        self.update_linenumberarea_width(0)

    def get_scrollflagarea_width(self):
        if self.scrollflagarea_enabled:
            return ScrollFlagArea.WIDTH
        else:
            return 0

    def scrollflagarea_paint_event(self, event):
        make_flag = self.scrollflagarea.make_flag_qrect
        painter = QPainter(self.scrollflagarea)
        painter.fillRect(event.rect(), self.area_background_color)
        block = self.document().firstBlock()
        for line_number in xrange(1, self.document().blockCount()+1):
            data = block.userData()
            if data:
                if data.code_analysis:
                    # Warnings
                    color = self.warning_color
                    for _message, error in data.code_analysis:
                        if error:
                            color = self.error_color
                            break
                    set_scrollflagarea_painter(painter, color)
                    painter.drawRect(make_flag(line_number))
                if data.todo:
                    # TODOs
                    set_scrollflagarea_painter(painter, self.todo_color)
                    painter.drawRect(make_flag(line_number))
                if data.breakpoint:
                    # Breakpoints
                    set_scrollflagarea_painter(painter,
                                                     self.breakpoint_color)
                    painter.drawRect(make_flag(line_number))
            block = block.next()
        # Occurences
        if self.occurences:
            set_scrollflagarea_painter(painter, self.occurence_color)
            for line in self.occurences:
                painter.drawRect(make_flag(line))

    def resizeEvent(self, event):
        """Reimplemented Qt method to handle line number area resizing"""
        TextEditBaseWidget.resizeEvent(self, event)
        cr = self.contentsRect()
        self.linenumberarea.setGeometry(\
                        QRect(cr.left(), cr.top(),
                              self.compute_linenumberarea_width(), cr.height()))
        self.__set_scrollflagarea_geometry(cr)

    def __set_scrollflagarea_geometry(self, contentrect):
        cr = contentrect
        if self.verticalScrollBar().isVisible():
            vsbw = self.verticalScrollBar().contentsRect().width()
        else:
            vsbw = 0
        _left, _top, right, _bottom = self.getContentsMargins()
        if right > vsbw:
            # Depending on the platform (e.g. on Ubuntu), the scrollbar sizes
            # may be taken into account in the contents margins whereas it is
            # not on Windows for example
            vsbw = 0
        self.scrollflagarea.setGeometry(\
                        QRect(cr.right()-ScrollFlagArea.WIDTH-vsbw, cr.top(),
                              self.scrollflagarea.WIDTH, cr.height()))

    #-----edgeline
    def viewportEvent(self, event):
        # 80-column edge line
        cr = self.contentsRect()
        offset = self.contentOffset()
        x = self.blockBoundingGeometry(self.firstVisibleBlock()) \
            .translated(offset.x(), offset.y()).left() \
            +self.get_linenumberarea_width() \
            +self.fontMetrics().width('9')*self.edge_line.column+5
        self.edge_line.setGeometry(\
                        QRect(x, cr.top(), 1, cr.bottom()))
        self.__set_scrollflagarea_geometry(cr)
        return TextEditBaseWidget.viewportEvent(self, event)

    #-----highlight current line
    def highlight_current_line(self):
        """Highlight current line"""
        if self.highlight_current_line_enabled:
            selection = QTextEdit.ExtraSelection()
            selection.format.setProperty(QTextFormat.FullWidthSelection,
                                         QVariant(True))
            selection.format.setBackground(self.currentline_color)
            selection.cursor = self.textCursor()
            selection.cursor.clearSelection()
            self.set_extra_selections('current_line', [selection])
            self.update_extra_selections()
        else:
            self.clear_extra_selections('current_line')

    def delete(self):
        """Remove selected text"""
        # Used by global callbacks in Spyder -> delete_action
        self.remove_selected_text()

    def _apply_highlighter_color_scheme(self):
        hl = self.highlighter
        if hl is not None:
            self.set_palette(background=hl.get_background_color(),
                             foreground=hl.get_foreground_color())
            self.currentline_color = hl.get_currentline_color()
            self.occurence_color = hl.get_occurence_color()
            self.ctrl_click_color = hl.get_ctrlclick_color()
            self.area_background_color = hl.get_sideareas_color()
            self.matched_p_color = hl.get_matched_p_color()
            self.unmatched_p_color = hl.get_unmatched_p_color()
        self.highlight_current_line()

    def apply_highlighter_settings(self, color_scheme=None):
        if self.highlighter_class is not None:
            if not isinstance(self.highlighter, self.highlighter_class):
                # Highlighter object has not been constructed yet
                # or language has changed so it must be re-constructed
                if self.highlighter is not None:
                    # Removing old highlighter
                    self.highlighter.setParent(None)
                    self.highlighter.setDocument(None)
                self.highlighter = self.highlighter_class(self.document(),
                                                self.font(), self.color_scheme)
                self._apply_highlighter_color_scheme()
            else:
                # Highlighter object has already been created:
                # updating highlighter settings (font and color scheme)
                self.highlighter.setup_formats(self.font())
                if color_scheme is not None:
                    self.set_color_scheme(color_scheme)
                else:
                    self.highlighter.rehighlight()

    def set_font(self, font, color_scheme=None):
        """Set shell font"""
        # Note: why using this method to set color scheme instead of
        #       'set_color_scheme'? To avoid rehighlighting the document twice
        #       at startup.
        if color_scheme is not None:
            self.color_scheme = color_scheme
        self.setFont(font)
        self.update_linenumberarea_width(0)
        self.apply_highlighter_settings(color_scheme)

    def set_color_scheme(self, color_scheme):
        self.color_scheme = color_scheme
        if self.highlighter is not None:
            self.highlighter.set_color_scheme(color_scheme)
        self._apply_highlighter_color_scheme()

    def set_text(self, text):
        """Set the text of the editor"""
        self.setPlainText(text)
        self.set_eol_chars(text)
#        if self.supported_language:
#            self.highlighter.rehighlight()

    def set_text_from_file(self, filename, language=None):
        """Set the text of the editor from file *fname*"""
        text, _enc = encoding.read(filename)
        if language is None:
            language = get_file_language(filename, text)
        self.set_language(language)
        self.set_text(text)

    def append(self, text):
        """Append text to the end of the text widget"""
        cursor = self.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(text)

    def paste(self):
        """
        Reimplement QPlainTextEdit's method to fix the following issue:
        on Windows, pasted text has only 'LF' EOL chars even if the original
        text has 'CRLF' EOL chars
        """
        clipboard = QApplication.clipboard()
        text = unicode(clipboard.text())
        if len(text.splitlines()) > 1:
            eol_chars = self.get_line_separator()
            clipboard.setText( eol_chars.join((text+eol_chars).splitlines()) )
        # Standard paste
        TextEditBaseWidget.paste(self)

    def get_block_data(self, block):
        return self.highlighter.block_data.get(block)

    def get_fold_level(self, block_nb):
        """Is it a fold header line?
        If so, return fold level
        If not, return None"""
        block = self.document().findBlockByNumber(block_nb)
        return self.get_block_data(block).fold_level

#===============================================================================
#    High-level editor features
#===============================================================================
    def _center_cursor(self):
        """QPlainTextEdit's "centerCursor" requires the widget to be visible"""
        self.centerCursor()
        self.disconnect(self, SIGNAL("focus_in()"), self._center_cursor)

    def go_to_line(self, line, word=''):
        """Go to line number *line* and eventually highlight it"""
        block = self.document().findBlockByNumber(line-1)
        cursor = self.textCursor()
        cursor.setPosition(block.position())
        self.setTextCursor(cursor)
        if self.isVisible():
            self.centerCursor()
        else:
            self.connect(self, SIGNAL("focus_in()"), self._center_cursor)
        self.horizontalScrollBar().setValue(0)
        if word and unicode(word) in unicode(block.text()):
            self.find(word, QTextDocument.FindCaseSensitively)

    def cleanup_code_analysis(self):
        """Remove all code analysis markers"""
        self.clear_extra_selections('code_analysis')
        for data in self.blockuserdata_list[:]:
            data.code_analysis = []
            if data.is_empty():
                del data
        # When the new code analysis results are empty, it is necessary
        # to update manually the scrollflag area (otherwise, the old flags
        # will still be displayed):
        self.scrollflagarea.update()

    def process_code_analysis(self, check_results):
        """Analyze filename code with pyflakes"""
        self.cleanup_code_analysis()
        if check_results is None:
            # Not able to compile module
            return
        cursor = self.textCursor()
        document = self.document()
        flags = QTextDocument.FindCaseSensitively|QTextDocument.FindWholeWords
        for message, line_number, error in check_results:
            # Note: line_number start from 1 (not 0)
            block = self.document().findBlockByNumber(line_number-1)
            data = block.userData()
            if not data:
                data = BlockUserData(self)
            data.code_analysis.append( (message, error) )
            block.setUserData(data)
            refs = re.findall(r"\'[a-zA-Z0-9_]*\'", message)
            for ref in refs:
                # Highlighting found references
                text = ref[1:-1]
                # Scanning line number *line* and following lines if continued
                def is_line_splitted(line_no):
                    text = unicode(document.findBlockByNumber(line_no).text())
                    stripped = text.strip()
                    return stripped.endswith('\\') or stripped.endswith(',') \
                           or len(stripped) == 0
                line2 = line_number-1
                while line2 < self.blockCount()-1 and is_line_splitted(line2):
                    line2 += 1
                cursor.setPosition(block.position())
                cursor.movePosition(QTextCursor.StartOfBlock)
                regexp = QRegExp(r"\b%s\b" % QRegExp.escape(text),
                                 Qt.CaseSensitive)
                cursor = document.find(regexp, cursor, flags)
                color = self.error_color if error else self.warning_color
                self.__highlight_selection('code_analysis', cursor,
                                           underline_color=QColor(color))
#                old_pos = None
#                if cursor:
#                    while cursor.blockNumber() <= line2 and cursor.position() != old_pos:
#                        self.__highlight_selection('code_analysis', cursor,
#                                       underline_color=self.warning_color)
#                        cursor = document.find(text, cursor, flags)
        self.update_extra_selections()

    def __show_code_analysis_results(self, line_number, code_analysis):
        """Show warning/error messages"""
        msglist = [ msg for msg, _error in code_analysis ]
        self.show_calltip(_("Code analysis"), msglist,
                          color='#129625', at_line=line_number)

    def go_to_next_warning(self):
        """Go to next code analysis warning message
        and return new cursor position"""
        block = self.textCursor().block()
        line_count = self.document().blockCount()
        while True:
            if block.blockNumber()+1 < line_count:
                block = block.next()
            else:
                block = self.document().firstBlock()
            data = block.userData()
            if data and data.code_analysis:
                break
        line_number = block.blockNumber()+1
        self.go_to_line(line_number)
        self.__show_code_analysis_results(line_number, data.code_analysis)
        return self.get_position('cursor')

    def go_to_previous_warning(self):
        """Go to previous code analysis warning message
        and return new cursor position"""
        block = self.textCursor().block()
        while True:
            if block.blockNumber() > 0:
                block = block.previous()
            else:
                block = self.document().lastBlock()
            data = block.userData()
            if data and data.code_analysis:
                break
        line_number = block.blockNumber()+1
        self.go_to_line(line_number)
        self.__show_code_analysis_results(line_number, data.code_analysis)
        return self.get_position('cursor')

    #------Tasks management
    def go_to_next_todo(self):
        """Go to next todo and return new cursor position"""
        block = self.textCursor().block()
        line_count = self.document().blockCount()
        while True:
            if block.blockNumber()+1 < line_count:
                block = block.next()
            else:
                block = self.document().firstBlock()
            data = block.userData()
            if data and data.todo:
                break
        line_number = block.blockNumber()+1
        self.go_to_line(line_number)
        self.show_calltip(_("To do"), data.todo,
                          color='#3096FC', at_line=line_number)
        return self.get_position('cursor')

    def process_todo(self, todo_results):
        """Process todo finder results"""
        for data in self.blockuserdata_list[:]:
            data.todo = ''
            if data.is_empty():
                del data
        for message, line_number in todo_results:
            block = self.document().findBlockByNumber(line_number-1)
            data = block.userData()
            if not data:
                data = BlockUserData(self)
            data.todo = message
            block.setUserData(data)
        self.scrollflagarea.update()

    #------Comments/Indentation
    def add_prefix(self, prefix):
        """Add prefix to current line or selected line(s)"""
        cursor = self.textCursor()
        if self.has_selected_text():
            # Add prefix to selected line(s)
            start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd()

            # Let's see if selection begins at a block start
            first_pos = min([start_pos, end_pos])
            first_cursor = self.textCursor()
            first_cursor.setPosition(first_pos)
            begins_at_block_start = first_cursor.atBlockStart()

            cursor.beginEditBlock()
            cursor.setPosition(end_pos)
            # Check if end_pos is at the start of a block: if so, starting
            # changes from the previous block
            if cursor.atBlockStart():
                cursor.movePosition(QTextCursor.PreviousBlock)
                if cursor.position() < start_pos:
                    cursor.setPosition(start_pos)

            while cursor.position() >= start_pos:
                cursor.movePosition(QTextCursor.StartOfBlock)
                cursor.insertText(prefix)
                if start_pos == 0 and cursor.blockNumber() == 0:
                    # Avoid infinite loop when indenting the very first line
                    break
                cursor.movePosition(QTextCursor.PreviousBlock)
                cursor.movePosition(QTextCursor.EndOfBlock)
            cursor.endEditBlock()
            if begins_at_block_start:
                # Extending selection to prefix:
                cursor = self.textCursor()
                start_pos = cursor.selectionStart()
                end_pos = cursor.selectionEnd()
                if start_pos < end_pos:
                    start_pos -= len(prefix)
                else:
                    end_pos -= len(prefix)
                cursor.setPosition(start_pos, QTextCursor.MoveAnchor)
                cursor.setPosition(end_pos, QTextCursor.KeepAnchor)
                self.setTextCursor(cursor)
        else:
            # Add prefix to current line
            cursor.beginEditBlock()
            cursor.movePosition(QTextCursor.StartOfBlock)
            cursor.insertText(prefix)
            cursor.endEditBlock()

    def __is_cursor_at_start_of_block(self, cursor):
        cursor.movePosition(QTextCursor.StartOfBlock)

    def remove_suffix(self, suffix):
        """
        Remove suffix from current line (there should not be any selection)
        """
        cursor = self.textCursor()
        cursor.setPosition(cursor.position()-len(suffix),
                           QTextCursor.KeepAnchor)
        if unicode(cursor.selectedText()) == suffix:
            cursor.removeSelectedText()

    def remove_prefix(self, prefix):
        """Remove prefix from current line or selected line(s)"""
        cursor = self.textCursor()
        if self.has_selected_text():
            # Remove prefix from selected line(s)
            start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd()
            cursor.beginEditBlock()
            cursor.setPosition(end_pos)
            # Check if end_pos is at the start of a block: if so, starting
            # changes from the previous block
            if cursor.atBlockStart():
                cursor.movePosition(QTextCursor.PreviousBlock)
                if cursor.position() < start_pos:
                    cursor.setPosition(start_pos)

            old_pos = None
            while cursor.position() >= start_pos:
                new_pos = cursor.position()
                if old_pos == new_pos:
                    break
                else:
                    old_pos = new_pos
                cursor.movePosition(QTextCursor.StartOfBlock)
                if len(unicode(cursor.block().text())) >= len(prefix):
                    cursor.setPosition(cursor.position()+len(prefix),
                                       QTextCursor.KeepAnchor)
                    if unicode(cursor.selectedText()) == prefix:
                        cursor.removeSelectedText()
                cursor.movePosition(QTextCursor.PreviousBlock)
                cursor.movePosition(QTextCursor.EndOfBlock)
            cursor.endEditBlock()
        else:
            # Remove prefix from current line
            cursor.movePosition(QTextCursor.StartOfBlock)
            cursor.setPosition(cursor.position()+len(prefix),
                               QTextCursor.KeepAnchor)
            if unicode(cursor.selectedText()) == prefix:
                cursor.removeSelectedText()

    def fix_indent(self, forward=True):
        """
        Fix indentation (Python only, no text selection)
        forward=True: fix indent only if text is not enough indented
                      (otherwise force indent)
        forward=False: fix indent only if text is too much indented
                       (otherwise force unindent)

        Returns True if indent needed to be fixed
        """
        if not self.is_python() and not self.is_cython():
            return
        cursor = self.textCursor()
        block_nb = cursor.blockNumber()
        cursor.movePosition(QTextCursor.PreviousBlock)
        prevtext = unicode(cursor.block().text()).rstrip()
        indent = self.get_indentation(block_nb)
        correct_indent = self.get_indentation(block_nb-1)
        if prevtext.endswith(':'):
            # Indent
            correct_indent += 4
        elif prevtext.endswith('continue') or prevtext.endswith('break') \
             or prevtext.endswith('pass'):
            # Unindent
            correct_indent -= 4
        elif prevtext.endswith(',') \
             and len(re.split(r'\(|\{|\[', prevtext)) > 1:
            rlmap = {")":"(", "]":"[", "}":"{"}
            for par in rlmap:
                i_right = prevtext.rfind(par)
                if i_right != -1:
                    prevtext = prevtext[:i_right]
                    for _i in range(len(prevtext.split(par))):
                        i_left = prevtext.rfind(rlmap[par])
                        if i_left != -1:
                            prevtext = prevtext[:i_left]
                        else:
                            break
            else:
                prevexpr = re.split(r'\(|\{|\[', prevtext)[-1]
                correct_indent = len(prevtext)-len(prevexpr)

        if (forward and indent >= correct_indent) or \
           (not forward and indent <= correct_indent):
            # No indentation fix is necessary
            return False

        if correct_indent >= 0:
            cursor = self.textCursor()
            cursor.beginEditBlock()
            cursor.movePosition(QTextCursor.StartOfBlock)
            cursor.setPosition(cursor.position()+indent, QTextCursor.KeepAnchor)
            cursor.removeSelectedText()
            cursor.insertText(" "*correct_indent)
            cursor.endEditBlock()
            return True

    def indent(self):
        """Indent current line or selection"""
        leading_text = self.get_text('sol', 'cursor')
        if self.has_selected_text():
            self.add_prefix(" "*4)
        elif not leading_text.strip() or (self.tab_indents and self.tab_mode):
            if self.is_python() or self.is_cython():
                if not self.fix_indent(forward=True):
                    self.add_prefix(" "*4)
            else:
                self.add_prefix(" "*4)
        else:
            self.insert_text(" "*(4-(len(leading_text) % 4)))

    def indent_or_replace(self):
        """Indent or replace by 4 spaces depending on selection and tab mode"""
        if (self.tab_indents and self.tab_mode) or not self.has_selected_text():
            self.indent()
        else:
            cursor = self.textCursor()
            if self.get_selected_text() == unicode(cursor.block().text()):
                self.indent()
            else:
                cursor1 = self.textCursor()
                cursor1.setPosition(cursor.selectionStart())
                cursor2 = self.textCursor()
                cursor2.setPosition(cursor.selectionEnd())
                if cursor1.blockNumber() != cursor2.blockNumber():
                    self.indent()
                else:
                    self.replace(" "*4)

    def unindent(self):
        """Unindent current line or selection"""
        if self.has_selected_text():
            self.remove_prefix(" "*4)
        else:
            leading_text = self.get_text('sol', 'cursor')
            if not leading_text.strip() or (self.tab_indents and self.tab_mode):
                if self.is_python() or self.is_cython():
                    if not self.fix_indent(forward=False):
                        self.remove_prefix(" "*4)
                elif leading_text.endswith('\t'):
                    self.remove_prefix('\t')
                else:
                    self.remove_prefix(" "*4)

    def comment(self):
        """Comment current line or selection"""
        self.add_prefix(self.comment_string)

    def uncomment(self):
        """Uncomment current line or selection"""
        self.remove_prefix(self.comment_string)

    def blockcomment(self):
        """Block comment current line or selection"""
        comline = self.comment_string + '='*(80-len(self.comment_string)) \
                  + self.get_line_separator()
        cursor = self.textCursor()
        if self.has_selected_text():
            self.extend_selection_to_complete_lines()
            start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd()
            cursor.setPosition(start_pos)
        else:
            start_pos = end_pos = cursor.position()
        cursor.beginEditBlock()
        cursor.setPosition(start_pos)
        cursor.movePosition(QTextCursor.StartOfBlock)
        while cursor.position() <= end_pos:
            cursor.insertText("# ")
            cursor.movePosition(QTextCursor.EndOfBlock)
            if cursor.atEnd():
                break
            cursor.movePosition(QTextCursor.NextBlock)
        cursor.setPosition(end_pos)
        cursor.movePosition(QTextCursor.EndOfBlock)
        if cursor.atEnd():
            cursor.insertText(self.get_line_separator())
        else:
            cursor.movePosition(QTextCursor.NextBlock)
        cursor.insertText(comline)
        cursor.setPosition(start_pos)
        cursor.movePosition(QTextCursor.StartOfBlock)
        cursor.insertText(comline)
        cursor.endEditBlock()

    def __is_comment_bar(self, cursor):
        return cursor.block().text().startsWith('#' + '='*79)

    def unblockcomment(self):
        """Un-block comment current line or selection"""
        def __is_comment_bar(cursor):
            return cursor.block().text().startsWith('#' + '='*79)
        # Finding first comment bar
        cursor1 = self.textCursor()
        if __is_comment_bar(cursor1):
            return
        while not __is_comment_bar(cursor1):
            cursor1.movePosition(QTextCursor.PreviousBlock)
            if cursor1.atStart():
                break
        if not __is_comment_bar(cursor1):
            return
        def __in_block_comment(cursor):
            return cursor.block().text().startsWith('#')
        # Finding second comment bar
        cursor2 = QTextCursor(cursor1)
        cursor2.movePosition(QTextCursor.NextBlock)
        while not __is_comment_bar(cursor2) and __in_block_comment(cursor2):
            cursor2.movePosition(QTextCursor.NextBlock)
            if cursor2.block() == self.document().lastBlock():
                break
        if not __is_comment_bar(cursor2):
            return
        # Removing block comment
        cursor3 = self.textCursor()
        cursor3.beginEditBlock()
        cursor3.setPosition(cursor1.position())
        cursor3.movePosition(QTextCursor.NextBlock)
        while cursor3.position() < cursor2.position():
            cursor3.setPosition(cursor3.position()+2, QTextCursor.KeepAnchor)
            cursor3.removeSelectedText()
            cursor3.movePosition(QTextCursor.NextBlock)
        for cursor in (cursor2, cursor1):
            cursor3.setPosition(cursor.position())
            cursor3.select(QTextCursor.BlockUnderCursor)
            cursor3.removeSelectedText()
        cursor3.endEditBlock()

#===============================================================================
#    Qt Event handlers
#===============================================================================
    def setup_context_menu(self):
        """Setup context menu"""
        self.undo_action = create_action(self, _("Undo"),
                           shortcut=keybinding('Undo'),
                           icon=get_icon('undo.png'), triggered=self.undo)
        self.redo_action = create_action(self, _("Redo"),
                           shortcut=keybinding('Redo'),
                           icon=get_icon('redo.png'), triggered=self.redo)
        self.cut_action = create_action(self, _("Cut"),
                           shortcut=keybinding('Cut'),
                           icon=get_icon('editcut.png'), triggered=self.cut)
        self.copy_action = create_action(self, _("Copy"),
                           shortcut=keybinding('Copy'),
                           icon=get_icon('editcopy.png'), triggered=self.copy)
        paste_action = create_action(self, _("Paste"),
                           shortcut=keybinding('Paste'),
                           icon=get_icon('editpaste.png'), triggered=self.paste)
        self.delete_action = create_action(self, _("Delete"),
                           shortcut=keybinding('Delete'),
                           icon=get_icon('editdelete.png'),
                           triggered=self.delete)
        selectall_action = create_action(self, _("Select All"),
                           shortcut=keybinding('SelectAll'),
                           icon=get_icon('selectall.png'),
                           triggered=self.selectAll)
        comment_action = create_action(self, _("Comment"),
                           icon=get_icon("comment.png"),
                           triggered=self.comment)
        uncomment_action = create_action(self, _("Uncomment"),
                           icon=get_icon("uncomment.png"),
                           triggered=self.uncomment)
        self.menu = QMenu(self)
        add_actions(self.menu, (self.undo_action, self.redo_action, None,
                                self.cut_action, self.copy_action,
                                paste_action, self.delete_action,
                                None, selectall_action, None,
                                comment_action, uncomment_action))
        # Read-only context-menu
        self.readonly_menu = QMenu(self)
        add_actions(self.readonly_menu,
                    (self.copy_action, None, selectall_action))

    def keyPressEvent(self, event):
        """Reimplement Qt method"""
        key = event.key()
        ctrl = event.modifiers() & Qt.ControlModifier
        shift = event.modifiers() & Qt.ShiftModifier
        text = unicode(event.text())
        if text:
            self.__clear_occurences()
        if QToolTip.isVisible():
            self.hide_tooltip_if_necessary(key)
        if key in (Qt.Key_Enter, Qt.Key_Return):
            if not shift and not ctrl:
                if self.is_completion_widget_visible() \
                   and self.codecompletion_enter:
                    self.select_completion_list()
                else:
                    QPlainTextEdit.keyPressEvent(self, event)
                    self.fix_indent()
            elif shift:
                # Ignoring QPlainTextEdit default Shift+Enter keybinding
                # which will print a new line in the same block:
                event = QKeyEvent(event.type(), event.key(), Qt.NoModifier,
                                  event.text(), event.isAutoRepeat(),
                                  event.count())
                QPlainTextEdit.keyPressEvent(self, event)
                return
        elif key == Qt.Key_Insert and not shift and not ctrl:
            self.setOverwriteMode(not self.overwriteMode())
        elif key == Qt.Key_Backspace and not shift and not ctrl:
            leading_text = self.get_text('sol', 'cursor')
            leading_length = len(leading_text)
            trailing_spaces = leading_length-len(leading_text.rstrip())
            if self.has_selected_text() or not self.intelligent_backspace:
                QPlainTextEdit.keyPressEvent(self, event)
            else:
                trailing_text = self.get_text('cursor', 'eol')
                if leading_length > 4 and not leading_text.strip():
                    if leading_length % 4 == 0:
                        self.unindent()
                    else:
                        QPlainTextEdit.keyPressEvent(self, event)
                elif trailing_spaces and not trailing_text.strip():
                    self.remove_suffix(" "*trailing_spaces)
                elif leading_text and trailing_text and \
                     leading_text[-1]+trailing_text[0] in ('()', '[]', '{}'):
                    cursor = self.textCursor()
                    cursor.movePosition(QTextCursor.PreviousCharacter)
                    cursor.movePosition(QTextCursor.NextCharacter,
                                        QTextCursor.KeepAnchor, 2)
                    cursor.removeSelectedText()
                else:
                    QPlainTextEdit.keyPressEvent(self, event)
                    if self.is_completion_widget_visible():
                        self.completion_text = self.completion_text[:-1]
        elif key == Qt.Key_Period:
            self.insert_text(text)
            if (self.is_python() or self.is_cython()) and \
               self.codecompletion_auto:
                # Enable auto-completion only if last token isn't a float
                last_obj = getobj(self.get_text('sol', 'cursor'))
                if last_obj and not last_obj.isdigit():
                    self.emit(SIGNAL('trigger_code_completion(bool)'), True)
        elif key == Qt.Key_Home and not ctrl:
            self.stdkey_home(shift)
            event.accept()
        elif text == '(' and not self.has_selected_text():
            self.hide_completion_widget()
            position = self.get_position('cursor')
            s_trailing_text = self.get_text('cursor', 'eol').strip()
            if self.close_parentheses_enabled and \
               (len(s_trailing_text) == 0 or \
                s_trailing_text[0] in (',', ')', ']', '}')):
                self.insert_text('()')
                cursor = self.textCursor()
                cursor.movePosition(QTextCursor.PreviousCharacter)
                self.setTextCursor(cursor)
            else:
                self.insert_text(text)
            if (self.is_python() or self.is_cython()) and \
               self.get_text('sol', 'cursor') and self.calltips:
                self.emit(SIGNAL('trigger_calltip(int)'), position)
            event.accept()
        elif text in ('[', '{') and not self.has_selected_text() \
             and self.close_parentheses_enabled:
            s_trailing_text = self.get_text('cursor', 'eol').strip()
            if len(s_trailing_text) == 0 or \
               s_trailing_text[0] in (',', ')', ']', '}'):
                self.insert_text({'{': '{}', '[': '[]'}[text])
                cursor = self.textCursor()
                cursor.movePosition(QTextCursor.PreviousCharacter)
                self.setTextCursor(cursor)
                event.accept()
            else:
                QPlainTextEdit.keyPressEvent(self, event)
        elif key in (Qt.Key_ParenRight, Qt.Key_BraceRight, Qt.Key_BracketRight)\
             and not self.has_selected_text() and self.close_parentheses_enabled \
             and not self.textCursor().atBlockEnd():
            cursor = self.textCursor()
            cursor.movePosition(QTextCursor.NextCharacter,
                                QTextCursor.KeepAnchor)
            text = unicode(cursor.selectedText())
            if text == {Qt.Key_ParenRight: ')', Qt.Key_BraceRight: '}',
                        Qt.Key_BracketRight: ']'}[key]:
                cursor.clearSelection()
                self.setTextCursor(cursor)
                event.accept()
            else:
                QPlainTextEdit.keyPressEvent(self, event)
        elif key == Qt.Key_Colon and not self.has_selected_text() \
             and self.auto_unindent_enabled:
            leading_text = self.get_text('sol', 'cursor')
            if leading_text.lstrip() in ('else', 'finally'):
                ind = lambda txt: len(txt)-len(txt.lstrip())
                prevtxt = unicode(self.textCursor().block().previous().text())
                if ind(leading_text) == ind(prevtxt):
                    self.unindent()
            QPlainTextEdit.keyPressEvent(self, event)
        elif key == Qt.Key_Space and not shift and not ctrl \
             and not self.has_selected_text() and self.auto_unindent_enabled:
            leading_text = self.get_text('sol', 'cursor')
            if leading_text.lstrip() in ('elif', 'except'):
                ind = lambda txt: len(txt)-len(txt.lstrip())
                prevtxt = unicode(self.textCursor().block().previous().text())
                if ind(leading_text) == ind(prevtxt):
                    self.unindent()
            QPlainTextEdit.keyPressEvent(self, event)
        elif key == Qt.Key_Tab:
            # Important note: <TAB> can't be called with a QShortcut because
            # of its singular role with respect to widget focus management
            self.indent_or_replace()
        elif key == Qt.Key_Backtab:
            # Backtab, i.e. Shift+<TAB>, could be treated as a QShortcut but
            # there is no point since <TAB> can't (see above)
            self.unindent()
        else:
            QPlainTextEdit.keyPressEvent(self, event)
            if self.is_completion_widget_visible() and text:
                self.completion_text += text

    def mouseMoveEvent(self, event):
        """Underline words when pressing <CONTROL>"""
        if self.has_selected_text():
            QPlainTextEdit.mouseMoveEvent(self, event)
            return
        if self.go_to_definition_enabled and \
           event.modifiers() & Qt.ControlModifier:
            text = self.get_word_at(event.pos())
            if text and (self.is_python() or self.is_cython()) \
               and not is_keyword(unicode(text)):
                if not self.__cursor_changed:
                    QApplication.setOverrideCursor(
                                                QCursor(Qt.PointingHandCursor))
                    self.__cursor_changed = True
                cursor = self.cursorForPosition(event.pos())
                cursor.select(QTextCursor.WordUnderCursor)
                self.clear_extra_selections('ctrl_click')
                self.__highlight_selection('ctrl_click', cursor, update=True,
                                foreground_color=self.ctrl_click_color,
                                underline_color=self.ctrl_click_color,
                                underline_style=QTextCharFormat.SingleUnderline)
                event.accept()
                return
        if self.__cursor_changed:
            QApplication.restoreOverrideCursor()
            self.__cursor_changed = False
            self.clear_extra_selections('ctrl_click')
        QPlainTextEdit.mouseMoveEvent(self, event)

    def leaveEvent(self, event):
        """If cursor has not been restored yet, do it now"""
        if self.__cursor_changed:
            QApplication.restoreOverrideCursor()
            self.__cursor_changed = False
            self.clear_extra_selections('ctrl_click')
        QPlainTextEdit.leaveEvent(self, event)

    def mousePressEvent(self, event):
        """Reimplement Qt method"""
        if os.name != 'posix' and event.button() == Qt.MidButton:
            self.setFocus()
            event = QMouseEvent(QEvent.MouseButtonPress, event.pos(),
                                Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
            QPlainTextEdit.mousePressEvent(self, event)
            QPlainTextEdit.mouseReleaseEvent(self, event)
            self.paste()
        elif event.button() == Qt.LeftButton \
             and (event.modifiers() & Qt.ControlModifier):
            cursor = self.cursorForPosition(event.pos())
            position = cursor.position()
            cursor.select(QTextCursor.WordUnderCursor)
            text = unicode(cursor.selectedText())
            QPlainTextEdit.mousePressEvent(self, event)
            if self.go_to_definition_enabled and text is not None and \
               (self.is_python() or self.is_cython()) and not is_keyword(text):
                self.emit(SIGNAL("go_to_definition(int)"), position)
        else:
            QPlainTextEdit.mousePressEvent(self, event)

    def contextMenuEvent(self, event):
        """Reimplement Qt method"""
        state = self.has_selected_text()
        self.copy_action.setEnabled(state)
        self.cut_action.setEnabled(state)
        self.delete_action.setEnabled(state)
        self.undo_action.setEnabled( self.document().isUndoAvailable() )
        self.redo_action.setEnabled( self.document().isRedoAvailable() )
        menu = self.menu
        if self.isReadOnly():
            menu = self.readonly_menu
        menu.popup(event.globalPos())
        event.accept()

    #------ Drag and drop
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        if mimedata2url(event.mimeData()):
            # Let the parent widget handle this
            event.ignore()
        else:
            TextEditBaseWidget.dragEnterEvent(self, event)

    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        if mimedata2url(event.mimeData()):
            # Let the parent widget handle this
            event.ignore()
        else:
            TextEditBaseWidget.dropEvent(self, event)

#===============================================================================
# CodeEditor's Printer
#===============================================================================

#TODO: Implement the header and footer support
class Printer(QPrinter):
    def __init__(self, mode=QPrinter.ScreenResolution, header_font=None):
        QPrinter.__init__(self, mode)
        self.setColorMode(QPrinter.Color)
        self.setPageOrder(QPrinter.FirstPageFirst)
        self.date = time.ctime()
        if header_font is not None:
            self.header_font = header_font

    # <!> The following method is simply ignored by QPlainTextEdit
    #     (this is a copy from QsciEditor's Printer)
    def formatPage(self, painter, drawing, area, pagenr):
        header = '%s - %s - Page %s' % (self.docName(), self.date, pagenr)
        painter.save()
        painter.setFont(self.header_font)
        painter.setPen(QColor(Qt.black))
        if drawing:
            painter.drawText(area.right()-painter.fontMetrics().width(header),
                             area.top()+painter.fontMetrics().ascent(), header)
        area.setTop(area.top()+painter.fontMetrics().height()+5)
        painter.restore()

#===============================================================================
# Editor + Class browser test
#===============================================================================
class TestWidget(QSplitter):
    def __init__(self, parent):
        QSplitter.__init__(self, parent)
        self.editor = CodeEditor(self)
        self.editor.setup_editor(linenumbers=True, code_analysis=False,
                                 todo_list=False, tab_mode=False,
                                 font=QFont("Courier New", 10),
                                 color_scheme='Pydev')
        self.addWidget(self.editor)
        self.classtree = OutlineExplorer(self)
        self.addWidget(self.classtree)
        self.connect(self.classtree, SIGNAL("edit_goto(QString,int,QString)"),
                     lambda _fn, line, word: self.editor.go_to_line(line, word))
        self.setStretchFactor(0, 4)
        self.setStretchFactor(1, 1)
        self.setWindowIcon(get_icon('spyder.svg'))

    def load(self, filename):
        self.editor.set_text_from_file(filename)
        self.setWindowTitle("%s - %s (%s)" % (_("Editor"),
                                              osp.basename(filename),
                                              osp.dirname(filename)))
        self.classtree.set_current_editor(self.editor, filename, False)

def test(fname):
    from spyderlib.utils.qthelpers import qapplication
    app = qapplication()
    win = TestWidget(None)
    win.show()
    win.load(fname)
    win.resize(800, 800)

    analysis_results = check(fname)
    win.editor.process_code_analysis(analysis_results)

    sys.exit(app.exec_())

if __name__ == '__main__':
    if len(sys.argv) > 1:
        fname = sys.argv[1]
    else:
        fname = __file__
#        fname = r"d:\Python\scintilla\src\LexCPP.cxx"
#        fname = r"C:\Python26\Lib\pdb.py"
#        fname = r"C:\Python26\Lib\ssl.py"
#        fname = r"D:\Python\testouille.py"
#        fname = r"C:\Python26\Lib\pydoc.py"
    test(fname)