text:
puts "What is your first name?"
first_name = gets.chomp
puts "What is your middle name?"
middle_name = gets.chomp
puts "What is your last name?"
last_name = gets.chomp

puts "That's a long name, " + first_name.capitalize + " " + middle_name.capitalize + " " + last_name.capitalize + "! It's nice to meet you."

puts "What is your favorite guitar amplifier volume, " + first_name.capitalize + "?"
fav_num = gets.chomp
fav_num = fav_num.to_i + 1
puts "That's a great number, " + first_name.capitalize + ", but wouldn't " + fav_num.to_s + " be even better? It's one louder."

# LINK TO OTHER EXERCISES:
# [Address formatting](/address/my_solution.rb)
# Address excercise on github if relative path does not work:
# https://github.com/jasonallenphotography/phase-0/blob/master/week-4/address/my_solution.rb

# [Math](/math/my_solution.rb)
# Math excercise on github if relative path does not work:
# https://github.com/jasonallenphotography/phase-0/blob/master/week-4/math/my_solution.rb

# REFLECTION
# How do you define a local variable?
# A local variable defines a piece of data within a method or other local scope.

# How do you define a method?
# A method is a defined function that can take parameters, and establishes a local scope. It can perform a task using local variables.

# What is the difference between a local variable and a method?
#  A method performs a task or calculation, and may contain local vairables.

# How do you run a ruby program from the command line?
# `$ ruby PROGRAM_NAME.rb` runs a ruby program.

# How do you run an RSpec file from the command line?
# `$ rspec SPECIFICATION_FILE.rb` runs an RSpec in the current directory.

# What was confusing about this material? What made sense?
# The only confusion I had was that the `rspec` in one of the exercises called for `return` instead of `puts`, and was throwing errors at me even though the output was identical to what it expected! I solved this issue by reading the `rspec`, but otherwise I had no confusion in this exercise.


text:
package org.hisp.dhis.outboundmessage;

import java.util.List;

import org.hisp.dhis.common.DeliveryChannel;

/**
 * @author gZubair <rajazubair.asghar@gmail.com>
 */

public class OutboundMessageBatch
{
    private final List<OutboundMessage> messages;

    private final DeliveryChannel deliveryChannel;

    public OutboundMessageBatch( List<OutboundMessage> messages, DeliveryChannel deliveryChannel )
    {
        this.messages = messages;
        this.deliveryChannel = deliveryChannel;
    }

    public List<OutboundMessage> getMessages()
    {
        return messages;
    }

    public DeliveryChannel getDeliveryChannel()
    {
        return deliveryChannel;
    }
}

redpajama_set_name: RedPajamaGithub


text:
import unittest
import budget.util.addset as a

class TestAddSet(unittest.TestCase):
    def key_exists(self):
        foo = { 'foo' : 0 }
        foo = a.addset(foo, 'foo', 1)
        self.assertEqual(foo['foo'], 1)

    def key_absent(self):
        foo = {}
        foo = a.addset(foo, 'foo', 1)
        self.assertEqual(foo['foo'], 1)

    def runTest(self):
        self.key_exists()
        self.key_absent()


text:
* ***BACKWARD INCOMPATIBLE CHANGES***
 * Update to the FNV_1a hash function **NOTE:** Breaks backwards compatibility with
 previously exported blooms using the default hash!

### Version 1.8.2
* Added CPP Guards
* Ensured that on-disk blooms correctly updated `elements_added`

### Version 1.8.1
* Improved the speed of calculating the number of bits set: [more info](https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable)
* cppcheck code analysis and minor improvements

### Version 1.8.0
* Removed python version - see [pyprobables](https://github.com/barrust/pyprobables)

### Version 1.7.14
* Micro optimizations

## Version 1.7.12 and 1.7.13
* Python implementation changes and improvements

### Version 1.7.11
* Remove big endian check since exported blooms are identical on these systems
* Set critical section to a named critical section
* Fix divide by zero issue for Jaccard Index on empty Bloom Filters
* In-line wrapper functions where possible

### Version 1.7.10
* Unique HashFunction type when using with related libraries

### Version 1.7.9
* Remove `#define` construct to allow functions to better be used directly in
if-statements

### Version 1.7.8
* Pad bloom array with an additional null char

### Version 1.7.7
* Added calculating union and intersection of Bloom filters
* Added calculating the Jaccard Index of two Bloom filters
* Added estimating elements bases only on number bits set

### Version 1.7.6
* Changed default hash to FNV-1A **NOTE:** Breaks backwards compatibility with
previously exported blooms using the default hash!
* Removed **-lcrypto** requirement
* Restructured project
* Combined test files

### Version 1.7.5
* Moved reused logic into a function
* Added clear bloom filter
* Pre-computed calculation of log(2) for speed
* Renamed default hash - **NOTE:** this hash function may change in the future!

### Version 1.7.1
* Added easier to use functions when using the default hashing algorithm
* Minor clean up to reduce some function calls

### Version 1.6.2
* Updated to allow for hashes to be generated, added, and checked against
similar bloom filters without having to re-hash

### Version 1.6.0
* Updated to support parallel construction of a bloom filter using OpenMP
* **NOTE:** using parallel with on disk bloom filters is about the same as
single threaded
* Update does not force parallel construction only makes it possible. See
bloom_multi_thread.c for an example

### Version 1.5.1
* Fixed an issue when importing hundreds of blooms on disk

### Version 1.5.0
* Added the ability to import a bloom on disk

### Version 1.1.0
* Changed the export / import file format for future use in importing a bloom
on disk

### Version 1.0.0
* Original version with initialize, destroy, import and export


text:
import optparse
import calendar
import datetime
import Global

def main():
    # configuration of the parser for the arguments
    parser = optparse.OptionParser()
    parser.add_option('-c','--clients',dest='clients',type='str',
                      help="list of hosts to get the configs. [format: "
                      "client1,client2,...  default: 'all']" + Global.MATCH_RULE)
    parser.add_option('-s','--state',dest='state',type='str',
                      help="run the script only in clients with the given "
                      "states. [possible_values: 'on','off','dead' "
                      "default: 'on']. To select more than one state, "
                      "separate the values with comma."
    (opts, _) = parser.parse_args()

    try:
        if opts.clients == 'all' or opts.clients is None:
            clients = opts.clients
            ok, ConfigsReceived = Global.handle_request('GET_CONFIGS','all')
        else:
            clients = set(opts.clients.split(","))
            ConfigsReceived = Global.match_clients(clients)
            ok = True
        if not ok:
            raise MatchClientsErr('Could not get configs of server.')
    except Global.MatchClientsErr as err:
        print(err)
        return
  import paramiko
import cmd

class RunCommand(cmd.Cmd):
    """ Simple shell to run a command on the host """

    prompt = 'ssh > '

    def __init__(self):
        cmd.Cmd.__init__(self)
        self.hosts = []
        self.connections = []

    def do_add_host(self, args):
        """add_host
        Add the host to the host list"""
        if args:
            self.hosts.append(args.split(','))
        else:
            print "usage: host "

    def do_connect(self, args):
        """Connect to all hosts in the hosts list"""
        for host in self.hosts:
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(
                paramiko.AutoAddPolicy())
            client.connect(host[0],
                username=host[1],
                password=host[2])
            self.connections.append(client)

    def do_run(self, command):
        """run
        Execute this command on all hosts in the list"""
        if command:
            for host, conn in zip(self.hosts, self.connections):
                stdin, stdout, stderr = conn.exec_command(command)
                stdin.close()
                for line in stdout.read().splitlines():
                    print 'host: %s: %s' % (host[0], line)
        else:
            print "usage: run "

    def do_close(self, args):
        for conn in self.connections:
            conn.close()

if __name__ == '__main__':
    RunCommand().cmdloop()


text:
<?php declare(strict_types=1);

namespace SmartEmailing\v3\Request\Eshops\Model\Holder;

use SmartEmailing\v3\Models\AbstractMapHolder;
use SmartEmailing\v3\Request\Eshops\Model\Attribute;

/**
 * Class Attributes
 *
 * @package SmartEmailing\v3\Request\Eshops\Model\Holder
 */
class Attributes extends AbstractMapHolder
{
	/**
	 * Inserts attribute into the attributes.
	 *
	 * @param Attribute $list
	 *
	 * @return Attributes
	 */
	public function add(Attribute $list): Attributes
	{
		$this->insertEntry($list);
		return $this;
	}

	/**
	 * Creates Attribute entry and inserts it to the array
	 *
	 * @param $name
	 * @param $value
	 *
	 * @return Attribute
	 */
	public function create($name, $value): Attribute
	{
		$list = new Attribute($name, $value);
		$this->add($list);
		return $list;
	}
	/**
	 * Adds an entry model into items list. Only unique items are added (represented by the id property)
	 *
	 * @param $entry
	 *
	 * @return boolean if the entry was added (first time added)
	 */
	protected function insertEntry($entry): bool
	{
		// Allow only unique values
		if (isset($this->idMap[$entry->name])) {
			return false;
		}

		$this->items[] = $entry;
		$this->idMap[$entry->name] = $entry;

		return true;
	}
}


text:
package org.dmfs.rfc3986;

import org.dmfs.optional.Optional;

/**
 * An authority.
 * <p>
 * An authority has a host, optional user info and an optional port.
 *
 * @author Marten Gajda
 */
public interface Authority
{
    /**
     * Returns the {@link Optional} user info part of the authority in encoded form.
     *
     * @return The {@link UriEncoded} user info.
     */
    Optional<? extends UriEncoded> userInfo();

    /**
     * Returns the host of this authority.
     *
     * @return The {@link UriEncoded} host.
     */
    UriEncoded host();

    /**
     * The {@link Optional} port number.
     *
     * @return An {@link Optional} port number (as an {@link Integer}).
     */
    Optional<Integer> port();
}


text:
var UserRolesService = function() {
    var BASE = '../../services/sys/userRolesService';
    return {
    	findUserRoles: function(user) {
    		var userRoles = [];
    		AjaxUtil.sendGetAsyncRequest(BASE + '/getUserRoles/'+user.userId+'/0', function(resp) {
    			userRoles = resp;
        		return userRoles;
            });
    		return userRoles;
        },
        findSysRoles: function(user) {
    		var roles = [];
    		AjaxUtil.sendGetAsyncRequest(BASE + '../../../../services/sys/rolesService/findRolesList/1000/1', function(resp) {
    			roles = resp.result;
        		return roles;
            });
    		return roles;
        },
        batchInsertUserRole: function(userAccount,items) {
            AjaxUtil.sendPostData(BASE + '/saveOrUpdate', items, function(userRoles) {
            	var items = userRoles.obj;
            	for(var i=0;i<items.length;i++){
            		//插入行
            		UserRolesView.insertUserRoleRow(user.userAccount,items[i]);
            	}
            });
        },
        batchRemoveUserRoles: function(userAccount,items) {
            AjaxUtil.sendPutData(BASE + '/delete',items, function() {
            	UserRolesView.deleteUserRoleRow(userAccount,items);
            });
        }
    }
}();


text:
		Home

		<?php if(isset($module) && !empty($module)) echo $module;?>

Create new a category

			<form class="form-horizontal" method="post" action="">
			  <fieldset>

					<?php echo validation_errors(); ?>

				<?php
					if(isset($data) && !empty($data))
					{
						echo '

						  Author Name:

							<input class="span6 input-xlarge uneditable-input" name="author_name" type="text" id="author" placeholder="Author name..." value="'.$data['name'].'" />

						  Born:

							<input class="input-xlarge uneditable-input" name="author_born" type="date" id="author" max="'.date("d/m/Y").'" value="'.$data['born'].'" />

						  Field:

							<input class="input-xlarge uneditable-input" name="author_field" type="text" id="author" placeholder="Field..." value="'.$data['field'].'" />

							  Introduce:

								<textarea name="author_introduce" class="cleditor" id="textarea1" rows="3">'.$data['introduce'].'</textarea>

';
						echo '

								<input name="submit" type="submit" class="btn btn-danger" value="Delete now"/>
								<button type="reset" class="btn">Cancel</button>

';

					}
					else
					{
						echo '

									<button type="button" class="close" data-dismiss="alert">×</button>
									Warning! No data.

';
						echo 'Cancel';
					}
				?>

			  </fieldset>
			</form>


text:
license: >
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
---

# Иконки и заставки

В этом разделе показано, как настроить иконку приложения и дополнительные заставки для различных платформ, как при работе с Cordova CLI (описано в разделе Интерфейс Командной Строки) так и с помощью средств SDK специфических для платформы (подробнее в разделе Руководстве по поддерживаемым платформам).

## Настройка иконок в CLI

При работе в CLI можно определить icon(s) app через элемент `<icon>` (`config.xml`). Если значок не задан, то используется логотип Apache Cordova.

        <icon src="res/ios/icon.png" platform="ios" width="57" height="57" density="mdpi" />

src: (обязательный) указывает расположение файла с изображением, относительно каталога проекта

platform: (необязательно) Целевая платформа

width: (необязательно) ширина иконки в пикселях

height: (необязательно) высота иконки в пикселях

density: (опционально) Только для android, указывает плотность пикселей для иконки

Следующая конфигурация может использоваться для определения одной иконки по умолчанию, которая будет использоваться для всех платформ.

        <icon src="res/icon.png" />

Для каждой платформы можно также определить подобранные с точностью до пикселя значки, заданные для различных разрешений экрана.

Amazon Fire OS

         <platform name="amazon-fireos">
                  <icon src="res/android/ldpi.png" density="ldpi" />
                  <icon src="res/android/mdpi.png" density="mdpi" />
                  <icon src="res/android/hdpi.png" density="hdpi" />
                  <icon src="res/android/xhdpi.png" density="xhdpi" />
         </platform>

Android

         <platform name="android">
                  <icon src="res/android/ldpi.png" density="ldpi" />
                  <icon src="res/android/mdpi.png" density="mdpi" />
                  <icon src="res/android/hdpi.png" density="hdpi" />
                  <icon src="res/android/xhdpi.png" density="xhdpi" />
         </platform>

BlackBerry10

         <platform name="blackberry10">
                  <icon src="res/bb10/icon-86.png" />
                  <icon src="res/bb10/icon-150.png" />
         </platform>

Смотрите документацию BlackBerry при ориентировании на набор различных размеров и языков. [http://developer.blackberry.com/html5/documentation/icon_element.html]

Firefox OS

         <platform name="firefoxos">
                  <icon src="res/ff/logo.png" width="60" height="60" />
         </platform>

iOS

         <platform name="ios">
                  <!-- iOS 8.0+ -->
                  <!-- iPhone 6 Plus  -->
                  <icon src="res/ios/icon-60@3x.png" width="180" height="180" />
                  <!-- iOS 7.0+ -->
                  <!-- iPhone / iPod Touch  -->
                  <icon src="res/ios/icon-60.png" width="60" height="60" />
                  <icon src="res/ios/icon-60@2x.png" width="120" height="120" />
                  <!-- iPad -->
                  <icon src="res/ios/icon-76.png" width="76" height="76" />
                  <icon src="res/ios/icon-76@2x.png" width="152" height="152" />
                  <!-- iOS 6.1 -->
                  <!-- Spotlight Icon -->
                  <icon src="res/ios/icon-40.png" width="40" height="40" />
                  <icon src="res/ios/icon-40@2x.png" width="80" height="80" />
                  <!-- iPhone / iPod Touch -->
                  <icon src="res/ios/icon.png" width="57" height="57" />
                  <icon src="res/ios/icon@2x.png" width="114" height="114" />
                  <!-- iPad -->
                  <icon src="res/ios/icon-72.png" width="72" height="72" />
                  <icon src="res/ios/icon-72@2x.png" width="144" height="144" />
                  <!-- iPhone Spotlight and Settings Icon -->
                  <icon src="res/ios/icon-small.png" width="29" height="29" />
                  <icon src="res/ios/icon-small@2x.png" width="58" height="58" />
                  <!-- iPad Spotlight and Settings Icon -->
                  <icon src="res/ios/icon-50.png" width="50" height="50" />
                  <icon src="res/ios/icon-50@2x.png" width="100" height="100" />
         </platform>

Tizen

         <platform name="tizen">
                  <icon src="res/tizen/icon-128.png" width="128" height="128" />
         </platform>

Windows Phone8

         <platform name="wp8">
                  <icon src="res/wp/ApplicationIcon.png" width="99" height="99" />
                  <!-- tile image -->
                  <icon src="res/wp/Background.png" width="159" height="159" />
         </platform>

Windows8

         <platform name="windows8">
                  <icon src="res/windows8/logo.png" width="150" height="150" />
                  <icon src="res/windows8/smalllogo.png" width="30" height="30" />
                  <icon src="res/windows8/storelogo.png" width="50" height="50" />
         </platform>

## Настройка заставки с помощью CLI

В файле `config.xml` верхнего уровня (не том который расположен в `platforms` ), добавьте элементы конфигурации как те, которые указаны здесь.

# Пример конфигурации

Пожалуйста, обратите внимание, что значение атрибута "src" определяется относительно каталога проекта, а не относительно каталога www. Вы можете называть файл исходного изображения как угодно. Внутреннее имя в приложении определяется Cordova.

    <platform name="android">
        <!-- you can use any density that exists in the Android project -->
        <splash src="res/screen/android/splash-land-hdpi.png" density="land-hdpi"/>
        <splash src="res/screen/android/splash-land-ldpi.png" density="land-ldpi"/>
        <splash src="res/screen/android/splash-land-mdpi.png" density="land-mdpi"/>
        <splash src="res/screen/android/splash-land-xhdpi.png" density="land-xhdpi"/>

        <splash src="res/screen/android/splash-port-hdpi.png" density="port-hdpi"/>
        <splash src="res/screen/android/splash-port-ldpi.png" density="port-ldpi"/>
        <splash src="res/screen/android/splash-port-mdpi.png" density="port-mdpi"/>
        <splash src="res/screen/android/splash-port-xhdpi.png" density="port-xhdpi"/>
    </platform>

    <platform name="ios">
        <!-- images are determined by width and height. The following are supported -->
        <splash src="res/screen/ios/Default~iphone.png" width="320" height="480"/>
        <splash src="res/screen/ios/Default@2x~iphone.png" width="640" height="960"/>
        <splash src="res/screen/ios/Default-Portrait~ipad.png" width="768" height="1024"/>
        <splash src="res/screen/ios/Default-Portrait@2x~ipad.png" width="1536" height="2048"/>
        <splash src="res/screen/ios/Default-Landscape~ipad.png" width="1024" height="768"/>
        <splash src="res/screen/ios/Default-Landscape@2x~ipad.png" width="2048" height="1536"/>
        <splash src="res/screen/ios/Default-568h@2x~iphone.png" width="640" height="1136"/>
        <splash src="res/screen/ios/Default-667h.png" width="750" height="1334"/>
        <splash src="res/screen/ios/Default-736h.png" width="1242" height="2208"/>
        <splash src="res/screen/ios/Default-Landscape-736h.png" width="2208" height="1242"/>
    </platform>

    <platform name="wp8">
        <!-- images are determined by width and height. The following are supported -->
        <splash src="res/screen/wp8/SplashScreenImage.jpg" width="768" height="1280"/>
    </platform>

    <platform name="windows8">
        <!-- images are determined by width and height. The following are supported -->
        <splash src="res/screen/windows8/splashscreen.png" width="620" height="300"/>
    </platform>

    <platform name="blackberry10">
        <!-- Add a rim:splash element for each resolution and locale you wish -->
        <!-- http://developer.blackberry.com/html5/documentation/rim_splash_element.html -->
        <rim:splash src="res/screen/windows8/splashscreen.png"/>
    </platform>

    <preference name="SplashScreenDelay" value="10000" />

# Поддерживаемые платформы

На данный момент (Cordova 3.5.0 июль 2014 года) следующие платформы поддерживают экраны-заставки.

    android
    ios
    wp8
    windows8
    blackberry10

# Плагин SplashScreen

Apache Cordova также предлагает специальный плагин для упралвения экранами-заставками, который может быть использован для программного отображения и скрытия заставки во время запуска приложения https://github.com/apache/cordova-plugin-splashscreen


text:
// Copyright (c) Aghyad khlefawi. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Coddee.Loggers;
using Coddee.Mvvm;
using Coddee.Notification;
using Coddee.Services;
using Coddee.WPF.DefaultShell;
using Coddee.WPF.Events;
using Coddee.Services.Navigation;
using Coddee.WPF;
using Coddee.WPF.Security;
using Coddee.WPF.Services.Dialogs;

namespace Coddee.AppBuilder
{
    /// <summary>
    /// Builder extensions for WPF applications.
    /// </summary>
    public static class BuilderExtensions
    {
        /// <summary>
        /// Add a console to the application
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="toggleCondition">A function that is executed on the shell KeyDown event show return true to toggle the console</param>
        /// <returns></returns>
        public static IApplicationBuilder UseApplicationConsole(this IApplicationBuilder builder,
                                                                   Func<KeyEventArgs, bool> toggleCondition)
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.AppConsoleBuildAction((container) =>
                  {
                      var shell = (ContentControl)container.Resolve<IShell>();
                      if (shell == null)
                          throw new ApplicationBuildException("The method must be called after the UseShell method");

                      var applicationConsole = container.Resolve<IApplicationConsole>();
                      var logger = (LogAggregator)container.Resolve<ILogger>();
                      applicationConsole.Initialize(shell, logger.MinimumLevel);

                      //Add the console logger to loggers collection
                      logger.AddLogger(applicationConsole.GetLogger(), LoggerTypes.ApplicationConsole);

                      //Sets the Shell KeyDown event handler to toggle the console visibility
                      applicationConsole.SetToggleCondition(toggleCondition);
                  }));
            return builder;
        }

        /// <summary>
        /// Add a console to the application
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="toggleCondition">A function that is executed on the shell KeyDown event show return true to toggle the console</param>
        /// <returns></returns>
        public static IApplicationBuilder UseCoddeeDebugTool(this IApplicationBuilder builder,
                                                                Func<KeyEventArgs, bool> toggleCondition)
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.DebugToolBuildAction(async (container) =>
                {
                    var shell = container.Resolve<IShell>();
                    if (shell == null)
                        throw new
                            ApplicationBuildException("The method must be called after the UseShell method");
                    var debugTool = container.Resolve<IDebugTool>();

                    await debugTool.Initialize();

                    debugTool.SetToggleCondition(toggleCondition);
                }));
            return builder;
        }

        /// <summary>
        /// Sets a default shell for the WPF application
        /// </summary>
        /// <typeparam name="TContent">The main content type to be shown on startup</typeparam>
        /// <returns></returns>
        public static IApplicationBuilder UseDefaultShell<TContent>(
            this IApplicationBuilder builder,
            WindowState state = WindowState.Maximized,
            Action<Window> config = null)
            where TContent : IPresentable
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.ShellBuildAction((container) =>
            {
                var wpfApplication = container.Resolve<WPFApplication>();
                var shellViewModel = BuildDefaultShell<TContent>(builder, state, config, container);
                shellViewModel.Initialize().ContinueWith((t) =>
                {
                    container.Resolve<IEventDispatcher>().GetEvent<ApplicationStartedEvent>().Raise(wpfApplication);
                });
                wpfApplication.ShowWindow();
            }));
            return builder;
        }

        /// <summary>
        /// Sets a default shell for the WPF application
        /// </summary>
        /// <typeparam name="TContent">The main content type to be shown on startup</typeparam>
        /// <typeparam name="TLogin"></typeparam>
        /// <returns></returns>
        public static IApplicationBuilder UseDefaultShellWithLogin<TContent, TLogin>(
            this IApplicationBuilder builder,
            WindowState state = WindowState.Maximized,
            Action<Window> config = null)
            where TContent : IPresentable
            where TLogin : ILoginViewModel
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.ShellBuildAction((container) =>
            {
                var wpfApplication = container.Resolve<WPFApplication>();
                var shellViewModel = BuildDefaultShell<TContent>(builder, state, config, container);
                var loginViewModel = container.Resolve<TLogin>();
                BuildLogin(container, wpfApplication, shellViewModel, loginViewModel);
            }));
            return builder;
        }

        /// <summary>
        /// Sets a custom shell for the WPF application
        /// </summary>
        /// <typeparam name="TShellViewModel"></typeparam>
        /// <returns></returns>
        public static IApplicationBuilder UseCustomShell<TShellViewModel>(this IApplicationBuilder builder,
                                                                             Action<Window> config = null)
            where TShellViewModel : IShellViewModel
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.ShellBuildAction((container) =>
            {
                var wpfApplication = container.Resolve<WPFApplication>();
                var shellViewModel = BuildCustomShell<TShellViewModel>(config, container);
                shellViewModel.Initialize().ContinueWith((t) =>
                {
                    container.Resolve<IEventDispatcher>().GetEvent<ApplicationStartedEvent>().Raise(wpfApplication);
                });
                wpfApplication.ShowWindow();
            }));
            return builder;
        }

        /// <summary>
        /// Sets the application theme colors
        /// </summary>
        /// <returns></returns>
        public static IApplicationBuilder UseTheme(this IApplicationBuilder builder, ApplicationColors colors)
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.ApplicationThemeAction((container) =>
            {
                ApplicationTheme.SetTheme(colors);
            }));
            return builder;
        }

        /// <summary>
        /// Sets a custom shell for the WPF application
        /// </summary>
        /// <returns></returns>
        public static IApplicationBuilder UseCustomShellWithLogin<TShellViewModel, TLogin>(this IApplicationBuilder builder,
                                                                                Action<Window> config = null)
            where TShellViewModel : IShellViewModel
            where TLogin : ILoginViewModel
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.ShellBuildAction((container) =>
            {
                var wpfApplication = container.Resolve<WPFApplication>();
                var shellViewModel = BuildCustomShell<TShellViewModel>(config, container);
                var loginViewModel = container.Resolve<TLogin>();
                BuildLogin(container, wpfApplication, shellViewModel, loginViewModel);
            }));
            return builder;
        }

        private static void BuildLogin<TShellViewModel, TLogin>(IContainer container, WPFApplication wpfApplication, TShellViewModel shellViewModel, TLogin loginViewModel)
            where TShellViewModel : IShellViewModel
            where TLogin : ILoginViewModel
        {
            container.RegisterInstance<ILoginViewModel>(loginViewModel);
            loginViewModel.Initialize()
                .ContinueWith(lt =>
                {
                    var loggedIn = false;
                    var loginView = loginViewModel.GetView() as Window;

                    if (loginView == null)
                        throw new ApplicationBuildException("The login view muse be of type Window");

                    loginView.Closed += delegate
                    {
                        if (!loggedIn)
                            wpfApplication.GetSystemApplication().Shutdown();
                    };
                    loginViewModel.LoggedIn += (s, args) =>
                    {
                        loggedIn = true;
                        wpfApplication.ShowWindow();
                        shellViewModel.Initialize().ContinueWith((t) =>
                        {
                            container.Resolve<IEventDispatcher>().GetEvent<ApplicationStartedEvent>().Raise(wpfApplication);
                        });
                        loginView.Close();
                    };
                    wpfApplication.GetSystemApplication()
                        .Dispatcher.Invoke(() =>
                        {
                            loginView.Show();
                        });
                });
        }

        private static TShellViewModel BuildCustomShell<TShellViewModel>(Action<Window> config,
                                                                         IContainer container)
            where TShellViewModel : IShellViewModel
        {
            container.Resolve<IGlobalVariablesService>().GetVariable<UsingDefaultShellGlobalVariable>().SetValue(false);
            var vmManager = container.Resolve<IViewModelsManager>();

            var shellViewModel = vmManager.CreateViewModel<TShellViewModel>(null);
            container.RegisterInstance<IShellViewModel>(shellViewModel);

            var wpfApplication = container.Resolve<WPFApplication>();
            var systemApplication = wpfApplication.GetSystemApplication();

            var shell = (IShell)shellViewModel.GetView();
            container.RegisterInstance<IShell>(shell);

            var shellWindow = (Window)shell;
            config?.Invoke(shellWindow);
            systemApplication.MainWindow = shellWindow;
            shellWindow.Closed += delegate { systemApplication.Shutdown(); };
            return shellViewModel;
        }

        private static IDefaultShellViewModel BuildDefaultShell<TContent>(IApplicationBuilder builder, WindowState state, Action<Window> config, IContainer container) where TContent : IPresentable
        {
            container.Resolve<IGlobalVariablesService>().GetVariable<UsingDefaultShellGlobalVariable>().SetValue(true);

            var vmManager = container.Resolve<IViewModelsManager>();
            var shellViewModel = vmManager.CreateViewModel<DefaultShellViewModel>(null);

            container.RegisterInstance<IShellViewModel>(shellViewModel);
            container.RegisterInstance<IDefaultShellViewModel>(shellViewModel);

            var wpfApplication = container.Resolve<WPFApplication>();
            var systemApplication = wpfApplication.GetSystemApplication();

            var shell = (DefaultShellView)shellViewModel.GetView();
            shell.SetState(state);
            config?.Invoke(shell);
            container.RegisterInstance<IShell>(shell);
            systemApplication.MainWindow = shell;
            shell.Closed += delegate { systemApplication.Shutdown(); };

            shellViewModel.SetMainContent(typeof(TContent),
                                          builder.BuildActionsCoordinator
                                              .BuildActionExists(BuildActionsKeys
                                                                     .Navigation));

            return shellViewModel;
        }

        /// <summary>
        /// Sets a default shell for the WPF application
        /// </summary>
        /// <returns></returns>
        public static IApplicationBuilder UseNavigation(
            this IApplicationBuilder builder,
            params INavigationItem[] navigationItems)
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.NavigationBuildAction((container) =>
            {
                var navs = new List<INavigationItem>();
                var nav = container.Resolve<INavigationService>();
                var gud = container.Resolve<IGlobalVariablesService>().GetVariable<UsingDefaultShellGlobalVariable>();
                if (gud.IsValueSet && gud.GetValue())
                {
                    var homeNav = new NavigationItem(container.Resolve<IDefaultShellViewModel>().GetMainContent(),
                                                     "Home",
                                                     "M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z");
                    DefaultNavigations.Home = homeNav;

                    navs.Add(homeNav);
                    navs.AddRange(navigationItems);

                    nav.Initialize(DefaultRegions.NavbarRegion,
                                   DefaultRegions.ApplicationMainRegion,
                                   navs);
                }
                else
                {
                    navs.AddRange(navigationItems);
                    nav.Initialize(DefaultRegions.NavbarRegion,
                                   DefaultRegions.ApplicationMainRegion,
                                   navs);
                }
            }));
            return builder;
        }

        /// <summary>
        /// Use Dialog service
        /// </summary>
        /// <returns></returns>
        public static IApplicationBuilder UseDialogs(this IApplicationBuilder builder,
                                                        Region dialogRegion)
        {
            builder.BuildActionsCoordinator.AddAction(DefaultBuildActions.DialogServiceBuildAction(
                                                                                (container) =>
                     {
                         container.Resolve<IDialogService>().Initialize(dialogRegion);
                         container.Resolve<IDialogExplorer>().Initialize();
                     }));
            return builder;
        }

        /// <summary>
        /// Use Dialog service
        /// </summary>
        /// <returns></returns>
        public static IApplicationBuilder UseDialogs(this IApplicationBuilder builder)
        {
            return builder.UseDialogs(DefaultRegions.DialogRegion);
        }

        /// <summary>
        /// Use the toast service
        /// </summary>
        /// <param name="duration">The duration the toast will stay visible in milliseconds</param>
        /// <param name="builder">The application builder</param>
        /// <returns></returns>
        public static IApplicationBuilder UseToast(this IApplicationBuilder builder, double duration = 3000)
        {
            return builder.UseToast(DefaultRegions.ToastRegion, duration);
        }

        /// <summary>
        /// Use the toast service
        /// </summary>
        /// <param name="builder">The application builder</param>
        /// <param name="toastRegion">The region in which the toasts will be viewed</param>
        /// <param name="duration">The duration the toast will stay visible in milliseconds</param>
        /// <returns></returns>
        public static IApplicationBuilder UseToast(this IApplicationBuilder builder,
                                                      Region toastRegion,
                                                      double duration = 3000)
        {
            builder.BuildActionsCoordinator.AddAction(new BuildAction(BuildActionsKeys.Toast, container =>
            {
                container.Resolve<IToastService>().Initialize(toastRegion, TimeSpan.FromMilliseconds(duration));
            }));
            return builder;
        }

        /// <summary>
        /// Initializes the <see cref="INotificationService"/>
        /// </summary>
        public static IApplicationBuilder UseNotification(this IApplicationBuilder builder, Region notificationRegion, double duration)
        {
            builder.BuildActionsCoordinator.AddActionAfter(new BuildAction(BuildActionsKeys.Notification,
                                                                           container =>
                                                                           {
                                                                               NotificationService service = (NotificationService)container.Resolve<INotificationService>();
                                                                               service.Inititlize(notificationRegion, duration);
                                                                           }), BuildActionsKeys.Toast);
            return builder;
        }

        /// <summary>
        /// Initializes the <see cref="INotificationService"/>
        /// </summary>
        public static IApplicationBuilder UseNotification(this IApplicationBuilder builder)
        {
            return builder.UseNotification(DefaultRegions.NotificationRegion, 5000);
        }
    }

}


text:
package me.jessyan.mvparms.demo.mvp.model.api.service;

import com.jess.arms.http.BaseServiceManager;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * Created by jess on 8/5/16 13:01
 * contact with jess.yan.effort@gmail.com
 */
@Singleton
public class ServiceManager implements BaseServiceManager {
    private CommonService mCommonService;
    private UserService mUserService;
    private HomeService mHomeService;

    /**
     * 如果需要添加service只需在构造方法中添加对应的service,在提供get方法返回出去,只要在ServiceModule提供了该service
     * Dagger2会自行注入
     *
     * @param commonService
     */
    @Inject
    public ServiceManager(CommonService commonService, UserService userService, HomeService homeService) {
        this.mCommonService = commonService;
        this.mUserService = userService;
        this.mHomeService = homeService;
    }

    public CommonService getCommonService() {
        return mCommonService;
    }

    public HomeService getHomeService() {
        return mHomeService;
    }

    public UserService getUserService() {
        return mUserService;
    }

    /**
     * 这里可以释放一些资源(注意这里是单例,即不需要在activity的生命周期调用)
     */
    @Override
    public void onDestory() {

    }
}


text:
package com.ddshell.framework.security.service;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.ddshell.framework.common.util.GenericCrudService;
import com.ddshell.framework.common.util.GlobalVars;
import com.ddshell.framework.security.entity.User;
import com.ddshell.framework.security.repository.UserRepository;
import com.ddshell.framework.security.shiro.entity.ShiroUser;
import com.ddshell.framework.security.shiro.service.ShiroUserService;

@Transactional(readOnly = true)
public abstract class UserService<T extends User> extends
		GenericCrudService<T, Long> implements ShiroUserService {

	private static final String LOGIN_USER = UserService.class.getName()
			+ ".loginUser";

	@Autowired
	private GlobalVars globalVars;

	public T findByLoginName(String loginName) {
		return ((UserRepository<T>) getRepository()).findByLoginName(loginName);
	}

	@Override
	public byte[] getSaltBytes(ShiroUser user) {
		String salt = user.getSalt();
		if (salt == null) {
			return null;
		}

		try {
			return Hex.decodeHex(salt.toCharArray());
		} catch (DecoderException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	@Transactional
	public T saveUser(T user) {
		String plainPassword = user.getPlainPassword();
		if (!StringUtils.isEmpty(plainPassword)) {
			user.setPassword(encodeUserPassword(plainPassword, user));
		}
		return super.save(user);
	}

	@Transactional
	public boolean changeLoginUserPassword(String plainOldPassword,
			String plainNewPassword) {
		if (StringUtils.isEmpty(plainOldPassword)
				|| StringUtils.isEmpty(plainNewPassword)) {
			return false;
		}

		T loginUser = getLoginUser();

		if (!encodeUserPassword(plainOldPassword, loginUser).equalsIgnoreCase(
				loginUser.getPassword())) {
			return false;
		}

		T user = findOne(loginUser.getId());
		user.setPlainPassword(plainNewPassword);
		user = save(user);

		loginUser.setPassword(user.getPassword());

		return true;
	}

	public T getLoginUser() {
		Subject subject = SecurityUtils.getSubject();
		if (!subject.isAuthenticated()) {
			return null;
		}

		Session session = subject.getSession(true);
		if (session == null) {
			return null;
		}

		@SuppressWarnings("unchecked")
		T loginUser = (T) session.getAttribute(LOGIN_USER);
		if (loginUser == null) {
			loginUser = findByLoginName((String) subject.getPrincipal());
			session.setAttribute(LOGIN_USER, loginUser);
		}

		return loginUser;
	}

	private String encodeUserPassword(String plainPassword, T user) {
		SimpleHash hash = new SimpleHash(globalVars.hashAlgorithmName,
				plainPassword, getSaltBytes(user), globalVars.hashIterations);
		return hash.toString();
	}

}


text:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Jitrino: Jitrino.EntryBackedgeMethodProfile Class Reference</title>
<link href="hydoxygen.css" rel="stylesheet" type="text/css">
<link href="tabs.css" rel="stylesheet" type="text/css">

Main Page

Modules

Packages

Classes

Files

Class List

Class Hierarchy

Class Members

Jitrino.EntryBackedgeMethodProfile

Jitrino.EntryBackedgeMethodProfile Class Reference
#include <EMInterface.h>

Inheritance diagram for Jitrino.EntryBackedgeMethodProfile:

<map name="Jitrino.EntryBackedgeMethodProfile_map">
<area href="class_jitrino_1_1_method_profile.html" alt="Jitrino.MethodProfile" shape="rect" coords="0,0,216,24">
</map>
List of all members.

Public Member Functions

 	EntryBackedgeMethodProfile (Method_Profile_Handle mph, MethodDesc &md, uint32 *_entryCounter, uint32 *_backedgeCounter)

uint32 	getEntryExecCount () const

uint32 	getBackedgeExecCount () const

uint32 * 	getEntryCounter () const

uint32 * 	getBackedgeCounter () const

Constructor & Destructor Documentation

          Jitrino.EntryBackedgeMethodProfile.EntryBackedgeMethodProfile
          (
          Method_Profile_Handle
           mph,

          MethodDesc &
           md,

          uint32 *
           _entryCounter,

          uint32 *
           _backedgeCounter

          )

Member Function Documentation

          uint32 Jitrino.EntryBackedgeMethodProfile.getEntryExecCount
          (

           )
           const

          uint32 Jitrino.EntryBackedgeMethodProfile.getBackedgeExecCount
          (

           )
           const

          uint32* Jitrino.EntryBackedgeMethodProfile.getEntryCounter
          (

           )
           const

          uint32* Jitrino.EntryBackedgeMethodProfile.getBackedgeCounter
          (

           )
           const

The documentation for this class was generated from the following file:

EMInterface.h

<address style="text-align: center;">

Genereated on Tue Mar 11 19:25:40 2008 by Doxygen.

(c) Copyright 2005, 2008 The Apache Software Foundation or its licensors, as applicable.

</address>


text:
"""dbSNP - genetic variation within and across different species."""

# We can use another GATK resource since it's pretty much the same
from example import Resource as iResource

class Resource(iResource):
  """docstring for dbSNP Resource"""
  def __init__(self):
    super(Resource, self).__init__()

    self.id = "dbsnp"

    self.parts = 1
    self.names = ["dbsnp.vcf.gz"]

  def paths(self, version):
    bundle_id, assembly = self.defineVersion(version)

    if float(bundle_id) == self.latest():
      num = '138'
    else:
      num = '137'

    # 1 file
    base = "{base}/{bundle}/{assembly}".format(base=self.baseUrl,
                                               bundle=bundle_id,
                                               assembly=assembly)
    f = "dbsnp_{num}.{assembly}.vcf.gz".format(num=num, assembly=assembly)

    return ["{base}/{file}".format(base=base, file=f)]


text:
date: 2014-09-22
layout: post
published: true
title: Couldn't Find User With Id=sign_in Devise on Rails 4
comments: true
categories: [rails]
---
When the default routes are created for devise you always get:

```
devise_for :users
resource :users
```

but I tried to write custom routes for devise and user to get invitations working. But I added the custom router below the ```resource :users```. This resulted in the following error:

```
Couldn't Find User With Id=sign_in Devise on Rails 3
```

But finally I figure it out. ```routes.rb``` gets read top down and firt rule that matched wins. So ```resource ;users```one over the devise rules. When I put it in the correct order it worked again :)


text:
#ifndef ASYLO_IDENTITY_PROVISIONING_SGX_INTERNAL_MOCK_SGX_PCS_CLIENT_H_
#define ASYLO_IDENTITY_PROVISIONING_SGX_INTERNAL_MOCK_SGX_PCS_CLIENT_H_

#include <gmock/gmock.h>
#include "asylo/identity/platform/sgx/machine_configuration.pb.h"
#include "asylo/identity/provisioning/sgx/internal/platform_provisioning.pb.h"
#include "asylo/identity/provisioning/sgx/internal/sgx_pcs_client.h"
#include "asylo/util/statusor.h"

namespace asylo {
namespace sgx {

class MockSgxPcsClient : public SgxPcsClient {
 public:
  MOCK_METHOD(StatusOr<GetPckCertificateResult>, GetPckCertificate,
              (const Ppid &, const CpuSvn &, const PceSvn &, const PceId &),
              (override));

  MOCK_METHOD(StatusOr<GetPckCertificatesResult>, GetPckCertificates,
              (const Ppid &, const PceId &), (override));

  MOCK_METHOD(StatusOr<GetCrlResult>, GetCrl, (SgxCaType), (override));

  MOCK_METHOD(StatusOr<GetTcbInfoResult>, GetTcbInfo, (const Fmspc &),
              (override));
};

}  // namespace sgx
}  // namespace asylo

#endif  // ASYLO_IDENTITY_PROVISIONING_SGX_INTERNAL_MOCK_SGX_PCS_CLIENT_H_


text: Rails.application.config.session_store :cookie_store, key: '_thatstypical_session'


text:
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using JetBrains.Annotations;

namespace XyrusWorx {
	[PublicAPI]
	[Cmdlet(VerbsLifecycle.Invoke, "Isolated")]
	public class InvokeIsolatedCommand : Cmdlet
	{
		[Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
		public string Command { get; set; }

		[Parameter]
		public object Context { get; set; }

		[Parameter]
		public IsolationScope Scope { get; set; }

		protected override void ProcessRecord()
		{
			var ownScope = Scope == null;
			var scope = Scope ?? new IsolationScope(this, Runspace.DefaultRunspace);

			try
			{
				var context = new PSObject(Context ?? new object());
				scope.Execute(this, new object[]{context}, Command);
			}
			finally
			{
				if (ownScope)
				{
					scope.Exit(this);
				}
			}
		}
	}
}


text:
/*************************************************************************/
/*  texture_region_editor_plugin.cpp                                     */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

#include "texture_region_editor_plugin.h"

#include "core/core_string_names.h"
#include "core/input/input.h"
#include "core/os/keyboard.h"
#include "editor/editor_scale.h"
#include "scene/gui/check_box.h"

/**
	@author Mariano Suligoy
*/

void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) {
	Vector2 line = (to - from).normalized() * 10;

	// Draw a translucent background line to make the foreground line visible on any background.
	edit_draw->draw_line(
			from,
			to,
			EditorNode::get_singleton()->get_theme_base()->get_theme_color(SNAME("mono_color"), SNAME("Editor")).inverted() * Color(1, 1, 1, 0.5),
			Math::round(2 * EDSCALE));

	while (from.distance_squared_to(to) > 200) {
		edit_draw->draw_line(
				from,
				from + line,
				EditorNode::get_singleton()->get_theme_base()->get_theme_color(SNAME("mono_color"), SNAME("Editor")),
				Math::round(2 * EDSCALE));

		from += line * 2;
	}
}

void TextureRegionEditor::_region_draw() {
	Ref<Texture2D> base_tex = nullptr;
	if (atlas_tex.is_valid()) {
		base_tex = atlas_tex->get_atlas();
	} else if (node_sprite_2d) {
		base_tex = node_sprite_2d->get_texture();
	} else if (node_sprite_3d) {
		base_tex = node_sprite_3d->get_texture();
	} else if (node_ninepatch) {
		base_tex = node_ninepatch->get_texture();
	} else if (obj_styleBox.is_valid()) {
		base_tex = obj_styleBox->get_texture();
	}

	if (base_tex.is_null()) {
		return;
	}

	Transform2D mtx;
	mtx.elements[2] = -draw_ofs * draw_zoom;
	mtx.scale_basis(Vector2(draw_zoom, draw_zoom));

	RS::get_singleton()->canvas_item_add_set_transform(edit_draw->get_canvas_item(), mtx);
	edit_draw->draw_texture(base_tex, Point2());
	RS::get_singleton()->canvas_item_add_set_transform(edit_draw->get_canvas_item(), Transform2D());

	if (snap_mode == SNAP_GRID) {
		Color grid_color = Color(1.0, 1.0, 1.0, 0.15);
		Size2 s = edit_draw->get_size();
		int last_cell = 0;

		if (snap_step.x != 0) {
			if (snap_separation.x == 0) {
				for (int i = 0; i < s.width; i++) {
					int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(i, 0)).x - snap_offset.x) / snap_step.x));
					if (i == 0) {
						last_cell = cell;
					}
					if (last_cell != cell) {
						edit_draw->draw_line(Point2(i, 0), Point2(i, s.height), grid_color);
					}
					last_cell = cell;
				}
			} else {
				for (int i = 0; i < s.width; i++) {
					int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(i, 0)).x - snap_offset.x) / (snap_step.x + snap_separation.x)));
					if (i == 0) {
						last_cell = cell;
					}
					if (last_cell != cell) {
						edit_draw->draw_rect(Rect2(i - snap_separation.x * draw_zoom, 0, snap_separation.x * draw_zoom, s.height), grid_color);
					}
					last_cell = cell;
				}
			}
		}

		if (snap_step.y != 0) {
			if (snap_separation.y == 0) {
				for (int i = 0; i < s.height; i++) {
					int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(0, i)).y - snap_offset.y) / snap_step.y));
					if (i == 0) {
						last_cell = cell;
					}
					if (last_cell != cell) {
						edit_draw->draw_line(Point2(0, i), Point2(s.width, i), grid_color);
					}
					last_cell = cell;
				}
			} else {
				for (int i = 0; i < s.height; i++) {
					int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(0, i)).y - snap_offset.y) / (snap_step.y + snap_separation.y)));
					if (i == 0) {
						last_cell = cell;
					}
					if (last_cell != cell) {
						edit_draw->draw_rect(Rect2(0, i - snap_separation.y * draw_zoom, s.width, snap_separation.y * draw_zoom), grid_color);
					}
					last_cell = cell;
				}
			}
		}
	} else if (snap_mode == SNAP_AUTOSLICE) {
		for (const Rect2 &r : autoslice_cache) {
			Vector2 endpoints[4] = {
				mtx.basis_xform(r.position),
				mtx.basis_xform(r.position + Vector2(r.size.x, 0)),
				mtx.basis_xform(r.position + r.size),
				mtx.basis_xform(r.position + Vector2(0, r.size.y))
			};
			for (int i = 0; i < 4; i++) {
				int next = (i + 1) % 4;
				edit_draw->draw_line(endpoints[i] - draw_ofs * draw_zoom, endpoints[next] - draw_ofs * draw_zoom, Color(0.3, 0.7, 1, 1), 2);
			}
		}
	}

	Ref<Texture2D> select_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons"));

	Rect2 scroll_rect(Point2(), base_tex->get_size());

	const Vector2 raw_endpoints[4] = {
		rect.position,
		rect.position + Vector2(rect.size.x, 0),
		rect.position + rect.size,
		rect.position + Vector2(0, rect.size.y)
	};
	const Vector2 endpoints[4] = {
		mtx.basis_xform(raw_endpoints[0]),
		mtx.basis_xform(raw_endpoints[1]),
		mtx.basis_xform(raw_endpoints[2]),
		mtx.basis_xform(raw_endpoints[3])
	};
	Color color = get_theme_color(SNAME("mono_color"), SNAME("Editor"));
	for (int i = 0; i < 4; i++) {
		int prev = (i + 3) % 4;
		int next = (i + 1) % 4;

		Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized();
		ofs *= Math_SQRT2 * (select_handle->get_size().width / 2);

		edit_draw->draw_line(endpoints[i] - draw_ofs * draw_zoom, endpoints[next] - draw_ofs * draw_zoom, color, 2);

		if (snap_mode != SNAP_AUTOSLICE) {
			edit_draw->draw_texture(select_handle, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor() - draw_ofs * draw_zoom);
		}

		ofs = (endpoints[next] - endpoints[i]) / 2;
		ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2);

		if (snap_mode != SNAP_AUTOSLICE) {
			edit_draw->draw_texture(select_handle, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor() - draw_ofs * draw_zoom);
		}

		scroll_rect.expand_to(raw_endpoints[i]);
	}

	const Size2 scroll_margin = edit_draw->get_size() / draw_zoom;
	scroll_rect.position -= scroll_margin;
	scroll_rect.size += scroll_margin * 2;

	updating_scroll = true;

	hscroll->set_min(scroll_rect.position.x);
	hscroll->set_max(scroll_rect.position.x + scroll_rect.size.x);
	if (ABS(scroll_rect.position.x - (scroll_rect.position.x + scroll_rect.size.x)) <= scroll_margin.x) {
		hscroll->hide();
	} else {
		hscroll->show();
		hscroll->set_page(scroll_margin.x);
		hscroll->set_value(draw_ofs.x);
	}

	vscroll->set_min(scroll_rect.position.y);
	vscroll->set_max(scroll_rect.position.y + scroll_rect.size.y);
	if (ABS(scroll_rect.position.y - (scroll_rect.position.y + scroll_rect.size.y)) <= scroll_margin.y) {
		vscroll->hide();
		draw_ofs.y = scroll_rect.position.y;
	} else {
		vscroll->show();
		vscroll->set_page(scroll_margin.y);
		vscroll->set_value(draw_ofs.y);
	}

	Size2 hmin = hscroll->get_combined_minimum_size();
	Size2 vmin = vscroll->get_combined_minimum_size();

	// Avoid scrollbar overlapping.
	hscroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, vscroll->is_visible() ? -vmin.width : 0);
	vscroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, hscroll->is_visible() ? -hmin.height : 0);

	updating_scroll = false;

	if (node_ninepatch || obj_styleBox.is_valid()) {
		float margins[4] = { 0 };
		if (node_ninepatch) {
			margins[0] = node_ninepatch->get_patch_margin(SIDE_TOP);
			margins[1] = node_ninepatch->get_patch_margin(SIDE_BOTTOM);
			margins[2] = node_ninepatch->get_patch_margin(SIDE_LEFT);
			margins[3] = node_ninepatch->get_patch_margin(SIDE_RIGHT);
		} else if (obj_styleBox.is_valid()) {
			margins[0] = obj_styleBox->get_margin_size(SIDE_TOP);
			margins[1] = obj_styleBox->get_margin_size(SIDE_BOTTOM);
			margins[2] = obj_styleBox->get_margin_size(SIDE_LEFT);
			margins[3] = obj_styleBox->get_margin_size(SIDE_RIGHT);
		}

		Vector2 pos[4] = {
			mtx.basis_xform(Vector2(0, margins[0])) + Vector2(0, endpoints[0].y - draw_ofs.y * draw_zoom),
			-mtx.basis_xform(Vector2(0, margins[1])) + Vector2(0, endpoints[2].y - draw_ofs.y * draw_zoom),
			mtx.basis_xform(Vector2(margins[2], 0)) + Vector2(endpoints[0].x - draw_ofs.x * draw_zoom, 0),
			-mtx.basis_xform(Vector2(margins[3], 0)) + Vector2(endpoints[2].x - draw_ofs.x * draw_zoom, 0)
		};

		draw_margin_line(edit_draw, pos[0], pos[0] + Vector2(edit_draw->get_size().x, 0));
		draw_margin_line(edit_draw, pos[1], pos[1] + Vector2(edit_draw->get_size().x, 0));
		draw_margin_line(edit_draw, pos[2], pos[2] + Vector2(0, edit_draw->get_size().y));
		draw_margin_line(edit_draw, pos[3], pos[3] + Vector2(0, edit_draw->get_size().y));
	}
}

void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
	Transform2D mtx;
	mtx.elements[2] = -draw_ofs * draw_zoom;
	mtx.scale_basis(Vector2(draw_zoom, draw_zoom));

	const real_t handle_radius = 8 * EDSCALE;
	const real_t handle_offset = 4 * EDSCALE;

	// Position of selection handles.
	const Vector2 endpoints[8] = {
		mtx.xform(rect.position) + Vector2(-handle_offset, -handle_offset),
		mtx.xform(rect.position + Vector2(rect.size.x / 2, 0)) + Vector2(0, -handle_offset),
		mtx.xform(rect.position + Vector2(rect.size.x, 0)) + Vector2(handle_offset, -handle_offset),
		mtx.xform(rect.position + Vector2(rect.size.x, rect.size.y / 2)) + Vector2(handle_offset, 0),
		mtx.xform(rect.position + rect.size) + Vector2(handle_offset, handle_offset),
		mtx.xform(rect.position + Vector2(rect.size.x / 2, rect.size.y)) + Vector2(0, handle_offset),
		mtx.xform(rect.position + Vector2(0, rect.size.y)) + Vector2(-handle_offset, handle_offset),
		mtx.xform(rect.position + Vector2(0, rect.size.y / 2)) + Vector2(-handle_offset, 0)
	};

	Ref<InputEventMouseButton> mb = p_input;
	if (mb.is_valid()) {
		if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
			if (mb->is_pressed()) {
				if (node_ninepatch || obj_styleBox.is_valid()) {
					edited_margin = -1;
					float margins[4] = { 0 };
					if (node_ninepatch) {
						margins[0] = node_ninepatch->get_patch_margin(SIDE_TOP);
						margins[1] = node_ninepatch->get_patch_margin(SIDE_BOTTOM);
						margins[2] = node_ninepatch->get_patch_margin(SIDE_LEFT);
						margins[3] = node_ninepatch->get_patch_margin(SIDE_RIGHT);
					} else if (obj_styleBox.is_valid()) {
						margins[0] = obj_styleBox->get_margin_size(SIDE_TOP);
						margins[1] = obj_styleBox->get_margin_size(SIDE_BOTTOM);
						margins[2] = obj_styleBox->get_margin_size(SIDE_LEFT);
						margins[3] = obj_styleBox->get_margin_size(SIDE_RIGHT);
					}

					Vector2 pos[4] = {
						mtx.basis_xform(rect.position + Vector2(0, margins[0])) - draw_ofs * draw_zoom,
						mtx.basis_xform(rect.position + rect.size - Vector2(0, margins[1])) - draw_ofs * draw_zoom,
						mtx.basis_xform(rect.position + Vector2(margins[2], 0)) - draw_ofs * draw_zoom,
						mtx.basis_xform(rect.position + rect.size - Vector2(margins[3], 0)) - draw_ofs * draw_zoom
					};
					if (Math::abs(mb->get_position().y - pos[0].y) < 8) {
						edited_margin = 0;
						prev_margin = margins[0];
					} else if (Math::abs(mb->get_position().y - pos[1].y) < 8) {
						edited_margin = 1;
						prev_margin = margins[1];
					} else if (Math::abs(mb->get_position().x - pos[2].x) < 8) {
						edited_margin = 2;
						prev_margin = margins[2];
					} else if (Math::abs(mb->get_position().x - pos[3].x) < 8) {
						edited_margin = 3;
						prev_margin = margins[3];
					}
					if (edited_margin >= 0) {
						drag_from = mb->get_position();
						drag = true;
					}
				}
				if (edited_margin < 0 && snap_mode == SNAP_AUTOSLICE) {
					Vector2 point = mtx.affine_inverse().xform(mb->get_position());
					for (const Rect2 &E : autoslice_cache) {
						if (E.has_point(point)) {
							rect = E;
							if (Input::get_singleton()->is_key_pressed(KEY_CTRL) && !(Input::get_singleton()->is_key_pressed(Key(KEY_SHIFT | KEY_ALT)))) {
								Rect2 r;
								if (atlas_tex.is_valid()) {
									r = atlas_tex->get_region();
								} else if (node_sprite_2d) {
									r = node_sprite_2d->get_region_rect();
								} else if (node_sprite_3d) {
									r = node_sprite_3d->get_region_rect();
								} else if (node_ninepatch) {
									r = node_ninepatch->get_region_rect();
								} else if (obj_styleBox.is_valid()) {
									r = obj_styleBox->get_region_rect();
								}
								rect.expand_to(r.position);
								rect.expand_to(r.get_end());
							}
							undo_redo->create_action(TTR("Set Region Rect"));
							if (atlas_tex.is_valid()) {
								undo_redo->add_do_method(atlas_tex.ptr(), "set_region", rect);
								undo_redo->add_undo_method(atlas_tex.ptr(), "set_region", atlas_tex->get_region());
							} else if (node_sprite_2d) {
								undo_redo->add_do_method(node_sprite_2d, "set_region_rect", rect);
								undo_redo->add_undo_method(node_sprite_2d, "set_region_rect", node_sprite_2d->get_region_rect());
							} else if (node_sprite_3d) {
								undo_redo->add_do_method(node_sprite_3d, "set_region_rect", rect);
								undo_redo->add_undo_method(node_sprite_3d, "set_region_rect", node_sprite_3d->get_region_rect());
							} else if (node_ninepatch) {
								undo_redo->add_do_method(node_ninepatch, "set_region_rect", rect);
								undo_redo->add_undo_method(node_ninepatch, "set_region_rect", node_ninepatch->get_region_rect());
							} else if (obj_styleBox.is_valid()) {
								undo_redo->add_do_method(obj_styleBox.ptr(), "set_region_rect", rect);
								undo_redo->add_undo_method(obj_styleBox.ptr(), "set_region_rect", obj_styleBox->get_region_rect());
							}
							undo_redo->add_do_method(this, "_update_rect");
							undo_redo->add_undo_method(this, "_update_rect");
							undo_redo->add_do_method(edit_draw, "update");
							undo_redo->add_undo_method(edit_draw, "update");
							undo_redo->commit_action();
							break;
						}
					}
				} else if (edited_margin < 0) {
					drag_from = mtx.affine_inverse().xform(mb->get_position());
					if (snap_mode == SNAP_PIXEL) {
						drag_from = drag_from.snapped(Vector2(1, 1));
					} else if (snap_mode == SNAP_GRID) {
						drag_from = snap_point(drag_from);
					}
					drag = true;
					if (atlas_tex.is_valid()) {
						rect_prev = atlas_tex->get_region();
					} else if (node_sprite_2d) {
						rect_prev = node_sprite_2d->get_region_rect();
					} else if (node_sprite_3d) {
						rect_prev = node_sprite_3d->get_region_rect();
					} else if (node_ninepatch) {
						rect_prev = node_ninepatch->get_region_rect();
					} else if (obj_styleBox.is_valid()) {
						rect_prev = obj_styleBox->get_region_rect();
					}

					for (int i = 0; i < 8; i++) {
						Vector2 tuv = endpoints[i];
						if (tuv.distance_to(mb->get_position()) < handle_radius) {
							drag_index = i;
						}
					}

					if (drag_index == -1) {
						creating = true;
						rect = Rect2(drag_from, Size2());
					}
				}

			} else if (drag) {
				if (edited_margin >= 0) {
					undo_redo->create_action(TTR("Set Margin"));
					static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT };
					if (node_ninepatch) {
						undo_redo->add_do_method(node_ninepatch, "set_patch_margin", side[edited_margin], node_ninepatch->get_patch_margin(side[edited_margin]));
						undo_redo->add_undo_method(node_ninepatch, "set_patch_margin", side[edited_margin], prev_margin);
					} else if (obj_styleBox.is_valid()) {
						undo_redo->add_do_method(obj_styleBox.ptr(), "set_margin_size", side[edited_margin], obj_styleBox->get_margin_size(side[edited_margin]));
						undo_redo->add_undo_method(obj_styleBox.ptr(), "set_margin_size", side[edited_margin], prev_margin);
						obj_styleBox->emit_signal(CoreStringNames::get_singleton()->changed);
					}
					edited_margin = -1;
				} else {
					undo_redo->create_action(TTR("Set Region Rect"));
					if (atlas_tex.is_valid()) {
						undo_redo->add_do_method(atlas_tex.ptr(), "set_region", atlas_tex->get_region());
						undo_redo->add_undo_method(atlas_tex.ptr(), "set_region", rect_prev);
					} else if (node_sprite_2d) {
						undo_redo->add_do_method(node_sprite_2d, "set_region_rect", node_sprite_2d->get_region_rect());
						undo_redo->add_undo_method(node_sprite_2d, "set_region_rect", rect_prev);
					} else if (node_sprite_3d) {
						undo_redo->add_do_method(node_sprite_3d, "set_region_rect", node_sprite_3d->get_region_rect());
						undo_redo->add_undo_method(node_sprite_3d, "set_region_rect", rect_prev);
					} else if (node_ninepatch) {
						undo_redo->add_do_method(node_ninepatch, "set_region_rect", node_ninepatch->get_region_rect());
						undo_redo->add_undo_method(node_ninepatch, "set_region_rect", rect_prev);
					} else if (obj_styleBox.is_valid()) {
						undo_redo->add_do_method(obj_styleBox.ptr(), "set_region_rect", obj_styleBox->get_region_rect());
						undo_redo->add_undo_method(obj_styleBox.ptr(), "set_region_rect", rect_prev);
					}
					drag_index = -1;
				}
				undo_redo->add_do_method(this, "_update_rect");
				undo_redo->add_undo_method(this, "_update_rect");
				undo_redo->add_do_method(edit_draw, "update");
				undo_redo->add_undo_method(edit_draw, "update");
				undo_redo->commit_action();
				drag = false;
				creating = false;
			}

		} else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) {
			if (drag) {
				drag = false;
				if (edited_margin >= 0) {
					static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT };
					if (node_ninepatch) {
						node_ninepatch->set_patch_margin(side[edited_margin], prev_margin);
					}
					if (obj_styleBox.is_valid()) {
						obj_styleBox->set_margin_size(side[edited_margin], prev_margin);
					}
					edited_margin = -1;
				} else {
					apply_rect(rect_prev);
					rect = rect_prev;
					edit_draw->update();
					drag_index = -1;
				}
			}
		} else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) {
			_zoom_on_position(draw_zoom * ((0.95 + (0.05 * mb->get_factor())) / 0.95), mb->get_position());
		} else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) {
			_zoom_on_position(draw_zoom * (1 - (0.05 * mb->get_factor())), mb->get_position());
		}
	}

	Ref<InputEventMouseMotion> mm = p_input;

	if (mm.is_valid()) {
		if (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE || Input::get_singleton()->is_key_pressed(KEY_SPACE)) {
			Vector2 dragged(mm->get_relative().x / draw_zoom, mm->get_relative().y / draw_zoom);
			hscroll->set_value(hscroll->get_value() - dragged.x);
			vscroll->set_value(vscroll->get_value() - dragged.y);
		} else if (drag) {
			if (edited_margin >= 0) {
				float new_margin = 0;

				if (snap_mode != SNAP_GRID) {
					if (edited_margin == 0) {
						new_margin = prev_margin + (mm->get_position().y - drag_from.y) / draw_zoom;
					} else if (edited_margin == 1) {
						new_margin = prev_margin - (mm->get_position().y - drag_from.y) / draw_zoom;
					} else if (edited_margin == 2) {
						new_margin = prev_margin + (mm->get_position().x - drag_from.x) / draw_zoom;
					} else if (edited_margin == 3) {
						new_margin = prev_margin - (mm->get_position().x - drag_from.x) / draw_zoom;
					} else {
						ERR_PRINT("Unexpected edited_margin");
					}

					if (snap_mode == SNAP_PIXEL) {
						new_margin = Math::round(new_margin);
					}
				} else {
					Vector2 pos_snapped = snap_point(mtx.affine_inverse().xform(mm->get_position()));
					Rect2 rect_rounded = Rect2(rect.position.round(), rect.size.round());

					if (edited_margin == 0) {
						new_margin = pos_snapped.y - rect_rounded.position.y;
					} else if (edited_margin == 1) {
						new_margin = rect_rounded.size.y + rect_rounded.position.y - pos_snapped.y;
					} else if (edited_margin == 2) {
						new_margin = pos_snapped.x - rect_rounded.position.x;
					} else if (edited_margin == 3) {
						new_margin = rect_rounded.size.x + rect_rounded.position.x - pos_snapped.x;
					} else {
						ERR_PRINT("Unexpected edited_margin");
					}
				}

				if (new_margin < 0) {
					new_margin = 0;
				}
				static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT };
				if (node_ninepatch) {
					node_ninepatch->set_patch_margin(side[edited_margin], new_margin);
				}
				if (obj_styleBox.is_valid()) {
					obj_styleBox->set_margin_size(side[edited_margin], new_margin);
				}
			} else {
				Vector2 new_pos = mtx.affine_inverse().xform(mm->get_position());
				if (snap_mode == SNAP_PIXEL) {
					new_pos = new_pos.snapped(Vector2(1, 1));
				} else if (snap_mode == SNAP_GRID) {
					new_pos = snap_point(new_pos);
				}

				if (creating) {
					rect = Rect2(drag_from, Size2());
					rect.expand_to(new_pos);
					apply_rect(rect);
					edit_draw->update();
					return;
				}

				switch (drag_index) {
					case 0: {
						Vector2 p = rect_prev.get_end();
						rect = Rect2(p, Size2());
						rect.expand_to(new_pos);
						apply_rect(rect);
					} break;
					case 1: {
						Vector2 p = rect_prev.position + Vector2(0, rect_prev.size.y);
						rect = Rect2(p, Size2(rect_prev.size.x, 0));
						rect.expand_to(new_pos);
						apply_rect(rect);
					} break;
					case 2: {
						Vector2 p = rect_prev.position + Vector2(0, rect_prev.size.y);
						rect = Rect2(p, Size2());
						rect.expand_to(new_pos);
						apply_rect(rect);
					} break;
					case 3: {
						Vector2 p = rect_prev.position;
						rect = Rect2(p, Size2(0, rect_prev.size.y));
						rect.expand_to(new_pos);
						apply_rect(rect);
					} break;
					case 4: {
						Vector2 p = rect_prev.position;
						rect = Rect2(p, Size2());
						rect.expand_to(new_pos);
						apply_rect(rect);
					} break;
					case 5: {
						Vector2 p = rect_prev.position;
						rect = Rect2(p, Size2(rect_prev.size.x, 0));
						rect.expand_to(new_pos);
						apply_rect(rect);
					} break;
					case 6: {
						Vector2 p = rect_prev.position + Vector2(rect_prev.size.x, 0);
						rect = Rect2(p, Size2());
						rect.expand_to(new_pos);
						apply_rect(rect);
					} break;
					case 7: {
						Vector2 p = rect_prev.position + Vector2(rect_prev.size.x, 0);
						rect = Rect2(p, Size2(0, rect_prev.size.y));
						rect.expand_to(new_pos);
						apply_rect(rect);
					} break;
				}
			}
			edit_draw->update();
		}
	}

	Ref<InputEventMagnifyGesture> magnify_gesture = p_input;
	if (magnify_gesture.is_valid()) {
		_zoom_on_position(draw_zoom * magnify_gesture->get_factor(), magnify_gesture->get_position());
	}

	Ref<InputEventPanGesture> pan_gesture = p_input;
	if (pan_gesture.is_valid()) {
		hscroll->set_value(hscroll->get_value() + hscroll->get_page() * pan_gesture->get_delta().x / 8);
		vscroll->set_value(vscroll->get_value() + vscroll->get_page() * pan_gesture->get_delta().y / 8);
	}
}

void TextureRegionEditor::_scroll_changed(float) {
	if (updating_scroll) {
		return;
	}

	draw_ofs.x = hscroll->get_value();
	draw_ofs.y = vscroll->get_value();
	edit_draw->update();
}

void TextureRegionEditor::_set_snap_mode(int p_mode) {
	snap_mode = p_mode;

	if (snap_mode == SNAP_GRID) {
		hb_grid->show();
	} else {
		hb_grid->hide();
	}

	if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) {
		_update_autoslice();
	}

	edit_draw->update();
}

void TextureRegionEditor::_set_snap_off_x(float p_val) {
	snap_offset.x = p_val;
	edit_draw->update();
}

void TextureRegionEditor::_set_snap_off_y(float p_val) {
	snap_offset.y = p_val;
	edit_draw->update();
}

void TextureRegionEditor::_set_snap_step_x(float p_val) {
	snap_step.x = p_val;
	edit_draw->update();
}

void TextureRegionEditor::_set_snap_step_y(float p_val) {
	snap_step.y = p_val;
	edit_draw->update();
}

void TextureRegionEditor::_set_snap_sep_x(float p_val) {
	snap_separation.x = p_val;
	edit_draw->update();
}

void TextureRegionEditor::_set_snap_sep_y(float p_val) {
	snap_separation.y = p_val;
	edit_draw->update();
}

void TextureRegionEditor::_zoom_on_position(float p_zoom, Point2 p_position) {
	if (p_zoom < 0.25 || p_zoom > 8) {
		return;
	}

	float prev_zoom = draw_zoom;
	draw_zoom = p_zoom;
	Point2 ofs = p_position;
	ofs = ofs / prev_zoom - ofs / draw_zoom;
	draw_ofs = (draw_ofs + ofs).round();

	edit_draw->update();
}

void TextureRegionEditor::_zoom_in() {
	_zoom_on_position(draw_zoom * 1.5, edit_draw->get_size() / 2.0);
}

void TextureRegionEditor::_zoom_reset() {
	_zoom_on_position(1.0, edit_draw->get_size() / 2.0);
}

void TextureRegionEditor::_zoom_out() {
	_zoom_on_position(draw_zoom / 1.5, edit_draw->get_size() / 2.0);
}

void TextureRegionEditor::apply_rect(const Rect2 &p_rect) {
	if (atlas_tex.is_valid()) {
		atlas_tex->set_region(p_rect);
	} else if (node_sprite_2d) {
		node_sprite_2d->set_region_rect(p_rect);
	} else if (node_sprite_3d) {
		node_sprite_3d->set_region_rect(p_rect);
	} else if (node_ninepatch) {
		node_ninepatch->set_region_rect(p_rect);
	} else if (obj_styleBox.is_valid()) {
		obj_styleBox->set_region_rect(p_rect);
	}
}

void TextureRegionEditor::_update_rect() {
	if (atlas_tex.is_valid()) {
		rect = atlas_tex->get_region();
	} else if (node_sprite_2d) {
		rect = node_sprite_2d->get_region_rect();
	} else if (node_sprite_3d) {
		rect = node_sprite_3d->get_region_rect();
	} else if (node_ninepatch) {
		rect = node_ninepatch->get_region_rect();
		if (rect == Rect2()) {
			rect = Rect2(Vector2(), node_ninepatch->get_texture()->get_size());
		}
	} else if (obj_styleBox.is_valid()) {
		rect = obj_styleBox->get_region_rect();
	}
}

void TextureRegionEditor::_update_autoslice() {
	autoslice_is_dirty = false;
	autoslice_cache.clear();

	Ref<Texture2D> texture = nullptr;
	if (atlas_tex.is_valid()) {
		texture = atlas_tex->get_atlas();
	} else if (node_sprite_2d) {
		texture = node_sprite_2d->get_texture();
	} else if (node_sprite_3d) {
		texture = node_sprite_3d->get_texture();
	} else if (node_ninepatch) {
		texture = node_ninepatch->get_texture();
	} else if (obj_styleBox.is_valid()) {
		texture = obj_styleBox->get_texture();
	}

	if (texture.is_null()) {
		return;
	}

	for (int y = 0; y < texture->get_height(); y++) {
		for (int x = 0; x < texture->get_width(); x++) {
			if (texture->is_pixel_opaque(x, y)) {
				bool found = false;
				for (Rect2 &E : autoslice_cache) {
					Rect2 grown = E.grow(1.5);
					if (grown.has_point(Point2(x, y))) {
						E.expand_to(Point2(x, y));
						E.expand_to(Point2(x + 1, y + 1));
						x = E.position.x + E.size.x - 1;
						bool merged = true;
						while (merged) {
							merged = false;
							bool queue_erase = false;
							for (List<Rect2>::Element *F = autoslice_cache.front(); F; F = F->next()) {
								if (queue_erase) {
									autoslice_cache.erase(F->prev());
									queue_erase = false;
								}
								if (F->get() == E) {
									continue;
								}
								if (E.grow(1).intersects(F->get())) {
									E.expand_to(F->get().position);
									E.expand_to(F->get().position + F->get().size);
									if (F->prev()) {
										F = F->prev();
										autoslice_cache.erase(F->next());
									} else {
										queue_erase = true;
										// Can't delete the first rect in the list.
									}
									merged = true;
								}
							}
						}
						found = true;
						break;
					}
				}
				if (!found) {
					Rect2 new_rect(x, y, 1, 1);
					autoslice_cache.push_back(new_rect);
				}
			}
		}
	}
	cache_map[texture->get_rid()] = autoslice_cache;
}

void TextureRegionEditor::_notification(int p_what) {
	switch (p_what) {
		case NOTIFICATION_ENTER_TREE:
		case NOTIFICATION_THEME_CHANGED: {
			edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree")));
		} break;
		case NOTIFICATION_READY: {
			zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons")));
			zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons")));
			zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons")));

			vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE);
			hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE);
		} break;
		case NOTIFICATION_VISIBILITY_CHANGED: {
			if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) {
				_update_autoslice();
			}
		} break;
		case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
			// This happens when the user leaves the Editor and returns,
			// they could have changed the textures, so the cache is cleared.
			cache_map.clear();
			_edit_region();
		} break;
	}
}

void TextureRegionEditor::_node_removed(Object *p_obj) {
	if (p_obj == node_sprite_2d || p_obj == node_sprite_3d || p_obj == node_ninepatch || p_obj == obj_styleBox.ptr() || p_obj == atlas_tex.ptr()) {
		node_sprite_2d = nullptr;
		node_sprite_3d = nullptr;
		node_ninepatch = nullptr;
		obj_styleBox = Ref<StyleBox>(nullptr);
		atlas_tex = Ref<AtlasTexture>(nullptr);
		hide();
	}
}

void TextureRegionEditor::_bind_methods() {
	ClassDB::bind_method(D_METHOD("_edit_region"), &TextureRegionEditor::_edit_region);
	ClassDB::bind_method(D_METHOD("_node_removed"), &TextureRegionEditor::_node_removed);
	ClassDB::bind_method(D_METHOD("_zoom_on_position"), &TextureRegionEditor::_zoom_on_position);
	ClassDB::bind_method(D_METHOD("_update_rect"), &TextureRegionEditor::_update_rect);
}

bool TextureRegionEditor::is_stylebox() {
	return obj_styleBox.is_valid();
}

bool TextureRegionEditor::is_atlas_texture() {
	return atlas_tex.is_valid();
}

bool TextureRegionEditor::is_ninepatch() {
	return node_ninepatch != nullptr;
}

Sprite2D *TextureRegionEditor::get_sprite_2d() {
	return node_sprite_2d;
}

Sprite3D *TextureRegionEditor::get_sprite_3d() {
	return node_sprite_3d;
}

void TextureRegionEditor::edit(Object *p_obj) {
	if (node_sprite_2d) {
		node_sprite_2d->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
	}
	if (node_sprite_3d) {
		node_sprite_3d->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
	}
	if (node_ninepatch) {
		node_ninepatch->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
	}
	if (obj_styleBox.is_valid()) {
		obj_styleBox->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
	}
	if (atlas_tex.is_valid()) {
		atlas_tex->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
	}
	if (p_obj) {
		node_sprite_2d = Object::cast_to<Sprite2D>(p_obj);
		node_sprite_3d = Object::cast_to<Sprite3D>(p_obj);
		node_ninepatch = Object::cast_to<NinePatchRect>(p_obj);

		bool is_resource = false;
		if (Object::cast_to<StyleBoxTexture>(p_obj)) {
			obj_styleBox = Ref<StyleBoxTexture>(Object::cast_to<StyleBoxTexture>(p_obj));
			is_resource = true;
		}
		if (Object::cast_to<AtlasTexture>(p_obj)) {
			atlas_tex = Ref<AtlasTexture>(Object::cast_to<AtlasTexture>(p_obj));
			is_resource = true;
		}

		if (is_resource) {
			p_obj->connect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
		} else {
			p_obj->connect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
		}
		_edit_region();
	} else {
		node_sprite_2d = nullptr;
		node_sprite_3d = nullptr;
		node_ninepatch = nullptr;
		obj_styleBox = Ref<StyleBoxTexture>(nullptr);
		atlas_tex = Ref<AtlasTexture>(nullptr);
	}
	edit_draw->update();
	if ((node_sprite_2d && !node_sprite_2d->is_region_enabled()) || (node_sprite_3d && !node_sprite_3d->is_region_enabled())) {
		set_process(true);
	}
	if (!p_obj) {
		set_process(false);
	}
}

void TextureRegionEditor::_texture_changed() {
	if (!is_visible()) {
		return;
	}
	_edit_region();
}

void TextureRegionEditor::_edit_region() {
	Ref<Texture2D> texture = nullptr;
	if (atlas_tex.is_valid()) {
		texture = atlas_tex->get_atlas();
	} else if (node_sprite_2d) {
		texture = node_sprite_2d->get_texture();
	} else if (node_sprite_3d) {
		texture = node_sprite_3d->get_texture();
	} else if (node_ninepatch) {
		texture = node_ninepatch->get_texture();
	} else if (obj_styleBox.is_valid()) {
		texture = obj_styleBox->get_texture();
	}

	if (texture.is_null()) {
		_zoom_reset();
		hscroll->hide();
		vscroll->hide();
		edit_draw->update();
		return;
	}

	if (cache_map.has(texture->get_rid())) {
		autoslice_cache = cache_map[texture->get_rid()];
		autoslice_is_dirty = false;
	} else {
		if (is_visible() && snap_mode == SNAP_AUTOSLICE) {
			_update_autoslice();
		} else {
			autoslice_is_dirty = true;
		}
	}

	_update_rect();
	edit_draw->update();
}

Vector2 TextureRegionEditor::snap_point(Vector2 p_target) const {
	if (snap_mode == SNAP_GRID) {
		p_target.x = Math::snap_scalar_separation(snap_offset.x, snap_step.x, p_target.x, snap_separation.x);
		p_target.y = Math::snap_scalar_separation(snap_offset.y, snap_step.y, p_target.y, snap_separation.y);
	}

	return p_target;
}

TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) {
	node_sprite_2d = nullptr;
	node_sprite_3d = nullptr;
	node_ninepatch = nullptr;
	obj_styleBox = Ref<StyleBoxTexture>(nullptr);
	atlas_tex = Ref<AtlasTexture>(nullptr);
	editor = p_editor;
	undo_redo = editor->get_undo_redo();

	snap_step = Vector2(10, 10);
	snap_separation = Vector2(0, 0);
	snap_mode = SNAP_NONE;
	edited_margin = -1;
	drag_index = -1;
	drag = false;

	HBoxContainer *hb_tools = memnew(HBoxContainer);
	add_child(hb_tools);
	hb_tools->add_child(memnew(Label(TTR("Snap Mode:"))));

	snap_mode_button = memnew(OptionButton);
	hb_tools->add_child(snap_mode_button);
	snap_mode_button->add_item(TTR("None"), 0);
	snap_mode_button->add_item(TTR("Pixel Snap"), 1);
	snap_mode_button->add_item(TTR("Grid Snap"), 2);
	snap_mode_button->add_item(TTR("Auto Slice"), 3);
	snap_mode_button->select(0);
	snap_mode_button->connect("item_selected", callable_mp(this, &TextureRegionEditor::_set_snap_mode));

	hb_grid = memnew(HBoxContainer);
	hb_tools->add_child(hb_grid);

	hb_grid->add_child(memnew(VSeparator));
	hb_grid->add_child(memnew(Label(TTR("Offset:"))));

	sb_off_x = memnew(SpinBox);
	sb_off_x->set_min(-256);
	sb_off_x->set_max(256);
	sb_off_x->set_step(1);
	sb_off_x->set_value(snap_offset.x);
	sb_off_x->set_suffix("px");
	sb_off_x->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_off_x));
	hb_grid->add_child(sb_off_x);

	sb_off_y = memnew(SpinBox);
	sb_off_y->set_min(-256);
	sb_off_y->set_max(256);
	sb_off_y->set_step(1);
	sb_off_y->set_value(snap_offset.y);
	sb_off_y->set_suffix("px");
	sb_off_y->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_off_y));
	hb_grid->add_child(sb_off_y);

	hb_grid->add_child(memnew(VSeparator));
	hb_grid->add_child(memnew(Label(TTR("Step:"))));

	sb_step_x = memnew(SpinBox);
	sb_step_x->set_min(-256);
	sb_step_x->set_max(256);
	sb_step_x->set_step(1);
	sb_step_x->set_value(snap_step.x);
	sb_step_x->set_suffix("px");
	sb_step_x->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_step_x));
	hb_grid->add_child(sb_step_x);

	sb_step_y = memnew(SpinBox);
	sb_step_y->set_min(-256);
	sb_step_y->set_max(256);
	sb_step_y->set_step(1);
	sb_step_y->set_value(snap_step.y);
	sb_step_y->set_suffix("px");
	sb_step_y->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_step_y));
	hb_grid->add_child(sb_step_y);

	hb_grid->add_child(memnew(VSeparator));
	hb_grid->add_child(memnew(Label(TTR("Separation:"))));

	sb_sep_x = memnew(SpinBox);
	sb_sep_x->set_min(0);
	sb_sep_x->set_max(256);
	sb_sep_x->set_step(1);
	sb_sep_x->set_value(snap_separation.x);
	sb_sep_x->set_suffix("px");
	sb_sep_x->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_sep_x));
	hb_grid->add_child(sb_sep_x);

	sb_sep_y = memnew(SpinBox);
	sb_sep_y->set_min(0);
	sb_sep_y->set_max(256);
	sb_sep_y->set_step(1);
	sb_sep_y->set_value(snap_separation.y);
	sb_sep_y->set_suffix("px");
	sb_sep_y->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_sep_y));
	hb_grid->add_child(sb_sep_y);

	hb_grid->hide();

	edit_draw = memnew(Panel);
	add_child(edit_draw);
	edit_draw->set_v_size_flags(SIZE_EXPAND_FILL);
	edit_draw->connect("draw", callable_mp(this, &TextureRegionEditor::_region_draw));
	edit_draw->connect("gui_input", callable_mp(this, &TextureRegionEditor::_region_input));

	draw_zoom = 1.0;
	edit_draw->set_clip_contents(true);

	HBoxContainer *zoom_hb = memnew(HBoxContainer);
	edit_draw->add_child(zoom_hb);
	zoom_hb->set_begin(Point2(5, 5));

	zoom_out = memnew(Button);
	zoom_out->set_flat(true);
	zoom_out->set_tooltip(TTR("Zoom Out"));
	zoom_out->connect("pressed", callable_mp(this, &TextureRegionEditor::_zoom_out));
	zoom_hb->add_child(zoom_out);

	zoom_reset = memnew(Button);
	zoom_reset->set_flat(true);
	zoom_reset->set_tooltip(TTR("Zoom Reset"));
	zoom_reset->connect("pressed", callable_mp(this, &TextureRegionEditor::_zoom_reset));
	zoom_hb->add_child(zoom_reset);

	zoom_in = memnew(Button);
	zoom_in->set_flat(true);
	zoom_in->set_tooltip(TTR("Zoom In"));
	zoom_in->connect("pressed", callable_mp(this, &TextureRegionEditor::_zoom_in));
	zoom_hb->add_child(zoom_in);

	vscroll = memnew(VScrollBar);
	vscroll->set_step(0.001);
	edit_draw->add_child(vscroll);
	vscroll->connect("value_changed", callable_mp(this, &TextureRegionEditor::_scroll_changed));
	hscroll = memnew(HScrollBar);
	hscroll->set_step(0.001);
	edit_draw->add_child(hscroll);
	hscroll->connect("value_changed", callable_mp(this, &TextureRegionEditor::_scroll_changed));

	updating_scroll = false;
	autoslice_is_dirty = true;
}

void TextureRegionEditorPlugin::edit(Object *p_object) {
	region_editor->edit(p_object);
}

bool TextureRegionEditorPlugin::handles(Object *p_object) const {
	return p_object->is_class("Sprite2D") || p_object->is_class("Sprite3D") || p_object->is_class("NinePatchRect") || p_object->is_class("StyleBoxTexture") || p_object->is_class("AtlasTexture");
}

void TextureRegionEditorPlugin::_editor_visiblity_changed() {
	manually_hidden = !region_editor->is_visible_in_tree();
}

void TextureRegionEditorPlugin::make_visible(bool p_visible) {
	if (p_visible) {
		texture_region_button->show();
		bool is_node_configured = region_editor->is_stylebox() || region_editor->is_atlas_texture() || region_editor->is_ninepatch();
		is_node_configured |= region_editor->get_sprite_2d() && region_editor->get_sprite_2d()->is_region_enabled();
		is_node_configured |= region_editor->get_sprite_3d() && region_editor->get_sprite_3d()->is_region_enabled();
		if ((is_node_configured && !manually_hidden) || texture_region_button->is_pressed()) {
			editor->make_bottom_panel_item_visible(region_editor);
		}
	} else {
		if (region_editor->is_visible_in_tree()) {
			editor->hide_bottom_panel();
			manually_hidden = false;
		}
		texture_region_button->hide();
		region_editor->edit(nullptr);
	}
}

Dictionary TextureRegionEditorPlugin::get_state() const {
	Dictionary state;
	state["snap_offset"] = region_editor->snap_offset;
	state["snap_step"] = region_editor->snap_step;
	state["snap_separation"] = region_editor->snap_separation;
	state["snap_mode"] = region_editor->snap_mode;
	return state;
}

void TextureRegionEditorPlugin::set_state(const Dictionary &p_state) {
	Dictionary state = p_state;
	if (state.has("snap_step")) {
		Vector2 s = state["snap_step"];
		region_editor->sb_step_x->set_value(s.x);
		region_editor->sb_step_y->set_value(s.y);
		region_editor->snap_step = s;
	}

	if (state.has("snap_offset")) {
		Vector2 ofs = state["snap_offset"];
		region_editor->sb_off_x->set_value(ofs.x);
		region_editor->sb_off_y->set_value(ofs.y);
		region_editor->snap_offset = ofs;
	}

	if (state.has("snap_separation")) {
		Vector2 sep = state["snap_separation"];
		region_editor->sb_sep_x->set_value(sep.x);
		region_editor->sb_sep_y->set_value(sep.y);
		region_editor->snap_separation = sep;
	}

	if (state.has("snap_mode")) {
		region_editor->_set_snap_mode(state["snap_mode"]);
		region_editor->snap_mode_button->select(state["snap_mode"]);
	}
}

void TextureRegionEditorPlugin::_bind_methods() {
}

TextureRegionEditorPlugin::TextureRegionEditorPlugin(EditorNode *p_node) {
	manually_hidden = false;
	editor = p_node;

	region_editor = memnew(TextureRegionEditor(p_node));
	region_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
	region_editor->hide();
	region_editor->connect("visibility_changed", callable_mp(this, &TextureRegionEditorPlugin::_editor_visiblity_changed));

	texture_region_button = p_node->add_bottom_panel_item(TTR("TextureRegion"), region_editor);
	texture_region_button->hide();
}


text:
require 'spec_helper'

describe Flipper::Echo::Slack::Notifier do
  describe '#channel' do
    it 'is null if options do not include channel' do
      notifier = Flipper::Echo::Slack::Notifier.new('https://hook')

      expect(notifier.channel).to eq(nil)
    end

    it 'returns channel from options without prepending' do
      notifier = Flipper::Echo::Slack::Notifier.new(
        'https://hook', channel: '#custom_channel')

      expect(notifier.channel).to eq('#custom_channel')
    end

    it 'returns channel from options with prepending' do
      notifier = Flipper::Echo::Slack::Notifier.new(
        'https://hook', channel: 'custom_channel')

      expect(notifier.channel).to eq('#custom_channel')
    end
  end

  describe '#username' do
    it 'is default if options do not include username' do
      notifier = Flipper::Echo::Slack::Notifier.new('https://hook')

      expect(notifier.username).to eq('Flipper')
    end

    it 'returns username from options' do
      notifier = Flipper::Echo::Slack::Notifier.new(
        'https://hook', username: 'Custom Username')

      expect(notifier.username).to eq('Custom Username')
    end
  end

  describe '#icon_url' do
    it 'is default if options do not include url' do
      notifier = Flipper::Echo::Slack::Notifier.new('https://hook')

      expect(notifier.icon_url).to match(/flipper\-echo\-icon/)
    end

    it 'returns username from options' do
      notifier = Flipper::Echo::Slack::Notifier.new(
        'https://hook', icon_url: 'custom-icon')

      expect(notifier.icon_url).to eq('custom-icon')
    end
  end

  describe '#notify' do
    let :feature do
      double(:feature, name: :experimental)
    end

    let :event do
      Flipper::Echo::Event.new(feature, :action)
    end

    it 'calls notifier with expected options' do
      notifier = Flipper::Echo::Slack::Notifier.new('https://hook')

      expect(notifier).to receive(:post) do |options|
        expect(options[:text]).to match(/experimental/)
      end

      notifier.notify(event)
    end
  end

  describe '#post' do
    it 'posts expected json payload' do
      notifier = Flipper::Echo::Slack::Notifier.new('https://hook')

      expect(notifier.send(:http)).to receive(:request) do |request|
        expect(request.body).to eq('{"foo":"bar"}')
        expect(request.content_type).to eq('application/json')

        double(:response, code: '200')
      end

      notifier.send(:post, foo: 'bar')
    end

    it 'prints unexpected api response to stderr' do
      notifier = Flipper::Echo::Slack::Notifier.new('https://hook')

      expect(notifier.send(:http)).to(
        receive(:request).and_return(double(:response, code: '404')))

      expect { notifier.send(:post, foo: 'bar') }.to output.to_stderr
    end
  end

  describe '.normalize_channel' do
    it 'is nil for blank name' do
      expect(Flipper::Echo::Slack::Notifier.normalize_channel(nil)).to eq(nil)
      expect(Flipper::Echo::Slack::Notifier.normalize_channel('')).to eq(nil)
    end

    it 'preserves name including number sign' do
      result = Flipper::Echo::Slack::Notifier.normalize_channel('#channel_name')

      expect(result).to eq('#channel_name')
    end

    it 'prepends number sign if necessary' do
      result = Flipper::Echo::Slack::Notifier.normalize_channel('channel_name')

      expect(result).to eq('#channel_name')
    end

    it 'preserves at sign' do
      result = Flipper::Echo::Slack::Notifier.normalize_channel('@channel_name')

      expect(result).to eq('@channel_name')
    end
  end
end


text:
package org.innovateuk.ifs.security;

import org.innovateuk.ifs.alert.resource.AlertResource;
import org.innovateuk.ifs.commons.security.PermissionEntityLookupStrategies;
import org.innovateuk.ifs.commons.security.PermissionEntityLookupStrategy;
import org.innovateuk.ifs.mapper.AlertMapper;
import org.innovateuk.ifs.repository.AlertRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Lookup strategy for {@link AlertResource}, used for permissioning.
 */
@Component
@PermissionEntityLookupStrategies
public class AlertLookupStrategy {

    @Autowired
    private AlertRepository alertRepository;

    @Autowired
    private AlertMapper alertMapper;

    @PermissionEntityLookupStrategy
    public AlertResource getAlertResource(final Long id){
        return alertMapper.mapToResource(alertRepository.findById(id).orElse(null));
    }

}